256 lines
9.1 KiB
JavaScript
256 lines
9.1 KiB
JavaScript
const fsPromises = require('fs').promises;
|
|
const storage = require('node-persist');
|
|
const express = require('express');
|
|
const lodash = require('lodash');
|
|
const { jsonParser } = require('../express-common');
|
|
const { checkForNewContent } = require('./content-manager');
|
|
const {
|
|
KEY_PREFIX,
|
|
toKey,
|
|
requireAdminMiddleware,
|
|
getUserAvatar,
|
|
getAllUserHandles,
|
|
getPasswordSalt,
|
|
getPasswordHash,
|
|
getUserDirectories,
|
|
ensurePublicDirectoriesExist,
|
|
} = require('../users');
|
|
const { DEFAULT_USER } = require('../constants');
|
|
|
|
const router = express.Router();
|
|
|
|
router.post('/get', requireAdminMiddleware, jsonParser, async (_request, response) => {
|
|
try {
|
|
/** @type {import('../users').User[]} */
|
|
const users = await storage.values(x => x.key.startsWith(KEY_PREFIX));
|
|
|
|
/** @type {Promise<import('../users').UserViewModel>[]} */
|
|
const viewModelPromises = users
|
|
.map(user => new Promise(resolve => {
|
|
getUserAvatar(user.handle).then(avatar =>
|
|
resolve({
|
|
handle: user.handle,
|
|
name: user.name,
|
|
avatar: avatar,
|
|
admin: user.admin,
|
|
enabled: user.enabled,
|
|
created: user.created,
|
|
password: !!user.password,
|
|
}),
|
|
);
|
|
}));
|
|
|
|
const viewModels = await Promise.all(viewModelPromises);
|
|
viewModels.sort((x, y) => (x.created ?? 0) - (y.created ?? 0));
|
|
return response.json(viewModels);
|
|
} catch (error) {
|
|
console.error('User list failed:', error);
|
|
return response.sendStatus(500);
|
|
}
|
|
});
|
|
|
|
router.post('/disable', requireAdminMiddleware, jsonParser, async (request, response) => {
|
|
try {
|
|
if (!request.body.handle) {
|
|
console.log('Disable user failed: Missing required fields');
|
|
return response.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
if (request.body.handle === request.user.profile.handle) {
|
|
console.log('Disable user failed: Cannot disable yourself');
|
|
return response.status(400).json({ error: 'Cannot disable yourself' });
|
|
}
|
|
|
|
/** @type {import('../users').User} */
|
|
const user = await storage.getItem(toKey(request.body.handle));
|
|
|
|
if (!user) {
|
|
console.log('Disable user failed: User not found');
|
|
return response.status(404).json({ error: 'User not found' });
|
|
}
|
|
|
|
user.enabled = false;
|
|
await storage.setItem(toKey(request.body.handle), user);
|
|
return response.sendStatus(204);
|
|
} catch (error) {
|
|
console.error('User disable failed:', error);
|
|
return response.sendStatus(500);
|
|
}
|
|
});
|
|
|
|
router.post('/enable', requireAdminMiddleware, jsonParser, async (request, response) => {
|
|
try {
|
|
if (!request.body.handle) {
|
|
console.log('Enable user failed: Missing required fields');
|
|
return response.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
/** @type {import('../users').User} */
|
|
const user = await storage.getItem(toKey(request.body.handle));
|
|
|
|
if (!user) {
|
|
console.log('Enable user failed: User not found');
|
|
return response.status(404).json({ error: 'User not found' });
|
|
}
|
|
|
|
user.enabled = true;
|
|
await storage.setItem(toKey(request.body.handle), user);
|
|
return response.sendStatus(204);
|
|
} catch (error) {
|
|
console.error('User enable failed:', error);
|
|
return response.sendStatus(500);
|
|
}
|
|
});
|
|
|
|
router.post('/promote', requireAdminMiddleware, jsonParser, async (request, response) => {
|
|
try {
|
|
if (!request.body.handle) {
|
|
console.log('Promote user failed: Missing required fields');
|
|
return response.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
/** @type {import('../users').User} */
|
|
const user = await storage.getItem(toKey(request.body.handle));
|
|
|
|
if (!user) {
|
|
console.log('Promote user failed: User not found');
|
|
return response.status(404).json({ error: 'User not found' });
|
|
}
|
|
|
|
user.admin = true;
|
|
await storage.setItem(toKey(request.body.handle), user);
|
|
return response.sendStatus(204);
|
|
} catch (error) {
|
|
console.error('User promote failed:', error);
|
|
return response.sendStatus(500);
|
|
}
|
|
});
|
|
|
|
router.post('/demote', requireAdminMiddleware, jsonParser, async (request, response) => {
|
|
try {
|
|
if (!request.body.handle) {
|
|
console.log('Demote user failed: Missing required fields');
|
|
return response.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
if (request.body.handle === request.user.profile.handle) {
|
|
console.log('Demote user failed: Cannot demote yourself');
|
|
return response.status(400).json({ error: 'Cannot demote yourself' });
|
|
}
|
|
|
|
/** @type {import('../users').User} */
|
|
const user = await storage.getItem(toKey(request.body.handle));
|
|
|
|
if (!user) {
|
|
console.log('Demote user failed: User not found');
|
|
return response.status(404).json({ error: 'User not found' });
|
|
}
|
|
|
|
user.admin = false;
|
|
await storage.setItem(toKey(request.body.handle), user);
|
|
return response.sendStatus(204);
|
|
} catch (error) {
|
|
console.error('User demote failed:', error);
|
|
return response.sendStatus(500);
|
|
}
|
|
});
|
|
|
|
router.post('/create', requireAdminMiddleware, jsonParser, async (request, response) => {
|
|
try {
|
|
if (!request.body.handle || !request.body.name) {
|
|
console.log('Create user failed: Missing required fields');
|
|
return response.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
const handles = await getAllUserHandles();
|
|
const handle = lodash.kebabCase(String(request.body.handle).toLowerCase().trim());
|
|
|
|
if (!handle) {
|
|
console.log('Create user failed: Invalid handle');
|
|
return response.status(400).json({ error: 'Invalid handle' });
|
|
}
|
|
|
|
if (handles.some(x => x === handle)) {
|
|
console.log('Create user failed: User with that handle already exists');
|
|
return response.status(409).json({ error: 'User already exists' });
|
|
}
|
|
|
|
const salt = getPasswordSalt();
|
|
const password = request.body.password ? getPasswordHash(request.body.password, salt) : '';
|
|
|
|
const newUser = {
|
|
handle: handle,
|
|
name: request.body.name || 'Anonymous',
|
|
created: Date.now(),
|
|
password: password,
|
|
salt: salt,
|
|
admin: !!request.body.admin,
|
|
enabled: true,
|
|
};
|
|
|
|
await storage.setItem(toKey(handle), newUser);
|
|
|
|
// Create user directories
|
|
console.log('Creating data directories for', newUser.handle);
|
|
await ensurePublicDirectoriesExist();
|
|
const directories = getUserDirectories(newUser.handle);
|
|
await checkForNewContent([directories]);
|
|
return response.json({ handle: newUser.handle });
|
|
} catch (error) {
|
|
console.error('User create failed:', error);
|
|
return response.sendStatus(500);
|
|
}
|
|
});
|
|
|
|
router.post('/delete', requireAdminMiddleware, jsonParser, async (request, response) => {
|
|
try {
|
|
if (!request.body.handle) {
|
|
console.log('Delete user failed: Missing required fields');
|
|
return response.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
if (request.body.handle === request.user.profile.handle) {
|
|
console.log('Delete user failed: Cannot delete yourself');
|
|
return response.status(400).json({ error: 'Cannot delete yourself' });
|
|
}
|
|
|
|
if (request.body.handle === DEFAULT_USER.handle) {
|
|
console.log('Delete user failed: Cannot delete default user');
|
|
return response.status(400).json({ error: 'Sorry, but the default user cannot be deleted. It is required as a fallback.' });
|
|
}
|
|
|
|
await storage.removeItem(toKey(request.body.handle));
|
|
|
|
if (request.body.purge) {
|
|
const directories = getUserDirectories(request.body.handle);
|
|
console.log('Deleting data directories for', request.body.handle);
|
|
await fsPromises.rm(directories.root, { recursive: true, force: true });
|
|
}
|
|
|
|
return response.sendStatus(204);
|
|
} catch (error) {
|
|
console.error('User delete failed:', error);
|
|
return response.sendStatus(500);
|
|
}
|
|
});
|
|
|
|
router.post('/slugify', requireAdminMiddleware, jsonParser, async (request, response) => {
|
|
try {
|
|
if (!request.body.text) {
|
|
console.log('Slugify failed: Missing required fields');
|
|
return response.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
const text = lodash.kebabCase(String(request.body.text).toLowerCase().trim());
|
|
|
|
return response.send(text);
|
|
} catch (error) {
|
|
console.error('Slugify failed:', error);
|
|
return response.sendStatus(500);
|
|
}
|
|
});
|
|
|
|
module.exports = {
|
|
router,
|
|
};
|