Skip to content

Commit

Permalink
merge(sql_refactor): better sql; (#9)
Browse files Browse the repository at this point in the history
**Changes:**
- Migrations are now run with `refinery`.
I changed the format to use `,` at the start of lines, this makes it
harder to accidentally leave a trailing `,` on the last line.

- Permissions are now case insensitive & I changed the case used in
responses from `camelCase` to `PascalCase`.

- Changed the name of session to token and moved `POST /login` to `POST
/token` & `DELETE /session` to `DELETE /token`.

- Moved `DangerousUser` to `User` and it no longer stores sensitive
information only the permissions and username.

- I also refactored a bunch of internal stuff, but I am not going to
write that down because I am the only one who is ever going to read this
-_-

> This PR might be a bit too big...

[feat(sql): mv migrations to dir run by
refinery.](91b8872)

[feat(database)!: permissions now case
insensitive.](ce55af0)

[feat(migrations): add migrations of permissions and
sessions.](e59d115)

[refactor(login): moved
api::user::DangerousUserLogin.](e11adb8)

[refactor(api): mv user_login ->
session_write.](97d1958)

[feat(tokens)!: mv /login -> /token & /session ->
/token.](51beb2e)

[refactor(tokens)!: DangerousUser -> User & use token
table.](5345175)

[feat(sql): junction tables & better
sql.](29d37e8)

[fix(dependencies): rm unused dependency
sqlvec.](24b4d2a)

[fix(dependencies): rm unused dependency
rusqlite-from-row.](2522c45)
  • Loading branch information
5-pebbles authored Mar 21, 2024
2 parents 096c4fa + 1313186 commit 844e79e
Show file tree
Hide file tree
Showing 44 changed files with 755 additions and 571 deletions.
225 changes: 175 additions & 50 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ edition = "2021"

[dependencies]
rocket = { version = "0.5.0", features = ["json"] }
rusqlite-from-row = "0.2.2"
strum = { version = "0.26.1", features = ["derive"] }

sqlvec = { version = "0.0.2", features = ["serde"] }
bcrypt = "0.15.0"
uuid = { version = "1.7.0", features = ["v4"] }

utoipa = { version = "4.2.0", features = ["rocket_extras", "yaml"] }
refinery = { version = "0.8.12", features = ["rusqlite"] }

[dependencies.rocket_sync_db_pools]
version = "0.1.0"
Expand Down
2 changes: 1 addition & 1 deletion docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,4 @@
}
}
}
}
}
3 changes: 3 additions & 0 deletions migrations/U10__users.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CREATE TABLE IF NOT EXISTS users (username TEXT PRIMARY KEY
, hash TEXT NOT NULL
)
7 changes: 7 additions & 0 deletions migrations/U11__user_permissions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS user_permissions (id TEXT NOT NULL
, username TEXT NOT NULL
, PRIMARY KEY (id, username)
, FOREIGN KEY (id) REFERENCES permissions(id) ON DELETE CASCADE ON UPDATE CASCADE
, FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE ON UPDATE CASCADE
);

4 changes: 4 additions & 0 deletions migrations/U15__tokens.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS tokens (id TEXT PRIMARY KEY
, username TEXT NOT NULL
, FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE ON UPDATE CASCADE
)
1 change: 1 addition & 0 deletions migrations/U1__pragma.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PRAGMA foreign_keys = ON
6 changes: 6 additions & 0 deletions migrations/U20__invites.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS invites (code TEXT PRIMARY KEY
, permissions TEXT NOT NULL DEFAULT ''
, remaining INTEGER NOT NULL DEFAULT 1
, creator TEXT NOT NULL
, FOREIGN KEY (creator) REFERENCES users(username)
)
7 changes: 7 additions & 0 deletions migrations/U21__invite_permissions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS invite_permissions (id TEXT NOT NULL
, code TEXT NOT NULL
, PRIMARY KEY (id, code)
, FOREIGN KEY (id) REFERENCES permissions(id) ON DELETE CASCADE ON UPDATE CASCADE
, FOREIGN KEY (code) REFERENCES invites(code) ON DELETE CASCADE ON UPDATE CASCADE
);

