'use strict'; const config = require('config'); const knex = require('./knex'); const { indexApi } = require('./manticore'); const { HttpError } = require('./errors'); const slugify = require('./utils/slugify'); const logger = require('./logger')(__filename); let lastActorsViewRefresh = 0; function curateStash(stash) { if (!stash) { return null; } const curatedStash = { id: stash.id, name: stash.name, slug: stash.slug, primary: stash.primary, }; return curatedStash; } function curateStashEntry(stash, user) { const curatedStashEntry = { user_id: user.id, name: stash.name, slug: slugify(stash.name), public: false, }; return curatedStashEntry; } async function fetchStash(stashId, sessionUser) { if (!sessionUser) { throw new HttpError('You are not authenthicated', 401); } const stash = await knex('stashes') .where({ id: stashId, user_id: sessionUser.id, }) .first(); if (!stash) { throw new HttpError('You are not authorized to access this stash', 403); } return curateStash(stash); } async function fetchStashes(domain, itemId, sessionUser) { const stashes = await knex(`stashes_${domain}s`) .select('stashes.*') .where({ [`${domain}_id`]: itemId, user_id: sessionUser.id, }) .leftJoin('stashes', 'stashes.id', `stashes_${domain}s.stash_id`); return stashes.map((stash) => curateStash(stash)); } async function createStash(newStash, sessionUser) { if (!sessionUser) { throw new HttpError('You are not authenthicated', 401); } try { const stash = await knex('stashes') .insert(curateStashEntry(newStash, sessionUser)) .returning('*'); return curateStash(stash); } catch (error) { if (error.routine === '_bt_check_unique') { throw new HttpError('Stash name should be unique', 409); } throw error; } } async function updateStash(stashId, newStash, sessionUser) { if (!sessionUser) { throw new HttpError('You are not authenthicated', 401); } const stash = await knex('stashes') .where({ id: stashId, user_id: sessionUser.id, }) .update(newStash) .returning('*'); if (!stash) { throw new HttpError('You are not authorized to modify this stash', 403); } return curateStash(stash); } async function removeStash(stashId, sessionUser) { if (!sessionUser) { throw new HttpError('You are not authenthicated', 401); } const removed = await knex('stashes') .where({ id: stashId, user_id: sessionUser.id, primary: false, }) .delete(); if (removed === 0) { throw new HttpError('Unable to remove this stash', 400); } } async function refreshActorsView() { if (new Date() - lastActorsViewRefresh > config.stashes.viewRefreshCooldown * 60000) { // don't refresh actors view more than once an hour lastActorsViewRefresh = new Date(); logger.debug('Refreshing actors view'); return knex.schema.refreshMaterializedView('actors_meta'); } logger.silly('Skipping actors view refresh'); return false; } async function stashActor(actorId, stashId, sessionUser) { const stash = await fetchStash(stashId, sessionUser); const [stashed] = await knex('stashes_actors') .insert({ stash_id: stash.id, actor_id: actorId, }) .returning(['id', 'created_at']); 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), }, }); refreshActorsView(); return fetchStashes('actor', actorId, sessionUser); } async function stashScene(sceneId, stashId, sessionUser) { const stash = await fetchStash(stashId, sessionUser); const [stashed] = await knex('stashes_scenes') .insert({ stash_id: stash.id, scene_id: sceneId, }) .returning(['id', 'created_at']); 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), }, }); return fetchStashes('scene', sceneId, sessionUser); } async function stashMovie(movieId, stashId, sessionUser) { const stash = await fetchStash(stashId, sessionUser); const [stashed] = await knex('stashes_movies') .insert({ stash_id: stash.id, movie_id: movieId, }) .returning(['id', 'created_at']); 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), }, }); return fetchStashes('movie', movieId, sessionUser); } async function unstashActor(actorId, stashId, sessionUser) { await knex .from('stashes_actors AS deletable') .where('deletable.actor_id', actorId) .where('deletable.stash_id', stashId) .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(); await indexApi.callDelete({ index: 'actors_stashed', query: { bool: { must: [ { equals: { actor_id: actorId } }, { equals: { stash_id: stashId } }, { equals: { user_id: sessionUser.id } }, ], }, }, }); refreshActorsView(); return fetchStashes('actor', actorId, sessionUser); } async function unstashScene(sceneId, stashId, sessionUser) { await knex .from('stashes_scenes AS deletable') .where('deletable.scene_id', sceneId) .where('deletable.stash_id', stashId) .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(); await indexApi.callDelete({ index: 'scenes_stashed', query: { bool: { must: [ { equals: { scene_id: sceneId } }, { equals: { stash_id: stashId } }, { equals: { user_id: sessionUser.id } }, ], }, }, }); return fetchStashes('scene', sceneId, sessionUser); } async function unstashMovie(movieId, stashId, sessionUser) { await knex .from('stashes_movies AS deletable') .where('deletable.movie_id', movieId) .where('deletable.stash_id', stashId) .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)) .delete(); await indexApi.callDelete({ index: 'movies_stashed', query: { bool: { must: [ { equals: { movie_id: movieId } }, { equals: { stash_id: stashId } }, { equals: { user_id: sessionUser.id } }, ], }, }, }); return fetchStashes('movie', movieId, sessionUser); } module.exports = { createStash, curateStash, removeStash, stashActor, stashScene, stashMovie, unstashScene, unstashActor, unstashMovie, updateStash, };