From 56534800d89f4b1c0f00bc69aaa89e5d95a7e80d Mon Sep 17 00:00:00 2001 From: DebaucheryLibrarian Date: Thu, 8 Jun 2023 01:16:44 +0200 Subject: [PATCH] Added stash transfer tool. --- .gitignore | 3 +- assets/components/actors/tile.vue | 6 +- assets/components/header/menu.vue | 2 +- migrations/20230607231459_stash_unique.js | 19 ++++ src/tools/stashes-load.js | 133 ++++++++++++++++++++++ src/tools/stashes-save.js | 84 ++++++++++++++ 6 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 migrations/20230607231459_stash_unique.js create mode 100644 src/tools/stashes-load.js create mode 100644 src/tools/stashes-save.js diff --git a/.gitignore b/.gitignore index e3b54977..4ce5a18d 100755 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,8 @@ config/* !config/default.js assets/js/config/ !assets/js/config/default.js -export* +/export* +/stashes* *.heapprofile *.heapsnapshot .vscode diff --git a/assets/components/actors/tile.vue b/assets/components/actors/tile.vue index 7949141a..399b7d34 100755 --- a/assets/components/actors/tile.vue +++ b/assets/components/actors/tile.vue @@ -67,21 +67,21 @@ v-show="(!stash || stash.primary) && favorited" icon="heart7" class="stash stashed" - @click.prevent.native="unstashActor" + @click.stop.native="unstashActor" /> diff --git a/assets/components/header/menu.vue b/assets/components/header/menu.vue index 3e1ce93f..a4591360 100755 --- a/assets/components/header/menu.vue +++ b/assets/components/header/menu.vue @@ -9,7 +9,7 @@ Favorites diff --git a/migrations/20230607231459_stash_unique.js b/migrations/20230607231459_stash_unique.js new file mode 100644 index 00000000..91088418 --- /dev/null +++ b/migrations/20230607231459_stash_unique.js @@ -0,0 +1,19 @@ +exports.up = async (knex) => { + await knex.schema.alterTable('stashes', (table) => { + table.unique(['user_id', 'slug']); + }); + + await knex.raw(` + CREATE UNIQUE INDEX unique_primary ON stashes (user_id, "primary") WHERE ("primary" = TRUE); + `); +}; + +exports.down = async (knex) => { + await knex.schema.alterTable('stashes', (table) => { + table.dropUnique(['user_id', 'slug']); + }); + + await knex.raw(` + DROP INDEX unique_primary; + `); +}; diff --git a/src/tools/stashes-load.js b/src/tools/stashes-load.js new file mode 100644 index 00000000..db8ce48c --- /dev/null +++ b/src/tools/stashes-load.js @@ -0,0 +1,133 @@ +const fs = require('fs'); +const knex = require('../knex'); +const args = require('../argv'); + +async function getStashId(stash, user) { + const existingStash = await knex('stashes') + .select('id') + .where('user_id', user.id) + .where((builder) => { + builder + .where('slug', stash.slug) + .orWhere('primary', stash.primary); + }) + .first(); + + if (existingStash) { + return existingStash.id; + } + + const [stashId] = await knex('stashes') + .insert({ + user_id: user.id, + name: stash.name, + slug: stash.slug, + public: stash.public, + created_at: stash.createdAt, + }) + .returning('id'); + + return stashId; +} + +async function importReleases(type, stash, user, filename) { + const curatedType = type === 'release' ? 'scene' : type; + + await stash[`${curatedType}s`].reduce(async (chain, scene) => { + await chain; + + const release = await knex(`${type}s`) + .select(`${type}s.id`, 'entities.id as entity_id', 'entities.name as entity_name') + .leftJoin('entities', 'entities.id', `${type}s.entity_id`) + .where(`${type}s.entry_id`, scene.entryId) + .where('entities.slug', scene.entitySlug) + .where('entities.type', scene.entityType) + .first(); + + if (!release) { + throw new Error(`${curatedType.slice(0, 1).toUpperCase}${curatedType.slice(1)} ${scene.title} in ${scene.entityType} ${scene.entitySlug} does not exist`); + } + + await knex(`stashes_${curatedType}s`) + .insert({ + stash_id: stash.id, + [`${curatedType}_id`]: release.id, + comment: `import ${filename}`, + created_at: scene.createdAt, + }) + .onConflict(['stash_id', `${curatedType}_id`]) + .ignore(); + + console.log(`Imported ${stash.username} stash ${release.entity_name} ${curatedType} "${scene.title}"`); + }, Promise.resolve()); +} + +async function importActors(stash, user, filename) { + await stash.actors.reduce(async (chain, actor) => { + await chain; + + const actorEntry = await knex('actors') + .select('actors.*') + .leftJoin('entities', 'entities.id', 'actors.entity_id') + .where('actors.slug', actor.slug) + .where((builder) => { + if (actor.entitySlug) { + builder + .where('entities.slug', actor.entitySlug) + .where('entities.type', actor.entityType); + } + }) + .first(); + + if (!actorEntry) { + throw new Error(`Actor ${actor.slug} in ${user.username} stash ${stash.name} does not exist`); + } + + await knex('stashes_actors') + .insert({ + stash_id: stash.id, + actor_id: actorEntry.id, + comment: `import ${filename}`, + created_at: actor.createdAt, + }) + .onConflict(['stash_id', 'actor_id']) + .ignore(); + + console.log(`Imported ${stash.username} stash actor "${actorEntry.name}"`); + }, Promise.resolve()); +} + +async function load() { + const filename = process.argv[2]; + const file = await fs.promises.readFile(filename, 'utf8'); + + const stashes = file.split('\n') + .filter(Boolean) + .map((data) => JSON.parse(data)) + .filter((stash) => !args.username || stash.username === args.username); + + await stashes.reduce(async (stashChain, stash, index) => { + await stashChain; + + const user = await knex('users') + .select('id') + .where('username', stash.username) + .first(); + + if (!user) { + throw new Error(`No user '${stash.username}'`); + } + + const stashId = await getStashId(stash, user); + + await importReleases('release', { ...stash, id: stashId }, user, filename); + await importReleases('movie', { ...stash, id: stashId }, user, filename); + await importActors({ ...stash, id: stashId }, user, filename); + + console.log(`Imported ${index + 1}/${stashes.length} stash ${stash.name} from ${stash.username}`); + }, Promise.resolve()); + + process.exit(); +} + +load(); diff --git a/src/tools/stashes-save.js b/src/tools/stashes-save.js new file mode 100644 index 00000000..c10921e3 --- /dev/null +++ b/src/tools/stashes-save.js @@ -0,0 +1,84 @@ +'use strict'; + +// const config = require('config'); +// const util = require('util'); +// const path = require('path'); + +const fs = require('fs'); +const moment = require('moment'); + +const knex = require('../knex'); + +async function save() { + const stashes = await knex('stashes') + .select('stashes.*', 'users.username') + .leftJoin('users', 'users.id', 'stashes.user_id'); + + const filename = `stashes-${moment().format('YYYY-MM-DD_hh_mm')}.json`; + let savedStashes = 0; + + await stashes.reduce(async (chain, stash) => { + await chain; + + const scenes = await knex('stashes_scenes') + .select('releases.title', 'releases.created_at', 'releases.entry_id', 'entities.slug as entity_slug', 'entities.type as entity_type') + .leftJoin('releases', 'releases.id', 'stashes_scenes.scene_id') + .leftJoin('entities', 'entities.id', 'releases.entity_id') + .where('stashes_scenes.stash_id', stash.id); + + const movies = await knex('stashes_movies') + .select('movies.title', 'movies.created_at', 'movies.entry_id', 'entities.slug as entity_slug', 'entities.type as entity_type') + .leftJoin('movies', 'movies.id', 'stashes_movies.movie_id') + .leftJoin('entities', 'entities.id', 'movies.entity_id') + .where('stashes_movies.stash_id', stash.id); + + const actors = await knex('stashes_actors') + .select('actors.slug', 'actors.created_at', 'entities.slug as entity_slug', 'entities.type as entity_type') + .leftJoin('actors', 'actors.id', 'stashes_actors.actor_id') + .leftJoin('entities', 'entities.id', 'actors.entity_id') + .where('stashes_actors.stash_id', stash.id); + + console.log('scenes', scenes); + console.log('movies', movies); + console.log('actors', actors); + + const curatedStash = JSON.stringify({ + username: stash.username, + name: stash.name, + slug: stash.slug, + public: stash.public, + primary: stash.primary, + createdAt: stash.created_at, + scenes: scenes.map((scene) => ({ + title: scene.title, + entryId: scene.entry_id, + entitySlug: scene.entity_slug, + entityType: scene.entity_type, + createdAt: scene.created_at, + })), + movies: movies.map((movie) => ({ + title: movie.title, + entryId: movie.entry_id, + entitySlug: movie.entity_slug, + entityType: movie.entity_type, + createdAt: movie.created_at, + })), + actors: actors.map((actor) => ({ + slug: actor.slug, + entitySlug: actor.entity_slug, + entityType: actor.entity_type, + createdAt: actor.created_at, + })), + }); + + await fs.promises.appendFile(filename, `${curatedStash}\n`); + + savedStashes += 1; + }, Promise.resolve([])); + + console.log(`Saved ${savedStashes} stashes`); + + process.exit(); +} + +save();