This commit is contained in:
Miguel da Mota 2024-01-01 03:18:33 +01:00
parent 8a19a733d6
commit 52ef924f12
28 changed files with 577 additions and 170 deletions

252
Cargo.lock generated
View file

@ -30,7 +30,7 @@ dependencies = [
"actix-service",
"actix-utils",
"ahash",
"base64 0.21.5",
"base64",
"bitflags 2.4.1",
"brotli",
"bytes",
@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
dependencies = [
"quote",
"syn 2.0.43",
"syn 2.0.44",
]
[[package]]
@ -179,7 +179,7 @@ dependencies = [
"actix-router",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.44",
]
[[package]]
@ -199,9 +199,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
dependencies = [
"cfg-if",
"getrandom",
@ -251,13 +251,13 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.75"
version = "0.1.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.44",
]
[[package]]
@ -281,12 +281,6 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.5"
@ -299,7 +293,7 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d1c9c15093eb224f0baa400f38fcd713fc1391a6f1c389d886beef146d60a3"
dependencies = [
"base64 0.21.5",
"base64",
"blowfish",
"getrandom",
"subtle",
@ -413,7 +407,7 @@ dependencies = [
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets",
"windows-targets 0.48.5",
]
[[package]]
@ -479,9 +473,9 @@ dependencies = [
[[package]]
name = "deranged"
version = "0.3.10"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
@ -523,7 +517,7 @@ dependencies = [
"diesel_table_macro_syntax",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.44",
]
[[package]]
@ -543,7 +537,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
dependencies = [
"syn 2.0.43",
"syn 2.0.44",
]
[[package]]
@ -650,8 +644,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@ -719,9 +715,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "iana-time-zone"
version = "0.1.58"
version = "0.1.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@ -794,18 +790,18 @@ dependencies = [
]
[[package]]
name = "jwt"
version = "0.16.0"
name = "jsonwebtoken"
version = "9.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f"
checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4"
dependencies = [
"base64 0.13.1",
"crypto-common",
"digest",
"hmac",
"base64",
"js-sys",
"pem",
"ring",
"serde",
"serde_json",
"sha2",
"simple_asn1",
]
[[package]]
@ -913,6 +909,27 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "num-bigint"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.17"
@ -957,7 +974,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
"windows-targets 0.48.5",
]
[[package]]
@ -966,6 +983,16 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pem"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310"
dependencies = [
"base64",
"serde",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -1013,18 +1040,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.71"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a"
dependencies = [
"proc-macro2",
]
@ -1108,6 +1135,20 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "ring"
version = "0.17.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
dependencies = [
"cc",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@ -1167,14 +1208,14 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.44",
]
[[package]]
name = "serde_json"
version = "1.0.108"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9"
dependencies = [
"itoa",
"ryu",
@ -1233,6 +1274,18 @@ dependencies = [
"libc",
]
[[package]]
name = "simple_asn1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
dependencies = [
"num-bigint",
"num-traits",
"thiserror",
"time",
]
[[package]]
name = "slab"
version = "0.4.9"
@ -1258,6 +1311,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "subtle"
version = "2.5.0"
@ -1277,15 +1336,35 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.43"
version = "2.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
checksum = "92d27c2c202598d05175a6dd3af46824b7f747f8d8e9b14c623f19fa5069735d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.44",
]
[[package]]
name = "time"
version = "0.3.31"
@ -1442,6 +1521,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.0"
@ -1478,7 +1563,7 @@ dependencies = [
"diesel_migrations",
"dotenvy",
"hmac",
"jwt",
"jsonwebtoken",
"lazy_static",
"serde",
"serde_json",
@ -1512,7 +1597,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.44",
"wasm-bindgen-shared",
]
@ -1534,7 +1619,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.44",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -1547,11 +1632,11 @@ checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
name = "windows-core"
version = "0.51.1"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets",
"windows-targets 0.52.0",
]
[[package]]
@ -1560,7 +1645,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
"windows-targets 0.48.5",
]
[[package]]
@ -1569,13 +1654,28 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
@ -1584,42 +1684,84 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.31"
@ -1646,7 +1788,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
"syn 2.0.44",
]
[[package]]

View file

@ -17,6 +17,6 @@ dotenvy = "*"
hmac = "0.12"
sha2 = "0.10"
lazy_static = "1"
jwt = "0.16"
jsonwebtoken = "9"
serde = "1"
serde_json = "1"

View file

@ -1,2 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE users;
drop table users;

