Merged migrations.

This commit is contained in:
DebaucheryLibrarian
2026-01-29 20:13:32 +01:00
parent 30923f7cda
commit 888fa50d8a
43 changed files with 1190 additions and 2294 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('random_campaign', (table) => {
table.integer('id')
.notNullable()
.references('id')
.inTable('campaigns');
});
await knex.raw(`
CREATE OR REPLACE FUNCTION get_random_campaign(min_ratio decimal default 0, max_ratio decimal default 1000.0) RETURNS random_campaign AS $$
SELECT * FROM (
SELECT DISTINCT ON (CASE WHEN parent_id IS NOT NULL THEN parent_id ELSE entity_id END)
banner_id, url, entity_id, affiliate_id, parent_id, id
FROM (
SELECT
campaigns.*, entities.parent_id as parent_id
FROM campaigns
LEFT JOIN entities ON entities.id = campaigns.entity_id
LEFT JOIN banners ON banners.id = campaigns.banner_id
WHERE banner_id IS NOT NULL
AND ratio >= min_ratio
AND ratio <= max_ratio
ORDER BY RANDOM()
) random_campaigns
) random_banners
ORDER BY RANDOM()
LIMIT 1;
$$ LANGUAGE SQL STABLE;
`);
};
exports.down = async (knex) => {
await knex.schema.alterTable('random_campaign', (table) => {
table.dropColumn('campaign_id');
});
await knex.raw(`
CREATE OR REPLACE FUNCTION get_random_campaign(min_ratio decimal default 0, max_ratio decimal default 1000.0) RETURNS random_campaign AS $$
SELECT * FROM (
SELECT DISTINCT ON (CASE WHEN parent_id IS NOT NULL THEN parent_id ELSE entity_id END)
banner_id, url, entity_id, affiliate_id, parent_id
FROM (
SELECT
campaigns.*, entities.parent_id as parent_id
FROM campaigns
LEFT JOIN entities ON entities.id = campaigns.entity_id
LEFT JOIN banners ON banners.id = campaigns.banner_id
WHERE banner_id IS NOT NULL
AND ratio >= min_ratio
AND ratio <= max_ratio
ORDER BY RANDOM()
) random_campaigns
) random_banners
ORDER BY RANDOM()
LIMIT 1;
$$ LANGUAGE SQL STABLE;
`);
};

View File

@@ -1,47 +0,0 @@
const config = require('config');
exports.up = async (knex) => {
await knex.raw(`
CREATE MATERIALIZED VIEW releases_summaries AS (
SELECT
releases.id as release_id,
channels.slug as channel_slug,
channels.type as channel_type,
networks.slug as network_slug,
networks.type as network_type,
parent_networks.slug as parent_network_slug,
parent_networks.type as parent_network_type,
studios.showcased IS NOT false
AND (channels.showcased IS NOT false OR COALESCE(studios.showcased, false) = true)
AND (networks.showcased IS NOT false OR COALESCE(channels.showcased, false) = true OR COALESCE(studios.showcased, false) = true)
AS showcased,
batches.showcased AS batch_showcased,
releases.effective_date,
releases.created_at,
array_agg(tags.slug ORDER BY tags.priority DESC) FILTER (WHERE tags.slug IS NOT NULL) AS tags
FROM releases
LEFT JOIN releases_tags ON releases_tags.release_id = releases.id
LEFT JOIN tags ON tags.id = releases_tags.tag_id
LEFT JOIN entities AS channels ON channels.id = releases.entity_id
LEFT JOIN entities AS studios ON studios.id = releases.studio_id
LEFT JOIN entities AS networks ON networks.id = channels.parent_id
LEFT JOIN entities AS parent_networks ON parent_networks.id = networks.parent_id
LEFT JOIN batches ON batches.id = releases.updated_batch_id
GROUP BY releases.id, studios.showcased, batches.showcased,
channels.showcased, channels.slug, channels.type,
networks.showcased, networks.slug, networks.type,
parent_networks.slug, parent_networks.type
);
COMMENT ON MATERIALIZED VIEW releases_summaries IS E'@foreignKey (release_id) references releases (id)';
GRANT ALL ON releases_summaries TO :visitor;
`, {
visitor: knex.raw(config.database.query.user),
});
};
exports.down = async (knex) => {
await knex.raw(`
DROP MATERIALIZED VIEW IF EXISTS releases_summaries;
`);
};

View File

@@ -1,27 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('actors_social', (table) => {
table.integer('profile_id')
.references('id')
.inTable('actors_profiles');
table.dropUnique(['url', 'actor_id']);
table.unique(['url', 'actor_id', 'profile_id']);
});
await knex.raw(`
CREATE UNIQUE INDEX actors_social_url_actor_id_null_unique ON actors_social (url, actor_id) WHERE profile_id IS NULL;
`);
};
exports.down = async (knex) => {
await knex.raw(`
DROP INDEX actors_social_url_actor_id_null_unique;
`);
await knex.schema.alterTable('actors_social', (table) => {
table.dropUnique(['url', 'actor_id', 'profile_id']);
table.unique(['url', 'actor_id']);
table.dropColumn('profile_id');
});
};

