commit
326a362eb8
@ -0,0 +1,24 @@
|
|||||||
|
require "./base.cr"
|
||||||
|
|
||||||
|
module Invidious::Database::Annotations
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def insert(id : String, annotations : String)
|
||||||
|
request = <<-SQL
|
||||||
|
INSERT INTO annotations
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT DO NOTHING
|
||||||
|
SQL
|
||||||
|
|
||||||
|
PG_DB.exec(request, id, annotations)
|
||||||
|
end
|
||||||
|
|
||||||
|
def select(id : String) : Annotation?
|
||||||
|
request = <<-SQL
|
||||||
|
SELECT * FROM annotations
|
||||||
|
WHERE id = $1
|
||||||
|
SQL
|
||||||
|
|
||||||
|
return PG_DB.query_one?(request, id, as: Annotation)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,110 @@
|
|||||||
|
require "pg"
|
||||||
|
|
||||||
|
module Invidious::Database
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def check_enum(db, enum_name, struct_type = nil)
|
||||||
|
return # TODO
|
||||||
|
|
||||||
|
if !db.query_one?("SELECT true FROM pg_type WHERE typname = $1", enum_name, as: Bool)
|
||||||
|
LOGGER.info("check_enum: CREATE TYPE #{enum_name}")
|
||||||
|
|
||||||
|
db.using_connection do |conn|
|
||||||
|
conn.as(PG::Connection).exec_all(File.read("config/sql/#{enum_name}.sql"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_table(db, table_name, struct_type = nil)
|
||||||
|
# Create table if it doesn't exist
|
||||||
|
begin
|
||||||
|
db.exec("SELECT * FROM #{table_name} LIMIT 0")
|
||||||
|
rescue ex
|
||||||
|
LOGGER.info("check_table: check_table: CREATE TABLE #{table_name}")
|
||||||
|
|
||||||
|
db.using_connection do |conn|
|
||||||
|
conn.as(PG::Connection).exec_all(File.read("config/sql/#{table_name}.sql"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return if !struct_type
|
||||||
|
|
||||||
|
struct_array = struct_type.type_array
|
||||||
|
column_array = get_column_array(db, table_name)
|
||||||
|
column_types = File.read("config/sql/#{table_name}.sql").match(/CREATE TABLE public\.#{table_name}\n\((?<types>[\d\D]*?)\);/)
|
||||||
|
.try &.["types"].split(",").map(&.strip).reject &.starts_with?("CONSTRAINT")
|
||||||
|
|
||||||
|
return if !column_types
|
||||||
|
|
||||||
|
struct_array.each_with_index do |name, i|
|
||||||
|
if name != column_array[i]?
|
||||||
|
if !column_array[i]?
|
||||||
|
new_column = column_types.select(&.starts_with?(name))[0]
|
||||||
|
LOGGER.info("check_table: ALTER TABLE #{table_name} ADD COLUMN #{new_column}")
|
||||||
|
db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}")
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
# Column doesn't exist
|
||||||
|
if !column_array.includes? name
|
||||||
|
new_column = column_types.select(&.starts_with?(name))[0]
|
||||||
|
db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Column exists but in the wrong position, rotate
|
||||||
|
if struct_array.includes? column_array[i]
|
||||||
|
until name == column_array[i]
|
||||||
|
new_column = column_types.select(&.starts_with?(column_array[i]))[0]?.try &.gsub("#{column_array[i]}", "#{column_array[i]}_new")
|
||||||
|
|
||||||
|
# There's a column we didn't expect
|
||||||
|
if !new_column
|
||||||
|
LOGGER.info("check_table: ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]}")
|
||||||
|
db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE")
|
||||||
|
|
||||||
|
column_array = get_column_array(db, table_name)
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
LOGGER.info("check_table: ALTER TABLE #{table_name} ADD COLUMN #{new_column}")
|
||||||
|
db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}")
|
||||||
|
|
||||||
|
LOGGER.info("check_table: UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}")
|
||||||
|
db.exec("UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}")
|
||||||
|
|
||||||
|
LOGGER.info("check_table: ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE")
|
||||||
|
db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE")
|
||||||
|
|
||||||
|
LOGGER.info("check_table: ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}")
|
||||||
|
db.exec("ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}")
|
||||||
|
|
||||||
|
column_array = get_column_array(db, table_name)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
LOGGER.info("check_table: ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE")
|
||||||
|
db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return if column_array.size <= struct_array.size
|
||||||
|
|
||||||
|
column_array.each do |column|
|
||||||
|
if !struct_array.includes? column
|
||||||
|
LOGGER.info("check_table: ALTER TABLE #{table_name} DROP COLUMN #{column} CASCADE")
|
||||||
|
db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column} CASCADE")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_column_array(db, table_name)
|
||||||
|
column_array = [] of String
|
||||||
|
db.query("SELECT * FROM #{table_name} LIMIT 0") do |rs|
|
||||||
|
rs.column_count.times do |i|
|
||||||
|
column = rs.as(PG::ResultSet).field(i)
|
||||||
|
column_array << column.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return column_array
|
||||||
|
end
|
||||||
|
end
|
@ -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
|
@ -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
|
@ -0,0 +1,257 @@
|
|||||||
|
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(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
|
||||||
|
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
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# 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
|
||||||
|
# -------------------
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
#
|
||||||
|
# This module contains functions related to the "playlist_videos" table.
|
||||||
|
#
|
||||||
|
module Invidious::Database::PlaylistVideos
|
||||||
|
extend self
|
||||||
|
|
||||||
|
private alias VideoIndex = Int64 | Array(Int64)
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# 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
|
@ -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
|
@ -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
|
@ -0,0 +1,218 @@
|
|||||||
|
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 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
|
||||||
|
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 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
|
||||||
|
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
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# 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
|
||||||
|
# -------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
@ -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
|
Loading…
Reference in New Issue