This commit is contained in:
JMARyA 2024-10-04 19:39:51 +02:00
parent fdb45f953e
commit ca45a6df02
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
6 changed files with 58 additions and 35 deletions

View file

@ -3,7 +3,7 @@ services:
synthwave: synthwave:
build: . build: .
ports: ports:
- "8080:8080" - "8080:8000"
depends_on: depends_on:
- postgres - postgres
volumes: volumes:
@ -11,10 +11,14 @@ services:
- ./media:/media # Audio files - ./media:/media # Audio files
environment: environment:
- "DATABASE_URL=postgres://user:pass@postgres/synthwave" - "DATABASE_URL=postgres://user:pass@postgres/synthwave"
- "RUST_LOG=info"
- "ROCKET_ADDRESS=0.0.0.0"
postgres: postgres:
image: timescale/timescaledb:latest-pg16 image: timescale/timescaledb:latest-pg16
restart: always restart: always
ports:
- 5432:5432
volumes: volumes:
- ./db:/var/lib/postgresql/data/ - ./db:/var/lib/postgresql/data/
environment: environment:

View file

@ -1,15 +1,16 @@
CREATE TYPE user_role AS ENUM ('regular', 'admin');
CREATE TABLE IF NOT EXISTS user ( CREATE TABLE IF NOT EXISTS users (
username varchar(255) NOT NULL PRIMARY KEY, username VARCHAR(255) NOT NULL PRIMARY KEY,
password text NOT NULL, "password" text NOT NULL,
user_role text NOT NULL DEFAULT 'Regular' CHECK (user_role IN ('regular', 'admin')) user_role user_role NOT NULL DEFAULT 'regular'
); );
CREATE TABLE IF NOT EXISTS user_session ( CREATE TABLE IF NOT EXISTS user_session (
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
token text NOT NULL, token text NOT NULL,
user varchar(255) NOT NULL, "user" varchar(255) NOT NULL,
FOREIGN KEY(user) REFERENCES user(username) FOREIGN KEY("user") REFERENCES users(username)
); );
CREATE TABLE IF NOT EXISTS artist ( CREATE TABLE IF NOT EXISTS artist (
@ -20,7 +21,7 @@ CREATE TABLE IF NOT EXISTS artist (
CREATE TABLE IF NOT EXISTS album ( CREATE TABLE IF NOT EXISTS album (
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
title text NOT NULL, title text NOT NULL,
artist UUID artist UUID,
FOREIGN KEY(artist) REFERENCES artist(id) FOREIGN KEY(artist) REFERENCES artist(id)
); );
@ -28,7 +29,7 @@ CREATE TABLE IF NOT EXISTS track (
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
path text NOT NULL, path text NOT NULL,
title text, title text,
date_added timestampz NOT NULL DEFAULT current_timestamp, date_added timestamptz NOT NULL DEFAULT current_timestamp,
album UUID, album UUID,
artist UUID, artist UUID,
meta jsonb, meta jsonb,
@ -36,23 +37,27 @@ CREATE TABLE IF NOT EXISTS track (
FOREIGN KEY(artist) REFERENCES artist(id) FOREIGN KEY(artist) REFERENCES artist(id)
); );
CREATE TYPE event_kind AS ENUM ('play', 'played', 'stop');
CREATE TABLE IF NOT EXISTS events ( CREATE TABLE IF NOT EXISTS events (
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), id UUID NOT NULL DEFAULT gen_random_uuid(),
time timestampz NOT NULL DEFAULT current_timestamp, time timestamptz NOT NULL DEFAULT current_timestamp,
kind text CHECK (kind IN ('play', 'played', 'stop')), kind event_kind NOT NULL,
user VARCHAR(255) NOT NULL, "user" VARCHAR(255) NOT NULL,
track UUID NOT NULL, track UUID NOT NULL,
FOREIGN KEY(user) REFERENCES user(username), FOREIGN KEY("user") REFERENCES users(username),
FOREIGN KEY(track) REFERENCES track(id) FOREIGN KEY(track) REFERENCES track(id)
); );
SELECT create_hypertable('events', by_range('time')); SELECT create_hypertable('events', by_range('time'));
CREATE TYPE visibility AS ENUM ('public', 'private');
CREATE TABLE IF NOT EXISTS playlist ( CREATE TABLE IF NOT EXISTS playlist (
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
owner VARCHAR(255) NOT NULL, owner VARCHAR(255) NOT NULL,
title text NOT NULL, title text NOT NULL,
visibility text NOT NULL DEFAULT 'private' CHECK (visibility IN ('public', 'private')), visibility visibility NOT NULL DEFAULT 'private',
tracks UUID[] NOT NULL DEFAULT [], tracks UUID[] NOT NULL DEFAULT '{}',
FOREIGN KEY(owner) REFERENCES user(username) FOREIGN KEY(owner) REFERENCES users(username)
); );

View file

@ -6,7 +6,7 @@ use crate::{get_pg, library::user::User};
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct Event { pub struct Event {
pub id: uuid::Uuid, pub id: uuid::Uuid,
pub timestamp: chrono::DateTime<chrono::Utc>, pub time: chrono::DateTime<chrono::Utc>,
pub kind: EventKind, pub kind: EventKind,
pub user: String, pub user: String,
pub track: uuid::Uuid, pub track: uuid::Uuid,
@ -22,7 +22,7 @@ pub enum EventKind {
impl Event { impl Event {
pub async fn create(kind: EventKind, user: &User, track: uuid::Uuid) -> Self { pub async fn create(kind: EventKind, user: &User, track: uuid::Uuid) -> Self {
sqlx::query_as("INSERT INTO events (kind, user, track) VALUES ($1, $2, $3) RETURNING *") sqlx::query_as("INSERT INTO events (kind, \"user\", track) VALUES ($1, $2, $3) RETURNING *")
.bind(kind) .bind(kind)
.bind(&user.username) .bind(&user.username)
.bind(track) .bind(track)
@ -32,7 +32,7 @@ impl Event {
} }
pub async fn get_latest_events_of(u: &User) -> Vec<Self> { pub async fn get_latest_events_of(u: &User) -> Vec<Self> {
sqlx::query_as("SELECT * FROM events WHERE user = $1 ORDER BY time DESC LIMIT 300") sqlx::query_as("SELECT * FROM events WHERE \"user\" = $1 ORDER BY time DESC LIMIT 300")
.bind(&u.username) .bind(&u.username)
.fetch_all(get_pg!()) .fetch_all(get_pg!())
.await .await

View file

@ -3,7 +3,11 @@ use serde_json::json;
use sqlx::prelude::FromRow; use sqlx::prelude::FromRow;
use std::{collections::HashSet, str::FromStr}; use std::{collections::HashSet, str::FromStr};
use crate::{get_pg, library::album::Album, route::ToAPI}; use crate::{
get_pg,
library::album::Album,
route::{to_uuid, ToAPI},
};
use super::{event::Event, metadata::AudioMetadata, user::User}; use super::{event::Event, metadata::AudioMetadata, user::User};
@ -20,11 +24,21 @@ pub struct Track {
impl Track { impl Track {
pub async fn create(data: &serde_json::Map<String, serde_json::Value>) { pub async fn create(data: &serde_json::Map<String, serde_json::Value>) {
sqlx::query("INSERT INTO track (path, title, meta) VALUES ($1, $2, $4)") sqlx::query(
"INSERT INTO track (path, title, meta, album, artist) VALUES ($1, $2, $3, $4, $5)",
)
.bind(data.get("path").unwrap().as_str().unwrap().to_string()) .bind(data.get("path").unwrap().as_str().unwrap().to_string())
.bind(data.get("title").unwrap().as_str().unwrap().to_string()) .bind(data.get("title").unwrap().as_str().unwrap().to_string())
.bind(data.get("meta")) .bind(data.get("meta"))
.fetch_one(get_pg!()) .bind(
data.get("album")
.map(|x| to_uuid(x.as_str().unwrap()).unwrap()),
)
.bind(
data.get("artist")
.map(|x| to_uuid(x.as_str().unwrap()).unwrap()),
)
.execute(get_pg!())
.await .await
.unwrap(); .unwrap();
} }

View file

@ -31,7 +31,7 @@ pub enum UserRole {
impl User { impl User {
pub async fn find(username: &str) -> Option<Self> { pub async fn find(username: &str) -> Option<Self> {
sqlx::query_as("SELECT * FROM user WHERE username = $1") sqlx::query_as("SELECT * FROM users WHERE username = $1")
.bind(username) .bind(username)
.fetch_optional(get_pg!()) .fetch_optional(get_pg!())
.await .await
@ -49,11 +49,11 @@ impl User {
user_role: role, user_role: role,
}; };
sqlx::query("INSERT INTO user (username, password, user_role) VALUES ($1, $2, $3)") sqlx::query("INSERT INTO users (username, \"password\", user_role) VALUES ($1, $2, $3)")
.bind(&u.username) .bind(&u.username)
.bind(&u.password) .bind(&u.password)
.bind(&u.user_role) .bind(&u.user_role)
.fetch_one(get_pg!()) .execute(get_pg!())
.await .await
.unwrap(); .unwrap();
@ -73,7 +73,7 @@ impl User {
/// Change the password of a `User` /// Change the password of a `User`
pub async fn passwd(self, old: &str, new: &str) -> Result<(), ()> { pub async fn passwd(self, old: &str, new: &str) -> Result<(), ()> {
if self.verify_pw(old) { if self.verify_pw(old) {
sqlx::query("UPDATE user SET password = $1 WHERE username = $2;") sqlx::query("UPDATE users SET \"password\" = $1 WHERE username = $2;")
.bind(bcrypt::hash(new, bcrypt::DEFAULT_COST).unwrap()) .bind(bcrypt::hash(new, bcrypt::DEFAULT_COST).unwrap())
.bind(&self.username) .bind(&self.username)
.fetch_one(get_pg!()) .fetch_one(get_pg!())
@ -87,7 +87,7 @@ impl User {
} }
pub async fn find_all() -> Vec<Self> { pub async fn find_all() -> Vec<Self> {
sqlx::query_as("SELECT * FROM user") sqlx::query_as("SELECT * FROM users")
.fetch_all(get_pg!()) .fetch_all(get_pg!())
.await .await
.unwrap() .unwrap()
@ -95,7 +95,7 @@ impl User {
pub async fn session(&self) -> Session { pub async fn session(&self) -> Session {
sqlx::query_as( sqlx::query_as(
"INSERT INTO user_session (token, user) VALUES ($1, $2) RETURNING id, token, user", "INSERT INTO user_session (token, \"user\") VALUES ($1, $2) RETURNING id, token, \"user\"",
) )
.bind(gen_token(64)) .bind(gen_token(64))
.bind(&self.username) .bind(&self.username)

View file

@ -30,7 +30,7 @@ impl<'r> FromRequest<'r> for User {
async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
match request.headers().get_one("token") { match request.headers().get_one("token") {
Some(key) => { Some(key) => {
if let Some(user) = sqlx::query_as("SELECT * FROM user WHERE id = (SELECT user FROM user_session WHERE token = $1)").bind(key).fetch_optional(get_pg!()).await.unwrap() { if let Some(user) = sqlx::query_as("SELECT * FROM users WHERE username = (SELECT \"user\" FROM user_session WHERE token = $1)").bind(key).fetch_optional(get_pg!()).await.unwrap() {
Outcome::Success(user) Outcome::Success(user)
} else { } else {
Outcome::Error((Status::Unauthorized, ())) Outcome::Error((Status::Unauthorized, ()))