Upgraded dependencies, bumped to Node 24.

This commit is contained in:
2026-07-12 05:27:14 +02:00
parent 2d4786a4fd
commit d745b10d24
181 changed files with 18720 additions and 23134 deletions
+61 -48
View File
@@ -1,29 +1,30 @@
import process from 'node:process';
import config from 'config';
import convert from 'convert';
import { differenceInYears } from 'date-fns';
import { unit } from 'mathjs';
import { MerkleJson } from 'merkle-json';
import moment from 'moment';
import { nanoid } from 'nanoid';
import omit from 'object.omit';
import convert from 'convert';
import unprint from 'unprint';
import initLogger from './logger.js';
import { knexOwner as knex, knexManticore } from './knex.js';
import redis from './redis.js';
import { utilsApi } from './manticore.js';
import { HttpError } from './errors.js';
import { fetchCountriesByAlpha2 } from './countries.js';
import { curateEntity } from './entities.js';
import { curateMedia } from './media.js';
import { curateStash } from './stashes.js';
import { interpolateProfiles, platformsByHostname } from '../common/actors.mjs';
import { resolvePlace } from '../common/geo.mjs';
import escape from '../utils/escape-manticore.js';
import slugify from '../utils/slugify.js';
import { curateRevision } from './revisions.js';
import { interpolateProfiles, platformsByHostname } from '../common/actors.mjs'; // eslint-disable-line import/namespace
import { resolvePlace } from '../common/geo.mjs'; // eslint-disable-line import/namespace
import { syncScenes, syncActors, syncStashes } from './sync.js';
import verifyAbility from '../utils/verify-ability.js';
import { fetchCountriesByAlpha2 } from './countries.js';
import { curateEntity } from './entities.js';
import { HttpError } from './errors.js';
import { knexOwner as knex, knexManticore } from './knex.js';
import initLogger from './logger.js';
import { querySql } from './manticore.js';
import { curateMedia } from './media.js';
import redis from './redis.js';
import { curateRevision } from './revisions.js';
import { curateStash } from './stashes.js';
import { syncActors, syncScenes, syncStashes } from './sync.js';
const logger = initLogger();
const mj = new MerkleJson();
@@ -79,10 +80,10 @@ export function curateActor(actor, context = {}) {
})) || [], // used for profile pages
alias: actor.alias
? {
id: actor.alias.id,
slug: actor.alias.slug,
name: actor.alias.name,
}
id: actor.alias.id,
slug: actor.alias.slug,
name: actor.alias.name,
}
: null,
gender: actor.gender,
age: actor.age,
@@ -213,20 +214,22 @@ export function sortActorsByGender(actors, context = {}) {
const titleSlug = slugify(context.title);
const titleActors = titleSlug ? genderActors.sort((actorA, actorB) => {
const actorASlug = actorA.slug.split('-')[0];
const actorBSlug = actorB.slug.split('-')[0];
const titleActors = titleSlug
? genderActors.sort((actorA, actorB) => {
const actorASlug = actorA.slug.split('-')[0];
const actorBSlug = actorB.slug.split('-')[0];
if (titleSlug.includes(actorASlug) && !titleSlug.includes(actorBSlug)) {
return -1;
}
if (titleSlug.includes(actorASlug) && !titleSlug.includes(actorBSlug)) {
return -1;
}
if (titleSlug.includes(actorBSlug) && !titleSlug.includes(actorASlug)) {
return 1;
}
if (titleSlug.includes(actorBSlug) && !titleSlug.includes(actorASlug)) {
return 1;
}
return 0;
}) : alphaActors;
return 0;
})
: alphaActors;
return titleActors;
}
@@ -303,14 +306,14 @@ export async function fetchActorsById(actorIds, options = {}, reqUser) {
.whereIn('actor_id', actorIds),
reqUser
? knex('stashes_actors')
.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_actors.actor_id', actorIds)
.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_actors.actor_id', actorIds)
: [],
reqUser
? knex('alerts_users_actors')
.where('user_id', reqUser.id)
.whereIn('actor_id', actorIds)
.where('user_id', reqUser.id)
.whereIn('actor_id', actorIds)
: [],
].slice(0, options.shallow ? 1 : -1));
@@ -395,20 +398,20 @@ async function queryManticoreSql(filters, options, _reqUser) {
builder
.innerJoin('actors', 'actors.id', 'actors_stashed.actor_id')
.where('stash_id', filters.stashId);
} else {
}
else {
builder.select(knex.raw('*, weight() as _score'));
}
if (filters.query) {
if (filters.query.charAt(0) === '#') {
builder.where('id', Number(escape(filters.query.slice(1))));
} else {
}
else {
builder.whereRaw(`match('@(name,aliases) :query:${filters.query.charAt(0) === '=' ? '' : '*'}', actors)`, { query: escape(filters.query) });
}
}
console.log('FILTERS', filters);
if (!filters.includeAliased) {
builder.where('alias_for', 0);
}
@@ -426,7 +429,8 @@ async function queryManticoreSql(filters, options, _reqUser) {
if (filters.gender === 'other') {
builder.whereNull('gender');
} else if (filters.gender) {
}
else if (filters.gender) {
builder.where('gender', filters.gender);
}
@@ -483,33 +487,39 @@ async function queryManticoreSql(filters, options, _reqUser) {
{ column: 'actors.slug', order: options.order[1] },
{ column: 'actors.entity_id', order: 'asc' },
]);
} else if (options.order?.[0] === 'likes') {
}
else if (options.order?.[0] === 'likes') {
builder.orderBy([
{ column: 'actors.stashed', order: options.order[1] },
{ column: 'actors.slug', order: 'asc' },
]);
} else if (options.order?.[0] === 'scenes') {
}
else if (options.order?.[0] === 'scenes') {
builder.orderBy([
{ column: 'actors.scenes', order: options.order[1] },
{ column: 'actors.slug', order: 'asc' },
]);
} else if (options.order?.[0] === 'results') {
}
else if (options.order?.[0] === 'results') {
builder.orderBy([
{ column: '_score', order: options.order[1] },
{ column: 'actors.stashed', order: 'desc' },
{ column: 'actors.slug', order: 'asc' },
]);
} else if (options.order?.[0] === 'stashed' && filters.stashId) {
}
else if (options.order?.[0] === 'stashed' && filters.stashId) {
builder.orderBy([
{ column: 'stashed_at', order: options.order[1] },
{ column: 'actors.slug', order: 'asc' },
]);
} else if (options.order) {
}
else if (options.order) {
builder.orderBy([
{ column: `actors.${options.order[0]}`, order: options.order[1] },
{ column: 'actors.slug', order: 'asc' },
]);
} else {
}
else {
builder.orderBy('actors.slug', 'asc');
builder.orderBy([
{ column: 'actors.slug', order: 'asc' },
@@ -535,11 +545,13 @@ async function queryManticoreSql(filters, options, _reqUser) {
console.log(curatedSqlQuery);
}
const results = await utilsApi.sql(curatedSqlQuery);
const results = await querySql(curatedSqlQuery);
const countries = results
.find((result) => (result.columns[0].country || result.columns[0]['actors.country']) && result.columns[1]['count(*)'])
?.data.map((row) => ({ key: row.country || row['actors.country'], doc_count: row['count(*)'] })).filter((country) => !!country.key)
?.data
.map((row) => ({ key: row.country || row['actors.country'], doc_count: row['count(*)'] }))
.filter((country) => !!country.key)
|| [];
const total = Number(results.at(-1).data.find((entry) => entry.Variable_name === 'total_found').Value);
@@ -742,7 +754,8 @@ export async function mergeActors(targetActorId, sourceActorIds, reqUser) {
*/
await trx.commit();
} catch (error) {
}
catch (error) {
await trx.rollback();
throw error;
+4 -2
View File
@@ -13,7 +13,8 @@ function getWatchUrl(scene) {
if (scene.network) {
return new URL(scene.network.url).href;
}
} catch (_error) {
}
catch (_error) {
// invalid URL
}
@@ -88,7 +89,8 @@ export function getAffiliateSceneUrl(scene) {
function getEntityUrl(entity) {
try {
return new URL(entity.url || entity.parent?.url).href;
} catch (_error) {
}
catch (_error) {
return null;
}
}
+4 -4
View File
@@ -1,10 +1,10 @@
import escapeRegexp from 'escape-string-regexp';
import promiseProps from '../utils/promise-props.js';
import { getIdsBySlug } from './cache.js';
import { HttpError } from './errors.js';
import { knexOwner as knex } from './knex.js';
import { fetchScenesById } from './scenes.js';
import { getIdsBySlug } from './cache.js';
import promiseProps from '../utils/promise-props.js';
import { HttpError } from './errors.js';
function curateAlert(alert, context = {}) {
return {
@@ -263,7 +263,7 @@ function curateNotification(notification, scenes) {
matchedTags: scene.tags.filter((tag) => notification.alert_tags.includes(tag.id)),
matchedEntity: [scene.channel, scene.network].find((entity) => notification.alert_entities.includes(entity?.id)) || null,
matchedExpressions: notification.alert_matches
.filter((match) => new RegExp(match.expression, 'ui').test(scene[match.property]))
.filter((match) => new RegExp(match.expression, 'iu').test(scene[match.property]))
.map((match) => ({
id: match.id,
property: match.property,
+9 -5
View File
@@ -1,4 +1,4 @@
import { parse } from '@brillout/json-serializer/parse'; /* eslint-disable-line import/extensions */
import { parse } from '@brillout/json-serializer/parse';
import events from '#/src/events.js';
const postHeaders = {
@@ -69,7 +69,8 @@ export async function get(path, query = {}, options = {}) {
showFeedback(false, options, body.statusMessage);
throw new Error(body.statusMessage);
} catch (error) {
}
catch (error) {
showFeedback(false, options, error.message);
throw error;
}
@@ -97,7 +98,8 @@ export async function post(path, data, options = {}) {
showFeedback(false, options, body.statusMessage);
throw new Error(body.statusMessage);
} catch (error) {
}
catch (error) {
showFeedback(false, options, error.message);
throw error;
}
@@ -125,7 +127,8 @@ export async function patch(path, data, options = {}) {
showFeedback(false, options, body.statusMessage);
throw new Error(body.statusMessage);
} catch (error) {
}
catch (error) {
showFeedback(false, options, error.message);
throw error;
}
@@ -153,7 +156,8 @@ export async function del(path, options = {}) {
showFeedback(false, options, body.statusMessage);
throw new Error(body.statusMessage);
} catch (error) {
}
catch (error) {
showFeedback(false, options, error.message);
throw error;
}
+1 -1
View File
@@ -1,6 +1,6 @@
import initServer from './web/server.js';
import { initCaches } from './cache.js';
import { initSyncCron } from './sync.js';
import initServer from './web/server.js';
async function init() {
await initCaches();
+1
View File
@@ -1,3 +1,4 @@
import process from 'node:process';
import yargs from 'yargs';
const { argv } = yargs(process.argv.slice(2))
+10 -9
View File
@@ -1,18 +1,18 @@
import config from 'config';
import util from 'util';
import crypto from 'crypto';
import fs from 'fs/promises';
import { createAvatar } from '@dicebear/core';
import crypto from 'node:crypto';
import fs from 'node:fs/promises';
import util from 'node:util';
import { shapes } from '@dicebear/collection';
import { createAvatar } from '@dicebear/core';
import { faker } from '@faker-js/faker';
import config from 'config';
import { verify } from 'hcaptcha';
import slugify from '../utils/slugify.js';
import { HttpError } from './errors.js';
import { knexOwner as knex } from './knex.js';
import initLogger, { initAccessLogger } from './logger.js';
import redis from './redis.js';
import { curateUser, fetchUser } from './users.js';
import { HttpError } from './errors.js';
import slugify from '../utils/slugify.js';
import initLogger, { initAccessLogger } from './logger.js';
const logger = initLogger();
const accessLogger = initAccessLogger();
@@ -67,7 +67,8 @@ export async function login(credentials, userIp) {
try {
await fs.access(`media/avatars/${user.id}_${user.username}.png`);
} catch (error) {
}
catch (error) {
await generateAvatar(user);
}
+4 -4
View File
@@ -1,9 +1,9 @@
import redis from './redis.js';
import { cacheTagIds } from './tags.js';
import { cacheEntityIds } from './entities.js';
import { cacheCampaigns } from './campaigns.js';
import { cacheEntityIds } from './entities.js';
import redis from './redis.js';
import { cacheTagIds } from './tags.js';
export async function getIdsBySlug(slugs, domain, toMap) {
if (!slugs) {
return [];
+5 -5
View File
@@ -1,10 +1,10 @@
import crypto from 'crypto';
import crypto from 'node:crypto';
import { knexOwner as knex } from './knex.js';
import { curateEntity } from './entities.js';
import redis from './redis.js';
import initLogger from './logger.js';
import { getAffiliateEntityUrl } from './affiliates.js';
import { curateEntity } from './entities.js';
import { knexOwner as knex } from './knex.js';
import initLogger from './logger.js';
import redis from './redis.js';
const logger = initLogger();
+3 -3
View File
@@ -1,12 +1,12 @@
import config from 'config';
import {
TextCensor,
RegExpMatcher,
DataSet,
englishDataset,
englishRecommendedTransformers,
DataSet,
pattern,
RegExpMatcher,
TextCensor,
// asteriskCensorStrategy,
} from 'obscenity';
+24 -20
View File
@@ -1,9 +1,9 @@
import knex from './knex.js';
import redis from './redis.js';
import initLogger from './logger.js';
import entityPrefixes from './entities-prefixes.js';
import { getAffiliateEntityUrl } from './affiliates.js';
import { censor } from './censor.js';
import entityPrefixes from './entities-prefixes.js';
import knex from './knex.js';
import initLogger from './logger.js';
import redis from './redis.js';
const logger = initLogger();
@@ -33,12 +33,14 @@ export function curateEntity(entity, context = {}) {
parent: entity,
restriction: context.restriction,
})) || [],
affiliate: entity.affiliate ? {
id: entity.affiliate.id,
entityId: entity.affiliate.entity_id,
url: entity.affiliate.url,
parameters: entity.affiliate.parameters || {},
} : null,
affiliate: entity.affiliate
? {
id: entity.affiliate.id,
entityId: entity.affiliate.entity_id,
url: entity.affiliate.url,
parameters: entity.affiliate.parameters || {},
}
: null,
...context?.append?.[entity.id],
alerts: {
only: context?.alerts?.filter((alert) => alert.is_only).flatMap((alert) => alert.alert_ids) || [],
@@ -123,22 +125,24 @@ export async function fetchEntitiesById(entityIds, options = {}, reqUser, contex
}
})
.groupBy('entities.id', 'parents.id', 'affiliates.id', 'affiliates.entity_id', 'network_affiliates.id', 'network_affiliates.entity_id'),
options.includeChildren ? knex('entities')
.whereIn('entities.parent_id', entityIds)
.whereNot('type', 'info')
.orderBy([
{ column: knex.raw('array_position(array[\'network\', \'channel\']::varchar[], type)'), order: 'asc' },
{ column: 'independent', order: 'desc' },
{ column: 'slug', order: 'asc' },
]) : [],
options.includeChildren
? knex('entities')
.whereIn('entities.parent_id', entityIds)
.whereNot('type', 'info')
.orderBy([
{ column: knex.raw('array_position(array[\'network\', \'channel\']::varchar[], type)'), order: 'asc' },
{ column: 'independent', order: 'desc' },
{ column: 'slug', order: 'asc' },
])
: [],
knex('entities_tags')
.select('entity_id', 'tags.*')
.leftJoin('tags', 'tags.id', 'tag_id')
.whereIn('entity_id', entityIds),
reqUser
? knex('alerts_users_entities')
.where('user_id', reqUser.id)
.whereIn('entity_id', entityIds)
.where('user_id', reqUser.id)
.whereIn('entity_id', entityIds)
: [],
]);
+1
View File
@@ -1,3 +1,4 @@
import process from 'node:process';
import config from 'config';
import knex from 'knex';
+8 -5
View File
@@ -1,13 +1,13 @@
import util from 'util';
import path from 'path';
import path from 'node:path';
import util from 'node:util';
import stackParser from 'error-stack-parser';
import * as winston from 'winston';
import 'winston-daily-rotate-file';
import stackParser from 'error-stack-parser';
// import args from './args';
export default function initLogger(customLabel) {
const filepath = stackParser.parse(new Error())[1]?.fileName;
const filepath = stackParser.parse(new Error('stack'))[1]?.fileName;
const contextLabel = customLabel || path.basename(filepath, '.js');
return winston.createLogger({
@@ -18,7 +18,10 @@ export default function initLogger(customLabel) {
: { ...info, message: typeof info.message === 'string' ? info.message : util.inspect(info.message) }))(),
winston.format.colorize(),
winston.format.printf(({
level, timestamp, label, message,
level,
timestamp,
label,
message,
}) => `${timestamp} ${level} [${label || contextLabel}] ${message}`),
),
transports: [
+8
View File
@@ -8,3 +8,11 @@ mantiClient.basePath = `http://${config.database.manticore.host}:${config.databa
export const searchApi = new manticore.SearchApi(mantiClient);
export const indexApi = new manticore.IndexApi(mantiClient);
export const utilsApi = new manticore.UtilsApi();
// manticoresearch v10's utilsApi.sql() wraps the response in a `SqlResponse` oneOf model instead of
// returning the parsed array/object directly; unwrap it to keep the previous plain data shape.
export async function querySql(query) {
const response = await utilsApi.sql(query);
return response.getActualInstance();
}
+4 -4
View File
@@ -34,10 +34,10 @@ export function curateMedia(media, context = {}) {
isRestricted: context.isRestricted,
biometrics: media.biometrics
? {
...media.biometrics.biometrics,
width: media.biometrics.width,
height: media.biometrics.height,
}
...media.biometrics.biometrics,
width: media.biometrics.width,
height: media.biometrics.height,
}
: null,
createdAt: media.created_at,
};
+60 -44
View File
@@ -1,16 +1,17 @@
import process from 'node:process';
import config from 'config';
import { knexQuery as knex, knexOwner, knexManticore } from './knex.js';
import { utilsApi } from './manticore.js';
import { HttpError } from './errors.js';
import { fetchActorsById, curateActor, sortActorsByGender } from './actors.js';
import { curateMedia } from './media.js';
import { fetchTagsById } from './tags.js';
import { fetchEntitiesById } from './entities.js';
import { curateStash } from './stashes.js';
import { censor } from './censor.js';
import escape from '../utils/escape-manticore.js';
import promiseProps from '../utils/promise-props.js';
import { curateActor, fetchActorsById, sortActorsByGender } from './actors.js';
import { censor } from './censor.js';
import { fetchEntitiesById } from './entities.js';
import { HttpError } from './errors.js';
import { knexQuery as knex, knexManticore, knexOwner } from './knex.js';
import { querySql } from './manticore.js';
import { curateMedia } from './media.js';
import { curateStash } from './stashes.js';
import { fetchTagsById } from './tags.js';
function curateMovie(rawMovie, assets, context = {}) {
if (!rawMovie) {
@@ -36,13 +37,15 @@ function curateMovie(rawMovie, assets, context = {}) {
isIndependent: assets.channel.independent,
hasLogo: assets.channel.has_logo,
},
network: assets.channel.network_id ? {
id: assets.channel.network_id,
slug: assets.channel.network_slug,
name: censor(assets.channel.network_name, context.restriction),
type: assets.channel.network_type,
hasLogo: assets.channel.has_logo,
} : null,
network: assets.channel.network_id
? {
id: assets.channel.network_id,
slug: assets.channel.network_slug,
name: censor(assets.channel.network_name, context.restriction),
type: assets.channel.network_type,
hasLogo: assets.channel.has_logo,
}
: null,
actors: sortActorsByGender(assets.actors.map((actor) => curateActor(actor, { sceneDate: rawMovie.effective_date }))),
directors: assets.directors.map((director) => ({
id: director.id,
@@ -130,20 +133,22 @@ export async function fetchMoviesById(movieIds, reqUser, context = {}) {
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
.orderBy('media.index')
.groupBy('media.id', 'movies_covers.movie_id', 'sfw_media.id'),
photos: context.restriction ? [] : knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
photos: context.restriction
? []
: knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('movies_scenes')
.select('media.*', 'movies_scenes.movie_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
.whereIn('movies_scenes.movie_id', movieIds)
.whereNotNull('media.id')
.leftJoin('releases_photos', 'releases_photos.release_id', 'movies_scenes.scene_id')
.leftJoin('media', 'media.id', 'releases_photos.media_id')
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
.groupBy('media.id', 'movies_scenes.movie_id', 'sfw_media.id');
}),
return trx('movies_scenes')
.select('media.*', 'movies_scenes.movie_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
.whereIn('movies_scenes.movie_id', movieIds)
.whereNotNull('media.id')
.leftJoin('releases_photos', 'releases_photos.release_id', 'movies_scenes.scene_id')
.leftJoin('media', 'media.id', 'releases_photos.media_id')
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
.groupBy('media.id', 'movies_scenes.movie_id', 'sfw_media.id');
}),
caps: knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
@@ -163,9 +168,9 @@ export async function fetchMoviesById(movieIds, reqUser, context = {}) {
.leftJoin('media', 'media.id', 'movies_trailers.media_id'),
stashes: reqUser
? knexOwner('stashes_movies')
.leftJoin('stashes', 'stashes.id', 'stashes_movies.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_movies.movie_id', movieIds)
.leftJoin('stashes', 'stashes.id', 'stashes_movies.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_movies.movie_id', movieIds)
: [],
});
@@ -267,7 +272,8 @@ async function queryManticoreSql(filters, options) {
builder
.innerJoin('movies', 'movies.id', 'movies_stashed.movie_id')
.where('stash_id', filters.stashId);
} else {
}
else {
builder.select(knex.raw(`
*,
year(movies.effective_date) as effective_year,
@@ -311,32 +317,38 @@ async function queryManticoreSql(filters, options) {
builder
.where('effective_date', '<=', Math.round(Date.now() / 1000))
.orderBy('movies.effective_date', 'desc'); // can't seem to use alias if it matches column-name? behavior not fully understand, but this works
} else if (filters.scope === 'upcoming') {
}
else if (filters.scope === 'upcoming') {
builder
.where('effective_date', '>', Math.round(Date.now() / 1000))
.orderBy('movies.effective_date', 'asc');
} else if (filters.scope === 'new') {
}
else if (filters.scope === 'new') {
builder.orderBy([
{ column: 'movies.created_at', order: 'desc' },
{ column: 'movies.effective_date', order: 'asc' },
]);
} else if (filters.scope === 'likes') {
}
else if (filters.scope === 'likes') {
builder.orderBy([
{ column: 'movies.stashed', order: 'desc' },
{ column: 'movies.effective_date', order: 'desc' },
]);
} else if (filters.scope === 'results') {
}
else if (filters.scope === 'results') {
builder.orderBy([
{ column: '_score', order: 'desc' },
{ column: 'movies.stashed', order: 'desc' },
{ column: 'movies.effective_date', order: 'desc' },
]);
} else if (filters.scope === 'stashed' && filters.stashId) {
}
else if (filters.scope === 'stashed' && filters.stashId) {
builder.orderBy([
{ column: 'stashed_at', order: 'desc' },
{ column: 'movies.effective_date', order: 'desc' },
]);
} else {
}
else {
builder.orderBy('movies.effective_date', 'desc');
}
})
@@ -361,27 +373,31 @@ async function queryManticoreSql(filters, options) {
console.log(curatedSqlQuery);
}
const results = await utilsApi.sql(curatedSqlQuery);
const results = await querySql(curatedSqlQuery);
// console.log(results[0]);
const years = results
.find((result) => (result.columns[0].years || result.columns[0]['movies.years']) && result.columns[1]['count(*)'])
?.data.map((row) => ({ key: row.years || row['movies.years'], doc_count: row['count(*)'] }))
?.data
.map((row) => ({ key: row.years || row['movies.years'], doc_count: row['count(*)'] }))
|| [];
const actorIds = results
.find((result) => (result.columns[0].actor_ids || result.columns[0]['movies.actor_ids']) && result.columns[1]['count(*)'])
?.data.map((row) => ({ key: row.actor_ids || row['movies.actor_ids'], doc_count: row['count(*)'] }))
?.data
.map((row) => ({ key: row.actor_ids || row['movies.actor_ids'], doc_count: row['count(*)'] }))
|| [];
const tagIds = results
.find((result) => (result.columns[0].tag_ids || result.columns[0]['movies.tag_ids']) && result.columns[1]['count(*)'])
?.data.map((row) => ({ key: row.tag_ids || row['movies.tag_ids'], doc_count: row['count(*)'] }))
?.data
.map((row) => ({ key: row.tag_ids || row['movies.tag_ids'], doc_count: row['count(*)'] }))
|| [];
const channelIds = results
.find((result) => (result.columns[0].channel_id || result.columns[0]['movies.channel_id']) && result.columns[1]['count(*)'])
?.data.map((row) => ({ key: row.channel_id || row['movies.channel_id'], doc_count: row['count(*)'] }))
?.data
.map((row) => ({ key: row.channel_id || row['movies.channel_id'], doc_count: row['count(*)'] }))
|| [];
const total = Number(results.at(-1).data.find((entry) => entry.Variable_name === 'total_found')?.Value) || 0;
+6 -4
View File
@@ -16,10 +16,12 @@ export default function navigate(path, query, options = {}) {
if (options.redirect) {
window.location.href = url;
} else if (options.replace) {
history.replaceState({}, '', url); // eslint-disable-line no-restricted-globals
} else {
history.pushState({}, '', url); // eslint-disable-line no-restricted-globals
}
else if (options.replace) {
history.replaceState({}, '', url);
}
else {
history.pushState({}, '', url);
}
events.emit('route', url);
+8 -6
View File
@@ -11,12 +11,14 @@ export function curateRevision(revision) {
id: revision.user_id,
username: revision.username,
},
review: typeof revision.approved === 'boolean' ? {
isApproved: revision.approved,
userId: revision.reviewed_by,
username: revision.reviewer_username,
reviewedAt: revision.reviewed_at,
} : null,
review: typeof revision.approved === 'boolean'
? {
isApproved: revision.approved,
userId: revision.reviewed_by,
username: revision.reviewer_username,
reviewedAt: revision.reviewed_at,
}
: null,
appliedAt: revision.applied_at,
failed: revision.failed,
createdAt: revision.created_at,
+152 -120
View File
@@ -1,23 +1,24 @@
import process from 'node:process';
import config from 'config';
import { MerkleJson } from 'merkle-json';
import argv from './argv.js';
import { knexQuery as knex, knexOwner, knexManticore } from './knex.js';
import { utilsApi } from './manticore.js';
import { HttpError } from './errors.js';
import { fetchActorsById, curateActor, sortActorsByGender } from './actors.js';
import { fetchTagsById } from './tags.js';
import { fetchMoviesById } from './movies.js';
import { fetchEntitiesById } from './entities.js';
import { curateStash } from './stashes.js';
import { curateMedia } from './media.js';
import escape from '../utils/escape-manticore.js';
import promiseProps from '../utils/promise-props.js';
import initLogger from './logger.js';
import { curateRevision } from './revisions.js';
import { curateActor, fetchActorsById, sortActorsByGender } from './actors.js';
import { getAffiliateSceneUrl } from './affiliates.js';
import argv from './argv.js';
import { censor } from './censor.js';
import { fetchEntitiesById } from './entities.js';
import { HttpError } from './errors.js';
import { knexQuery as knex, knexManticore, knexOwner } from './knex.js';
import initLogger from './logger.js';
import { querySql } from './manticore.js';
import { curateMedia } from './media.js';
import { fetchMoviesById } from './movies.js';
import { curateRevision } from './revisions.js';
import { curateStash } from './stashes.js';
import { syncScenes } from './sync.js';
import { fetchTagsById } from './tags.js';
const logger = initLogger();
const mj = new MerkleJson();
@@ -50,27 +51,33 @@ function curateScene(rawScene, assets, reqUser, context) {
isIndependent: assets.channel.independent,
hasLogo: assets.channel.has_logo,
},
network: assets.channel.network_id ? {
id: assets.channel.network_id,
slug: assets.channel.network_slug,
name: censor(assets.channel.network_name, context.restriction),
url: assets.network_url,
type: assets.channel.network_type,
hasLogo: assets.channel.network_has_logo,
} : null,
studio: assets.studio ? {
id: assets.studio.id,
slug: assets.studio.slug,
name: assets.studio.name,
type: assets.studio.type,
hasLogo: assets.studio.has_logo,
} : null,
affiliate: assets.channel.affiliate ? {
id: assets.channel.affiliate.id,
url: assets.channel.affiliate.url,
parameters: assets.channel.affiliate.parameters || {},
entityId: assets.channel.affiliate.entity_id,
} : null,
network: assets.channel.network_id
? {
id: assets.channel.network_id,
slug: assets.channel.network_slug,
name: censor(assets.channel.network_name, context.restriction),
url: assets.network_url,
type: assets.channel.network_type,
hasLogo: assets.channel.network_has_logo,
}
: null,
studio: assets.studio
? {
id: assets.studio.id,
slug: assets.studio.slug,
name: assets.studio.name,
type: assets.studio.type,
hasLogo: assets.studio.has_logo,
}
: null,
affiliate: assets.channel.affiliate
? {
id: assets.channel.affiliate.id,
url: assets.channel.affiliate.url,
parameters: assets.channel.affiliate.parameters || {},
entityId: assets.channel.affiliate.entity_id,
}
: null,
actors: sortActorsByGender(assets.actors.map((actor) => curateActor(actor, {
sceneDate: rawScene.effective_date,
stashes: assets.actorStashes.filter((actorStash) => actorStash.actor_id === actor.id),
@@ -246,84 +253,98 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
.whereIn('chapters.release_id', sceneIds)
.groupBy('chapters.id', 'posters.id')
.orderBy('time', 'asc'),
movies: context.includePartOf ? knex('movies_scenes')
.select('movies_scenes.scene_id', 'movies.*', knex.raw('json_agg(media) as movie_covers'))
.leftJoin('movies', 'movies.id', 'movies_scenes.movie_id')
.leftJoin('movies_covers', 'movies_covers.movie_id', 'movies.id')
.leftJoin('media', 'media.id', 'movies_covers.media_id')
.whereIn('scene_id', sceneIds)
.groupBy('movies.id', 'movies_scenes.scene_id') : [],
series: context.includePartOf ? knex('series_scenes')
.select('series_scenes.scene_id', 'series.*', knex.raw('row_to_json(media) as serie_poster'))
.leftJoin('series', 'series.id', 'series_scenes.serie_id')
.leftJoin('series_posters', 'series_posters.serie_id', 'series.id')
.leftJoin('media', 'media.id', 'series_posters.media_id')
.whereIn('scene_id', sceneIds)
.groupBy('series.id', 'series_scenes.scene_id', 'media.*') : [],
movies: context.includePartOf
? knex('movies_scenes')
.select('movies_scenes.scene_id', 'movies.*', knex.raw('json_agg(media) as movie_covers'))
.leftJoin('movies', 'movies.id', 'movies_scenes.movie_id')
.leftJoin('movies_covers', 'movies_covers.movie_id', 'movies.id')
.leftJoin('media', 'media.id', 'movies_covers.media_id')
.whereIn('scene_id', sceneIds)
.groupBy('movies.id', 'movies_scenes.scene_id')
: [],
series: context.includePartOf
? knex('series_scenes')
.select('series_scenes.scene_id', 'series.*', knex.raw('row_to_json(media) as serie_poster'))
.leftJoin('series', 'series.id', 'series_scenes.serie_id')
.leftJoin('series_posters', 'series_posters.serie_id', 'series.id')
.leftJoin('media', 'media.id', 'series_posters.media_id')
.whereIn('scene_id', sceneIds)
.groupBy('series.id', 'series_scenes.scene_id', 'media.*')
: [],
posters: knex('releases_posters')
.select('media.*', 'releases_posters.release_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
.whereIn('release_id', sceneIds)
.leftJoin('media', 'media.id', 'releases_posters.media_id')
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
.groupBy('media.id', 'releases_posters.release_id', 'sfw_media.id'),
photos: context.includeAssets && !context.restriction ? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
photos: context.includeAssets && !context.restriction
? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_photos')
.select('media.*', 'releases_photos.release_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
.leftJoin('media', 'media.id', 'releases_photos.media_id')
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
.whereIn('release_id', sceneIds)
.orderBy('index')
.groupBy('media.id', 'releases_photos.release_id', 'sfw_media.id');
}) : [],
caps: context.includeAssets ? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_photos')
.select('media.*', 'releases_photos.release_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
.leftJoin('media', 'media.id', 'releases_photos.media_id')
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
.whereIn('release_id', sceneIds)
.orderBy('index')
.groupBy('media.id', 'releases_photos.release_id', 'sfw_media.id');
})
: [],
caps: context.includeAssets
? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_caps')
.select('media.*', 'releases_caps.release_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
.leftJoin('media', 'media.id', 'releases_caps.media_id')
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
.whereIn('release_id', sceneIds)
.orderBy('index')
.groupBy('media.id', 'releases_caps.release_id', 'sfw_media.id');
}) : [],
trailers: context.includeAssets ? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_caps')
.select('media.*', 'releases_caps.release_id', knex.raw('row_to_json(sfw_media) as sfw_media'))
.leftJoin('media', 'media.id', 'releases_caps.media_id')
.leftJoin('media as sfw_media', 'sfw_media.id', 'media.sfw_media_id')
.whereIn('release_id', sceneIds)
.orderBy('index')
.groupBy('media.id', 'releases_caps.release_id', 'sfw_media.id');
})
: [],
trailers: context.includeAssets
? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_trailers')
.whereIn('release_id', sceneIds)
.leftJoin('media', 'media.id', 'releases_trailers.media_id');
}) : [],
teasers: context.includeAssets ? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_trailers')
.whereIn('release_id', sceneIds)
.leftJoin('media', 'media.id', 'releases_trailers.media_id');
})
: [],
teasers: context.includeAssets
? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_teasers')
.whereIn('release_id', sceneIds)
.leftJoin('media', 'media.id', 'releases_teasers.media_id');
}) : [],
fingerprints: context.includeAssets ? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_teasers')
.whereIn('release_id', sceneIds)
.leftJoin('media', 'media.id', 'releases_teasers.media_id');
})
: [],
fingerprints: context.includeAssets
? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_fingerprints')
.select('scene_id', 'hash', 'type', 'duration', 'source', 'source_submissions', knex.raw('min(coalesce(source_created_at, created_at)) as created_at'))
.whereIn('scene_id', sceneIds)
.orderBy([
{ column: 'source_submissions', order: 'desc' },
{ column: knex.raw('min(coalesce(source_created_at, created_at))'), order: 'desc' },
])
.groupBy(['scene_id', 'hash', 'type', 'duration', 'source', 'source_submissions']);
}) : [],
return trx('releases_fingerprints')
.select('scene_id', 'hash', 'type', 'duration', 'source', 'source_submissions', knex.raw('min(coalesce(source_created_at, created_at)) as created_at'))
.whereIn('scene_id', sceneIds)
.orderBy([
{ column: 'source_submissions', order: 'desc' },
{ column: knex.raw('min(coalesce(source_created_at, created_at))'), order: 'desc' },
])
.groupBy(['scene_id', 'hash', 'type', 'duration', 'source', 'source_submissions']);
})
: [],
lastBatch: knex('batches')
.select('id')
.where('showcased', true)
@@ -331,17 +352,17 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
.first(),
stashes: reqUser
? knexOwner('stashes_scenes')
.leftJoin('stashes', 'stashes.id', 'stashes_scenes.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_scenes.scene_id', sceneIds)
.leftJoin('stashes', 'stashes.id', 'stashes_scenes.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_scenes.scene_id', sceneIds)
: [],
});
const actorStashes = reqUser && context.actorStashes
? await knexOwner('stashes_actors')
.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_actors.actor_id', actors.map((actor) => actor.id))
.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_actors.actor_id', actors.map((actor) => actor.id))
: [];
return sceneIds.map((sceneId) => {
@@ -415,7 +436,8 @@ function curateOptions(options) {
function curateFacet(results, field) {
return results
.find((result) => result.columns[0][field] && (result.columns[1]['count(distinct id)'] || result.columns[1]['count(*)']))
?.data.map((row) => ({ key: row[field], doc_count: row['count(distinct id)'] || row['count(*)'] }))
?.data
.map((row) => ({ key: row[field], doc_count: row['count(distinct id)'] || row['count(*)'] }))
.filter((row) => !!row.key)
|| [];
}
@@ -482,7 +504,8 @@ async function queryManticoreSql(filters, options, _reqUser) {
builder
.innerJoin('scenes', 'scenes.id', 'scenes_stashed.scene_id')
.where('stash_id', filters.stashId);
} else {
}
else {
builder.select(knex.raw(`
*,
year(scenes.effective_date) as effective_year,
@@ -508,7 +531,8 @@ async function queryManticoreSql(filters, options, _reqUser) {
whereBuilder.orWhere('any(assigned_tag_ids)', actorId * 1_000_00 + tagId);
});
});
} else {
}
else {
builder.where('any(tag_ids)', tagId);
}
});
@@ -561,34 +585,41 @@ async function queryManticoreSql(filters, options, _reqUser) {
builder
.where('effective_date', '<=', Math.round(Date.now() / 1000))
.orderBy('scenes.effective_date', 'desc'); // can't seem to use alias if it matches column-name? behavior not fully understand, but this works
} else if (filters.scope === 'upcoming') {
}
else if (filters.scope === 'upcoming') {
builder
.where('effective_date', '>', Math.round(Date.now() / 1000))
.orderBy('scenes.effective_date', 'asc');
} else if (filters.scope === 'new') {
}
else if (filters.scope === 'new') {
builder.orderBy([
{ column: 'scenes.created_at', order: 'desc' },
{ column: 'scenes.effective_date', order: 'asc' },
]);
} else if (filters.scope === 'likes') {
}
else if (filters.scope === 'likes') {
builder.orderBy([
{ column: 'scenes.stashed', order: 'desc' },
{ column: 'scenes.effective_date', order: 'desc' },
]);
} else if (filters.scope === 'results') {
}
else if (filters.scope === 'results') {
builder.orderBy([
{ column: '_score', order: 'desc' },
{ column: 'scenes.stashed', order: 'desc' },
{ column: 'scenes.effective_date', order: 'desc' },
]);
} else if (filters.scope === 'stashed' && filters.stashId) {
}
else if (filters.scope === 'stashed' && filters.stashId) {
builder.orderBy([
{ column: 'stashed_at', order: 'desc' },
{ column: 'scenes.effective_date', order: 'desc' },
]);
} else if (filters.scope === 'oldest') {
}
else if (filters.scope === 'oldest') {
builder.orderBy('scenes.effective_date', 'asc');
} else {
}
else {
builder.orderBy('scenes.effective_date', 'desc');
}
})
@@ -608,14 +639,14 @@ async function queryManticoreSql(filters, options, _reqUser) {
const curatedSqlQuery = filters.stashId
? sqlQuery
: sqlQuery
.replace(/scenes\./g, '')
.replace(/scenes_\./g, 'scenes.');
.replace(/scenes\./g, '')
.replace(/scenes_\./g, 'scenes.');
if (process.env.NODE_ENV === 'development' && argv.debug) {
console.log(curatedSqlQuery);
}
const results = await utilsApi.sql(curatedSqlQuery);
const results = await querySql(curatedSqlQuery);
const years = curateFacet(results, 'years_facet');
const actorIds = curateFacet(results, 'actors_facet');
@@ -952,7 +983,8 @@ export async function reviewSceneRevision(revisionId, isApproved, { feedback },
if (isApproved) {
try {
await applySceneRevision([revisionId], reqUser);
} catch (error) {
}
catch (error) {
await knexOwner('scenes_revisions')
.where('id', revisionId)
.update({
+14 -12
View File
@@ -1,8 +1,8 @@
import { knexQuery as knex, knexOwner } from './knex.js';
import promiseProps from '../utils/promise-props.js';
import { curateActor, sortActorsByGender } from './actors.js';
import { knexQuery as knex, knexOwner } from './knex.js';
import { curateMedia } from './media.js';
import { curateStash } from './stashes.js';
import promiseProps from '../utils/promise-props.js';
function curateSerie(rawSerie, assets) {
if (!rawSerie) {
@@ -27,13 +27,15 @@ function curateSerie(rawSerie, assets) {
isIndependent: assets.channel.independent,
hasLogo: assets.channel.has_logo,
},
network: assets.channel.network_id ? {
id: assets.channel.network_id,
slug: assets.channel.network_slug,
name: assets.channel.network_name,
type: assets.channel.network_type,
hasLogo: assets.channel.has_logo,
} : null,
network: assets.channel.network_id
? {
id: assets.channel.network_id,
slug: assets.channel.network_slug,
name: assets.channel.network_name,
type: assets.channel.network_type,
hasLogo: assets.channel.has_logo,
}
: null,
actors: sortActorsByGender(assets.actors.map((actor) => curateActor(actor, { sceneDate: rawSerie.effective_date }))),
directors: assets.directors.map((director) => ({
id: director.id,
@@ -116,9 +118,9 @@ export async function fetchSeriesById(serieIds, reqUser) {
.leftJoin('media', 'media.id', 'releases_photos.media_id'),
stashes: reqUser
? knexOwner('stashes_series')
.leftJoin('stashes', 'stashes.id', 'stashes_series.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_series.serie_id', serieIds)
.leftJoin('stashes', 'stashes.id', 'stashes_series.stash_id')
.where('stashes.user_id', reqUser.id)
.whereIn('stashes_series.serie_id', serieIds)
: [],
});
+38 -27
View File
@@ -1,11 +1,11 @@
import config from 'config';
import { CronJob } from 'cron';
import { knexOwner as knex } from './knex.js';
import { indexApi } from './manticore.js';
import { HttpError } from './errors.js';
import slugify from './utils/slugify.js';
import { knexOwner as knex } from './knex.js';
import initLogger from './logger.js';
import { indexApi } from './manticore.js';
import slugify from './utils/slugify.js';
const logger = initLogger();
@@ -29,12 +29,14 @@ export function curateStash(stash, assets = {}) {
stashedScenes: stash.stashed_scenes ?? null,
stashedMovies: stash.stashed_movies ?? null,
stashedActors: stash.stashed_actors ?? null,
user: assets.user ? {
id: assets.user.id,
username: assets.user.username,
avatar: `/media/avatars/${assets.user.id}_${assets.user.username}.png`,
createdAt: assets.user.created_at,
} : null,
user: assets.user
? {
id: assets.user.id,
username: assets.user.username,
avatar: `/media/avatars/${assets.user.id}_${assets.user.username}.png`,
createdAt: assets.user.created_at,
}
: null,
comment: stash.comment,
meta: stash.meta,
};
@@ -81,7 +83,8 @@ export async function fetchStashById(stashIdOrSlug, sessionUser) {
.where((builder) => {
if (typeof stashIdOrSlug === 'number') {
builder.where('id', stashIdOrSlug);
} else {
}
else {
builder
.where('slug', stashIdOrSlug)
.where('user_id', sessionUser.id);
@@ -99,7 +102,8 @@ export async function fetchStashByUsernameAndSlug(usernameOrId, stashSlug, sessi
.where((builder) => {
if (typeof usernameOrId === 'number') {
builder.where('id', usernameOrId);
} else {
}
else {
builder.where('username', usernameOrId);
}
})
@@ -125,9 +129,9 @@ export async function fetchUserStashes(usernameOrId, reqUser) {
const userId = typeof usernameOrId === 'number'
? usernameOrId
: await knex('users')
.where('username', usernameOrId)
.first()
.then((user) => user?.id);
.where('username', usernameOrId)
.first()
.then((user) => user?.id);
if (!userId) {
throw new HttpError(`Could not find user '${usernameOrId}'`);
@@ -214,7 +218,8 @@ export async function createStash(newStash, sessionUser) {
const curatedStash = curateStash(stash, { user: sessionUser });
return curatedStash;
} catch (error) {
}
catch (error) {
if (error.routine === '_bt_check_unique') {
throw new HttpError('Stash name should be unique', 409);
}
@@ -245,7 +250,8 @@ export async function updateStash(stashIdOrSlug, updatedStash, sessionUser) {
.where((builder) => {
if (typeof stashIdOrSlug === 'number') {
builder.where('id', stashIdOrSlug);
} else {
}
else {
builder.where('slug', stashIdOrSlug);
}
})
@@ -258,7 +264,8 @@ export async function updateStash(stashIdOrSlug, updatedStash, sessionUser) {
}
return curateStash(stash);
} catch (error) {
}
catch (error) {
if (error.routine === '_bt_check_unique') {
throw new HttpError('Stash name should be unique', 409);
}
@@ -322,7 +329,7 @@ export async function stashActor(actorId, stashId, sessionUser) {
.returning('*');
await indexApi.replace({
index: 'actors_stashed',
table: 'actors_stashed',
id: stashed.id,
doc: {
actor_id: actorId,
@@ -338,7 +345,8 @@ export async function stashActor(actorId, stashId, sessionUser) {
// return fetchDomainStashes('actor', actorId, sessionUser);
return curateStashed(stashed);
} catch (error) {
}
catch (error) {
if (error.routine === '_bt_check_unique') {
throw new HttpError(`Actor ${actorId} is already stashed in '${stash.name}'`, 409);
}
@@ -375,7 +383,7 @@ export async function unstashActor(actorId, stashId, sessionUser) {
try {
await indexApi.callDelete({
index: 'actors_stashed',
table: 'actors_stashed',
query: {
bool: {
must: [
@@ -386,7 +394,8 @@ export async function unstashActor(actorId, stashId, sessionUser) {
},
},
});
} catch (error) {
}
catch (error) {
console.log(error);
}
@@ -422,7 +431,7 @@ export async function stashScene(sceneId, stashId, sessionUser) {
.returning('*');
await indexApi.replace({
index: 'scenes_stashed',
table: 'scenes_stashed',
id: stashed.id,
doc: {
// ...doc.replace.doc,
@@ -439,7 +448,8 @@ export async function stashScene(sceneId, stashId, sessionUser) {
// return fetchDomainStashes('scene', sceneId, sessionUser);
return curateStashed(stashed);
} catch (error) {
}
catch (error) {
if (error.routine === '_bt_check_unique') {
throw new HttpError(`Scene ${sceneId} is already stashed in '${stash.name}'`, 409);
}
@@ -475,7 +485,7 @@ export async function unstashScene(sceneId, stashId, sessionUser) {
.returning('*');
await indexApi.callDelete({
index: 'scenes_stashed',
table: 'scenes_stashed',
query: {
bool: {
must: [
@@ -519,7 +529,7 @@ export async function stashMovie(movieId, stashId, sessionUser) {
.returning('*');
await indexApi.replace({
index: 'movies_stashed',
table: 'movies_stashed',
id: stashed.id,
doc: {
movie_id: movieId,
@@ -535,7 +545,8 @@ export async function stashMovie(movieId, stashId, sessionUser) {
// return fetchDomainStashes('movie', movieId, sessionUser);
return curateStashed(stashed);
} catch (error) {
}
catch (error) {
if (error.routine === '_bt_check_unique') {
throw new HttpError(`Movie ${movieId} is already stashed in '${stash.name}'`, 409);
}
@@ -571,7 +582,7 @@ export async function unstashMovie(movieId, stashId, sessionUser) {
.delete();
await indexApi.callDelete({
index: 'movies_stashed',
table: 'movies_stashed',
query: {
bool: {
must: [
+18 -17
View File
@@ -1,12 +1,13 @@
import process from 'node:process';
import config from 'config';
import { format } from 'date-fns';
import { CronJob } from 'cron';
import { format } from 'date-fns';
import initLogger from './logger.js';
import { knexOwner as knex } from './knex.js';
import { searchApi, indexApi } from './manticore.js';
import chunk from '../utils/chunk.js';
import filterTitle from '../utils/filter-title.js';
import { knexOwner as knex } from './knex.js';
import initLogger from './logger.js';
import { indexApi, searchApi } from './manticore.js';
const logger = initLogger();
@@ -31,7 +32,7 @@ export async function syncStashes(domain = 'scene', ids) {
const stashDocs = stashChunk.map((stash) => ({
replace: {
index: `${domain}s_stashed`,
table: `${domain}s_stashed`,
id: stash.stashed_id,
doc: {
[`${domain}_id`]: stash[`${domain}_id`],
@@ -60,7 +61,7 @@ export async function syncStashes(domain = 'scene', ids) {
await chain;
const searchResponse = await searchApi.search({
index: `${domain}s_stashed`,
table: `${domain}s_stashed`,
query: {
in: {
[`${domain}_id`]: itemIdChunk,
@@ -72,7 +73,7 @@ export async function syncStashes(domain = 'scene', ids) {
const docs = searchResponse?.hits?.hits ?? [];
const orphanedIds = docs
.map((hit) => hit._id)
.map((hit) => hit.id)
.filter((manticoreId) => !validStashedIds.has(manticoreId));
if (orphanedIds.length === 0) {
@@ -81,7 +82,7 @@ export async function syncStashes(domain = 'scene', ids) {
const deleteDocs = orphanedIds.map((orphanId) => ({
delete: {
index: `${domain}s_stashed`,
table: `${domain}s_stashed`,
id: orphanId,
},
}));
@@ -93,7 +94,7 @@ export async function syncStashes(domain = 'scene', ids) {
}
export async function syncManticoreScenes(sceneIds) {
logger.info(`Updating Manticore search documents for ${sceneIds ? sceneIds.length : 'all' } scenes`);
logger.info(`Updating Manticore search documents for ${sceneIds ? sceneIds.length : 'all'} scenes`);
const scenes = await knex.raw(`
SELECT
@@ -177,7 +178,7 @@ export async function syncManticoreScenes(sceneIds) {
if (!scene) {
return {
delete: {
index: 'scenes',
table: 'scenes',
id: sceneId,
},
};
@@ -216,7 +217,7 @@ export async function syncManticoreScenes(sceneIds) {
return {
replace: {
index: 'scenes',
table: 'scenes',
id: scene.id,
doc: {
title: scene.title || undefined,
@@ -279,7 +280,7 @@ export async function syncScenes(releaseIds) {
}
export async function syncManticoreMovies(movieIds) {
logger.info(`Updating Manticore search documents for ${movieIds ? movieIds.length : 'all' } movies`);
logger.info(`Updating Manticore search documents for ${movieIds ? movieIds.length : 'all'} movies`);
const movies = await knex.raw(`
SELECT
@@ -343,7 +344,7 @@ export async function syncManticoreMovies(movieIds) {
if (!movie) {
return {
delete: {
index: 'movies',
table: 'movies',
id: movieId,
},
};
@@ -362,7 +363,7 @@ export async function syncManticoreMovies(movieIds) {
return {
replace: {
index: 'movies',
table: 'movies',
id: movie.id,
doc: {
title: movie.title || undefined,
@@ -406,7 +407,7 @@ export async function syncMovies(releaseIds) {
}
export async function syncManticoreActors(actorIds) {
logger.info(`Updating Manticore search documents for ${actorIds ? actorIds.length : 'all' } actors`);
logger.info(`Updating Manticore search documents for ${actorIds ? actorIds.length : 'all'} actors`);
// manually select date of birth, otherwise it is retrieved in local timezone but interpreted as UTC...
const actors = await knex.raw(`
@@ -430,7 +431,7 @@ export async function syncManticoreActors(actorIds) {
if (!actor) {
return {
delete: {
index: 'actors',
table: 'actors',
id: actorId,
},
};
@@ -438,7 +439,7 @@ export async function syncManticoreActors(actorIds) {
return {
replace: {
index: 'actors',
table: 'actors',
id: actor.id,
doc: {
entity_id: actor.entity_id || undefined,
+24 -21
View File
@@ -1,10 +1,10 @@
import knex from './knex.js';
import redis from './redis.js';
import initLogger from './logger.js';
import { censor } from './censor.js';
import knex from './knex.js';
import initLogger from './logger.js';
import { curateMedia } from './media.js';
import redis from './redis.js';
const logger = initLogger();
function curateTag(tag, context = {}) {
@@ -52,7 +52,8 @@ export async function fetchTags(options = {}, context = {}) {
order: 'asc',
},
]);
} else if (!options.includeAliases) {
}
else if (!options.includeAliases) {
builder.whereNull('tags.alias_for');
}
}),
@@ -97,24 +98,26 @@ export async function fetchTagsById(tagIds, options = {}, reqUser, context = {})
.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string'))
.groupBy('media.id', 'tags_posters.tag_id', 'sfw_media.id', 'entities.id', 'parents.id'),
context.restriction ? [] : knex('tags_photos')
.select('tags_photos.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent'))
.leftJoin('tags', 'tags.id', 'tags_photos.tag_id')
.leftJoin('media', 'media.id', 'tags_photos.media_id')
.leftJoin('entities', 'entities.id', 'media.entity_id')
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string')),
context.restriction
? []
: knex('tags_photos')
.select('tags_photos.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent'))
.leftJoin('tags', 'tags.id', 'tags_photos.tag_id')
.leftJoin('media', 'media.id', 'tags_photos.media_id')
.leftJoin('entities', 'entities.id', 'media.entity_id')
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string')),
reqUser
? knex('alerts_users_tags')
.select('alerts_users_tags.*')
.leftJoin('tags', 'tags.id', 'alerts_users_tags.tag_id')
.where('user_id', reqUser.id)
.where((whereBuilder) => {
whereBuilder
.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string'));
})
.select('alerts_users_tags.*')
.leftJoin('tags', 'tags.id', 'alerts_users_tags.tag_id')
.where('user_id', reqUser.id)
.where((whereBuilder) => {
whereBuilder
.whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number'))
.orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string'));
})
: [],
]);
+3 -2
View File
@@ -1,9 +1,9 @@
import config from 'config';
import { parse } from 'yaml';
import { knexOwner as knex } from './knex.js';
// import { curateStash } from './stashes.js';
import { HttpError } from './errors.js';
import { knexOwner as knex } from './knex.js';
function curateTemplate(template) {
return {
@@ -90,7 +90,8 @@ export async function createTemplate(template, reqUser) {
try {
parse(template.template);
} catch (error) {
}
catch (error) {
throw new HttpError(`Invalid YAML: ${error.message}`, 400);
}
+47
View File
@@ -0,0 +1,47 @@
function parseBoolFromString(string) {
if (string === 'true') {
return true;
}
if (string === 'false') {
return false;
}
return string;
}
function parseValue(value) {
if (typeof value === 'string') {
return parseBoolFromString(value);
}
if (Array.isArray(value)) {
return value.map(parseValue);
}
if (value && typeof value === 'object') {
return parseObject(value);
}
return value;
}
function parseObject(obj) {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, parseValue(value)]));
}
// Express 5's req.query is a getter that re-parses the URL on every access, so it can no longer
// be reassigned like `req.query = ...` (that's a silent no-op); it must be shadowed with its own
// property on the request instance instead. See https://github.com/expressjs/express/issues/6633
export default function queryBoolean() {
return (req, _res, next) => {
Object.defineProperty(req, 'query', {
value: parseObject(req.query),
configurable: true,
enumerable: true,
writable: true,
});
next();
};
}
+3 -2
View File
@@ -39,7 +39,7 @@ const accentMap = {
ỹ: 'y',
};
const plainCharRegex = /[a-zA-Z0-9]/;
const plainCharRegex = /[a-z0-9]/i;
const defaultPunctuationRegex = /[.,?!:;&'‘’"“”…()[\]{}<>/*—]/;
const defaultSymbolRegex = /[@$€£#%^+=\\~]/;
@@ -111,7 +111,8 @@ export default function slugify(strings, delimiter = '-', {
}
return '';
}).join('');
})
.join('');
const components = normalized.trim().split(/\s+/).filter(Boolean);
+4 -4
View File
@@ -1,13 +1,13 @@
import Router from 'express-promise-router';
import { Router } from 'express';
import omit from 'object.omit';
import {
createActor,
createActorRevision,
fetchActorRevisions,
fetchActors,
fetchActorsById,
createActor,
mergeActors,
fetchActorRevisions,
createActorRevision,
reviewActorRevision,
} from '../actors.js';
+4 -4
View File
@@ -1,12 +1,12 @@
import Router from 'express-promise-router';
import { Router } from 'express';
import {
fetchAlerts,
createAlert,
removeAlert,
fetchAlerts,
fetchNotifications,
updateNotifications,
removeAlert,
updateNotification,
updateNotifications,
} from '../alerts.js';
// actors and actorIds, stashes and stashIds are aliases for consistency
+5 -7
View File
@@ -1,15 +1,14 @@
/* eslint-disable no-param-reassign */
import { stringify } from '@brillout/json-serializer/stringify'; /* eslint-disable-line import/extensions */
import { stringify } from '@brillout/json-serializer/stringify';
import IPCIDR from 'ip-cidr';
import argv from '../argv.js';
import {
login,
signup,
fetchUserKeys,
createKey,
removeUserKey,
fetchUserKeys,
flushUserKeys,
login,
removeUserKey,
signup,
} from '../auth.js';
import { fetchUser } from '../users.js';
@@ -111,4 +110,3 @@ export async function flushUserKeysApi(req, res) {
res.status(204).send();
}
/* eslint-enable no-param-reassign */
+4 -4
View File
@@ -4,26 +4,26 @@ export default function consentHandler(req, res, next) {
if (Object.hasOwn(req.query, 'lgbt')) {
const lgbtFilters = Array.from(new Set([...(req.tagFilter || []).filter((tag) => !['gay', 'bisexual', 'transsexual'].includes(tag)), 'extreme-insertion']));
req.tagFilter = lgbtFilters; // eslint-disable-line no-param-reassign
req.tagFilter = lgbtFilters;
res.cookie('tags', JSON.stringify(lgbtFilters));
}
if (Object.hasOwn(req.query, 'straight')) {
const straightFilters = Array.from(new Set([...(req.tagFilter || []), 'gay', 'bisexual', 'transsexual', 'extreme-insertion']));
req.tagFilter = straightFilters; // eslint-disable-line no-param-reassign
req.tagFilter = straightFilters;
res.cookie('tags', JSON.stringify(straightFilters));
}
if (Object.hasOwn(req.query, 'consent')) {
req.session.hasConsent = true; // eslint-disable-line no-param-reassign
req.session.hasConsent = true;
res.redirect(redirect || req.path);
return;
}
if (!req.path.includes('/consent') && !req.session?.hasConsent) {
res.redirect(`/consent?redirect=${req.originalUrl === '/' ? '' : encodeURIComponent(req.originalUrl) }`);
res.redirect(`/consent?redirect=${req.originalUrl === '/' ? '' : encodeURIComponent(req.originalUrl)}`);
return;
}
+2 -2
View File
@@ -1,12 +1,12 @@
import { parseResolveInfo } from 'graphql-parse-resolve-info';
import { getIdsBySlug } from '../cache.js';
import {
fetchEntities,
fetchEntitiesById,
} from '../entities.js';
import { getIdsBySlug } from '../cache.js';
export async function fetchEntitiesApi(req, res) {
const entities = await fetchEntities(req.query);
+38 -38
View File
@@ -2,8 +2,8 @@ import config from 'config';
import { format } from 'date-fns';
import {
graphql,
buildSchema,
graphql,
GraphQLScalarType,
} from 'graphql';
@@ -12,56 +12,56 @@ import {
JSONResolver,
} from 'graphql-scalars';
import {
scenesSchema,
fetchScenesGraphql,
fetchScenesByIdGraphql,
} from './scenes.js';
import {
moviesSchema,
fetchMoviesGraphql,
fetchMoviesByIdGraphql,
} from './movies.js';
import {
entitiesSchema,
fetchEntitiesGraphql,
fetchEntitiesByIdGraphql,
} from './entities.js';
import { verifyKey } from '../auth.js';
import {
actorsSchema,
fetchActorsGraphql,
fetchActorsByIdGraphql,
fetchActorsGraphql,
} from './actors.js';
import {
stashesSchema,
fetchUserStashesGraphql,
fetchStashGraphql,
createStashGraphql,
updateStashGraphql,
removeStashGraphql,
stashSceneGraphql,
unstashSceneGraphql,
stashActorGraphql,
unstashActorGraphql,
stashMovieGraphql,
unstashMovieGraphql,
} from './stashes.js';
import {
alertsSchema,
fetchAlertsGraphql,
createAlertGraphql,
removeAlertGraphql,
fetchAlertsGraphql,
fetchNotificationsGraphql,
removeAlertGraphql,
updateNotificationGraphql,
updateNotificationsGraphql,
} from './alerts.js';
import { verifyKey } from '../auth.js';
import {
entitiesSchema,
fetchEntitiesByIdGraphql,
fetchEntitiesGraphql,
} from './entities.js';
import {
fetchMoviesByIdGraphql,
fetchMoviesGraphql,
moviesSchema,
} from './movies.js';
import {
fetchScenesByIdGraphql,
fetchScenesGraphql,
scenesSchema,
} from './scenes.js';
import {
createStashGraphql,
fetchStashGraphql,
fetchUserStashesGraphql,
removeStashGraphql,
stashActorGraphql,
stashesSchema,
stashMovieGraphql,
stashSceneGraphql,
unstashActorGraphql,
unstashMovieGraphql,
unstashSceneGraphql,
updateStashGraphql,
} from './stashes.js';
const schema = buildSchema(`
type Query {
@@ -114,7 +114,7 @@ export async function graphqlApi(req, res) {
await verifyKey(req.headers['api-user'], req.headers['api-key'], req);
req.user = { // eslint-disable-line no-param-reassign
req.user = {
id: Number(req.headers['api-user']),
};
+18 -14
View File
@@ -1,17 +1,19 @@
import config from 'config';
import { renderPage } from 'vike/server'; // eslint-disable-line import/extensions
import { renderPage } from 'vike/server';
import { socials } from '../../common/actors.mjs';
import { fetchUnseenNotificationsCount } from '../alerts.js';
import { fetchUserStashes } from '../stashes.js';
import { fetchUserTemplates } from '../users.js';
import { fetchUnseenNotificationsCount } from '../alerts.js';
import { socials } from '../../common/actors.mjs'; // eslint-disable-line import/namespace
export default async function mainHandler(req, res, next) {
const [stashes, templates, unseenNotifications] = req.user ? await Promise.all([
fetchUserStashes(req.user.id, req.user),
fetchUserTemplates(req.user),
fetchUnseenNotificationsCount(req.user),
]) : [];
const [stashes, templates, unseenNotifications] = req.user
? await Promise.all([
fetchUserStashes(req.user.id, req.user),
fetchUserTemplates(req.user),
fetchUnseenNotificationsCount(req.user),
])
: [];
const pageContextInit = {
urlOriginal: req.originalUrl,
@@ -27,12 +29,14 @@ export default async function mainHandler(req, res, next) {
abilities: req.user.abilities,
avatar: req.user.avatar,
},
assets: req.user ? {
stashes,
primaryStash: stashes.find((stash) => stash.isPrimary),
templates,
unseenNotifications,
} : null,
assets: req.user
? {
stashes,
primaryStash: stashes.find((stash) => stash.isPrimary),
templates,
unseenNotifications,
}
: null,
env: {
theme: req.cookies.theme || req.headers['sec-ch-prefers-color-scheme'] || 'light',
scenesView: req.cookies.scenesView || 'list',
+6 -6
View File
@@ -1,12 +1,12 @@
import { stringify } from '@brillout/json-serializer/stringify'; /* eslint-disable-line import/extensions */
import { stringify } from '@brillout/json-serializer/stringify';
import { fetchMovies, fetchMoviesById } from '../movies.js';
import { fetchStashByUsernameAndSlug } from '../stashes.js';
import { parseActorIdentifier } from '../query.js';
import { getIdsBySlug } from '../cache.js';
import slugify from '../../utils/slugify.js';
import promiseProps from '../../utils/promise-props.js';
import slugify from '../../utils/slugify.js';
import { getIdsBySlug } from '../cache.js';
import { HttpError } from '../errors.js';
import { fetchMovies, fetchMoviesById } from '../movies.js';
import { parseActorIdentifier } from '../query.js';
import { fetchStashByUsernameAndSlug } from '../stashes.js';
export async function curateMoviesQuery(query) {
return {
+4 -3
View File
@@ -1,6 +1,6 @@
import config from 'config';
import path from 'path';
import path from 'node:path';
import { Reader } from '@maxmind/geoip2-node';
import config from 'config';
import initLogger from '../logger.js';
@@ -69,7 +69,8 @@ export default async function initRestrictionHandler() {
req.restriction = restriction;
req.country = country;
} catch (error) {
}
catch (error) {
logger.error(`Failed Maxmind IP lookup for ${req.ip}: ${error.message}`);
req.session.restrictionIp = req.userIp;
+2 -2
View File
@@ -1,6 +1,6 @@
// https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-when-using-the-experimental-modules-flag/50052194#50052194
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = `${__dirname}/..`;
+11 -11
View File
@@ -1,22 +1,22 @@
import Router from 'express-promise-router';
import { stringify } from '@brillout/json-serializer/stringify'; /* eslint-disable-line import/extensions */
import { stringify } from '@brillout/json-serializer/stringify';
import { Router } from 'express';
import promiseProps from '../../utils/promise-props.js';
import slugify from '../../utils/slugify.js';
import { getIdsBySlug } from '../cache.js';
import { HttpError } from '../errors.js';
import { parseActorIdentifier } from '../query.js';
import {
createSceneRevision,
fetchSceneRevisions,
fetchScenes,
fetchScenesById,
fetchSceneRevisions,
createSceneRevision,
reviewSceneRevision,
} from '../scenes.js';
import { fetchStashByUsernameAndSlug } from '../stashes.js';
import { parseActorIdentifier } from '../query.js';
import { getIdsBySlug } from '../cache.js';
import slugify from '../../utils/slugify.js';
import { HttpError } from '../errors.js';
import promiseProps from '../../utils/promise-props.js';
export async function curateScenesQuery(query) {
const splitYears = query.years?.split(',') || [];
const splitTags = query.tags?.split(',') || [];
+45 -45
View File
@@ -1,50 +1,50 @@
import config from 'config';
import path from 'path';
import express from 'express';
import boolParser from 'express-query-boolean';
import Router from 'express-promise-router';
import session from 'express-session';
import RedisStore from 'connect-redis';
import path from 'node:path';
import process from 'node:process';
import compression from 'compression';
import cookie from 'cookie';
import redis from '../redis.js';
import errorHandler from './error.js';
import consentHandler from './consent.js';
import initRestrictionHandler from './restrictions.js';
import { scenesRouter } from './scenes.js';
import { actorsRouter } from './actors.js';
import { syncRouter } from './sync.js';
import { fetchMoviesApi } from './movies.js';
import { fetchEntitiesApi } from './entities.js';
import { fetchTagsApi } from './tags.js';
import config from 'config';
import { RedisStore } from 'connect-redis';
import { parseCookie } from 'cookie';
import express, { Router } from 'express';
import session from 'express-session';
import { verifyKey } from '../auth.js';
import initLogger from '../logger.js';
import redis from '../redis.js';
import queryBoolean from '../utils/query-boolean.js';
import { actorsRouter } from './actors.js';
import { router as alertsRouter } from './alerts.js';
import {
createKeyApi,
fetchUserKeysApi,
flushUserKeysApi,
loginApi,
logoutApi,
removeUserKeyApi,
setUserApi,
signupApi,
} from './auth.js';
import consentHandler from './consent.js';
import { fetchEntitiesApi } from './entities.js';
import errorHandler from './error.js';
import { graphqlApi } from './graphql.js';
import mainHandler from './main.js';
import { fetchMoviesApi } from './movies.js';
import {
setUserApi,
loginApi,
logoutApi,
signupApi,
fetchUserKeysApi,
createKeyApi,
removeUserKeyApi,
flushUserKeysApi,
} from './auth.js';
import initRestrictionHandler from './restrictions.js';
import { scenesRouter } from './scenes.js';
import { router as userRouter } from './users.js';
import { router as stashesRouter } from './stashes.js';
import { router as alertsRouter } from './alerts.js';
import { syncRouter } from './sync.js';
import { initCachesApi } from './system.js';
import initLogger from '../logger.js';
import { fetchTagsApi } from './tags.js';
import { router as userRouter } from './users.js';
const logger = initLogger();
const isProduction = process.env.NODE_ENV === 'production';
@@ -57,8 +57,11 @@ export default async function initServer() {
app.use(compression());
app.disable('x-powered-by');
app.set('view engine', 'ejs');
// Express 5 defaults to the 'simple' query parser (Node's querystring) instead of 'extended'
// (qs), which drops support for the bracket/nested query syntax the app relies on.
app.set('query parser', 'extended');
router.use(boolParser());
router.use(queryBoolean());
router.use('/', express.static('public'));
router.use('/', express.static('static'));
@@ -66,12 +69,10 @@ export default async function initServer() {
router.use((req, _res, next) => {
if (req.headers.cookie) {
const cookies = cookie.parse(req.headers.cookie);
const cookies = parseCookie(req.headers.cookie);
/* eslint-disable no-param-reassign */
req.cookies = cookies;
req.tagFilter = cookies.tags ? JSON.parse(cookies.tags) : [];
/* eslint-enable no-param-reassign */
}
next();
@@ -99,7 +100,8 @@ export default async function initServer() {
// (In dev, Vite's middleware serves our static assets.)
const sirv = (await import('sirv')).default;
router.use(sirv('dist/client'));
} else {
}
else {
// We instantiate Vite's development server and integrate its middleware to our server.
// ⚠️ We instantiate it only in development. (It isn't needed in production and it
// would unnecessarily bloat our production server.)
@@ -121,7 +123,7 @@ export default async function initServer() {
res.sendFile(path.join(import.meta.dirname, '../../assets/consent.html'));
});
router.use('/api/*', async (req, _res, next) => {
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);
}
@@ -167,16 +169,14 @@ export default async function initServer() {
router.use(consentHandler);
router.use((_req, res, next) => {
/* eslint-disable no-param-reassign */
res.set('Accept-CH', 'Sec-CH-Prefers-Color-Scheme');
res.set('Vary', 'Sec-CH-Prefers-Color-Scheme');
res.set('Critical-CH', 'Sec-CH-Prefers-Color-Scheme');
/* eslint-enable no-param-reassign */
next();
});
router.get('*', mainHandler);
router.get('/{*splat}', mainHandler);
router.use(errorHandler);
app.use(router);
+5 -5
View File
@@ -1,17 +1,17 @@
import Router from 'express-promise-router';
import { Router } from 'express';
import {
fetchUserStashes,
createStash,
fetchStashByUsernameAndSlug,
fetchUserStashes,
removeStash,
stashActor,
stashScene,
stashMovie,
stashScene,
unstashActor,
unstashScene,
unstashMovie,
unstashScene,
updateStash,
fetchStashByUsernameAndSlug,
} from '../stashes.js';
export const stashesSchema = `
+9 -9
View File
@@ -1,15 +1,15 @@
import Router from 'express-promise-router';
import {
syncScenes,
syncMovies,
syncActors,
syncStashes,
syncQueue,
} from '../sync.js';
import { Router } from 'express';
import verifyAbility from '../../utils/verify-ability.js';
import {
syncActors,
syncMovies,
syncQueue,
syncScenes,
syncStashes,
} from '../sync.js';
export const syncRouter = Router();
async function syncScenesApi(req, res) {
+1 -1
View File
@@ -1,5 +1,5 @@
import { HttpError } from '../errors.js';
import { initCaches } from '../cache.js';
import { HttpError } from '../errors.js';
export async function initCachesApi(req, res) {
if (req.user?.role !== 'admin') {
+4 -4
View File
@@ -1,12 +1,12 @@
import Router from 'express-promise-router';
import { stringify } from '@brillout/json-serializer/stringify'; /* eslint-disable-line import/extensions */
import { stringify } from '@brillout/json-serializer/stringify';
import { Router } from 'express';
import {
createBan,
createTemplate,
fetchUser,
fetchUserTemplates,
createTemplate,
removeTemplate,
createBan,
} from '../users.js';
async function fetchUserApi(req, res) {