3 changes: 3 additions & 0 deletions migrations/U25__genres.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CREATE TABLE IF NOT EXISTS genres (
id TEXT PRIMARY KEY
);
4 changes: 4 additions & 0 deletions migrations/U30__artists.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS artists (id TEXT PRIMARY KEY
, name TEXT NOT NULL
, bio TEXT NOT NULL DEFAULT ''
);
6 changes: 6 additions & 0 deletions migrations/U31__artist_genres.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS artist_genres (artist_id TEXT NOT NULL
, genre_id TEXT NOT NULL
, PRIMARY KEY (artist_id, genre_id)
, FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE ON UPDATE CASCADE
, FOREIGN KEY (genre_id) REFERENCES genres(id) ON DELETE CASCADE ON UPDATE CASCADE
);
5 changes: 5 additions & 0 deletions migrations/U35__albums.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS albums (id TEXT PRIMARY KEY
, name TEXT NOT NULL
, release INTEGER NOT NULL DEFAULT 0
, count INTEGER NOT NULL DEFAULT 1
);
6 changes: 6 additions & 0 deletions migrations/U36__artist_albums.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS artist_albums (album_id TEXT NOT NULL
, artist_id TEXT NOT NULL
, PRIMARY KEY (album_id, artist_id)
, FOREIGN KEY (album_id) REFERENCES albums(id) ON DELETE CASCADE ON UPDATE CASCADE
, FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE ON UPDATE CASCADE
);
6 changes: 6 additions & 0 deletions migrations/U37__album_genres.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS album_genres (album_id TEXT NOT NULL
, genre_id TEXT NOT NULL
, PRIMARY KEY (album_id, genre_id)
, FOREIGN KEY (album_id) REFERENCES albums(id) ON DELETE CASCADE ON UPDATE CASCADE
, FOREIGN KEY (genre_id) REFERENCES genres(id) ON DELETE CASCADE ON UPDATE CASCADE
);
6 changes: 6 additions & 0 deletions migrations/U40__tracks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS tracks (id TEXT PRIMARY KEY
, name TEXT NOT NULL
, release INTEGER NOT NULL DEFAULT 0
, duration INTEGER NOT NULL DEFAULT 0
, lyrics TEXT NOT NULL DEFAULT ''
);
6 changes: 6 additions & 0 deletions migrations/U41__album_tracks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS album_tracks (track_id TEXT NOT NULL
, album_id TEXT NOT NULL
, PRIMARY KEY (track_id, album_id)
, FOREIGN KEY (track_id) REFERENCES tracks(id) ON DELETE CASCADE ON UPDATE CASCADE
, FOREIGN KEY (album_id) REFERENCES albums(id) ON DELETE CASCADE ON UPDATE CASCADE
);
6 changes: 6 additions & 0 deletions migrations/U42__track_genres.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS track_genres (track_id TEXT NOT NULL
, genre_id TEXT NOT NULL
, PRIMARY KEY (track_id, genre_id)
, FOREIGN KEY (track_id) REFERENCES tracks(id) ON DELETE CASCADE ON UPDATE CASCADE
, FOREIGN KEY (genre_id) REFERENCES genres(id) ON DELETE CASCADE ON UPDATE CASCADE
);
4 changes: 4 additions & 0 deletions migrations/U5__permissions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS permissions (
id TEXT PRIMARY KEY
);

12 changes: 12 additions & 0 deletions migrations/U6__add_permissions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::database::permissions::Permission;
use strum::IntoEnumIterator;

pub fn migration() -> String {
format!(
"INSERT OR IGNORE INTO permissions (id) VALUES ('{}');",
Permission::iter()
.map(|p| p.to_string())
.collect::<Vec<String>>()
.join("'), ('")
)
}
19 changes: 9 additions & 10 deletions src/api/music/albums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use rocket_sync_db_pools::rusqlite::{params, Error::QueryReturnedNoRows, ToSql};

use crate::{
api::errors::ApiError,
database::{albums::Album, database::Database, permissions::Permission, users::DangerousUser},
database::{albums::Album, database::Database, permissions::Permission, users::User},
};

type Result<T> = std::result::Result<T, ApiError>;

