Merged database migrations.

This commit is contained in:
DebaucheryLibrarian
2023-06-19 00:39:00 +02:00
parent 85942c5d00
commit dbaddfb291
21 changed files with 385 additions and 576 deletions

View File

@@ -64,6 +64,7 @@ exports.up = (knex) => Promise.resolve()
table.boolean('independent')
.defaultTo(false);
table.boolean('showcased');
table.boolean('visible')
.defaultTo(true);
@@ -651,6 +652,8 @@ exports.up = (knex) => Promise.resolve()
table.integer('duration')
.unsigned();
table.specificType('qualities', 'text[]');
table.boolean('deep');
table.text('deep_url', 1000);
@@ -804,6 +807,8 @@ exports.up = (knex) => Promise.resolve()
table.text('original_tag');
table.unique(['tag_id', 'release_id']);
table.index('tag_id');
table.index('release_id');
}))
.then(() => knex.schema.createTable('releases_search', (table) => {
table.integer('release_id', 16)
@@ -873,6 +878,8 @@ exports.up = (knex) => Promise.resolve()
table.datetime('created_at')
.defaultTo(knex.fn.now());
table.index('scene_id');
}))
.then(() => knex.schema.createTable('movies_covers', (table) => {
table.integer('movie_id', 16)
@@ -916,12 +923,151 @@ exports.up = (knex) => Promise.resolve()
table.unique('movie_id');
}))
.then(() => knex.schema.createTable('movies_photos', (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', 'media_id']);
}))
.then(() => knex.schema.createTable('movies_search', (table) => {
table.integer('movie_id', 16)
.references('id')
.inTable('movies')
.onDelete('cascade');
}))
.then(() => knex.schema.createTable('series', (table) => {
table.increments('id', 16);
table.integer('entity_id', 12)
.references('id')
.inTable('entities')
.notNullable();
table.integer('studio_id', 12)
.references('id')
.inTable('entities');
table.text('entry_id');
table.unique(['entity_id', 'entry_id']);
table.text('url', 1000);
table.text('title');
table.text('slug');
table.timestamp('date');
table.index('date');
table.enum('date_precision', ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'])
.defaultTo('day');
table.text('description');
table.boolean('deep');
table.text('deep_url', 1000);
table.text('comment');
table.integer('created_batch_id', 12)
.references('id')
.inTable('batches')
.onDelete('cascade');
table.integer('updated_batch_id', 12)
.references('id')
.inTable('batches')
.onDelete('cascade');
table.datetime('created_at')
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('series_scenes', (table) => {
table.integer('serie_id', 16)
.notNullable()
.references('id')
.inTable('series')
.onDelete('cascade');
table.integer('scene_id', 16)
.notNullable()
.references('id')
.inTable('releases')
.onDelete('cascade');
table.unique(['serie_id', 'scene_id']);
table.datetime('created_at')
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('series_trailers', (table) => {
table.integer('serie_id', 16)
.unique()
.notNullable()
.references('id')
.inTable('series')
.onDelete('cascade');
table.text('media_id', 21)
.notNullable()
.references('id')
.inTable('media');
}))
.then(() => knex.schema.createTable('series_posters', (table) => {
table.integer('serie_id', 16)
.notNullable()
.references('id')
.inTable('series')
.onDelete('cascade');
table.text('media_id', 21)
.notNullable()
.references('id')
.inTable('media')
.onDelete('cascade');
table.unique('serie_id');
}))
.then(() => knex.schema.createTable('series_covers', (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', 'media_id']);
}))
.then(() => knex.schema.createTable('series_photos', (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', 'media_id']);
}))
.then(() => knex.schema.createTable('series_search', (table) => {
table.integer('serie_id', 16)
.references('id')
.inTable('series')
.onDelete('cascade');
}))
.then(() => knex.schema.createTable('chapters', (table) => {
table.increments('id', 16);
@@ -1062,6 +1208,8 @@ exports.up = (knex) => Promise.resolve()
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
table.datetime('last_login');
}))
.then(() => knex.schema.createTable('stashes', (table) => {
table.increments('id');
@@ -1088,7 +1236,12 @@ exports.up = (knex) => Promise.resolve()
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
table.unique(['user_id', 'slug']);
}))
.then(() => knex.raw(`
CREATE UNIQUE INDEX unique_primary ON stashes (user_id, "primary") WHERE ("primary" = TRUE);
`))
.then(() => knex.schema.createTable('stashes_scenes', (table) => {
table.integer('stash_id')
.notNullable()
@@ -1152,6 +1305,27 @@ exports.up = (knex) => Promise.resolve()
.notNullable()
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('stashes_series', (table) => {
table.integer('stash_id')
.notNullable()
.references('id')
.inTable('stashes')
.onDelete('cascade');
table.integer('serie_id')
.notNullable()
.references('id')
.inTable('series')
.onDelete('cascade');
table.unique(['stash_id', 'serie_id']);
table.string('comment');
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('alerts', (table) => {
table.increments('id');
@@ -1329,6 +1503,9 @@ exports.up = (knex) => Promise.resolve()
.notNullable()
.defaultTo(knex.fn.now());
}))
.then(() => knex.raw(`
ALTER TABLE banners ADD COLUMN ratio numeric GENERATED ALWAYS AS (ROUND(width::decimal/ height::decimal, 2)) STORED;
`))
.then(() => knex.schema.createTable('banners_tags', (table) => {
table.increments('id');
@@ -1372,12 +1549,32 @@ exports.up = (knex) => Promise.resolve()
.notNullable()
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('random_campaign', (table) => {
table.text('banner_id')
.references('id')
.inTable('banners');
table.text('url');
table.integer('entity_id')
.references('id')
.inTable('entities');
table.string('affiliate_id')
.references('id')
.inTable('affiliates');
table.integer('parent_id')
.references('id')
.inTable('entities');
}))
// SEARCH AND SORT
.then(() => { // eslint-disable-line arrow-body-style
// allow vim fold
return 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
@@ -1401,6 +1598,8 @@ exports.up = (knex) => Promise.resolve()
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
@@ -1421,15 +1620,13 @@ exports.up = (knex) => Promise.resolve()
CREATE TABLE movies_search_results (movie_id integer, rank real, FOREIGN KEY (movie_id) REFERENCES movies (id));
CREATE FUNCTION search_releases(query text) RETURNS SETOF releases_search_results AS $$
SELECT releases.id, ranks.rank FROM (
SELECT
releases_search.release_id,
ts_rank(releases_search.document, to_tsquery('english', array_to_string(array(SELECT * FROM regexp_matches(query, '[A-Za-zÀ-ÖØ-öø-ÿ0-9]+', 'g')), '|'))) AS rank
SELECT results.release_id, ts_rank(results.document::tsvector, curate_search_query(query)) as rank
FROM (
SELECT releases_search.release_id, document
FROM releases_search
) ranks
LEFT JOIN releases ON releases.id = ranks.release_id
WHERE ranks.rank > 0
ORDER BY ranks.rank DESC;
WHERE document::tsvector @@ curate_search_query(query)
) AS results
ORDER BY rank DESC;
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION search_movies(query text) RETURNS SETOF movies_search_results AS $$
@@ -1513,6 +1710,52 @@ exports.up = (knex) => Promise.resolve()
ORDER BY actors.name;
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION entities_scenes(entity entities) RETURNS SETOF releases AS $$
WITH RECURSIVE children AS (
SELECT entities.id
FROM entities
WHERE entities.id = entity.id
UNION ALL
SELECT entities.id
FROM entities
INNER JOIN children ON children.id = entities.parent_id
)
SELECT releases FROM releases
INNER JOIN children ON children.id = releases.entity_id
UNION
SELECT releases FROM releases
INNER JOIN children ON children.id = releases.studio_id;
$$ LANGUAGE SQL STABLE;
CREATE OR REPLACE FUNCTION entities_scene_total(entity entities) RETURNS bigint AS $$
SELECT COUNT(id)
FROM releases
WHERE releases.entity_id = entity.id;
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION entities_scene_tags(entity entities, selectable_tags text[]) RETURNS SETOF tags AS $$
SELECT tags.*
FROM releases
LEFT JOIN
releases_tags ON releases_tags.release_id = releases.id
LEFT JOIN
tags ON tags.id = releases_tags.tag_id
WHERE
releases.entity_id = entity.id
AND
CASE WHEN array_length(selectable_tags, 1) IS NOT NULL
THEN tags.slug = ANY(selectable_tags)
ELSE true
END
GROUP BY tags.id
ORDER BY tags.name;
$$ LANGUAGE SQL STABLE;
/* GraphQL/Postgraphile 'every' applies to the data, will only include scenes for which every assigned tag is selected,
instead of what we want; scenes with every selected tag, but possibly also some others */
CREATE FUNCTION actors_scenes(actor actors, selected_tags text[], mode text DEFAULT 'all') RETURNS SETOF releases AS $$
@@ -1589,7 +1832,7 @@ exports.up = (knex) => Promise.resolve()
ORDER BY tags.priority DESC
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION movies_photos(movie movies) RETURNS SETOF media AS $$
CREATE FUNCTION movies_scenes_photos(movie movies) RETURNS SETOF media AS $$
SELECT media.*
FROM movies_scenes
LEFT JOIN
@@ -1607,15 +1850,111 @@ exports.up = (knex) => Promise.resolve()
SELECT EXISTS(SELECT true WHERE (SELECT id FROM batches ORDER BY created_at DESC LIMIT 1) = release.created_batch_id);
$$ LANGUAGE sql STABLE;
CREATE FUNCTION banners_ratio(banner banners) RETURNS numeric AS $$
SELECT ROUND(banner.width::decimal / banner.height::decimal, 2);
CREATE FUNCTION series_actors(serie series) RETURNS SETOF actors AS $$
SELECT actors.*
FROM series_scenes
LEFT JOIN
releases ON releases.id = series_scenes.scene_id
LEFT JOIN
releases_actors ON releases_actors.release_id = releases.id
LEFT JOIN
actors ON actors.id = releases_actors.actor_id
WHERE series_scenes.serie_id = serie.id
AND actors.id IS NOT NULL
GROUP BY actors.id
ORDER BY actors.name, actors.gender
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION get_random_campaign() RETURNS SETOF campaigns AS $$
SELECT * FROM campaigns
ORDER BY random()
CREATE FUNCTION series_tags(serie series) RETURNS SETOF tags AS $$
SELECT tags.*
FROM series_scenes
LEFT JOIN
releases ON releases.id = series_scenes.scene_id
LEFT JOIN
releases_tags ON releases_tags.release_id = releases.id
LEFT JOIN
tags ON tags.id = releases_tags.tag_id
WHERE series_scenes.serie_id = serie.id
AND tags.id IS NOT NULL
GROUP BY tags.id
ORDER BY tags.priority DESC
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION series_scenes_photos(serie series) RETURNS SETOF media AS $$
SELECT media.*
FROM series_scenes
LEFT JOIN
releases ON releases.id = series_scenes.scene_id
INNER JOIN
releases_photos ON releases_photos.release_id = releases.id
LEFT JOIN
media ON media.id = releases_photos.media_id
WHERE series_scenes.serie_id = serie.id
GROUP BY media.id
ORDER BY media.index ASC
$$ LANGUAGE SQL STABLE;
CREATE 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;
$$ LANGUAGE SQL STABLE;
`);
})
// VIEWS AND COMMENTS
.then(() => { // eslint-disable-line arrow-body-style
// allow vim fold
return knex.raw(`
CREATE MATERIALIZED VIEW releases_not_showcased AS (
SELECT releases.id AS release_id FROM releases
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
WHERE (studios.showcased = false)
OR (channels.showcased = false AND studios.showcased IS NOT true)
OR (networks.showcased = false AND channels.showcased IS NOT true AND studios.showcased IS NOT true)
);
CREATE UNIQUE INDEX ON releases_not_showcased (release_id);
COMMENT ON MATERIALIZED VIEW releases_not_showcased IS E'@foreignKey (release_id) references releases (id)';
COMMENT ON COLUMN users.password IS E'@omit';
COMMENT ON COLUMN users.email IS E'@omit';
COMMENT ON COLUMN users.email_verified IS E'@omit';
COMMENT ON COLUMN users.abilities IS E'@omit';
COMMENT ON COLUMN actors.height IS E'@omit read,update,create,delete,all,many';
COMMENT ON COLUMN actors.weight IS E'@omit read,update,create,delete,all,many';
COMMENT ON COLUMN actors.penis_length IS E'@omit read,update,create,delete,all,many';
COMMENT ON COLUMN actors.penis_girth IS E'@omit read,update,create,delete,all,many';
COMMENT ON FUNCTION entities_scenes IS E'@sortable';
COMMENT ON FUNCTION actors_tags IS E'@sortable';
COMMENT ON FUNCTION actors_channels IS E'@sortable';
COMMENT ON FUNCTION actors_actors IS E'@sortable';
COMMENT ON FUNCTION actors_scenes IS E'@sortable';
COMMENT ON FUNCTION tags_scenes IS E'@sortable';
COMMENT ON FUNCTION search_releases IS E'@sortable';
COMMENT ON FUNCTION search_entities IS E'@sortable';
COMMENT ON FUNCTION search_actors IS E'@sortable';
COMMENT ON FUNCTION search_movies IS E'@sortable';
COMMENT ON FUNCTION search_tags IS E'@sortable';
`);
})
// POLICIES
@@ -1632,6 +1971,7 @@ exports.up = (knex) => Promise.resolve()
ALTER TABLE stashes_scenes ENABLE ROW LEVEL SECURITY;
ALTER TABLE stashes_movies ENABLE ROW LEVEL SECURITY;
ALTER TABLE stashes_actors ENABLE ROW LEVEL SECURITY;
ALTER TABLE stashes_series ENABLE ROW LEVEL SECURITY;
CREATE POLICY stashes_policy_select ON stashes FOR SELECT USING (stashes.public OR stashes.user_id = current_user_id());
CREATE POLICY stashes_policy_update ON stashes FOR UPDATE USING (stashes.public OR stashes.user_id = current_user_id());
@@ -1662,6 +2002,14 @@ exports.up = (knex) => Promise.resolve()
AND (stashes.user_id = current_user_id() OR stashes.public)
));
CREATE POLICY stashes_policy ON stashes_series
USING (EXISTS (
SELECT *
FROM stashes
WHERE stashes.id = stashes_series.stash_id
AND (stashes.user_id = current_user_id() OR stashes.public)
));
ALTER TABLE alerts ENABLE ROW LEVEL SECURITY;
ALTER TABLE alerts_tags ENABLE ROW LEVEL SECURITY;
ALTER TABLE alerts_scenes ENABLE ROW LEVEL SECURITY;
@@ -1729,33 +2077,6 @@ exports.up = (knex) => Promise.resolve()
`, {
visitor: knex.raw(config.database.query.user),
});
})
// VIEWS AND COMMENTS
.then(() => { // eslint-disable-line arrow-body-style
// allow vim fold
return knex.raw(`
COMMENT ON COLUMN users.password IS E'@omit';
COMMENT ON COLUMN users.email IS E'@omit';
COMMENT ON COLUMN users.email_verified IS E'@omit';
COMMENT ON COLUMN users.abilities IS E'@omit';
COMMENT ON COLUMN actors.height IS E'@omit read,update,create,delete,all,many';
COMMENT ON COLUMN actors.weight IS E'@omit read,update,create,delete,all,many';
COMMENT ON COLUMN actors.penis_length IS E'@omit read,update,create,delete,all,many';
COMMENT ON COLUMN actors.penis_girth IS E'@omit read,update,create,delete,all,many';
COMMENT ON FUNCTION actors_tags IS E'@sortable';
COMMENT ON FUNCTION actors_channels IS E'@sortable';
COMMENT ON FUNCTION actors_actors IS E'@sortable';
COMMENT ON FUNCTION actors_scenes IS E'@sortable';
COMMENT ON FUNCTION tags_scenes IS E'@sortable';
COMMENT ON FUNCTION search_releases IS E'@sortable';
COMMENT ON FUNCTION search_entities IS E'@sortable';
COMMENT ON FUNCTION search_actors IS E'@sortable';
COMMENT ON FUNCTION search_movies IS E'@sortable';
COMMENT ON FUNCTION search_tags IS E'@sortable';
`);
});
exports.down = (knex) => { // eslint-disable-line arrow-body-style
@@ -1776,8 +2097,17 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
DROP TABLE IF EXISTS movies_scenes CASCADE;
DROP TABLE IF EXISTS movies_covers CASCADE;
DROP TABLE IF EXISTS movies_posters CASCADE;
DROP TABLE IF EXISTS movies_photos CASCADE;
DROP TABLE IF EXISTS movies_trailers CASCADE;
DROP TABLE IF EXISTS stashes_series CASCADE;
DROP TABLE IF EXISTS series_scenes CASCADE;
DROP TABLE IF EXISTS series_trailers CASCADE;
DROP TABLE IF EXISTS series_posters CASCADE;
DROP TABLE IF EXISTS series_covers CASCADE;
DROP TABLE IF EXISTS series_photos CASCADE;
DROP TABLE IF EXISTS series_search CASCADE;
DROP TABLE IF EXISTS clips_tags CASCADE;
DROP TABLE IF EXISTS clips_posters CASCADE;
DROP TABLE IF EXISTS clips_photos CASCADE;
@@ -1789,6 +2119,7 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
DROP TABLE IF EXISTS banners_tags CASCADE;
DROP TABLE IF EXISTS banners CASCADE;
DROP TABLE IF EXISTS campaigns CASCADE;
DROP TABLE IF EXISTS random_campaign CASCADE;
DROP TABLE IF EXISTS affiliates CASCADE;
DROP TABLE IF EXISTS batches CASCADE;
@@ -1812,6 +2143,7 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
DROP TABLE IF EXISTS movies CASCADE;
DROP TABLE IF EXISTS clips CASCADE;
DROP TABLE IF EXISTS chapters CASCADE;
DROP TABLE IF EXISTS series CASCADE;
DROP TABLE IF EXISTS releases CASCADE;
DROP TABLE IF EXISTS actors CASCADE;
DROP TABLE IF EXISTS tags CASCADE;
@@ -1853,6 +2185,9 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
DROP FUNCTION IF EXISTS get_random_sfw_media_id;
DROP FUNCTION IF EXISTS releases_is_new;
DROP FUNCTION IF EXISTS entities_scenes;
DROP FUNCTION IF EXISTS entities_scene_total;
DROP FUNCTION IF EXISTS entities_scene_tags;
DROP FUNCTION IF EXISTS actors_tags;
DROP FUNCTION IF EXISTS actors_channels;
DROP FUNCTION IF EXISTS actors_actors;
@@ -1860,7 +2195,11 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
DROP FUNCTION IF EXISTS movies_actors;
DROP FUNCTION IF EXISTS movies_tags;
DROP FUNCTION IF EXISTS movies_photos;
DROP FUNCTION IF EXISTS movies_scenes_photos;
DROP FUNCTION IF EXISTS series_actors;
DROP FUNCTION IF EXISTS series_tags;
DROP FUNCTION IF EXISTS series_scenes_photos;
DROP POLICY IF EXISTS stashes_policy ON stashes;
DROP POLICY IF EXISTS stashes_policy ON stashes_scenes;