View file

@ -1,13 +1,13 @@
-- Your SQL goes here
CREATE TABLE IF NOT EXISTS users
create table if not exists users
(
id VARCHAR(24) DEFAULT nanoid(24),
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
password TEXT NOT NULL,
id varchar(24) default nanoid(24),
name varchar(255) not null,
email varchar(255) not null,
password text not null,
updated_at TIMESTAMP,
created_at TIMESTAMP DEFAULT now() NOT NULL,
updated_at timestamp,
created_at timestamp default now() not null,
PRIMARY KEY (id)
primary key (id)
);

View file

@ -1,2 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE playlists;
drop table playlists;

View file

@ -1,16 +1,16 @@
CREATE TABLE IF NOT EXISTS playlists
create table if not exists playlists
(
id VARCHAR(24) DEFAULT nanoid(24),
name VARCHAR(255) NOT NULL,
id varchar(24) default nanoid(24),
name varchar(255) not null,
public bool default false,
creator_id VARCHAR(24) NOT NULL,
creator_id varchar(24) not null,
created_at TIMESTAMP DEFAULT now() NOT NULL,
updated_at TIMESTAMP,
created_at timestamp default now() not null,
updated_at timestamp,
PRIMARY KEY (id),
FOREIGN KEY (creator_id)
REFERENCES users (id)
primary key (id),
foreign key (creator_id)
references users (id)
on delete cascade
);
CREATE INDEX ON playlists (creator_id);

View file

@ -1,2 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE tracks;
drop table tracks;

View file

@ -1,16 +1,16 @@
-- Your SQL goes here
CREATE TABLE IF NOT EXISTS tracks
create table if not exists tracks
(
id VARCHAR(24) DEFAULT nanoid(24),
title VARCHAR(255) NOT NULL,
duration_ms INT NOT NULL DEFAULT 0,
id varchar(24) default nanoid(24),
title varchar(255) not null,
duration_ms int not null default 0,
created_at TIMESTAMP DEFAULT now() NOT NULL,
updated_at TIMESTAMP,
created_at timestamp default now() not null,
updated_at timestamp,
-- music services
spotify_id VARCHAR(21) UNIQUE,
tidal_id VARCHAR(10) UNIQUE,
spotify_id varchar(21) unique,
tidal_id varchar(10) unique,
PRIMARY KEY (id)
primary key (id)
);

View file

@ -1,2 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE playlists_tracks;
drop table playlists_tracks;

View file

