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