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