Este commit está contenido en:
Your Name
2020-05-27 17:44:02 +00:00
padre bff2789ec0
commit ada9dd5fa7
Se han modificado 23 ficheros con 1273 adiciones y 0 borrados

Ver fichero

@@ -0,0 +1,5 @@
FROM node:8-slim
RUN mkdir -p /usr/share/man/man1 && mkdir -p /usr/share/man/man5 && mkdir -p /usr/share/man/man8
RUN apt update && apt -y upgrade && apt -y install python default-jdk build-essential libimage-exiftool-perl && apt clean
USER node
WORKDIR /arjion

Ver fichero

@@ -0,0 +1,17 @@
module.exports = {
mailserver: 'smtp.hatthieves.es',
mailport: 587,
mailsecure: true,
mailfrom: 'webmaster@hatthieves.es',
mailuser: 'webmaster@hatthieves.es',
mailpass: 'w3bm4st3r.',
mailtext: verify_link => 'Verify your account visiting next link: https://meta.hatthieves.es/verify?link=' + verify_link,
mailhtml: verify_link => 'Verify your account visiting next <a href="https://meta.hatthieves.es/verify?link=' + verify_link + '">Verify Link</a>',
indexhost: 'https://elastic.hatthieves.es',
indexuser: 'docker',
indexpass: 'docker',
index: 'arjion',
type: 'user',
port: 3000,
anonymous: 'anonymous'
}

Ver fichero

@@ -0,0 +1,2 @@
#!/bin/bash
yarn && node server

Ver fichero

@@ -0,0 +1,108 @@
module.exports = (config, client6) => {
(async () => {
const exist = await client6.indices.exists({
index: config.index
})
if (!exist.body) {
await client6.indices.create({
index: config.index,
body: {
settings: {
number_of_shards: 1,
number_of_replicas: 0
},
mappings: {
user: {
dynamic_templates: [
{
metadata_as_keywords: {
path_match: 'documents.metadata.*',
mapping: {
type: 'keyword'
}
}
}
],
properties: {
date: {
type: 'date',
format: 'dd-MM-yyyy HH:mm:ss||dd-MM-yyyy||epoch_millis'
},
user: {
type: 'text',
fielddata: true
},
password: {
type: 'keyword'
},
email: {
type: 'text'
},
verified: {
type: 'boolean'
},
verify_link: {
type: 'text'
},
locked: {
type: 'boolean'
},
documents: {
type: 'nested',
properties: {
date: {
type: 'date',
format: 'dd-MM-yyyy HH:mm:ss||dd-MM-yyyy||epoch_millis'
},
originalname: {
type: 'text'
},
filename: {
type: 'text'
},
path: {
type: 'text'
},
mimetype: {
type: 'text'
},
size: {
type: 'long'
},
metadata: {
type: 'object'
},
language: {
type: 'text'
},
sha256: {
type: 'text'
},
deleted: {
type: 'boolean'
}
}
}
}
}
}
}
}).catch(err => console.error)
await client6.index({
index: config.index,
type: config.type,
body: {
date: Date.now(),
user: config.anonymous,
password: null,
email: null,
verified: true,
verify_link: null,
documents: [],
locked: false
},
refresh: true
}).catch(err => console.error)
}
})()
}

Ver fichero