@ -1,5 +1,6 @@
CREATE TABLE IF NOT EXISTS playlists_tracks (
playlist_id VARCHAR(24) REFERENCES playlists(id),
track_id VARCHAR(24) REFERENCES tracks(id),
PRIMARY KEY (playlist_id, track_id)
create table if not exists playlists_tracks
(
playlist_id varchar(24) references playlists (id) on delete cascade,
track_id varchar(24) references tracks (id) on delete cascade,
primary key (playlist_id, track_id)
);

View file

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table artists;

View file

@ -0,0 +1,15 @@
-- Your SQL goes here
create table if not exists artists
(
id varchar(24) default nanoid(24),
name varchar(255) NOT NULL,
created_at TIMESTAMP DEFAULT now() NOT NULL,
updated_at TIMESTAMP,
-- music services
spotify_id VARCHAR(21) UNIQUE,
tidal_id VARCHAR(10) UNIQUE,
primary key (id)
);

View file

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table artists_tracks;

View file

@ -0,0 +1,8 @@
-- Your SQL goes here
create table if not exists artists_tracks
(
artist_id varchar(24) references artists (id) on delete cascade,
track_id varchar(24) references tracks (id) on delete cascade,
primary key (artist_id, track_id)
);

View file

@ -1,13 +1,18 @@
use crate::utils::get_jwt_secret;
use jwt::VerifyWithKey;
use std::collections::BTreeMap;
use crate::routes::auth::JWTClaims;
use jsonwebtoken::errors::Error;
use jsonwebtoken::{decode, DecodingKey, EncodingKey, TokenData, Validation};
pub fn get_token(token: &str) -> Result<BTreeMap<String, String>, &str> {
let secret = get_jwt_secret().unwrap();
let claims = token.verify_with_key(&secret);
const JWT_SECRET: &str = "secret";
match claims {
Ok(claims) => Ok(claims),
Err(_e) => return Err("Error parsing token"),
}
pub fn get_token(token: &str) -> Result<TokenData<JWTClaims>, Error> {
let token = decode::<JWTClaims>(&token, &get_decoding_key(), &Validation::default())?;
Ok(token)
}
pub fn get_encoding_key() -> EncodingKey {
EncodingKey::from_secret(JWT_SECRET.as_ref())
}
pub fn get_decoding_key() -> DecodingKey {
DecodingKey::from_secret(JWT_SECRET.as_ref())
}

View file

@ -1,9 +1,10 @@
extern crate core;
mod helpers;
mod middlewares;
mod models;
mod routes;
mod schema;
mod utils;
use crate::helpers::db;
use actix_web::{web, App, HttpServer};
@ -20,7 +21,7 @@ async fn main() -> std::io::Result<()> {
.service(web::scope("/playlists").service(routes::playlists::get_playlist))
.service(routes::auth::login)
.service(routes::me::routes())
.service(web::scope("/users").service(routes::users::get_user))
.service(routes::users::routes())
})
.bind(("127.0.0.1", 9000))?
.run()

View file

@ -9,18 +9,24 @@ pub fn get_user(req: HttpRequest) -> Result<Users, ErrorResponse> {
match authorization {
Some(header) => {
let claims = get_token(header.to_str().unwrap());
let token_data = get_token(header.to_str().unwrap());
match claims {
Ok(claims) => {
let user = Users::find(claims["user_id"].as_str())?;
match token_data {
Ok(token_data) => {
let user = Users::find(token_data.claims.user_id.as_str())?;
Ok(user)
}
Err(e) => {
return Err(ErrorResponse {
message: e.to_string(),
status: StatusCode::INTERNAL_SERVER_ERROR,
return Err(match e.kind() {
jsonwebtoken::errors::ErrorKind::ExpiredSignature => ErrorResponse {
message: "Not Authorized".to_string(),
status: StatusCode::UNAUTHORIZED,
},
_ => ErrorResponse {
message: e.to_string(),
status: StatusCode::INTERNAL_SERVER_ERROR,
},
})
}
}

79
src/models/artists.rs Normal file
View file

@ -0,0 +1,79 @@
use crate::helpers::db;
use crate::schema::{artists, artists_tracks};
use chrono::NaiveDateTime;
use diesel::result::Error;
use diesel::{
AsChangeset, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable,
};
use serde::{Deserialize, Serialize};
#[derive(AsChangeset, Insertable, Queryable, Selectable, Deserialize, Serialize)]
#[diesel(table_name = crate::schema::artists)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Artist {
pub id: String,
pub name: String,
created_at: NaiveDateTime,
updated_at: Option<NaiveDateTime>,
pub spotify_id: Option<String>,
pub tidal_id: Option<String>,
}
#[derive(Debug, Deserialize, Queryable, Serialize)]
pub struct Artists {
pub id: String,
pub title: String,
created_at: NaiveDateTime,
updated_at: Option<NaiveDateTime>,
pub spotify_id: Option<String>,
pub tidal_id: Option<String>,
}
impl Artists {
pub fn find(id: String) -> Result<Self, Error> {
let conn = &mut db::connection()?;
let playlist = artists::table.filter(artists::id.eq(id)).first(conn)?;
Ok(playlist)
}
pub fn create(artist: Artist) -> Result<Self, Error> {
let conn = &mut db::connection()?;
let playlist = diesel::insert_into(artists::table)
.values(Artist::from(artist))
.get_result(conn)?;
Ok(playlist)
}
pub fn find_by_track(track_id: &str) -> Result<Vec<Artists>, Error> {
let conn = &mut db::connection()?;
let tracks: Vec<(String, String)> = artists_tracks::table
.filter(artists_tracks::track_id.eq(track_id))
.get_results::<(String, String)>(conn)?;
let artists = tracks
.into_iter()
.map(|(artist_id, _)| Artists::find(artist_id).unwrap())
.collect::<Vec<Artists>>();
Ok(artists)
}
}
impl Artist {
fn from(artist: Artist) -> Artist {
Artist {
id: artist.id,
name: artist.name,
created_at: artist.created_at,
updated_at: artist.updated_at,
spotify_id: artist.spotify_id,
tidal_id: artist.tidal_id,
}
}
}

View file

@ -1,3 +1,4 @@
pub mod playlist;
pub mod artists;
pub mod playlists;
pub mod tracks;
pub mod user;

View file

@ -1,11 +1,11 @@
use crate::helpers::db;
use crate::models::tracks::Tracks;
use crate::models::tracks::{Track, Tracks, TracksWithArtists};
use crate::models::user::Users;
use crate::schema::playlists;
use chrono::NaiveDateTime;
use diesel::result::Error;
use diesel::{
AsChangeset, EqAll, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable,
AsChangeset, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable,
};
use serde::{Deserialize, Serialize};
@ -22,6 +22,7 @@ pub struct PlaylistCreator {
pub struct Playlist {
pub id: String,
pub name: String,
pub public: bool,
pub creator_id: String,
pub created_at: NaiveDateTime,
@ -32,6 +33,7 @@ pub struct Playlist {
pub struct Playlists {
pub id: String,
pub name: String,
pub public: bool,
pub creator_id: String,
pub created_at: NaiveDateTime,
@ -45,24 +47,36 @@ impl Playlists {
Ok(playlist)
}
pub fn create(playlist: Playlist) -> Result<Self, Error> {
pub fn create(playlist: NewPlaylist) -> Result<Self, Error> {
let conn = &mut db::connection()?;
let playlist = diesel::insert_into(playlists::table)
.values(Playlist::from(playlist))
.values(playlist)
.get_result(conn)?;
Ok(playlist)
}
pub fn find_for_user(user_id: &str) -> Result<Vec<Playlists>, Error> {
pub fn find_for_user(user_id: &str, filter_public: bool) -> Result<Vec<Playlists>, Error> {
let conn = &mut db::connection()?;
let playlists = playlists::table
let mut playlists = playlists::table
.filter(playlists::creator_id.eq(user_id))
.get_results(conn)?;
.into_boxed();
if filter_public {
playlists = playlists.filter(playlists::public.eq(true));
}
let playlists = playlists.get_results(conn)?;
Ok(playlists)
}
pub fn get_tracks(&self) -> Result<Vec<Tracks>, Error> {
pub fn get_tracks(&self) -> Result<Vec<TracksWithArtists>, Error> {
let tracks = Tracks::find_by_playlist(&self.id)?;
let tracks: Vec<TracksWithArtists> = tracks
.into_iter()
.map(Track::with_artists)
.collect::<Result<Vec<TracksWithArtists>, _>>()?;
Ok(tracks)
}
@ -72,11 +86,28 @@ impl Playlists {
}
}
#[derive(Debug, Insertable, Deserialize)]
#[diesel(table_name = playlists)]
pub struct NewPlaylist {
pub name: String,
pub public: bool,
pub creator_id: Option<String>,
}
impl Playlist {
pub fn create(name: &str, public: bool, creator_id: &str) -> NewPlaylist {
NewPlaylist {
name: name.to_string(),
public,
creator_id: Some(creator_id.to_string()),
}
}
fn from(playlist: Playlist) -> Playlist {
Playlist {
id: playlist.id,
name: playlist.name,
public: playlist.public,
creator_id: playlist.creator_id,
created_at: playlist.created_at,

View file

@ -1,4 +1,5 @@
use crate::helpers::db;
use crate::models::artists::Artists;
use crate::schema::tracks;
use chrono::NaiveDateTime;
use diesel::result::Error;
@ -69,11 +70,10 @@ impl Tracks {
Ok(tracks)
}
// pub fn get_artist(&self) -> Result<Artist, Error> {
// let conn = &mut db::connection();
// let artist = ;
// Ok(artist)
// }
pub fn get_artists(&self) -> Result<Vec<Artists>, Error> {
let artists = Artists::find_by_track(&self.id)?;
Ok(artists)
}
}
impl Track {
@ -90,4 +90,31 @@ impl Track {
tidal_id: track.tidal_id,
}
}
pub fn with_artists(track: Tracks) -> Result<TracksWithArtists, Error> {
let artists = track.get_artists()?;
Ok(TracksWithArtists {
id: track.id,
title: track.title,
duration_ms: track.duration_ms,
artists,
spotify_id: track.spotify_id,
tidal_id: track.tidal_id,
})
}
}
#[derive(Deserialize, Serialize)]
pub struct TracksWithArtists {
pub id: String,
pub title: String,
pub duration_ms: i32,
pub artists: Vec<Artists>,
pub spotify_id: Option<String>,
pub tidal_id: Option<String>,
}

View file

@ -1,5 +1,6 @@
use crate::helpers::db;
use crate::schema::users;
use bcrypt::BcryptError;
use chrono::NaiveDateTime;
use diesel::result::Error;
use diesel::{
@ -54,8 +55,8 @@ impl Users {
Ok(user)
}
pub fn verify_password(password: &str, user: &Users) -> bool {
bcrypt::verify(password, &user.password).unwrap()
pub fn verify_password(password: &str, hash: &str) -> Result<bool, BcryptError> {
bcrypt::verify(password, hash)
}
}

View file

@ -1,40 +1,53 @@
use crate::helpers::jwt::get_encoding_key;
use crate::middlewares::error::ErrorResponse;
use crate::models::user::Users;
use crate::utils::get_jwt_secret;
use actix_web::http::StatusCode;
use actix_web::{post, web, HttpResponse};
use jwt::SignWithKey;
use chrono::{Days, Utc};
use jsonwebtoken::{encode, Header};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Deserialize, Serialize)]
pub struct JWTClaims {
pub user_id: String,
pub exp: usize,
}
#[post("/auth/login")]
async fn login(body: web::Json<LoginBody>) -> Result<HttpResponse, ErrorResponse> {
#[derive(Deserialize, Serialize)]
struct Response {
access_token: String,
exp: usize,
}
let user = Users::find_by_email(&body.email);
match user {
Ok(user) => {
let password = Users::verify_password(&body.password, &user);
Ok(user) => match Users::verify_password(&body.password, &user.password) {
Ok(_res) => {
let exp = Utc::now()
.checked_add_days(Days::new(30))
.expect("valid timestamp")
.timestamp();
if password == false {
let claims = JWTClaims {
user_id: user.id.to_string(),
exp: exp as usize,
};
let token = encode(&Header::default(), &claims, &get_encoding_key()).unwrap();
Ok(HttpResponse::Ok().json(Response {
access_token: token,
exp: exp as usize,
}))
}
Err(_e) => {
return Err(ErrorResponse {
message: "Invalid credentials.".to_string(),
status: StatusCode::BAD_REQUEST,
});
})
}
let key = get_jwt_secret().unwrap();
let mut claims = BTreeMap::new();
claims.insert("user_id", &user.id);
let token_str = claims.sign_with_key(&key).unwrap();
Ok(HttpResponse::Ok().json(Response {
access_token: token_str,
}))
}
},
Err(_err) => {
return Err(ErrorResponse {
message: "Invalid credentials.".to_string(),

View file

@ -1,11 +1,14 @@
use crate::middlewares::error::ErrorResponse;
use crate::middlewares::user::get_user;
use crate::models::playlist::Playlists;
use actix_web::{get, web, HttpRequest, HttpResponse, Scope};
use crate::models::playlists::{NewPlaylist, Playlists};
use actix_web::{get, post, web, HttpRequest, HttpResponse, Scope};
use serde::Serialize;
pub fn routes() -> Scope {
web::scope("/me").service(me).service(me_playlists)
web::scope("/me")
.service(me)
.service(me_playlists)
.service(create_playlist)
}
#[derive(Serialize)]
@ -36,6 +39,20 @@ async fn me_playlists(req: HttpRequest) -> Result<HttpResponse, ErrorResponse> {
}
Ok(HttpResponse::Ok().json(Response {
playlists: Playlists::find_for_user(&user.id)?,
playlists: Playlists::find_for_user(&user.id, false)?,
}))
}
#[post("/playlists")]
async fn create_playlist(
req: HttpRequest,
mut playlist: web::Json<NewPlaylist>,
) -> Result<HttpResponse, ErrorResponse> {
let user = get_user(req)?;
playlist.creator_id = Option::from(user.id);
Playlists::create(playlist.into_inner())?;
Ok(HttpResponse::Accepted().finish())
}

View file

@ -1,6 +1,6 @@
use crate::middlewares::error::ErrorResponse;
use crate::models::playlist::{PlaylistCreator, Playlists};
use crate::models::tracks::Tracks;
use crate::models::playlists::{PlaylistCreator, Playlists};
use crate::models::tracks::TracksWithArtists;
use actix_web::{get, web, HttpResponse};
use serde::{Deserialize, Serialize};
@ -9,7 +9,9 @@ struct GetPlaylistResponse {
pub id: String,
pub name: String,
pub creator: PlaylistCreator,
pub tracks: Vec<Tracks>,
pub tracks: Vec<TracksWithArtists>,
pub tracks_count: usize,
pub duration: usize,
}
#[get("/{playlist_id}")]
@ -19,6 +21,14 @@ pub async fn get_playlist(path: web::Path<String>) -> Result<HttpResponse, Error
let creator = playlist.get_creator()?;
let tracks = playlist.get_tracks()?;
let tracks_count = tracks.len();
let duration = tracks
.iter()
.map(|track| track.duration_ms)
.reduce(|a, b| a + b)
.unwrap() as usize;
Ok(HttpResponse::Ok().json(GetPlaylistResponse {
id: playlist.id.to_string(),
name: playlist.name.to_string(),
@ -26,6 +36,8 @@ pub async fn get_playlist(path: web::Path<String>) -> Result<HttpResponse, Error
id: creator.id,
name: creator.name,
},
tracks: playlist.get_tracks()?,
tracks,
tracks_count,
duration,
}))
}

View file

@ -1,27 +1,29 @@
use crate::middlewares::error::ErrorResponse;
use crate::models::playlists::Playlists;
use crate::models::user::Users;
use actix_web::http::StatusCode;
use actix_web::{get, web, HttpResponse, Result};
use actix_web::{get, web, HttpResponse, Result, Scope};
use diesel::result::Error as DBError;
use serde::Serialize;
pub fn routes() -> Scope {
web::scope("/users")
.service(get_user)
.service(get_user_playlists)
}
#[derive(Serialize)]
struct GetUserResponse {
id: String,
name: String,
cover: String,
}
#[get("/{user_id}")]
async fn get_user(path: web::Path<String>) -> Result<HttpResponse, ErrorResponse> {
let user_id = path.into_inner();
let user = Users::find(user_id.as_str());
fn get_a_user(user_id: &str) -> Result<Users, ErrorResponse> {
let user = Users::find(user_id);
match user {
Ok(user) => Ok(HttpResponse::Ok().json(GetUserResponse {
id: user.id,
name: user.name,
})),
Ok(user) => Ok(user),
Err(DBError::NotFound) => {
return Err(ErrorResponse {
message: "User not found".to_string(),
@ -36,3 +38,24 @@ async fn get_user(path: web::Path<String>) -> Result<HttpResponse, ErrorResponse
}
}
}
#[get("/{user_id}")]
async fn get_user(path: web::Path<String>) -> Result<HttpResponse, ErrorResponse> {
let user = get_a_user(&path.into_inner())?;
let user_id = &user.id;
Ok(HttpResponse::Ok().json(GetUserResponse {
id: user_id.to_string(),
name: user.name,
cover: format!("https://assets.vybr.net/users/{}.png", user_id),
}))
}
#[get("/{user_id}/playlists")]
async fn get_user_playlists(path: web::Path<String>) -> Result<HttpResponse, ErrorResponse> {
let user = get_a_user(&path.into_inner())?;
let playlists = Playlists::find_for_user(&user.id, true)?;
Ok(HttpResponse::Ok().json(playlists))
}

View file

@ -1,11 +1,36 @@
// @generated automatically by Diesel CLI.
diesel::table! {
artists (id) {
#[max_length = 24]
id -> Varchar,
#[max_length = 255]
name -> Varchar,
created_at -> Timestamp,
updated_at -> Nullable<Timestamp>,
#[max_length = 21]
spotify_id -> Nullable<Varchar>,
#[max_length = 10]
tidal_id -> Nullable<Varchar>,
}
}
diesel::table! {
artists_tracks (artist_id, track_id) {
#[max_length = 24]
artist_id -> Varchar,
#[max_length = 24]
track_id -> Varchar,
}
}
diesel::table! {
playlists (id) {
#[max_length = 24]
id -> Varchar,
#[max_length = 255]
name -> Varchar,
public -> Bool,
#[max_length = 24]
creator_id -> Varchar,
created_at -> Timestamp,
@ -52,11 +77,15 @@ diesel::table! {
}
}
diesel::joinable!(artists_tracks -> artists (artist_id));
diesel::joinable!(artists_tracks -> tracks (track_id));
diesel::joinable!(playlists -> users (creator_id));
diesel::joinable!(playlists_tracks -> playlists (playlist_id));
diesel::joinable!(playlists_tracks -> tracks (track_id));
diesel::allow_tables_to_appear_in_same_query!(
artists,
artists_tracks,
playlists,
playlists_tracks,
tracks,

View file

@ -1,8 +0,0 @@
use hmac::{Hmac, Mac};
use sha2::Sha256;
use std::error::Error;
pub fn get_jwt_secret() -> Result<Hmac<Sha256>, Box<dyn Error>> {
let key: Hmac<Sha256> = Hmac::new_from_slice(b"secret")?;
Ok(key)
}