Skip to content

Commit 55da1e3

Browse files
authored
Merge pull request #2878 from matthewmcgarvey/migrations
Add custom migration implementation
2 parents f3aa0d6 + bf054df commit 55da1e3

13 files changed

+382
-0
lines changed

src/invidious.cr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ require "compress/zip"
2727
require "protodec/utils"
2828

2929
require "./invidious/database/*"
30+
require "./invidious/database/migrations/*"
3031
require "./invidious/helpers/*"
3132
require "./invidious/yt_backend/*"
3233
require "./invidious/frontend/*"
@@ -102,6 +103,10 @@ Kemal.config.extra_options do |parser|
102103
puts SOFTWARE.to_pretty_json
103104
exit
104105
end
106+
parser.on("--migrate", "Run any migrations") do
107+
Invidious::Database::Migrator.new(PG_DB).migrate
108+
exit
109+
end
105110
end
106111

107112
Kemal::CLI.new ARGV
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
abstract class Invidious::Database::Migration
2+
macro inherited
3+
Migrator.migrations << self
4+
end
5+
6+
@@version : Int64?
7+
8+
def self.version(version : Int32 | Int64)
9+
@@version = version.to_i64
10+
end
11+
12+
getter? completed = false
13+
14+
def initialize(@db : DB::Database)
15+
end
16+
17+
abstract def up(conn : DB::Connection)
18+
19+
def migrate
20+
# migrator already ignores completed migrations
21+
# but this is an extra check to make sure a migration doesn't run twice
22+
return if completed?
23+
24+
@db.transaction do |txn|
25+
up(txn.connection)
26+
track(txn.connection)
27+
@completed = true
28+
end
29+
end
30+
31+
def version : Int64
32+
@@version.not_nil!
33+
end
34+
35+
private def track(conn : DB::Connection)
36+
conn.exec("INSERT INTO #{Migrator::MIGRATIONS_TABLE} (version) VALUES ($1)", version)
37+
end
38+
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module Invidious::Database::Migrations
2+
class CreateChannelsTable < Migration
3+
version 1
4+
5+
def up(conn : DB::Connection)
6+
conn.exec <<-SQL
7+
CREATE TABLE IF NOT EXISTS public.channels
8+
(
9+
id text NOT NULL,
10+
author text,
11+
updated timestamp with time zone,
12+
deleted boolean,
13+
subscribed timestamp with time zone,
14+
CONSTRAINT channels_id_key UNIQUE (id)
15+
);
16+
SQL
17+
18+
conn.exec <<-SQL
19+
GRANT ALL ON TABLE public.channels TO current_user;
20+
SQL
21+
22+
conn.exec <<-SQL
23+
CREATE INDEX IF NOT EXISTS channels_id_idx
24+
ON public.channels
25+
USING btree
26+
(id COLLATE pg_catalog."default");
27+
SQL
28+
end
29+
end
30+
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module Invidious::Database::Migrations
2+
class CreateVideosTable < Migration
3+
version 2
4+
5+
def up(conn : DB::Connection)
6+
conn.exec <<-SQL
7+
CREATE UNLOGGED TABLE IF NOT EXISTS public.videos
8+
(
9+
id text NOT NULL,
10+
info text,
11+
updated timestamp with time zone,
12+
CONSTRAINT videos_pkey PRIMARY KEY (id)
13+
);
14+
SQL
15+
16+
conn.exec <<-SQL
17+
GRANT ALL ON TABLE public.videos TO current_user;
18+
SQL
19+
20+
conn.exec <<-SQL
21+
CREATE UNIQUE INDEX IF NOT EXISTS id_idx
22+
ON public.videos
23+
USING btree
24+
(id COLLATE pg_catalog."default");
25+
SQL
26+
end
27+
end
28+
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
module Invidious::Database::Migrations
2+
class CreateChannelVideosTable < Migration
3+
version 3
4+
5+
def up(conn : DB::Connection)
6+
conn.exec <<-SQL
7+
CREATE TABLE IF NOT EXISTS public.channel_videos
8+
(
9+
id text NOT NULL,
10+
title text,
11+
published timestamp with time zone,
12+
updated timestamp with time zone,
13+
ucid text,
14+
author text,
15+
length_seconds integer,
16+
live_now boolean,
17+
premiere_timestamp timestamp with time zone,
18+
views bigint,
19+
CONSTRAINT channel_videos_id_key UNIQUE (id)
20+
);
21+
SQL
22+
23+
conn.exec <<-SQL
24+
GRANT ALL ON TABLE public.channel_videos TO current_user;
25+
SQL
26+
27+
conn.exec <<-SQL
28+
CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx
29+
ON public.channel_videos
30+
USING btree
31+
(ucid COLLATE pg_catalog."default");
32+
SQL
33+
end
34+
end
35+
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module Invidious::Database::Migrations
2+
class CreateUsersTable < Migration
3+
version 4
4+
5+
def up(conn : DB::Connection)
6+
conn.exec <<-SQL
7+
CREATE TABLE IF NOT EXISTS public.users
8+
(
9+
updated timestamp with time zone,
10+
notifications text[],
11+
subscriptions text[],
12+
email text NOT NULL,
13+
preferences text,
14+
password text,
15+
token text,
16+
watched text[],
17+
feed_needs_update boolean,
18+
CONSTRAINT users_email_key UNIQUE (email)
19+
);
20+
SQL
21+
22+
conn.exec <<-SQL
23+
GRANT ALL ON TABLE public.users TO current_user;
24+
SQL
25+
26+
conn.exec <<-SQL
27+
CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx
28+
ON public.users
29+
USING btree
30+
(lower(email) COLLATE pg_catalog."default");
31+
SQL
32+
end
33+
end
34+
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module Invidious::Database::Migrations
2+
class CreateSessionIdsTable < Migration
3+
version 5
4+
5+
def up(conn : DB::Connection)
6+
conn.exec <<-SQL
7+
CREATE TABLE IF NOT EXISTS public.session_ids
8+
(
9+
id text NOT NULL,
10+
email text,
11+
issued timestamp with time zone,
12+
CONSTRAINT session_ids_pkey PRIMARY KEY (id)
13+
);
14+
SQL
15+
16+
conn.exec <<-SQL
17+
GRANT ALL ON TABLE public.session_ids TO current_user;
18+
SQL
19+
20+
conn.exec <<-SQL
21+
CREATE INDEX IF NOT EXISTS session_ids_id_idx
22+
ON public.session_ids
23+
USING btree
24+
(id COLLATE pg_catalog."default");
25+
SQL
26+
end
27+
end
28+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module Invidious::Database::Migrations
2+
class CreateNoncesTable < Migration
3+
version 6
4+
5+
def up(conn : DB::Connection)
6+
conn.exec <<-SQL
7+
CREATE TABLE IF NOT EXISTS public.nonces
8+
(
9+
nonce text,
10+
expire timestamp with time zone,
11+
CONSTRAINT nonces_id_key UNIQUE (nonce)
12+
);
13+
SQL
14+
15+
conn.exec <<-SQL
16+
GRANT ALL ON TABLE public.nonces TO current_user;
17+
SQL
18+
19+
conn.exec <<-SQL
20+
CREATE INDEX IF NOT EXISTS nonces_nonce_idx
21+
ON public.nonces
22+
USING btree
23+
(nonce COLLATE pg_catalog."default");
24+
SQL
25+
end
26+
end
27+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module Invidious::Database::Migrations
2+
class CreateAnnotationsTable < Migration
3+
version 7
4+
5+
def up(conn : DB::Connection)
6+
conn.exec <<-SQL
7+
CREATE TABLE IF NOT EXISTS public.annotations
8+
(
9+
id text NOT NULL,
10+
annotations xml,
11+
CONSTRAINT annotations_id_key UNIQUE (id)
12+
);
13+
SQL
14+
15+
conn.exec <<-SQL
16+
GRANT ALL ON TABLE public.annotations TO current_user;
17+
SQL
18+
end
19+
end
20+
end
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
module Invidious::Database::Migrations
2+
class CreatePlaylistsTable < Migration
3+
version 8
4+
5+
def up(conn : DB::Connection)
6+
if !privacy_type_exists?(conn)
7+
conn.exec <<-SQL
8+
CREATE TYPE public.privacy AS ENUM
9+
(
10+
'Public',
11+
'Unlisted',
12+
'Private'
13+
);
14+
SQL
15+
end
16+
17+
conn.exec <<-SQL
18+
CREATE TABLE IF NOT EXISTS public.playlists
19+
(
20+
title text,
21+
id text primary key,
22+
author text,
23+
description text,
24+
video_count integer,
25+
created timestamptz,
26+
updated timestamptz,
27+
privacy privacy,
28+
index int8[]
29+
);
30+
SQL
31+
32+
conn.exec <<-SQL
33+
GRANT ALL ON public.playlists TO current_user;
34+
SQL
35+
end
36+
37+
private def privacy_type_exists?(conn : DB::Connection) : Bool
38+
request = <<-SQL
39+
SELECT 1 AS one
40+
FROM pg_type
41+
INNER JOIN pg_namespace ON pg_namespace.oid = pg_type.typnamespace
42+
WHERE pg_namespace.nspname = 'public'
43+
AND pg_type.typname = 'privacy'
44+
LIMIT 1;
45+
SQL
46+
47+
!conn.query_one?(request, as: Int32).nil?
48+
end
49+
end
50+
end

0 commit comments

Comments
 (0)