@@ -0,0 +1,210 @@
module.exports = (router, passport, config, client6, upload, fs, tika, promisify, crypto, transporter) => {
router.get('/', (req, res) => {
return res.render('home', { user: req.user })
})
router.get('/login', (req, res) => {
return res.render('login', { error: req.flash('error') })
})
router.post('/login', passport.authenticate('local', { failWithError: true, successRedirect: '/' }), (err, req, res, next) => {
if (err) {
req.flash('error', err.message)
return res.redirect('/login')
}
})
router.get('/logout', (req, res) => {
req.logout()
return res.redirect('/')
})
router.get('/register', (req, res) => {
return res.render('register', { error: req.flash('error') })
})
router.post('/register', (req, res, next) => {
if (req.body.email === req.body.email2) {
if (req.body.username.length >= 4) {
client6.search({
index: config.index,
body: {
query: {
match: {
user: req.body.username
}
}
}
}, (err, user) => {
if (err) {
return next(err)
}
else if (user.body.hits.total === 0) {
if (req.body.password.length > 6) {
if (req.body.email.match(/\S+@\S+\.\S+/)) {
const verify_link = crypto.createHash('sha256').update(Date.now().toString()).digest('hex')
transporter.sendMail({
from: config.mailfrom,
to: req.body.email,
subject: 'Verify link',
text: config.mailtext(verify_link),
html: config.mailhtml(verify_link)
}, async (error, info) => {
if (error) {
return next(err)
} else {
await client6.index({
index: config.index,
type: config.type,
body: {
date: Date.now(),
user: req.body.username,
password: crypto.createHash('sha256').update(req.body.password).digest('base64'),
email: req.body.email,
verified: false,
verify_link: verify_link,
documents: [],
locked: false
},
refresh: true
}).catch(err => {
return next(err)
})
return res.render('verify', { message: info })
}
})
} else {
req.flash('error', 'Email not valid')
return res.redirect('/register')
}
} else {
req.flash('error', 'Password must be more than 6 chars')
return res.redirect('/register')
}
} else {
req.flash('error', 'User exists')
return res.redirect('/register')
}
})
} else {
req.flash('error', 'Username must be 4 or more chars')
return res.redirect('/register')
}
} else {
req.flash('error', 'Email must be the same and real')
return res.redirect('/register')
}
})
router.get('/verify', (req, res, next) => {
if (req.query.link && req.query.link.length === 64 && req.query.link.match(/^[0-9a-f]{64}$/g)) {
client6.search({
index: config.index,
body: {
query: {
match: {
verify_link: req.query.link
}
}
}
}, async (err, user) => {
if (err) {
return next(err)
} else if (user.body.hits.total === 1 && !user.body.hits.hits[0]._source.verified) {
await client6.update({
index: config.index,
type: config.type,
id: user.body.hits.hits[0]._id,
body: {
doc: {
verified: true
}
}
}).catch(err => next(err))
return res.redirect('/login')
} else {
req.flash('error', 'Invalid user')
return res.redirect('/login')
}
})
} else {
req.flash('error', 'Invalid link')
return res.redirect('/login')
}
})
router.get('/upload', (req, res) => {
return res.render('upload')
})
router.post('/upload', upload.array('documents', 6), (req, res, next) => {
client6.search({
index: config.index,
body: {
query: {
match: {
user: config.anonymous
}
}
}
}, async (err, user) => {
if (err) {
return next(err)
} else if (user.body.hits.total === 1 && !user.body.hits.hits[0]._source.locked) {
const files = req.files.map(file => {
return promisify(tika.language)(fs.readFileSync(__dirname + '/../' + file.path).toString()).then((language, reasonablyCertain) => {
return promisify(tika.meta)(__dirname + '/../' + file.path, { contentType: file.mimetype }).then(async meta => {
delete meta['X-Parsed-By']
delete meta.resourceName
await client6.update({
index: config.index,
type: config.type,
id: user.body.hits.hits[0]._id,
body: {
script: {
source: 'ctx._source.documents.add(params.document)',
params: {
document: {
date: Date.now(),
originalname: file.originalname,
filename: file.filename,
path: file.path,
mimetype: file.mimetype,
size: file.size,
metadata: meta,
language: !file.mimetype.match(/^image/) ? language : null,
sha256: crypto.createHash('sha256').update(fs.readFileSync(__dirname + '/../' + file.path)).digest('hex'),
deleted: false
}
}
}
},
refresh: 'wait_for',
retry_on_conflict: 6
}).catch(err => next(err))
return {
name: file.originalname,
size: file.size,
mime: file.mimetype,
language: !file.mimetype.match(/^image/) ? language : null,
filename: file.filename,
metadata: meta
}
}).catch(err => next(err))
}).catch(err => next(err))
})
Promise.all(files).then(data => {
return res.render('file', { documents: data })
})
} else {
return next(new Error('File trouble'))
}
})
})
router.get('/error', (req, res) => {
return res.render('error')
})
return router
}

Ver fichero

@@ -0,0 +1,62 @@
module.exports = (passport, Strategy, crypto, config, client6) => {
passport.use(new Strategy((username, password, cb) => {
if (username && password) {
client6.search({
index: config.index,
body: {
query: {
match: {
user: username
}
}
}
}, (err, user) => {
if (err) {
return cb(err)
}
if (!user || !user.body) {
return cb(null, false)
}
if (user.body.hits.total === 1 && user.body.hits.hits[0]._source.verified && !user.body.hits.hits[0]._source.locked
&& user.body.hits.hits[0]._source.password === crypto.createHash('sha256').update(password).digest('base64')) {
return cb(null, {
id: user.body.hits.hits[0]._id,
user: user.body.hits.hits[0]._source.user,
email: user.body.hits.hits[0]._source.email
})
}
else {
return cb(null, false)
}
})
} else {
return cb(null, false)
}
}))
passport.serializeUser((user, cb) => {
cb(null, user.user)
})
passport.deserializeUser(async (username, cb) => {
client6.search({
index: config.index,
body: {
query: {
match: {
user: username
}
}
}
}, (err, user) => {
if (err) {
return cb(err)
}
return cb(null, {
id: user.body.hits.hits[0]._id,
user: user.body.hits.hits[0]._source.user,
email: user.body.hits.hits[0]._source.email
})
})
})
}

Ver fichero