View File

@@ -1,24 +0,0 @@
const config = require('config');
exports.up = async (knex) => {
await knex.schema.createTable('releases_caps', (table) => {
table.integer('release_id')
.notNullable()
.references('id')
.inTable('releases')
.onDelete('cascade');
table.text('media_id')
.notNullable()
.references('id')
.inTable('media');
});
await knex.raw('GRANT ALL ON releases_caps TO :visitor;', {
visitor: knex.raw(config.database.query.user),
});
};
exports.down = async (knex) => {
await knex.schema.dropTable('releases_caps');
};

View File

@@ -1,27 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('actors_profiles', (table) => {
table.string('hair_type');
table.decimal('shoe_size');
table.string('blood_type');
});
await knex.schema.alterTable('actors', (table) => {
table.string('hair_type');
table.decimal('shoe_size');
table.string('blood_type');
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('actors_profiles', (table) => {
table.dropColumn('hair_type');
table.dropColumn('shoe_size');
table.dropColumn('blood_type');
});
await knex.schema.alterTable('actors', (table) => {
table.dropColumn('hair_type');
table.dropColumn('shoe_size');
table.dropColumn('blood_type');
});
};

View File

@@ -1,13 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('batches', (table) => {
table.boolean('showcased')
.notNullable()
.defaultTo(true);
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('batches', (table) => {
table.dropColumn('showcased');
});
};

View File

@@ -1,27 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('releases', (table) => {
table.specificType('alt_titles', 'text ARRAY');
});
await knex.schema.alterTable('movies', (table) => {
table.specificType('alt_titles', 'text ARRAY');
});
await knex.schema.alterTable('series', (table) => {
table.specificType('alt_titles', 'text ARRAY');
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('releases', (table) => {
table.dropColumn('alt_titles');
});
await knex.schema.alterTable('movies', (table) => {
table.dropColumn('alt_titles');
});
await knex.schema.alterTable('series', (table) => {
table.dropColumn('alt_titles');
});
};

View File

@@ -1,36 +0,0 @@
exports.up = async (knex) => {
await knex.schema.createTable('movies_teasers', (table) => {
table.integer('movie_id', 16)
.notNullable()
.references('id')
.inTable('movies')
.onDelete('cascade');
table.text('media_id', 21)
.notNullable()
.references('id')
.inTable('media');
table.unique('movie_id');
});
await knex.schema.createTable('series_teasers', (table) => {
table.integer('serie_id', 16)
.notNullable()
.references('id')
.inTable('series')
.onDelete('cascade');
table.text('media_id', 21)
.notNullable()
.references('id')
.inTable('media');
table.unique('serie_id');
});
};
exports.down = async (knex) => {
await knex.schema.dropTable('movies_teasers');
await knex.schema.dropTable('series_teasers');
};

View File

@@ -1,19 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('releases', (table) => {
table.integer('photo_count');
});
await knex.schema.alterTable('movies', (table) => {
table.integer('photo_count');
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('releases', (table) => {
table.dropColumn('photo_count');
});
await knex.schema.alterTable('movies', (table) => {
table.dropColumn('photo_count');
});
};

View File

@@ -1,42 +0,0 @@
const config = require('config');
exports.up = async (knex) => {
await knex.schema.alterTable('alerts', (table) => {
table.boolean('all')
.defaultTo(true);
});
await knex.schema.alterTable('alerts_entities', (table) => {
table.dropUnique('alert_id');
});
await knex.schema.createTable('alerts_matches', (table) => {
table.increments('id');
table.integer('alert_id')
.references('id')
.inTable('alerts')
.onDelete('cascade');
table.string('property');
table.string('expression');
});
await knex.raw(`
GRANT SELECT ON alerts_matches TO :visitor;
`, {
visitor: knex.raw(config.database.query.user),
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('alerts', (table) => {
table.dropColumn('all');
});
await knex.schema.alterTable('alerts_entities', (table) => {
table.unique('alert_id');
});
await knex.schema.dropTable('alerts_matches');
};

View File

@@ -1,21 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('tags', (table) => {
table.specificType('implied_tag_ids', 'integer[]');
});
await knex.schema.alterTable('releases_tags', (table) => {
table.enum('source', ['scraper', 'editor', 'implied'])
.notNullable()
.defaultTo('scraper');
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('tags', (table) => {
table.dropColumn('implied_tag_ids');
});
await knex.schema.alterTable('releases_tags', (table) => {
table.dropColumn('source');
});
};

View File

@@ -1,19 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('movies', (table) => {
table.integer('photo_count');
});
await knex.schema.alterTable('series', (table) => {
table.integer('photo_count');
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('movies', (table) => {
table.dropColumn('photo_count');
});
await knex.schema.alterTable('series', (table) => {
table.dropColumn('photo_count');
});
};

View File

@@ -1,62 +0,0 @@
const config = require('config');
exports.up = async function up(knex) {
await knex.raw(`
CREATE MATERIALIZED VIEW actors_meta AS (
SELECT
actors.id as actor_id,
COUNT(DISTINCT stashes_actors)::integer as stashed,
COUNT(DISTINCT releases_actors)::integer as scenes,
row_to_json(avatars) as avatar
FROM actors
LEFT JOIN stashes_actors ON stashes_actors.actor_id = actors.id
LEFT JOIN releases_actors ON releases_actors.actor_id = actors.id
LEFT JOIN media AS avatars ON avatars.id = actors.avatar_media_id
GROUP BY
actors.id,
avatars.id
);
CREATE MATERIALIZED VIEW scenes_meta AS (
SELECT
releases.id as scene_id,
COUNT(DISTINCT stashes_scenes)::integer as stashed
FROM releases
LEFT JOIN stashes_scenes ON stashes_scenes.scene_id = releases.id
GROUP BY releases.id
);
CREATE MATERIALIZED VIEW movies_meta AS (
SELECT
movie_id,
stashed,
stashed_scenes,
stashed + stashed_scenes as stashed_total
FROM (
SELECT
movies.id as movie_id,
COUNT(DISTINCT stashes_movies)::integer as stashed,
COUNT(DISTINCT stashes_scenes)::integer as stashed_scenes
FROM movies
LEFT JOIN stashes_movies ON stashes_movies.movie_id = movies.id
LEFT JOIN movies_scenes ON movies_scenes.movie_id = movies.id
LEFT JOIN stashes_scenes ON stashes_scenes.scene_id = movies_scenes.scene_id
GROUP BY movies.id
) AS meta
);
GRANT ALL ON actors_meta TO :visitor;
GRANT ALL ON scenes_meta TO :visitor;
GRANT ALL ON movies_meta TO :visitor;
`, {
visitor: knex.raw(config.database.query.user),
});
};
exports.down = async function down(knex) {
await knex.raw(`
DROP MATERIALIZED VIEW IF EXISTS actors_meta;
DROP MATERIALIZED VIEW IF EXISTS scenes_meta;
DROP MATERIALIZED VIEW IF EXISTS movies_meta;
`);
};

View File

@@ -1,138 +0,0 @@
const config = require('config');
const manticore = require('manticoresearch');
const mantiClient = new manticore.ApiClient();
mantiClient.basePath = `http://${config.database.manticore.host}:${config.database.manticore.httpPort}`;
const utilsApi = new manticore.UtilsApi(mantiClient);
const scenesFields = `
id int,
title text,
title_filtered text,
shoot_id text,
channel_id int,
channel_name text,
channel_slug text,
network_id int,
network_name text,
network_slug text,
studio_id int,
studio_name text,
studio_slug text,
entity_ids multi,
actor_ids multi,
actors text,
tag_ids multi,
tags text,
movie_ids multi,
movies text,
serie_ids multi,
series text,
meta text,
date timestamp,
is_showcased bool,
created_at timestamp,
effective_date timestamp,
stashed int,
dupe_index int
`;
const moviesFields = `
id int,
title text,
title_filtered text,
channel_id int,
channel_name text,
channel_slug text,
network_id int,
network_name text,
network_slug text,
entity_ids multi,
actor_ids multi,
actors text,
tag_ids multi,
tags text,
meta text,
date timestamp,
has_cover bool,
created_at timestamp,
effective_date timestamp,
stashed int,
stashed_scenes int,
stashed_total int,
dupe_index int
`;
const actorsFields = `
id int,
name text,
slug string,
gender string,
date_of_birth timestamp,
country string,
has_avatar bool,
mass int,
height int,
cup string,
natural_boobs int,
penis_length int,
penis_girth int,
stashed int,
scenes int
`;
exports.up = async () => {
try {
await utilsApi.sql(`create table if not exists scenes (${scenesFields})`);
await utilsApi.sql(`create table if not exists scenes_stashed (
scene_id int,
stash_id int,
user_id int,
created_at timestamp
)`);
await utilsApi.sql(`create table if not exists movies (${moviesFields})`);
await utilsApi.sql(`create table if not exists movies_stashed (
movie_id int,
stash_id int,
user_id int,
created_at timestamp
)`);
await utilsApi.sql(`create table if not exists actors (${actorsFields}) min_prefix_len='3'`);
await utilsApi.sql(`create table if not exists actors_stashed (
actor_id int,
stash_id int,
user_id int,
created_at timestamp
)`);
/*
await knex.schema.alterTable('stashes_scenes', (table) => table.increments('id'));
await knex.schema.alterTable('stashes_movies', (table) => table.increments('id'));
await knex.schema.alterTable('stashes_actors', (table) => table.increments('id'));
await knex.schema.alterTable('stashes_series', (table) => table.increments('id'));
*/
} catch (error) {
console.log(error);
}
};
exports.down = async (knex) => {
await utilsApi.sql('drop table if exists scenes');
await utilsApi.sql('drop table if exists scenes_stashed');
await utilsApi.sql('drop table if exists movies');
await utilsApi.sql('drop table if exists movies_stashed');
await utilsApi.sql('drop table if exists actors');
await utilsApi.sql('drop table if exists actors_stashed');
await knex.schema.alterTable('stashes_scenes', (table) => table.dropColumn('id'));
await knex.schema.alterTable('stashes_movies', (table) => table.dropColumn('id'));
await knex.schema.alterTable('stashes_actors', (table) => table.dropColumn('id'));
await knex.schema.alterTable('stashes_series', (table) => table.dropColumn('id'));
};

View File

@@ -1,21 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('users', (table) => {
table.dropUnique('username');
});
await knex.raw(`
CREATE UNIQUE INDEX username_unique_index ON users (LOWER(username));
CREATE UNIQUE INDEX email_unique_index ON users (LOWER(email));
`);
};
exports.down = async (knex) => {
await knex.raw(`
DROP INDEX IF EXISTS username_unique_index;
DROP INDEX IF EXISTS email_unique_index;
`);
await knex.schema.alterTable('users', (table) => {
table.unique('username');
});
};

View File

@@ -1,22 +0,0 @@
exports.up = async function up(knex) {
await knex.raw(`
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
);
`);
};
exports.down = async function down(knex) {
await knex.raw(`
DROP MATERIALIZED VIEW IF EXISTS stashes_meta;
`);
};

View File

@@ -1,38 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('alerts', (table) => {
table.boolean('all_actors')
.notNullable()
.defaultTo(true);
table.boolean('all_entities')
.notNullable()
.defaultTo(true);
table.boolean('all_tags')
.notNullable()
.defaultTo(true);
table.boolean('all_matches')
.notNullable()
.defaultTo(true);
});
await knex.raw(`
UPDATE alerts
SET
all_actors = false,
all_entities = false,
all_tags = false,
all_matches= false
WHERE alerts.all = false;
`);
};
exports.down = async (knex) => {
await knex.schema.alterTable('alerts', (table) => {
table.dropColumn('all_actors');
table.dropColumn('all_entities');
table.dropColumn('all_tags');
table.dropColumn('all_matches');
});
};

View File

@@ -1,65 +0,0 @@
const config = require('config');
exports.up = async (knex) => {
await knex.schema.alterTable('entities', (table) => {
// internal options, as opposed to parameters for scraper options
table.json('options');
});
await knex.schema.alterTable('releases', (table) => {
table.dropForeign('entity_id');
table.foreign('entity_id')
.references('id')
.inTable('entities')
.onDelete('cascade');
});
await knex.schema.alterTable('releases_caps', (table) => {
table.unique(['release_id', 'media_id']);
});
await knex.schema.createTable('movies_tags', (table) => {
table.integer('tag_id')
.references('id')
.inTable('tags');
table.integer('movie_id')
.notNullable()
.references('id')
.inTable('movies')
.onDelete('cascade');
table.text('original_tag');
table.text('source')
.defaultTo('scraper');
table.unique(['tag_id', 'movie_id']);
});
await knex.raw('GRANT ALL ON ALL TABLES IN SCHEMA public TO :visitor;', {
visitor: knex.raw(config.database.query.user),
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('entities', (table) => {
table.dropColumn('options');
});
await knex.schema.alterTable('releases', (table) => {
table.dropForeign('entity_id');
table.foreign('entity_id')
.references('id')
.inTable('entities')
.onDelete('no action');
});
await knex.schema.alterTable('releases_caps', (table) => {
table.dropUnique(['release_id', 'media_id']);
});
await knex.schema.dropTable('movies_tags');
};

View File

@@ -1,31 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('entities_tags', (table) => {
table.dropForeign('tag_id');
table.dropForeign('entity_id');
table.foreign('tag_id')
.references('id')
.inTable('tags')
.onDelete('cascade');
table.foreign('entity_id')
.references('id')
.inTable('entities')
.onDelete('cascade');
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('entities_tags', (table) => {
table.dropForeign('tag_id');
table.dropForeign('entity_id');
table.foreign('tag_id')
.references('id')
.inTable('tags');
table.foreign('entity_id')
.references('id')
.inTable('entities');
});
};

View File

@@ -1,25 +0,0 @@
exports.up = async (knex) => {
await knex.schema.createTable('users_templates', (table) => {
table.increments('id');
table.integer('user_id')
.notNullable()
.references('id')
.inTable('users');
table.string('name')
.notNullable();
table.text('template')
.notNullable();
table.unique(['user_id', 'name']);
table.datetime('created_at')
.defaultTo(knex.fn.now());
});
};
exports.down = async (knex) => {
await knex.schema.dropTable('users_templates');
};

View File

@@ -1,28 +0,0 @@
exports.up = async (knex) => {
await knex.schema.createTable('users_keys', (table) => {
table.increments('id');
table.integer('user_id')
.notNullable()
.references('id')
.inTable('users');
table.text('key')
.notNullable();
table.string('identifier');
table.unique(['user_id', 'identifier']);
table.datetime('last_used_at');
table.specificType('last_used_ip', 'inet');
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
});
};
exports.down = async (knex) => {
await knex.schema.dropTable('users_keys');
};

View File

@@ -1,87 +0,0 @@
exports.up = async (knex) => {
await knex.schema.createTable('scenes_revisions', (table) => {
table.increments('id');
table.integer('scene_id')
.notNullable()
.references('id')
.inTable('releases')
.onDelete('set null');
table.integer('user_id')
.references('id')
.inTable('users')
.onDelete('set null');
table.json('base')
.notNullable();
table.json('deltas')
.notNullable();
table.text('hash')
.notNullable();
table.text('comment');
table.boolean('approved');
table.integer('reviewed_by')
.references('id')
.inTable('users')
.onDelete('set null');
table.datetime('reviewed_at');
table.text('feedback');
table.datetime('applied_at');
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
});
await knex.schema.createTable('bans', (table) => {
table.increments('id');
table.integer('user_id')
.references('id')
.inTable('users')
.onDelete('set null');
table.string('username');
table.specificType('ip', 'cidr');
table.boolean('match_all')
.notNullable()
.defaultTo(false);
table.string('scope');
table.boolean('shadow');
table.integer('banned_by')
.references('id')
.inTable('users')
.onDelete('set null');
table.datetime('expires_at')
.notNullable();
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
});
await knex.schema.alterTable('users', (table) => {
table.specificType('last_ip', 'cidr');
});
};
exports.down = async (knex) => {
await knex.schema.dropTable('scenes_revisions');
await knex.schema.dropTable('bans');
await knex.schema.alterTable('users', (table) => {
table.dropColumn('last_ip');
});
};

View File

@@ -1,35 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('actors', (table) => {
table.integer('leg');
table.integer('foot');
table.integer('thigh');
});
await knex.schema.alterTable('actors_profiles', (table) => {
table.integer('leg');
table.integer('foot');
table.integer('thigh');
});
await knex.schema.alterTable('releases', (table) => {
table.integer('video_count');
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('actors', (table) => {
table.dropColumn('leg');
table.dropColumn('foot');
table.dropColumn('thigh');
});
await knex.schema.alterTable('actors_profiles', (table) => {
table.dropColumn('leg');
table.dropColumn('foot');
table.dropColumn('thigh');
});
await knex.schema.alterTable('releases', (table) => {
table.dropColumn('video_count');
});
};

View File

@@ -1,15 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('scenes_revisions', (table) => {
table.integer('scene_id')
.nullable()
.alter();
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('scenes_revisions', (table) => {
table.integer('scene_id')
.notNullable()
.alter();
});
};

View File

@@ -1,119 +0,0 @@
exports.up = async (knex) => {
await knex.raw('CREATE UNIQUE INDEX unique_main_profiles ON actors_profiles (actor_id) WHERE (entity_id IS NULL);');
await knex.schema.createTable('actors_revisions', (table) => {
table.increments('id');
table.integer('actor_id')
.references('id')
.inTable('actors')
.onDelete('set null');
table.integer('profile_id')
.references('id')
.inTable('actors_profiles')
.onDelete('set null');
table.integer('user_id')
.references('id')
.inTable('users')
.onDelete('set null');
table.json('base')
.notNullable();
table.json('deltas')
.notNullable();
table.text('hash')
.notNullable();
table.text('comment');
table.boolean('approved');
table.integer('reviewed_by')
.references('id')
.inTable('users')
.onDelete('set null');
table.datetime('reviewed_at');
table.text('feedback');
table.datetime('applied_at');
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
});
await knex.schema.alterTable('actors', (table) => {
table.integer('boobs_volume');
table.enum('boobs_implant', ['saline', 'silicone', 'gummy', 'fat']);
table.enum('boobs_placement', ['over', 'under']);
table.string('boobs_surgeon');
table.boolean('natural_butt');
table.integer('butt_volume');
table.enum('butt_implant', ['bbl', 'lift', 'silicone', 'lipo', 'filler', 'mms']);
table.boolean('natural_lips');
table.integer('lips_volume');
table.string('agency');
});
await knex.schema.alterTable('actors_profiles', (table) => {
table.integer('boobs_volume');
table.enum('boobs_implant', ['saline', 'silicone', 'gummy', 'fat']);
table.enum('boobs_placement', ['over', 'under']);
table.string('boobs_surgeon');
table.boolean('natural_butt');
table.integer('butt_volume');
table.enum('butt_implant', ['bbl', 'lift', 'silicone', 'lipo', 'filler', 'mms']);
table.boolean('natural_lips');
table.integer('lips_volume');
table.string('agency');
});
};
exports.down = async (knex) => {
await knex.raw('DROP INDEX unique_main_profiles;');
await knex.schema.dropTable('actors_revisions');
await knex.schema.alterTable('actors', (table) => {
table.dropColumn('boobs_volume');
table.dropColumn('boobs_implant');
table.dropColumn('boobs_placement');
table.dropColumn('boobs_surgeon');
table.dropColumn('natural_butt');
table.dropColumn('butt_volume');
table.dropColumn('butt_implant');
table.dropColumn('natural_lips');
table.dropColumn('lips_volume');
table.dropColumn('agency');
});
await knex.schema.alterTable('actors_profiles', (table) => {
table.dropColumn('boobs_volume');
table.dropColumn('boobs_implant');
table.dropColumn('boobs_placement');
table.dropColumn('boobs_surgeon');
table.dropColumn('natural_butt');
table.dropColumn('butt_volume');
table.dropColumn('butt_implant');
table.dropColumn('natural_lips');
table.dropColumn('lips_volume');
table.dropColumn('agency');
});
};

View File

@@ -1,19 +0,0 @@
function createColumns(table) {
table.enum('boobs_incision', ['mammary', 'areolar', 'crescent', 'lollipop', 'anchor', 'axillary', 'umbilical']);
table.boolean('natural_labia');
}
exports.up = async (knex) => {
await knex.schema.alterTable('actors', createColumns);
await knex.schema.alterTable('actors_profiles', createColumns);
};
function dropColumns(table) {
table.dropColumn('boobs_incision');
table.dropColumn('natural_labia');
}
exports.down = async (knex) => {
await knex.schema.alterTable('actors', dropColumns);
await knex.schema.alterTable('actors_profiles', dropColumns);
};

View File

@@ -1,75 +0,0 @@
exports.up = async function(knex) {
// restore avatars in table in case of rollback and rerun
const avatars = await knex('actors_avatars')
.select('actors_avatars.*', 'actors_profiles.actor_id')
.leftJoin('actors_profiles', 'actors_profiles.id', 'actors_avatars.profile_id');
await knex('actors_avatars').delete();
await knex.schema.alterTable('actors_avatars', (table) => {
table.integer('profile_id')
.nullable()
.alter();
table.integer('actor_id')
.notNullable()
.references('id')
.inTable('actors');
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
table.dropUnique('profile_id');
table.unique(['profile_id', 'media_id']);
});
await knex.schema.alterTable('media', (table) => {
// actor avatars often retain the same URL when updated, handle URL-deduping in app code
table.dropUnique('source');
table.string('source_version'); // usually etag
});
await knex.raw('CREATE UNIQUE INDEX unique_main_avatars ON actors_avatars (actor_id) WHERE (profile_id IS NULL);');
if (avatars.length > 0) {
await knex('actors_avatars').insert(avatars);
}
const profiles = await knex('actors_profiles')
.select('id', 'actor_id', 'avatar_media_id')
.whereNotNull('avatar_media_id');
await knex('actors_avatars')
.insert(profiles.map((profile) => ({
actor_id: profile.actor_id,
profile_id: profile.id,
media_id: profile.avatar_media_id,
})))
.onConflict()
.ignore();
};
exports.down = async function(knex) {
// no need to delete all entries, only the ones incompatible with the old scheme
await knex('actors_avatars')
.whereNull('profile_id')
.delete();
await knex.schema.alterTable('actors_avatars', (table) => {
table.integer('profile_id')
.notNullable()
.alter();
table.dropColumn('actor_id');
table.dropColumn('created_at');
table.unique('profile_id');
table.dropUnique(['profile_id', 'media_id']);
});
await knex.schema.alterTable('media', (table) => {
table.dropColumn('source_version');
table.unique('source');
});
};

View File

@@ -1,46 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('actors_social', (table) => {
table.dropUnique(['url', 'actor_id', 'profile_id']);
table.dropColumn('profile_id');
table.string('handle');
table.boolean('is_broken')
.notNullable()
.defaultTo(false);
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_social ADD CONSTRAINT socials_url_or_handle CHECK (num_nulls(handle, url) = 1);');
await knex.raw('ALTER TABLE actors_social ADD CONSTRAINT socials_handle_and_platform CHECK (num_nulls(platform, handle) = 2 or num_nulls(platform, handle) = 0);');
await knex.schema.renameTable('actors_social', 'actors_socials');
};
exports.down = async (knex) => {
await knex.raw('ALTER TABLE actors_socials DROP CONSTRAINT socials_url_or_handle;');
await knex.raw('ALTER TABLE actors_socials DROP CONSTRAINT socials_handle_and_platform;');
await knex.schema.renameTable('actors_socials', 'actors_social');
await knex.schema.alterTable('actors_social', (table) => {
table.dropUnique(['actor_id', 'platform', 'handle']);
table.dropUnique(['actor_id', 'url']);
table.integer('profile_id')
.references('id')
.inTable('actors_profiles');
table.dropColumn('handle');
table.dropColumn('verified_at');
table.dropColumn('pinged_at');
table.dropColumn('is_broken');
table.unique(['url', 'actor_id', 'profile_id']);
});
};

View File

@@ -1,53 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('media', (table) => table.index('sfw_media_id'));
await knex.schema.alterTable('actors_profiles', (table) => table.index('avatar_media_id'));
await knex.schema.alterTable('actors_avatars', (table) => table.index('media_id'));
await knex.schema.alterTable('actors_photos', (table) => table.index('media_id'));
await knex.schema.alterTable('chapters_photos', (table) => table.index('media_id'));
await knex.schema.alterTable('chapters_posters', (table) => table.index('media_id'));
await knex.schema.alterTable('movies_covers', (table) => table.index('media_id'));
await knex.schema.alterTable('movies_photos', (table) => table.index('media_id'));
await knex.schema.alterTable('movies_posters', (table) => table.index('media_id'));
await knex.schema.alterTable('movies_teasers', (table) => table.index('media_id'));
await knex.schema.alterTable('movies_trailers', (table) => table.index('media_id'));
await knex.schema.alterTable('releases_caps', (table) => table.index('media_id'));
await knex.schema.alterTable('releases_covers', (table) => table.index('media_id'));
await knex.schema.alterTable('releases_posters', (table) => table.index('media_id'));
await knex.schema.alterTable('releases_photos', (table) => table.index('media_id'));
await knex.schema.alterTable('releases_teasers', (table) => table.index('media_id'));
await knex.schema.alterTable('releases_trailers', (table) => table.index('media_id'));
await knex.schema.alterTable('series_covers', (table) => table.index('media_id'));
await knex.schema.alterTable('series_photos', (table) => table.index('media_id'));
await knex.schema.alterTable('series_posters', (table) => table.index('media_id'));
await knex.schema.alterTable('series_teasers', (table) => table.index('media_id'));
await knex.schema.alterTable('series_trailers', (table) => table.index('media_id'));
await knex.schema.alterTable('tags_photos', (table) => table.index('media_id'));
await knex.schema.alterTable('tags_posters', (table) => table.index('media_id'));
};
exports.down = async (knex) => {
await knex.schema.alterTable('media', (table) => table.dropIndex('sfw_media_id'));
await knex.schema.alterTable('actors_profiles', (table) => table.dropIndex('avatar_media_id'));
await knex.schema.alterTable('actors_avatars', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('actors_photos', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('chapters_photos', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('chapters_posters', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('movies_covers', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('movies_photos', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('movies_posters', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('movies_teasers', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('movies_trailers', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('releases_caps', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('releases_covers', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('releases_posters', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('releases_photos', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('releases_teasers', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('releases_trailers', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('series_covers', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('series_photos', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('series_posters', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('series_teasers', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('series_trailers', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('tags_photos', (table) => table.dropIndex('media_id'));
await knex.schema.alterTable('tags_posters', (table) => table.dropIndex('media_id'));
};

View File

@@ -1,102 +0,0 @@
const config = require('config');
exports.up = async (knex) => {
await knex.schema.alterTable('alerts', (table) => {
table.boolean('from_preset')
.notNullable()
.defaultTo(false);
table.text('comment');
});
await knex.schema.createMaterializedView('alerts_users_actors', (view) => {
view.columns('user_id', 'actor_id', 'alert_ids');
view.as(
knex('alerts_actors')
.select(
'alerts.user_id',
'alerts_actors.actor_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_actors.id is null) as is_only'),
)
.leftJoin('alerts', 'alerts.id', 'alerts_actors.alert_id')
.leftJoin('alerts_entities', 'alerts_entities.alert_id', 'alerts_actors.alert_id')
.leftJoin('alerts_tags', 'alerts_tags.alert_id', 'alerts_actors.alert_id')
.leftJoin('alerts_matches', 'alerts_matches.alert_id', 'alerts_actors.alert_id')
.leftJoin('alerts_actors as related_actors', (joinBuilder) => {
joinBuilder
.on('related_actors.alert_id', 'alerts_actors.alert_id')
.on('related_actors.actor_id', '!=', 'alerts_actors.actor_id');
})
.groupBy(['user_id', 'alerts_actors.actor_id', 'is_only']),
);
});
await knex.schema.createMaterializedView('alerts_users_tags', (view) => {
view.columns('user_id', 'tag_id', 'alert_ids');
view.as(
knex('alerts_tags')
.select(
'alerts.user_id',
'alerts_tags.tag_id',
knex.raw('array_agg(distinct alerts.id) as alert_ids'),
knex.raw('(alerts_actors.id is null and alerts_entities.id is null and alerts_matches.id is null and related_tags.id is null) as is_only'),
)
.leftJoin('alerts', 'alerts.id', 'alerts_tags.alert_id')
.leftJoin('alerts_entities', 'alerts_entities.alert_id', 'alerts_tags.alert_id')
.leftJoin('alerts_actors', 'alerts_actors.alert_id', 'alerts_tags.alert_id')
.leftJoin('alerts_matches', 'alerts_matches.alert_id', 'alerts_tags.alert_id')
.leftJoin('alerts_tags as related_tags', (joinBuilder) => {
joinBuilder
.on('related_tags.alert_id', 'alerts_tags.alert_id')
.on('related_tags.tag_id', '!=', 'alerts_tags.tag_id');
})
.groupBy(['user_id', 'alerts_tags.tag_id', 'is_only']),
);
});
await knex.schema.createMaterializedView('alerts_users_entities', (view) => {
view.columns('user_id', 'entity_id', 'alert_ids');
view.as(
knex('alerts_entities')
.select(
'alerts.user_id',
'alerts_entities.entity_id',
knex.raw('array_agg(distinct alerts.id) as alert_ids'),
knex.raw('(alerts_actors.id is null and alerts_tags.id is null and alerts_matches.id is null and related_entities.id is null) as is_only'),
)
.leftJoin('alerts', 'alerts.id', 'alerts_entities.alert_id')
.leftJoin('alerts_tags', 'alerts_tags.alert_id', 'alerts_entities.alert_id')
.leftJoin('alerts_actors', 'alerts_actors.alert_id', 'alerts_entities.alert_id')
.leftJoin('alerts_matches', 'alerts_matches.alert_id', 'alerts_entities.alert_id')
.leftJoin('alerts_entities as related_entities', (joinBuilder) => {
joinBuilder
.on('related_entities.alert_id', 'alerts_entities.alert_id')
.on('related_entities.entity_id', '!=', 'alerts_entities.entity_id');
})
.groupBy(['user_id', 'alerts_entities.entity_id', 'is_only']),
);
});
await knex.raw(`
GRANT SELECT ON alerts_users_actors TO :visitor;
GRANT SELECT ON alerts_users_entities TO :visitor;
GRANT SELECT ON alerts_users_tags TO :visitor;
`, {
visitor: knex.raw(config.database.query.user),
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('alerts', (table) => {
table.dropColumn('from_preset');
table.dropColumn('comment');
});
await knex.schema.dropMaterializedView('alerts_users_actors');
await knex.schema.dropMaterializedView('alerts_users_tags');
await knex.schema.dropMaterializedView('alerts_users_entities');
};

View File

@@ -1,21 +0,0 @@
exports.up = async function(knex) {
await knex.schema.alterTable('alerts', (table) => {
table.json('meta');
});
await knex.schema.alterTable('stashes', (table) => {
table.text('comment');
table.json('meta');
});
};
exports.down = async function(knex) {
await knex.schema.alterTable('alerts', (table) => {
table.dropColumn('meta');
});
await knex.schema.alterTable('stashes', (table) => {
table.dropColumn('comment');
table.dropColumn('meta');
});
};

View File

@@ -1,22 +0,0 @@
exports.up = async (knex) => {
await knex('affiliates')
.update('parameters', null);
await knex.schema.alterTable('affiliates', (table) => {
table.dropUnique(['entity_id', 'parameters']);
});
await knex.schema.alterTable('affiliates', (table) => {
table.json('parameters')
.alter();
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('affiliates', (table) => {
table.string('parameters')
.alter();
table.unique(['entity_id', 'parameters']);
});
};

View File

@@ -1,14 +0,0 @@
exports.up = async (knex) => {
await knex.raw(`
/* allow scenes without dates to be mixed inbetween scenes with dates */
ALTER TABLE series
ADD COLUMN effective_date timestamptz
GENERATED ALWAYS AS (COALESCE(date, created_at)) STORED;
`);
};
exports.down = async (knex) => {
await knex.schema.alterTable('series', (table) => {
table.dropColumn('effective_date');
});
};

View File

@@ -1,71 +0,0 @@
const config = require('config');
exports.up = async (knex) => {
await knex.schema.createTable('fingerprints_types', (table) => {
table.string('type')
.primary();
});
await knex('fingerprints_types').insert([
'oshash',
'phash',
'md5',
'blake2',
].map((type) => ({ type })));
await knex.schema.createTable('releases_fingerprints', (table) => {
table.increments('id');
table.integer('scene_id')
.notNullable()
.references('id')
.inTable('releases');
table.string('hash')
.notNullable()
.index();
table.string('type')
.notNullable()
.references('type')
.inTable('fingerprints_types');
table.integer('duration');
table.integer('width');
table.integer('height');
table.integer('user_id')
.references('id')
.inTable('users');
table.string('source');
table.integer('source_submissions');
table.json('source_meta');
table.integer('batch_id')
.notNullable()
.references('id')
.inTable('batches');
table.datetime('source_created_at');
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
});
await knex.raw(`
create unique index scenes_fingerprints_unique
on releases_fingerprints (scene_id, hash, source, user_id)
nulls not distinct
`);
await knex.raw('GRANT ALL ON ALL TABLES IN SCHEMA public TO :visitor;', {
visitor: knex.raw(config.database.query.user),
});
};
exports.down = async function(knex) {
await knex.schema.dropTable('releases_fingerprints');
await knex.schema.dropTable('fingerprints_types');
};

View File

@@ -1,11 +0,0 @@
exports.up = async (knex) => {
await knex.schema.alterTable('banners', (table) => {
table.text('html');
});
};
exports.down = async (knex) => {
await knex.schema.alterTable('banners', (table) => {
table.dropColumn('html');
});
};