Added stash GraphQL mutations. Added movies to GraphQL queries. Moved key management to profile page, only for approved users.

This commit is contained in:
2025-03-31 06:14:56 +02:00
parent 09bba4fe1e
commit 1025285796
14 changed files with 721 additions and 235 deletions

View File

@@ -40,6 +40,21 @@ export function curateStash(stash, assets = {}) {
return curatedStash;
}
function curateStashed(stashed) {
if (!stashed) {
return null;
}
const curatedStashed = {
id: stashed.id,
stashId: stashed.stash_id,
actorId: stashed.actor_id,
createdAt: stashed.created_at,
};
return curatedStashed;
}
function curateStashEntry(stash, user) {
const curatedStashEntry = {
user_id: user?.id || undefined,
@@ -57,9 +72,17 @@ function verifyStashAccess(stash, sessionUser) {
}
}
export async function fetchStashById(stashId, sessionUser) {
export async function fetchStashById(stashIdOrSlug, sessionUser) {
const stash = await knex('stashes')
.where('id', stashId)
.where((builder) => {
if (typeof stashIdOrSlug === 'number') {
builder.where('id', stashIdOrSlug);
} else {
builder
.where('slug', stashIdOrSlug)
.where('user_id', sessionUser.id);
}
})
.first();
verifyStashAccess(stash, sessionUser);
@@ -67,8 +90,16 @@ export async function fetchStashById(stashId, sessionUser) {
return curateStash(stash);
}
export async function fetchStashByUsernameAndSlug(username, stashSlug, sessionUser) {
const user = await knex('users').where('username', username).first();
export async function fetchStashByUsernameAndSlug(usernameOrId, stashSlug, sessionUser) {
const user = await knex('users')
.where((builder) => {
if (typeof usernameOrId === 'number') {
builder.where('id', usernameOrId);
} else {
builder.where('username', usernameOrId);
}
})
.first();
if (!user) {
throw new HttpError('This user does not exist.', 404);
@@ -86,10 +117,22 @@ export async function fetchStashByUsernameAndSlug(username, stashSlug, sessionUs
return curateStash(stash, { user });
}
export async function fetchUserStashes(userId, reqUser) {
export async function fetchUserStashes(usernameOrId, reqUser) {
const userId = typeof usernameOrId === 'number'
? usernameOrId
: await knex('users')
.where('username', usernameOrId)
.first()
.then((user) => user?.id);
if (!userId) {
throw new HttpError(`Could not find user '${usernameOrId}'`);
}
const stashes = await knex('stashes')
.select('stashes.*', 'stashes_meta.*')
.leftJoin('stashes_meta', 'stashes_meta.stash_id', 'stashes.id')
.leftJoin('users', 'users.id', 'stashes.user_id')
.where('user_id', userId)
.modify((builder) => {
if (userId !== reqUser?.id) {
@@ -172,7 +215,7 @@ export async function createStash(newStash, sessionUser) {
}
}
export async function updateStash(stashId, updatedStash, sessionUser) {
export async function updateStash(stashIdOrSlug, updatedStash, sessionUser) {
if (!sessionUser) {
throw new HttpError('You are not authenthicated', 401);
}
@@ -182,11 +225,15 @@ export async function updateStash(stashId, updatedStash, sessionUser) {
}
try {
const stash = await knex('stashes')
.where({
id: stashId,
user_id: sessionUser.id,
const [stash] = await knex('stashes')
.where((builder) => {
if (typeof stashIdOrSlug === 'number') {
builder.where('id', stashIdOrSlug);
} else {
builder.where('slug', stashIdOrSlug);
}
})
.where('user_id', sessionUser.id)
.update(curateStashEntry(updatedStash))
.returning('*');
@@ -209,57 +256,86 @@ export async function removeStash(stashId, sessionUser) {
throw new HttpError('You are not authenthicated', 401);
}
const removed = await knex('stashes')
const stash = await fetchStashById(stashId, sessionUser);
if (!stash) {
throw new HttpError(`Could not find stash '${stashId}'`, 404);
}
const [removed] = await knex('stashes')
.where({
id: stashId,
id: stash.id,
user_id: sessionUser.id,
primary: false,
})
.delete();
.delete()
.returning('*');
if (removed === 0) {
throw new HttpError('Unable to remove this stash', 400);
}
return curateStash(stash);
}
export async function stashActor(actorId, stashId, sessionUser) {
const stash = await fetchStashById(stashId, sessionUser);
const [stashed] = await knex('stashes_actors')
.insert({
stash_id: stash.id,
actor_id: actorId,
})
.returning(['id', 'created_at']);
if (!stash) {
throw new HttpError(`Could not find stash '${stashId}'`, 404);
}
await indexApi.replace({
index: 'actors_stashed',
id: stashed.id,
doc: {
actor_id: actorId,
user_id: sessionUser.id,
stash_id: stashId,
created_at: Math.round(stashed.created_at.getTime() / 1000),
},
});
try {
const [stashed] = await knex('stashes_actors')
.insert({
stash_id: stash.id,
actor_id: actorId,
})
.returning('*');
logger.verbose(`${sessionUser.username} (${sessionUser.id}) stashed actor ${actorId} to stash ${stash.id} (${stash.name})`);
await indexApi.replace({
index: 'actors_stashed',
id: stashed.id,
doc: {
actor_id: actorId,
user_id: sessionUser.id,
stash_id: stash.id,
created_at: Math.round(stashed.created_at.getTime() / 1000),
},
});
refreshView('actors');
logger.verbose(`${sessionUser.username} (${sessionUser.id}) stashed actor ${actorId} to stash ${stash.id} (${stash.name})`);
return fetchDomainStashes('actor', actorId, sessionUser);
refreshView('actors');
// return fetchDomainStashes('actor', actorId, sessionUser);
return curateStashed(stashed);
} catch (error) {
if (error.routine === '_bt_check_unique') {
throw new HttpError(`Actor ${actorId} is already stashed in '${stash.name}'`, 409);
}
throw error;
}
}
export async function unstashActor(actorId, stashId, sessionUser) {
await knex
const stash = await fetchStashById(stashId, sessionUser);
if (!stash) {
throw new HttpError(`Could not find stash '${stashId}'`, 404);
}
const [unstashed] = await knex
.from('stashes_actors AS deletable')
.where('deletable.actor_id', actorId)
.where('deletable.stash_id', stashId)
.where('deletable.stash_id', stash.id)
.whereExists(knex('stashes_actors') // verify user owns this stash, complimentary to row-level security
.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
.where('stashes_actors.stash_id', knex.raw('deletable.stash_id'))
.where('stashes.user_id', sessionUser.id))
.delete();
.delete()
.returning('*');
try {
await indexApi.callDelete({
@@ -268,7 +344,7 @@ export async function unstashActor(actorId, stashId, sessionUser) {
bool: {
must: [
{ equals: { actor_id: actorId } },
{ equals: { stash_id: stashId } },
{ equals: { stash_id: stash.id } },
{ equals: { user_id: sessionUser.id } },
],
},
@@ -278,52 +354,73 @@ export async function unstashActor(actorId, stashId, sessionUser) {
console.log(error);
}
logger.verbose(`${sessionUser.username} (${sessionUser.id}) unstashed actor ${actorId} from stash ${stashId}`);
logger.verbose(`${sessionUser.username} (${sessionUser.id}) unstashed actor ${actorId} from stash ${stashId} (${stash.name})`);
refreshView('actors');
return fetchDomainStashes('actor', actorId, sessionUser);
// return fetchDomainStashes('actor', actorId, sessionUser);
return curateStashed(unstashed);
}
export async function stashScene(sceneId, stashId, sessionUser) {
const stash = await fetchStashById(stashId, sessionUser);
const [stashed] = await knex('stashes_scenes')
.insert({
stash_id: stash.id,
scene_id: sceneId,
})
.returning(['id', 'created_at']);
if (!stash) {
throw new HttpError(`Could not find stash '${stashId}'`, 404);
}
await indexApi.replace({
index: 'scenes_stashed',
id: stashed.id,
doc: {
// ...doc.replace.doc,
scene_id: sceneId,
user_id: sessionUser.id,
stash_id: stashId,
created_at: Math.round(stashed.created_at.getTime() / 1000),
},
});
try {
const [stashed] = await knex('stashes_scenes')
.insert({
stash_id: stash.id,
scene_id: sceneId,
})
.returning('*');
logger.verbose(`${sessionUser.username} (${sessionUser.id}) stashed scene ${sceneId} to stash ${stash.id} (${stash.name})`);
await indexApi.replace({
index: 'scenes_stashed',
id: stashed.id,
doc: {
// ...doc.replace.doc,
scene_id: sceneId,
user_id: sessionUser.id,
stash_id: stash.id,
created_at: Math.round(stashed.created_at.getTime() / 1000),
},
});
refreshView('scenes');
logger.verbose(`${sessionUser.username} (${sessionUser.id}) stashed scene ${sceneId} to stash ${stash.id} (${stash.name})`);
return fetchDomainStashes('scene', sceneId, sessionUser);
refreshView('scenes');
// return fetchDomainStashes('scene', sceneId, sessionUser);
return curateStashed(stashed);
} catch (error) {
if (error.routine === '_bt_check_unique') {
throw new HttpError(`Scene ${sceneId} is already stashed in '${stash.name}'`, 409);
}
throw error;
}
}
export async function unstashScene(sceneId, stashId, sessionUser) {
await knex
const stash = await fetchStashById(stashId, sessionUser);
if (!stash) {
throw new HttpError(`Could not find stash '${stashId}'`, 404);
}
const [unstashed] = await knex
.from('stashes_scenes AS deletable')
.where('deletable.scene_id', sceneId)
.where('deletable.stash_id', stashId)
.where('deletable.stash_id', stash.id)
.whereExists(knex('stashes_scenes') // verify user owns this stash, complimentary to row-level security
.leftJoin('stashes', 'stashes.id', 'stashes_scenes.stash_id')
.where('stashes_scenes.stash_id', knex.raw('deletable.stash_id'))
.where('stashes.user_id', sessionUser.id))
.delete();
.delete()
.returning('*');
await indexApi.callDelete({
index: 'scenes_stashed',
@@ -331,57 +428,78 @@ export async function unstashScene(sceneId, stashId, sessionUser) {
bool: {
must: [
{ equals: { scene_id: sceneId } },
{ equals: { stash_id: stashId } },
{ equals: { stash_id: stash.id } },
{ equals: { user_id: sessionUser.id } },
],
},
},
});
logger.verbose(`${sessionUser.username} (${sessionUser.id}) unstashed scene ${sceneId} from stash ${stashId}`);
logger.verbose(`${sessionUser.username} (${sessionUser.id}) unstashed scene ${sceneId} from stash ${stash.id} (${stash.name})`);
refreshView('scenes');
return fetchDomainStashes('scene', sceneId, sessionUser);
// return fetchDomainStashes('scene', sceneId, sessionUser);
return curateStashed(unstashed);
}
export async function stashMovie(movieId, stashId, sessionUser) {
const stash = await fetchStashById(stashId, sessionUser);
const [stashed] = await knex('stashes_movies')
.insert({
stash_id: stash.id,
movie_id: movieId,
})
.returning(['id', 'created_at']);
if (!stash) {
throw new HttpError(`Could not find stash '${stashId}'`, 404);
}
await indexApi.replace({
index: 'movies_stashed',
id: stashed.id,
doc: {
movie_id: movieId,
user_id: sessionUser.id,
stash_id: stashId,
created_at: Math.round(stashed.created_at.getTime() / 1000),
},
});
try {
const [stashed] = await knex('stashes_movies')
.insert({
stash_id: stash.id,
movie_id: movieId,
})
.returning('*');
logger.verbose(`${sessionUser.username} (${sessionUser.id}) stashed movie ${movieId} to stash ${stash.id} (${stash.name})`);
await indexApi.replace({
index: 'movies_stashed',
id: stashed.id,
doc: {
movie_id: movieId,
user_id: sessionUser.id,
stash_id: stash.id,
created_at: Math.round(stashed.created_at.getTime() / 1000),
},
});
refreshView('movies');
logger.verbose(`${sessionUser.username} (${sessionUser.id}) stashed movie ${movieId} to stash ${stash.id} (${stash.name})`);
return fetchDomainStashes('movie', movieId, sessionUser);
refreshView('movies');
// return fetchDomainStashes('movie', movieId, sessionUser);
return curateStashed(stashed);
} catch (error) {
if (error.routine === '_bt_check_unique') {
throw new HttpError(`Movie ${movieId} is already stashed in '${stash.name}'`, 409);
}
throw error;
}
}
export async function unstashMovie(movieId, stashId, sessionUser) {
await knex
const stash = await fetchStashById(stashId, sessionUser);
if (!stash) {
throw new HttpError(`Could not find stash '${stashId}'`, 404);
}
const [unstashed] = await knex
.from('stashes_movies AS deletable')
.where('deletable.movie_id', movieId)
.where('deletable.stash_id', stashId)
.where('deletable.stash_id', stash.id)
.whereExists(knex('stashes_movies') // verify user owns this stash, complimentary to row-level security
.leftJoin('stashes', 'stashes.id', 'stashes_movies.stash_id')
.where('stashes_movies.stash_id', knex.raw('deletable.stash_id'))
.where('stashes.user_id', sessionUser.id))
.returning('*')
.delete();
await indexApi.callDelete({
@@ -390,18 +508,19 @@ export async function unstashMovie(movieId, stashId, sessionUser) {
bool: {
must: [
{ equals: { movie_id: movieId } },
{ equals: { stash_id: stashId } },
{ equals: { stash_id: stash.id } },
{ equals: { user_id: sessionUser.id } },
],
},
},
});
logger.verbose(`${sessionUser.username} (${sessionUser.id}) unstashed movie ${movieId} from stash ${stashId}`);
logger.verbose(`${sessionUser.username} (${sessionUser.id}) unstashed movie ${movieId} from stash ${stash.id} (${stash.name})`);
refreshView('movies');
return fetchDomainStashes('movie', movieId, sessionUser);
// return fetchDomainStashes('movie', movieId, sessionUser);
return curateStashed(unstashed);
}
CronJob.from({