@@ -0,0 +1,236 @@
module.exports = (router, config, client6, upload, fs, tika, promisify, crypto, exiftool) => {
router.get('/profile', (req, res) => {
return res.render('profile', { user: req.user })
})
router.get('/upload', (req, res) => {
return res.render('upload', { user: req.user })
})
router.post('/upload', upload.array('documents', 6), (req, res, next) => {
const files = req.files.map(file => {
return promisify(tika.language)(fs.readFileSync(__dirname + '/../' + file.path).toString()).then((language, reasonablyCertain) => {
return promisify(tika.meta)(__dirname + '/../' + file.path, { contentType: file.mimetype }).then(async meta => {
delete meta['X-Parsed-By']
delete meta.resourceName
await client6.update({
index: config.index,
type: config.type,
id: req.user.id,
body: {
script: {
source: 'ctx._source.documents.add(params.document)',
params: {
document: {
date: Date.now(),
originalname: file.originalname,
filename: file.filename,
path: file.path,
mimetype: file.mimetype,
size: file.size,
metadata: meta,
language: !file.mimetype.match(/^image/) ? language : null,
sha256: crypto.createHash('sha256').update(fs.readFileSync(__dirname + '/../' + file.path)).digest('hex'),
deleted: false
}
}
}
},
refresh: 'wait_for',
retry_on_conflict: 6
}).catch(err => next(err))
return {
name: file.originalname,
size: file.size,
mime: file.mimetype,
language: !file.mimetype.match(/^image/) ? language : null,
filename: file.filename,
metadata: meta
}
}).catch(err => next(err))
}).catch(err => next(err))
})
Promise.all(files).then(data => {
return res.render('file', { documents: data, user: req.user })
})
})
router.get('/download-meta', (req, res, next) => {
if (req.query.file && req.query.file.length === 32 && req.query.file.match(/^[0-9a-f]{32}$/g)) {
client6.search({
index: config.index,
body: {
query: {
nested: {
path: 'documents',
query: {
match: {
'documents.filename': req.query.file
}
}
}
}
}
}, async (err, user) => {
if (err) {
return next(err)
} else if (user.body.hits.total === 1 && !user.body.hits.hits[0]._source.locked) {
const document = user.body.hits.hits[0]._source.documents.filter(document => document.filename === req.query.file)[0]
fs.exists(__dirname + '/../' + document.path, exists => {
if (exists) {
const ep = new exiftool.ExiftoolProcess('/usr/bin/exiftool'),
read = fs.createReadStream(__dirname + '/../' + document.path),
write = fs.createWriteStream(`/tmp/${document.filename}`)
res.set('Content-Disposition', `attachment; filename="${document.originalname}"`)
res.set('Content-Type', 'application/octet-stream')
read.on('error', err => next(err))
write.on('error', err => next(err))
write.on('finish', () => {
ep
.open()
.then(() => {
ep.writeMetadata(`/tmp/${document.filename}`, { all: '' }, ['overwrite_original'])
})
.then(console.log, err => next(err))
.then(() => {
ep.close()
const nometa = fs.createReadStream(`/tmp/${document.filename}`)
nometa.on('error', err => next(err))
nometa.pipe(res)
})
.catch(err => next(err))
})
read.pipe(write)
} else {
return next(new Error('File not exists'))
}
})
} else {
return next(new Error('File trouble'))
}
})
} else {
return next(new Error('Is not a file'))
}
})
router.get('/download', (req, res, next) => {
if (req.query.file && req.query.file.length === 32 && req.query.file.match(/^[0-9a-f]{32}$/g)) {
client6.search({
index: config.index,
body: {
query: {
nested: {
path: 'documents',
query: {
match: {
'documents.filename': req.query.file
}
}
}
}
}
}, async (err, user) => {
if (err) {
return next(err)
} else if (user.body.hits.total === 1 && !user.body.hits.hits[0]._source.locked) {
const document = user.body.hits.hits[0]._source.documents.filter(document => document.filename === req.query.file)[0]
fs.exists(__dirname + '/../' + document.path, exists => {
if (exists) {
res.set('Content-Disposition', `attachment; filename="${document.originalname}"`)
res.set('Content-Type', 'application/octet-stream')
fs.createReadStream(__dirname + '/../' + document.path).pipe(res)
} else {
return next(new Error('File not exists'))
}
})
} else {
return next(new Error('File trouble'))
}
})
} else {
return next(new Error('Is not a file'))
}
})
router.get('/files', (req, res, next) => {
if (req.user && req.user.user) {
client6.search({
index: config.index,
body: {
query: {
match: {
user: req.user.user
}
}
}
}, async (err, user) => {
if (err) {
return next(err)
} else if (user.body.hits.total === 1 && !user.body.hits.hits[0]._source.locked) {
const documents = user.body.hits.hits[0]._source.documents
res.render('files', {
documents: documents.filter(document => !document.deleted).map(document => {
return {
name: document.originalname,
size: document.size,
mime: document.mimetype,
language: document.language,
filename: document.filename,
metadata: document.metadata
}
}),
user: req.user
})
}
})
} else {
return next(new Error('User required'))
}
})
router.get('/deletefile', (req, res, next) => {
if (req.query.file && req.query.file.length === 32 && req.query.file.match(/^[0-9a-f]{32}$/g)) {
client6.search({
index: config.index,
body: {
query: {
nested: {
path: 'documents',
query: {
match: {
'documents.filename': req.query.file
}
}
}
}
}
}, async (err, user) => {
if (err) {
return next(err)
} else if (user.body.hits.total === 1 && !user.body.hits.hits[0]._source.locked) {
await client6.update({
index: config.index,
type: config.type,
id: user.body.hits.hits[0]._id,
body: {
script: {
lang: 'painless',
source: 'for (d in ctx._source.documents) { if (d.filename == "' + req.query.file + '") { d.deleted = true }}'
}
},
refresh: 'wait_for',
retry_on_conflict: 6
}).catch(err => next(err))
return res.redirect('/user/files')
} else {
return next(new Error('File trouble'))
}
})
} else {
return next(new Error('File required'))
}
})
return router
}

