Added experimental edit page and revision history.
This commit is contained in:
@@ -112,6 +112,7 @@ export async function patch(path, data, options = {}) {
|
||||
});
|
||||
|
||||
if (res.status === 204) {
|
||||
showFeedback(true, options);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import redis from './redis.js';
|
||||
|
||||
export async function getIdsBySlug(slugs, domain) {
|
||||
export async function getIdsBySlug(slugs, domain, toMap) {
|
||||
if (!slugs) {
|
||||
return [];
|
||||
}
|
||||
@@ -21,5 +21,9 @@ export async function getIdsBySlug(slugs, domain) {
|
||||
return Number(id);
|
||||
}));
|
||||
|
||||
if (toMap) {
|
||||
return Object.fromEntries(slugs.map((slug, index) => [slug, ids[index]]));
|
||||
}
|
||||
|
||||
return ids.filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export function curateMedia(media, context = {}) {
|
||||
|
||||
return {
|
||||
id: media.id,
|
||||
hash: media.hash,
|
||||
path: media.path,
|
||||
thumbnail: media.thumbnail,
|
||||
lazy: media.lazy,
|
||||
|
||||
181
src/scenes.js
181
src/scenes.js
@@ -11,6 +11,9 @@ 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';
|
||||
|
||||
const logger = initLogger();
|
||||
|
||||
function getWatchUrl(scene) {
|
||||
if (scene.url) {
|
||||
@@ -64,6 +67,7 @@ function curateScene(rawScene, assets) {
|
||||
description: rawScene.description,
|
||||
duration: rawScene.duration,
|
||||
shootId: rawScene.shoot_id,
|
||||
productionDate: rawScene.production_date,
|
||||
channel: {
|
||||
id: assets.channel.id,
|
||||
slug: assets.channel.slug,
|
||||
@@ -595,3 +599,180 @@ export async function fetchScenes(filters, rawOptions, reqUser) {
|
||||
limit: options.limit,
|
||||
};
|
||||
}
|
||||
|
||||
async function applySceneValueDelta(sceneId, delta, trx) {
|
||||
console.log('value delta', delta);
|
||||
|
||||
return knexOwner('releases')
|
||||
.where('id', sceneId)
|
||||
.update(delta.key, delta.value)
|
||||
.transacting(trx);
|
||||
}
|
||||
|
||||
async function applySceneActorsDelta(sceneId, delta, trx) {
|
||||
console.log('actors delta', delta);
|
||||
|
||||
await knexOwner('releases_actors')
|
||||
.where('release_id', sceneId)
|
||||
.delete()
|
||||
.transacting(trx);
|
||||
|
||||
await knexOwner('releases_actors')
|
||||
.insert(delta.value.map((actorId) => ({
|
||||
release_id: sceneId,
|
||||
actor_id: actorId,
|
||||
})))
|
||||
.transacting(trx);
|
||||
}
|
||||
|
||||
async function applySceneTagsDelta(sceneId, delta, trx) {
|
||||
console.log('tags delta', delta);
|
||||
|
||||
await knexOwner('releases_tags')
|
||||
.where('release_id', sceneId)
|
||||
.whereNotNull('tag_id')
|
||||
.delete()
|
||||
.transacting(trx);
|
||||
|
||||
await knexOwner('releases_tags')
|
||||
.insert(delta.value.map((tagId) => ({
|
||||
release_id: sceneId,
|
||||
tag_id: tagId,
|
||||
source: 'editor',
|
||||
})))
|
||||
.transacting(trx);
|
||||
}
|
||||
|
||||
async function applySceneRevision(sceneIds) {
|
||||
const revisions = await knexOwner('scenes_revisions')
|
||||
.whereIn('scene_id', sceneIds)
|
||||
.whereNull('applied_at');
|
||||
|
||||
await revisions.reduce(async (chain, revision) => {
|
||||
await chain;
|
||||
|
||||
console.log('revision', revision);
|
||||
|
||||
await knexOwner.transaction(async (trx) => {
|
||||
await revision.deltas.map(async (delta) => {
|
||||
if ([
|
||||
'title',
|
||||
'description',
|
||||
'date',
|
||||
'duration',
|
||||
'production_date',
|
||||
'production_location',
|
||||
'production_city',
|
||||
'production_state',
|
||||
].includes(delta.key)) {
|
||||
return applySceneValueDelta(revision.scene_id, delta, trx);
|
||||
}
|
||||
|
||||
if (delta.key === 'actors') {
|
||||
return applySceneActorsDelta(revision.scene_id, delta, trx);
|
||||
}
|
||||
|
||||
if (delta.key === 'tags') {
|
||||
return applySceneTagsDelta(revision.scene_id, delta, trx);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
await knexOwner('scenes_revisions')
|
||||
.where('id', revision.id)
|
||||
.update('applied_at', knex.fn.now());
|
||||
|
||||
// await trx.commit();
|
||||
}).catch(async (error) => {
|
||||
logger.error(`Failed to apply revision ${revision.id} on scene ${revision.scene_id}: ${error.message}`);
|
||||
});
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
const keyMap = {
|
||||
productionDate: 'production_date',
|
||||
};
|
||||
|
||||
export async function createSceneRevision(sceneId, { edits, comment }, reqUser) {
|
||||
const [
|
||||
[scene],
|
||||
openRevisions,
|
||||
] = await Promise.all([
|
||||
fetchScenesById([sceneId], { reqUser, includeAssets: true }),
|
||||
knexOwner('scenes_revisions')
|
||||
.where('user_id', reqUser.id)
|
||||
.whereNull('approved_by')
|
||||
.whereNot('failed', true),
|
||||
]);
|
||||
|
||||
if (!scene) {
|
||||
throw new HttpError(`No scene with ID ${sceneId} found to update`, 404);
|
||||
}
|
||||
|
||||
if (openRevisions.length >= config.revisions.unapprovedLimit) {
|
||||
throw new HttpError(`You have ${config.revisions.unapprovedLimit} unapproved revisions, please wait for approval before submitting another revision.`, 429);
|
||||
}
|
||||
|
||||
const baseScene = Object.fromEntries(Object.entries(scene).map(([key, values]) => {
|
||||
if ([
|
||||
'effectiveDate',
|
||||
'isNew',
|
||||
'network',
|
||||
'stashes',
|
||||
'watchUrl',
|
||||
].includes(key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (values?.hash) {
|
||||
return [key, values.hash];
|
||||
}
|
||||
|
||||
if (values?.id) {
|
||||
return [key, values.id];
|
||||
}
|
||||
|
||||
if (Array.isArray(values)) {
|
||||
return [key, values.map((value) => value?.hash || value?.id || value)];
|
||||
}
|
||||
|
||||
return [key, values];
|
||||
}).filter(Boolean));
|
||||
|
||||
const deltas = Object.entries(edits).map(([key, value]) => {
|
||||
if (baseScene[key] === value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const valueSet = new Set(value);
|
||||
const baseSet = new Set(baseScene[key]);
|
||||
|
||||
if (valueSet.size === baseSet.size && baseScene[key].every((id) => valueSet.has(id))) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key: keyMap[key] || key,
|
||||
value,
|
||||
};
|
||||
}).filter(Boolean);
|
||||
|
||||
if (deltas.length === 0) {
|
||||
throw new HttpError('No effective changes provided', 400);
|
||||
}
|
||||
|
||||
await knexOwner('scenes_revisions').insert({
|
||||
user_id: reqUser.id,
|
||||
scene_id: scene.id,
|
||||
base: JSON.stringify(baseScene),
|
||||
deltas: JSON.stringify(deltas),
|
||||
comment,
|
||||
});
|
||||
|
||||
if (['admin', 'editor'].includes(reqUser.role)) {
|
||||
await applySceneRevision([scene.id]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,13 +47,17 @@ export async function fetchTags(options = {}) {
|
||||
column: knex.raw('similarity(aliases.slug, :query)', { query }),
|
||||
order: 'desc',
|
||||
},
|
||||
{
|
||||
column: 'aliases.priority',
|
||||
order: 'desc',
|
||||
},
|
||||
{
|
||||
column: 'aliases.slug',
|
||||
order: 'asc',
|
||||
},
|
||||
]);
|
||||
} else if (!options.includeAliases) {
|
||||
builder.whereNull('alias_for');
|
||||
builder.whereNull('tags.alias_for');
|
||||
}
|
||||
}),
|
||||
knex('tags_posters')
|
||||
|
||||
@@ -22,6 +22,7 @@ export default async function mainHandler(req, res, next) {
|
||||
id: req.user.id,
|
||||
username: req.user.username,
|
||||
email: req.user.email,
|
||||
role: req.user.role,
|
||||
avatar: req.user.avatar,
|
||||
},
|
||||
assets: req.user ? {
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { stringify } from '@brillout/json-serializer/stringify'; /* eslint-disable-line import/extensions */
|
||||
|
||||
import { fetchScenes, fetchScenesById } from '../scenes.js';
|
||||
import {
|
||||
fetchScenes,
|
||||
fetchScenesById,
|
||||
createSceneRevision,
|
||||
} from '../scenes.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) {
|
||||
@@ -197,6 +203,18 @@ export async function fetchScenesGraphql(query, req) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchSceneApi(req, res) {
|
||||
const [scene] = await fetchScenesById([Number(req.params.sceneId)], { reqUser: req.user });
|
||||
|
||||
console.log(req.params.sceneId, scene);
|
||||
|
||||
if (!scene) {
|
||||
throw new HttpError(`No scene with ID ${req.params.sceneId} found`, 404);
|
||||
}
|
||||
|
||||
res.send(scene);
|
||||
}
|
||||
|
||||
export async function fetchScenesByIdGraphql(query, req) {
|
||||
const scenes = await fetchScenesById([].concat(query.id, query.ids).filter(Boolean), {
|
||||
reqUser: req.user,
|
||||
@@ -209,3 +227,9 @@ export async function fetchScenesByIdGraphql(query, req) {
|
||||
|
||||
return scenes[0];
|
||||
}
|
||||
|
||||
export async function createSceneRevisionApi(req, res) {
|
||||
await createSceneRevision(Number(req.params.sceneId), req.body, req.user);
|
||||
|
||||
res.status(204).send();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,12 @@ import redis from '../redis.js';
|
||||
import errorHandler from './error.js';
|
||||
import consentHandler from './consent.js';
|
||||
|
||||
import { fetchScenesApi } from './scenes.js';
|
||||
import {
|
||||
fetchScenesApi,
|
||||
fetchSceneApi,
|
||||
createSceneRevisionApi,
|
||||
} from './scenes.js';
|
||||
|
||||
import { fetchActorsApi } from './actors.js';
|
||||
import { fetchMoviesApi } from './movies.js';
|
||||
import { fetchEntitiesApi } from './entities.js';
|
||||
@@ -179,6 +184,8 @@ export default async function initServer() {
|
||||
|
||||
// SCENES
|
||||
router.get('/api/scenes', fetchScenesApi);
|
||||
router.get('/api/scenes/:sceneId', fetchSceneApi);
|
||||
router.patch('/api/scenes/:sceneId', createSceneRevisionApi);
|
||||
|
||||
// ACTORS
|
||||
router.get('/api/actors', fetchActorsApi);
|
||||
|
||||
Reference in New Issue
Block a user