parent
88beaa6f7d
commit
d6f553e99f
|
@ -0,0 +1,20 @@
|
|||
<% let expirationDate = null; %>
|
||||
<p>Hello <%=user.name%>,</p>
|
||||
<p>The following files have been assigned from <%=prevUser.name%> to you<%=prevUser.userId !== assignUser.userId ? ' by ' + assignUser.name : ''%>:</p>
|
||||
<ul>
|
||||
<% uploads.forEach((upload) => { %>
|
||||
<% upload.files.forEach((file) => { %>
|
||||
<li><%=file.name%> (<%=humanFileSize(file.size, true)%>)</li>
|
||||
<%
|
||||
});
|
||||
|
||||
if (upload.expirationDate !== null && (expirationDate === null || upload.expirationDate < expirationDate))
|
||||
expirationDate = upload.expirationDate;
|
||||
%>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<% if (expirationDate !== null) { %>
|
||||
These files will be automatically deleted after <%=expirationDate.toLocaleString('en-US')%>
|
||||
<% } %>
|
||||
<p>You can download these files by logging in to <a href="<%=adminUrl%>"><%=adminUrl%></a></p>
|
||||
<p>Cheers,<br />Recv</p>
|
|
@ -0,0 +1 @@
|
|||
📄 <%=fileCount%> file<%=fileCount != 1 ? 's have' : ' has'%> been assigned to you
|
|
@ -1,36 +1,12 @@
|
|||
<%
|
||||
function humanFileSize(bytes, si)
|
||||
{
|
||||
var thresh = si ? 1000 : 1024;
|
||||
if(Math.abs(bytes) < thresh)
|
||||
{
|
||||
return bytes + ' B';
|
||||
}
|
||||
|
||||
var units = si
|
||||
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
|
||||
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
|
||||
var u = -1;
|
||||
|
||||
do
|
||||
{
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
}
|
||||
while(Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||
|
||||
return bytes.toFixed(1) + ' ' + units[u];
|
||||
}
|
||||
%>
|
||||
<p>Hello <%= user.name %>,</p>
|
||||
<p>The following files have just been uploaded:</p>
|
||||
<p>Hello <%=user.name%>,</p>
|
||||
<p>The following files have been uploaded:</p>
|
||||
<ul>
|
||||
<% upload.files.forEach((file) => { %>
|
||||
<li><%= file.name %> (<%= humanFileSize(file.size, true) %>)</li>
|
||||
<li><%=file.name%> (<%=humanFileSize(file.size, true)%>)</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<% if (upload.expirationDate !== null) { %>
|
||||
These files will be automatically deleted after <%=upload.expirationDate.toLocaleString('en-US')%>
|
||||
<% } %>
|
||||
<p>You can download these files by logging in to <a href="<%= adminUrl %>"><%= adminUrl %></a></p>
|
||||
<p>You can download these files by logging in to <a href="<%=adminUrl%>"><%=adminUrl%></a></p>
|
||||
<p>Cheers,<br />Recv</p>
|
|
@ -1 +1 @@
|
|||
📄 File upload notification
|
||||
📄 <%=upload.files.count%> file<%=upload.files.count != 1 ? 's' : ''%> uploaded
|
4
index.js
4
index.js
|
@ -1,13 +1,13 @@
|
|||
'use strict'
|
||||
const fs = require('fs');
|
||||
const merge = require('deepmerge');
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
let configDefaults = require('./config.defaults');
|
||||
if (fs.existsSync('./config.js'))
|
||||
{
|
||||
let configChanges = require('./config');
|
||||
global.config = merge(configDefaults, configChanges);
|
||||
global.config = _.merge(configDefaults, configChanges);
|
||||
}
|
||||
else
|
||||
global.config = configDefaults;
|
||||
|
|
387
lib/api/admin.js
387
lib/api/admin.js
|
@ -1,387 +0,0 @@
|
|||
const express = require('express');
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const path = require('path');
|
||||
const resolvePath = require('resolve-path');
|
||||
const AuthTokens = require('../authtokens');
|
||||
const ExpirationUnits = require('../expirationunits');
|
||||
const disk = require('diskusage');
|
||||
const fs = require('mz/fs');
|
||||
|
||||
|
||||
async function checkAuthorization(req, res, repository, onVerified)
|
||||
{
|
||||
var token;
|
||||
|
||||
if (req.headers.authorization)
|
||||
{
|
||||
if (req.headers.authorization.split(' ')[0] !== 'Bearer')
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
token = req.headers.authorization.split(' ')[1];
|
||||
}
|
||||
else if (req.cookies && req.cookies.adminToken)
|
||||
{
|
||||
token = req.cookies.adminToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
jwt.verify(token, config.jwtSecret, async (err, decoded) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoded.userId)
|
||||
{
|
||||
var user = await repository.users.get(decoded.userId);
|
||||
if (user === null || !user.active)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
else
|
||||
await onVerified(user);
|
||||
}
|
||||
else
|
||||
res.sendStatus(400);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log(e);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = (repository) =>
|
||||
{
|
||||
var router = express.Router();
|
||||
|
||||
|
||||
router.get('/whoami', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
res.send({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
auth: user.auth
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/diskspace', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
disk.check(config.fileUpload.path, (err, info) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(500);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(info);
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
/*
|
||||
Codes
|
||||
*/
|
||||
router.get('/codes', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var codes = await repository.codes.list(user.hasAuth(AuthTokens.ViewAllCodes) ? null : user.id);
|
||||
var usernames = await repository.users.getNames();
|
||||
|
||||
codes.forEach((item) =>
|
||||
{
|
||||
item.username = usernames[item.userId];
|
||||
});
|
||||
|
||||
res.send(codes);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/codes/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var code = await repository.codes.get(req.params.id);
|
||||
if (code === null || (code.userId !== user.id && !user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await repository.users.get(code.userId);
|
||||
if (user !== null)
|
||||
code.username = user.name;
|
||||
|
||||
res.send(code);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.post('/codes', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var postedCode = req.body;
|
||||
|
||||
|
||||
if (config.code.maxExpiration !== null)
|
||||
{
|
||||
let now = new Date();
|
||||
|
||||
if (ExpirationUnits.apply(postedCode.expiration) > ExpirationUnits.apply(config.code.maxExpiration))
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (postedCode.id)
|
||||
{
|
||||
var code = await repository.codes.get(postedCode.id);
|
||||
if (code === null || (code.userId !== user.id && !user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.codes.update({
|
||||
id: postedCode.id,
|
||||
expiration: postedCode.expiration,
|
||||
description: postedCode.description,
|
||||
message: postedCode.message
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
var codeId = await repository.codes.insert({
|
||||
userId: user.id,
|
||||
created: postedCode.created || new Date(),
|
||||
expiration: postedCode.expiration,
|
||||
description: postedCode.description,
|
||||
message: postedCode.message
|
||||
});
|
||||
}
|
||||
|
||||
res.send(codeId);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/codes/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var code = await repository.codes.get(req.params.id);
|
||||
if (code == null || (code.userId !== user.id && !user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
repository.codes.delete(code.id);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/expiration', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
res.send({
|
||||
max: config.code.maxExpiration,
|
||||
default: config.code.defaultExpiration
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
/*
|
||||
Uploads
|
||||
*/
|
||||
router.get('/uploads', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var files = await repository.uploads.list(user.hasAuth(AuthTokens.ViewAllUploads) ? null : user.id);
|
||||
var usernames = await repository.users.getNames();
|
||||
var codedescriptions = await repository.codes.getDescriptions();
|
||||
|
||||
files.forEach((item) =>
|
||||
{
|
||||
item.username = item.userId !== null ? usernames[item.userId] : null;
|
||||
item.codedescription = item.code !== null ? codedescriptions[item.code] : null;
|
||||
});
|
||||
|
||||
res.send(files);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/uploads/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var upload = await repository.uploads.get(req.params.id);
|
||||
if (upload == null || (upload.userId !== user.id && !user.hasAuth(AuthTokens.ViewAllUploads)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.uploads.delete(upload.id);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/download/:fileid/:displayname', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
// TODO should we check if the user has access to the file?
|
||||
// for now not that important, if you know the file's UID and are logged in
|
||||
|
||||
var fullpath = resolvePath(config.fileUpload.path, req.params.fileid);
|
||||
res.download(fullpath, req.params.displayname);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
/*
|
||||
Users
|
||||
*/
|
||||
router.get('/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
if (!user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
var users = await repository.users.list();
|
||||
res.send(users);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.get('/users/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
if (req.params.id !== user.id && !user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await repository.users.get(req.params.id);
|
||||
if (user === null)
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(user);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.post('/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
var postedUser = req.body;
|
||||
|
||||
if (postedUser.id)
|
||||
{
|
||||
if (postedUser.id !== user.id && !user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.users.update({
|
||||
id: postedUser.id,
|
||||
username: postedUser.username,
|
||||
name: postedUser.name,
|
||||
password: postedUser.password,
|
||||
email: postedUser.email,
|
||||
auth: postedUser.auth,
|
||||
active: postedUser.active
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = await repository.users.insert({
|
||||
username: postedUser.username,
|
||||
name: postedUser.name,
|
||||
password: postedUser.password,
|
||||
email: postedUser.email,
|
||||
auth: postedUser.auth,
|
||||
active: postedUser.active,
|
||||
createdByUserId: postedUser.createdByUserId
|
||||
});
|
||||
}
|
||||
|
||||
res.send(userId);
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/users/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
if (!user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
repository.users.delete(req.params.id);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
}));
|
||||
|
||||
return router;
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
const asyncHandler = require('express-async-handler');
|
||||
const AuthTokens = require('../../authtokens');
|
||||
const ExpirationUnits = require('../../expirationunits');
|
||||
const NotificationType = require('../../repository/notification').NotificationType;
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
module.exports = (repository, router) =>
|
||||
{
|
||||
router.get('/codes', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var codes = await repository.codes.list(req.user.hasAuth(AuthTokens.ViewAllCodes) ? null : req.user.id);
|
||||
var usernames = await repository.users.getNames();
|
||||
|
||||
codes.forEach((item) =>
|
||||
{
|
||||
item.username = usernames[item.userId];
|
||||
});
|
||||
|
||||
res.send(codes);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/codes/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var code = await repository.codes.get(req.params.id);
|
||||
if (code === null || (code.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await repository.users.get(code.userId);
|
||||
if (user !== null)
|
||||
code.username = user.name;
|
||||
|
||||
res.send(code);
|
||||
}));
|
||||
|
||||
|
||||
router.post('/codes', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var postedCode = req.body;
|
||||
|
||||
|
||||
if (config.code.maxExpiration !== null)
|
||||
{
|
||||
let now = new Date();
|
||||
|
||||
if (ExpirationUnits.apply(postedCode.expiration) > ExpirationUnits.apply(config.code.maxExpiration))
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (postedCode.id)
|
||||
{
|
||||
var code = await repository.codes.get(postedCode.id);
|
||||
if (code === null || (code.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.codes.update({
|
||||
id: postedCode.id,
|
||||
expiration: postedCode.expiration,
|
||||
description: postedCode.description,
|
||||
message: postedCode.message
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
var codeId = await repository.codes.insert({
|
||||
userId: req.user.id,
|
||||
created: postedCode.created || new Date(),
|
||||
expiration: postedCode.expiration,
|
||||
description: postedCode.description,
|
||||
message: postedCode.message
|
||||
});
|
||||
}
|
||||
|
||||
res.send(codeId);
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/codes/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var code = await repository.codes.get(req.params.id);
|
||||
if (code == null || (code.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
repository.codes.delete(code.id);
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/expiration', asyncHandler(async (req, res) =>
|
||||
{
|
||||
res.send({
|
||||
max: config.code.maxExpiration,
|
||||
default: config.code.defaultExpiration
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
router.post('/assign/code', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var postedCode = req.body;
|
||||
|
||||
var code = await repository.codes.get(postedCode.id);
|
||||
if (code === null || (code.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllCodes)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if (code.userId !== postedCode.userId)
|
||||
{
|
||||
var target = await repository.users.get(postedCode.userId);
|
||||
if (target === null)
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.codes.move(postedCode.id, postedCode.userId);
|
||||
await repository.uploads.move(postedCode.id, postedCode.userId);
|
||||
|
||||
await repository.notifications.insert({
|
||||
userId: postedCode.userId,
|
||||
codeId: postedCode.id,
|
||||
type: NotificationType.CodeMoved,
|
||||
metadata: {
|
||||
prevUserId: code.userId,
|
||||
assignUserId: req.user.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/assign/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var users = await repository.users.list();
|
||||
if (users === null)
|
||||
{
|
||||
res.send([]);
|
||||
return;
|
||||
}
|
||||
|
||||
let assignableUsers = _.map(_.filter(users,
|
||||
(user) => { return user.active }),
|
||||
(user) => { return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.name
|
||||
}} );
|
||||
|
||||
res.send(assignableUsers);
|
||||
}));
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
const express = require('express');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
|
||||
async function checkAuthorization(req, res, repository, onVerified)
|
||||
{
|
||||
var token;
|
||||
|
||||
if (req.headers.authorization)
|
||||
{
|
||||
if (req.headers.authorization.split(' ')[0] !== 'Bearer')
|
||||
{
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
token = req.headers.authorization.split(' ')[1];
|
||||
}
|
||||
else if (req.cookies && req.cookies.adminToken)
|
||||
{
|
||||
token = req.cookies.adminToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
jwt.verify(token, config.jwtSecret, async (err, decoded) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoded.userId)
|
||||
{
|
||||
var user = await repository.users.get(decoded.userId);
|
||||
if (user === null || !user.active)
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
else
|
||||
await onVerified(user);
|
||||
}
|
||||
else
|
||||
res.sendStatus(400);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log(e);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = (repository) =>
|
||||
{
|
||||
var router = express.Router();
|
||||
router.use(async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await checkAuthorization(req, res, repository, (user) =>
|
||||
{
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
|
||||
require('./status')(repository, router);
|
||||
require('./codes')(repository, router);
|
||||
require('./uploads')(repository, router);
|
||||
require('./users')(repository, router);
|
||||
|
||||
return router;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
const asyncHandler = require('express-async-handler');
|
||||
const disk = require('diskusage');
|
||||
|
||||
|
||||
module.exports = (repository, router) =>
|
||||
{
|
||||
router.get('/whoami', (req, res) =>
|
||||
{
|
||||
res.send({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
auth: req.user.auth
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
router.get('/diskspace', (req, res) =>
|
||||
{
|
||||
disk.check(config.fileUpload.path, (err, info) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.sendStatus(500);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(info);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
const asyncHandler = require('express-async-handler');
|
||||
const AuthTokens = require('../../authtokens');
|
||||
const resolvePath = require('resolve-path');
|
||||
|
||||
|
||||
module.exports = (repository, router) =>
|
||||
{
|
||||
router.get('/uploads', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var files = await repository.uploads.list(req.user.hasAuth(AuthTokens.ViewAllUploads) ? null : req.user.id);
|
||||
var usernames = await repository.users.getNames();
|
||||
var codedescriptions = await repository.codes.getDescriptions();
|
||||
|
||||
files.forEach((item) =>
|
||||
{
|
||||
item.username = item.userId !== null ? usernames[item.userId] : null;
|
||||
item.codedescription = item.codeId !== null ? codedescriptions[item.codeId] : null;
|
||||
});
|
||||
|
||||
res.send(files);
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/uploads/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var upload = await repository.uploads.get(req.params.id);
|
||||
if (upload == null || (upload.userId !== req.user.id && !req.user.hasAuth(AuthTokens.ViewAllUploads)))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.uploads.delete(upload.id);
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/codeuploads/:code', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var uploads = await repository.uploads.listForCode(req.params.code);
|
||||
if (uploads === null)
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.user.hasAuth(AuthTokens.ViewAllUploads))
|
||||
{
|
||||
for (let i = 0; i < uploads.length; i++)
|
||||
{
|
||||
if (uploads[i].userId !== req.user.id)
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < uploads.length; i++)
|
||||
await repository.uploads.delete(uploads[i].id);
|
||||
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/download/:fileid/:displayname', asyncHandler(async (req, res) =>
|
||||
{
|
||||
// TODO should we check if the user has access to the file?
|
||||
// for now not that important, if you know the file's UID and are logged in
|
||||
|
||||
var fullpath = resolvePath(config.fileUpload.path, req.params.fileid);
|
||||
res.download(fullpath, req.params.displayname);
|
||||
}));
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
const asyncHandler = require('express-async-handler');
|
||||
const AuthTokens = require('../../authtokens');
|
||||
|
||||
|
||||
module.exports = (repository, router) =>
|
||||
{
|
||||
router.get('/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
if (!req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
var users = await repository.users.list();
|
||||
res.send(users);
|
||||
}));
|
||||
|
||||
|
||||
router.get('/users/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
if (req.params.id !== req.user.id && !req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await repository.users.get(req.params.id);
|
||||
if (user === null)
|
||||
{
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(user);
|
||||
}));
|
||||
|
||||
|
||||
router.post('/users', asyncHandler(async (req, res) =>
|
||||
{
|
||||
var postedUser = req.body;
|
||||
|
||||
if (postedUser.id)
|
||||
{
|
||||
if (postedUser.id !== req.user.id && !req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.users.update({
|
||||
id: postedUser.id,
|
||||
username: postedUser.username,
|
||||
name: postedUser.name,
|
||||
password: postedUser.password,
|
||||
email: postedUser.email,
|
||||
auth: postedUser.auth,
|
||||
active: postedUser.active
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = await repository.users.insert({
|
||||
username: postedUser.username,
|
||||
name: postedUser.name,
|
||||
password: postedUser.password,
|
||||
email: postedUser.email,
|
||||
auth: postedUser.auth,
|
||||
active: postedUser.active,
|
||||
createdByUserId: postedUser.createdByUserId
|
||||
});
|
||||
}
|
||||
|
||||
res.send(userId);
|
||||
}));
|
||||
|
||||
|
||||
router.delete('/users/:id', asyncHandler(async (req, res) =>
|
||||
{
|
||||
if (!req.user.hasAuth(AuthTokens.ManageUsers))
|
||||
{
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
repository.users.delete(req.params.id);
|
||||
res.sendStatus(200);
|
||||
}));
|
||||
}
|
|
@ -19,7 +19,7 @@ module.exports = (repository) =>
|
|||
if (code !== null)
|
||||
{
|
||||
jwt.sign({
|
||||
code: req.body.code,
|
||||
codeId: req.body.code,
|
||||
codeUserId: code.userId,
|
||||
codeExpirationTime: code.expirationDate !== null ? code.expirationDate.getTime() : null
|
||||
}, config.jwtSecret, (err, token) =>
|
||||
|
|
|
@ -4,6 +4,7 @@ const jwt = require('jsonwebtoken');
|
|||
const resolvePath = require('resolve-path');
|
||||
const fs = require('fs');
|
||||
const async = require('async');
|
||||
const NotificationType = require('../repository/notification').NotificationType;
|
||||
|
||||
|
||||
async function checkAuthorization(req, res, onVerified)
|
||||
|
@ -25,7 +26,7 @@ async function checkAuthorization(req, res, onVerified)
|
|||
return;
|
||||
}
|
||||
|
||||
if (decoded.code)
|
||||
if (decoded.codeId)
|
||||
await onVerified(decoded);
|
||||
else
|
||||
res.sendStatus(400);
|
||||
|
@ -106,16 +107,17 @@ module.exports = (repository, tusServer) =>
|
|||
return;
|
||||
}
|
||||
|
||||
var uploadId = await repository.uploads.insert(
|
||||
decoded.codeUserId,
|
||||
decoded.code,
|
||||
req.body.files,
|
||||
decoded.codeExpirationTime !== null ? new Date(decoded.codeExpirationTime) : null
|
||||
);
|
||||
var uploadId = await repository.uploads.insert({
|
||||
userId: decoded.codeUserId,
|
||||
codeId: decoded.codeId,
|
||||
files: req.body.files,
|
||||
expirationDate: decoded.codeExpirationTime !== null ? new Date(decoded.codeExpirationTime) : null
|
||||
});
|
||||
|
||||
await repository.notifications.insert({
|
||||
userId: decoded.codeUserId,
|
||||
uploadId: uploadId
|
||||
uploadId: uploadId,
|
||||
type: NotificationType.UploadComplete
|
||||
});
|
||||
|
||||
res.send({ id: uploadId });
|
||||
|
|
|
@ -20,6 +20,10 @@ class Code
|
|||
self.description = values.description || null;
|
||||
self.message = values.message || null;
|
||||
self.messageHTML = values.messageHTML || null;
|
||||
self.history = values.history || [{
|
||||
userId: self.userId,
|
||||
date: self.created
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,15 +59,23 @@ class CodeRepository
|
|||
if ((await self.get(codeId)) !== null)
|
||||
throw new Error('Code ' + codeId + ' already exists');
|
||||
|
||||
let now = new Date();
|
||||
|
||||
self.store.insert({
|
||||
_id: codeId,
|
||||
userId: code.userId,
|
||||
created: code.created || new Date(),
|
||||
created: code.created || now,
|
||||
expiration: code.expiration,
|
||||
expirationDate: ExpirationUnits.apply(code.expiration, code.created),
|
||||
description: code.description,
|
||||
message: code.message,
|
||||
messageHTML: self.getMessageHTML(code.message)
|
||||
messageHTML: self.getMessageHTML(code.message),
|
||||
history: [
|
||||
{
|
||||
userId: code.userId,
|
||||
date: code.created || now
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
return codeId;
|
||||
|
@ -107,6 +119,42 @@ class CodeRepository
|
|||
}
|
||||
|
||||
|
||||
move(codeId, userId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.update({ _id: codeId }, {
|
||||
$set: {
|
||||
userId: userId
|
||||
},
|
||||
$push: {
|
||||
history: {
|
||||
userId: userId,
|
||||
date: new Date()
|
||||
}
|
||||
}
|
||||
},
|
||||
(err, numAffected) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (numAffected == 0)
|
||||
{
|
||||
reject();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
list(userId)
|
||||
{
|
||||
var self = this;
|
||||
|
@ -180,13 +228,13 @@ class CodeRepository
|
|||
}
|
||||
|
||||
|
||||
delete(code)
|
||||
delete(codeId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.remove({ _id: code }, (err, numRemoved) =>
|
||||
self.store.remove({ _id: codeId }, (err, numRemoved) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
const map = require('lodash/map');
|
||||
|
||||
|
||||
const NotificationType = {
|
||||
UploadComplete: 'uploadComplete',
|
||||
CodeMoved: 'codeMoved'
|
||||
}
|
||||
|
||||
|
||||
class Notification
|
||||
{
|
||||
constructor(values)
|
||||
|
@ -8,9 +14,12 @@ class Notification
|
|||
var self = this;
|
||||
|
||||
self.id = values.id || values._id || null;
|
||||
self.userId = values.userId || null;
|
||||
self.uploadId = values.uploadId || null;
|
||||
self.codeId = values.codeId || null;
|
||||
self.userId = values.userId || null;
|
||||
self.type = values.type || NotificationType.UploadComplete;
|
||||
self.attempt = values.attempt || 0;
|
||||
self.metadata = values.metadata || null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,8 +61,11 @@ class NotificationRepository
|
|||
{
|
||||
self.store.insert({
|
||||
userId: notification.userId,
|
||||
codeId: notification.codeId,
|
||||
uploadId: notification.uploadId,
|
||||
attempt: notification.attempt
|
||||
attempt: notification.attempt,
|
||||
type: notification.type,
|
||||
metadata: notification.metadata
|
||||
}, (err, dbNotification) =>
|
||||
{
|
||||
if (err)
|
||||
|
@ -118,6 +130,7 @@ class NotificationRepository
|
|||
|
||||
|
||||
module.exports = {
|
||||
NotificationType,
|
||||
Notification,
|
||||
NotificationRepository
|
||||
}
|
|
@ -13,7 +13,7 @@ class Upload
|
|||
|
||||
self.id = values.id || values._id || null;
|
||||
self.userId = values.userId || null;
|
||||
self.code = values.code || null;
|
||||
self.codeId = values.codeId || null;
|
||||
self.created = values.created || new Date();
|
||||
self.expirationDate = values.expirationDate || null;
|
||||
self.files = values.files || [];
|
||||
|
@ -54,25 +54,25 @@ class UploadRepository
|
|||
}
|
||||
|
||||
|
||||
insert(userId, code, files, expirationDate)
|
||||
insert(upload)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
var upload = {
|
||||
created: new Date(),
|
||||
userId: userId,
|
||||
code: code,
|
||||
expirationDate: expirationDate,
|
||||
files: map(filter(files,
|
||||
let insertUpload = {
|
||||
created: upload.created || new Date(),
|
||||
userId: upload.userId,
|
||||
codeId: upload.codeId,
|
||||
expirationDate: upload.expirationDate,
|
||||
files: map(filter(upload.files,
|
||||
(file) => file.hasOwnProperty('id') && file.hasOwnProperty('name')),
|
||||
(file) => { return { id: file.id, name: file.name, size: file.size } })
|
||||
};
|
||||
|
||||
if (upload.files.length)
|
||||
if (insertUpload.files.length)
|
||||
{
|
||||
self.store.insert(upload, (err, dbUpload) =>
|
||||
self.store.insert(insertUpload, (err, dbUpload) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
|
@ -114,6 +114,29 @@ class UploadRepository
|
|||
}
|
||||
|
||||
|
||||
listForCode(codeId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.find({ codeId: codeId }, (err, docs) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(docs.map((dbUpload) =>
|
||||
{
|
||||
return new Upload(dbUpload);
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
get(uploadId)
|
||||
{
|
||||
var self = this;
|
||||
|
@ -134,6 +157,36 @@ class UploadRepository
|
|||
}
|
||||
|
||||
|
||||
move(codeId, userId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
self.store.update({ codeId: codeId }, {
|
||||
$set: {
|
||||
userId: userId
|
||||
}
|
||||
},
|
||||
(err, numAffected) =>
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (numAffected == 0)
|
||||
{
|
||||
reject();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
delete(uploadId)
|
||||
{
|
||||
var self = this;
|
||||
|
|
|
@ -5,6 +5,32 @@ const fs = require('mz/fs');
|
|||
const path = require('path');
|
||||
const getPaths = require('get-paths');
|
||||
const AbstractIntervalWorker = require('./abstractintervalworker');
|
||||
const NotificationType = require('../repository/notification').NotificationType;
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
function humanFileSize(bytes, si)
|
||||
{
|
||||
var thresh = si ? 1000 : 1024;
|
||||
if(Math.abs(bytes) < thresh)
|
||||
{
|
||||
return bytes + ' B';
|
||||
}
|
||||
|
||||
var units = si
|
||||
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
|
||||
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
|
||||
var u = -1;
|
||||
|
||||
do
|
||||
{
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
}
|
||||
while(Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||
|
||||
return bytes.toFixed(1) + ' ' + units[u];
|
||||
}
|
||||
|
||||
|
||||
class NotificationWorker extends AbstractIntervalWorker
|
||||
|
@ -24,7 +50,7 @@ class NotificationWorker extends AbstractIntervalWorker
|
|||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
async.eachOfSeries(notifications,
|
||||
async (item) => { await self.sendNotification(item) },
|
||||
async (item) => { await self.handleNotification(item) },
|
||||
(err) =>
|
||||
{
|
||||
if (err)
|
||||
|
@ -36,17 +62,104 @@ class NotificationWorker extends AbstractIntervalWorker
|
|||
}
|
||||
|
||||
|
||||
async sendNotification(notification)
|
||||
async handleNotification(notification)
|
||||
{
|
||||
let self = this;
|
||||
|
||||
let user = await self.repository.users.get(notification.userId);
|
||||
if (user === null || !user.email)
|
||||
return;
|
||||
|
||||
let upload = await self.repository.uploads.get(notification.uploadId);
|
||||
if (upload === null)
|
||||
switch (notification.type)
|
||||
{
|
||||
case NotificationType.UploadComplete:
|
||||
await self.sendUploadNotification(notification, user);
|
||||
break;
|
||||
|
||||
case NotificationType.CodeMoved:
|
||||
await self.sendCodeMovedNotification(notification, user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getDefaultLocals(user)
|
||||
{
|
||||
return {
|
||||
// Data
|
||||
user: user,
|
||||
|
||||
// Configuration
|
||||
adminUrl: config.notifications.adminUrl,
|
||||
|
||||
// Helper functions
|
||||
humanFileSize: humanFileSize
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async sendUploadNotification(notification, user)
|
||||
{
|
||||
let self = this;
|
||||
let locals = self.getDefaultLocals(user);
|
||||
|
||||
locals.upload = await self.repository.uploads.get(notification.uploadId);
|
||||
if (locals.upload === null)
|
||||
return;
|
||||
|
||||
await self.sendNotification(notification, user, locals, 'uploadnotification');
|
||||
}
|
||||
|
||||
|
||||
async sendCodeMovedNotification(notification, user)
|
||||
{
|
||||
let self = this;
|
||||
let locals = self.getDefaultLocals(user);
|
||||
|
||||
locals.uploads = await self.repository.uploads.listForCode(notification.codeId);
|
||||
if (locals.uploads === null || locals.uploads.length == 0)
|
||||
return;
|
||||
|
||||
locals.fileCount = 0;
|
||||
_.forEach(locals.uploads, (upload) =>
|
||||
{
|
||||
locals.fileCount += upload.files.length;
|
||||
});
|
||||
|
||||
locals.prevUser = {
|
||||
userId: null,
|
||||
name: ''
|
||||
};
|
||||
|
||||
if (notification.metadata !== null)
|
||||
{
|
||||
let resolveUser = async (userId) =>
|
||||
{
|
||||
if (!userId)
|
||||
return null;
|
||||
|
||||
let user = await self.repository.users.get(userId);
|
||||
if (user !== null)
|
||||
{
|
||||
return {
|
||||
userId: userId,
|
||||
name: user.name
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
locals.prevUser = await resolveUser(notification.metadata.prevUserId);
|
||||
locals.assignUser = await resolveUser(notification.metadata.assignUserId);
|
||||
}
|
||||
|
||||
await self.sendNotification(notification, user, locals, 'movednotification');
|
||||
}
|
||||
|
||||
|
||||
async sendNotification(notification, user, locals, template)
|
||||
{
|
||||
let self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
const email = new Email({
|
||||
|
@ -99,24 +212,21 @@ class NotificationWorker extends AbstractIntervalWorker
|
|||
|
||||
email
|
||||
.send({
|
||||
template: 'uploadnotification',
|
||||
template: template,
|
||||
message: {
|
||||
to: user.email
|
||||
},
|
||||
locals: {
|
||||
user: user,
|
||||
upload: upload,
|
||||
adminUrl: config.notifications.adminUrl
|
||||
}
|
||||
locals: locals
|
||||
})
|
||||
.then(async () =>
|
||||
{
|
||||
await self.repository.notifications.delete(notification.id);
|
||||
console.log('Notification sent to: ' + user.email + ' for upload ID: ' + upload.id);
|
||||
console.log('Notification sent to: ' + user.email);
|
||||
resolve();
|
||||
})
|
||||
.catch(async (error) =>
|
||||
{
|
||||
console.log(error);
|
||||
notification.attempt++;
|
||||
if (notification.attempt > config.notifications.maxAttempts)
|
||||
await self.repository.notifications.delete(notification.id);
|
||||
|
|
|
@ -3132,11 +3132,6 @@
|
|||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
|
||||
"integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8="
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz",
|
||||
"integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw=="
|
||||
},
|
||||
"define-properties": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
"body-parser": "^1.18.2",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"debug": "^3.1.0",
|
||||
"deepmerge": "^2.1.0",
|
||||
"diskusage": "^0.2.4",
|
||||
"ejs": "^2.5.9",
|
||||
"email-templates": "^3.6.0",
|
||||
|
|
|
@ -191,6 +191,6 @@ a
|
|||
|
||||
.confirmDelete
|
||||
{
|
||||
color: red;
|
||||
color: red !important;
|
||||
}
|
||||
</style>
|
|
@ -16,6 +16,8 @@ import merge from 'lodash/merge';
|
|||
import faSpinner from '@fortawesome/fontawesome-free-solid/faSpinner';
|
||||
import faBan from '@fortawesome/fontawesome-free-solid/faBan';
|
||||
import faTrashAlt from '@fortawesome/fontawesome-free-solid/faTrashAlt';
|
||||
import faUser from '@fortawesome/fontawesome-free-solid/faUser';
|
||||
|
||||
|
||||
if (typeof customMessages !== 'undefined')
|
||||
{
|
||||
|
@ -32,7 +34,7 @@ if (typeof customMessages !== 'undefined')
|
|||
}
|
||||
|
||||
|
||||
fontawesome.library.add(faSpinner, faBan, faTrashAlt);
|
||||
fontawesome.library.add(faSpinner, faBan, faTrashAlt, faUser);
|
||||
Vue.component('fa', FontAwesomeIcon);
|
||||
|
||||
Vue.use(VueI18n);
|
||||
|
|
|
@ -67,7 +67,10 @@ export default {
|
|||
created: 'Date',
|
||||
code: 'Code',
|
||||
owner: 'Owner',
|
||||
userDeleted: 'deleted'
|
||||
userDeleted: 'deleted',
|
||||
|
||||
assign: 'Change owner',
|
||||
assignApply: 'Apply'
|
||||
},
|
||||
|
||||
codes: {
|
||||
|
|
|
@ -67,8 +67,11 @@ export default {
|
|||
created: 'Datum',
|
||||
code: 'Code',
|
||||
owner: 'Eigenaar',
|
||||
userDeleted: 'verwijderd'
|
||||
},
|
||||
userDeleted: 'verwijderd',
|
||||
|
||||
assign: 'Verander eigenaar',
|
||||
assignApply: 'Toepassen'
|
||||
},
|
||||
|
||||
codes: {
|
||||
add: 'Genereer code',
|
||||
|
|
|
@ -1,34 +1,46 @@
|
|||
<template>
|
||||
<div id="uploads">
|
||||
<div v-if="uploads !== null" class="list">
|
||||
<div v-for="upload in uploads">
|
||||
<div class="properties">
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1-1"><span class="text codedescription">{{ upload.codedescription || upload.code }}</span></div>
|
||||
<div class="pure-u-1-3"><span class="text">{{ upload.code }}</span></div>
|
||||
<div class="pure-u-1-3">
|
||||
<span class="text" v-if="hasAuth('viewAllUploads')" :class="{ userDeleted: !upload.username }">
|
||||
{{ upload.username || $t('admin.uploads.userDeleted') }}
|
||||
</span>
|
||||
<div v-for="group in groupedUploads">
|
||||
<template v-for="(upload, index) in group.uploads">
|
||||
<div class="properties" v-if="index == 0">
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1-1"><span class="text codedescription">{{ upload.codedescription || upload.codeId }}</span></div>
|
||||
<div class="pure-u-1-3"><span class="text">{{ upload.codeId }}</span></div>
|
||||
<div class="pure-u-1-3">
|
||||
<span class="text" v-if="hasAuth('viewAllUploads')" :class="{ userDeleted: !upload.username }">
|
||||
{{ upload.username || $t('admin.uploads.userDeleted') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="pure-u-1-3 right"><span class="text">{{ upload.created | formatDateTime }}</span></div>
|
||||
</div>
|
||||
<div class="pure-u-1-3 right"><span class="text">{{ upload.created | formatDateTime }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pure-menu pure-menu-horizontal">
|
||||
<ul class="pure-menu-list">
|
||||
<li class="pure-menu-item" v-if="confirmDelete == upload.id"><a href="#" class="pure-menu-link" @click.prevent="cancelDelete"><fa icon="ban"></fa> {{ $t('admin.cancel') }}</a></li>
|
||||
<li class="pure-menu-item"><a href="#" class="pure-menu-link" :class="{ confirmDelete: confirmDelete == upload.id }" @click.prevent="deleteClick(upload.id)"><fa icon="trash-alt"></fa> {{ $t('admin.delete') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pure-menu pure-menu-horizontal" v-if="index == 0">
|
||||
<ul class="pure-menu-list">
|
||||
<li class="pure-menu-item"><a href="#" class="pure-menu-link" @click.prevent="showAssign = !showAssign"><fa icon="user"></fa> {{ $t('admin.uploads.assign') }}</a></li>
|
||||
<li class="pure-menu-item" v-if="confirmDelete == upload.codeId"><a href="#" class="pure-menu-link" @click.prevent="cancelDelete"><fa icon="ban"></fa> {{ $t('admin.cancel') }}</a></li>
|
||||
<li class="pure-menu-item"><a href="#" class="pure-menu-link" :class="{ confirmDelete: confirmDelete == upload.codeId }" @click.prevent="deleteClick(upload.codeId)"><fa icon="trash-alt"></fa> {{ $t('admin.delete') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="file" v-for="file in upload.files" :title="file.name">
|
||||
<a :href="getDownloadUrl(file)">
|
||||
<img :src="getFileIconUrl(file.name)" class="icon">
|
||||
<span class="filename">{{ file.name }}</span>
|
||||
<span class="size">{{ file.size | formatSizeSI }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="showAssign" class="assign pure-form pure-form-horizontal">
|
||||
<select v-model="assignUser">
|
||||
<option v-for="user in assignUsers" v-if="user.id !== upload.userId" :value="user.id">{{ user.name }}</option>
|
||||
</select>
|
||||
|
||||
<button class="pure-button pure-button-primary" @click.prevent="assignCode(upload.codeId)">{{ $t('admin.uploads.assignApply') }}</button>
|
||||
<button class="pure-button" @click.prevent="showAssign = false">{{ $t('admin.cancel') }}</button>
|
||||
</div>
|
||||
|
||||
<div class="file" v-for="file in upload.files" :title="file.name">
|
||||
<a :href="getDownloadUrl(file)">
|
||||
<img :src="getFileIconUrl(file.name)" class="icon">
|
||||
<span class="filename">{{ file.name }}</span>
|
||||
<span class="size">{{ file.size | formatSizeSI }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="uploads.length == 0" class="nodata">
|
||||
|
@ -45,6 +57,7 @@
|
|||
<script>
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import forEach from 'lodash/forEach';
|
||||
import axios from 'axios';
|
||||
import shared from '../../shared';
|
||||
|
||||
|
@ -53,7 +66,10 @@ export default {
|
|||
{
|
||||
return {
|
||||
uploads: null,
|
||||
confirmDelete: null
|
||||
confirmDelete: null,
|
||||
showAssign: false,
|
||||
assignUser: null,
|
||||
assignUsers: []
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -78,8 +94,47 @@ export default {
|
|||
self.uploads = orderBy(response.data, ['created'], ['desc']);
|
||||
})
|
||||
.catch((error) => { shared.$emit('apiError', error, this.$router) });
|
||||
|
||||
|
||||
axios.get('/admin/assign/users', {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + shared.adminToken
|
||||
}
|
||||
})
|
||||
.then((response) =>
|
||||
{
|
||||
self.assignUsers = orderBy(response.data, ['name'], ['asc']);
|
||||
})
|
||||
.catch((error) => { shared.$emit('apiError', error, this.$router) });
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
groupedUploads()
|
||||
{
|
||||
let self = this;
|
||||
let codes = {};
|
||||
|
||||
// Get unique codes and their highest upload time
|
||||
forEach(self.uploads, (upload) =>
|
||||
{
|
||||
if (!codes.hasOwnProperty(upload.codeId))
|
||||
{
|
||||
codes[upload.codeId] = {
|
||||
maxCreated: upload.created,
|
||||
uploads: [upload]
|
||||
};
|
||||
}
|
||||
else
|
||||
// uploads is sorted by created descending, so no need to update maxCreated
|
||||
codes[upload.codeId].uploads.push(upload);
|
||||
});
|
||||
|
||||
return orderBy(codes, ['maxCreated'], ['desc']);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
hasAuth(token)
|
||||
{
|
||||
|
@ -110,29 +165,26 @@ export default {
|
|||
},
|
||||
|
||||
|
||||
deleteClick(uploadId)
|
||||
deleteClick(codeId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
if (self.confirmDelete == uploadId)
|
||||
if (self.confirmDelete == codeId)
|
||||
{
|
||||
self.confirmDelete = null;
|
||||
|
||||
axios.delete('/admin/uploads/' + encodeURIComponent(uploadId), {
|
||||
axios.delete('/admin/codeuploads/' + encodeURIComponent(codeId), {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + shared.adminToken
|
||||
}})
|
||||
.then((response) =>
|
||||
{
|
||||
var index = findIndex(self.uploads, (item) => { return item.id == uploadId; });
|
||||
if (index > -1)
|
||||
self.uploads.splice(index, 1);
|
||||
})
|
||||
.catch((error) => { shared.$emit('apiError', error, this.$router) });
|
||||
}
|
||||
else
|
||||
{
|
||||
self.confirmDelete = uploadId;
|
||||
self.confirmDelete = codeId;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -140,6 +192,72 @@ export default {
|
|||
{
|
||||
var self = this;
|
||||
self.confirmDelete = null;
|
||||
},
|
||||
|
||||
|
||||
assignCode(codeId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
let userId = self.assignUser;
|
||||
if (!userId)
|
||||
return;
|
||||
|
||||
self.assignUser = null;
|
||||
self.showAssign = false;
|
||||
|
||||
axios.post('/admin/assign/code', {
|
||||
id: codeId,
|
||||
userId: userId
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + shared.adminToken
|
||||
}
|
||||
})
|
||||
.then((response) =>
|
||||
{
|
||||
if (self.hasAuth('viewAllUploads'))
|
||||
{
|
||||
let username = null;
|
||||
forEach(self.assignUsers, (user) =>
|
||||
{
|
||||
if (user.id == userId)
|
||||
username = user.name;
|
||||
})
|
||||
|
||||
self.updateCodeUser(codeId, userId, username);
|
||||
}
|
||||
else
|
||||
self.removeCodeUploads(codeId);
|
||||
})
|
||||
.catch((error) => { shared.$emit('apiError', error, this.$router) })
|
||||
},
|
||||
|
||||
|
||||
removeCodeUploads(codeId)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
for (let i = self.uploads.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (self.uploads[i].codeId == codeId)
|
||||
self.uploads.splice(i, 1);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
updateCodeUser(codeId, userId, username)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
forEach(self.uploads, (upload) =>
|
||||
{
|
||||
if (upload.codeId == codeId)
|
||||
{
|
||||
upload.userId = userId;
|
||||
upload.username = username;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,4 +325,10 @@ export default {
|
|||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.assign
|
||||
{
|
||||
margin-top: .5rem;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue