Merged migrations.

This commit is contained in:
DebaucheryLibrarian
2026-07-25 03:46:43 +02:00
parent f1898e6942
commit f34de8d1b0
19 changed files with 270 additions and 733 deletions

View File

@@ -40,6 +40,7 @@ exports.up = async (knex) => {
.index(); .index();
table.text('name'); table.text('name');
table.string('name_stylized');
table.text('slug', 32); table.text('slug', 32);
table.text('type') table.text('type')
@@ -126,10 +127,15 @@ exports.up = async (knex) => {
.defaultTo(knex.fn.now()); .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(` await knex.raw(`
CREATE FUNCTION get_random_sfw_media_id() RETURNS varchar AS $$ CREATE FUNCTION get_random_sfw_media_id() RETURNS varchar AS $$
SELECT id FROM media SELECT id FROM media_sfw
WHERE is_sfw = true
ORDER BY random() ORDER BY random()
LIMIT 1; LIMIT 1;
$$ LANGUAGE sql STABLE; $$ LANGUAGE sql STABLE;
@@ -143,6 +149,36 @@ exports.up = async (knex) => {
.defaultTo(knex.raw('get_random_sfw_media_id()')); .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) => { await knex.schema.createTable('tags_groups', (table) => {
table.increments('id'); table.increments('id');
@@ -335,7 +371,7 @@ exports.up = async (knex) => {
table.decimal('shoe_size'); table.decimal('shoe_size');
table.integer('leg'); table.integer('leg');
table.integer('foot'); table.decimal('foot');
table.integer('thigh'); table.integer('thigh');
table.string('blood_type'); table.string('blood_type');
@@ -357,6 +393,9 @@ exports.up = async (knex) => {
table.string('agency'); table.string('agency');
table.boolean('allow_global_match');
table.text('comment');
table.text('avatar_media_id', 21) table.text('avatar_media_id', 21)
.references('id') .references('id')
.inTable('media'); .inTable('media');
@@ -443,7 +482,7 @@ exports.up = async (knex) => {
table.decimal('shoe_size'); table.decimal('shoe_size');
table.integer('leg'); table.integer('leg');
table.integer('foot'); table.decimal('foot');
table.integer('thigh'); table.integer('thigh');
table.string('blood_type'); table.string('blood_type');
@@ -655,6 +694,7 @@ exports.up = async (knex) => {
.inTable('actors'); .inTable('actors');
table.integer('profile_id', 12) table.integer('profile_id', 12)
.notNullable()
.references('id') .references('id')
.inTable('actors_profiles') .inTable('actors_profiles')
.onDelete('cascade'); .onDelete('cascade');
@@ -672,8 +712,6 @@ exports.up = async (knex) => {
table.unique(['profile_id', 'media_id']); 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) => { await knex.schema.createTable('actors_photos', (table) => {
table.integer('actor_id', 12) table.integer('actor_id', 12)
.notNullable() .notNullable()
@@ -713,12 +751,20 @@ exports.up = async (knex) => {
table.datetime('pinged_at'); table.datetime('pinged_at');
table.datetime('verified_at'); table.datetime('verified_at');
table.unique(['actor_id', 'platform', 'handle']);
table.unique(['actor_id', 'url']); 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_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('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) => { await knex.schema.createTable('releases', (table) => {
table.increments('id'); table.increments('id');
@@ -741,6 +787,7 @@ exports.up = async (knex) => {
table.text('url', 1000); table.text('url', 1000);
table.text('title'); table.text('title');
table.specificType('alt_titles', 'text ARRAY'); table.specificType('alt_titles', 'text ARRAY');
table.specificType('alt_descriptions', 'text ARRAY');
table.text('slug'); table.text('slug');
table.timestamp('date'); table.timestamp('date');
@@ -758,6 +805,13 @@ exports.up = async (knex) => {
table.enum('date_precision', ['year', 'month', 'week', 'day', 'hour', 'minute', 'second']) table.enum('date_precision', ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'])
.defaultTo('day'); .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.text('description');
table.integer('duration') table.integer('duration')
@@ -773,6 +827,8 @@ exports.up = async (knex) => {
table.text('comment'); table.text('comment');
table.json('attributes');
table.integer('created_batch_id', 12) table.integer('created_batch_id', 12)
.references('id') .references('id')
.inTable('batches') .inTable('batches')
@@ -937,6 +993,8 @@ exports.up = async (knex) => {
}); });
await knex.schema.createTable('releases_tags', (table) => { await knex.schema.createTable('releases_tags', (table) => {
table.increments('id');
table.integer('tag_id', 12) table.integer('tag_id', 12)
.references('id') .references('id')
.inTable('tags'); .inTable('tags');
@@ -947,9 +1005,13 @@ exports.up = async (knex) => {
.inTable('releases') .inTable('releases')
.onDelete('cascade'); .onDelete('cascade');
table.integer('actor_id')
.references('id')
.inTable('actors');
table.text('original_tag'); table.text('original_tag');
table.unique(['tag_id', 'release_id']); table.unique(['release_id', 'original_tag']);
table.index('tag_id'); table.index('tag_id');
table.index('release_id'); table.index('release_id');
@@ -958,12 +1020,7 @@ exports.up = async (knex) => {
.defaultTo('scraper'); .defaultTo('scraper');
}); });
await knex.schema.createTable('releases_search', (table) => { 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));');
table.integer('release_id', 16)
.references('id')
.inTable('releases')
.onDelete('cascade');
});
await knex.schema.createTable('movies', (table) => { await knex.schema.createTable('movies', (table) => {
table.increments('id'); table.increments('id');
@@ -984,6 +1041,7 @@ exports.up = async (knex) => {
table.text('url', 1000); table.text('url', 1000);
table.text('title'); table.text('title');
table.specificType('alt_titles', 'text ARRAY'); table.specificType('alt_titles', 'text ARRAY');
table.specificType('alt_descriptions', 'text ARRAY');
table.text('slug'); table.text('slug');
table.timestamp('date'); table.timestamp('date');
@@ -1000,6 +1058,8 @@ exports.up = async (knex) => {
table.text('comment'); table.text('comment');
table.json('attributes');
table.integer('created_batch_id', 12) table.integer('created_batch_id', 12)
.references('id') .references('id')
.inTable('batches') .inTable('batches')
@@ -1134,13 +1194,6 @@ exports.up = async (knex) => {
table.unique(['movie_id', 'media_id']); 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) => { await knex.schema.createTable('series', (table) => {
table.increments('id'); table.increments('id');
@@ -1159,6 +1212,7 @@ exports.up = async (knex) => {
table.text('url', 1000); table.text('url', 1000);
table.text('title'); table.text('title');
table.specificType('alt_titles', 'text ARRAY'); table.specificType('alt_titles', 'text ARRAY');
table.specificType('alt_descriptions', 'text ARRAY');
table.text('slug'); table.text('slug');
table.timestamp('date'); table.timestamp('date');
@@ -1175,6 +1229,8 @@ exports.up = async (knex) => {
table.text('comment'); table.text('comment');
table.json('attributes');
table.integer('created_batch_id', 12) table.integer('created_batch_id', 12)
.references('id') .references('id')
.inTable('batches') .inTable('batches')
@@ -1288,13 +1344,6 @@ exports.up = async (knex) => {
table.unique(['serie_id', 'media_id']); 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) => { await knex.schema.createTable('chapters', (table) => {
table.increments('id'); table.increments('id');
@@ -1316,6 +1365,8 @@ exports.up = async (knex) => {
table.text('title'); table.text('title');
table.text('description'); table.text('description');
table.datetime('date');
table.integer('created_batch_id', 12) table.integer('created_batch_id', 12)
.references('id') .references('id')
.inTable('batches') .inTable('batches')
@@ -1379,6 +1430,34 @@ exports.up = async (knex) => {
table.unique(['tag_id', 'chapter_id']); 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) => { await knex.schema.createTable('users_roles', (table) => {
table.string('role') table.string('role')
.primary(); .primary();
@@ -1396,7 +1475,9 @@ exports.up = async (knex) => {
{ subject: 'actor', action: 'create' }, { subject: 'actor', action: 'create' },
{ subject: 'actor', action: 'update' }, { subject: 'actor', action: 'update' },
{ subject: 'actor', action: 'delete' }, { 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.specificType('last_ip', 'cidr');
table.jsonb('settings');
table.datetime('created_at') table.datetime('created_at')
.notNullable() .notNullable()
.defaultTo(knex.fn.now()); .defaultTo(knex.fn.now());
@@ -1660,6 +1743,98 @@ exports.up = async (knex) => {
.defaultTo(knex.fn.now()); .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) => { await knex.schema.createTable('alerts', (table) => {
table.increments('id'); table.increments('id');
@@ -1698,6 +1873,10 @@ exports.up = async (knex) => {
.notNullable() .notNullable()
.defaultTo(false); .defaultTo(false);
table.boolean('is_expired')
.notNullable()
.defaultTo(false);
table.json('meta'); table.json('meta');
table.text('comment'); 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) => { await knex.schema.createTable('notifications', (table) => {
table.increments('id'); table.increments('id');
@@ -1901,6 +2105,18 @@ exports.up = async (knex) => {
.notNullable() .notNullable()
.defaultTo(false); .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') table.datetime('created_at')
.notNullable() .notNullable()
.defaultTo(knex.fn.now()); .defaultTo(knex.fn.now());
@@ -2177,12 +2393,20 @@ exports.up = async (knex) => {
.inTable('entities'); .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 // SEARCH AND SORT
await knex.raw(` 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 */ /* allow scenes without dates to be mixed inbetween scenes with dates */
ALTER TABLE releases ALTER TABLE releases
ADD COLUMN effective_date timestamptz ADD COLUMN effective_date timestamptz
@@ -2200,19 +2424,12 @@ exports.up = async (knex) => {
// INDEXES // INDEXES
await knex.raw(` await knex.raw(`
CREATE UNIQUE INDEX unique_actor_slugs_network ON actors (slug, entity_id, entry_id); 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_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_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_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 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 // FUNCTIONS
@@ -2275,7 +2492,8 @@ exports.up = async (knex) => {
stashes.id as stash_id, stashes.id as stash_id,
COUNT(DISTINCT stashes_scenes)::integer as stashed_scenes, COUNT(DISTINCT stashes_scenes)::integer as stashed_scenes,
COUNT(DISTINCT stashes_movies)::integer as stashed_movies, 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 FROM stashes
LEFT JOIN stashes_scenes ON stashes_scenes.stash_id = stashes.id 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_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_tags CASCADE;
DROP TABLE IF EXISTS chapters_posters CASCADE; DROP TABLE IF EXISTS chapters_posters CASCADE;
DROP TABLE IF EXISTS chapters_photos 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_tags CASCADE;
DROP TABLE IF EXISTS banners 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 batches CASCADE;
DROP TABLE IF EXISTS fingerprints_types 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_avatars CASCADE;
DROP TABLE IF EXISTS actors_photos 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 sites CASCADE;
DROP TABLE IF EXISTS studios CASCADE; DROP TABLE IF EXISTS studios CASCADE;
DROP TABLE IF EXISTS media_sfw 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 media CASCADE;
DROP TABLE IF EXISTS countries CASCADE; DROP TABLE IF EXISTS countries CASCADE;
DROP TABLE IF EXISTS languages CASCADE;
DROP TABLE IF EXISTS networks CASCADE; DROP TABLE IF EXISTS networks CASCADE;
DROP TABLE IF EXISTS entities_types 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_actors CASCADE;
DROP TABLE IF EXISTS stashes 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_scenes CASCADE;
DROP TABLE IF EXISTS alerts_actors CASCADE; DROP TABLE IF EXISTS alerts_actors CASCADE;
DROP TABLE IF EXISTS alerts_tags CASCADE; DROP TABLE IF EXISTS alerts_tags CASCADE;

View File

@@ -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');
});
};

View File

@@ -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');
});
};

View File

@@ -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');
});
};

View File

@@ -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');
};

View File

@@ -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']);
});
};

View File

@@ -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();
});
};

View File

@@ -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');
});
};

View File

@@ -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');
};

View File

@@ -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');
};

View File

@@ -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');
};

View File

@@ -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');
};

View File

@@ -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);
`);
};

View File

@@ -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');
};

View File

@@ -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');
});
};

View File

@@ -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');
});
};

View File

@@ -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);
`);
};

View File

@@ -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
);
`);
};

View File

@@ -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);
`);
};