'use strict'; const inquirer = require('inquirer'); const logger = require('./logger')(__filename); const knex = require('./knex'); const { flushOrphanedMedia } = require('./media'); function curateRelease(release, withMedia = false, withPoster = true) { if (!release) { return null; } return { id: release.id, entryId: release.entry_id, shootId: release.shoot_id, title: release.title, url: release.url, date: release.date, description: release.description, duration: release.duration, entity: release.entity && { id: release.entity.id, name: release.entity.name, slug: release.entity.slug, parent: release.parent && { id: release.parent.id, name: release.parent.name, slug: release.parent.slug, }, }, actors: (release.actors || []).map(actor => ({ id: actor.id, name: actor.name, slug: actor.slug, gender: actor.gender, entityId: actor.entity_id, aliasFor: actor.alias_for, })), tags: (release.tags || []).map(tag => ({ id: tag.id, name: tag.name, slug: tag.slug, })), ...((withMedia || withPoster) && { poster: release.poster ? { id: release.poster.id, path: release.poster.path, thumbnail: release.poster.thumbnail, width: release.poster.width, height: release.poster.height, size: release.poster.size, } : null, }), ...(withMedia && { photos: (release.photos || []).map(photo => ({ id: photo.id, path: photo.path, thumbnail: release.poster.thumbnail, width: photo.width, height: photo.height, size: photo.size, })), trailer: release.trailer ? { id: release.trailer.id, path: release.trailer.path, } : null, }), createdAt: release.created_at, }; } function withRelations(queryBuilder, withMedia = false, withPoster = true) { queryBuilder .select(knex.raw(` releases.id, releases.entry_id, releases.shoot_id, releases.title, releases.url, releases.date, releases.description, releases.duration, releases.created_at, row_to_json(entities) as entity, row_to_json(parents) as parent, COALESCE(json_agg(DISTINCT actors) FILTER (WHERE actors.id IS NOT NULL), '[]') as actors, COALESCE(json_agg(DISTINCT tags) FILTER (WHERE tags.id IS NOT NULL), '[]') as tags `)) .leftJoin('entities', 'entities.id', 'releases.entity_id') .leftJoin('entities as parents', 'parents.id', 'entities.parent_id') .leftJoin('releases_actors', 'releases_actors.release_id', 'releases.id') .leftJoin('actors', 'actors.id', 'releases_actors.actor_id') .leftJoin('releases_tags', 'releases_tags.release_id', 'releases.id') .leftJoin('tags', 'tags.id', 'releases_tags.tag_id') .groupBy(knex.raw(` releases.id, releases.entry_id, releases.shoot_id, releases.title, releases.url, releases.date, releases.description, releases.duration, releases.created_at, entities.id, parents.id `)); if (withMedia || withPoster) { queryBuilder .select(knex.raw(` row_to_json(posters) as poster `)) .leftJoin('releases_posters', 'releases_posters.release_id', 'releases.id') .leftJoin('media as posters', 'posters.id', 'releases_posters.media_id') .groupBy('posters.id'); } if (withMedia) { queryBuilder .select(knex.raw(` row_to_json(trailers) as trailer, COALESCE(json_agg(DISTINCT photos) FILTER (WHERE photos.id IS NOT NULL), '[]') as photos `)) .leftJoin('releases_photos', 'releases_photos.release_id', 'releases.id') .leftJoin('media as photos', 'photos.id', 'releases_photos.media_id') .leftJoin('releases_trailers', 'releases_trailers.release_id', 'releases.id') .leftJoin('media as trailers', 'trailers.id', 'releases_trailers.media_id') .groupBy('posters.id', 'trailers.id'); } } async function fetchScene(releaseId) { const release = await knex('releases') .where('releases.id', releaseId) .modify(withRelations, true, true) .first(); return curateRelease(release, true); } async function fetchScenes(limit = 100) { const releases = await knex('releases') .modify(withRelations, false, true) .limit(Math.min(limit, 1000000)); return releases.map(release => curateRelease(release)); } async function searchScenes(query, limit = 100) { const releases = await knex .from(knex.raw('search_releases(?) as releases', [query])) .modify(withRelations, false, true) .limit(Math.min(limit, 1000000)); return releases.map(release => curateRelease(release)); } async function deleteScenes(sceneIds) { if (sceneIds.length === 0) { return 0; } await knex('movies_scenes') .whereIn('scene_id', sceneIds) .delete(); const deleteCount = await knex('releases') .whereIn('id', sceneIds) .delete(); logger.info(`Removed ${deleteCount}/${sceneIds.length} scenes`); return deleteCount; } async function deleteMovies(movieIds) { if (movieIds.length === 0) { return 0; } await knex('movies_scenes') .whereIn('movie_id', movieIds) .delete(); const deleteCount = await knex('movies') .whereIn('id', movieIds) .delete(); logger.info(`Removed ${deleteCount}/${movieIds.length} movies`); return deleteCount; } async function flushScenes() { const sceneIds = await knex('releases').select('id').pluck('id'); const confirmed = await inquirer.prompt([{ type: 'confirm', name: 'flushScenes', message: `You are about to remove ${sceneIds.length} scenes. Are you sure?`, default: false, }]); if (!confirmed.flushScenes) { logger.warn('Confirmation rejected, not flushing scenes'); return; } const deleteCount = await deleteScenes(sceneIds); await flushOrphanedMedia(); logger.info(`Removed ${deleteCount}/${sceneIds.length} scenes`); } async function flushMovies() { const movieIds = await knex('movies').select('id').pluck('id'); const confirmed = await inquirer.prompt([{ type: 'confirm', name: 'flushMovies', message: `You are about to remove ${movieIds.length} movies. Are you sure?`, default: false, }]); if (!confirmed.flushScenes) { logger.warn('Confirmation rejected, not flushing scenes'); return; } const deleteCount = await deleteMovies(movieIds); await flushOrphanedMedia(); logger.info(`Removed ${deleteCount}/${movieIds.length} movies`); } async function flushBatches(batchIds) { const [sceneIds, movieIds] = await Promise.all([ knex('releases') .select('releases.id') .whereIn('created_batch_id', batchIds) .pluck('releases.id'), knex('movies').whereIn('created_batch_id', batchIds) .select('movies.id') .whereIn('created_batch_id', batchIds) .pluck('movies.id'), ]); const confirmed = await inquirer.prompt([{ type: 'confirm', name: 'flushBatches', message: `You are about to remove ${sceneIds.length} scenes and ${movieIds.length} movies from batches ${batchIds}. Are you sure?`, default: false, }]); if (!confirmed.flushBatches) { logger.warn(`Confirmation rejected, not flushing scenes or movies for batches ${batchIds}`); return; } const [deletedScenesCount, deletedMoviesCount] = await Promise.all([ deleteScenes(sceneIds), deleteMovies(movieIds), ]); logger.info(`Removed ${deletedScenesCount} scenes and ${deletedMoviesCount} movies for batches ${batchIds}`); await flushOrphanedMedia(); } module.exports = { curateRelease, fetchScene, fetchScenes, flushBatches, flushMovies, flushScenes, searchScenes, deleteScenes, deleteMovies, };