Added e-mail alert settings, saveable but not integrated yet.
This commit is contained in:
@@ -73,7 +73,7 @@ export async function login(credentials, userIp) {
|
||||
}
|
||||
|
||||
// fetched the raw user for password verification, don't return directly to user
|
||||
return curateUser(user);
|
||||
return curateUser(user, user);
|
||||
}
|
||||
|
||||
export async function signup(credentials, userIp) {
|
||||
|
||||
49
src/users.js
49
src/users.js
@@ -14,13 +14,18 @@ function curateTemplate(template) {
|
||||
};
|
||||
}
|
||||
|
||||
export function curateUser(user, _assets = {}, reqUser) {
|
||||
export function curateUser(user, reqUser) {
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// const curatedStashes = assets.stashes?.filter(Boolean).map((stash) => curateStash(stash)) || [];
|
||||
|
||||
const privateUser = {
|
||||
email: user.email,
|
||||
settings: user.settings,
|
||||
};
|
||||
|
||||
const curatedUser = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
@@ -30,11 +35,7 @@ export function curateUser(user, _assets = {}, reqUser) {
|
||||
role: user.role,
|
||||
abilities: [...user.role_abilities || [], ...user.abilities || []],
|
||||
createdAt: user.created_at,
|
||||
...(reqUser?.id === user.id
|
||||
? {
|
||||
email: user.email,
|
||||
}
|
||||
: null),
|
||||
...(reqUser?.id === user.id ? privateUser : null),
|
||||
};
|
||||
|
||||
return curatedUser;
|
||||
@@ -67,12 +68,10 @@ export async function fetchUser(userId, options = {}, reqUser) {
|
||||
}
|
||||
|
||||
if (options.raw) {
|
||||
// return { user, stashes, templates };
|
||||
return { user };
|
||||
}
|
||||
|
||||
// return curateUser(user, { stashes, templates });
|
||||
return curateUser(user, {}, reqUser);
|
||||
return curateUser(user, reqUser);
|
||||
}
|
||||
|
||||
export async function fetchUserTemplates(reqUser) {
|
||||
@@ -137,3 +136,35 @@ export async function createBan(ban, reqUser) {
|
||||
|
||||
await knex('bans').insert(curatedBan);
|
||||
}
|
||||
|
||||
const validSettingKeys = [
|
||||
'alertEmailEnabled',
|
||||
'alertEmailFrequency',
|
||||
];
|
||||
|
||||
export async function updateSettings(settings, reqUser) {
|
||||
if (!reqUser) {
|
||||
throw new HttpError('You need to be authenticated to change your settings', 401);
|
||||
}
|
||||
|
||||
console.log('SETTINGS', settings);
|
||||
|
||||
const invalidKeys = Object.keys(settings).filter((key) => !validSettingKeys.includes(key));
|
||||
|
||||
if (invalidKeys.length > 0) {
|
||||
throw new HttpError(`Invalid settings: ${invalidKeys.join()}`, 400);
|
||||
}
|
||||
|
||||
if (settings.alertEmailFrequency && !['instant', 'daily', 'weekly', 'monthly'].includes(settings.alertEmailFrequency)) {
|
||||
throw new HttpError('Invalid email frequency setting', 400);
|
||||
}
|
||||
|
||||
const [{ settings: updatedSettings }] = await knex('users')
|
||||
.where('id', reqUser.id)
|
||||
.update('settings', knex.raw('coalesce(settings, \'{}\'::jsonb) || ?::jsonb', [JSON.stringify(settings)]))
|
||||
.returning('settings');
|
||||
|
||||
console.log('UPDATED SETTINGS', updatedSettings);
|
||||
|
||||
return updatedSettings;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify';
|
||||
import { Router } from 'express';
|
||||
import omit from 'object.omit';
|
||||
|
||||
@@ -45,12 +44,12 @@ export async function fetchActorsApi(req, res) {
|
||||
order: req.query.order?.split('.') || ['likes', 'desc'],
|
||||
}, req.user);
|
||||
|
||||
res.send(stringify({
|
||||
res.send({
|
||||
actors,
|
||||
countries,
|
||||
limit,
|
||||
total,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
export const actorsSchema = `
|
||||
@@ -189,7 +188,7 @@ export async function fetchActorsByIdGraphql(query, _req, _info) {
|
||||
export async function createActorApi(req, res) {
|
||||
const actor = await createActor(req.body.actor, omit(req.body, ['actor']), req.user);
|
||||
|
||||
res.send(stringify({ actor }));
|
||||
res.send({ actor });
|
||||
}
|
||||
|
||||
export async function mergeActorsApi(req, res) {
|
||||
@@ -200,13 +199,13 @@ export async function mergeActorsApi(req, res) {
|
||||
req.user,
|
||||
);
|
||||
|
||||
res.send(stringify(result));
|
||||
res.send(result);
|
||||
}
|
||||
|
||||
async function fetchActorRevisionsApi(req, res) {
|
||||
const revisions = await fetchActorRevisions(Number(req.params.revisionId) || null, req.query, req.user);
|
||||
|
||||
res.send(stringify(revisions));
|
||||
res.send(revisions);
|
||||
}
|
||||
|
||||
async function createActorRevisionApi(req, res) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify';
|
||||
import { Router } from 'express';
|
||||
|
||||
import {
|
||||
@@ -147,7 +146,7 @@ export const alertsSchema = `
|
||||
export async function fetchAlertsApi(req, res) {
|
||||
const alerts = await fetchAlerts(req.user);
|
||||
|
||||
res.send(stringify(alerts));
|
||||
res.send(alerts);
|
||||
}
|
||||
|
||||
export async function fetchAlertsGraphql(query, req) {
|
||||
@@ -159,7 +158,7 @@ export async function fetchAlertsGraphql(query, req) {
|
||||
export async function createAlertApi(req, res) {
|
||||
const alert = await createAlert(req.body, req.user);
|
||||
|
||||
res.send(stringify(alert));
|
||||
res.send(alert);
|
||||
}
|
||||
|
||||
export async function createAlertGraphql(query, req) {
|
||||
@@ -185,7 +184,7 @@ export async function fetchNotificationsApi(req, res) {
|
||||
limit: req.query.limit || 10,
|
||||
});
|
||||
|
||||
res.send(stringify(notifications));
|
||||
res.send(notifications);
|
||||
}
|
||||
|
||||
export async function fetchNotificationsGraphql(query, req) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify';
|
||||
import crypto from 'node:crypto';
|
||||
import IPCIDR from 'ip-cidr';
|
||||
import argv from '../argv.js';
|
||||
|
||||
@@ -13,6 +13,46 @@ import {
|
||||
|
||||
import { fetchUser } from '../users.js';
|
||||
|
||||
// changes every time the process restarts, so a deploy invalidates all cached session users
|
||||
// even if they're within the staleness window below
|
||||
const runtimeId = crypto.randomUUID();
|
||||
const sessionStaleThreshold = 5 * 60 * 1000;
|
||||
|
||||
// spread into a new object rather than mutating the user we're about to hand back to the
|
||||
// client (loginApi/signupApi send this straight to the browser) — runtimeId/refreshedAt are
|
||||
// session bookkeeping only, they shouldn't leak into the API response
|
||||
function toSession(user) {
|
||||
return {
|
||||
...user,
|
||||
runtimeId,
|
||||
refreshedAt: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
function isSessionStale(sessionUser) {
|
||||
return sessionUser.runtimeId !== runtimeId || Date.now() - sessionUser.refreshedAt > sessionStaleThreshold;
|
||||
}
|
||||
|
||||
// concurrent requests for the same user (e.g. the burst of asset/API requests a single page
|
||||
// load fires) share one in-flight refresh instead of each independently hitting the DB
|
||||
const pendingSessionRefreshes = new Map();
|
||||
|
||||
async function refreshSessionUser(sessionUser) {
|
||||
const pending = pendingSessionRefreshes.get(sessionUser.id);
|
||||
|
||||
if (pending) {
|
||||
return pending;
|
||||
}
|
||||
|
||||
const refresh = fetchUser(sessionUser.id, {}, sessionUser)
|
||||
.then((user) => toSession(user))
|
||||
.finally(() => pendingSessionRefreshes.delete(sessionUser.id));
|
||||
|
||||
pendingSessionRefreshes.set(sessionUser.id, refresh);
|
||||
|
||||
return refresh;
|
||||
}
|
||||
|
||||
function getIp(req) {
|
||||
if (argv.ip) {
|
||||
return argv.ip;
|
||||
@@ -47,6 +87,10 @@ export async function setUserApi(req, res, next) {
|
||||
req.userIp = ip;
|
||||
|
||||
if (req.session.user) {
|
||||
if (isSessionStale(req.session.user)) {
|
||||
req.session.user = await refreshSessionUser(req.session.user);
|
||||
}
|
||||
|
||||
req.user = req.session.user;
|
||||
req.user.ip = ip;
|
||||
}
|
||||
@@ -55,7 +99,7 @@ export async function setUserApi(req, res, next) {
|
||||
}
|
||||
|
||||
export async function updateSessionUser(req) {
|
||||
const user = await fetchUser(req.session.user.id, {}, req.session.user);
|
||||
const user = toSession(await fetchUser(req.session.user.id, {}, req.session.user));
|
||||
|
||||
req.session.user = user;
|
||||
|
||||
@@ -66,8 +110,8 @@ export async function updateSessionUser(req) {
|
||||
export async function loginApi(req, res) {
|
||||
const user = await login(req.body, req.userIp);
|
||||
|
||||
req.session.user = user;
|
||||
res.send(stringify(user));
|
||||
req.session.user = toSession(user);
|
||||
res.send(user);
|
||||
}
|
||||
|
||||
export async function logoutApi(req, res) {
|
||||
@@ -83,20 +127,20 @@ export async function logoutApi(req, res) {
|
||||
export async function signupApi(req, res) {
|
||||
const user = await signup(req.body, req.userIp);
|
||||
|
||||
req.session.user = user;
|
||||
res.send(stringify(user));
|
||||
req.session.user = toSession(user);
|
||||
res.send(user);
|
||||
}
|
||||
|
||||
export async function fetchUserKeysApi(req, res) {
|
||||
const keys = await fetchUserKeys(req.user);
|
||||
|
||||
res.send(stringify(keys));
|
||||
res.send(keys);
|
||||
}
|
||||
|
||||
export async function createKeyApi(req, res) {
|
||||
const key = await createKey(req.user);
|
||||
|
||||
res.send(stringify(key));
|
||||
res.send(key);
|
||||
}
|
||||
|
||||
export async function removeUserKeyApi(req, res) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify';
|
||||
import { parseResolveInfo } from 'graphql-parse-resolve-info';
|
||||
|
||||
import { getIdsBySlug } from '../cache.js';
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
export async function fetchEntitiesApi(req, res) {
|
||||
const entities = await fetchEntities(req.query);
|
||||
|
||||
res.send(stringify(entities));
|
||||
res.send(entities);
|
||||
}
|
||||
|
||||
export const entitiesSchema = `
|
||||
|
||||
@@ -26,6 +26,7 @@ export default async function mainHandler(req, res, next) {
|
||||
id: req.user.id,
|
||||
username: req.user.username,
|
||||
email: req.user.email,
|
||||
settings: req.user.settings,
|
||||
role: req.user.role,
|
||||
abilities: req.user.abilities,
|
||||
avatar: req.user.avatar,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify';
|
||||
|
||||
import promiseProps from '../../utils/promise-props.js';
|
||||
import slugify from '../../utils/slugify.js';
|
||||
import { getIdsBySlug } from '../cache.js';
|
||||
@@ -35,7 +33,7 @@ export async function fetchMoviesApi(req, res) {
|
||||
limit: Number(req.query.limit) || 30,
|
||||
}, req.user, { restriction: req.restriction });
|
||||
|
||||
res.send(stringify({
|
||||
res.send({
|
||||
movies,
|
||||
aggYears,
|
||||
aggActors,
|
||||
@@ -43,7 +41,7 @@ export async function fetchMoviesApi(req, res) {
|
||||
aggChannels,
|
||||
limit,
|
||||
total,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchMovieApi(req, res) {
|
||||
@@ -53,7 +51,7 @@ export async function fetchMovieApi(req, res) {
|
||||
throw new HttpError(`No movie with ID ${req.params.movieId} found`, 404);
|
||||
}
|
||||
|
||||
res.send(stringify(movie));
|
||||
res.send(movie);
|
||||
}
|
||||
|
||||
export const moviesSchema = `
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify';
|
||||
import { Router } from 'express';
|
||||
|
||||
import promiseProps from '../../utils/promise-props.js';
|
||||
@@ -74,7 +73,7 @@ async function fetchScenesApi(req, res) {
|
||||
restriction: req.restriction,
|
||||
});
|
||||
|
||||
res.send(stringify({
|
||||
res.send({
|
||||
scenes,
|
||||
aggYears,
|
||||
aggActors,
|
||||
@@ -83,7 +82,7 @@ async function fetchScenesApi(req, res) {
|
||||
aggChannels,
|
||||
limit,
|
||||
total,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
export const scenesSchema = `
|
||||
@@ -261,7 +260,7 @@ async function fetchSceneApi(req, res) {
|
||||
throw new HttpError(`No scene with ID ${req.params.sceneId} found`, 404);
|
||||
}
|
||||
|
||||
res.send(stringify(scene));
|
||||
res.send(scene);
|
||||
}
|
||||
|
||||
export async function fetchScenesByIdGraphql(query, req) {
|
||||
@@ -281,7 +280,7 @@ export async function fetchScenesByIdGraphql(query, req) {
|
||||
async function fetchSceneRevisionsApi(req, res) {
|
||||
const revisions = await fetchSceneRevisions(Number(req.params.revisionId) || null, req.query, req.user);
|
||||
|
||||
res.send(stringify(revisions));
|
||||
res.send(revisions);
|
||||
}
|
||||
|
||||
async function createSceneRevisionApi(req, res) {
|
||||
|
||||
@@ -37,6 +37,7 @@ import { fetchMoviesApi } from './movies.js';
|
||||
import initRestrictionHandler from './restrictions.js';
|
||||
|
||||
import { scenesRouter } from './scenes.js';
|
||||
import serializeApiResponses from './serialize.js';
|
||||
|
||||
import { router as stashesRouter } from './stashes.js';
|
||||
import { syncRouter } from './sync.js';
|
||||
@@ -123,6 +124,8 @@ export default async function initServer() {
|
||||
res.sendFile(path.join(import.meta.dirname, '../../assets/consent.html'));
|
||||
});
|
||||
|
||||
router.use('/api/{*splat}', serializeApiResponses);
|
||||
|
||||
router.use('/api/{*splat}', async (req, _res, next) => {
|
||||
if (req.headers['api-user']) {
|
||||
req.user = await verifyKey(req.headers['api-user'], req.headers['api-key'], req);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify';
|
||||
import { Router } from 'express';
|
||||
|
||||
import {
|
||||
@@ -101,7 +100,7 @@ export const stashesSchema = `
|
||||
export async function fetchUserStashesApi(req, res) {
|
||||
const stashes = await fetchUserStashes(req.user.id, req.user);
|
||||
|
||||
res.send(stringify(stashes));
|
||||
res.send(stashes);
|
||||
}
|
||||
|
||||
export async function fetchUserStashesGraphql(query, req) {
|
||||
@@ -119,7 +118,7 @@ export async function fetchStashGraphql(query, req) {
|
||||
export async function createStashApi(req, res) {
|
||||
const stash = await createStash(req.body, req.user);
|
||||
|
||||
res.send(stringify(stash));
|
||||
res.send(stash);
|
||||
}
|
||||
|
||||
export async function createStashGraphql(query, req) {
|
||||
@@ -131,7 +130,7 @@ export async function createStashGraphql(query, req) {
|
||||
export async function updateStashApi(req, res) {
|
||||
const stash = await updateStash(Number(req.params.stashId), req.body, req.user);
|
||||
|
||||
res.send(stringify(stash));
|
||||
res.send(stash);
|
||||
}
|
||||
|
||||
export async function updateStashGraphql(query, req) {
|
||||
@@ -155,7 +154,7 @@ export async function removeStashGraphql(query, req) {
|
||||
export async function stashActorApi(req, res) {
|
||||
const stashed = await stashActor(req.body.actorId, Number(req.params.stashId), req.user);
|
||||
|
||||
res.send(stringify(stashed));
|
||||
res.send(stashed);
|
||||
}
|
||||
|
||||
export async function stashActorGraphql(query, req) {
|
||||
@@ -167,7 +166,7 @@ export async function stashActorGraphql(query, req) {
|
||||
export async function unstashActorApi(req, res) {
|
||||
const unstashed = await unstashActor(Number(req.params.actorId), Number(req.params.stashId), req.user);
|
||||
|
||||
res.send(stringify(unstashed));
|
||||
res.send(unstashed);
|
||||
}
|
||||
|
||||
export async function unstashActorGraphql(query, req) {
|
||||
@@ -179,7 +178,7 @@ export async function unstashActorGraphql(query, req) {
|
||||
export async function stashSceneApi(req, res) {
|
||||
const stashed = await stashScene(req.body.sceneId, Number(req.params.stashId), req.user);
|
||||
|
||||
res.send(stringify(stashed));
|
||||
res.send(stashed);
|
||||
}
|
||||
|
||||
export async function stashSceneGraphql(query, req) {
|
||||
@@ -191,7 +190,7 @@ export async function stashSceneGraphql(query, req) {
|
||||
export async function unstashSceneApi(req, res) {
|
||||
const unstashed = await unstashScene(Number(req.params.sceneId), Number(req.params.stashId), req.user);
|
||||
|
||||
res.send(stringify(unstashed));
|
||||
res.send(unstashed);
|
||||
}
|
||||
|
||||
export async function unstashSceneGraphql(query, req) {
|
||||
@@ -203,7 +202,7 @@ export async function unstashSceneGraphql(query, req) {
|
||||
export async function stashMovieApi(req, res) {
|
||||
const stashed = await stashMovie(req.body.movieId, Number(req.params.stashId), req.user);
|
||||
|
||||
res.send(stringify(stashed));
|
||||
res.send(stashed);
|
||||
}
|
||||
|
||||
export async function stashMovieGraphql(query, req) {
|
||||
@@ -215,7 +214,7 @@ export async function stashMovieGraphql(query, req) {
|
||||
export async function unstashMovieApi(req, res) {
|
||||
const unstashed = await unstashMovie(Number(req.params.movieId), Number(req.params.stashId), req.user);
|
||||
|
||||
res.send(stringify(unstashed));
|
||||
res.send(unstashed);
|
||||
}
|
||||
|
||||
export async function unstashMovieGraphql(query, req) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify';
|
||||
|
||||
import { fetchTags } from '../tags.js';
|
||||
|
||||
export async function fetchTagsApi(req, res) {
|
||||
@@ -9,5 +7,5 @@ export async function fetchTagsApi(req, res) {
|
||||
restriction: req.restriction,
|
||||
});
|
||||
|
||||
res.send(stringify(tags));
|
||||
res.send(tags);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify';
|
||||
import { Router } from 'express';
|
||||
|
||||
import {
|
||||
@@ -7,24 +6,33 @@ import {
|
||||
fetchUser,
|
||||
fetchUserTemplates,
|
||||
removeTemplate,
|
||||
updateSettings,
|
||||
} from '../users.js';
|
||||
|
||||
async function fetchUserApi(req, res) {
|
||||
const user = await fetchUser(req.params.userId, {}, req.user);
|
||||
|
||||
res.send(stringify(user));
|
||||
res.send(user);
|
||||
}
|
||||
|
||||
async function updateSettingsApi(req, res) {
|
||||
const updatedSettings = await updateSettings(req.body, req.user);
|
||||
|
||||
req.session.user.settings = updatedSettings;
|
||||
|
||||
res.send(updatedSettings);
|
||||
}
|
||||
|
||||
async function fetchUserTemplatesApi(req, res) {
|
||||
const templates = await fetchUserTemplates(req.user);
|
||||
|
||||
res.send(stringify(templates));
|
||||
res.send(templates);
|
||||
}
|
||||
|
||||
async function createTemplateApi(req, res) {
|
||||
const template = await createTemplate(req.body, req.user);
|
||||
|
||||
res.send(stringify(template));
|
||||
res.send(template);
|
||||
}
|
||||
|
||||
async function removeTemplateApi(req, res) {
|
||||
@@ -44,6 +52,8 @@ export const router = Router();
|
||||
router.get('/api/users/:userId', fetchUserApi);
|
||||
router.get('/api/users/:userId/templates', fetchUserTemplatesApi);
|
||||
|
||||
router.patch('/api/me/settings', updateSettingsApi);
|
||||
|
||||
router.post('/api/templates', createTemplateApi);
|
||||
router.delete('/api/templates/:templateId', removeTemplateApi);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user