From f34de8d1b08a6adc7a1bd99064f67395a39a95d8 Mon Sep 17 00:00:00 2001 From: DebaucheryLibrarian Date: Sat, 25 Jul 2026 03:46:43 +0200 Subject: [PATCH] Merged migrations. --- migrations/20190325001339_releases.js | 312 +++++++++++++++--- migrations/20260203052449_scene_attributes.js | 19 -- migrations/20260206034715_raw_releases.js | 11 - migrations/20260207034922_chapter_details.js | 58 ---- .../20260208044729_random_sfw_improvements.js | 28 -- .../20260222055254_unique_origin_tags.js | 31 -- migrations/20260301042453_foot_float.js | 23 -- .../20260302222545_series_alt_descriptions.js | 13 - migrations/20260304020542_scene_actor_tags.js | 21 -- ...cene_language_production_date_precision.js | 27 -- migrations/20260403231603_feeds.js | 100 ------ migrations/20260520044355_actors_unique.js | 59 ---- migrations/20260608053154_sync_abilities.js | 87 ----- migrations/20260704023524_biometrics.js | 35 -- migrations/20260713052703_scene_alerts.js | 48 --- migrations/20260718034747_users_settings.js | 29 -- ...3120000_actors_avatars_profile_not_null.js | 24 -- .../20260725000000_stashes_meta_last_added.js | 38 --- ...0000_actors_socials_handle_lower_unique.js | 40 --- 19 files changed, 270 insertions(+), 733 deletions(-) delete mode 100644 migrations/20260203052449_scene_attributes.js delete mode 100644 migrations/20260206034715_raw_releases.js delete mode 100644 migrations/20260207034922_chapter_details.js delete mode 100644 migrations/20260208044729_random_sfw_improvements.js delete mode 100644 migrations/20260222055254_unique_origin_tags.js delete mode 100644 migrations/20260301042453_foot_float.js delete mode 100644 migrations/20260302222545_series_alt_descriptions.js delete mode 100644 migrations/20260304020542_scene_actor_tags.js delete mode 100644 migrations/20260402051404_scene_language_production_date_precision.js delete mode 100644 migrations/20260403231603_feeds.js delete mode 100644 migrations/20260520044355_actors_unique.js delete mode 100644 migrations/20260608053154_sync_abilities.js delete mode 100644 migrations/20260704023524_biometrics.js delete mode 100644 migrations/20260713052703_scene_alerts.js delete mode 100644 migrations/20260718034747_users_settings.js delete mode 100644 migrations/20260723120000_actors_avatars_profile_not_null.js delete mode 100644 migrations/20260725000000_stashes_meta_last_added.js delete mode 100644 migrations/20260725010000_actors_socials_handle_lower_unique.js diff --git a/migrations/20190325001339_releases.js b/migrations/20190325001339_releases.js index 164ccaae..64b916c5 100755 --- a/migrations/20190325001339_releases.js +++ b/migrations/20190325001339_releases.js @@ -40,6 +40,7 @@ exports.up = async (knex) => { .index(); table.text('name'); + table.string('name_stylized'); table.text('slug', 32); table.text('type') @@ -126,10 +127,15 @@ exports.up = async (knex) => { .defaultTo(knex.fn.now()); }); + await knex.schema.createMaterializedView('media_sfw', (view) => { + view.as(knex('media').select('id').where('is_sfw', true)); + }); + + await knex.raw('CREATE UNIQUE INDEX media_sfw_id ON media_sfw(id)'); + await knex.raw(` CREATE FUNCTION get_random_sfw_media_id() RETURNS varchar AS $$ - SELECT id FROM media - WHERE is_sfw = true + SELECT id FROM media_sfw ORDER BY random() LIMIT 1; $$ LANGUAGE sql STABLE; @@ -143,6 +149,36 @@ exports.up = async (knex) => { .defaultTo(knex.raw('get_random_sfw_media_id()')); }); + await knex.schema.createTable('media_biometrics', (table) => { + table.increments('id'); + + table.string('media_id', 21) + .references('id') + .inTable('media') + .notNullable() + .onDelete('cascade'); + + table.integer('width'); + table.integer('height'); + + table.integer('face_index') + .notNullable() + .defaultTo(0); + + table.json('biometrics'); + table.specificType('embedding', 'vector(1024)'); + + table.datetime('updated_at') + .notNullable() + .defaultTo(knex.fn.now()); + + table.datetime('created_at') + .notNullable() + .defaultTo(knex.fn.now()); + + table.unique(['media_id', 'face_index']); + }); + await knex.schema.createTable('tags_groups', (table) => { table.increments('id'); @@ -335,7 +371,7 @@ exports.up = async (knex) => { table.decimal('shoe_size'); table.integer('leg'); - table.integer('foot'); + table.decimal('foot'); table.integer('thigh'); table.string('blood_type'); @@ -357,6 +393,9 @@ exports.up = async (knex) => { table.string('agency'); + table.boolean('allow_global_match'); + table.text('comment'); + table.text('avatar_media_id', 21) .references('id') .inTable('media'); @@ -443,7 +482,7 @@ exports.up = async (knex) => { table.decimal('shoe_size'); table.integer('leg'); - table.integer('foot'); + table.decimal('foot'); table.integer('thigh'); table.string('blood_type'); @@ -655,6 +694,7 @@ exports.up = async (knex) => { .inTable('actors'); table.integer('profile_id', 12) + .notNullable() .references('id') .inTable('actors_profiles') .onDelete('cascade'); @@ -672,8 +712,6 @@ exports.up = async (knex) => { table.unique(['profile_id', 'media_id']); }); - await knex.raw('CREATE UNIQUE INDEX unique_main_avatars ON actors_avatars (actor_id) WHERE (profile_id IS NULL);'); - await knex.schema.createTable('actors_photos', (table) => { table.integer('actor_id', 12) .notNullable() @@ -713,12 +751,20 @@ exports.up = async (knex) => { table.datetime('pinged_at'); table.datetime('verified_at'); - table.unique(['actor_id', 'platform', 'handle']); table.unique(['actor_id', 'url']); }); await knex.raw('ALTER TABLE actors_socials ADD CONSTRAINT socials_url_or_handle CHECK (num_nulls(handle, url) = 1);'); await knex.raw('ALTER TABLE actors_socials ADD CONSTRAINT socials_handle_and_platform CHECK (num_nulls(platform, handle) = 2 or num_nulls(platform, handle) = 0);'); + await knex.raw('CREATE UNIQUE INDEX actors_socials_actor_id_platform_handle_lower_unique ON actors_socials (actor_id, platform, lower(handle));'); + + await knex.schema.createTable('languages', (table) => { + table.string('alpha2') + .primary(); + + table.text('name'); + table.text('name_native'); + }); await knex.schema.createTable('releases', (table) => { table.increments('id'); @@ -741,6 +787,7 @@ exports.up = async (knex) => { table.text('url', 1000); table.text('title'); table.specificType('alt_titles', 'text ARRAY'); + table.specificType('alt_descriptions', 'text ARRAY'); table.text('slug'); table.timestamp('date'); @@ -758,6 +805,13 @@ exports.up = async (knex) => { table.enum('date_precision', ['year', 'month', 'week', 'day', 'hour', 'minute', 'second']) .defaultTo('day'); + table.enum('production_date_precision', ['year', 'month', 'week', 'day', 'hour', 'minute', 'second']) + .defaultTo('day'); + + table.string('language_alpha2') + .references('alpha2') + .inTable('languages'); + table.text('description'); table.integer('duration') @@ -773,6 +827,8 @@ exports.up = async (knex) => { table.text('comment'); + table.json('attributes'); + table.integer('created_batch_id', 12) .references('id') .inTable('batches') @@ -937,6 +993,8 @@ exports.up = async (knex) => { }); await knex.schema.createTable('releases_tags', (table) => { + table.increments('id'); + table.integer('tag_id', 12) .references('id') .inTable('tags'); @@ -947,9 +1005,13 @@ exports.up = async (knex) => { .inTable('releases') .onDelete('cascade'); + table.integer('actor_id') + .references('id') + .inTable('actors'); + table.text('original_tag'); - table.unique(['tag_id', 'release_id']); + table.unique(['release_id', 'original_tag']); table.index('tag_id'); table.index('release_id'); @@ -958,12 +1020,7 @@ exports.up = async (knex) => { .defaultTo('scraper'); }); - await knex.schema.createTable('releases_search', (table) => { - table.integer('release_id', 16) - .references('id') - .inTable('releases') - .onDelete('cascade'); - }); + await knex.raw('CREATE UNIQUE INDEX releases_tags_tag_id_release_id_actor_id ON releases_tags (tag_id, release_id, COALESCE(actor_id, -1));'); await knex.schema.createTable('movies', (table) => { table.increments('id'); @@ -984,6 +1041,7 @@ exports.up = async (knex) => { table.text('url', 1000); table.text('title'); table.specificType('alt_titles', 'text ARRAY'); + table.specificType('alt_descriptions', 'text ARRAY'); table.text('slug'); table.timestamp('date'); @@ -1000,6 +1058,8 @@ exports.up = async (knex) => { table.text('comment'); + table.json('attributes'); + table.integer('created_batch_id', 12) .references('id') .inTable('batches') @@ -1134,13 +1194,6 @@ exports.up = async (knex) => { table.unique(['movie_id', 'media_id']); }); - await knex.schema.createTable('movies_search', (table) => { - table.integer('movie_id', 16) - .references('id') - .inTable('movies') - .onDelete('cascade'); - }); - await knex.schema.createTable('series', (table) => { table.increments('id'); @@ -1159,6 +1212,7 @@ exports.up = async (knex) => { table.text('url', 1000); table.text('title'); table.specificType('alt_titles', 'text ARRAY'); + table.specificType('alt_descriptions', 'text ARRAY'); table.text('slug'); table.timestamp('date'); @@ -1175,6 +1229,8 @@ exports.up = async (knex) => { table.text('comment'); + table.json('attributes'); + table.integer('created_batch_id', 12) .references('id') .inTable('batches') @@ -1288,13 +1344,6 @@ exports.up = async (knex) => { table.unique(['serie_id', 'media_id']); }); - await knex.schema.createTable('series_search', (table) => { - table.integer('serie_id', 16) - .references('id') - .inTable('series') - .onDelete('cascade'); - }); - await knex.schema.createTable('chapters', (table) => { table.increments('id'); @@ -1316,6 +1365,8 @@ exports.up = async (knex) => { table.text('title'); table.text('description'); + table.datetime('date'); + table.integer('created_batch_id', 12) .references('id') .inTable('batches') @@ -1379,6 +1430,34 @@ exports.up = async (knex) => { table.unique(['tag_id', 'chapter_id']); }); + await knex.schema.createTable('chapters_trailers', (table) => { + table.integer('chapter_id') + .notNullable() + .references('id') + .inTable('chapters') + .onDelete('cascade'); + + table.text('media_id') + .notNullable() + .references('id') + .inTable('media') + .onDelete('cascade'); + }); + + await knex.schema.createTable('chapters_teasers', (table) => { + table.integer('chapter_id') + .notNullable() + .references('id') + .inTable('chapters') + .onDelete('cascade'); + + table.text('media_id') + .notNullable() + .references('id') + .inTable('media') + .onDelete('cascade'); + }); + await knex.schema.createTable('users_roles', (table) => { table.string('role') .primary(); @@ -1396,7 +1475,9 @@ exports.up = async (knex) => { { subject: 'actor', action: 'create' }, { subject: 'actor', action: 'update' }, { subject: 'actor', action: 'delete' }, - { plainUrls: true }, + { subject: 'actor', action: 'merge' }, + { subject: 'sync' }, + { subject: 'plainUrls' }, ]), }, { @@ -1442,6 +1523,8 @@ exports.up = async (knex) => { table.specificType('last_ip', 'cidr'); + table.jsonb('settings'); + table.datetime('created_at') .notNullable() .defaultTo(knex.fn.now()); @@ -1660,6 +1743,98 @@ exports.up = async (knex) => { .defaultTo(knex.fn.now()); }); + await knex.schema.createTable('feeds', (table) => { + table.increments('id'); + + table.integer('user_id') + .notNullable() + .references('id') + .inTable('users') + .onDelete('cascade'); + + table.string('name') + .notNullable(); + + table.string('slug') + .notNullable(); + + table.boolean('public'); + table.boolean('primary'); + + table.text('comment'); + table.json('meta'); + + table.datetime('created_at') + .notNullable() + .defaultTo(knex.fn.now()); + }); + + await knex.schema.createTable('feeds_entities', (table) => { + table.increments('id'); + + table.integer('feed_id') + .notNullable() + .references('id') + .inTable('feeds') + .onDelete('cascade'); + + table.integer('entity_id') + .notNullable() + .references('id') + .inTable('entities') + .onDelete('cascade'); + + table.text('comment'); + + table.datetime('created_at') + .notNullable() + .defaultTo(knex.fn.now()); + }); + + await knex.schema.createTable('feeds_actors', (table) => { + table.increments('id'); + + table.integer('feed_id') + .notNullable() + .references('id') + .inTable('feeds') + .onDelete('cascade'); + + table.integer('actor_id') + .notNullable() + .references('id') + .inTable('actors') + .onDelete('cascade'); + + table.text('comment'); + + table.datetime('created_at') + .notNullable() + .defaultTo(knex.fn.now()); + }); + + await knex.schema.createTable('feeds_tags', (table) => { + table.increments('id'); + + table.integer('feed_id') + .notNullable() + .references('id') + .inTable('feeds') + .onDelete('cascade'); + + table.integer('tag_id') + .notNullable() + .references('id') + .inTable('tags') + .onDelete('cascade'); + + table.text('comment'); + + table.datetime('created_at') + .notNullable() + .defaultTo(knex.fn.now()); + }); + await knex.schema.createTable('alerts', (table) => { table.increments('id'); @@ -1698,6 +1873,10 @@ exports.up = async (knex) => { .notNullable() .defaultTo(false); + table.boolean('is_expired') + .notNullable() + .defaultTo(false); + table.json('meta'); table.text('comment'); @@ -1878,6 +2057,31 @@ exports.up = async (knex) => { ); }); + await knex.schema.createMaterializedView('alerts_users_scenes', (view) => { + view.columns('user_id', 'scene_id', 'alert_ids'); + + view.as( + knex('alerts_scenes') + .select( + 'alerts.user_id', + 'alerts_scenes.scene_id', + knex.raw('array_agg(distinct alerts.id) as alert_ids'), + knex.raw('(alerts_tags.id is null and alerts_entities.id is null and alerts_matches.id is null and related_scenes.id is null) as is_only'), + ) + .leftJoin('alerts', 'alerts.id', 'alerts_scenes.alert_id') + .leftJoin('alerts_entities', 'alerts_entities.alert_id', 'alerts_scenes.alert_id') + .leftJoin('alerts_tags', 'alerts_tags.alert_id', 'alerts_scenes.alert_id') + .leftJoin('alerts_matches', 'alerts_matches.alert_id', 'alerts_scenes.alert_id') + .leftJoin('alerts_scenes as related_scenes', (joinBuilder) => { + joinBuilder + .on('related_scenes.alert_id', 'alerts_scenes.alert_id') + .on('related_scenes.scene_id', '!=', 'alerts_scenes.scene_id'); + }) + .where('alerts.is_expired', false) + .groupBy(['user_id', 'alerts_scenes.scene_id', 'is_only']), + ); + }); + await knex.schema.createTable('notifications', (table) => { table.increments('id'); @@ -1901,6 +2105,18 @@ exports.up = async (knex) => { .notNullable() .defaultTo(false); + table.datetime('seen_at'); + + table.boolean('notify') + .notNullable() + .defaultTo(true); + + table.boolean('email') + .notNullable() + .defaultTo(false); + + table.datetime('emailed_at'); + table.datetime('created_at') .notNullable() .defaultTo(knex.fn.now()); @@ -2177,12 +2393,20 @@ exports.up = async (knex) => { .inTable('entities'); }); + await knex.schema.createTable('sync', (table) => { + table.increments('id'); + + table.string('domain'); + table.specificType('item_ids', 'integer array'); + + table.text('comment'); + + table.datetime('created_at') + .defaultTo(knex.fn.now()); + }); + // SEARCH AND SORT await knex.raw(` - ALTER TABLE releases_search ADD COLUMN document tsvector; - ALTER TABLE movies_search ADD COLUMN document tsvector; - ALTER TABLE series_search ADD COLUMN document tsvector; - /* allow scenes without dates to be mixed inbetween scenes with dates */ ALTER TABLE releases ADD COLUMN effective_date timestamptz @@ -2200,19 +2424,12 @@ exports.up = async (knex) => { // INDEXES await knex.raw(` CREATE UNIQUE INDEX unique_actor_slugs_network ON actors (slug, entity_id, entry_id); - CREATE UNIQUE INDEX unique_actor_slugs ON actors (slug) WHERE entity_id IS NULL; + CREATE UNIQUE INDEX unique_actor_slugs ON actors (slug, entry_id) WHERE entity_id IS NULL; CREATE UNIQUE INDEX unique_entity_campaigns_banner_url ON campaigns (entity_id, url, banner_id) WHERE affiliate_id IS NULL; CREATE UNIQUE INDEX unique_entity_campaigns_url ON campaigns (entity_id, url) WHERE banner_id IS NULL AND affiliate_id IS NULL; CREATE UNIQUE INDEX unique_entity_campaigns_banner_affiliate ON campaigns (entity_id, affiliate_id, banner_id) WHERE url IS NULL; CREATE UNIQUE INDEX unique_entity_campaigns_affiliate ON campaigns (entity_id, affiliate_id) WHERE banner_id IS NULL AND url IS NULL; - - CREATE UNIQUE INDEX releases_search_unique ON releases_search (release_id); - CREATE UNIQUE INDEX movies_search_unique ON movies_search (movie_id); - CREATE INDEX releases_search_index ON releases_search USING GIN (document); - CREATE INDEX movies_search_index ON movies_search USING GIN (document); - CREATE UNIQUE INDEX series_search_unique ON series_search (serie_id); - CREATE INDEX series_search_index ON series_search USING GIN (document); `); // FUNCTIONS @@ -2275,7 +2492,8 @@ exports.up = async (knex) => { stashes.id as stash_id, COUNT(DISTINCT stashes_scenes)::integer as stashed_scenes, COUNT(DISTINCT stashes_movies)::integer as stashed_movies, - COUNT(DISTINCT stashes_actors)::integer as stashed_actors + COUNT(DISTINCT stashes_actors)::integer as stashed_actors, + MAX(GREATEST(stashes_scenes.created_at, stashes_movies.created_at, stashes_actors.created_at)) as last_stash_at FROM stashes LEFT JOIN stashes_scenes ON stashes_scenes.stash_id = stashes.id LEFT JOIN stashes_movies ON stashes_movies.stash_id = stashes.id @@ -2372,6 +2590,8 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style DROP TABLE IF EXISTS chapters_tags CASCADE; DROP TABLE IF EXISTS chapters_posters CASCADE; DROP TABLE IF EXISTS chapters_photos CASCADE; + DROP TABLE IF EXISTS chapters_trailers CASCADE; + DROP TABLE IF EXISTS chapters_teasers CASCADE; DROP TABLE IF EXISTS banners_tags CASCADE; DROP TABLE IF EXISTS banners CASCADE; @@ -2381,6 +2601,7 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style DROP TABLE IF EXISTS batches CASCADE; DROP TABLE IF EXISTS fingerprints_types CASCADE; + DROP TABLE IF EXISTS sync CASCADE; DROP TABLE IF EXISTS actors_avatars CASCADE; DROP TABLE IF EXISTS actors_photos CASCADE; @@ -2412,8 +2633,10 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style DROP TABLE IF EXISTS sites CASCADE; DROP TABLE IF EXISTS studios CASCADE; DROP TABLE IF EXISTS media_sfw CASCADE; + DROP TABLE IF EXISTS media_biometrics CASCADE; DROP TABLE IF EXISTS media CASCADE; DROP TABLE IF EXISTS countries CASCADE; + DROP TABLE IF EXISTS languages CASCADE; DROP TABLE IF EXISTS networks CASCADE; DROP TABLE IF EXISTS entities_types CASCADE; @@ -2426,6 +2649,11 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style DROP TABLE IF EXISTS stashes_actors CASCADE; DROP TABLE IF EXISTS stashes CASCADE; + DROP TABLE IF EXISTS feeds_entities CASCADE; + DROP TABLE IF EXISTS feeds_actors CASCADE; + DROP TABLE IF EXISTS feeds_tags CASCADE; + DROP TABLE IF EXISTS feeds CASCADE; + DROP TABLE IF EXISTS alerts_scenes CASCADE; DROP TABLE IF EXISTS alerts_actors CASCADE; DROP TABLE IF EXISTS alerts_tags CASCADE; diff --git a/migrations/20260203052449_scene_attributes.js b/migrations/20260203052449_scene_attributes.js deleted file mode 100644 index 93b3644d..00000000 --- a/migrations/20260203052449_scene_attributes.js +++ /dev/null @@ -1,19 +0,0 @@ -exports.up = async (knex) => { - await knex.schema.alterTable('releases', (table) => { - table.json('attributes'); - }); - - await knex.schema.alterTable('movies', (table) => { - table.json('attributes'); - }); -}; - -exports.down = async (knex) => { - await knex.schema.alterTable('releases', (table) => { - table.dropColumn('attributes'); - }); - - await knex.schema.alterTable('movies', (table) => { - table.dropColumn('attributes'); - }); -}; diff --git a/migrations/20260206034715_raw_releases.js b/migrations/20260206034715_raw_releases.js deleted file mode 100644 index 37818527..00000000 --- a/migrations/20260206034715_raw_releases.js +++ /dev/null @@ -1,11 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.alterTable('releases', (table) => { - table.specificType('alt_descriptions', 'text ARRAY'); - }); -}; - -exports.down = async function(knex) { - await knex.schema.alterTable('releases', (table) => { - table.dropColumn('alt_descriptions'); - }); -}; diff --git a/migrations/20260207034922_chapter_details.js b/migrations/20260207034922_chapter_details.js deleted file mode 100644 index e30d2d23..00000000 --- a/migrations/20260207034922_chapter_details.js +++ /dev/null @@ -1,58 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.alterTable('chapters', (table) => { - table.datetime('date'); - }); - - await knex.schema.createTable('chapters_trailers', (table) => { - table.integer('chapter_id') - .notNullable() - .references('id') - .inTable('chapters') - .onDelete('cascade'); - - table.text('media_id') - .notNullable() - .references('id') - .inTable('media') - .onDelete('cascade'); - }); - - await knex.schema.createTable('chapters_teasers', (table) => { - table.integer('chapter_id') - .notNullable() - .references('id') - .inTable('chapters') - .onDelete('cascade'); - - table.text('media_id') - .notNullable() - .references('id') - .inTable('media') - .onDelete('cascade'); - }); - - await knex.schema.alterTable('entities', (table) => { - table.string('name_stylized'); - }); - - await knex.schema.alterTable('movies', (table) => { - table.specificType('alt_descriptions', 'text ARRAY'); - }); -}; - -exports.down = async function(knex) { - await knex.schema.alterTable('chapters', (table) => { - table.dropColumn('date'); - }); - - await knex.schema.alterTable('entities', (table) => { - table.dropColumn('name_stylized'); - }); - - await knex.schema.dropTable('chapters_trailers'); - await knex.schema.dropTable('chapters_teasers'); - - await knex.schema.alterTable('movies', (table) => { - table.dropColumn('alt_descriptions'); - }); -}; diff --git a/migrations/20260208044729_random_sfw_improvements.js b/migrations/20260208044729_random_sfw_improvements.js deleted file mode 100644 index e8328cde..00000000 --- a/migrations/20260208044729_random_sfw_improvements.js +++ /dev/null @@ -1,28 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.createMaterializedView('media_sfw', (view) => { - view.as(knex('media').select('id').where('is_sfw', true)); - }); - - await knex.raw('CREATE UNIQUE INDEX media_sfw_id ON media_sfw(id)'); - - await knex.raw(` - CREATE OR REPLACE FUNCTION get_random_sfw_media_id() RETURNS varchar AS $$ - SELECT id FROM media_sfw - ORDER BY random() - LIMIT 1; - $$ LANGUAGE sql STABLE; - `); -}; - -exports.down = async function(knex) { - await knex.raw(` - CREATE OR REPLACE FUNCTION get_random_sfw_media_id() RETURNS varchar AS $$ - SELECT id FROM media - WHERE is_sfw = true - ORDER BY random() - LIMIT 1; - $$ LANGUAGE sql STABLE; - `); - - await knex.schema.dropMaterializedView('media_sfw'); -}; diff --git a/migrations/20260222055254_unique_origin_tags.js b/migrations/20260222055254_unique_origin_tags.js deleted file mode 100644 index 55396f8a..00000000 --- a/migrations/20260222055254_unique_origin_tags.js +++ /dev/null @@ -1,31 +0,0 @@ -exports.up = async (knex) => { - // dedupe - await knex.raw(` - DELETE - FROM releases_tags - WHERE ctid IN - ( - SELECT ctid - FROM( - SELECT - *, - ctid, - row_number() OVER (PARTITION BY release_id, original_tag ORDER BY ctid) - FROM releases_tags - )s - WHERE row_number >= 2 - ) - `); - - await knex.schema.alterTable('releases_tags', (table) => { - table.increments('id'); - table.unique(['release_id', 'original_tag']); - }); -}; - -exports.down = async (knex) => { - await knex.schema.alterTable('releases_tags', (table) => { - table.dropColumn('id'); - table.dropUnique(['release_id', 'original_tag']); - }); -}; diff --git a/migrations/20260301042453_foot_float.js b/migrations/20260301042453_foot_float.js deleted file mode 100644 index 4e0831ed..00000000 --- a/migrations/20260301042453_foot_float.js +++ /dev/null @@ -1,23 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.alterTable('actors', (table) => { - table.decimal('foot') - .alter(); - }); - - await knex.schema.alterTable('actors_profiles', (table) => { - table.decimal('foot') - .alter(); - }); -}; - -exports.down = async function(knex) { - await knex.schema.alterTable('actors', (table) => { - table.integer('foot') - .alter(); - }); - - await knex.schema.alterTable('actors_profiles', (table) => { - table.integer('foot') - .alter(); - }); -}; diff --git a/migrations/20260302222545_series_alt_descriptions.js b/migrations/20260302222545_series_alt_descriptions.js deleted file mode 100644 index bb06310a..00000000 --- a/migrations/20260302222545_series_alt_descriptions.js +++ /dev/null @@ -1,13 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.alterTable('series', (table) => { - table.specificType('alt_descriptions', 'text ARRAY'); - table.json('attributes'); - }); -}; - -exports.down = async function(knex) { - await knex.schema.alterTable('series', (table) => { - table.dropColumn('alt_descriptions'); - table.dropColumn('attributes'); - }); -}; diff --git a/migrations/20260304020542_scene_actor_tags.js b/migrations/20260304020542_scene_actor_tags.js deleted file mode 100644 index 8a853310..00000000 --- a/migrations/20260304020542_scene_actor_tags.js +++ /dev/null @@ -1,21 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.alterTable('releases_tags', (table) => { - table.integer('actor_id') - .references('id') - .inTable('actors'); - - table.dropUnique(['tag_id', 'release_id']); - }); - - await knex.raw('CREATE UNIQUE INDEX releases_tags_tag_id_release_id_actor_id ON releases_tags (tag_id, release_id, COALESCE(actor_id, -1))'); -}; - -exports.down = async function(knex) { - await knex.schema.alterTable('releases_tags', (table) => { - table.dropColumn('actor_id'); - - table.unique(['tag_id', 'release_id']); - }); - - await knex.raw('DROP INDEX IF EXISTS releases_tags_tag_id_release_id_actor_id'); -}; diff --git a/migrations/20260402051404_scene_language_production_date_precision.js b/migrations/20260402051404_scene_language_production_date_precision.js deleted file mode 100644 index 75ed726b..00000000 --- a/migrations/20260402051404_scene_language_production_date_precision.js +++ /dev/null @@ -1,27 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.createTable('languages', (table) => { - table.string('alpha2') - .primary(); - - table.text('name'); - table.text('name_native'); - }); - - await knex.schema.alterTable('releases', (table) => { - table.enum('production_date_precision', ['year', 'month', 'week', 'day', 'hour', 'minute', 'second']) - .defaultTo('day'); - - table.string('language_alpha2') - .references('alpha2') - .inTable('languages'); - }); -}; - -exports.down = async function(knex) { - await knex.schema.alterTable('releases', (table) => { - table.dropColumn('production_date_precision'); - table.dropColumn('language_alpha2'); - }); - - await knex.schema.dropTable('languages'); -}; diff --git a/migrations/20260403231603_feeds.js b/migrations/20260403231603_feeds.js deleted file mode 100644 index 6fa607ed..00000000 --- a/migrations/20260403231603_feeds.js +++ /dev/null @@ -1,100 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.createTable('feeds', (table) => { - table.increments('id'); - - table.integer('user_id') - .notNullable() - .references('id') - .inTable('users') - .onDelete('cascade'); - - table.string('name') - .notNullable(); - - table.string('slug') - .notNullable(); - - table.boolean('public'); - table.boolean('primary'); - - table.text('comment'); - table.json('meta'); - - table.datetime('created_at') - .notNullable() - .defaultTo(knex.fn.now()); - }); - - await knex.schema.createTable('feeds_entities', (table) => { - table.increments('id'); - - table.integer('feed_id') - .notNullable() - .references('id') - .inTable('feeds') - .onDelete('cascade'); - - table.integer('entity_id') - .notNullable() - .references('id') - .inTable('entities') - .onDelete('cascade'); - - table.text('comment'); - - table.datetime('created_at') - .notNullable() - .defaultTo(knex.fn.now()); - }); - - await knex.schema.createTable('feeds_actors', (table) => { - table.increments('id'); - - table.integer('feed_id') - .notNullable() - .references('id') - .inTable('feeds') - .onDelete('cascade'); - - table.integer('actor_id') - .notNullable() - .references('id') - .inTable('actors') - .onDelete('cascade'); - - table.text('comment'); - - table.datetime('created_at') - .notNullable() - .defaultTo(knex.fn.now()); - }); - - await knex.schema.createTable('feeds_tags', (table) => { - table.increments('id'); - - table.integer('feed_id') - .notNullable() - .references('id') - .inTable('feeds') - .onDelete('cascade'); - - table.integer('tag_id') - .notNullable() - .references('id') - .inTable('tags') - .onDelete('cascade'); - - table.text('comment'); - - table.datetime('created_at') - .notNullable() - .defaultTo(knex.fn.now()); - }); -}; - -exports.down = async function(knex) { - await knex.schema.dropTable('feeds_tags'); - await knex.schema.dropTable('feeds_actors'); - await knex.schema.dropTable('feeds_entities'); - await knex.schema.dropTable('feeds'); -}; diff --git a/migrations/20260520044355_actors_unique.js b/migrations/20260520044355_actors_unique.js deleted file mode 100644 index aa488603..00000000 --- a/migrations/20260520044355_actors_unique.js +++ /dev/null @@ -1,59 +0,0 @@ -exports.up = async function(knex) { - await knex.raw(` - DROP INDEX unique_actor_slugs; - CREATE UNIQUE INDEX unique_actor_slugs ON actors (slug, entry_id) WHERE entity_id IS NULL; - `); - - await knex.schema.alterTable('actors', (table) => { - table.boolean('allow_global_match'); - table.text('comment'); - }); - - await knex('users_roles') - .update('abilities', JSON.stringify([ - { subject: 'scene', action: 'create' }, - { subject: 'scene', action: 'update' }, - { subject: 'scene', action: 'delete' }, - { subject: 'actor', action: 'create' }, - { subject: 'actor', action: 'update' }, - { subject: 'actor', action: 'delete' }, - { subject: 'actor', action: 'merge' }, - { plainUrls: true }, - ])) - .where('role', 'admin'); -}; - -exports.down = async function(knex) { - const dupes = await knex('actors') - .select('name', 'slug', knex.raw('count(*) as count')) - .whereNull('entity_id') - .groupBy('name', 'slug') - .havingRaw('count(*) > 1') - .orderBy('count', 'desc'); - - if (dupes.length > 0) { - console.log('DUPES\n', dupes.map((actor) => `${actor.name} ${actor.slug} ${actor.count}`).join('\n')); - } - - await knex.raw(` - DROP INDEX unique_actor_slugs; - CREATE UNIQUE INDEX unique_actor_slugs ON actors (slug) WHERE entity_id IS NULL; - `); - - await knex.schema.alterTable('actors', (table) => { - table.dropColumn('allow_global_match'); - table.dropColumn('comment'); - }); - - await knex('users_roles') - .update('abilities', JSON.stringify([ - { subject: 'scene', action: 'create' }, - { subject: 'scene', action: 'update' }, - { subject: 'scene', action: 'delete' }, - { subject: 'actor', action: 'create' }, - { subject: 'actor', action: 'update' }, - { subject: 'actor', action: 'delete' }, - { plainUrls: true }, - ])) - .where('role', 'admin'); -}; diff --git a/migrations/20260608053154_sync_abilities.js b/migrations/20260608053154_sync_abilities.js deleted file mode 100644 index 631f1568..00000000 --- a/migrations/20260608053154_sync_abilities.js +++ /dev/null @@ -1,87 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.createTable('sync', (table) => { - table.increments('id'); - - table.string('domain'); - table.specificType('item_ids', 'integer array'); - - table.text('comment'); - - table.datetime('created_at') - .defaultTo(knex.fn.now()); - }); - - await knex('users_roles') - .update('abilities', JSON.stringify([ - { subject: 'scene', action: 'create' }, - { subject: 'scene', action: 'update' }, - { subject: 'scene', action: 'delete' }, - { subject: 'actor', action: 'create' }, - { subject: 'actor', action: 'update' }, - { subject: 'actor', action: 'delete' }, - { subject: 'actor', action: 'merge' }, - { subject: 'sync' }, - { subject: 'plainUrls' }, - ])) - .where('role', 'admin'); - - await knex.raw(` - DROP TABLE IF EXISTS releases_search CASCADE; - DROP TABLE IF EXISTS movies_search CASCADE; - DROP TABLE IF EXISTS series_search CASCADE; - - DROP TABLE IF EXISTS releases_search_results CASCADE; - DROP TABLE IF EXISTS movies_search_results CASCADE; - `); -}; - -exports.down = async function(knex) { - await knex.schema.dropTable('sync'); - - await knex('users_roles') - .update('abilities', JSON.stringify([ - { subject: 'scene', action: 'create' }, - { subject: 'scene', action: 'update' }, - { subject: 'scene', action: 'delete' }, - { subject: 'actor', action: 'create' }, - { subject: 'actor', action: 'update' }, - { subject: 'actor', action: 'delete' }, - { subject: 'actor', action: 'merge' }, - { plainUrls: true }, - ])) - .where('role', 'admin'); - - await knex.schema.createTable('releases_search', (table) => { - table.integer('release_id', 16) - .references('id') - .inTable('releases') - .onDelete('cascade'); - }); - - await knex.schema.createTable('movies_search', (table) => { - table.integer('movie_id', 16) - .references('id') - .inTable('movies') - .onDelete('cascade'); - }); - - await knex.schema.createTable('series_search', (table) => { - table.integer('serie_id', 16) - .references('id') - .inTable('series') - .onDelete('cascade'); - }); - - await knex.raw(` - ALTER TABLE releases_search ADD COLUMN document tsvector; - ALTER TABLE movies_search ADD COLUMN document tsvector; - ALTER TABLE series_search ADD COLUMN document tsvector; - - CREATE UNIQUE INDEX releases_search_unique ON releases_search (release_id); - CREATE UNIQUE INDEX movies_search_unique ON movies_search (movie_id); - CREATE INDEX releases_search_index ON releases_search USING GIN (document); - CREATE INDEX movies_search_index ON movies_search USING GIN (document); - CREATE UNIQUE INDEX series_search_unique ON series_search (serie_id); - CREATE INDEX series_search_index ON series_search USING GIN (document); - `); -}; diff --git a/migrations/20260704023524_biometrics.js b/migrations/20260704023524_biometrics.js deleted file mode 100644 index c7ea8707..00000000 --- a/migrations/20260704023524_biometrics.js +++ /dev/null @@ -1,35 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.createTable('media_biometrics', (table) => { - table.increments('id'); - - table.string('media_id', 21) - .references('id') - .inTable('media') - .notNullable() - .onDelete('cascade'); - - table.integer('width'); - table.integer('height'); - - table.integer('face_index') - .notNullable() - .defaultTo(0); - - table.json('biometrics'); - table.specificType('embedding', 'vector(1024)'); - - table.datetime('updated_at') - .notNullable() - .defaultTo(knex.fn.now()); - - table.datetime('created_at') - .notNullable() - .defaultTo(knex.fn.now()); - - table.unique(['media_id', 'face_index']); - }); -}; - -exports.down = async function(knex) { - await knex.schema.dropTable('media_biometrics'); -}; diff --git a/migrations/20260713052703_scene_alerts.js b/migrations/20260713052703_scene_alerts.js deleted file mode 100644 index 0e93eb04..00000000 --- a/migrations/20260713052703_scene_alerts.js +++ /dev/null @@ -1,48 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.alterTable('notifications', (table) => { - table.datetime('seen_at'); - }); - - await knex.schema.alterTable('alerts', (table) => { - table.boolean('is_expired') - .notNullable() - .defaultTo(false); - }); - - await knex.schema.createMaterializedView('alerts_users_scenes', (view) => { - view.columns('user_id', 'scene_id', 'alert_ids'); - - view.as( - knex('alerts_scenes') - .select( - 'alerts.user_id', - 'alerts_scenes.scene_id', - knex.raw('array_agg(distinct alerts.id) as alert_ids'), - knex.raw('(alerts_tags.id is null and alerts_entities.id is null and alerts_matches.id is null and related_scenes.id is null) as is_only'), - ) - .leftJoin('alerts', 'alerts.id', 'alerts_scenes.alert_id') - .leftJoin('alerts_entities', 'alerts_entities.alert_id', 'alerts_scenes.alert_id') - .leftJoin('alerts_tags', 'alerts_tags.alert_id', 'alerts_scenes.alert_id') - .leftJoin('alerts_matches', 'alerts_matches.alert_id', 'alerts_scenes.alert_id') - .leftJoin('alerts_scenes as related_scenes', (joinBuilder) => { - joinBuilder - .on('related_scenes.alert_id', 'alerts_scenes.alert_id') - .on('related_scenes.scene_id', '!=', 'alerts_scenes.scene_id'); - }) - .where('alerts.is_expired', false) - .groupBy(['user_id', 'alerts_scenes.scene_id', 'is_only']), - ); - }); -}; - -exports.down = async function(knex) { - await knex.schema.dropMaterializedView('alerts_users_scenes'); - - await knex.schema.alterTable('alerts', (table) => { - table.dropColumn('is_expired'); - }); - - await knex.schema.alterTable('notifications', (table) => { - table.dropColumn('seen_at'); - }); -}; diff --git a/migrations/20260718034747_users_settings.js b/migrations/20260718034747_users_settings.js deleted file mode 100644 index e806705b..00000000 --- a/migrations/20260718034747_users_settings.js +++ /dev/null @@ -1,29 +0,0 @@ -exports.up = async function(knex) { - await knex.schema.alterTable('users', (table) => { - table.jsonb('settings'); - }); - - await knex.schema.alterTable('notifications', (table) => { - table.boolean('notify') - .notNullable() - .defaultTo(true); - - table.boolean('email') - .notNullable() - .defaultTo(false); - - table.datetime('emailed_at'); - }); -}; - -exports.down = async function(knex) { - await knex.schema.alterTable('users', (table) => { - table.dropColumn('settings'); - }); - - await knex.schema.alterTable('notifications', (table) => { - table.dropColumn('notify'); - table.dropColumn('email'); - table.dropColumn('emailed_at'); - }); -}; diff --git a/migrations/20260723120000_actors_avatars_profile_not_null.js b/migrations/20260723120000_actors_avatars_profile_not_null.js deleted file mode 100644 index 577dd297..00000000 --- a/migrations/20260723120000_actors_avatars_profile_not_null.js +++ /dev/null @@ -1,24 +0,0 @@ -exports.up = async function(knex) { - const orphanedAvatars = await knex('actors_avatars') - .whereNull('profile_id'); - - if (orphanedAvatars.length > 0) { - console.log(`Deleting ${orphanedAvatars.length} actors_avatars rows with no profile_id`); - } - - await knex('actors_avatars') - .whereNull('profile_id') - .delete(); - - await knex.raw(` - DROP INDEX IF EXISTS unique_main_avatars; - ALTER TABLE actors_avatars ALTER COLUMN profile_id SET NOT NULL; - `); -}; - -exports.down = async function(knex) { - await knex.raw(` - ALTER TABLE actors_avatars ALTER COLUMN profile_id DROP NOT NULL; - CREATE UNIQUE INDEX unique_main_avatars ON actors_avatars (actor_id) WHERE (profile_id IS NULL); - `); -}; diff --git a/migrations/20260725000000_stashes_meta_last_added.js b/migrations/20260725000000_stashes_meta_last_added.js deleted file mode 100644 index 226e5198..00000000 --- a/migrations/20260725000000_stashes_meta_last_added.js +++ /dev/null @@ -1,38 +0,0 @@ -exports.up = async function(knex) { - await knex.raw(` - DROP MATERIALIZED VIEW stashes_meta; - - CREATE MATERIALIZED VIEW stashes_meta AS ( - SELECT - stashes.id as stash_id, - COUNT(DISTINCT stashes_scenes)::integer as stashed_scenes, - COUNT(DISTINCT stashes_movies)::integer as stashed_movies, - COUNT(DISTINCT stashes_actors)::integer as stashed_actors, - MAX(GREATEST(stashes_scenes.created_at, stashes_movies.created_at, stashes_actors.created_at)) as last_stash_at - FROM stashes - LEFT JOIN stashes_scenes ON stashes_scenes.stash_id = stashes.id - LEFT JOIN stashes_movies ON stashes_movies.stash_id = stashes.id - LEFT JOIN stashes_actors ON stashes_actors.stash_id = stashes.id - GROUP BY stashes.id - ); - `); -}; - -exports.down = async function(knex) { - await knex.raw(` - DROP MATERIALIZED VIEW stashes_meta; - - CREATE MATERIALIZED VIEW stashes_meta AS ( - SELECT - stashes.id as stash_id, - COUNT(DISTINCT stashes_scenes)::integer as stashed_scenes, - COUNT(DISTINCT stashes_movies)::integer as stashed_movies, - COUNT(DISTINCT stashes_actors)::integer as stashed_actors - FROM stashes - LEFT JOIN stashes_scenes ON stashes_scenes.stash_id = stashes.id - LEFT JOIN stashes_movies ON stashes_movies.stash_id = stashes.id - LEFT JOIN stashes_actors ON stashes_actors.stash_id = stashes.id - GROUP BY stashes.id - ); - `); -}; diff --git a/migrations/20260725010000_actors_socials_handle_lower_unique.js b/migrations/20260725010000_actors_socials_handle_lower_unique.js deleted file mode 100644 index 92d20eda..00000000 --- a/migrations/20260725010000_actors_socials_handle_lower_unique.js +++ /dev/null @@ -1,40 +0,0 @@ -exports.up = async function(knex) { - const duplicates = await knex.raw(` - SELECT actor_id, platform, lower(handle) AS handle_lower, COUNT(*) AS count - FROM actors_socials - WHERE handle IS NOT NULL - GROUP BY actor_id, platform, lower(handle) - HAVING COUNT(*) > 1 - `); - - if (duplicates.rows.length > 0) { - console.log(`Deduping ${duplicates.rows.length} actors_socials handle group(s)`, duplicates.rows.map((row) => `${row.actor_id}/${row.platform}/${row.handle_lower} (${row.count})`).join(', ')); - } - - await knex.raw(` - DELETE FROM actors_socials - WHERE id IN ( - SELECT id FROM ( - SELECT - id, - ROW_NUMBER() OVER ( - PARTITION BY actor_id, platform, lower(handle) - ORDER BY (handle = lower(handle)) ASC, created_at ASC - ) AS rn - FROM actors_socials - WHERE handle IS NOT NULL - ) ranked - WHERE rn > 1 - ); - - ALTER TABLE actors_socials DROP CONSTRAINT actors_social_actor_id_platform_handle_unique; - CREATE UNIQUE INDEX actors_socials_actor_id_platform_handle_lower_unique ON actors_socials (actor_id, platform, lower(handle)); - `); -}; - -exports.down = async function(knex) { - await knex.raw(` - DROP INDEX actors_socials_actor_id_platform_handle_lower_unique; - ALTER TABLE actors_socials ADD CONSTRAINT actors_social_actor_id_platform_handle_unique UNIQUE (actor_id, platform, handle); - `); -};