#[post("/album", data = "<album>")]
async fn album_write(db: Database, user: DangerousUser, album: Json<Album>) -> Result<Json<Album>> {
if !user.has_permissions(&[Permission::AlbumWrite]) {
async fn album_write(db: Database, user: User, album: Json<Album>) -> Result<Json<Album>> {
if !user.permissions.contains(&Permission::AlbumWrite) {
Err(Status::Forbidden)?
}

Expand Down Expand Up @@ -55,7 +55,7 @@ async fn album_write(db: Database, user: DangerousUser, album: Json<Album>) -> R
#[get("/album?<id>&<name>&<maxrelease>&<minrelease>&<genres>&<maxcount>&<mincount>&<limit>")]
async fn album_get(
db: Database,
user: DangerousUser,
user: User,
id: Option<String>,
name: Option<String>,
maxrelease: Option<u16>,
Expand All @@ -65,7 +65,7 @@ async fn album_get(
mincount: Option<u16>,
limit: Option<u16>,
) -> Result<Json<Vec<Album>>> {
if !user.has_permissions(&[Permission::AlbumRead]) {
if !user.permissions.contains(&Permission::AlbumRead) {
Err(Status::Forbidden)?
}

Expand Down Expand Up @@ -154,12 +154,14 @@ async fn album_get(
}

#[delete("/album/<id>")]
async fn album_delete(db: Database, user: DangerousUser, id: String) -> Result<()> {
if !user.has_permissions(&[Permission::AlbumDelete]) {
async fn album_delete(db: Database, user: User, id: String) -> Result<()> {
if !user.permissions.contains(&Permission::AlbumDelete) {
Err(Status::Forbidden)?
}

db.run(move |conn| -> Result<()> {
conn.execute_batch("PRAGMA foreign_keys = ON;")?;

let tx = conn.transaction()?;

if let Err(QueryReturnedNoRows) =
Expand All @@ -169,9 +171,6 @@ async fn album_delete(db: Database, user: DangerousUser, id: String) -> Result<(
}

tx.execute("DELETE FROM albums WHERE id = ?", params![id])?;
tx.execute("DELETE FROM album_genres WHERE album_id = ?", params![id])?;
tx.execute("DELETE FROM artist_albums WHERE album_id = ?", params![id])?;
tx.execute("DELETE FROM album_tracks WHERE album_id = ?", params![id])?;

tx.commit()?;

Expand Down
24 changes: 9 additions & 15 deletions src/api/music/artists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,14 @@ use rocket_sync_db_pools::rusqlite::{params, Error::QueryReturnedNoRows, ToSql};

use crate::{
api::errors::ApiError,
database::{
artists::Artist, database::Database, permissions::Permission, users::DangerousUser,
},
database::{artists::Artist, database::Database, permissions::Permission, users::User},
};

type Result<T> = std::result::Result<T, ApiError>;

#[post("/artist", data = "<artist>")]
async fn artist_write(
db: Database,
user: DangerousUser,
artist: Json<Artist>,
) -> Result<Json<Artist>> {
if !user.has_permissions(&[Permission::ArtistWrite]) {
async fn artist_write(db: Database, user: User, artist: Json<Artist>) -> Result<Json<Artist>> {
if !user.permissions.contains(&Permission::ArtistWrite) {
Err(Status::Forbidden)?
}

Expand Down Expand Up @@ -59,13 +53,13 @@ async fn artist_write(
#[get("/artist?<id>&<name>&<genres>&<limit>")]
async fn artist_get(
db: Database,
user: DangerousUser,
user: User,
id: Option<String>,
name: Option<String>,
genres: Option<Json<Vec<String>>>,
limit: Option<u16>,
) -> Result<Json<Vec<Artist>>> {
if !user.has_permissions(&[Permission::ArtistRead]) {
if !user.permissions.contains(&Permission::ArtistRead) {
Err(Status::Forbidden)?
}

Expand Down Expand Up @@ -116,12 +110,14 @@ async fn artist_get(
}

#[delete("/artist/<id>")]
async fn artist_delete(db: Database, user: DangerousUser, id: String) -> Result<()> {
if !user.has_permissions(&[Permission::ArtistDelete]) {
async fn artist_delete(db: Database, user: User, id: String) -> Result<()> {
if !user.permissions.contains(&Permission::ArtistDelete) {
Err(Status::Forbidden)?
}

db.run(move |conn| -> Result<()> {
conn.execute_batch("PRAGMA foreign_keys = ON;")?;

let tx = conn.transaction()?;

if let Err(QueryReturnedNoRows) = tx.query_row(
Expand All @@ -133,8 +129,6 @@ async fn artist_delete(db: Database, user: DangerousUser, id: String) -> Result<
}

tx.execute("DELETE FROM artists WHERE id = ?", params![id])?;
tx.execute("DELETE FROM artist_genres WHERE artist_id = ?", params![id])?;
tx.execute("DELETE FROM artist_albums WHERE artist_id = ?", params![id])?;

tx.commit()?;

Expand Down
28 changes: 9 additions & 19 deletions src/api/music/genres.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::{
api::errors::ApiError,
database::{database::Database, permissions::Permission, users::DangerousUser},
database::{database::Database, permissions::Permission, users::User},
};
use rocket::{fairing::AdHoc, http::Status, serde::json::Json};
use rocket_sync_db_pools::rusqlite::{params, Error::QueryReturnedNoRows, ToSql};

type Result<T> = std::result::Result<T, ApiError>;

#[post("/genre/<genre>")]
async fn genre_write(db: Database, user: DangerousUser, genre: String) -> Result<Json<String>> {
if !user.has_permissions(&[Permission::GenreWrite]) {
async fn genre_write(db: Database, user: User, genre: String) -> Result<Json<String>> {
if !user.permissions.contains(&Permission::GenreWrite) {
Err(Status::Forbidden)?
}
db.run(move |conn| -> Result<Json<String>> {
Expand All @@ -23,11 +23,11 @@ async fn genre_write(db: Database, user: DangerousUser, genre: String) -> Result
#[get("/genre?<genre>&<limit>")]
async fn genre_get(
db: Database,
user: DangerousUser,
user: User,
genre: Option<String>,
limit: Option<u16>,
) -> Result<Json<Vec<String>>> {
if !user.has_permissions(&[Permission::GenreRead]) {
if !user.permissions.contains(&Permission::GenreRead) {
Err(Status::Forbidden)?
}

Expand Down Expand Up @@ -56,12 +56,14 @@ async fn genre_get(
}

#[delete("/genre/<genre>")]
async fn genre_delete(db: Database, user: DangerousUser, genre: String) -> Result<()> {
if !user.has_permissions(&[Permission::GenreDelete]) {
async fn genre_delete(db: Database, user: User, genre: String) -> Result<()> {
if !user.permissions.contains(&Permission::GenreDelete) {
Err(Status::Forbidden)?
}

db.run(move |conn| -> Result<()> {
conn.execute_batch("PRAGMA foreign_keys = ON;")?;

let tx = conn.transaction()?;

if let Err(QueryReturnedNoRows) =
Expand All @@ -73,18 +75,6 @@ async fn genre_delete(db: Database, user: DangerousUser, genre: String) -> Resul
}

tx.execute("DELETE FROM genres WHERE id = ?", params![genre])?;
tx.execute(
"DELETE FROM artist_genres WHERE genre_id = ?",
params![genre],
)?;
tx.execute(
"DELETE FROM album_genres WHERE genre_id = ?",
params![genre],
)?;
tx.execute(
"DELETE FROM track_genres WHERE genre_id = ?",
params![genre],
)?;

tx.commit()?;

Expand Down
18 changes: 9 additions & 9 deletions src/api/music/tracks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use rocket_sync_db_pools::rusqlite::{params, Error::QueryReturnedNoRows, ToSql};

use crate::{
api::errors::ApiError,
database::{database::Database, permissions::Permission, tracks::Track, users::DangerousUser},
database::{database::Database, permissions::Permission, tracks::Track, users::User},
};

type Result<T> = std::result::Result<T, ApiError>;

#[post("/track", data = "<track>")]
async fn track_write(db: Database, user: DangerousUser, track: Json<Track>) -> Result<Json<Track>> {
if !user.has_permissions(&[Permission::TrackWrite]) {
async fn track_write(db: Database, user: User, track: Json<Track>) -> Result<Json<Track>> {
if !user.permissions.contains(&Permission::TrackWrite) {
Err(Status::Forbidden)?
}

Expand Down Expand Up @@ -56,7 +56,7 @@ async fn track_write(db: Database, user: DangerousUser, track: Json<Track>) -> R
#[get("/track?<id>&<name>&<maxrelease>&<minrelease>&<genres>&<albums>&<artists>&<lyrics>&<limit>")]
async fn track_get(
db: Database,
user: DangerousUser,
user: User,
id: Option<String>,
name: Option<String>,
maxrelease: Option<u16>,
Expand All @@ -67,7 +67,7 @@ async fn track_get(
lyrics: Option<String>,
limit: Option<u16>,
) -> Result<Json<Vec<Track>>> {
if !user.has_permissions(&[Permission::TrackRead]) {
if !user.permissions.contains(&Permission::TrackRead) {
Err(Status::Forbidden)?
}
db.run(move |conn| -> Result<Json<Vec<Track>>> {
Expand Down Expand Up @@ -164,12 +164,14 @@ async fn track_get(
}

#[delete("/track/<id>")]
async fn track_delete(db: Database, user: DangerousUser, id: String) -> Result<()> {
if !user.has_permissions(&[Permission::TrackDelete]) {
async fn track_delete(db: Database, user: User, id: String) -> Result<()> {
if !user.permissions.contains(&Permission::TrackDelete) {
Err(Status::Forbidden)?
}

db.run(move |conn| -> Result<()> {
conn.execute_batch("PRAGMA foreign_keys = ON;")?;

let tx = conn.transaction()?;

if let Err(QueryReturnedNoRows) =
Expand All @@ -179,8 +181,6 @@ async fn track_delete(db: Database, user: DangerousUser, id: String) -> Result<(
}

tx.execute("DELETE FROM tracks WHERE id = ?", params![id])?;
tx.execute("DELETE FROM track_genres WHERE track_id = ?", params![id])?;
tx.execute("DELETE FROM album_tracks WHERE track_id = ?", params![id])?;

tx.commit()?;

Expand Down
Loading

0 comments on commit 844e79e

Please sign in to comment.