complete
This commit is contained in:
parent
fdb45f953e
commit
ca45a6df02
6 changed files with 58 additions and 35 deletions
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,13 +24,23 @@ 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(
|
||||||
.bind(data.get("path").unwrap().as_str().unwrap().to_string())
|
"INSERT INTO track (path, title, meta, album, artist) VALUES ($1, $2, $3, $4, $5)",
|
||||||
.bind(data.get("title").unwrap().as_str().unwrap().to_string())
|
)
|
||||||
.bind(data.get("meta"))
|
.bind(data.get("path").unwrap().as_str().unwrap().to_string())
|
||||||
.fetch_one(get_pg!())
|
.bind(data.get("title").unwrap().as_str().unwrap().to_string())
|
||||||
.await
|
.bind(data.get("meta"))
|
||||||
.unwrap();
|
.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
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn of_path(path: &str) -> Option<Self> {
|
pub async fn of_path(path: &str) -> Option<Self> {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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, ()))
|
||||||
|
|
Loading…
Reference in a new issue