Ver fichero

@@ -0,0 +1,35 @@
{
"name": "arjion",
"version": "1.0.0",
"description": "Arjion HatMeta",
"main": "server.js",
"scripts": {
"install": "cp -r node_modules/material-components-web/dist/material-components-web.min.css* node_modules/material-icons/iconfont public/css/ && cp node_modules/material-components-web/dist/material-components-web.min.js public/js/"
},
"keywords": [
"arjion",
"metadata"
],
"author": "ale & gus",
"license": "MIT",
"dependencies": {
"body-parser": "*",
"connect-ensure-login": "*",
"connect-flash": "*",
"ejs": "*",
"es6": "npm:@elastic/elasticsearch@^6.8.0",
"es6-promisify": "*",
"express": "*",
"express-session": "*",
"material-components-web": "*",
"material-icons": "*",
"morgan": "*",
"multer": "*",
"node-exiftool": "*",
"nodemailer": "*",
"passport": "*",
"passport-local": "*",
"rotating-file-stream": "^1.4.6",
"tika": "*"
}
}

Ver fichero

@@ -0,0 +1,43 @@
body {
margin: 0 auto;
}
.index {
color: white;
text-decoration: none;
}
.mdc-card {
margin: 1rem;
}
.mdc-layout-grid__cell {
text-align: center;
margin: 0 auto;
width: 650px;
}
.collapsible:after {
content: "\0002B"; /* Unicode character for "plus" sign (+) */
font-size: 13px;
color: white;
float: right;
margin-left: 5px;
}
.active:after {
content: "\2212"; /* Unicode character for "minus" sign (-) */
}
.content {
padding: 0 18px;
background-color: white;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
:root {
--mdc-theme-primary: black;
--mdc-theme-secondary: black;
}

Ver fichero

@@ -0,0 +1,46 @@
window.onload = function () {
new mdc.topAppBar.MDCTopAppBar(document.querySelector('.mdc-top-app-bar'))
var menu = new mdc.menu.MDCMenu(document.querySelector('.mdc-menu'))
menu.setAnchorCorner(mdc.menuSurface.Corner.BOTTOM_LEFT)
document.getElementById('menu-button').addEventListener('click', function (event) {
menu.open = !menu.open
})
document.querySelectorAll('.mdc-text-field').forEach(function (field) {
mdc.textField.MDCTextField.attachTo(field)
})
document.querySelectorAll('.mdc-icon-button').forEach(function (field) {
new mdc.iconButton.MDCIconButtonToggle(field)
})
if (document.querySelector('.collapsible')) {
document.querySelectorAll('.collapsible').forEach(function (col) {
col.addEventListener('click', function () {
this.classList.toggle('active')
var content = this.parentNode.parentNode.nextElementSibling.nextElementSibling
if (content.style.maxHeight) {
content.style.maxHeight = null
} else {
content.style.maxHeight = content.scrollHeight + "px"
}
})
})
}
if (document.querySelector('.mdc-dialog')) {
var dialog = new mdc.dialog.MDCDialog(document.querySelector('.mdc-dialog'))
if (document.querySelector('.mdc-dialog #delete')) {
document.querySelectorAll('.delete').forEach(function (d) {
d.addEventListener('click', function (event) {
document.body.setAttribute('data-selected', this.getAttribute('data-filename'))
dialog.open()
})
})
document.querySelector('.opened').addEventListener('click', function (event) {
document.body.getAttribute('data-selected') ? window.location.href = '/user/deletefile?file=' + document.body.getAttribute('data-selected') : ''
})
document.querySelector('.closed').addEventListener('click', function (event) {
dialog.close()
})
} else if (document.querySelector('.mdc-dialog #error')) {
dialog.open()
}
}
}

Ver fichero

@@ -0,0 +1,79 @@
const express = require('express'),
passport = require('passport'),
Strategy = require('passport-local').Strategy,
crypto = require('crypto'),
fs = require('fs'),
config = require('./config'),
rfs = require('rotating-file-stream'),
accessLogStream = rfs('access.log', {
interval: '1d', // rotate daily
path: __dirname + '/log'
}),
upload = require('multer')({
dest: 'uploads/',
limits: {
fileSize: 5 * 1024 * 1024 // 5Mb
}
}),
tika = require('tika'),
flash = require('connect-flash'),
promisify = require("es6-promisify").promisify,
nodemailer = require('nodemailer'),
transporter = nodemailer.createTransport({
host: config.mailserver,
port: config.mailport,
secure: config.mailsecure,
auth: {
user: config.mailuser,
pass: config.mailpass
}
}),
{ Client: Client6 } = require('es6'),
client6 = new Client6({
node: config.indexhost,
auth: {
username: config.indexuser,
password: config.indexpass
}
}),
exiftool = require('node-exiftool');
// Initialize elastic
require('./lib/elastic')(config, client6)
// Server
const app = express(),
server = app.disable('x-powered-by').listen(config.port, () => {
console.log(`Listening on ${server.address().address}:${server.address().port}`)
})
app.set('trust proxy', 1)
app.set('views', __dirname + '/views')
app.set('view engine', 'ejs')
// Middleware
app.use(require('morgan')('combined', { stream: accessLogStream }))
app.use(require('body-parser').json())
app.use(require('body-parser').urlencoded({ extended: false }))
app.use(require('express-session')({ secret: 'keyboard hat', resave: false, saveUninitialized: true }))
app.use(flash())
// Auth
require('./lib/passport')(passport, Strategy, crypto, config, client6)
app.use(passport.initialize())
app.use(passport.session({ cookie: { maxAge: 43200 } })) // 12H
// Routes
app.use('/', require('./lib/index')(express.Router(), passport, config, client6, upload, fs, tika, promisify, crypto, transporter))
app.use('/user', require('connect-ensure-login').ensureLoggedIn('/login'), require('./lib/user')(express.Router(), config, client6, upload, fs, tika, promisify, crypto, exiftool))
// Resources
app.use('/css', express.static(__dirname + '/public/css'))
app.use('/js', express.static(__dirname + '/public/js'))
// Errors
app.use((err, req, res, next) => {
res.status(500)
return res.render('error', { error: err.message })
})

Ver fichero

@@ -0,0 +1,5 @@
<%- include('header') %>
<h3>Error: <%= locals.error %>, go <button class="mdc-button mdc-button--raised"
onclick="window.location.href=document.referrer"><span class="mdc-button__ripple"></span><span
class="mdc-button__text">Back</span></button></h3>
<%- include('footer') %>

Ver fichero

@@ -0,0 +1,78 @@
<%- include('header') %>
<% if (locals.documents && locals.documents.length > 0) { %>
<% documents.map(document => {
var chips = [] %>
<div class="mdc-card">
<div class="mdc-card__primary-action" tabindex="0">
<ul class="mdc-list">
<li class="mdc-list-item" tabindex="0">
<h3 class="mdc-list-item__text"><%= document.name %></h3>
</li>
<li role="separator" class="mdc-list-divider"></li>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Size: <%= document.size %></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Mime: <%= document.mime %></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
<% if (document.language) { %>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Language: <%= document.language %></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
<% } if (document.metadata) { %>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Metadata: <button type="button"
class="collapsible mdc-button mdc-button--raised">
<div class="mdc-button__ripple"></div>
</button></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
<ul class="content">
<% for(var i=0; i < Object.keys(document.metadata).length; i++) { %>
<li class="mdc-list-item" <% if (i === 0) { %> tabindex="0" <% } %>>
<span class="mdc-list-item__text"><%= Object.keys(document.metadata)[i] %>:
<%= Object.keys(document.metadata).length > 0 ? document.metadata[Object.keys(document.metadata)[i]].join(', ') : '' %></span>
</li>
<% chips.push(Object.keys(document.metadata)[i]) %>
<% } %>
</ul>
<li role="separator" class="mdc-list-divider"></li>
<div class="mdc-chip-set" role="grid">
<% chips.map(chip => { %>
<div class="mdc-chip" role="row">
<div class="mdc-chip__ripple"></div>
<span role="gridcell">
<span role="button" <% if (i === 0) { %> tabindex="0" <% } %> class="mdc-chip__primary-action">
<span class="mdc-chip__text"><%=chip %></span>
</span>
</span>
</div>
<% }) %>
</div>
<% } %>
</ul>
</div>
</div>
<% })
} else { %>
<div class="mdc-dialog">
<div class="mdc-dialog__container">
<div class="mdc-dialog__surface" role="alertdialog" aria-modal="true" aria-labelledby="error"
aria-describedby="error-content">
<h2 class="mdc-dialog__title" id="error">Error</h2>
<div class="mdc-dialog__content" id="error-content">
<ul class="mdc-list mdc-list--avatar-list">
<li class="mdc-list-item" tabindex="0" data-mdc-dialog-action="none">
<span class="mdc-list-item__text">NO Documents</span>
</li>
</ul>
</div>
</div>
</div>
<div class="mdc-dialog__scrim"></div>
</div>
<% } %>
<%- include('footer') %>

Ver fichero

@@ -0,0 +1,117 @@
<%- include('header') %>
<% if (locals.documents && locals.documents.length > 0) { %>
<% documents.map(document => {
var chips = [] %>
<div class="mdc-card">
<div class="mdc-card__primary-action" tabindex="0">
<ul class="mdc-list">
<li class="mdc-list-item" tabindex="0">
<h3 class="mdc-list-item__text"><%= document.name %></h3>
</li>
<li role="separator" class="mdc-list-divider"></li>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Size: <%= document.size %></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Mime: <%= document.mime %></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
<% if (document.language) { %>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Language: <%= document.language %></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
<% } if (document.metadata) { %>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Metadata:
<button type="button" class="collapsible mdc-button mdc-button--raised">
<div class="mdc-button__ripple"></div>
</button></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
<ul class="content">
<% for(var i=0; i < Object.keys(document.metadata).length; i++) { %>
<li class="mdc-list-item" <% if (i === 0) { %> tabindex="0" <% } %>>
<span class="mdc-list-item__text"><%= Object.keys(document.metadata)[i] %>:
<%= Object.keys(document.metadata).length > 0 ? document.metadata[Object.keys(document.metadata)[i]].join(', ') : '' %></span>
</li>
<% chips.push(Object.keys(document.metadata)[i]) %>
<% } %>
</ul>
<li role="separator" class="mdc-list-divider"></li>
<div class="mdc-chip-set" role="grid">
<% chips.map(chip => { %>
<div class="mdc-chip" role="row">
<div class="mdc-chip__ripple"></div>
<span role="gridcell">
<span role="button" <% if (i === 0) { %> tabindex="0" <% } %> class="mdc-chip__primary-action">
<span class="mdc-chip__text"><%=chip %></span>
</span>
</span>
</div>
<% }) %>
</div>
<li role="separator" class="mdc-list-divider"></li>
<% } %>
</ul>
</div>
<div class="mdc-card__actions">
<div class="mdc-card__action-buttons">
<button class="mdc-button mdc-button--raised mdc-card__action mdc-card__action--button"
onclick="window.location.href='/user/download?file=<%= document.filename %>'"><span
class="mdc-button__ripple"></span><span class="mdc-button__text">Download</span></button>
<button class="mdc-button mdc-button--raised mdc-card__action mdc-card__action--button"
onclick="window.location.href='/user/download-meta?file=<%= document.filename %>'"><span
class="mdc-button__ripple"></span><span class="mdc-button__text">Download with NO
Metadata</span></button>
</div>
<div class="mdc-card__action-icons">
<button class="mdc-button material-icons mdc-icon-button mdc-card__action mdc-card__action--icon delete"
title="Delete" data-filename="<%= document.filename %>">delete</button>
</div>
</div>
</div>
<% }) %>
<div class="mdc-dialog">
<div class="mdc-dialog__container">
<div class="mdc-dialog__surface" role="alertdialog" aria-modal="true" aria-labelledby="delete"
aria-describedby="delete-content">
<h2 class="mdc-dialog__title" id="delete">Delete file?</h2>
<div class="mdc-dialog__content" id="delete-content">
You will can NOT recover it...
</div>
<footer class="mdc-dialog__actions">
<button type="button" class="mdc-button mdc-dialog__button closed" data-mdc-dialog-action="No"
data-mdc-dialog-button-default>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">No</span>
</button>
<button type="button" class="mdc-button mdc-dialog__button opened" data-mdc-dialog-action="Yes">
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Yes</span>
</button>
</footer>
</div>
</div>
<div class="mdc-dialog__scrim"></div>
</div>
<% } else { %>
<div class="mdc-dialog">
<div class="mdc-dialog__container">
<div class="mdc-dialog__surface" role="alertdialog" aria-modal="true" aria-labelledby="error"
aria-describedby="error-content">
<h2 class="mdc-dialog__title" id="error">Error</h2>
<div class="mdc-dialog__content" id="error-content">
<ul class="mdc-list mdc-list--avatar-list">
<li class="mdc-list-item" tabindex="0" data-mdc-dialog-action="none">
<span class="mdc-list-item__text">NO Documents</span>
</li>
</ul>
</div>
</div>
</div>
<div class="mdc-dialog__scrim"></div>
</div>
<% } %>
<%- include('footer') %>

Ver fichero

@@ -0,0 +1,6 @@
</div>
</div>
</div>
</body>
</html>

Ver fichero

@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/css/material-components-web.min.css">
<link rel="stylesheet" type="text/css" href="/css/iconfont/material-icons.css">
<link rel="stylesheet" type="text/css" href="/css/main.css">
<script type="text/javascript" src="/js/material-components-web.min.js"></script>
<script type="text/javascript" src="/js/main.js"></script>
</head>
<body>
<header class="mdc-top-app-bar mdc-top-app-bar--fixed">
<div class="mdc-top-app-bar__row">
<section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start">
<a href="/" class="index">
<h2 class="mdc-top-app-bar__title"><span class="material-icons">school</span> HatMeta</h2>
</a>
</section>
<section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-end">
<div id="toolbar" class="toolbar mdc-menu-surface--anchor mdc-top-app-bar__action-item">
<button id="menu-button"
class="material-icons mdc-top-app-bar__navigation-icon mdc-icon-button">more_vert</button>
<div class="mdc-menu mdc-menu-surface">
<ul class="mdc-list" role="menu" aria-hidden="true" aria-orientation="vertical" tabindex="-1">
<% if (!locals.user) { %>
<li class="mdc-list-item" role="menuitem">
<button class="mdc-button" aria-label="login" onclick="window.location.href='/login'">
<span class="material-icons mdc-button__icon">perm_identity</span><span
class="mdc-button__label">LOGIN</span>
</button>
</li>
<li class="mdc-list-item" role="menuitem">
<button class="mdc-button" aria-label="upload"
onclick="window.location.href='/upload'"><span
class="material-icons mdc-button__icon">cloud_upload</span><span
class="mdc-button__label">UPLOAD</span></button>
</li>
<% } else { %>
<li class="mdc-list-item" role="menuitem">
<button class="mdc-button" aria-label="profile"
onclick="window.location.href='/user/profile'"><span
class="material-icons mdc-button__icon">perm_identity</span><span
class="mdc-button__label">PROFILE</span></button>
</li>
<li class="mdc-list-item" role="menuitem">
<button class="mdc-button" aria-label="folder"
onclick="window.location.href='/user/files'"><span
class="material-icons mdc-button__icon">folder</span><span
class="mdc-button__label">FOLDER</span></button>
</li>
<li class="mdc-list-item" role="menuitem">
<button class="mdc-button" aria-label="upload"
onclick="window.location.href='/user/upload'"><span
class="material-icons mdc-button__icon">cloud_upload</span><span
class="mdc-button__label">UPLOAD</span></button>
</li>
<li class="mdc-list-item" role="menuitem">
<button class="mdc-button" aria-label="logout"
onclick="window.location.href='/logout'"><span
class="material-icons mdc-button__icon">exit_to_app</span><span
class="mdc-button__label">LOGOUT</span></button>
</li>
<% } %>
</ul>
</div>
</div>
</section>
</div>
</header>
<br><br><br><br>
<div class="mdc-layout-grid">
<div class="mdc-layout-grid__inner">
<div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">

Ver fichero

@@ -0,0 +1,14 @@
<%- include('header') %>
<% if (!locals.user) { %>
<h3>Welcome! Please <button class="mdc-button mdc-button--raised" onclick="window.location.href='/login'"><span
class="mdc-button__ripple"></span><span class="mdc-button__text">Login</span></button> or
<button class="mdc-button mdc-button--raised" onclick="window.location.href='/register'"><span
class="mdc-button__ripple"></span><span class="mdc-button__text">Register</span></button></h3>
<% } else { %>
<h3>Hello, <%= locals.user.user %>. View your <button class="mdc-button mdc-button--raised"
onclick="window.location.href='/user/profile'"><span class="mdc-button__ripple"></span><span
class="mdc-button__text">Profile</span></button> or view your <button class="mdc-button mdc-button--raised"
onclick="window.location.href='/user/files'"><span class="mdc-button__ripple"></span><span
class="mdc-button__text">Folder</span></button></h3>
<% } %>
<%- include('footer') %>

Ver fichero

@@ -0,0 +1,36 @@
<%- include('header') %>
<form action="/login" method="post">
<div class="mdc-text-field">
<input class="mdc-text-field__input" id="username" name="username" type="text" required>
<div class="mdc-line-ripple"></div>
<label for="username" class="mdc-floating-label">Username</label>
</div>
<div class="mdc-text-field">
<input class="mdc-text-field__input" id="password" name="password" type="password" required>
<div class="mdc-line-ripple"></div>
<label for="password" class="mdc-floating-label">Password</label>
</div>
<br><br>
<div>
<button class="mdc-button mdc-button--raised" type="submit"><span class="mdc-button__ripple"></span>Login</button>
</div>
</form>
<% if (locals.error && locals.error.length > 0) { %>
<div class="mdc-dialog">
<div class="mdc-dialog__container">
<div class="mdc-dialog__surface" role="alertdialog" aria-modal="true" aria-labelledby="error"
aria-describedby="error-content">
<h2 class="mdc-dialog__title" id="error">Error</h2>
<div class="mdc-dialog__content" id="error-content">
<ul class="mdc-list mdc-list--avatar-list">
<li class="mdc-list-item" tabindex="0" data-mdc-dialog-action="none">
<span class="mdc-list-item__text"><%= locals.error %></span>
</li>
</ul>
</div>
</div>
</div>
<div class="mdc-dialog__scrim"></div>
</div>
<% } %>
<%- include('footer') %>

Ver fichero

@@ -0,0 +1,15 @@
<%- include('header') %>
<ul class="mdc-list">
<li class="mdc-list-item" tabindex="0">
<span class="mdc-list-item__text">User: <%= locals.user.user %></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
<li class="mdc-list-item">
<span class="mdc-list-item__text">Email: <%= locals.user.email %></span>
</li>
<li role="separator" class="mdc-list-divider"></li>
</ul>
<br><br>
<button class="mdc-button mdc-button--raised" onclick="window.location.href='/logout'"><span
class="mdc-button__ripple"></span><span class="mdc-button__text">Logout</span></button>
<%- include('footer') %>

Ver fichero

@@ -0,0 +1,32 @@
<%- include('header') %>
<form action="/register" method="post" onsubmit="return checkEmail(this)">
<div class="mdc-text-field">
<input class="mdc-text-field__input" id="username" name="username" type="text" required>
<div class="mdc-line-ripple"></div>
<label for="username" class="mdc-floating-label">Username</label>
</div>
<div class="mdc-text-field">
<input class="mdc-text-field__input" id="password" name="password" type="password" required>
<div class="mdc-line-ripple"></div>
<label for="password" class="mdc-floating-label">Password</label>
</div>
<div class="mdc-text-field">
<input class="mdc-text-field__input" id="email" name="email" type="email" required>
<div class="mdc-line-ripple"></div>
<label for="email" class="mdc-floating-label">Email</label>
</div>
<div class="mdc-text-field">
<input class="mdc-text-field__input" id="email2" name="email2" type="email" required>
<div class="mdc-line-ripple"></div>
<label for="email2" class="mdc-floating-label">Confirm Email</label>
</div>
<br><br>
<div>
<button class="mdc-button mdc-button--raised" type="submit"><span
class="mdc-button__ripple"></span>Submit</button>
</div>
</form>
<% if (locals.error && locals.error.length > 0) { %>
<p>Error: <%= locals.error %></p>
<% } %>
<%- include('footer') %>

Ver fichero

@@ -0,0 +1,13 @@
<%- include('header') %>
<form action="<%= !locals.user ? '/upload' : '/user/upload' %>" method="post" enctype="multipart/form-data">
<div class="mdc-text-field">
<input class="mdc-text-field__input" id="documents" name="documents" type="file" multiple required>
<div class="mdc-line-ripple"></div>
</div>
<br><br>
<div>
<button class="mdc-button mdc-button--raised" type="submit"><span
class="mdc-button__ripple"></span>Upload</button>
</div>
</form>
<%- include('footer') %>

Ver fichero

@@ -0,0 +1,14 @@
<%- include('header') %>
<% if (locals.message) {
if(message.accepted.length > 0) {
%>
<h3>Visit your email to activate your account <button class="mdc-button mdc-button--raised"
onclick="window.location.href='/login'"><span class="mdc-button__ripple"></span><span
class="mdc-button__text">Login</span></button></h3>
<% } else { %>
<h3>Error sending email, go <button class="mdc-button mdc-button--raised"
onclick="window.location.href=document.referrer"><span class="mdc-button__ripple"></span><span
class="mdc-button__text">Back</span></button></h3>
<% }
} %>
<%- include('footer') %>

Ver fichero

@@ -0,0 +1,25 @@
version: '2'
services:
arjion:
build: ./arjion
container_name: arjion
hostname: arjion
restart: always
entrypoint:
- /bin/bash
- /arjion/entrypoint.sh
volumes:
- ./arjion:/arjion
expose:
- 3000
networks:
mynet:
ipv4_address: 172.134.0.101
networks:
mynet:
driver: bridge
ipam:
config:
- subnet: 172.134.0.0/24