|
import { promises as fsPromises } from 'node:fs'; |
|
|
|
import storage from 'node-persist'; |
|
import express from 'express'; |
|
import lodash from 'lodash'; |
|
import { checkForNewContent, CONTENT_TYPES } from './content-manager.js'; |
|
import { |
|
KEY_PREFIX, |
|
toKey, |
|
requireAdminMiddleware, |
|
getUserAvatar, |
|
getAllUserHandles, |
|
getPasswordSalt, |
|
getPasswordHash, |
|
getUserDirectories, |
|
ensurePublicDirectoriesExist, |
|
} from '../users.js'; |
|
import { DEFAULT_USER } from '../constants.js'; |
|
|
|
export const router = express.Router(); |
|
|
|
router.post('/get', requireAdminMiddleware, async (_request, response) => { |
|
try { |
|
|
|
const users = await storage.values(x => x.key.startsWith(KEY_PREFIX)); |
|
|
|
|
|
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, async (request, response) => { |
|
try { |
|
if (!request.body.handle) { |
|
console.warn('Disable user failed: Missing required fields'); |
|
return response.status(400).json({ error: 'Missing required fields' }); |
|
} |
|
|
|
if (request.body.handle === request.user.profile.handle) { |
|
console.warn('Disable user failed: Cannot disable yourself'); |
|
return response.status(400).json({ error: 'Cannot disable yourself' }); |
|
} |
|
|
|
|
|
const user = await storage.getItem(toKey(request.body.handle)); |
|
|
|
if (!user) { |
|
console.error('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, async (request, response) => { |
|
try { |
|
if (!request.body.handle) { |
|
console.warn('Enable user failed: Missing required fields'); |
|
return response.status(400).json({ error: 'Missing required fields' }); |
|
} |
|
|
|
|
|
const user = await storage.getItem(toKey(request.body.handle)); |
|
|
|
if (!user) { |
|
console.error('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, async (request, response) => { |
|
try { |
|
if (!request.body.handle) { |
|
console.warn('Promote user failed: Missing required fields'); |
|
return response.status(400).json({ error: 'Missing required fields' }); |
|
} |
|
|
|
|
|
const user = await storage.getItem(toKey(request.body.handle)); |
|
|
|
if (!user) { |
|
console.error('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, async (request, response) => { |
|
try { |
|
if (!request.body.handle) { |
|
console.warn('Demote user failed: Missing required fields'); |
|
return response.status(400).json({ error: 'Missing required fields' }); |
|
} |
|
|
|
if (request.body.handle === request.user.profile.handle) { |
|
console.warn('Demote user failed: Cannot demote yourself'); |
|
return response.status(400).json({ error: 'Cannot demote yourself' }); |
|
} |
|
|
|
|
|
const user = await storage.getItem(toKey(request.body.handle)); |
|
|
|
if (!user) { |
|
console.error('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, async (request, response) => { |
|
try { |
|
if (!request.body.handle || !request.body.name) { |
|
console.warn('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.warn('Create user failed: Invalid handle'); |
|
return response.status(400).json({ error: 'Invalid handle' }); |
|
} |
|
|
|
if (handles.some(x => x === handle)) { |
|
console.warn('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); |
|
|
|
|
|
console.info('Creating data directories for', newUser.handle); |
|
await ensurePublicDirectoriesExist(); |
|
const directories = getUserDirectories(newUser.handle); |
|
await checkForNewContent([directories], [CONTENT_TYPES.SETTINGS]); |
|
return response.json({ handle: newUser.handle }); |
|
} catch (error) { |
|
console.error('User create failed:', error); |
|
return response.sendStatus(500); |
|
} |
|
}); |
|
|
|
router.post('/delete', requireAdminMiddleware, async (request, response) => { |
|
try { |
|
if (!request.body.handle) { |
|
console.warn('Delete user failed: Missing required fields'); |
|
return response.status(400).json({ error: 'Missing required fields' }); |
|
} |
|
|
|
if (request.body.handle === request.user.profile.handle) { |
|
console.warn('Delete user failed: Cannot delete yourself'); |
|
return response.status(400).json({ error: 'Cannot delete yourself' }); |
|
} |
|
|
|
if (request.body.handle === DEFAULT_USER.handle) { |
|
console.warn('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.info('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, async (request, response) => { |
|
try { |
|
if (!request.body.text) { |
|
console.warn('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); |
|
} |
|
}); |
|
|