From 998edba6f064eb4e09ca286ad33bfd967ef03e66 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Fri, 26 Nov 2021 19:36:31 +0100
Subject: [PATCH 01/16] Move DB queries related to 'videos' in a separate
module
---
src/invidious.cr | 3 ++-
src/invidious/database/base.cr | 4 +++
src/invidious/database/videos.cr | 43 ++++++++++++++++++++++++++++++++
src/invidious/videos.cr | 10 +++-----
4 files changed, 53 insertions(+), 7 deletions(-)
create mode 100644 src/invidious/database/base.cr
create mode 100644 src/invidious/database/videos.cr
diff --git a/src/invidious.cr b/src/invidious.cr
index ade13608..405fcadf 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -20,12 +20,13 @@ require "kemal"
require "athena-negotiation"
require "openssl/hmac"
require "option_parser"
-require "pg"
require "sqlite3"
require "xml"
require "yaml"
require "compress/zip"
require "protodec/utils"
+
+require "./invidious/database/*"
require "./invidious/helpers/*"
require "./invidious/yt_backend/*"
require "./invidious/*"
diff --git a/src/invidious/database/base.cr b/src/invidious/database/base.cr
new file mode 100644
index 00000000..055a6284
--- /dev/null
+++ b/src/invidious/database/base.cr
@@ -0,0 +1,4 @@
+require "pg"
+
+module Invidious::Database
+end
diff --git a/src/invidious/database/videos.cr b/src/invidious/database/videos.cr
new file mode 100644
index 00000000..e1fa01c3
--- /dev/null
+++ b/src/invidious/database/videos.cr
@@ -0,0 +1,43 @@
+require "./base.cr"
+
+module Invidious::Database::Videos
+ extend self
+
+ def insert(video : Video)
+ request = <<-SQL
+ INSERT INTO videos
+ VALUES ($1, $2, $3)
+ ON CONFLICT (id) DO NOTHING
+ SQL
+
+ PG_DB.exec(request, video.id, video.info.to_json, video.updated)
+ end
+
+ def delete(id)
+ request = <<-SQL
+ DELETE FROM videos *
+ WHERE id = $1
+ SQL
+
+ PG_DB.exec(request, id)
+ end
+
+ def update(video : Video)
+ request = <<-SQL
+ UPDATE videos
+ SET (id, info, updated) = ($1, $2, $3)
+ WHERE id = $1
+ SQL
+
+ PG_DB.exec(request, video.id, video.info.to_json, video.updated)
+ end
+
+ def select(id : String) : Video?
+ request = <<-SQL
+ SELECT * FROM videos
+ WHERE id = $1
+ SQL
+
+ return PG_DB.query_one?(request, id, as: Video)
+ end
+end
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
index d4ef0900..645d3678 100644
--- a/src/invidious/videos.cr
+++ b/src/invidious/videos.cr
@@ -994,7 +994,7 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_
end
def get_video(id, db, refresh = true, region = nil, force_refresh = false)
- if (video = db.query_one?("SELECT * FROM videos WHERE id = $1", id, as: Video)) && !region
+ if (video = Invidious::Database::Videos.select(id)) && !region
# If record was last updated over 10 minutes ago, or video has since premiered,
# refresh (expire param in response lasts for 6 hours)
if (refresh &&
@@ -1003,17 +1003,15 @@ def get_video(id, db, refresh = true, region = nil, force_refresh = false)
force_refresh
begin
video = fetch_video(id, region)
- db.exec("UPDATE videos SET (id, info, updated) = ($1, $2, $3) WHERE id = $1", video.id, video.info.to_json, video.updated)
+ Invidious::Database::Videos.update(video)
rescue ex
- db.exec("DELETE FROM videos * WHERE id = $1", id)
+ Invidious::Database::Videos.delete(id)
raise ex
end
end
else
video = fetch_video(id, region)
- if !region
- db.exec("INSERT INTO videos VALUES ($1, $2, $3) ON CONFLICT (id) DO NOTHING", video.id, video.info.to_json, video.updated)
- end
+ Invidious::Database::Videos.insert(video) if !region
end
return video
From 3deafe9f8da2805ab19900fbdfb4e90d0a2cea03 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Tue, 30 Nov 2021 02:24:24 +0100
Subject: [PATCH 02/16] Move DB queries related to playlists in a separate
module (1/3)
---
src/invidious.cr | 7 +-
src/invidious/database/playlists.cr | 94 ++++++++++++++++++++
src/invidious/playlists.cr | 10 +--
src/invidious/routes/api/v1/authenticated.cr | 14 ++-
src/invidious/routes/playlists.cr | 14 ++-
5 files changed, 108 insertions(+), 31 deletions(-)
create mode 100644 src/invidious/database/playlists.cr
diff --git a/src/invidious.cr b/src/invidious.cr
index 405fcadf..28d8ddac 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -816,11 +816,8 @@ post "/data_control" do |env|
index: Random::Secure.rand(0_i64..Int64::MAX),
})
- video_array = playlist_video.to_a
- args = arg_array(video_array)
-
- PG_DB.exec("INSERT INTO playlist_videos VALUES (#{args})", args: video_array)
- PG_DB.exec("UPDATE playlists SET index = array_append(index, $1), video_count = cardinality(index) + 1, updated = $2 WHERE id = $3", playlist_video.index, Time.utc, playlist.id)
+ Invidious::Database::PlaylistVideos.insert(playlist_video)
+ Invidious::Database::Playlists.update_video_added(playlist.id, playlist_video.index)
end
end
end
diff --git a/src/invidious/database/playlists.cr b/src/invidious/database/playlists.cr
new file mode 100644
index 00000000..037e25b7
--- /dev/null
+++ b/src/invidious/database/playlists.cr
@@ -0,0 +1,94 @@
+require "./base.cr"
+
+#
+# This module contains functions related to the "playlists" table.
+#
+module Invidious::Database::Playlists
+ extend self
+
+ # -------------------
+ # Insert / delete
+ # -------------------
+
+ def insert(playlist : InvidiousPlaylist)
+ playlist_array = playlist.to_a
+
+ request = <<-SQL
+ INSERT INTO playlists
+ VALUES (#{arg_array(playlist_array)})
+ SQL
+
+ PG_DB.exec(request, args: playlist_array)
+ end
+
+ # this function is a bit special: it will also remove all videos
+ # related to the given playlist ID in the "playlist_videos" table,
+ # in addition to deleting said ID from "playlists".
+ def delete(id : String)
+ request = <<-SQL
+ DELETE FROM playlist_videos * WHERE plid = $1;
+ DELETE FROM playlists * WHERE id = $1
+ SQL
+
+ PG_DB.exec(request, id)
+ end
+
+ # -------------------
+ # Update
+ # -------------------
+
+ def update_video_added(id : String, index : String | Int64)
+ request = <<-SQL
+ UPDATE playlists
+ SET index = array_append(index, $1),
+ video_count = cardinality(index) + 1,
+ updated = $2
+ WHERE id = $3
+ SQL
+
+ PG_DB.exec(request, index, Time.utc, id)
+ end
+
+ def update_video_removed(id : String, index : String | Int64)
+ request = <<-SQL
+ UPDATE playlists
+ SET index = array_remove(index, $1),
+ video_count = cardinality(index) - 1,
+ updated = $2
+ WHERE id = $3
+ SQL
+
+ PG_DB.exec(request, index, Time.utc, id)
+ end
+end
+
+#
+# This module contains functions related to the "playlist_videos" table.
+#
+module Invidious::Database::PlaylistVideos
+ extend self
+
+ # -------------------
+ # Insert / Delete
+ # -------------------
+
+ def insert(video : PlaylistVideo)
+ video_array = video.to_a
+
+ request = <<-SQL
+ INSERT INTO playlist_videos
+ VALUES (#{arg_array(video_array)})
+ SQL
+
+ PG_DB.exec(request, args: video_array)
+ end
+
+ def delete(index)
+ request = <<-SQL
+ DELETE FROM playlist_videos *
+ WHERE index = $1
+ SQL
+
+ PG_DB.exec(request, index)
+ end
+end
diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr
index f37667b5..685fa1c7 100644
--- a/src/invidious/playlists.cr
+++ b/src/invidious/playlists.cr
@@ -261,10 +261,7 @@ def create_playlist(db, title, privacy, user)
index: [] of Int64,
})
- playlist_array = playlist.to_a
- args = arg_array(playlist_array)
-
- db.exec("INSERT INTO playlists VALUES (#{args})", args: playlist_array)
+ Invidious::Database::Playlists.insert(playlist)
return playlist
end
@@ -282,10 +279,7 @@ def subscribe_playlist(db, user, playlist)
index: [] of Int64,
})
- playlist_array = playlist.to_a
- args = arg_array(playlist_array)
-
- db.exec("INSERT INTO playlists VALUES (#{args})", args: playlist_array)
+ Invidious::Database::Playlists.insert(playlist)
return playlist
end
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index aaf728ff..4fe8cd30 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -216,8 +216,7 @@ module Invidious::Routes::API::V1::Authenticated
return error_json(403, "Invalid user")
end
- PG_DB.exec("DELETE FROM playlist_videos * WHERE plid = $1", plid)
- PG_DB.exec("DELETE FROM playlists * WHERE id = $1", plid)
+ Invidious::Database::Playlists.delete(plid)
env.response.status_code = 204
end
@@ -266,11 +265,8 @@ module Invidious::Routes::API::V1::Authenticated
index: Random::Secure.rand(0_i64..Int64::MAX),
})
- video_array = playlist_video.to_a
- args = arg_array(video_array)
-
- PG_DB.exec("INSERT INTO playlist_videos VALUES (#{args})", args: video_array)
- PG_DB.exec("UPDATE playlists SET index = array_append(index, $1), video_count = cardinality(index) + 1, updated = $2 WHERE id = $3", playlist_video.index, Time.utc, plid)
+ Invidious::Database::PlaylistVideos.insert(playlist_video)
+ Invidious::Database::Playlists.update_video_added(plid, playlist_video.index)
env.response.headers["Location"] = "#{HOST_URL}/api/v1/auth/playlists/#{plid}/videos/#{playlist_video.index.to_u64.to_s(16).upcase}"
env.response.status_code = 201
@@ -302,8 +298,8 @@ module Invidious::Routes::API::V1::Authenticated
return error_json(404, "Playlist does not contain index")
end
- PG_DB.exec("DELETE FROM playlist_videos * WHERE index = $1", index)
- PG_DB.exec("UPDATE playlists SET index = array_remove(index, $1), video_count = cardinality(index) - 1, updated = $2 WHERE id = $3", index, Time.utc, plid)
+ Invidious::Database::PlaylistVideos.delete(index)
+ Invidious::Database::Playlists.update_video_removed(plid, index)
env.response.status_code = 204
end
diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr
index 7b7bd03f..d29aef09 100644
--- a/src/invidious/routes/playlists.cr
+++ b/src/invidious/routes/playlists.cr
@@ -122,8 +122,7 @@ module Invidious::Routes::Playlists
return env.redirect referer
end
- PG_DB.exec("DELETE FROM playlist_videos * WHERE plid = $1", plid)
- PG_DB.exec("DELETE FROM playlists * WHERE id = $1", plid)
+ Invidious::Database::Playlists.delete(plid)
env.redirect "/feed/playlists"
end
@@ -363,15 +362,12 @@ module Invidious::Routes::Playlists
index: Random::Secure.rand(0_i64..Int64::MAX),
})
- video_array = playlist_video.to_a
- args = arg_array(video_array)
-
- PG_DB.exec("INSERT INTO playlist_videos VALUES (#{args})", args: video_array)
- PG_DB.exec("UPDATE playlists SET index = array_append(index, $1), video_count = cardinality(index) + 1, updated = $2 WHERE id = $3", playlist_video.index, Time.utc, playlist_id)
+ Invidious::Database::PlaylistVideos.insert(playlist_video)
+ Invidious::Database::Playlists.update_video_added(playlist_id, playlist_video.index)
when "action_remove_video"
index = env.params.query["set_video_id"]
- PG_DB.exec("DELETE FROM playlist_videos * WHERE index = $1", index)
- PG_DB.exec("UPDATE playlists SET index = array_remove(index, $1), video_count = cardinality(index) - 1, updated = $2 WHERE id = $3", index, Time.utc, playlist_id)
+ Invidious::Database::PlaylistVideos.delete(index)
+ Invidious::Database::Playlists.update_video_removed(playlist_id, index)
when "action_move_video_before"
# TODO: Playlist stub
else
From 46d08237c6979912275b416a9294a807e5598bc5 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Tue, 30 Nov 2021 03:11:21 +0100
Subject: [PATCH 03/16] Move DB queries related to playlists in a separate
module (2/3)
---
src/invidious.cr | 2 +-
src/invidious/database/playlists.cr | 80 ++++++++++++++++++++
src/invidious/playlists.cr | 2 +-
src/invidious/routes/api/v1/authenticated.cr | 20 +++--
src/invidious/routes/feeds.cr | 4 +-
src/invidious/routes/playlists.cr | 18 +++--
src/invidious/views/playlist.ecr | 2 +-
7 files changed, 108 insertions(+), 20 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index 28d8ddac..93b3357a 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -790,7 +790,7 @@ post "/data_control" do |env|
next if !privacy
playlist = create_playlist(PG_DB, title, privacy, user)
- PG_DB.exec("UPDATE playlists SET description = $1 WHERE id = $2", description, playlist.id)
+ Invidious::Database::Playlists.update_description(playlist.id, description)
videos = item["videos"]?.try &.as_a?.try &.each_with_index do |video_id, idx|
raise InfoException.new("Playlist cannot have more than 500 videos") if idx > 500
diff --git a/src/invidious/database/playlists.cr b/src/invidious/database/playlists.cr
index 037e25b7..1dba64f3 100644
--- a/src/invidious/database/playlists.cr
+++ b/src/invidious/database/playlists.cr
@@ -37,6 +37,36 @@ module Invidious::Database::Playlists
# Update
# -------------------
+ def update(id : String, title : String, privacy, description, updated)
+ request = <<-SQL
+ UPDATE playlists
+ SET title = $1, privacy = $2, description = $3, updated = $4
+ WHERE id = $5
+ SQL
+
+ PG_DB.exec(request, title, privacy, description, updated, id)
+ end
+
+ def update_description(id : String, description)
+ request = <<-SQL
+ UPDATE playlists
+ SET description = $1
+ WHERE id = $2
+ SQL
+
+ PG_DB.exec(request, description, id)
+ end
+
+ def update_subscription_time(id : String)
+ request = <<-SQL
+ UPDATE playlists
+ SET subscribed = $1
+ WHERE id = $2
+ SQL
+
+ PG_DB.exec(request, Time.utc, id)
+ end
+
def update_video_added(id : String, index : String | Int64)
request = <<-SQL
UPDATE playlists
@@ -60,6 +90,56 @@ module Invidious::Database::Playlists
PG_DB.exec(request, index, Time.utc, id)
end
+
+ # -------------------
+ # Salect
+ # -------------------
+
+ def select(*, id : String, raise_on_fail : Bool = false) : InvidiousPlaylist?
+ request = <<-SQL
+ SELECT * FROM playlists
+ WHERE id = $1
+ SQL
+
+ if raise_on_fail
+ return PG_DB.query_one(request, id, as: InvidiousPlaylist)
+ else
+ return PG_DB.query_one?(request, id, as: InvidiousPlaylist)
+ end
+ end
+
+ def select_all(*, author : String) : Array(InvidiousPlaylist)
+ request = <<-SQL
+ SELECT * FROM playlists
+ WHERE author = $1
+ SQL
+
+ return PG_DB.query_all(request, author, as: InvidiousPlaylist)
+ end
+
+ # -------------------
+ # Misc checks
+ # -------------------
+
+ # Check if given playlist ID exists
+ def exists?(id : String) : Bool
+ request = <<-SQL
+ SELECT id FROM playlists
+ WHERE id = $1
+ SQL
+
+ return PG_DB.query_one?(request, id, as: String).nil?
+ end
+
+ # Count how many playlist a user has created.
+ def count_owned_by(author : String) : Int64
+ request = <<-SQL
+ SELECT count(*) FROM playlists
+ WHERE author = $1
+ SQL
+
+ return PG_DB.query_one?(request, author, as: Int64) || 0_i64
+ end
end
#
diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr
index 685fa1c7..f68dc3b0 100644
--- a/src/invidious/playlists.cr
+++ b/src/invidious/playlists.cr
@@ -323,7 +323,7 @@ end
def get_playlist(db, plid, locale, refresh = true, force_refresh = false)
if plid.starts_with? "IV"
- if playlist = db.query_one?("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ if playlist = Invidious::Database::Playlists.select(id: plid)
return playlist
else
raise InfoException.new("Playlist does not exist.")
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index 4fe8cd30..d74dca5c 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -127,7 +127,7 @@ module Invidious::Routes::API::V1::Authenticated
env.response.content_type = "application/json"
user = env.get("user").as(User)
- playlists = PG_DB.query_all("SELECT * FROM playlists WHERE author = $1", user.email, as: InvidiousPlaylist)
+ playlists = Invidious::Database::Playlists.select_all(author: user.email)
JSON.build do |json|
json.array do
@@ -153,7 +153,7 @@ module Invidious::Routes::API::V1::Authenticated
return error_json(400, "Invalid privacy setting.")
end
- if PG_DB.query_one("SELECT count(*) FROM playlists WHERE author = $1", user.email, as: Int64) >= 100
+ if Invidious::Database::Playlists.count_owned_by(user.email) >= 100
return error_json(400, "User cannot have more than 100 playlists.")
end
@@ -172,9 +172,12 @@ module Invidious::Routes::API::V1::Authenticated
env.response.content_type = "application/json"
user = env.get("user").as(User)
- plid = env.params.url["plid"]
+ plid = env.params.url["plid"]?
+ if !plid || plid.empty?
+ return error_json(400, "A playlist ID is required")
+ end
- playlist = PG_DB.query_one?("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ playlist = Invidious::Database::Playlists.select(id: plid)
if !playlist || playlist.author != user.email && playlist.privacy.private?
return error_json(404, "Playlist does not exist.")
end
@@ -195,7 +198,8 @@ module Invidious::Routes::API::V1::Authenticated
updated = playlist.updated
end
- PG_DB.exec("UPDATE playlists SET title = $1, privacy = $2, description = $3, updated = $4 WHERE id = $5", title, privacy, description, updated, plid)
+ Invidious::Database::Playlists.update(plid, title, privacy, description, updated)
+
env.response.status_code = 204
end
@@ -207,7 +211,7 @@ module Invidious::Routes::API::V1::Authenticated
plid = env.params.url["plid"]
- playlist = PG_DB.query_one?("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ playlist = Invidious::Database::Playlists.select(id: plid)
if !playlist || playlist.author != user.email && playlist.privacy.private?
return error_json(404, "Playlist does not exist.")
end
@@ -229,7 +233,7 @@ module Invidious::Routes::API::V1::Authenticated
plid = env.params.url["plid"]
- playlist = PG_DB.query_one?("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ playlist = Invidious::Database::Playlists.select(id: plid)
if !playlist || playlist.author != user.email && playlist.privacy.private?
return error_json(404, "Playlist does not exist.")
end
@@ -285,7 +289,7 @@ module Invidious::Routes::API::V1::Authenticated
plid = env.params.url["plid"]
index = env.params.url["index"].to_i64(16)
- playlist = PG_DB.query_one?("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ playlist = Invidious::Database::Playlists.select(id: plid)
if !playlist || playlist.author != user.email && playlist.privacy.private?
return error_json(404, "Playlist does not exist.")
end
diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr
index 9650bcf4..6424ab47 100644
--- a/src/invidious/routes/feeds.cr
+++ b/src/invidious/routes/feeds.cr
@@ -264,7 +264,7 @@ module Invidious::Routes::Feeds
path = env.request.path
if plid.starts_with? "IV"
- if playlist = PG_DB.query_one?("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ if playlist = Invidious::Database::Playlists.select(id: plid)
videos = get_playlist_videos(PG_DB, playlist, offset: 0, locale: locale)
return XML.build(indent: " ", encoding: "UTF-8") do |xml|
@@ -364,7 +364,7 @@ module Invidious::Routes::Feeds
if ucid = HTTP::Params.parse(URI.parse(topic).query.not_nil!)["channel_id"]?
PG_DB.exec("UPDATE channels SET subscribed = $1 WHERE id = $2", Time.utc, ucid)
elsif plid = HTTP::Params.parse(URI.parse(topic).query.not_nil!)["playlist_id"]?
- PG_DB.exec("UPDATE playlists SET subscribed = $1 WHERE id = $2", Time.utc, ucid)
+ Invidious::Database::Playlists.update_subscription_time(plid)
else
haltf env, status_code: 400
end
diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr
index d29aef09..b73782d5 100644
--- a/src/invidious/routes/playlists.cr
+++ b/src/invidious/routes/playlists.cr
@@ -46,7 +46,7 @@ module Invidious::Routes::Playlists
return error_template(400, "Invalid privacy setting.")
end
- if PG_DB.query_one("SELECT count(*) FROM playlists WHERE author = $1", user.email, as: Int64) >= 100
+ if Invidious::Database::Playlists.count_owned_by(user.email) >= 100
return error_template(400, "User cannot have more than 100 playlists.")
end
@@ -85,7 +85,11 @@ module Invidious::Routes::Playlists
sid = sid.as(String)
plid = env.params.query["list"]?
- playlist = PG_DB.query_one?("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ if !plid || plid.empty?
+ return error_template(400, "A playlist ID is required")
+ end
+
+ playlist = Invidious::Database::Playlists.select(id: plid)
if !playlist || playlist.author != user.email
return env.redirect referer
end
@@ -117,7 +121,7 @@ module Invidious::Routes::Playlists
return error_template(400, ex)
end
- playlist = PG_DB.query_one?("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ playlist = Invidious::Database::Playlists.select(id: plid)
if !playlist || playlist.author != user.email
return env.redirect referer
end
@@ -148,7 +152,7 @@ module Invidious::Routes::Playlists
page ||= 1
begin
- playlist = PG_DB.query_one("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ playlist = Invidious::Database::Playlists.select(id: plid, raise_on_fail: true)
if !playlist || playlist.author != user.email
return env.redirect referer
end
@@ -189,7 +193,7 @@ module Invidious::Routes::Playlists
return error_template(400, ex)
end
- playlist = PG_DB.query_one?("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ playlist = Invidious::Database::Playlists.select(id: plid)
if !playlist || playlist.author != user.email
return env.redirect referer
end
@@ -206,7 +210,7 @@ module Invidious::Routes::Playlists
updated = playlist.updated
end
- PG_DB.exec("UPDATE playlists SET title = $1, privacy = $2, description = $3, updated = $4 WHERE id = $5", title, privacy, description, updated, plid)
+ Invidious::Database::Playlists.update(plid, title, privacy, description, updated)
env.redirect "/playlist?list=#{plid}"
end
@@ -232,7 +236,7 @@ module Invidious::Routes::Playlists
page ||= 1
begin
- playlist = PG_DB.query_one("SELECT * FROM playlists WHERE id = $1", plid, as: InvidiousPlaylist)
+ playlist = Invidious::Database::Playlists.select(id: plid, raise_on_fail: true)
if !playlist || playlist.author != user.email
return env.redirect referer
end
diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr
index 136981da..7825b1f0 100644
--- a/src/invidious/views/playlist.ecr
+++ b/src/invidious/views/playlist.ecr
@@ -61,7 +61,7 @@
<% else %>
- <% if PG_DB.query_one?("SELECT id FROM playlists WHERE id = $1", playlist.id, as: String).nil? %>
+ <% if Invidious::Database::Playlists.exists?(playlist.id) %>
<% else %>
From d94d4c204548eff69ff7c310782c291c89c83bb2 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Wed, 1 Dec 2021 22:05:21 +0100
Subject: [PATCH 04/16] Move DB queries related to statistics in a separate
module
---
src/invidious/database/statistics.cr | 49 ++++++++++++++++++++
src/invidious/jobs/statistics_refresh_job.cr | 10 ++--
2 files changed, 55 insertions(+), 4 deletions(-)
create mode 100644 src/invidious/database/statistics.cr
diff --git a/src/invidious/database/statistics.cr b/src/invidious/database/statistics.cr
new file mode 100644
index 00000000..1df549e2
--- /dev/null
+++ b/src/invidious/database/statistics.cr
@@ -0,0 +1,49 @@
+require "./base.cr"
+
+module Invidious::Database::Statistics
+ extend self
+
+ # -------------------
+ # User stats
+ # -------------------
+
+ def count_users_total : Int64
+ request = <<-SQL
+ SELECT count(*) FROM users
+ SQL
+
+ PG_DB.query_one(request, as: Int64)
+ end
+
+ def count_users_active_1m : Int64
+ request = <<-SQL
+ SELECT count(*) FROM users
+ WHERE CURRENT_TIMESTAMP - updated < '6 months'
+ SQL
+
+ PG_DB.query_one(request, as: Int64)
+ end
+
+ def count_users_active_6m : Int64
+ request = <<-SQL
+ SELECT count(*) FROM users
+ WHERE CURRENT_TIMESTAMP - updated < '1 month'
+ SQL
+
+ PG_DB.query_one(request, as: Int64)
+ end
+
+ # -------------------
+ # Channel stats
+ # -------------------
+
+ def channel_last_update : Time?
+ request = <<-SQL
+ SELECT updated FROM channels
+ ORDER BY updated DESC
+ LIMIT 1
+ SQL
+
+ PG_DB.query_one?(request, as: Time)
+ end
+end
diff --git a/src/invidious/jobs/statistics_refresh_job.cr b/src/invidious/jobs/statistics_refresh_job.cr
index 6569c0a1..a113bd77 100644
--- a/src/invidious/jobs/statistics_refresh_job.cr
+++ b/src/invidious/jobs/statistics_refresh_job.cr
@@ -47,12 +47,14 @@ class Invidious::Jobs::StatisticsRefreshJob < Invidious::Jobs::BaseJob
private def refresh_stats
users = STATISTICS.dig("usage", "users").as(Hash(String, Int64))
- users["total"] = db.query_one("SELECT count(*) FROM users", as: Int64)
- users["activeHalfyear"] = db.query_one("SELECT count(*) FROM users WHERE CURRENT_TIMESTAMP - updated < '6 months'", as: Int64)
- users["activeMonth"] = db.query_one("SELECT count(*) FROM users WHERE CURRENT_TIMESTAMP - updated < '1 month'", as: Int64)
+
+ users["total"] = Invidious::Database::Statistics.count_users_total
+ users["activeHalfyear"] = Invidious::Database::Statistics.count_users_active_1m
+ users["activeMonth"] = Invidious::Database::Statistics.count_users_active_6m
+
STATISTICS["metadata"] = {
"updatedAt" => Time.utc.to_unix,
- "lastChannelRefreshedAt" => db.query_one?("SELECT updated FROM channels ORDER BY updated DESC LIMIT 1", as: Time).try &.to_unix || 0_i64,
+ "lastChannelRefreshedAt" => Invidious::Database::Statistics.channel_last_update.try &.to_unix || 0_i64,
}
end
end
From c021b93b5c7d38504b9cf40307d89b81241adfd9 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Thu, 2 Dec 2021 19:16:41 +0100
Subject: [PATCH 05/16] Move DB queries related to channels in a separate
module
---
src/invidious.cr | 8 +-
src/invidious/channels/channels.cr | 23 +--
src/invidious/database/channels.cr | 149 ++++++++++++++++++
src/invidious/helpers/helpers.cr | 5 +-
src/invidious/jobs/pull_popular_videos_job.cr | 9 +-
src/invidious/jobs/refresh_channels_job.cr | 4 +-
src/invidious/routes/api/v1/authenticated.cr | 8 +-
src/invidious/routes/feeds.cr | 5 +-
src/invidious/users.cr | 5 +-
9 files changed, 164 insertions(+), 52 deletions(-)
create mode 100644 src/invidious/database/channels.cr
diff --git a/src/invidious.cr b/src/invidious.cr
index 93b3357a..97809160 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -649,13 +649,7 @@ get "/subscription_manager" do |env|
format = env.params.query["format"]?
format ||= "rss"
- if user.subscriptions.empty?
- values = "'{}'"
- else
- values = "VALUES #{user.subscriptions.map { |id| %(('#{id}')) }.join(",")}"
- end
-
- subscriptions = PG_DB.query_all("SELECT * FROM channels WHERE id = ANY(#{values})", as: InvidiousChannel)
+ subscriptions = Invidious::Database::Channels.select(user.subscriptions)
subscriptions.sort_by!(&.author.downcase)
if action_takeout
diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr
index 827b6534..5d962ab4 100644
--- a/src/invidious/channels/channels.cr
+++ b/src/invidious/channels/channels.cr
@@ -152,21 +152,14 @@ def get_batch_channels(channels, db, refresh = false, pull_all_videos = true, ma
end
def get_channel(id, db, refresh = true, pull_all_videos = true)
- if channel = db.query_one?("SELECT * FROM channels WHERE id = $1", id, as: InvidiousChannel)
+ if channel = Invidious::Database::Channels.select(id)
if refresh && Time.utc - channel.updated > 10.minutes
channel = fetch_channel(id, db, pull_all_videos: pull_all_videos)
- channel_array = channel.to_a
- args = arg_array(channel_array)
-
- db.exec("INSERT INTO channels VALUES (#{args}) \
- ON CONFLICT (id) DO UPDATE SET author = $2, updated = $3", args: channel_array)
+ Invidious::Database::Channels.insert(channel, update_on_conflict: true)
end
else
channel = fetch_channel(id, db, pull_all_videos: pull_all_videos)
- channel_array = channel.to_a
- args = arg_array(channel_array)
-
- db.exec("INSERT INTO channels VALUES (#{args})", args: channel_array)
+ Invidious::Database::Channels.insert(channel)
end
return channel
@@ -241,10 +234,7 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil)
# We don't include the 'premiere_timestamp' here because channel pages don't include them,
# meaning the above timestamp is always null
- was_insert = db.query_one("INSERT INTO channel_videos VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) \
- ON CONFLICT (id) DO UPDATE SET title = $2, published = $3, \
- updated = $4, ucid = $5, author = $6, length_seconds = $7, \
- live_now = $8, views = $10 returning (xmax=0) as was_insert", *video.to_tuple, as: Bool)
+ was_insert = Invidious::Database::ChannelVideos.insert(video)
if was_insert
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Inserted, updating subscriptions")
@@ -284,10 +274,7 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil)
# We are notified of Red videos elsewhere (PubSub), which includes a correct published date,
# so since they don't provide a published date here we can safely ignore them.
if Time.utc - video.published > 1.minute
- was_insert = db.query_one("INSERT INTO channel_videos VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) \
- ON CONFLICT (id) DO UPDATE SET title = $2, published = $3, \
- updated = $4, ucid = $5, author = $6, length_seconds = $7, \
- live_now = $8, views = $10 returning (xmax=0) as was_insert", *video.to_tuple, as: Bool)
+ was_insert = Invidious::Database::ChannelVideos.insert(video)
db.exec("UPDATE users SET notifications = array_append(notifications, $1), \
feed_needs_update = true WHERE $2 = ANY(subscriptions)", video.id, video.ucid) if was_insert
diff --git a/src/invidious/database/channels.cr b/src/invidious/database/channels.cr
new file mode 100644
index 00000000..134cf59d
--- /dev/null
+++ b/src/invidious/database/channels.cr
@@ -0,0 +1,149 @@
+require "./base.cr"
+
+#
+# This module contains functions related to the "channels" table.
+#
+module Invidious::Database::Channels
+ extend self
+
+ # -------------------
+ # Insert / delete
+ # -------------------
+
+ def insert(channel : InvidiousChannel, update_on_conflict : Bool = false)
+ channel_array = channel.to_a
+
+ request = <<-SQL
+ INSERT INTO channels
+ VALUES (#{arg_array(channel_array)})
+ SQL
+
+ if update_on_conflict
+ request += <<-SQL
+ ON CONFLICT (id) DO UPDATE
+ SET author = $2, updated = $3
+ SQL
+ end
+
+ PG_DB.exec(request, args: channel_array)
+ end
+
+ # -------------------
+ # Update
+ # -------------------
+
+ def update_author(id : String, author : String)
+ request = <<-SQL
+ UPDATE channels
+ SET updated = $1, author = $2, deleted = false
+ WHERE id = $3
+ SQL
+
+ PG_DB.exec(request, Time.utc, author, id)
+ end
+
+ def update_mark_deleted(id : String)
+ request = <<-SQL
+ UPDATE channels
+ SET updated = $1, deleted = true
+ WHERE id = $2
+ SQL
+
+ PG_DB.exec(request, Time.utc, id)
+ end
+
+ # -------------------
+ # Select
+ # -------------------
+
+ def select(id : String) : InvidiousChannel?
+ request = <<-SQL
+ SELECT * FROM channels
+ WHERE id = $1
+ SQL
+
+ return PG_DB.query_one?(request, id, as: InvidiousChannel)
+ end
+
+ def select(ids : Array(String)) : Array(InvidiousChannel)?
+ return [] of InvidiousChannel if ids.empty?
+ values = ids.map { |id| %(('#{id}')) }.join(",")
+
+ request = <<-SQL
+ SELECT * FROM channels
+ WHERE id = ANY(VALUES #{values})
+ SQL
+
+ return PG_DB.query_all(request, as: InvidiousChannel)
+ end
+end
+
+#
+# This module contains functions related to the "channel_videos" table.
+#
+module Invidious::Database::ChannelVideos
+ extend self
+
+ # -------------------
+ # Insert
+ # -------------------
+
+ # This function returns the status of the query (i.e: success?)
+ def insert(video : ChannelVideo, with_premiere_timestamp : Bool = false) : Bool
+ if with_premiere_timestamp
+ last_items = "premiere_timestamp = $9, views = $10"
+ else
+ last_items = "views = $10"
+ end
+
+ request = <<-SQL
+ INSERT INTO channel_videos
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
+ ON CONFLICT (id) DO UPDATE
+ SET title = $2, published = $3, updated = $4, ucid = $5,
+ author = $6, length_seconds = $7, live_now = $8, #{last_items}
+ RETURNING (xmax=0) AS was_insert
+ SQL
+
+ return PG_DB.query_one(request, *video.to_tuple, as: Bool)
+ end
+
+ # -------------------
+ # Select
+ # -------------------
+
+ def select(ids : Array(String)) : Array(ChannelVideo)
+ return [] of ChannelVideo if ids.empty?
+
+ request = <<-SQL
+ SELECT * FROM channel_videos
+ WHERE id IN (#{arg_array(ids)})
+ ORDER BY published DESC
+ SQL
+
+ return PG_DB.query_all(request, args: ids, as: ChannelVideo)
+ end
+
+ def select_notfications(ucid : String, since : Time) : Array(ChannelVideo)
+ request = <<-SQL
+ SELECT * FROM channel_videos
+ WHERE ucid = $1 AND published > $2
+ ORDER BY published DESC
+ LIMIT 15
+ SQL
+
+ return PG_DB.query_all(request, ucid, since, as: ChannelVideo)
+ end
+
+ def select_popular_videos : Array(ChannelVideo)
+ request = <<-SQL
+ SELECT DISTINCT ON (ucid) *
+ FROM channel_videos
+ WHERE ucid IN (SELECT channel FROM (SELECT UNNEST(subscriptions) AS channel FROM users) AS d
+ GROUP BY channel ORDER BY COUNT(channel) DESC LIMIT 40)
+ ORDER BY ucid, published DESC
+ SQL
+
+ PG_DB.query_all(request, as: ChannelVideo)
+ end
+end
diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr
index 96a78eb9..014c04a8 100644
--- a/src/invidious/helpers/helpers.cr
+++ b/src/invidious/helpers/helpers.cr
@@ -235,11 +235,12 @@ def create_notification_stream(env, topics, connection_channel)
spawn do
begin
if since
+ since_unix = Time.unix(since.not_nil!)
+
topics.try &.each do |topic|
case topic
when .match(/UC[A-Za-z0-9_-]{22}/)
- PG_DB.query_all("SELECT * FROM channel_videos WHERE ucid = $1 AND published > $2 ORDER BY published DESC LIMIT 15",
- topic, Time.unix(since.not_nil!), as: ChannelVideo).each do |video|
+ Invidious::Database::ChannelVideos.select_notfications(topic, since_unix).each do |video|
response = JSON.parse(video.to_json(locale))
if fields_text = env.params.query["fields"]?
diff --git a/src/invidious/jobs/pull_popular_videos_job.cr b/src/invidious/jobs/pull_popular_videos_job.cr
index 38de816e..dc785bae 100644
--- a/src/invidious/jobs/pull_popular_videos_job.cr
+++ b/src/invidious/jobs/pull_popular_videos_job.cr
@@ -1,11 +1,4 @@
class Invidious::Jobs::PullPopularVideosJob < Invidious::Jobs::BaseJob
- QUERY = <<-SQL
- SELECT DISTINCT ON (ucid) *
- FROM channel_videos
- WHERE ucid IN (SELECT channel FROM (SELECT UNNEST(subscriptions) AS channel FROM users) AS d
- GROUP BY channel ORDER BY COUNT(channel) DESC LIMIT 40)
- ORDER BY ucid, published DESC
- SQL
POPULAR_VIDEOS = Atomic.new([] of ChannelVideo)
private getter db : DB::Database
@@ -14,7 +7,7 @@ class Invidious::Jobs::PullPopularVideosJob < Invidious::Jobs::BaseJob
def begin
loop do
- videos = db.query_all(QUERY, as: ChannelVideo)
+ videos = Invidious::Database::ChannelVideos.select_popular_videos
.sort_by!(&.published)
.reverse!
diff --git a/src/invidious/jobs/refresh_channels_job.cr b/src/invidious/jobs/refresh_channels_job.cr
index 2321e964..c224c745 100644
--- a/src/invidious/jobs/refresh_channels_job.cr
+++ b/src/invidious/jobs/refresh_channels_job.cr
@@ -35,11 +35,11 @@ class Invidious::Jobs::RefreshChannelsJob < Invidious::Jobs::BaseJob
lim_fibers = max_fibers
LOGGER.trace("RefreshChannelsJob: #{id} fiber : Updating DB")
- db.exec("UPDATE channels SET updated = $1, author = $2, deleted = false WHERE id = $3", Time.utc, channel.author, id)
+ Invidious::Database::Channels.update_author(id, channel.author)
rescue ex
LOGGER.error("RefreshChannelsJob: #{id} : #{ex.message}")
if ex.message == "Deleted or invalid channel"
- db.exec("UPDATE channels SET updated = $1, deleted = true WHERE id = $2", Time.utc, id)
+ Invidious::Database::Channels.update_mark_deleted(id)
else
lim_fibers = 1
LOGGER.error("RefreshChannelsJob: #{id} fiber : backing off for #{backoff}s")
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index d74dca5c..a3ac2add 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -72,13 +72,7 @@ module Invidious::Routes::API::V1::Authenticated
env.response.content_type = "application/json"
user = env.get("user").as(User)
- if user.subscriptions.empty?
- values = "'{}'"
- else
- values = "VALUES #{user.subscriptions.map { |id| %(('#{id}')) }.join(",")}"
- end
-
- subscriptions = PG_DB.query_all("SELECT * FROM channels WHERE id = ANY(#{values})", as: InvidiousChannel)
+ subscriptions = Invidious::Database::Channels.select(user.subscriptions)
JSON.build do |json|
json.array do
diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr
index 6424ab47..78e6bd40 100644
--- a/src/invidious/routes/feeds.cr
+++ b/src/invidious/routes/feeds.cr
@@ -416,10 +416,7 @@ module Invidious::Routes::Feeds
views: video.views,
})
- was_insert = PG_DB.query_one("INSERT INTO channel_videos VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
- ON CONFLICT (id) DO UPDATE SET title = $2, published = $3,
- updated = $4, ucid = $5, author = $6, length_seconds = $7,
- live_now = $8, premiere_timestamp = $9, views = $10 returning (xmax=0) as was_insert", *video.to_tuple, as: Bool)
+ was_insert = Invidious::Database::ChannelVideos.insert(video, with_premiere_timestamp: true)
PG_DB.exec("UPDATE users SET notifications = array_append(notifications, $1),
feed_needs_update = true WHERE $2 = ANY(subscriptions)", video.id, video.ucid) if was_insert
diff --git a/src/invidious/users.cr b/src/invidious/users.cr
index 584082be..92143437 100644
--- a/src/invidious/users.cr
+++ b/src/invidious/users.cr
@@ -242,10 +242,7 @@ def get_subscription_feed(db, user, max_results = 40, page = 1)
if user.preferences.notifications_only && !notifications.empty?
# Only show notifications
-
- args = arg_array(notifications)
-
- notifications = db.query_all("SELECT * FROM channel_videos WHERE id IN (#{args}) ORDER BY published DESC", args: notifications, as: ChannelVideo)
+ notifications = Invidious::Database::ChannelVideos.select(notifications)
videos = [] of ChannelVideo
notifications.sort_by!(&.published).reverse!
From 92eea3b18b406e7eb86e1bd95dfaf9078f49ed72 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Thu, 2 Dec 2021 23:57:13 +0100
Subject: [PATCH 06/16] Move DB queries related to session tokens in a separate
module
---
src/invidious.cr | 10 +--
src/invidious/database/nonces.cr | 46 ++++++++++++
src/invidious/database/sessions.cr | 74 ++++++++++++++++++++
src/invidious/helpers/handlers.cr | 4 +-
src/invidious/helpers/tokens.cr | 8 +--
src/invidious/routes/api/v1/authenticated.cr | 6 +-
src/invidious/routes/login.cr | 6 +-
src/invidious/users.cr | 8 +--
8 files changed, 140 insertions(+), 22 deletions(-)
create mode 100644 src/invidious/database/nonces.cr
create mode 100644 src/invidious/database/sessions.cr
diff --git a/src/invidious.cr b/src/invidious.cr
index 97809160..94620a26 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -247,7 +247,7 @@ before_all do |env|
# Invidious users only have SID
if !env.request.cookies.has_key? "SSID"
- if email = PG_DB.query_one?("SELECT email FROM session_ids WHERE id = $1", sid, as: String)
+ if email = Invidious::Database::SessionIDs.select_email(sid)
user = PG_DB.query_one("SELECT * FROM users WHERE email = $1", email, as: User)
csrf_token = generate_response(sid, {
":authorize_token",
@@ -633,6 +633,7 @@ get "/subscription_manager" do |env|
end
user = user.as(User)
+ sid = sid.as(String)
if !user.password
# Refresh account
@@ -1008,7 +1009,7 @@ post "/delete_account" do |env|
view_name = "subscriptions_#{sha256(user.email)}"
PG_DB.exec("DELETE FROM users * WHERE email = $1", user.email)
- PG_DB.exec("DELETE FROM session_ids * WHERE email = $1", user.email)
+ Invidious::Database::SessionIDs.delete(email: user.email)
PG_DB.exec("DROP MATERIALIZED VIEW #{view_name}")
env.request.cookies.each do |cookie|
@@ -1150,8 +1151,7 @@ get "/token_manager" do |env|
end
user = user.as(User)
-
- tokens = PG_DB.query_all("SELECT id, issued FROM session_ids WHERE email = $1 ORDER BY issued DESC", user.email, as: {session: String, issued: Time})
+ tokens = Invidious::Database::SessionIDs.select_all(user.email)
templated "token_manager"
end
@@ -1200,7 +1200,7 @@ post "/token_ajax" do |env|
case action
when .starts_with? "action_revoke_token"
- PG_DB.exec("DELETE FROM session_ids * WHERE id = $1 AND email = $2", session, user.email)
+ Invidious::Database::SessionIDs.delete(sid: session, email: user.email)
else
next error_json(400, "Unsupported action #{action}")
end
diff --git a/src/invidious/database/nonces.cr b/src/invidious/database/nonces.cr
new file mode 100644
index 00000000..469fcbd8
--- /dev/null
+++ b/src/invidious/database/nonces.cr
@@ -0,0 +1,46 @@
+require "./base.cr"
+
+module Invidious::Database::Nonces
+ extend self
+
+ # -------------------
+ # Insert
+ # -------------------
+
+ def insert(nonce : String, expire : Time)
+ request = <<-SQL
+ INSERT INTO nonces
+ VALUES ($1, $2)
+ ON CONFLICT DO NOTHING
+ SQL
+
+ PG_DB.exec(request, nonce, expire)
+ end
+
+ # -------------------
+ # Update
+ # -------------------
+
+ def update_set_expired(nonce : String)
+ request = <<-SQL
+ UPDATE nonces
+ SET expire = $1
+ WHERE nonce = $2
+ SQL
+
+ PG_DB.exec(request, Time.utc(1990, 1, 1), nonce)
+ end
+
+ # -------------------
+ # Select
+ # -------------------
+
+ def select(nonce : String) : Tuple(String, Time)?
+ request = <<-SQL
+ SELECT * FROM nonces
+ WHERE nonce = $1
+ SQL
+
+ return PG_DB.query_one?(request, nonce, as: {String, Time})
+ end
+end
diff --git a/src/invidious/database/sessions.cr b/src/invidious/database/sessions.cr
new file mode 100644
index 00000000..d5f85dd6
--- /dev/null
+++ b/src/invidious/database/sessions.cr
@@ -0,0 +1,74 @@
+require "./base.cr"
+
+module Invidious::Database::SessionIDs
+ extend self
+
+ # -------------------
+ # Insert
+ # -------------------
+
+ def insert(sid : String, email : String, handle_conflicts : Bool = false)
+ request = <<-SQL
+ INSERT INTO session_ids
+ VALUES ($1, $2, $3)
+ SQL
+
+ request += " ON CONFLICT (id) DO NOTHING" if handle_conflicts
+
+ PG_DB.exec(request, sid, email, Time.utc)
+ end
+
+ # -------------------
+ # Delete
+ # -------------------
+
+ def delete(*, sid : String)
+ request = <<-SQL
+ DELETE FROM session_ids *
+ WHERE id = $1
+ SQL
+
+ PG_DB.exec(request, sid)
+ end
+
+ def delete(*, email : String)
+ request = <<-SQL
+ DELETE FROM session_ids *
+ WHERE email = $1
+ SQL
+
+ PG_DB.exec(request, email)
+ end
+
+ def delete(*, sid : String, email : String)
+ request = <<-SQL
+ DELETE FROM session_ids *
+ WHERE id = $1 AND email = $2
+ SQL
+
+ PG_DB.exec(request, sid, email)
+ end
+
+ # -------------------
+ # Select
+ # -------------------
+
+ def select_email(sid : String) : String?
+ request = <<-SQL
+ SELECT email FROM session_ids
+ WHERE id = $1
+ SQL
+
+ PG_DB.query_one?(request, sid, as: String)
+ end
+
+ def select_all(email : String) : Array({session: String, issued: Time})
+ request = <<-SQL
+ SELECT id, issued FROM session_ids
+ WHERE email = $1
+ ORDER BY issued DESC
+ SQL
+
+ PG_DB.query_all(request, email, as: {session: String, issued: Time})
+ end
+end
diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr
index 045b6701..0aa86e64 100644
--- a/src/invidious/helpers/handlers.cr
+++ b/src/invidious/helpers/handlers.cr
@@ -99,7 +99,7 @@ class AuthHandler < Kemal::Handler
session = URI.decode_www_form(token["session"].as_s)
scopes, expire, signature = validate_request(token, session, env.request, HMAC_KEY, PG_DB, nil)
- if email = PG_DB.query_one?("SELECT email FROM session_ids WHERE id = $1", session, as: String)
+ if email = Invidious::Database::SessionIDs.select_email(session)
user = PG_DB.query_one("SELECT * FROM users WHERE email = $1", email, as: User)
end
elsif sid = env.request.cookies["SID"]?.try &.value
@@ -107,7 +107,7 @@ class AuthHandler < Kemal::Handler
raise "Cannot use token as SID"
end
- if email = PG_DB.query_one?("SELECT email FROM session_ids WHERE id = $1", sid, as: String)
+ if email = Invidious::Database::SessionIDs.select_email(sid)
user = PG_DB.query_one("SELECT * FROM users WHERE email = $1", email, as: User)
end
diff --git a/src/invidious/helpers/tokens.cr b/src/invidious/helpers/tokens.cr
index 3874799a..91405822 100644
--- a/src/invidious/helpers/tokens.cr
+++ b/src/invidious/helpers/tokens.cr
@@ -2,7 +2,7 @@ require "crypto/subtle"
def generate_token(email, scopes, expire, key, db)
session = "v1:#{Base64.urlsafe_encode(Random::Secure.random_bytes(32))}"
- PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", session, email, Time.utc)
+ Invidious::Database::SessionIDs.insert(session, email)
token = {
"session" => session,
@@ -30,7 +30,7 @@ def generate_response(session, scopes, key, db, expire = 6.hours, use_nonce = fa
if use_nonce
nonce = Random::Secure.hex(16)
- db.exec("INSERT INTO nonces VALUES ($1, $2) ON CONFLICT DO NOTHING", nonce, expire)
+ Invidious::Database::Nonces.insert(nonce, expire)
token["nonce"] = nonce
end
@@ -92,9 +92,9 @@ def validate_request(token, session, request, key, db, locale = nil)
raise InfoException.new("Invalid signature")
end
- if token["nonce"]? && (nonce = db.query_one?("SELECT * FROM nonces WHERE nonce = $1", token["nonce"], as: {String, Time}))
+ if token["nonce"]? && (nonce = Invidious::Database::Nonces.select(token["nonce"].as_s))
if nonce[1] > Time.utc
- db.exec("UPDATE nonces SET expire = $1 WHERE nonce = $2", Time.utc(1990, 1, 1), nonce[0])
+ Invidious::Database::Nonces.update_set_expired(nonce[0])
else
raise InfoException.new("Erroneous token")
end
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index a3ac2add..c95007c2 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -312,7 +312,7 @@ module Invidious::Routes::API::V1::Authenticated
user = env.get("user").as(User)
scopes = env.get("scopes").as(Array(String))
- tokens = PG_DB.query_all("SELECT id, issued FROM session_ids WHERE email = $1", user.email, as: {session: String, issued: Time})
+ tokens = Invidious::Database::SessionIDs.select_all(user.email)
JSON.build do |json|
json.array do
@@ -400,9 +400,9 @@ module Invidious::Routes::API::V1::Authenticated
# Allow tokens to revoke other tokens with correct scope
if session == env.get("session").as(String)
- PG_DB.exec("DELETE FROM session_ids * WHERE id = $1", session)
+ Invidious::Database::SessionIDs.delete(sid: session)
elsif scopes_include_scope(scopes, "GET:tokens")
- PG_DB.exec("DELETE FROM session_ids * WHERE id = $1", session)
+ Invidious::Database::SessionIDs.delete(sid: session)
else
return error_json(400, "Cannot revoke session #{session}")
end
diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr
index 2a50561d..e70206cc 100644
--- a/src/invidious/routes/login.cr
+++ b/src/invidious/routes/login.cr
@@ -336,7 +336,7 @@ module Invidious::Routes::Login
if Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
- PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", sid, email, Time.utc)
+ Invidious::Database::SessionIDs.insert(sid, email)
if Kemal.config.ssl || CONFIG.https_only
secure = true
@@ -455,7 +455,7 @@ module Invidious::Routes::Login
args = arg_array(user_array)
PG_DB.exec("INSERT INTO users VALUES (#{args})", args: user_array)
- PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", sid, email, Time.utc)
+ Invidious::Database::SessionIDs.insert(sid, email)
view_name = "subscriptions_#{sha256(user.email)}"
PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}")
@@ -511,7 +511,7 @@ module Invidious::Routes::Login
return error_template(400, ex)
end
- PG_DB.exec("DELETE FROM session_ids * WHERE id = $1", sid)
+ Invidious::Database::SessionIDs.delete(sid: sid)
env.request.cookies.each do |cookie|
cookie.expires = Time.utc(1990, 1, 1)
diff --git a/src/invidious/users.cr b/src/invidious/users.cr
index 92143437..3e9a9e68 100644
--- a/src/invidious/users.cr
+++ b/src/invidious/users.cr
@@ -30,7 +30,7 @@ struct User
end
def get_user(sid, headers, db, refresh = true)
- if email = db.query_one?("SELECT email FROM session_ids WHERE id = $1", sid, as: String)
+ if email = Invidious::Database::SessionIDs.select_email(sid)
user = db.query_one("SELECT * FROM users WHERE email = $1", email, as: User)
if refresh && Time.utc - user.updated > 1.minute
@@ -42,8 +42,7 @@ def get_user(sid, headers, db, refresh = true)
db.exec("INSERT INTO users VALUES (#{args}) \
ON CONFLICT (email) DO UPDATE SET updated = $1, subscriptions = $3", args: user_array)
- db.exec("INSERT INTO session_ids VALUES ($1,$2,$3) \
- ON CONFLICT (id) DO NOTHING", sid, user.email, Time.utc)
+ Invidious::Database::SessionIDs.insert(sid, user.email, handle_conflicts: true)
begin
view_name = "subscriptions_#{sha256(user.email)}"
@@ -60,8 +59,7 @@ def get_user(sid, headers, db, refresh = true)
db.exec("INSERT INTO users VALUES (#{args}) \
ON CONFLICT (email) DO UPDATE SET updated = $1, subscriptions = $3", args: user_array)
- db.exec("INSERT INTO session_ids VALUES ($1,$2,$3) \
- ON CONFLICT (id) DO NOTHING", sid, user.email, Time.utc)
+ Invidious::Database::SessionIDs.insert(sid, user.email, handle_conflicts: true)
begin
view_name = "subscriptions_#{sha256(user.email)}"
From 094f83564297257a956d2e42d3b70adfa78b3185 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Fri, 3 Dec 2021 02:27:51 +0100
Subject: [PATCH 07/16] Move DB queries related to 'users' in a separate module
(1/2)
---
src/invidious.cr | 15 +--
src/invidious/database/users.cr | 129 +++++++++++++++++++
src/invidious/helpers/handlers.cr | 4 +-
src/invidious/routes/api/v1/authenticated.cr | 4 +-
src/invidious/routes/feeds.cr | 2 +-
src/invidious/routes/login.cr | 9 +-
src/invidious/routes/watch.cr | 2 +-
src/invidious/users.cr | 16 +--
8 files changed, 147 insertions(+), 34 deletions(-)
create mode 100644 src/invidious/database/users.cr
diff --git a/src/invidious.cr b/src/invidious.cr
index 94620a26..91f19d69 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -248,7 +248,7 @@ before_all do |env|
# Invidious users only have SID
if !env.request.cookies.has_key? "SSID"
if email = Invidious::Database::SessionIDs.select_email(sid)
- user = PG_DB.query_one("SELECT * FROM users WHERE email = $1", email, as: User)
+ user = Invidious::Database::Users.select!(email: email)
csrf_token = generate_response(sid, {
":authorize_token",
":playlist_ajax",
@@ -458,10 +458,10 @@ post "/watch_ajax" do |env|
case action
when "action_mark_watched"
if !user.watched.includes? id
- PG_DB.exec("UPDATE users SET watched = array_append(watched, $1) WHERE email = $2", id, user.email)
+ Invidious::Database::Users.mark_watched(user, id)
end
when "action_mark_unwatched"
- PG_DB.exec("UPDATE users SET watched = array_remove(watched, $1) WHERE email = $2", id, user.email)
+ Invidious::Database::Users.mark_unwatched(user, id)
else
next error_json(400, "Unsupported action #{action}")
end
@@ -599,16 +599,15 @@ post "/subscription_ajax" do |env|
# Sync subscriptions with YouTube
subscribe_ajax(channel_id, action, env.request.headers)
end
- email = user.email
case action
when "action_create_subscription_to_channel"
if !user.subscriptions.includes? channel_id
get_channel(channel_id, PG_DB, false, false)
- PG_DB.exec("UPDATE users SET feed_needs_update = true, subscriptions = array_append(subscriptions, $1) WHERE email = $2", channel_id, email)
+ Invidious::Database::Users.subscribe_channel(user, channel_id)
end
when "action_remove_subscriptions"
- PG_DB.exec("UPDATE users SET feed_needs_update = true, subscriptions = array_remove(subscriptions, $1) WHERE email = $2", channel_id, email)
+ Invidious::Database::Users.unsubscribe_channel(user, channel_id)
else
next error_json(400, "Unsupported action #{action}")
end
@@ -1008,7 +1007,7 @@ post "/delete_account" do |env|
end
view_name = "subscriptions_#{sha256(user.email)}"
- PG_DB.exec("DELETE FROM users * WHERE email = $1", user.email)
+ Invidious::Database::Users.delete(user)
Invidious::Database::SessionIDs.delete(email: user.email)
PG_DB.exec("DROP MATERIALIZED VIEW #{view_name}")
@@ -1059,7 +1058,7 @@ post "/clear_watch_history" do |env|
next error_template(400, ex)
end
- PG_DB.exec("UPDATE users SET watched = '{}' WHERE email = $1", user.email)
+ Invidious::Database::Users.clear_watch_history(user)
env.redirect referer
end
diff --git a/src/invidious/database/users.cr b/src/invidious/database/users.cr
new file mode 100644
index 00000000..aa3b9f85
--- /dev/null
+++ b/src/invidious/database/users.cr
@@ -0,0 +1,129 @@
+require "./base.cr"
+
+module Invidious::Database::Users
+ extend self
+
+ # -------------------
+ # Insert / delete
+ # -------------------
+
+ def insert(user : User, update_on_conflict : Bool = false)
+ user_array = user.to_a
+ user_array[4] = user_array[4].to_json # User preferences
+
+ request = <<-SQL
+ INSERT INTO users
+ VALUES (#{arg_array(user_array)})
+ SQL
+
+ if update_on_conflict
+ request += <<-SQL
+ ON CONFLICT (email) DO UPDATE
+ SET updated = $1, subscriptions = $3
+ SQL
+ end
+
+ PG_DB.exec(request, args: user_array)
+ end
+
+ def delete(user : User)
+ request = <<-SQL
+ DELETE FROM users *
+ WHERE email = $1
+ SQL
+
+ PG_DB.exec(request, user.email)
+ end
+
+ # -------------------
+ # Update (history)
+ # -------------------
+
+ def mark_watched(user : User, vid : String)
+ request = <<-SQL
+ UPDATE users
+ SET watched = array_append(watched, $1)
+ WHERE email = $2
+ SQL
+
+ PG_DB.exec(request, vid, user.email)
+ end
+
+ def mark_unwatched(user : User, vid : String)
+ request = <<-SQL
+ UPDATE users
+ SET watched = array_remove(watched, $1)
+ WHERE email = $2
+ SQL
+
+ PG_DB.exec(request, vid, user.email)
+ end
+
+ def clear_watch_history(user : User)
+ request = <<-SQL
+ UPDATE users
+ SET watched = '{}'
+ WHERE email = $1
+ SQL
+
+ PG_DB.exec(request, user.email)
+ end
+
+ # -------------------
+ # Update (channels)
+ # -------------------
+
+ def subscribe_channel(user : User, ucid : String)
+ request = <<-SQL
+ UPDATE users
+ SET feed_needs_update = true,
+ subscriptions = array_append(subscriptions,$1)
+ WHERE email = $2
+ SQL
+
+ PG_DB.exec(request, ucid, user.email)
+ end
+
+ def unsubscribe_channel(user : User, ucid : String)
+ request = <<-SQL
+ UPDATE users
+ SET feed_needs_update = true,
+ subscriptions = array_remove(subscriptions, $1)
+ WHERE email = $2
+ SQL
+
+ PG_DB.exec(request, ucid, user.email)
+ end
+
+ # -------------------
+ # Select
+ # -------------------
+
+ def select(*, email : String) : User?
+ request = <<-SQL
+ SELECT * FROM users
+ WHERE email = $1
+ SQL
+
+ return PG_DB.query_one?(request, email, as: User)
+ end
+
+ # Same as select, but can raise an exception
+ def select!(*, email : String) : User
+ request = <<-SQL
+ SELECT * FROM users
+ WHERE email = $1
+ SQL
+
+ return PG_DB.query_one(request, email, as: User)
+ end
+
+ def select(*, token : String) : User?
+ request = <<-SQL
+ SELECT * FROM users
+ WHERE token = $1
+ SQL
+
+ return PG_DB.query_one?(request, token, as: User)
+ end
+end
diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr
index 0aa86e64..d52035c7 100644
--- a/src/invidious/helpers/handlers.cr
+++ b/src/invidious/helpers/handlers.cr
@@ -100,7 +100,7 @@ class AuthHandler < Kemal::Handler
scopes, expire, signature = validate_request(token, session, env.request, HMAC_KEY, PG_DB, nil)
if email = Invidious::Database::SessionIDs.select_email(session)
- user = PG_DB.query_one("SELECT * FROM users WHERE email = $1", email, as: User)
+ user = Invidious::Database::Users.select!(email: email)
end
elsif sid = env.request.cookies["SID"]?.try &.value
if sid.starts_with? "v1:"
@@ -108,7 +108,7 @@ class AuthHandler < Kemal::Handler
end
if email = Invidious::Database::SessionIDs.select_email(sid)
- user = PG_DB.query_one("SELECT * FROM users WHERE email = $1", email, as: User)
+ user = Invidious::Database::Users.select!(email: email)
end
scopes = [":*"]
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index c95007c2..d9b58ebf 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -94,7 +94,7 @@ module Invidious::Routes::API::V1::Authenticated
if !user.subscriptions.includes? ucid
get_channel(ucid, PG_DB, false, false)
- PG_DB.exec("UPDATE users SET feed_needs_update = true, subscriptions = array_append(subscriptions,$1) WHERE email = $2", ucid, user.email)
+ Invidious::Database::Users.subscribe_channel(user, ucid)
end
# For Google accounts, access tokens don't have enough information to
@@ -110,7 +110,7 @@ module Invidious::Routes::API::V1::Authenticated
ucid = env.params.url["ucid"]
- PG_DB.exec("UPDATE users SET feed_needs_update = true, subscriptions = array_remove(subscriptions, $1) WHERE email = $2", ucid, user.email)
+ Invidious::Database::Users.unsubscribe_channel(user, ucid)
env.response.status_code = 204
end
diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr
index 78e6bd40..4e7ec9ad 100644
--- a/src/invidious/routes/feeds.cr
+++ b/src/invidious/routes/feeds.cr
@@ -220,7 +220,7 @@ module Invidious::Routes::Feeds
haltf env, status_code: 403
end
- user = PG_DB.query_one?("SELECT * FROM users WHERE token = $1", token.strip, as: User)
+ user = Invidious::Database::Users.select(token: token.strip)
if !user
haltf env, status_code: 403
end
diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr
index e70206cc..8f703464 100644
--- a/src/invidious/routes/login.cr
+++ b/src/invidious/routes/login.cr
@@ -327,7 +327,7 @@ module Invidious::Routes::Login
return error_template(401, "Password is a required field")
end
- user = PG_DB.query_one?("SELECT * FROM users WHERE email = $1", email, as: User)
+ user = Invidious::Database::Users.select(email: email)
if user
if !user.password
@@ -449,12 +449,7 @@ module Invidious::Routes::Login
end
end
- user_array = user.to_a
- user_array[4] = user_array[4].to_json # User preferences
-
- args = arg_array(user_array)
-
- PG_DB.exec("INSERT INTO users VALUES (#{args})", args: user_array)
+ Invidious::Database::Users.insert(user)
Invidious::Database::SessionIDs.insert(sid, email)
view_name = "subscriptions_#{sha256(user.email)}"
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index b24222ff..c1ec0bc6 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -76,7 +76,7 @@ module Invidious::Routes::Watch
env.params.query.delete_all("iv_load_policy")
if watched && !watched.includes? id
- PG_DB.exec("UPDATE users SET watched = array_append(watched, $1) WHERE email = $2", id, user.as(User).email)
+ Invidious::Database::Users.mark_watched(user.as(User), id)
end
if notifications && notifications.includes? id
diff --git a/src/invidious/users.cr b/src/invidious/users.cr
index 3e9a9e68..933c451d 100644
--- a/src/invidious/users.cr
+++ b/src/invidious/users.cr
@@ -31,17 +31,12 @@ end
def get_user(sid, headers, db, refresh = true)
if email = Invidious::Database::SessionIDs.select_email(sid)
- user = db.query_one("SELECT * FROM users WHERE email = $1", email, as: User)
+ user = Invidious::Database::Users.select!(email: email)
if refresh && Time.utc - user.updated > 1.minute
user, sid = fetch_user(sid, headers, db)
- user_array = user.to_a
- user_array[4] = user_array[4].to_json # User preferences
- args = arg_array(user_array)
-
- db.exec("INSERT INTO users VALUES (#{args}) \
- ON CONFLICT (email) DO UPDATE SET updated = $1, subscriptions = $3", args: user_array)
+ Invidious::Database::Users.insert(user, update_on_conflict: true)
Invidious::Database::SessionIDs.insert(sid, user.email, handle_conflicts: true)
begin
@@ -52,13 +47,8 @@ def get_user(sid, headers, db, refresh = true)
end
else
user, sid = fetch_user(sid, headers, db)
- user_array = user.to_a
- user_array[4] = user_array[4].to_json # User preferences
- args = arg_array(user.to_a)
-
- db.exec("INSERT INTO users VALUES (#{args}) \
- ON CONFLICT (email) DO UPDATE SET updated = $1, subscriptions = $3", args: user_array)
+ Invidious::Database::Users.insert(user, update_on_conflict: true)
Invidious::Database::SessionIDs.insert(sid, user.email, handle_conflicts: true)
begin
From 7691f5352025d7b2158ebae73417dd8619baea32 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Fri, 3 Dec 2021 03:29:52 +0100
Subject: [PATCH 08/16] Move DB queries related to 'users' in a separate module
(2/2)
---
src/invidious.cr | 18 ++--
src/invidious/channels/channels.cr | 7 +-
src/invidious/database/users.cr | 89 ++++++++++++++++++++
src/invidious/routes/api/v1/authenticated.cr | 5 +-
src/invidious/routes/embed.cr | 2 +-
src/invidious/routes/feeds.cr | 7 +-
src/invidious/routes/login.cr | 8 +-
src/invidious/routes/preferences.cr | 20 ++---
src/invidious/routes/watch.cr | 2 +-
src/invidious/users.cr | 6 +-
10 files changed, 121 insertions(+), 43 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index 91f19d69..0149be11 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -759,18 +759,18 @@ post "/data_control" do |env|
user.subscriptions = get_batch_channels(user.subscriptions, PG_DB, false, false)
- PG_DB.exec("UPDATE users SET feed_needs_update = true, subscriptions = $1 WHERE email = $2", user.subscriptions, user.email)
+ Invidious::Database::Users.update_subscriptions(user)
end
if body["watch_history"]?
user.watched += body["watch_history"].as_a.map(&.as_s)
user.watched.uniq!
- PG_DB.exec("UPDATE users SET watched = $1 WHERE email = $2", user.watched, user.email)
+ Invidious::Database::Users.update_watch_history(user)
end
if body["preferences"]?
user.preferences = Preferences.from_json(body["preferences"].to_json)
- PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", user.preferences.to_json, user.email)
+ Invidious::Database::Users.update_preferences(user)
end
if playlists = body["playlists"]?.try &.as_a?
@@ -831,7 +831,7 @@ post "/data_control" do |env|
user.subscriptions = get_batch_channels(user.subscriptions, PG_DB, false, false)
- PG_DB.exec("UPDATE users SET feed_needs_update = true, subscriptions = $1 WHERE email = $2", user.subscriptions, user.email)
+ Invidious::Database::Users.update_subscriptions(user)
when "import_freetube"
user.subscriptions += body.scan(/"channelId":"(?[a-zA-Z0-9_-]{24})"/).map do |md|
md["channel_id"]
@@ -840,7 +840,7 @@ post "/data_control" do |env|
user.subscriptions = get_batch_channels(user.subscriptions, PG_DB, false, false)
- PG_DB.exec("UPDATE users SET feed_needs_update = true, subscriptions = $1 WHERE email = $2", user.subscriptions, user.email)
+ Invidious::Database::Users.update_subscriptions(user)
when "import_newpipe_subscriptions"
body = JSON.parse(body)
user.subscriptions += body["subscriptions"].as_a.compact_map do |channel|
@@ -859,7 +859,7 @@ post "/data_control" do |env|
user.subscriptions = get_batch_channels(user.subscriptions, PG_DB, false, false)
- PG_DB.exec("UPDATE users SET feed_needs_update = true, subscriptions = $1 WHERE email = $2", user.subscriptions, user.email)
+ Invidious::Database::Users.update_subscriptions(user)
when "import_newpipe"
Compress::Zip::Reader.open(IO::Memory.new(body)) do |file|
file.each_entry do |entry|
@@ -871,14 +871,14 @@ post "/data_control" do |env|
user.watched += db.query_all("SELECT url FROM streams", as: String).map(&.lchop("https://www.youtube.com/watch?v="))
user.watched.uniq!
- PG_DB.exec("UPDATE users SET watched = $1 WHERE email = $2", user.watched, user.email)
+ Invidious::Database::Users.update_watch_history(user)
user.subscriptions += db.query_all("SELECT url FROM subscriptions", as: String).map(&.lchop("https://www.youtube.com/channel/"))
user.subscriptions.uniq!
user.subscriptions = get_batch_channels(user.subscriptions, PG_DB, false, false)
- PG_DB.exec("UPDATE users SET feed_needs_update = true, subscriptions = $1 WHERE email = $2", user.subscriptions, user.email)
+ Invidious::Database::Users.update_subscriptions(user)
db.close
tempfile.delete
@@ -962,7 +962,7 @@ post "/change_password" do |env|
end
new_password = Crypto::Bcrypt::Password.create(new_password, cost: 10)
- PG_DB.exec("UPDATE users SET password = $1 WHERE email = $2", new_password.to_s, user.email)
+ Invidious::Database::Users.update_password(user, new_password.to_s)
env.redirect referer
end
diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr
index 5d962ab4..2ec510f0 100644
--- a/src/invidious/channels/channels.cr
+++ b/src/invidious/channels/channels.cr
@@ -238,8 +238,7 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil)
if was_insert
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Inserted, updating subscriptions")
- db.exec("UPDATE users SET notifications = array_append(notifications, $1), \
- feed_needs_update = true WHERE $2 = ANY(subscriptions)", video.id, video.ucid)
+ Invidious::Database::Users.add_notification(video)
else
LOGGER.trace("fetch_channel: #{ucid} : video #{video_id} : Updated")
end
@@ -275,9 +274,7 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil)
# so since they don't provide a published date here we can safely ignore them.
if Time.utc - video.published > 1.minute
was_insert = Invidious::Database::ChannelVideos.insert(video)
-
- db.exec("UPDATE users SET notifications = array_append(notifications, $1), \
- feed_needs_update = true WHERE $2 = ANY(subscriptions)", video.id, video.ucid) if was_insert
+ Invidious::Database::Users.add_notification(video) if was_insert
end
end
diff --git a/src/invidious/database/users.cr b/src/invidious/database/users.cr
index aa3b9f85..71650918 100644
--- a/src/invidious/database/users.cr
+++ b/src/invidious/database/users.cr
@@ -39,6 +39,16 @@ module Invidious::Database::Users
# Update (history)
# -------------------
+ def update_watch_history(user : User)
+ request = <<-SQL
+ UPDATE users
+ SET watched = $1
+ WHERE email = $2
+ SQL
+
+ PG_DB.exec(request, user.watched, user.email)
+ end
+
def mark_watched(user : User, vid : String)
request = <<-SQL
UPDATE users
@@ -73,6 +83,16 @@ module Invidious::Database::Users
# Update (channels)
# -------------------
+ def update_subscriptions(user : User)
+ request = <<-SQL
+ UPDATE users
+ SET feed_needs_update = true, subscriptions = $1
+ WHERE email = $2
+ SQL
+
+ PG_DB.exec(request, user.subscriptions, user.email)
+ end
+
def subscribe_channel(user : User, ucid : String)
request = <<-SQL
UPDATE users
@@ -95,6 +115,65 @@ module Invidious::Database::Users
PG_DB.exec(request, ucid, user.email)
end
+ # -------------------
+ # Update (notifs)
+ # -------------------
+
+ def add_notification(video : ChannelVideo)
+ request = <<-SQL
+ UPDATE users
+ SET notifications = array_append(notifications, $1),
+ feed_needs_update = true
+ WHERE $2 = ANY(subscriptions)
+ SQL
+
+ PG_DB.exec(request, video.id, video.ucid)
+ end
+
+ def remove_notification(user : User, vid : String)
+ request = <<-SQL
+ UPDATE users
+ SET notifications = array_remove(notifications, $1)
+ WHERE email = $2
+ SQL
+
+ PG_DB.exec(request, vid, user.email)
+ end
+
+ def clear_notifications(user : User)
+ request = <<-SQL
+ UPDATE users
+ SET notifications = $1, updated = $2
+ WHERE email = $3
+ SQL
+
+ PG_DB.exec(request, [] of String, Time.utc, user)
+ end
+
+ # -------------------
+ # Update (misc)
+ # -------------------
+
+ def update_preferences(user : User)
+ request = <<-SQL
+ UPDATE users
+ SET preferences = $1
+ WHERE email = $2
+ SQL
+
+ PG_DB.exec(request, user.preferences.to_json, user.email)
+ end
+
+ def update_password(user : User, pass : String)
+ request = <<-SQL
+ UPDATE users
+ SET password = $1
+ WHERE email = $2
+ SQL
+
+ PG_DB.exec(request, user.email, pass)
+ end
+
# -------------------
# Select
# -------------------
@@ -126,4 +205,14 @@ module Invidious::Database::Users
return PG_DB.query_one?(request, token, as: User)
end
+
+ def select_notifications(user : User) : Array(String)
+ request = <<-SQL
+ SELECT notifications
+ FROM users
+ WHERE email = $1
+ SQL
+
+ return PG_DB.query_one(request, user.email, as: Array(String))
+ end
end
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index d9b58ebf..62b09f79 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -22,12 +22,11 @@ module Invidious::Routes::API::V1::Authenticated
user = env.get("user").as(User)
begin
- preferences = Preferences.from_json(env.request.body || "{}")
+ user.preferences = Preferences.from_json(env.request.body || "{}")
rescue
- preferences = user.preferences
end
- PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences.to_json, user.email)
+ Invidious::Database::Users.update_preferences(user)
env.response.status_code = 204
end
diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr
index 049ee344..2c648b5a 100644
--- a/src/invidious/routes/embed.cr
+++ b/src/invidious/routes/embed.cr
@@ -137,7 +137,7 @@ module Invidious::Routes::Embed
# end
if notifications && notifications.includes? id
- PG_DB.exec("UPDATE users SET notifications = array_remove(notifications, $1) WHERE email = $2", id, user.as(User).email)
+ Invidious::Database::Users.remove_notification(user.as(User), id)
env.get("user").as(User).notifications.delete(id)
notifications.delete(id)
end
diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr
index 4e7ec9ad..be58dd8d 100644
--- a/src/invidious/routes/feeds.cr
+++ b/src/invidious/routes/feeds.cr
@@ -99,8 +99,7 @@ module Invidious::Routes::Feeds
# we know a user has looked at their feed e.g. in the past 10 minutes,
# they've already seen a video posted 20 minutes ago, and don't need
# to be notified.
- PG_DB.exec("UPDATE users SET notifications = $1, updated = $2 WHERE email = $3", [] of String, Time.utc,
- user.email)
+ Invidious::Database::Users.clear_notifications(user)
user.notifications = [] of String
env.set "user", user
@@ -417,9 +416,7 @@ module Invidious::Routes::Feeds
})
was_insert = Invidious::Database::ChannelVideos.insert(video, with_premiere_timestamp: true)
-
- PG_DB.exec("UPDATE users SET notifications = array_append(notifications, $1),
- feed_needs_update = true WHERE $2 = ANY(subscriptions)", video.id, video.ucid) if was_insert
+ Invidious::Database::Users.add_notification(video) if was_insert
end
end
diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr
index 8f703464..c94fd09b 100644
--- a/src/invidious/routes/login.cr
+++ b/src/invidious/routes/login.cr
@@ -303,8 +303,8 @@ module Invidious::Routes::Login
end
if env.request.cookies["PREFS"]?
- preferences = env.get("preferences").as(Preferences)
- PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences.to_json, user.email)
+ user.preferences = env.get("preferences").as(Preferences)
+ Invidious::Database::Users.update_preferences(user)
cookie = env.request.cookies["PREFS"]
cookie.expires = Time.utc(1990, 1, 1)
@@ -470,8 +470,8 @@ module Invidious::Routes::Login
end
if env.request.cookies["PREFS"]?
- preferences = env.get("preferences").as(Preferences)
- PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences.to_json, user.email)
+ user.preferences = env.get("preferences").as(Preferences)
+ Invidious::Database::Users.update_preferences(user)
cookie = env.request.cookies["PREFS"]
cookie.expires = Time.utc(1990, 1, 1)
diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr
index 15c00700..a832076c 100644
--- a/src/invidious/routes/preferences.cr
+++ b/src/invidious/routes/preferences.cr
@@ -170,11 +170,12 @@ module Invidious::Routes::PreferencesRoute
vr_mode: vr_mode,
show_nick: show_nick,
save_player_pos: save_player_pos,
- }.to_json).to_json
+ }.to_json)
if user = env.get? "user"
user = user.as(User)
- PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences, user.email)
+ user.preferences = preferences
+ Invidious::Database::Users.update_preferences(user)
if CONFIG.admins.includes? user.email
CONFIG.default_user_preferences.default_home = env.params.body["admin_default_home"]?.try &.as(String) || CONFIG.default_user_preferences.default_home
@@ -220,10 +221,10 @@ module Invidious::Routes::PreferencesRoute
end
if CONFIG.domain
- env.response.cookies["PREFS"] = HTTP::Cookie.new(name: "PREFS", domain: "#{CONFIG.domain}", value: URI.encode_www_form(preferences), expires: Time.utc + 2.years,
+ env.response.cookies["PREFS"] = HTTP::Cookie.new(name: "PREFS", domain: "#{CONFIG.domain}", value: URI.encode_www_form(preferences.to_json), expires: Time.utc + 2.years,
secure: secure, http_only: true)
else
- env.response.cookies["PREFS"] = HTTP::Cookie.new(name: "PREFS", value: URI.encode_www_form(preferences), expires: Time.utc + 2.years,
+ env.response.cookies["PREFS"] = HTTP::Cookie.new(name: "PREFS", value: URI.encode_www_form(preferences.to_json), expires: Time.utc + 2.years,
secure: secure, http_only: true)
end
end
@@ -241,18 +242,15 @@ module Invidious::Routes::PreferencesRoute
if user = env.get? "user"
user = user.as(User)
- preferences = user.preferences
- case preferences.dark_mode
+ case user.preferences.dark_mode
when "dark"
- preferences.dark_mode = "light"
+ user.preferences.dark_mode = "light"
else
- preferences.dark_mode = "dark"
+ user.preferences.dark_mode = "dark"
end
- preferences = preferences.to_json
-
- PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences, user.email)
+ Invidious::Database::Users.update_preferences(user)
else
preferences = env.get("preferences").as(Preferences)
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index c1ec0bc6..f7bd7d81 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -80,7 +80,7 @@ module Invidious::Routes::Watch
end
if notifications && notifications.includes? id
- PG_DB.exec("UPDATE users SET notifications = array_remove(notifications, $1) WHERE email = $2", id, user.as(User).email)
+ Invidious::Database::Users.remove_notification(user.as(User), id)
env.get("user").as(User).notifications.delete(id)
notifications.delete(id)
end
diff --git a/src/invidious/users.cr b/src/invidious/users.cr
index 933c451d..efc4dd52 100644
--- a/src/invidious/users.cr
+++ b/src/invidious/users.cr
@@ -224,8 +224,7 @@ def get_subscription_feed(db, user, max_results = 40, page = 1)
limit = max_results.clamp(0, MAX_ITEMS_PER_PAGE)
offset = (page - 1) * limit
- notifications = db.query_one("SELECT notifications FROM users WHERE email = $1", user.email,
- as: Array(String))
+ notifications = Invidious::Database::Users.select_notifications(user)
view_name = "subscriptions_#{sha256(user.email)}"
if user.preferences.notifications_only && !notifications.empty?
@@ -296,8 +295,7 @@ def get_subscription_feed(db, user, max_results = 40, page = 1)
else nil # Ignore
end
- notifications = PG_DB.query_one("SELECT notifications FROM users WHERE email = $1", user.email, as: Array(String))
-
+ notifications = Invidious::Database::Users.select_notifications(user)
notifications = videos.select { |v| notifications.includes? v.id }
videos = videos - notifications
end
From 85cf27119cb230259550bfb795dffcb724ebebf3 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Mon, 6 Dec 2021 17:02:15 +0100
Subject: [PATCH 09/16] Move DB queries related to playlists in a separate
module (3/3)
---
src/invidious.cr | 4 +-
src/invidious/database/playlists.cr | 83 +++++++++++++++++++++++++++++
src/invidious/playlists.cr | 10 ++--
src/invidious/routes/feeds.cr | 5 +-
src/invidious/views/watch.ecr | 2 +-
5 files changed, 94 insertions(+), 10 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index 0149be11..561fc9cf 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -656,7 +656,7 @@ get "/subscription_manager" do |env|
if format == "json"
env.response.content_type = "application/json"
env.response.headers["content-disposition"] = "attachment"
- playlists = PG_DB.query_all("SELECT * FROM playlists WHERE author = $1 AND id LIKE 'IV%' ORDER BY created", user.email, as: InvidiousPlaylist)
+ playlists = Invidious::Database::Playlists.select_like_iv(user.email)
next JSON.build do |json|
json.object do
@@ -672,7 +672,7 @@ get "/subscription_manager" do |env|
json.field "privacy", playlist.privacy.to_s
json.field "videos" do
json.array do
- PG_DB.query_all("SELECT id FROM playlist_videos WHERE plid = $1 ORDER BY array_position($2, index) LIMIT 500", playlist.id, playlist.index, as: String).each do |video_id|
+ Invidious::Database::PlaylistVideos.select_ids(playlist.id, playlist.index, limit: 500).each do |video_id|
json.string video_id
end
end
diff --git a/src/invidious/database/playlists.cr b/src/invidious/database/playlists.cr
index 1dba64f3..950d5f4b 100644
--- a/src/invidious/database/playlists.cr
+++ b/src/invidious/database/playlists.cr
@@ -117,6 +117,39 @@ module Invidious::Database::Playlists
return PG_DB.query_all(request, author, as: InvidiousPlaylist)
end
+ # -------------------
+ # Salect (filtered)
+ # -------------------
+
+ def select_like_iv(email : String) : Array(InvidiousPlaylist)
+ request = <<-SQL
+ SELECT * FROM playlists
+ WHERE author = $1 AND id LIKE 'IV%'
+ ORDER BY created
+ SQL
+
+ PG_DB.query_all(request, email, as: InvidiousPlaylist)
+ end
+
+ def select_not_like_iv(email : String) : Array(InvidiousPlaylist)
+ request = <<-SQL
+ SELECT * FROM playlists
+ WHERE author = $1 AND id NOT LIKE 'IV%'
+ ORDER BY created
+ SQL
+
+ PG_DB.query_all(request, email, as: InvidiousPlaylist)
+ end
+
+ def select_user_created_playlists(email : String) : Array({String, String})
+ request = <<-SQL
+ SELECT id,title FROM playlists
+ WHERE author = $1 AND id LIKE 'IV%'
+ SQL
+
+ PG_DB.query_all(request, email, as: {String, String})
+ end
+
# -------------------
# Misc checks
# -------------------
@@ -148,6 +181,8 @@ end
module Invidious::Database::PlaylistVideos
extend self
+ private alias VideoIndex = Int64 | Array(Int64)
+
# -------------------
# Insert / Delete
# -------------------
@@ -171,4 +206,52 @@ module Invidious::Database::PlaylistVideos
PG_DB.exec(request, index)
end
+
+ # -------------------
+ # Salect
+ # -------------------
+
+ def select(plid : String, index : VideoIndex, offset, limit = 100) : Array(PlaylistVideo)
+ request = <<-SQL
+ SELECT * FROM playlist_videos
+ WHERE plid = $1
+ ORDER BY array_position($2, index)
+ LIMIT $3
+ OFFSET $4
+ SQL
+
+ return PG_DB.query_all(request, plid, index, limit, offset, as: PlaylistVideo)
+ end
+
+ def select_index(plid : String, vid : String) : Int64?
+ request = <<-SQL
+ SELECT index FROM playlist_videos
+ WHERE plid = $1 AND id = $2
+ LIMIT 1
+ SQL
+
+ return PG_DB.query_one?(request, plid, vid, as: Int64)
+ end
+
+ def select_one_id(plid : String, index : VideoIndex) : String?
+ request = <<-SQL
+ SELECT id FROM playlist_videos
+ WHERE plid = $1
+ ORDER BY array_position($2, index)
+ LIMIT 1
+ SQL
+
+ return PG_DB.query_one?(request, plid, index, as: String)
+ end
+
+ def select_ids(plid : String, index : VideoIndex, limit = 500) : Array(String)
+ request = <<-SQL
+ SELECT id FROM playlist_videos
+ WHERE plid = $1
+ ORDER BY array_position($2, index)
+ LIMIT $3
+ SQL
+
+ return PG_DB.query_all(request, plid, index, limit, as: String)
+ end
end
diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr
index f68dc3b0..9128f7db 100644
--- a/src/invidious/playlists.cr
+++ b/src/invidious/playlists.cr
@@ -200,8 +200,8 @@ struct InvidiousPlaylist
json.field "videos" do
json.array do
- if !offset || offset == 0
- index = PG_DB.query_one?("SELECT index FROM playlist_videos WHERE plid = $1 AND id = $2 LIMIT 1", self.id, video_id, as: Int64)
+ if (!offset || offset == 0) && !video_id.nil?
+ index = Invidious::Database::PlaylistVideos.select_index(self.id, video_id)
offset = self.index.index(index) || 0
end
@@ -225,7 +225,8 @@ struct InvidiousPlaylist
end
def thumbnail
- @thumbnail_id ||= PG_DB.query_one?("SELECT id FROM playlist_videos WHERE plid = $1 ORDER BY array_position($2, index) LIMIT 1", self.id, self.index, as: String) || "-----------"
+ # TODO: Get playlist thumbnail from playlist data rather than first video
+ @thumbnail_id ||= Invidious::Database::PlaylistVideos.select_one_id(self.id, self.index) || "-----------"
"/vi/#{@thumbnail_id}/mqdefault.jpg"
end
@@ -411,8 +412,7 @@ def get_playlist_videos(db, playlist, offset, locale = nil, video_id = nil)
end
if playlist.is_a? InvidiousPlaylist
- db.query_all("SELECT * FROM playlist_videos WHERE plid = $1 ORDER BY array_position($2, index) LIMIT 100 OFFSET $3",
- playlist.id, playlist.index, offset, as: PlaylistVideo)
+ Invidious::Database::PlaylistVideos.select(playlist.id, playlist.index, offset, limit: 100)
else
if video_id
initial_data = YoutubeAPI.next({
diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr
index be58dd8d..b58a988f 100644
--- a/src/invidious/routes/feeds.cr
+++ b/src/invidious/routes/feeds.cr
@@ -15,13 +15,14 @@ module Invidious::Routes::Feeds
user = user.as(User)
- items_created = PG_DB.query_all("SELECT * FROM playlists WHERE author = $1 AND id LIKE 'IV%' ORDER BY created", user.email, as: InvidiousPlaylist)
+ # TODO: make a single DB call and separate the items here?
+ items_created = Invidious::Database::Playlists.select_like_iv(user.email)
items_created.map! do |item|
item.author = ""
item
end
- items_saved = PG_DB.query_all("SELECT * FROM playlists WHERE author = $1 AND id NOT LIKE 'IV%' ORDER BY created", user.email, as: InvidiousPlaylist)
+ items_saved = Invidious::Database::Playlists.select_not_like_iv(user.email)
items_saved.map! do |item|
item.author = ""
item
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index b85ea59d..fa4fe083 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -138,7 +138,7 @@ we're going to need to do it here in order to allow for translations.
<% if user %>
- <% playlists = PG_DB.query_all("SELECT id,title FROM playlists WHERE author = $1 AND id LIKE 'IV%'", user.email, as: {String, String}) %>
+ <% playlists = Invidious::Database::Playlists.select_user_created_playlists(user.email) %>
<% if !playlists.empty? %>