From 3fe4db0791c5cedf08b0c8a90ab1cb3739d85566 Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Sun, 16 Mar 2025 00:23:40 +0300 Subject: [PATCH] add new services for dropbox and google drive --- Cargo.lock | 62 + crates/dropbox/.gitignore | 1 + crates/dropbox/Cargo.toml | 38 + crates/dropbox/src/client.rs | 91 + crates/dropbox/src/crypto.rs | 22 + crates/dropbox/src/handlers/files.rs | 99 ++ crates/dropbox/src/handlers/mod.rs | 32 + crates/dropbox/src/main.rs | 67 + crates/dropbox/src/repo/dropbox_token.rs | 22 + crates/dropbox/src/repo/mod.rs | 1 + crates/dropbox/src/types/file.rs | 54 + crates/dropbox/src/types/mod.rs | 2 + crates/dropbox/src/types/token.rs | 8 + crates/dropbox/src/xata/dropbox.rs | 14 + crates/dropbox/src/xata/dropbox_path.rs | 14 + crates/dropbox/src/xata/dropbox_token.rs | 25 + crates/dropbox/src/xata/mod.rs | 5 + crates/dropbox/src/xata/track.rs | 31 + crates/dropbox/src/xata/user.rs | 13 + crates/googledrive/.env.example | 9 + crates/googledrive/.gitignore | 1 + crates/googledrive/Cargo.toml | 39 + crates/googledrive/src/client.rs | 97 ++ crates/googledrive/src/crypto.rs | 22 + crates/googledrive/src/handlers/files.rs | 82 + crates/googledrive/src/handlers/mod.rs | 33 + crates/googledrive/src/main.rs | 77 + .../src/repo/google_drive_token.rs | 22 + crates/googledrive/src/repo/mod.rs | 1 + crates/googledrive/src/types/file.rs | 32 + crates/googledrive/src/types/mod.rs | 2 + crates/googledrive/src/types/token.rs | 10 + crates/googledrive/src/xata/google_drive.rs | 14 + .../googledrive/src/xata/google_drive_path.rs | 14 + .../src/xata/google_drive_token.rs | 25 + crates/googledrive/src/xata/mod.rs | 5 + crates/googledrive/src/xata/track.rs | 31 + crates/googledrive/src/xata/user.rs | 13 + .../rocksky-auth/.xata/migrations/.ledger | 42 + .../migrations/mig_cv8tro2glbhgau6cq5j0.json | 57 + .../migrations/mig_cv8ts6o1g95li4qghh5g.json | 57 + .../migrations/mig_cv8tsfkld6k2hsabcgqg.json | 57 + .../migrations/mig_cv8tt0g1g95li4qghh6g.json | 19 + .../migrations/mig_cv8ttl4ld6k2hsabcgsg.json | 57 + .../migrations/mig_cv8tu7sld6k2hsabcgtg.json | 57 + .../migrations/mig_cv8tufsld6k2hsabcgug.json | 57 + .../migrations/mig_cv8tutiglbhgau6cq5n0.json | 57 + .../migrations/mig_cv8tv8iglbhgau6cq5p0.json | 18 + .../migrations/mig_cv8u07aglbhgau6cq5q0.json | 57 + .../migrations/mig_cv8u0daglbhgau6cq5r0.json | 57 + .../migrations/mig_cv8u5cqglbhgau6cq61g.json | 24 + .../migrations/mig_cv8u6n01g95li4qghha0.json | 24 + .../migrations/mig_cv8u7uqglbhgau6cq62g.json | 24 + .../migrations/mig_cv8u8j2glbhgau6cq63g.json | 30 + .../migrations/mig_cv8ub7cld6k2hsabch2g.json | 30 + .../migrations/mig_cv8ue4kld6k2hsabch3g.json | 30 + .../migrations/mig_cv8ueoaglbhgau6cq66g.json | 30 + .../migrations/mig_cv8ufnqglbhgau6cq67g.json | 30 + .../migrations/mig_cv8uh4aglbhgau6cq68g.json | 30 + .../migrations/mig_cv8ujhqglbhgau6cq69g.json | 30 + .../migrations/mig_cv8ukrg1g95li4qghhh0.json | 30 + .../migrations/mig_cv8ulakld6k2hsabch4g.json | 24 + .../migrations/mig_cv8umjo1g95li4qghhk0.json | 57 + .../migrations/mig_cv8un7g1g95li4qghhl0.json | 30 + .../migrations/mig_cv8unpo1g95li4qghhm0.json | 30 + .../migrations/mig_cv8up2aglbhgau6cq6bg.json | 25 + .../migrations/mig_cv8upbcld6k2hsabch6g.json | 20 + .../migrations/mig_cv8uq1sld6k2hsabch7g.json | 30 + .../migrations/mig_cv8uqh2glbhgau6cq6d0.json | 30 + .../migrations/mig_cv8ur1kld6k2hsabch8g.json | 24 + .../migrations/mig_cv8urh4ld6k2hsabch9g.json | 24 + .../migrations/mig_cv8us9g1g95li4qghhq0.json | 57 + .../migrations/mig_cv8usig1g95li4qghhr0.json | 57 + .../migrations/mig_cv8utv81g95li4qghhs0.json | 57 + .../migrations/mig_cv8uugqglbhgau6cq6e0.json | 25 + .../migrations/mig_cv8uv2iglbhgau6cq6f0.json | 30 + .../migrations/mig_cv8uveiglbhgau6cq6h0.json | 30 + .../migrations/mig_cv8v8gcld6k2hsabcheg.json | 57 + .../migrations/mig_cv8v92aglbhgau6cq6n0.json | 30 + .../migrations/mig_cv8v9faglbhgau6cq6o0.json | 24 + .../migrations/mig_cv8v9sqglbhgau6cq6p0.json | 30 + rockskyapi/rocksky-auth/src/dropbox/app.ts | 57 +- .../rocksky-auth/src/googledrive/app.ts | 62 +- .../rocksky-auth/src/schema/dropbox-tokens.ts | 10 + .../src/schema/google-drive-tokens.ts | 10 + rockskyapi/rocksky-auth/src/xata.ts | 1460 ++++++++++++++--- rockskyweb/bun.lock | 5 + rockskyweb/package.json | 1 + rockskyweb/src/App.tsx | 4 + rockskyweb/src/components/Icons/Dropbox.tsx | 15 + .../src/components/Icons/GoogleDrive.tsx | 35 + .../ScrobblesAreaChart/ScrobblesAreaChart.tsx | 8 +- .../src/layouts/CloudDrive/CloudDrive.tsx | 77 + rockskyweb/src/layouts/CloudDrive/index.tsx | 3 + rockskyweb/src/layouts/Main.tsx | 4 +- .../src/layouts/SpotifyLogin/SpotifyLogin.tsx | 2 - rockskyweb/src/pages/dropbox/Dropbox.tsx | 7 + rockskyweb/src/pages/dropbox/index.tsx | 3 + .../src/pages/googledrive/GoogleDrive.tsx | 7 + rockskyweb/src/pages/googledrive/index.tsx | 3 + 100 files changed, 4296 insertions(+), 202 deletions(-) create mode 100644 crates/dropbox/.gitignore create mode 100644 crates/dropbox/Cargo.toml create mode 100644 crates/dropbox/src/client.rs create mode 100644 crates/dropbox/src/crypto.rs create mode 100644 crates/dropbox/src/handlers/files.rs create mode 100644 crates/dropbox/src/handlers/mod.rs create mode 100644 crates/dropbox/src/main.rs create mode 100644 crates/dropbox/src/repo/dropbox_token.rs create mode 100644 crates/dropbox/src/repo/mod.rs create mode 100644 crates/dropbox/src/types/file.rs create mode 100644 crates/dropbox/src/types/mod.rs create mode 100644 crates/dropbox/src/types/token.rs create mode 100644 crates/dropbox/src/xata/dropbox.rs create mode 100644 crates/dropbox/src/xata/dropbox_path.rs create mode 100644 crates/dropbox/src/xata/dropbox_token.rs create mode 100644 crates/dropbox/src/xata/mod.rs create mode 100644 crates/dropbox/src/xata/track.rs create mode 100644 crates/dropbox/src/xata/user.rs create mode 100644 crates/googledrive/.env.example create mode 100644 crates/googledrive/.gitignore create mode 100644 crates/googledrive/Cargo.toml create mode 100644 crates/googledrive/src/client.rs create mode 100644 crates/googledrive/src/crypto.rs create mode 100644 crates/googledrive/src/handlers/files.rs create mode 100644 crates/googledrive/src/handlers/mod.rs create mode 100644 crates/googledrive/src/main.rs create mode 100644 crates/googledrive/src/repo/google_drive_token.rs create mode 100644 crates/googledrive/src/repo/mod.rs create mode 100644 crates/googledrive/src/types/file.rs create mode 100644 crates/googledrive/src/types/mod.rs create mode 100644 crates/googledrive/src/types/token.rs create mode 100644 crates/googledrive/src/xata/google_drive.rs create mode 100644 crates/googledrive/src/xata/google_drive_path.rs create mode 100644 crates/googledrive/src/xata/google_drive_token.rs create mode 100644 crates/googledrive/src/xata/mod.rs create mode 100644 crates/googledrive/src/xata/track.rs create mode 100644 crates/googledrive/src/xata/user.rs create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tro2glbhgau6cq5j0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ts6o1g95li4qghh5g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tsfkld6k2hsabcgqg.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tt0g1g95li4qghh6g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ttl4ld6k2hsabcgsg.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tu7sld6k2hsabcgtg.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tufsld6k2hsabcgug.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tutiglbhgau6cq5n0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tv8iglbhgau6cq5p0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u07aglbhgau6cq5q0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u0daglbhgau6cq5r0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u5cqglbhgau6cq61g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u6n01g95li4qghha0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u7uqglbhgau6cq62g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u8j2glbhgau6cq63g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ub7cld6k2hsabch2g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ue4kld6k2hsabch3g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ueoaglbhgau6cq66g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ufnqglbhgau6cq67g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uh4aglbhgau6cq68g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ujhqglbhgau6cq69g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ukrg1g95li4qghhh0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ulakld6k2hsabch4g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8umjo1g95li4qghhk0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8un7g1g95li4qghhl0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8unpo1g95li4qghhm0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8up2aglbhgau6cq6bg.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8upbcld6k2hsabch6g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uq1sld6k2hsabch7g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uqh2glbhgau6cq6d0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ur1kld6k2hsabch8g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8urh4ld6k2hsabch9g.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8us9g1g95li4qghhq0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8usig1g95li4qghhr0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8utv81g95li4qghhs0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uugqglbhgau6cq6e0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uv2iglbhgau6cq6f0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uveiglbhgau6cq6h0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v8gcld6k2hsabcheg.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v92aglbhgau6cq6n0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9faglbhgau6cq6o0.json create mode 100644 rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9sqglbhgau6cq6p0.json create mode 100644 rockskyapi/rocksky-auth/src/schema/dropbox-tokens.ts create mode 100644 rockskyapi/rocksky-auth/src/schema/google-drive-tokens.ts create mode 100644 rockskyweb/src/components/Icons/Dropbox.tsx create mode 100644 rockskyweb/src/components/Icons/GoogleDrive.tsx create mode 100644 rockskyweb/src/layouts/CloudDrive/CloudDrive.tsx create mode 100644 rockskyweb/src/layouts/CloudDrive/index.tsx create mode 100644 rockskyweb/src/pages/dropbox/Dropbox.tsx create mode 100644 rockskyweb/src/pages/dropbox/index.tsx create mode 100644 rockskyweb/src/pages/googledrive/GoogleDrive.tsx create mode 100644 rockskyweb/src/pages/googledrive/index.tsx diff --git a/Cargo.lock b/Cargo.lock index 389a7176..82135fc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1256,6 +1256,29 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dropbox" +version = "0.1.0" +dependencies = [ + "actix-web", + "aes", + "anyhow", + "async-nats", + "chrono", + "ctr", + "dotenv", + "hex", + "jsonwebtoken", + "owo-colors", + "redis", + "reqwest", + "serde", + "serde_json", + "sqlx", + "tokio", + "tokio-stream", +] + [[package]] name = "duckdb" version = "1.2.0" @@ -1615,6 +1638,30 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "googledrive" +version = "0.1.0" +dependencies = [ + "actix-web", + "aes", + "anyhow", + "async-nats", + "chrono", + "ctr", + "dotenv", + "hex", + "jsonwebtoken", + "owo-colors", + "redis", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", + "sqlx", + "tokio", + "tokio-stream", +] + [[package]] name = "h2" version = "0.3.26" @@ -3528,11 +3575,13 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", + "tokio-util", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", "windows-registry", @@ -4893,6 +4942,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.77" diff --git a/crates/dropbox/.gitignore b/crates/dropbox/.gitignore new file mode 100644 index 00000000..2eea525d --- /dev/null +++ b/crates/dropbox/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/crates/dropbox/Cargo.toml b/crates/dropbox/Cargo.toml new file mode 100644 index 00000000..98f8f3b3 --- /dev/null +++ b/crates/dropbox/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "dropbox" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +actix-web = "4.9.0" +aes = "0.8.4" +anyhow = "1.0.96" +async-nats = "0.39.0" +chrono = { version = "0.4.39", features = ["serde"] } +ctr = "0.9.2" +dotenv = "0.15.0" +hex = "0.4.3" +jsonwebtoken = "9.3.1" +owo-colors = "4.1.0" +redis = "0.29.0" +reqwest = { version = "0.12.12", features = [ + "rustls-tls", + "json", + "multipart", + "stream", +], default-features = false } +serde = { version = "1.0.217", features = ["derive"] } +serde_json = "1.0.139" +sqlx = { version = "0.8.3", features = [ + "runtime-tokio", + "tls-rustls", + "postgres", + "chrono", + "derive", + "macros", +] } +tokio = { version = "1.43.0", features = ["full"] } +tokio-stream = { version = "0.1.17", features = ["full"] } diff --git a/crates/dropbox/src/client.rs b/crates/dropbox/src/client.rs new file mode 100644 index 00000000..471e4818 --- /dev/null +++ b/crates/dropbox/src/client.rs @@ -0,0 +1,91 @@ +use std::env; + +use actix_web::HttpResponse; +use anyhow::Error; +use reqwest::Client; +use serde_json::json; + +use crate::types::{file::{EntryList, TemporaryLink}, token::AccessToken}; + +pub const BASE_URL: &str = "https://api.dropboxapi.com/2"; +pub const CONTENT_URL: &str = "https://content.dropboxapi.com/2"; + +pub async fn get_access_token(refresh_token: &str) -> Result { + let client = Client::new(); + let res = client.post("https://api.dropboxapi.com/oauth2/token") + .header("Content-Type", "application/x-www-form-urlencoded") + .query(&[ + ("grant_type", "refresh_token"), + ("refresh_token", refresh_token), + ("client_id", &env::var("DROPBOX_CLIENT_ID")?), + ("client_secret", &env::var("DROPBOX_CLIENT_SECRET")?), + ]) + .send() + .await?; + + Ok(res.json::().await?) +} + +pub struct DropboxClient { + pub access_token: String, +} + +impl DropboxClient { + pub async fn new(refresh_token: &str) -> Result { + let res = get_access_token(refresh_token).await?; + Ok(DropboxClient { + access_token: res.access_token, + }) + } + + pub async fn get_files(&self, path: &str) -> Result { + let client = Client::new(); + let res = client.post(&format!("{}/files/list_folder", BASE_URL)) + .bearer_auth(&self.access_token) + .json(&json!({ + "path": path, + "recursive": false, + "include_media_info": true, + "include_deleted": false, + "include_has_explicit_shared_members": false, + "include_mounted_folders": true, + "include_non_downloadable_files": true, + })) + .send() + .await?; + + Ok(res.json::().await?) + } + + pub async fn download_file(&self, path: &str) -> Result { + let client = Client::new(); + let res = client.post(&format!("{}/files/download", CONTENT_URL)) + .bearer_auth(&self.access_token) + .header("Dropbox-API-Arg", &json!({ "path": path }).to_string()) + .send() + .await?; + + let mut actix_response = HttpResponse::Ok(); + + // Forward headers + for (key, value) in res.headers().iter() { + actix_response.append_header((key.as_str(), value.to_str().unwrap_or(""))); + } + + // Forward body + let body = res.bytes_stream(); + + Ok(actix_response.streaming(body)) + } + + pub async fn get_temporary_link(&self, path: &str) -> Result { + let client = Client::new(); + let res = client.post(&format!("{}/files/get_temporary_link", BASE_URL)) + .bearer_auth(&self.access_token) + .json(&json!({ "path": path })) + .send() + .await?; + + Ok(res.json::().await?) + } +} diff --git a/crates/dropbox/src/crypto.rs b/crates/dropbox/src/crypto.rs new file mode 100644 index 00000000..52650605 --- /dev/null +++ b/crates/dropbox/src/crypto.rs @@ -0,0 +1,22 @@ +use std::env; + +use aes::{ + cipher::{KeyIvInit, StreamCipher}, + Aes256, +}; +use anyhow::Error; +use hex::decode; + +type Aes256Ctr = ctr::Ctr64BE; + +pub fn decrypt_aes_256_ctr(encrypted_text: &str, key: &[u8]) -> Result { + let iv = decode(env::var("SPOTIFY_ENCRYPTION_IV")?)?; + let ciphertext = decode(encrypted_text)?; + + let mut cipher = + Aes256Ctr::new_from_slices(key, &iv).map_err(|_| Error::msg("Invalid key or IV"))?; + let mut decrypted_data = ciphertext.clone(); + cipher.apply_keystream(&mut decrypted_data); + + Ok(String::from_utf8(decrypted_data)?) +} diff --git a/crates/dropbox/src/handlers/files.rs b/crates/dropbox/src/handlers/files.rs new file mode 100644 index 00000000..8c9fdf8a --- /dev/null +++ b/crates/dropbox/src/handlers/files.rs @@ -0,0 +1,99 @@ +use std::{env, sync::{Arc, Mutex}}; + +use actix_web::{web, HttpRequest, HttpResponse}; +use anyhow::Error; +use sqlx::{Pool, Postgres}; +use tokio_stream::StreamExt; + +use crate::{ + client::DropboxClient, + crypto::decrypt_aes_256_ctr, + read_payload, + repo::dropbox_token::find_dropbox_refresh_token, + types::file::{DownloadFileParams, GetFilesAtParams, GetFilesParams}, +}; + +pub const MUSIC_DIR: &str = "/Music"; + +pub async fn get_files(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc>>) -> Result { + let body = read_payload!(payload); + let params = serde_json::from_slice::(&body)?; + let pool = pool.lock().unwrap(); + // let did = "did:plc:7vdlgi2bflelz7mmuxoqjfcr"; + let refresh_token = find_dropbox_refresh_token(&pool, ¶ms.did).await?; + + if refresh_token.is_none() { + return Ok(HttpResponse::Unauthorized().finish()); + } + + let refresh_token = decrypt_aes_256_ctr( + &refresh_token.unwrap(), + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? + )?; + + let client = DropboxClient::new(&refresh_token).await?; + let entries = client.get_files(MUSIC_DIR).await?; + + Ok(HttpResponse::Ok().json(web::Json(entries))) +} + +pub async fn get_files_at(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc>>) -> Result { + let body = read_payload!(payload); + let params = serde_json::from_slice::(&body)?; + let pool = pool.lock().unwrap(); + let refresh_token = find_dropbox_refresh_token(&pool, ¶ms.did).await?; + + if refresh_token.is_none() { + return Ok(HttpResponse::Unauthorized().finish()); + } + + let refresh_token = decrypt_aes_256_ctr( + &refresh_token.unwrap(), + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? + )?; + + let client = DropboxClient::new(&refresh_token).await?; + let entries = client.get_files(¶ms.path).await?; + + Ok(HttpResponse::Ok().json(web::Json(entries))) +} + +pub async fn download_file(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc>>) -> Result { + let body = read_payload!(payload); + let params = serde_json::from_slice::(&body)?; + let pool = pool.lock().unwrap(); + let refresh_token = find_dropbox_refresh_token(&pool, ¶ms.did).await?; + + if refresh_token.is_none() { + return Ok(HttpResponse::Unauthorized().finish()); + } + + let refresh_token = decrypt_aes_256_ctr( + &refresh_token.unwrap(), + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? + )?; + + let client = DropboxClient::new(&refresh_token).await?; + client.download_file(¶ms.path).await +} + +pub async fn get_temporary_link(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc>>) -> Result { + let body = read_payload!(payload); + let params = serde_json::from_slice::(&body)?; + let pool = pool.lock().unwrap(); + let refresh_token = find_dropbox_refresh_token(&pool, ¶ms.did).await?; + + if refresh_token.is_none() { + return Ok(HttpResponse::Unauthorized().finish()); + } + + let refresh_token = decrypt_aes_256_ctr( + &refresh_token.unwrap(), + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? + )?; + + let client = DropboxClient::new(&refresh_token).await?; + let temporary_link = client.get_temporary_link(¶ms.path).await?; + + Ok(HttpResponse::Ok().json(web::Json(temporary_link))) +} diff --git a/crates/dropbox/src/handlers/mod.rs b/crates/dropbox/src/handlers/mod.rs new file mode 100644 index 00000000..eda3d373 --- /dev/null +++ b/crates/dropbox/src/handlers/mod.rs @@ -0,0 +1,32 @@ +use std::sync::{Arc, Mutex}; + +use actix_web::{web, HttpRequest, HttpResponse}; +use anyhow::Error; +use files::{download_file, get_files, get_files_at, get_temporary_link}; +use sqlx::{Pool, Postgres}; + +pub mod files; + +#[macro_export] +macro_rules! read_payload { + ($payload:expr) => {{ + let mut body = Vec::new(); + while let Some(chunk) = $payload.next().await { + match chunk { + Ok(bytes) => body.extend_from_slice(&bytes), + Err(err) => return Err(err.into()), + } + } + body + }}; +} + +pub async fn handle(method: &str, payload: &mut web::Payload, req: &HttpRequest, conn: Arc>>) -> Result { + match method { + "dropbox.getFiles" => get_files(payload, req, conn.clone()).await, + "dropbox.getFilesAt" => get_files_at(payload, req, conn.clone()).await, + "dropbox.downloadFile" => download_file(payload, req, conn.clone()).await, + "dropbox.getTemporaryLink" => get_temporary_link(payload, req, conn.clone()).await, + _ => return Err(anyhow::anyhow!("Method not found")), + } +} diff --git a/crates/dropbox/src/main.rs b/crates/dropbox/src/main.rs new file mode 100644 index 00000000..17ea4e38 --- /dev/null +++ b/crates/dropbox/src/main.rs @@ -0,0 +1,67 @@ +use std::{env, sync::{Arc, Mutex}}; +use actix_web::{get, post, web::{self, Data}, App, HttpRequest, HttpResponse, HttpServer, Responder}; +use anyhow::Error; +use dotenv::dotenv; +use handlers::handle; +use owo_colors::OwoColorize; +use serde_json::json; +use sqlx::{postgres::PgPoolOptions, Pool, Postgres}; + +pub mod xata; +pub mod crypto; +pub mod handlers; +pub mod repo; +pub mod types; +pub mod client; + +#[get("/")] +async fn index(_req: HttpRequest) -> HttpResponse { + HttpResponse::Ok().json(json!({ + "server": "Rocksky Dropbox Server", + "version": "0.1.0", + })) +} + +#[post("/{method}")] +async fn call_method( + data: web::Data>>>, + mut payload: web::Payload, + req: HttpRequest) -> Result { + let method = req.match_info().get("method").unwrap_or("unknown"); + println!("Method: {}", method.bright_green()); + + let conn = data.get_ref().clone(); + handle(method, &mut payload, &req, conn).await + .map_err(actix_web::error::ErrorInternalServerError) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + dotenv().ok(); + + let host = env::var("DROPBOX_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); + let port = env::var("DROPBOX_PORT").unwrap_or_else(|_| "7881".to_string()); + let addr = format!("{}:{}", host, port); + + let url = format!("http://{}", addr); + println!("Listening on {}", url.bright_green()); + + let pool = PgPoolOptions::new().max_connections(5).connect(&env::var("XATA_POSTGRES_URL")?).await?; + let conn = Arc::new(Mutex::new(pool)); + + let conn = conn.clone(); + HttpServer::new(move || { + App::new() + .app_data(Data::new(conn.clone())) + .service(index) + .service(call_method) + }) + .bind(&addr)? + .run() + .await + .map_err(Error::new)?; + + + Ok(()) +} + diff --git a/crates/dropbox/src/repo/dropbox_token.rs b/crates/dropbox/src/repo/dropbox_token.rs new file mode 100644 index 00000000..8096fc05 --- /dev/null +++ b/crates/dropbox/src/repo/dropbox_token.rs @@ -0,0 +1,22 @@ +use anyhow::Error; +use sqlx::{Pool, Postgres}; + +use crate::xata::dropbox_token::DropboxTokenWithDid; + +pub async fn find_dropbox_refresh_token(pool: &Pool, did: &str) -> Result, Error> { + let results: Vec = sqlx::query_as(r#" + SELECT * FROM dropbox d + LEFT JOIN users u ON d.user_id = u.xata_id + LEFT JOIN dropbox_tokens dt ON d.dropbox_token_id = dt.xata_id + WHERE u.did = $1 + "#) + .bind(did) + .fetch_all(pool) + .await?; + + if results.len() == 0 { + return Ok(None); + } + + Ok(Some(results[0].refresh_token.clone())) +} diff --git a/crates/dropbox/src/repo/mod.rs b/crates/dropbox/src/repo/mod.rs new file mode 100644 index 00000000..ec7354ac --- /dev/null +++ b/crates/dropbox/src/repo/mod.rs @@ -0,0 +1 @@ +pub mod dropbox_token; diff --git a/crates/dropbox/src/types/file.rs b/crates/dropbox/src/types/file.rs new file mode 100644 index 00000000..67b48535 --- /dev/null +++ b/crates/dropbox/src/types/file.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Entry { + #[serde(rename = ".tag")] + #[serde(skip_serializing_if = "Option::is_none")] + pub tag: Option, + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub client_modified: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub server_modified: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rev: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, + pub path_display: String, + pub path_lower: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub content_hash: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub is_downloadable: Option, + pub id: String, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct EntryList { + pub entries: Vec, + pub cursor: String, + pub has_more: bool, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct GetFilesParams { + pub did: String, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct GetFilesAtParams { + pub did: String, + pub path: String, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct DownloadFileParams { + pub did: String, + pub path: String, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct TemporaryLink { + pub metadata: Entry, + pub link: String, +} diff --git a/crates/dropbox/src/types/mod.rs b/crates/dropbox/src/types/mod.rs new file mode 100644 index 00000000..1d7f4d35 --- /dev/null +++ b/crates/dropbox/src/types/mod.rs @@ -0,0 +1,2 @@ +pub mod file; +pub mod token; diff --git a/crates/dropbox/src/types/token.rs b/crates/dropbox/src/types/token.rs new file mode 100644 index 00000000..902d6bb3 --- /dev/null +++ b/crates/dropbox/src/types/token.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct AccessToken { + pub access_token: String, + pub token_type: String, + pub expires_in: u32, +} diff --git a/crates/dropbox/src/xata/dropbox.rs b/crates/dropbox/src/xata/dropbox.rs new file mode 100644 index 00000000..c3a4b76f --- /dev/null +++ b/crates/dropbox/src/xata/dropbox.rs @@ -0,0 +1,14 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +#[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] +pub struct Dropbox { + pub xata_id: String, + pub dropbox_token_id: String, + pub user_id: String, + pub xata_version: i32, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_updatedat: DateTime, +} diff --git a/crates/dropbox/src/xata/dropbox_path.rs b/crates/dropbox/src/xata/dropbox_path.rs new file mode 100644 index 00000000..05939bf2 --- /dev/null +++ b/crates/dropbox/src/xata/dropbox_path.rs @@ -0,0 +1,14 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +#[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] +pub struct DropboxPath { + pub xata_id: String, + pub dropbox_id: String, + pub track_id: String, + pub xata_version: i32, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_updatedat: DateTime, +} diff --git a/crates/dropbox/src/xata/dropbox_token.rs b/crates/dropbox/src/xata/dropbox_token.rs new file mode 100644 index 00000000..5b214919 --- /dev/null +++ b/crates/dropbox/src/xata/dropbox_token.rs @@ -0,0 +1,25 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +#[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] +pub struct DropboxToken { + pub xata_id: String, + pub xata_version: i32, + pub refresh_token: String, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_updatedat: DateTime, +} + +#[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] +pub struct DropboxTokenWithDid { + pub xata_id: String, + pub xata_version: i32, + pub refresh_token: String, + pub did: String, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_updatedat: DateTime, +} diff --git a/crates/dropbox/src/xata/mod.rs b/crates/dropbox/src/xata/mod.rs new file mode 100644 index 00000000..0c31f629 --- /dev/null +++ b/crates/dropbox/src/xata/mod.rs @@ -0,0 +1,5 @@ +pub mod dropbox; +pub mod dropbox_path; +pub mod dropbox_token; +pub mod track; +pub mod user; diff --git a/crates/dropbox/src/xata/track.rs b/crates/dropbox/src/xata/track.rs new file mode 100644 index 00000000..cc2b0015 --- /dev/null +++ b/crates/dropbox/src/xata/track.rs @@ -0,0 +1,31 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, sqlx::FromRow, Serialize, Deserialize, Clone)] +pub struct Track { + pub xata_id: String, + pub title: String, + pub artist: String, + pub album_artist: String, + pub album_art: Option, + pub album: String, + pub track_number: i32, + pub duration: i32, + pub mb_id: Option, + pub youtube_link: Option, + pub spotify_link: Option, + pub tidal_link: Option, + pub apple_music_link: Option, + pub sha256: String, + pub lyrics: Option, + pub composer: Option, + pub genre: Option, + pub disc_number: i32, + pub copyright_message: Option, + pub label: Option, + pub uri: Option, + pub artist_uri: Option, + pub album_uri: Option, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, +} diff --git a/crates/dropbox/src/xata/user.rs b/crates/dropbox/src/xata/user.rs new file mode 100644 index 00000000..967749a9 --- /dev/null +++ b/crates/dropbox/src/xata/user.rs @@ -0,0 +1,13 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +#[derive(Debug, sqlx::FromRow, Deserialize, Clone)] +pub struct User { + pub xata_id: String, + pub display_name: String, + pub did: String, + pub handle: String, + pub avatar: String, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, +} diff --git a/crates/googledrive/.env.example b/crates/googledrive/.env.example new file mode 100644 index 00000000..0078d90c --- /dev/null +++ b/crates/googledrive/.env.example @@ -0,0 +1,9 @@ +JWT_SECRET="" + +SPOTIFY_CLIENT_ID= +SPOTIFY_CLIENT_SECRET= + +SPOTIFY_ENCRYPTION_KEY= +SPOTIFY_ENCRYPTION_IV= + +XATA_POSTGRES_URL="" \ No newline at end of file diff --git a/crates/googledrive/.gitignore b/crates/googledrive/.gitignore new file mode 100644 index 00000000..2eea525d --- /dev/null +++ b/crates/googledrive/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/crates/googledrive/Cargo.toml b/crates/googledrive/Cargo.toml new file mode 100644 index 00000000..9f2e1a8f --- /dev/null +++ b/crates/googledrive/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "googledrive" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +actix-web = "4.9.0" +aes = "0.8.4" +anyhow = "1.0.96" +async-nats = "0.39.0" +chrono = { version = "0.4.39", features = ["serde"] } +ctr = "0.9.2" +dotenv = "0.15.0" +hex = "0.4.3" +jsonwebtoken = "9.3.1" +owo-colors = "4.1.0" +redis = "0.29.0" +reqwest = { version = "0.12.12", features = [ + "rustls-tls", + "json", + "multipart", + "stream", +], default-features = false } +serde = { version = "1.0.217", features = ["derive"] } +serde_json = "1.0.139" +serde_urlencoded = "0.7.1" +sqlx = { version = "0.8.3", features = [ + "runtime-tokio", + "tls-rustls", + "postgres", + "chrono", + "derive", + "macros", +] } +tokio = { version = "1.43.0", features = ["full"] } +tokio-stream = { version = "0.1.17", features = ["full"] } diff --git a/crates/googledrive/src/client.rs b/crates/googledrive/src/client.rs new file mode 100644 index 00000000..b833c14d --- /dev/null +++ b/crates/googledrive/src/client.rs @@ -0,0 +1,97 @@ +use std::env; + +use actix_web::HttpResponse; +use anyhow::Error; +use reqwest::Client; + +use crate::types::{file::FileList, token::AccessToken}; + +pub const BASE_URL: &str = "https://www.googleapis.com/drive/v3"; + +pub async fn get_access_token(refresh_token: &str) -> Result { + let client = Client::new(); + + let params = [ + ("grant_type", "refresh_token"), + ("refresh_token", refresh_token), + ("client_id", &env::var("GOOGLE_CLIENT_ID")?), + ("client_secret", &env::var("GOOGLE_CLIENT_SECRET")?), + ]; + + let body = serde_urlencoded::to_string(¶ms)?; + let res = client.post("https://oauth2.googleapis.com/token") + .header("Content-Type", "application/x-www-form-urlencoded") + .header("Content-Length", body.len()) + .body(body) + .send() + .await?; + + Ok(res.json::().await?) +} + +pub struct GoogleDriveClient { + pub access_token: String, +} + +impl GoogleDriveClient { + pub async fn new(refresh_token: &str) -> Result { + let res = get_access_token(refresh_token).await?; + Ok(Self { + access_token: res.access_token, + }) + } + + pub async fn get_files(&self, name: &str) -> Result { + let client = Client::new(); + let url = format!("{}/files", BASE_URL); + let res = client.get(&url) + .bearer_auth(&self.access_token) + .query(&[ + ("q", format!("name='{}' and mimeType='application/vnd.google-apps.folder'", name).as_str()), + ("fields", "files(id, name, mimeType, parents)"), + ]) + .send() + .await?; + + Ok(res.json::().await?) + } + + pub async fn get_files_in_parents(&self, parent_id: &str) -> Result { + let client = Client::new(); + let url = format!("{}/files", BASE_URL); + let res = client.get(&url) + .bearer_auth(&self.access_token) + .query(&[ + ("q", format!("'{}' in parents", parent_id).as_str()), + ("fields", "files(id, name, mimeType, parents)"), + ]) + .send() + .await?; + Ok(res.json::().await?) + } + + pub async fn download_file(&self, file_id: &str) -> Result { + let client = Client::new(); + let url = format!("{}/files/{}", BASE_URL, file_id); + let res = client.get(&url) + .bearer_auth(&self.access_token) + .query(&[ + ("alt", "media"), + ]) + .send() + .await?; + + + let mut actix_response = HttpResponse::Ok(); + + // Forward headers + for (key, value) in res.headers().iter() { + actix_response.append_header((key.as_str(), value.to_str().unwrap_or(""))); + } + + // Forward body + let body = res.bytes_stream(); + + Ok(actix_response.streaming(body)) + } +} \ No newline at end of file diff --git a/crates/googledrive/src/crypto.rs b/crates/googledrive/src/crypto.rs new file mode 100644 index 00000000..52650605 --- /dev/null +++ b/crates/googledrive/src/crypto.rs @@ -0,0 +1,22 @@ +use std::env; + +use aes::{ + cipher::{KeyIvInit, StreamCipher}, + Aes256, +}; +use anyhow::Error; +use hex::decode; + +type Aes256Ctr = ctr::Ctr64BE; + +pub fn decrypt_aes_256_ctr(encrypted_text: &str, key: &[u8]) -> Result { + let iv = decode(env::var("SPOTIFY_ENCRYPTION_IV")?)?; + let ciphertext = decode(encrypted_text)?; + + let mut cipher = + Aes256Ctr::new_from_slices(key, &iv).map_err(|_| Error::msg("Invalid key or IV"))?; + let mut decrypted_data = ciphertext.clone(); + cipher.apply_keystream(&mut decrypted_data); + + Ok(String::from_utf8(decrypted_data)?) +} diff --git a/crates/googledrive/src/handlers/files.rs b/crates/googledrive/src/handlers/files.rs new file mode 100644 index 00000000..b5311b3c --- /dev/null +++ b/crates/googledrive/src/handlers/files.rs @@ -0,0 +1,82 @@ +use std::{env, sync::{Arc, Mutex}}; + +use actix_web::{web, HttpRequest, HttpResponse}; +use anyhow::Error; +use sqlx::{Pool, Postgres}; +use tokio_stream::StreamExt; + +pub const MUSIC_DIR: &str = "Music"; + +use crate::{ + client::GoogleDriveClient, + crypto::decrypt_aes_256_ctr, + read_payload, + repo::google_drive_token::find_google_drive_refresh_token, types::file::{DownloadFileParams, GetFilesInParentsParams, GetFilesParams} +}; + +pub async fn get_files(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc>>) -> Result { + let body = read_payload!(payload); + let params = serde_json::from_slice::(&body)?; + + let pool = pool.lock().unwrap(); + let refresh_token = find_google_drive_refresh_token(&pool, ¶ms.did).await?; + + if refresh_token.is_none() { + return Ok(HttpResponse::Unauthorized().finish()); + } + + let refresh_token = decrypt_aes_256_ctr( + &refresh_token.unwrap(), + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? + )?; + + let client = GoogleDriveClient::new(&refresh_token).await?; + let files = client.get_files(MUSIC_DIR).await?; + // 12qiJpTzARyls_ICbIxqagnOGalRM1yF4 + // 1MdKbZE2U17qr2kScr9LJ8Yrh9dGxM8wM + + Ok(HttpResponse::Ok().json(web::Json(files))) +} + +pub async fn get_files_in_parents(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc>>) -> Result { + let body = read_payload!(payload); + let params = serde_json::from_slice::(&body)?; + + let pool = pool.lock().unwrap(); + let refresh_token = find_google_drive_refresh_token(&pool, ¶ms.did).await?; + + if refresh_token.is_none() { + return Ok(HttpResponse::Unauthorized().finish()); + } + + let refresh_token = decrypt_aes_256_ctr( + &refresh_token.unwrap(), + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? + )?; + + let client = GoogleDriveClient::new(&refresh_token).await?; + let files = client.get_files_in_parents(¶ms.parent_id).await?; + + Ok(HttpResponse::Ok().json(web::Json(files))) +} + + +pub async fn download_file(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc>>) -> Result { + let body = read_payload!(payload); + let params = serde_json::from_slice::(&body)?; + + let pool = pool.lock().unwrap(); + let refresh_token = find_google_drive_refresh_token(&pool, ¶ms.did).await?; + + if refresh_token.is_none() { + return Ok(HttpResponse::Unauthorized().finish()); + } + + let refresh_token = decrypt_aes_256_ctr( + &refresh_token.unwrap(), + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? + )?; + + let client = GoogleDriveClient::new(&refresh_token).await?; + client.download_file(¶ms.file_id).await +} diff --git a/crates/googledrive/src/handlers/mod.rs b/crates/googledrive/src/handlers/mod.rs new file mode 100644 index 00000000..f5401490 --- /dev/null +++ b/crates/googledrive/src/handlers/mod.rs @@ -0,0 +1,33 @@ +use std::sync::{Arc, Mutex}; + +use actix_web::{web, HttpRequest, HttpResponse}; +use anyhow::Error; +use files::{download_file, get_files, get_files_in_parents}; +use sqlx::{Pool, Postgres}; + +pub mod files; + + +#[macro_export] +macro_rules! read_payload { + ($payload:expr) => {{ + let mut body = Vec::new(); + while let Some(chunk) = $payload.next().await { + // skip if None + match chunk { + Ok(bytes) => body.extend_from_slice(&bytes), + Err(err) => return Err(err.into()), + } + } + body + }}; +} + +pub async fn handle(method: &str, payload: &mut web::Payload, req: &HttpRequest, conn: Arc>>) -> Result { + match method { + "googledrive.getFiles" => get_files(payload, req, conn.clone()).await, + "googledrive.getFilesInParents" => get_files_in_parents(payload, req, conn.clone()).await, + "googledrive.downloadFile" => download_file(payload, req, conn.clone()).await, + _ => return Err(anyhow::anyhow!("Method not found")), + } +} diff --git a/crates/googledrive/src/main.rs b/crates/googledrive/src/main.rs new file mode 100644 index 00000000..93685be0 --- /dev/null +++ b/crates/googledrive/src/main.rs @@ -0,0 +1,77 @@ +use std::{env, sync::{Arc, Mutex}}; +use actix_web::{get, post, web::{self, Data}, App, HttpRequest, HttpResponse, HttpServer, Responder}; +use anyhow::Error; +use dotenv::dotenv; +use handlers::handle; +use owo_colors::OwoColorize; +use serde_json::json; +use sqlx::{postgres::PgPoolOptions, Pool, Postgres}; + +pub mod xata; +pub mod crypto; +pub mod handlers; +pub mod repo; +pub mod types; +pub mod client; + +#[get("/")] +async fn index(_req: HttpRequest) -> HttpResponse { + HttpResponse::Ok().json(json!({ + "server": "Rocksky GoogleDrive Server", + "version": "0.1.0", + })) +} + +#[post("/{method}")] +async fn call_method( + data: web::Data>>>, + mut payload: web::Payload, + req: HttpRequest) -> Result { + let method = req.match_info().get("method").unwrap_or("unknown"); + println!("Method: {}", method.bright_green()); + + let conn = data.get_ref().clone(); + handle(method, &mut payload, &req, conn).await + .map_err(actix_web::error::ErrorInternalServerError) +} + + +#[tokio::main] +async fn main() -> Result<(), Box> { + dotenv().ok(); + + /* + let pool = PgPoolOptions::new().max_connections(5).connect(&env::var("XATA_POSTGRES_URL")?).await?; + let refresh_token = find_google_drive_refresh_token(&pool, "did:plc:7vdlgi2bflelz7mmuxoqjfcr").await?; + + let refresh_token = decrypt_aes_256_ctr( + &refresh_token.unwrap(), + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? + )?; + + println!("Refresh token: {}", refresh_token); + */ + let host = env::var("GOOGLE_DRIVE_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); + let port = env::var("GOOGLE_DRIVE_PORT_PORT").unwrap_or_else(|_| "7880".to_string()); + let addr = format!("{}:{}", host, port); + + let url = format!("http://{}", addr); + println!("Listening on {}", url.bright_green()); + + let pool = PgPoolOptions::new().max_connections(5).connect(&env::var("XATA_POSTGRES_URL")?).await?; + let conn = Arc::new(Mutex::new(pool)); + + let conn = conn.clone(); + HttpServer::new(move || { + App::new() + .app_data(Data::new(conn.clone())) + .service(index) + .service(call_method) + }) + .bind(&addr)? + .run() + .await + .map_err(Error::new)?; + + Ok(()) +} \ No newline at end of file diff --git a/crates/googledrive/src/repo/google_drive_token.rs b/crates/googledrive/src/repo/google_drive_token.rs new file mode 100644 index 00000000..b7024e4c --- /dev/null +++ b/crates/googledrive/src/repo/google_drive_token.rs @@ -0,0 +1,22 @@ +use sqlx::{Pool, Postgres}; +use anyhow::Error; + +use crate::xata::google_drive_token::GoogleDriveTokenWithDid; + +pub async fn find_google_drive_refresh_token(pool: &Pool, did: &str) -> Result, Error> { + let results: Vec = sqlx::query_as(r#" + SELECT * FROM google_drive gd + LEFT JOIN users u ON gd.user_id = u.xata_id + LEFT JOIN google_drive_tokens gt ON gd.google_drive_token_id = gt.xata_id + WHERE u.did = $1 + "#) + .bind(did) + .fetch_all(pool) + .await?; + + if results.len() == 0 { + return Ok(None); + } + + Ok(Some(results[0].refresh_token.clone())) +} diff --git a/crates/googledrive/src/repo/mod.rs b/crates/googledrive/src/repo/mod.rs new file mode 100644 index 00000000..3181c60e --- /dev/null +++ b/crates/googledrive/src/repo/mod.rs @@ -0,0 +1 @@ +pub mod google_drive_token; diff --git a/crates/googledrive/src/types/file.rs b/crates/googledrive/src/types/file.rs new file mode 100644 index 00000000..4125c739 --- /dev/null +++ b/crates/googledrive/src/types/file.rs @@ -0,0 +1,32 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct File { + pub id: String, + pub name: String, + #[serde(rename = "mimeType")] + pub mime_type: String, + pub parents: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct FileList { + pub files: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetFilesParams { + pub did: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetFilesInParentsParams { + pub did: String, + pub parent_id: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct DownloadFileParams { + pub did: String, + pub file_id: String, +} diff --git a/crates/googledrive/src/types/mod.rs b/crates/googledrive/src/types/mod.rs new file mode 100644 index 00000000..1d7f4d35 --- /dev/null +++ b/crates/googledrive/src/types/mod.rs @@ -0,0 +1,2 @@ +pub mod file; +pub mod token; diff --git a/crates/googledrive/src/types/token.rs b/crates/googledrive/src/types/token.rs new file mode 100644 index 00000000..6844cff0 --- /dev/null +++ b/crates/googledrive/src/types/token.rs @@ -0,0 +1,10 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct AccessToken { + pub access_token: String, + pub token_type: String, + pub expires_in: u32, + pub scope: String, + pub refresh_token_expires_in: u32, +} diff --git a/crates/googledrive/src/xata/google_drive.rs b/crates/googledrive/src/xata/google_drive.rs new file mode 100644 index 00000000..781eb65a --- /dev/null +++ b/crates/googledrive/src/xata/google_drive.rs @@ -0,0 +1,14 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +#[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] +pub struct GoogleDrive { + pub xata_id: String, + pub user_id: String, + pub google_drive_token_id: String, + pub xata_version: i32, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_updatedat: DateTime, +} diff --git a/crates/googledrive/src/xata/google_drive_path.rs b/crates/googledrive/src/xata/google_drive_path.rs new file mode 100644 index 00000000..ba958569 --- /dev/null +++ b/crates/googledrive/src/xata/google_drive_path.rs @@ -0,0 +1,14 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +#[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] +pub struct GoogleDrivePath { + pub xata_id: String, + pub google_drive_id: String, + pub track_id: String, + pub xata_version: i32, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_updatedat: DateTime, +} diff --git a/crates/googledrive/src/xata/google_drive_token.rs b/crates/googledrive/src/xata/google_drive_token.rs new file mode 100644 index 00000000..31cc1b45 --- /dev/null +++ b/crates/googledrive/src/xata/google_drive_token.rs @@ -0,0 +1,25 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +#[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] +pub struct GoogleDriveToken { + pub xata_id: String, + pub refresh_token: String, + pub xata_version: i32, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_updatedat: DateTime, +} + +#[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] +pub struct GoogleDriveTokenWithDid { + pub xata_id: String, + pub refresh_token: String, + pub xata_version: i32, + pub did: String, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_updatedat: DateTime, +} diff --git a/crates/googledrive/src/xata/mod.rs b/crates/googledrive/src/xata/mod.rs new file mode 100644 index 00000000..e11a9303 --- /dev/null +++ b/crates/googledrive/src/xata/mod.rs @@ -0,0 +1,5 @@ +pub mod google_drive; +pub mod google_drive_path; +pub mod google_drive_token; +pub mod track; +pub mod user; diff --git a/crates/googledrive/src/xata/track.rs b/crates/googledrive/src/xata/track.rs new file mode 100644 index 00000000..cc2b0015 --- /dev/null +++ b/crates/googledrive/src/xata/track.rs @@ -0,0 +1,31 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, sqlx::FromRow, Serialize, Deserialize, Clone)] +pub struct Track { + pub xata_id: String, + pub title: String, + pub artist: String, + pub album_artist: String, + pub album_art: Option, + pub album: String, + pub track_number: i32, + pub duration: i32, + pub mb_id: Option, + pub youtube_link: Option, + pub spotify_link: Option, + pub tidal_link: Option, + pub apple_music_link: Option, + pub sha256: String, + pub lyrics: Option, + pub composer: Option, + pub genre: Option, + pub disc_number: i32, + pub copyright_message: Option, + pub label: Option, + pub uri: Option, + pub artist_uri: Option, + pub album_uri: Option, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, +} diff --git a/crates/googledrive/src/xata/user.rs b/crates/googledrive/src/xata/user.rs new file mode 100644 index 00000000..967749a9 --- /dev/null +++ b/crates/googledrive/src/xata/user.rs @@ -0,0 +1,13 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +#[derive(Debug, sqlx::FromRow, Deserialize, Clone)] +pub struct User { + pub xata_id: String, + pub display_name: String, + pub did: String, + pub handle: String, + pub avatar: String, + #[serde(with = "chrono::serde::ts_seconds")] + pub xata_createdat: DateTime, +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/.ledger b/rockskyapi/rocksky-auth/.xata/migrations/.ledger index 07ccddda..5397f49e 100644 --- a/rockskyapi/rocksky-auth/.xata/migrations/.ledger +++ b/rockskyapi/rocksky-auth/.xata/migrations/.ledger @@ -182,3 +182,45 @@ mig_cv55nu2tb4rhvnj37rog mig_cv55o3d2t0po8jv9tkkg mig_cv55o952t0po8jv9tklg sql_7fbe41bfacc536 +mig_cv8tro2glbhgau6cq5j0 +mig_cv8ts6o1g95li4qghh5g +mig_cv8tsfkld6k2hsabcgqg +mig_cv8tt0g1g95li4qghh6g +mig_cv8ttl4ld6k2hsabcgsg +mig_cv8tu7sld6k2hsabcgtg +mig_cv8tufsld6k2hsabcgug +mig_cv8tutiglbhgau6cq5n0 +mig_cv8tv8iglbhgau6cq5p0 +mig_cv8u07aglbhgau6cq5q0 +mig_cv8u0daglbhgau6cq5r0 +mig_cv8u5cqglbhgau6cq61g +mig_cv8u6n01g95li4qghha0 +mig_cv8u7uqglbhgau6cq62g +mig_cv8u8j2glbhgau6cq63g +mig_cv8ub7cld6k2hsabch2g +mig_cv8ue4kld6k2hsabch3g +mig_cv8ueoaglbhgau6cq66g +mig_cv8ufnqglbhgau6cq67g +mig_cv8uh4aglbhgau6cq68g +mig_cv8ujhqglbhgau6cq69g +mig_cv8ukrg1g95li4qghhh0 +mig_cv8ulakld6k2hsabch4g +mig_cv8umjo1g95li4qghhk0 +mig_cv8un7g1g95li4qghhl0 +mig_cv8unpo1g95li4qghhm0 +mig_cv8up2aglbhgau6cq6bg +mig_cv8upbcld6k2hsabch6g +mig_cv8uq1sld6k2hsabch7g +mig_cv8uqh2glbhgau6cq6d0 +mig_cv8ur1kld6k2hsabch8g +mig_cv8urh4ld6k2hsabch9g +mig_cv8us9g1g95li4qghhq0 +mig_cv8usig1g95li4qghhr0 +mig_cv8utv81g95li4qghhs0 +mig_cv8uugqglbhgau6cq6e0 +mig_cv8uv2iglbhgau6cq6f0 +mig_cv8uveiglbhgau6cq6h0 +mig_cv8v8gcld6k2hsabcheg +mig_cv8v92aglbhgau6cq6n0 +mig_cv8v9faglbhgau6cq6o0 +mig_cv8v9sqglbhgau6cq6p0 diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tro2glbhgau6cq5j0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tro2glbhgau6cq5j0.json new file mode 100644 index 00000000..84aca390 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tro2glbhgau6cq5j0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8tro2glbhgau6cq5j0", + "operations": [ + { + "create_table": { + "name": "dropbox", + "columns": [ + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_id", + "type": "text", + "check": { + "name": "dropbox_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"dropbox\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"dropbox\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8tro2glbhgau6cq5j0", + "parent": "sql_7fbe41bfacc536", + "schema": "public", + "startedAt": "2025-03-12T19:17:53.182563Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ts6o1g95li4qghh5g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ts6o1g95li4qghh5g.json new file mode 100644 index 00000000..ffaa026c --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ts6o1g95li4qghh5g.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ts6o1g95li4qghh5g", + "operations": [ + { + "create_table": { + "name": "google_drive", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "google_drive_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"google_drive\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"google_drive\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ts6o1g95li4qghh5g", + "parent": "mig_cv8tro2glbhgau6cq5j0", + "schema": "public", + "startedAt": "2025-03-12T19:18:52.959323Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tsfkld6k2hsabcgqg.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tsfkld6k2hsabcgqg.json new file mode 100644 index 00000000..bacbf043 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tsfkld6k2hsabcgqg.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8tsfkld6k2hsabcgqg", + "operations": [ + { + "create_table": { + "name": "s3", + "columns": [ + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_id", + "type": "text", + "check": { + "name": "s3_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"s3\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"s3\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8tsfkld6k2hsabcgqg", + "parent": "mig_cv8ts6o1g95li4qghh5g", + "schema": "public", + "startedAt": "2025-03-12T19:19:27.411041Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tt0g1g95li4qghh6g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tt0g1g95li4qghh6g.json new file mode 100644 index 00000000..a241ed26 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tt0g1g95li4qghh6g.json @@ -0,0 +1,19 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8tt0g1g95li4qghh6g", + "operations": [ + { + "rename_table": { + "to": "s3_bucket", + "from": "s3" + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8tt0g1g95li4qghh6g", + "parent": "mig_cv8tsfkld6k2hsabcgqg", + "schema": "public", + "startedAt": "2025-03-12T19:20:35.26839Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ttl4ld6k2hsabcgsg.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ttl4ld6k2hsabcgsg.json new file mode 100644 index 00000000..ae639af8 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ttl4ld6k2hsabcgsg.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ttl4ld6k2hsabcgsg", + "operations": [ + { + "create_table": { + "name": "gcs_bucket", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "gcs_bucket_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"gcs_bucket\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"gcs_bucket\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ttl4ld6k2hsabcgsg", + "parent": "mig_cv8tt0g1g95li4qghh6g", + "schema": "public", + "startedAt": "2025-03-12T19:21:56.287049Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tu7sld6k2hsabcgtg.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tu7sld6k2hsabcgtg.json new file mode 100644 index 00000000..78b10e92 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tu7sld6k2hsabcgtg.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8tu7sld6k2hsabcgtg", + "operations": [ + { + "create_table": { + "name": "google_drive_tokens", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "google_drive_tokens_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"google_drive_tokens\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"google_drive_tokens\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8tu7sld6k2hsabcgtg", + "parent": "mig_cv8ttl4ld6k2hsabcgsg", + "schema": "public", + "startedAt": "2025-03-12T19:23:12.701215Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tufsld6k2hsabcgug.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tufsld6k2hsabcgug.json new file mode 100644 index 00000000..86a51449 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tufsld6k2hsabcgug.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8tufsld6k2hsabcgug", + "operations": [ + { + "create_table": { + "name": "s3_tokens", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "s3_tokens_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"s3_tokens\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"s3_tokens\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8tufsld6k2hsabcgug", + "parent": "mig_cv8tu7sld6k2hsabcgtg", + "schema": "public", + "startedAt": "2025-03-12T19:23:44.838476Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tutiglbhgau6cq5n0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tutiglbhgau6cq5n0.json new file mode 100644 index 00000000..3eed10b8 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tutiglbhgau6cq5n0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8tutiglbhgau6cq5n0", + "operations": [ + { + "create_table": { + "name": "dropbox_tokens", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "dropbox_tokens_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"dropbox_tokens\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"dropbox_tokens\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8tutiglbhgau6cq5n0", + "parent": "mig_cv8tufsld6k2hsabcgug", + "schema": "public", + "startedAt": "2025-03-12T19:24:39.515532Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tv8iglbhgau6cq5p0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tv8iglbhgau6cq5p0.json new file mode 100644 index 00000000..c1db6342 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tv8iglbhgau6cq5p0.json @@ -0,0 +1,18 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8tv8iglbhgau6cq5p0", + "operations": [ + { + "drop_table": { + "name": "gcs_bucket" + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8tv8iglbhgau6cq5p0", + "parent": "mig_cv8tutiglbhgau6cq5n0", + "schema": "public", + "startedAt": "2025-03-12T19:25:23.135657Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u07aglbhgau6cq5q0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u07aglbhgau6cq5q0.json new file mode 100644 index 00000000..793fe00b --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u07aglbhgau6cq5q0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8u07aglbhgau6cq5q0", + "operations": [ + { + "create_table": { + "name": "dropbox_paths", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "dropbox_paths_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"dropbox_paths\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"dropbox_paths\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8u07aglbhgau6cq5q0", + "parent": "mig_cv8tv8iglbhgau6cq5p0", + "schema": "public", + "startedAt": "2025-03-12T19:27:25.612995Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u0daglbhgau6cq5r0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u0daglbhgau6cq5r0.json new file mode 100644 index 00000000..64061355 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u0daglbhgau6cq5r0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8u0daglbhgau6cq5r0", + "operations": [ + { + "create_table": { + "name": "s3_paths", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "s3_paths_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"s3_paths\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"s3_paths\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8u0daglbhgau6cq5r0", + "parent": "mig_cv8u07aglbhgau6cq5q0", + "schema": "public", + "startedAt": "2025-03-12T19:27:49.903669Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u5cqglbhgau6cq61g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u5cqglbhgau6cq61g.json new file mode 100644 index 00000000..80df49c0 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u5cqglbhgau6cq61g.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8u5cqglbhgau6cq61g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox_tokens", + "column": { + "name": "refresh_token", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8u5cqglbhgau6cq61g", + "parent": "mig_cv8u0daglbhgau6cq5r0", + "schema": "public", + "startedAt": "2025-03-12T19:38:28.311152Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u6n01g95li4qghha0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u6n01g95li4qghha0.json new file mode 100644 index 00000000..3e319031 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u6n01g95li4qghha0.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8u6n01g95li4qghha0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive_tokens", + "column": { + "name": "refresh_token", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8u6n01g95li4qghha0", + "parent": "mig_cv8u5cqglbhgau6cq61g", + "schema": "public", + "startedAt": "2025-03-12T19:41:16.419896Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u7uqglbhgau6cq62g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u7uqglbhgau6cq62g.json new file mode 100644 index 00000000..4a7b1fad --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u7uqglbhgau6cq62g.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8u7uqglbhgau6cq62g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_bucket", + "column": { + "name": "name", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8u7uqglbhgau6cq62g", + "parent": "mig_cv8u6n01g95li4qghha0", + "schema": "public", + "startedAt": "2025-03-12T19:43:56.410523Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u8j2glbhgau6cq63g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u8j2glbhgau6cq63g.json new file mode 100644 index 00000000..33d648cd --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u8j2glbhgau6cq63g.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8u8j2glbhgau6cq63g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_bucket", + "column": { + "name": "user_id", + "type": "text", + "comment": "{\"xata.link\":\"users\"}", + "references": { + "name": "user_id_link", + "table": "users", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8u8j2glbhgau6cq63g", + "parent": "mig_cv8u7uqglbhgau6cq62g", + "schema": "public", + "startedAt": "2025-03-12T19:45:17.407227Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ub7cld6k2hsabch2g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ub7cld6k2hsabch2g.json new file mode 100644 index 00000000..d36c8949 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ub7cld6k2hsabch2g.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ub7cld6k2hsabch2g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_bucket", + "column": { + "name": "s3_token_id", + "type": "text", + "comment": "{\"xata.link\":\"s3_tokens\"}", + "references": { + "name": "s3_token_id_link", + "table": "s3_tokens", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ub7cld6k2hsabch2g", + "parent": "mig_cv8u8j2glbhgau6cq63g", + "schema": "public", + "startedAt": "2025-03-12T19:50:53.634179Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ue4kld6k2hsabch3g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ue4kld6k2hsabch3g.json new file mode 100644 index 00000000..8a248def --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ue4kld6k2hsabch3g.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ue4kld6k2hsabch3g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox", + "column": { + "name": "dropbox_token_id", + "type": "text", + "comment": "{\"xata.link\":\"dropbox_tokens\"}", + "references": { + "name": "dropbox_token_id_link", + "table": "dropbox_tokens", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ue4kld6k2hsabch3g", + "parent": "mig_cv8ub7cld6k2hsabch2g", + "schema": "public", + "startedAt": "2025-03-12T19:57:06.582548Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ueoaglbhgau6cq66g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ueoaglbhgau6cq66g.json new file mode 100644 index 00000000..3460b541 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ueoaglbhgau6cq66g.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ueoaglbhgau6cq66g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox", + "column": { + "name": "user_id", + "type": "text", + "comment": "{\"xata.link\":\"users\"}", + "references": { + "name": "user_id_link", + "table": "users", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ueoaglbhgau6cq66g", + "parent": "mig_cv8ue4kld6k2hsabch3g", + "schema": "public", + "startedAt": "2025-03-12T19:58:26.520533Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ufnqglbhgau6cq67g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ufnqglbhgau6cq67g.json new file mode 100644 index 00000000..a8599ab6 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ufnqglbhgau6cq67g.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ufnqglbhgau6cq67g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive", + "column": { + "name": "google_drive_token_id", + "type": "text", + "comment": "{\"xata.link\":\"google_drive_tokens\"}", + "references": { + "name": "google_drive_token_id_link", + "table": "google_drive_tokens", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ufnqglbhgau6cq67g", + "parent": "mig_cv8ueoaglbhgau6cq66g", + "schema": "public", + "startedAt": "2025-03-12T20:00:32.127974Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uh4aglbhgau6cq68g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uh4aglbhgau6cq68g.json new file mode 100644 index 00000000..02608f0a --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uh4aglbhgau6cq68g.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8uh4aglbhgau6cq68g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive", + "column": { + "name": "user_id", + "type": "text", + "comment": "{\"xata.link\":\"users\"}", + "references": { + "name": "user_id_link", + "table": "users", + "column": "xata_id", + "on_delete": "SET NULL" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8uh4aglbhgau6cq68g", + "parent": "mig_cv8ufnqglbhgau6cq67g", + "schema": "public", + "startedAt": "2025-03-12T20:03:29.416183Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ujhqglbhgau6cq69g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ujhqglbhgau6cq69g.json new file mode 100644 index 00000000..500ece7f --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ujhqglbhgau6cq69g.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ujhqglbhgau6cq69g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox_paths", + "column": { + "name": "dropbox_id", + "type": "text", + "comment": "{\"xata.link\":\"dropbox\"}", + "references": { + "name": "dropbox_id_link", + "table": "dropbox", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ujhqglbhgau6cq69g", + "parent": "mig_cv8uh4aglbhgau6cq68g", + "schema": "public", + "startedAt": "2025-03-12T20:08:40.286709Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ukrg1g95li4qghhh0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ukrg1g95li4qghhh0.json new file mode 100644 index 00000000..cab3f9df --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ukrg1g95li4qghhh0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ukrg1g95li4qghhh0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox_paths", + "column": { + "name": "track_id", + "type": "text", + "comment": "{\"xata.link\":\"tracks\"}", + "references": { + "name": "track_id_link", + "table": "tracks", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ukrg1g95li4qghhh0", + "parent": "mig_cv8ujhqglbhgau6cq69g", + "schema": "public", + "startedAt": "2025-03-12T20:11:27.367833Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ulakld6k2hsabch4g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ulakld6k2hsabch4g.json new file mode 100644 index 00000000..d3c42369 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ulakld6k2hsabch4g.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ulakld6k2hsabch4g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox_paths", + "column": { + "name": "path", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ulakld6k2hsabch4g", + "parent": "mig_cv8ukrg1g95li4qghhh0", + "schema": "public", + "startedAt": "2025-03-12T20:12:27.123143Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8umjo1g95li4qghhk0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8umjo1g95li4qghhk0.json new file mode 100644 index 00000000..3979d683 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8umjo1g95li4qghhk0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8umjo1g95li4qghhk0", + "operations": [ + { + "create_table": { + "name": "google_drive_paths", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "google_drive_paths_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"google_drive_paths\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"google_drive_paths\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8umjo1g95li4qghhk0", + "parent": "mig_cv8ulakld6k2hsabch4g", + "schema": "public", + "startedAt": "2025-03-12T20:15:12.130883Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8un7g1g95li4qghhl0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8un7g1g95li4qghhl0.json new file mode 100644 index 00000000..3f5df94e --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8un7g1g95li4qghhl0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8un7g1g95li4qghhl0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive_paths", + "column": { + "name": "google_drive_id", + "type": "text", + "comment": "{\"xata.link\":\"google_drive\"}", + "references": { + "name": "google_drive_id_link", + "table": "google_drive", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8un7g1g95li4qghhl0", + "parent": "mig_cv8umjo1g95li4qghhk0", + "schema": "public", + "startedAt": "2025-03-12T20:16:31.074898Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8unpo1g95li4qghhm0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8unpo1g95li4qghhm0.json new file mode 100644 index 00000000..41d6152d --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8unpo1g95li4qghhm0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8unpo1g95li4qghhm0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive_paths", + "column": { + "name": "track_id", + "type": "text", + "comment": "{\"xata.link\":\"tracks\"}", + "references": { + "name": "track_id_link", + "table": "tracks", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8unpo1g95li4qghhm0", + "parent": "mig_cv8un7g1g95li4qghhl0", + "schema": "public", + "startedAt": "2025-03-12T20:17:44.010057Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8up2aglbhgau6cq6bg.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8up2aglbhgau6cq6bg.json new file mode 100644 index 00000000..63fa82f1 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8up2aglbhgau6cq6bg.json @@ -0,0 +1,25 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8up2aglbhgau6cq6bg", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive_paths", + "column": { + "name": "google_drive_file_id", + "type": "text", + "unique": true, + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8up2aglbhgau6cq6bg", + "parent": "mig_cv8unpo1g95li4qghhm0", + "schema": "public", + "startedAt": "2025-03-12T20:20:26.129287Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8upbcld6k2hsabch6g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8upbcld6k2hsabch6g.json new file mode 100644 index 00000000..56016f94 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8upbcld6k2hsabch6g.json @@ -0,0 +1,20 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8upbcld6k2hsabch6g", + "operations": [ + { + "alter_column": { + "name": "file_id", + "table": "google_drive_paths", + "column": "google_drive_file_id" + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8upbcld6k2hsabch6g", + "parent": "mig_cv8up2aglbhgau6cq6bg", + "schema": "public", + "startedAt": "2025-03-12T20:21:01.574135Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uq1sld6k2hsabch7g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uq1sld6k2hsabch7g.json new file mode 100644 index 00000000..e47439a0 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uq1sld6k2hsabch7g.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8uq1sld6k2hsabch7g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_paths", + "column": { + "name": "track_id", + "type": "text", + "comment": "{\"xata.link\":\"tracks\"}", + "references": { + "name": "track_id_link", + "table": "tracks", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8uq1sld6k2hsabch7g", + "parent": "mig_cv8upbcld6k2hsabch6g", + "schema": "public", + "startedAt": "2025-03-12T20:22:31.736456Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uqh2glbhgau6cq6d0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uqh2glbhgau6cq6d0.json new file mode 100644 index 00000000..81a429cc --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uqh2glbhgau6cq6d0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8uqh2glbhgau6cq6d0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_paths", + "column": { + "name": "s3_bucket_id", + "type": "text", + "comment": "{\"xata.link\":\"s3_bucket\"}", + "references": { + "name": "s3_bucket_id_link", + "table": "s3_bucket", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8uqh2glbhgau6cq6d0", + "parent": "mig_cv8uq1sld6k2hsabch7g", + "schema": "public", + "startedAt": "2025-03-12T20:23:32.674866Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ur1kld6k2hsabch8g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ur1kld6k2hsabch8g.json new file mode 100644 index 00000000..e2b4b491 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ur1kld6k2hsabch8g.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8ur1kld6k2hsabch8g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_tokens", + "column": { + "name": "secret_access_key", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8ur1kld6k2hsabch8g", + "parent": "mig_cv8uqh2glbhgau6cq6d0", + "schema": "public", + "startedAt": "2025-03-12T20:24:39.513081Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8urh4ld6k2hsabch9g.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8urh4ld6k2hsabch9g.json new file mode 100644 index 00000000..fccc6492 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8urh4ld6k2hsabch9g.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8urh4ld6k2hsabch9g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_tokens", + "column": { + "name": "client_access_key", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8urh4ld6k2hsabch9g", + "parent": "mig_cv8ur1kld6k2hsabch8g", + "schema": "public", + "startedAt": "2025-03-12T20:25:41.248772Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8us9g1g95li4qghhq0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8us9g1g95li4qghhq0.json new file mode 100644 index 00000000..cef2101b --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8us9g1g95li4qghhq0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8us9g1g95li4qghhq0", + "operations": [ + { + "create_table": { + "name": "sftp", + "columns": [ + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_id", + "type": "text", + "check": { + "name": "sftp_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"sftp\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"sftp\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8us9g1g95li4qghhq0", + "parent": "mig_cv8urh4ld6k2hsabch9g", + "schema": "public", + "startedAt": "2025-03-12T20:27:19.422672Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8usig1g95li4qghhr0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8usig1g95li4qghhr0.json new file mode 100644 index 00000000..c3aa44a2 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8usig1g95li4qghhr0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8usig1g95li4qghhr0", + "operations": [ + { + "create_table": { + "name": "sftp_access", + "columns": [ + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_id", + "type": "text", + "check": { + "name": "sftp_access_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"sftp_access\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"sftp_access\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8usig1g95li4qghhr0", + "parent": "mig_cv8us9g1g95li4qghhq0", + "schema": "public", + "startedAt": "2025-03-12T20:27:55.896177Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8utv81g95li4qghhs0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8utv81g95li4qghhs0.json new file mode 100644 index 00000000..6b2b6842 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8utv81g95li4qghhs0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8utv81g95li4qghhs0", + "operations": [ + { + "create_table": { + "name": "builtin_storage_paths", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "builtin_storage_paths_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"builtin_storage_paths\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"builtin_storage_paths\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8utv81g95li4qghhs0", + "parent": "mig_cv8usig1g95li4qghhr0", + "schema": "public", + "startedAt": "2025-03-12T20:30:54.210263Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uugqglbhgau6cq6e0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uugqglbhgau6cq6e0.json new file mode 100644 index 00000000..7c2a569b --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uugqglbhgau6cq6e0.json @@ -0,0 +1,25 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8uugqglbhgau6cq6e0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "builtin_storage_paths", + "column": { + "name": "path", + "type": "text", + "unique": true, + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8uugqglbhgau6cq6e0", + "parent": "mig_cv8utv81g95li4qghhs0", + "schema": "public", + "startedAt": "2025-03-12T20:32:05.065795Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uv2iglbhgau6cq6f0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uv2iglbhgau6cq6f0.json new file mode 100644 index 00000000..c0166efc --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uv2iglbhgau6cq6f0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8uv2iglbhgau6cq6f0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "builtin_storage_paths", + "column": { + "name": "track_id", + "type": "text", + "comment": "{\"xata.link\":\"tracks\"}", + "references": { + "name": "track_id_link", + "table": "tracks", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8uv2iglbhgau6cq6f0", + "parent": "mig_cv8uugqglbhgau6cq6e0", + "schema": "public", + "startedAt": "2025-03-12T20:33:15.916373Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uveiglbhgau6cq6h0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uveiglbhgau6cq6h0.json new file mode 100644 index 00000000..a02a352a --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uveiglbhgau6cq6h0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8uveiglbhgau6cq6h0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "builtin_storage_paths", + "column": { + "name": "user_id", + "type": "text", + "comment": "{\"xata.link\":\"users\"}", + "references": { + "name": "user_id_link", + "table": "users", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8uveiglbhgau6cq6h0", + "parent": "mig_cv8uv2iglbhgau6cq6f0", + "schema": "public", + "startedAt": "2025-03-12T20:34:03.576836Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v8gcld6k2hsabcheg.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v8gcld6k2hsabcheg.json new file mode 100644 index 00000000..bc138151 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v8gcld6k2hsabcheg.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8v8gcld6k2hsabcheg", + "operations": [ + { + "create_table": { + "name": "sftp_path", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "sftp_path_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"sftp_path\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"sftp_path\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8v8gcld6k2hsabcheg", + "parent": "mig_cv8uveiglbhgau6cq6h0", + "schema": "public", + "startedAt": "2025-03-12T20:53:21.873024Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v92aglbhgau6cq6n0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v92aglbhgau6cq6n0.json new file mode 100644 index 00000000..124ea28a --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v92aglbhgau6cq6n0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8v92aglbhgau6cq6n0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "sftp_path", + "column": { + "name": "track_id", + "type": "text", + "comment": "{\"xata.link\":\"tracks\"}", + "references": { + "name": "track_id_link", + "table": "tracks", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8v92aglbhgau6cq6n0", + "parent": "mig_cv8v8gcld6k2hsabcheg", + "schema": "public", + "startedAt": "2025-03-12T20:54:33.889601Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9faglbhgau6cq6o0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9faglbhgau6cq6o0.json new file mode 100644 index 00000000..6bf49fe5 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9faglbhgau6cq6o0.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8v9faglbhgau6cq6o0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "sftp_path", + "column": { + "name": "path", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8v9faglbhgau6cq6o0", + "parent": "mig_cv8v92aglbhgau6cq6n0", + "schema": "public", + "startedAt": "2025-03-12T20:55:26.567965Z" +} diff --git a/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9sqglbhgau6cq6p0.json b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9sqglbhgau6cq6p0.json new file mode 100644 index 00000000..1a54fc25 --- /dev/null +++ b/rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9sqglbhgau6cq6p0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_cv8v9sqglbhgau6cq6p0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "sftp_path", + "column": { + "name": "sftp_id", + "type": "text", + "comment": "{\"xata.link\":\"sftp\"}", + "references": { + "name": "sftp_id_link", + "table": "sftp", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_cv8v9sqglbhgau6cq6p0", + "parent": "mig_cv8v9faglbhgau6cq6o0", + "schema": "public", + "startedAt": "2025-03-12T20:56:20.296301Z" +} diff --git a/rockskyapi/rocksky-auth/src/dropbox/app.ts b/rockskyapi/rocksky-auth/src/dropbox/app.ts index 114fa0fa..8ebf2cf3 100644 --- a/rockskyapi/rocksky-auth/src/dropbox/app.ts +++ b/rockskyapi/rocksky-auth/src/dropbox/app.ts @@ -1,18 +1,71 @@ +import { equals } from "@xata.io/client"; +import axios from "axios"; +import { ctx } from "context"; import { Hono } from "hono"; +import jwt from "jsonwebtoken"; +import { encrypt } from "lib/crypto"; import { env } from "lib/env"; const app = new Hono(); app.get("/login", async (c) => { + const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim(); + + if (!bearer || bearer === "null") { + c.status(401); + return c.text("Unauthorized"); + } + + const { did } = jwt.verify(bearer, env.JWT_SECRET); + + const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); + if (!user) { + c.status(401); + return c.text("Unauthorized"); + } + const clientId = env.DROPBOX_CLIENT_ID; - const redirectUri = `https://www.dropbox.com/oauth2/authorize?client_id=${clientId}&redirect_uri=${env.DROPBOX_REDIRECT_URI}&response_type=code&token_access_type=offline`; + const redirectUri = `https://www.dropbox.com/oauth2/authorize?client_id=${clientId}&redirect_uri=${env.DROPBOX_REDIRECT_URI}&response_type=code&token_access_type=offline&state=${user.xata_id}`; return c.json({ redirectUri }); }); app.get("/oauth/callback", async (c) => { const params = new URLSearchParams(c.req.url.split("?")[1]); const entries = Object.fromEntries(params.entries()); - return c.json(entries); + // entries.code + const response = await axios.postForm( + "https://api.dropboxapi.com/oauth2/token", + { + code: entries.code, + grant_type: "authorization_code", + client_id: env.DROPBOX_CLIENT_ID, + client_secret: env.DROPBOX_CLIENT_SECRET, + redirect_uri: env.DROPBOX_REDIRECT_URI, + } + ); + + const dropbox = await ctx.client.db.dropbox + .select(["*", "user_id.*", "dropbox_token_id.*"]) + .filter("user_id.xata_id", equals(entries.state)) + .getFirst(); + + if (dropbox) { + await ctx.client.db.dropbox_tokens.delete(dropbox.dropbox_token_id.xata_id); + } + + const newDropboxToken = await ctx.client.db.dropbox_tokens.create({ + refresh_token: encrypt( + response.data.refresh_token, + env.SPOTIFY_ENCRYPTION_KEY + ), + }); + + await ctx.client.db.dropbox.create({ + dropbox_token_id: newDropboxToken.xata_id, + user_id: entries.state, + }); + + return c.redirect(`${env.FRONTEND_URL}/dropbox`); }); export default app; diff --git a/rockskyapi/rocksky-auth/src/googledrive/app.ts b/rockskyapi/rocksky-auth/src/googledrive/app.ts index 713383ad..c33e2b7d 100644 --- a/rockskyapi/rocksky-auth/src/googledrive/app.ts +++ b/rockskyapi/rocksky-auth/src/googledrive/app.ts @@ -1,11 +1,31 @@ +import { equals } from "@xata.io/client"; +import axios from "axios"; +import { ctx } from "context"; import fs from "fs"; import { google } from "googleapis"; import { Hono } from "hono"; +import jwt from "jsonwebtoken"; +import { encrypt } from "lib/crypto"; import { env } from "lib/env"; const app = new Hono(); app.get("/login", async (c) => { + const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim(); + + if (!bearer || bearer === "null") { + c.status(401); + return c.text("Unauthorized"); + } + + const { did } = jwt.verify(bearer, env.JWT_SECRET); + + const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); + if (!user) { + c.status(401); + return c.text("Unauthorized"); + } + const credentials = JSON.parse( fs.readFileSync("credentials.json").toString("utf-8") ); @@ -19,8 +39,9 @@ app.get("/login", async (c) => { // Generate Auth URL const authUrl = oAuth2Client.generateAuthUrl({ access_type: "offline", - // prompt: "consent", + prompt: "consent", scope: ["https://www.googleapis.com/auth/drive"], + state: user.xata_id, }); return c.json({ authUrl }); }); @@ -28,7 +49,44 @@ app.get("/login", async (c) => { app.get("/oauth/callback", async (c) => { const params = new URLSearchParams(c.req.url.split("?")[1]); const entries = Object.fromEntries(params.entries()); - return c.json(entries); + + const credentials = JSON.parse( + fs.readFileSync("credentials.json").toString("utf-8") + ); + const { client_id, client_secret } = credentials.installed || credentials.web; + + const response = await axios.postForm("https://oauth2.googleapis.com/token", { + code: entries.code, + client_id, + client_secret, + redirect_uri: env.GOOGLE_REDIRECT_URI, + grant_type: "authorization_code", + }); + + const googledrive = await ctx.client.db.google_drive + .select(["*", "user_id.*", "google_drive_token_id.*"]) + .filter("user_id.xata_id", equals(entries.state)) + .getFirst(); + + if (googledrive) { + await ctx.client.db.google_drive_tokens.delete( + googledrive.google_drive_token_id.xata_id + ); + } + + const newGoogleDriveToken = await ctx.client.db.google_drive_tokens.create({ + refresh_token: encrypt( + response.data.refresh_token, + env.SPOTIFY_ENCRYPTION_KEY + ), + }); + + await ctx.client.db.google_drive.create({ + google_drive_token_id: newGoogleDriveToken.xata_id, + user_id: entries.state, + }); + + return c.redirect(`${env.FRONTEND_URL}/googledrive`); }); export default app; diff --git a/rockskyapi/rocksky-auth/src/schema/dropbox-tokens.ts b/rockskyapi/rocksky-auth/src/schema/dropbox-tokens.ts new file mode 100644 index 00000000..b024e12f --- /dev/null +++ b/rockskyapi/rocksky-auth/src/schema/dropbox-tokens.ts @@ -0,0 +1,10 @@ +import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; + +const dropboxTokens = pgTable("dropbox_tokens", { + id: text("xata_id").primaryKey(), + refreshToken: text("refresh_token").notNull(), + createdAt: timestamp("xata_createdat").defaultNow().notNull(), + updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), +}); + +export default dropboxTokens; diff --git a/rockskyapi/rocksky-auth/src/schema/google-drive-tokens.ts b/rockskyapi/rocksky-auth/src/schema/google-drive-tokens.ts new file mode 100644 index 00000000..f3c5ba4b --- /dev/null +++ b/rockskyapi/rocksky-auth/src/schema/google-drive-tokens.ts @@ -0,0 +1,10 @@ +import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; + +const googleDriveTokens = pgTable("google_drive_tokens", { + id: text("xata_id").primaryKey(), + refreshToken: text("refresh_token").notNull(), + createdAt: timestamp("xata_createdat").defaultNow().notNull(), + updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), +}); + +export default googleDriveTokens; diff --git a/rockskyapi/rocksky-auth/src/xata.ts b/rockskyapi/rocksky-auth/src/xata.ts index dc8de11f..677df8b9 100644 --- a/rockskyapi/rocksky-auth/src/xata.ts +++ b/rockskyapi/rocksky-auth/src/xata.ts @@ -769,10 +769,10 @@ const tables = [ ], }, { - name: "loved_tracks", + name: "builtin_storage_paths", checkConstraints: { - loved_tracks_xata_id_length_xata_id: { - name: "loved_tracks_xata_id_length_xata_id", + builtin_storage_paths_xata_id_length_xata_id: { + name: "builtin_storage_paths_xata_id_length_xata_id", columns: ["xata_id"], definition: "CHECK ((length(xata_id) < 256))", }, @@ -783,28 +783,36 @@ const tables = [ columns: ["track_id"], referencedTable: "tracks", referencedColumns: ["xata_id"], - onDelete: "SET NULL", + onDelete: "CASCADE", }, user_id_link: { name: "user_id_link", columns: ["user_id"], referencedTable: "users", referencedColumns: ["xata_id"], - onDelete: "SET NULL", + onDelete: "CASCADE", }, }, primaryKey: [], uniqueConstraints: { - _pgroll_new_loved_tracks_xata_id_key: { - name: "_pgroll_new_loved_tracks_xata_id_key", + _pgroll_new_builtin_storage_paths_xata_id_key: { + name: "_pgroll_new_builtin_storage_paths_xata_id_key", columns: ["xata_id"], }, - loved_tracks__pgroll_new_uri_key: { - name: "loved_tracks__pgroll_new_uri_key", - columns: ["uri"], + builtin_storage_paths__pgroll_new_path_key: { + name: "builtin_storage_paths__pgroll_new_path_key", + columns: ["path"], }, }, columns: [ + { + name: "path", + type: "text", + notNull: true, + unique: true, + defaultValue: null, + comment: "", + }, { name: "track_id", type: "link", @@ -815,13 +823,90 @@ const tables = [ comment: '{"xata.link":"tracks"}', }, { - name: "uri", + name: "user_id", + type: "link", + link: { table: "users" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"users"}', + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", type: "text", - notNull: false, + notNull: true, unique: true, - defaultValue: null, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", comment: "", }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "dropbox", + checkConstraints: { + dropbox_xata_id_length_xata_id: { + name: "dropbox_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + dropbox_token_id_link: { + name: "dropbox_token_id_link", + columns: ["dropbox_token_id"], + referencedTable: "dropbox_tokens", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + user_id_link: { + name: "user_id_link", + columns: ["user_id"], + referencedTable: "users", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_dropbox_xata_id_key: { + name: "_pgroll_new_dropbox_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ + { + name: "dropbox_token_id", + type: "link", + link: { table: "dropbox_tokens" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"dropbox_tokens"}', + }, { name: "user_id", type: "link", @@ -866,46 +951,54 @@ const tables = [ ], }, { - name: "playlist_tracks", + name: "dropbox_paths", checkConstraints: { - playlist_tracks_xata_id_length_xata_id: { - name: "playlist_tracks_xata_id_length_xata_id", + dropbox_paths_xata_id_length_xata_id: { + name: "dropbox_paths_xata_id_length_xata_id", columns: ["xata_id"], definition: "CHECK ((length(xata_id) < 256))", }, }, foreignKeys: { - playlist_id_link: { - name: "playlist_id_link", - columns: ["playlist_id"], - referencedTable: "playlists", + dropbox_id_link: { + name: "dropbox_id_link", + columns: ["dropbox_id"], + referencedTable: "dropbox", referencedColumns: ["xata_id"], - onDelete: "SET NULL", + onDelete: "CASCADE", }, track_id_link: { name: "track_id_link", columns: ["track_id"], referencedTable: "tracks", referencedColumns: ["xata_id"], - onDelete: "SET NULL", + onDelete: "CASCADE", }, }, primaryKey: [], uniqueConstraints: { - _pgroll_new_playlist_tracks_xata_id_key: { - name: "_pgroll_new_playlist_tracks_xata_id_key", + _pgroll_new_dropbox_paths_xata_id_key: { + name: "_pgroll_new_dropbox_paths_xata_id_key", columns: ["xata_id"], }, }, columns: [ { - name: "playlist_id", + name: "dropbox_id", type: "link", - link: { table: "playlists" }, + link: { table: "dropbox" }, notNull: true, unique: false, defaultValue: null, - comment: '{"xata.link":"playlists"}', + comment: '{"xata.link":"dropbox"}', + }, + { + name: "path", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", }, { name: "track_id", @@ -951,18 +1044,85 @@ const tables = [ ], }, { - name: "playlists", + name: "dropbox_tokens", checkConstraints: { - playlists_xata_id_length_xata_id: { - name: "playlists_xata_id_length_xata_id", + dropbox_tokens_xata_id_length_xata_id: { + name: "dropbox_tokens_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: {}, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_dropbox_tokens_xata_id_key: { + name: "_pgroll_new_dropbox_tokens_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ + { + name: "refresh_token", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "google_drive", + checkConstraints: { + google_drive_xata_id_length_xata_id: { + name: "google_drive_xata_id_length_xata_id", columns: ["xata_id"], definition: "CHECK ((length(xata_id) < 256))", }, }, foreignKeys: { - created_by_link: { - name: "created_by_link", - columns: ["created_by"], + google_drive_token_id_link: { + name: "google_drive_token_id_link", + columns: ["google_drive_token_id"], + referencedTable: "google_drive_tokens", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + user_id_link: { + name: "user_id_link", + columns: ["user_id"], referencedTable: "users", referencedColumns: ["xata_id"], onDelete: "SET NULL", @@ -970,38 +1130,23 @@ const tables = [ }, primaryKey: [], uniqueConstraints: { - _pgroll_new_playlists_xata_id_key: { - name: "_pgroll_new_playlists_xata_id_key", + _pgroll_new_google_drive_xata_id_key: { + name: "_pgroll_new_google_drive_xata_id_key", columns: ["xata_id"], }, - playlists__pgroll_new_uri_key: { - name: "playlists__pgroll_new_uri_key", - columns: ["uri"], - }, - playlists_apple_music_link_unique: { - name: "playlists_apple_music_link_unique", - columns: ["apple_music_link"], - }, - playlists_spotify_link_unique: { - name: "playlists_spotify_link_unique", - columns: ["spotify_link"], - }, - playlists_tidal_link_unique: { - name: "playlists_tidal_link_unique", - columns: ["tidal_link"], - }, }, columns: [ { - name: "apple_music_link", - type: "text", - notNull: false, - unique: true, + name: "google_drive_token_id", + type: "link", + link: { table: "google_drive_tokens" }, + notNull: true, + unique: false, defaultValue: null, - comment: "", + comment: '{"xata.link":"google_drive_tokens"}', }, { - name: "created_by", + name: "user_id", type: "link", link: { table: "users" }, notNull: true, @@ -1010,50 +1155,929 @@ const tables = [ comment: '{"xata.link":"users"}', }, { - name: "description", - type: "text", - notNull: false, + name: "xata_createdat", + type: "datetime", + notNull: true, unique: false, - defaultValue: null, + defaultValue: "now()", comment: "", }, { - name: "name", + name: "xata_id", type: "text", notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, unique: false, - defaultValue: null, + defaultValue: "now()", comment: "", }, { - name: "picture", + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "google_drive_paths", + checkConstraints: { + google_drive_paths_xata_id_length_xata_id: { + name: "google_drive_paths_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + google_drive_id_link: { + name: "google_drive_id_link", + columns: ["google_drive_id"], + referencedTable: "google_drive", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + track_id_link: { + name: "track_id_link", + columns: ["track_id"], + referencedTable: "tracks", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_google_drive_paths_xata_id_key: { + name: "_pgroll_new_google_drive_paths_xata_id_key", + columns: ["xata_id"], + }, + google_drive_paths__pgroll_new_google_drive_file_id_key: { + name: "google_drive_paths__pgroll_new_google_drive_file_id_key", + columns: ["file_id"], + }, + }, + columns: [ + { + name: "file_id", type: "text", - notNull: false, + notNull: true, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "google_drive_id", + type: "link", + link: { table: "google_drive" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"google_drive"}', + }, + { + name: "track_id", + type: "link", + link: { table: "tracks" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"tracks"}', + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "google_drive_tokens", + checkConstraints: { + google_drive_tokens_xata_id_length_xata_id: { + name: "google_drive_tokens_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: {}, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_google_drive_tokens_xata_id_key: { + name: "_pgroll_new_google_drive_tokens_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ + { + name: "refresh_token", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "loved_tracks", + checkConstraints: { + loved_tracks_xata_id_length_xata_id: { + name: "loved_tracks_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + track_id_link: { + name: "track_id_link", + columns: ["track_id"], + referencedTable: "tracks", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, + user_id_link: { + name: "user_id_link", + columns: ["user_id"], + referencedTable: "users", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_loved_tracks_xata_id_key: { + name: "_pgroll_new_loved_tracks_xata_id_key", + columns: ["xata_id"], + }, + loved_tracks__pgroll_new_uri_key: { + name: "loved_tracks__pgroll_new_uri_key", + columns: ["uri"], + }, + }, + columns: [ + { + name: "track_id", + type: "link", + link: { table: "tracks" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"tracks"}', + }, + { + name: "uri", + type: "text", + notNull: false, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "user_id", + type: "link", + link: { table: "users" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"users"}', + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "playlist_tracks", + checkConstraints: { + playlist_tracks_xata_id_length_xata_id: { + name: "playlist_tracks_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + playlist_id_link: { + name: "playlist_id_link", + columns: ["playlist_id"], + referencedTable: "playlists", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, + track_id_link: { + name: "track_id_link", + columns: ["track_id"], + referencedTable: "tracks", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_playlist_tracks_xata_id_key: { + name: "_pgroll_new_playlist_tracks_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ + { + name: "playlist_id", + type: "link", + link: { table: "playlists" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"playlists"}', + }, + { + name: "track_id", + type: "link", + link: { table: "tracks" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"tracks"}', + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "playlists", + checkConstraints: { + playlists_xata_id_length_xata_id: { + name: "playlists_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + created_by_link: { + name: "created_by_link", + columns: ["created_by"], + referencedTable: "users", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_playlists_xata_id_key: { + name: "_pgroll_new_playlists_xata_id_key", + columns: ["xata_id"], + }, + playlists__pgroll_new_uri_key: { + name: "playlists__pgroll_new_uri_key", + columns: ["uri"], + }, + playlists_apple_music_link_unique: { + name: "playlists_apple_music_link_unique", + columns: ["apple_music_link"], + }, + playlists_spotify_link_unique: { + name: "playlists_spotify_link_unique", + columns: ["spotify_link"], + }, + playlists_tidal_link_unique: { + name: "playlists_tidal_link_unique", + columns: ["tidal_link"], + }, + }, + columns: [ + { + name: "apple_music_link", + type: "text", + notNull: false, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "created_by", + type: "link", + link: { table: "users" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"users"}', + }, + { + name: "description", + type: "text", + notNull: false, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "name", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "picture", + type: "text", + notNull: false, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "spotify_link", + type: "text", + notNull: false, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "tidal_link", + type: "text", + notNull: false, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "uri", + type: "text", + notNull: false, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "profile_shouts", + checkConstraints: { + profile_shouts_xata_id_length_xata_id: { + name: "profile_shouts_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + shout_id_link: { + name: "shout_id_link", + columns: ["shout_id"], + referencedTable: "shouts", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, + user_id_link: { + name: "user_id_link", + columns: ["user_id"], + referencedTable: "users", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_profile_shouts_xata_id_key: { + name: "_pgroll_new_profile_shouts_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ + { + name: "shout_id", + type: "link", + link: { table: "shouts" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"shouts"}', + }, + { + name: "user_id", + type: "link", + link: { table: "users" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"users"}', + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "radios", + checkConstraints: { + radios_xata_id_length_xata_id: { + name: "radios_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: {}, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_radios_xata_id_key: { + name: "_pgroll_new_radios_xata_id_key", + columns: ["xata_id"], + }, + radios__pgroll_new_uri_key: { + name: "radios__pgroll_new_uri_key", + columns: ["uri"], + }, + radios__pgroll_new_url_key: { + name: "radios__pgroll_new_url_key", + columns: ["url"], + }, + }, + columns: [ + { + name: "description", + type: "text", + notNull: false, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "genre", + type: "text", + notNull: false, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "logo", + type: "text", + notNull: false, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "name", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "uri", + type: "text", + notNull: false, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "url", + type: "text", + notNull: true, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "website", + type: "text", + notNull: false, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "s3_bucket", + checkConstraints: { + s3_xata_id_length_xata_id: { + name: "s3_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + s3_token_id_link: { + name: "s3_token_id_link", + columns: ["s3_token_id"], + referencedTable: "s3_tokens", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + user_id_link: { + name: "user_id_link", + columns: ["user_id"], + referencedTable: "users", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_s3_xata_id_key: { + name: "_pgroll_new_s3_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ + { + name: "name", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "s3_token_id", + type: "link", + link: { table: "s3_tokens" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"s3_tokens"}', + }, + { + name: "user_id", + type: "link", + link: { table: "users" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"users"}', + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, + { + name: "s3_paths", + checkConstraints: { + s3_paths_xata_id_length_xata_id: { + name: "s3_paths_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + s3_bucket_id_link: { + name: "s3_bucket_id_link", + columns: ["s3_bucket_id"], + referencedTable: "s3_bucket", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + track_id_link: { + name: "track_id_link", + columns: ["track_id"], + referencedTable: "tracks", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_s3_paths_xata_id_key: { + name: "_pgroll_new_s3_paths_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ + { + name: "s3_bucket_id", + type: "link", + link: { table: "s3_bucket" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"s3_bucket"}', + }, + { + name: "track_id", + type: "link", + link: { table: "tracks" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"tracks"}', + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, unique: false, - defaultValue: null, + defaultValue: "now()", comment: "", }, { - name: "spotify_link", - type: "text", - notNull: false, - unique: true, - defaultValue: null, + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", comment: "", }, + ], + }, + { + name: "s3_tokens", + checkConstraints: { + s3_tokens_xata_id_length_xata_id: { + name: "s3_tokens_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: {}, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_s3_tokens_xata_id_key: { + name: "_pgroll_new_s3_tokens_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ { - name: "tidal_link", + name: "client_access_key", type: "text", - notNull: false, - unique: true, + notNull: true, + unique: false, defaultValue: null, comment: "", }, { - name: "uri", + name: "secret_access_key", type: "text", - notNull: false, - unique: true, + notNull: true, + unique: false, defaultValue: null, comment: "", }, @@ -1092,19 +2116,33 @@ const tables = [ ], }, { - name: "profile_shouts", + name: "scrobbles", checkConstraints: { - profile_shouts_xata_id_length_xata_id: { - name: "profile_shouts_xata_id_length_xata_id", + scrobbles_xata_id_length_xata_id: { + name: "scrobbles_xata_id_length_xata_id", columns: ["xata_id"], definition: "CHECK ((length(xata_id) < 256))", }, }, foreignKeys: { - shout_id_link: { - name: "shout_id_link", - columns: ["shout_id"], - referencedTable: "shouts", + album_id_link: { + name: "album_id_link", + columns: ["album_id"], + referencedTable: "albums", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, + artist_id_link: { + name: "artist_id_link", + columns: ["artist_id"], + referencedTable: "artists", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, + track_id_link: { + name: "track_id_link", + columns: ["track_id"], + referencedTable: "tracks", referencedColumns: ["xata_id"], onDelete: "SET NULL", }, @@ -1118,20 +2156,50 @@ const tables = [ }, primaryKey: [], uniqueConstraints: { - _pgroll_new_profile_shouts_xata_id_key: { - name: "_pgroll_new_profile_shouts_xata_id_key", + _pgroll_new_scrobbles_xata_id_key: { + name: "_pgroll_new_scrobbles_xata_id_key", columns: ["xata_id"], }, + scrobbles__pgroll_new_uri_key: { + name: "scrobbles__pgroll_new_uri_key", + columns: ["uri"], + }, }, columns: [ { - name: "shout_id", + name: "album_id", type: "link", - link: { table: "shouts" }, + link: { table: "albums" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"albums"}', + }, + { + name: "artist_id", + type: "link", + link: { table: "artists" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"artists"}', + }, + { + name: "track_id", + type: "link", + link: { table: "tracks" }, notNull: true, unique: false, defaultValue: null, - comment: '{"xata.link":"shouts"}', + comment: '{"xata.link":"tracks"}', + }, + { + name: "uri", + type: "text", + notNull: false, + unique: true, + defaultValue: null, + comment: "", }, { name: "user_id", @@ -1177,10 +2245,10 @@ const tables = [ ], }, { - name: "radios", + name: "sftp", checkConstraints: { - radios_xata_id_length_xata_id: { - name: "radios_xata_id_length_xata_id", + sftp_xata_id_length_xata_id: { + name: "sftp_xata_id_length_xata_id", columns: ["xata_id"], definition: "CHECK ((length(xata_id) < 256))", }, @@ -1188,76 +2256,64 @@ const tables = [ foreignKeys: {}, primaryKey: [], uniqueConstraints: { - _pgroll_new_radios_xata_id_key: { - name: "_pgroll_new_radios_xata_id_key", + _pgroll_new_sftp_xata_id_key: { + name: "_pgroll_new_sftp_xata_id_key", columns: ["xata_id"], }, - radios__pgroll_new_uri_key: { - name: "radios__pgroll_new_uri_key", - columns: ["uri"], - }, - radios__pgroll_new_url_key: { - name: "radios__pgroll_new_url_key", - columns: ["url"], - }, }, columns: [ { - name: "description", - type: "text", - notNull: false, + name: "xata_createdat", + type: "datetime", + notNull: true, unique: false, - defaultValue: null, + defaultValue: "now()", comment: "", }, { - name: "genre", + name: "xata_id", type: "text", - notNull: false, - unique: false, - defaultValue: null, + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", comment: "", }, { - name: "logo", - type: "text", - notNull: false, + name: "xata_updatedat", + type: "datetime", + notNull: true, unique: false, - defaultValue: null, + defaultValue: "now()", comment: "", }, { - name: "name", - type: "text", + name: "xata_version", + type: "int", notNull: true, unique: false, - defaultValue: null, - comment: "", - }, - { - name: "uri", - type: "text", - notNull: false, - unique: true, - defaultValue: null, + defaultValue: "0", comment: "", }, - { - name: "url", - type: "text", - notNull: true, - unique: true, - defaultValue: null, - comment: "", + ], + }, + { + name: "sftp_access", + checkConstraints: { + sftp_access_xata_id_length_xata_id: { + name: "sftp_access_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", }, - { - name: "website", - type: "text", - notNull: false, - unique: false, - defaultValue: null, - comment: "", + }, + foreignKeys: {}, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_sftp_access_xata_id_key: { + name: "_pgroll_new_sftp_access_xata_id_key", + columns: ["xata_id"], }, + }, + columns: [ { name: "xata_createdat", type: "datetime", @@ -1293,73 +2349,54 @@ const tables = [ ], }, { - name: "scrobbles", + name: "sftp_path", checkConstraints: { - scrobbles_xata_id_length_xata_id: { - name: "scrobbles_xata_id_length_xata_id", + sftp_path_xata_id_length_xata_id: { + name: "sftp_path_xata_id_length_xata_id", columns: ["xata_id"], definition: "CHECK ((length(xata_id) < 256))", }, }, foreignKeys: { - album_id_link: { - name: "album_id_link", - columns: ["album_id"], - referencedTable: "albums", - referencedColumns: ["xata_id"], - onDelete: "SET NULL", - }, - artist_id_link: { - name: "artist_id_link", - columns: ["artist_id"], - referencedTable: "artists", + sftp_id_link: { + name: "sftp_id_link", + columns: ["sftp_id"], + referencedTable: "sftp", referencedColumns: ["xata_id"], - onDelete: "SET NULL", + onDelete: "CASCADE", }, track_id_link: { name: "track_id_link", columns: ["track_id"], referencedTable: "tracks", referencedColumns: ["xata_id"], - onDelete: "SET NULL", - }, - user_id_link: { - name: "user_id_link", - columns: ["user_id"], - referencedTable: "users", - referencedColumns: ["xata_id"], - onDelete: "SET NULL", + onDelete: "CASCADE", }, }, primaryKey: [], uniqueConstraints: { - _pgroll_new_scrobbles_xata_id_key: { - name: "_pgroll_new_scrobbles_xata_id_key", + _pgroll_new_sftp_path_xata_id_key: { + name: "_pgroll_new_sftp_path_xata_id_key", columns: ["xata_id"], }, - scrobbles__pgroll_new_uri_key: { - name: "scrobbles__pgroll_new_uri_key", - columns: ["uri"], - }, }, columns: [ { - name: "album_id", - type: "link", - link: { table: "albums" }, - notNull: false, + name: "path", + type: "text", + notNull: true, unique: false, defaultValue: null, - comment: '{"xata.link":"albums"}', + comment: "", }, { - name: "artist_id", + name: "sftp_id", type: "link", - link: { table: "artists" }, - notNull: false, + link: { table: "sftp" }, + notNull: true, unique: false, defaultValue: null, - comment: '{"xata.link":"artists"}', + comment: '{"xata.link":"sftp"}', }, { name: "track_id", @@ -1370,23 +2407,6 @@ const tables = [ defaultValue: null, comment: '{"xata.link":"tracks"}', }, - { - name: "uri", - type: "text", - notNull: false, - unique: true, - defaultValue: null, - comment: "", - }, - { - name: "user_id", - type: "link", - link: { table: "users" }, - notNull: true, - unique: false, - defaultValue: null, - comment: '{"xata.link":"users"}', - }, { name: "xata_createdat", type: "datetime", @@ -2877,6 +3897,27 @@ export type ArtistTracksRecord = ArtistTracks & XataRecord; export type Artists = InferredTypes["artists"]; export type ArtistsRecord = Artists & XataRecord; +export type BuiltinStoragePaths = InferredTypes["builtin_storage_paths"]; +export type BuiltinStoragePathsRecord = BuiltinStoragePaths & XataRecord; + +export type Dropbox = InferredTypes["dropbox"]; +export type DropboxRecord = Dropbox & XataRecord; + +export type DropboxPaths = InferredTypes["dropbox_paths"]; +export type DropboxPathsRecord = DropboxPaths & XataRecord; + +export type DropboxTokens = InferredTypes["dropbox_tokens"]; +export type DropboxTokensRecord = DropboxTokens & XataRecord; + +export type GoogleDrive = InferredTypes["google_drive"]; +export type GoogleDriveRecord = GoogleDrive & XataRecord; + +export type GoogleDrivePaths = InferredTypes["google_drive_paths"]; +export type GoogleDrivePathsRecord = GoogleDrivePaths & XataRecord; + +export type GoogleDriveTokens = InferredTypes["google_drive_tokens"]; +export type GoogleDriveTokensRecord = GoogleDriveTokens & XataRecord; + export type LovedTracks = InferredTypes["loved_tracks"]; export type LovedTracksRecord = LovedTracks & XataRecord; @@ -2892,9 +3933,27 @@ export type ProfileShoutsRecord = ProfileShouts & XataRecord; export type Radios = InferredTypes["radios"]; export type RadiosRecord = Radios & XataRecord; +export type S3Bucket = InferredTypes["s3_bucket"]; +export type S3BucketRecord = S3Bucket & XataRecord; + +export type S3Paths = InferredTypes["s3_paths"]; +export type S3PathsRecord = S3Paths & XataRecord; + +export type S3Tokens = InferredTypes["s3_tokens"]; +export type S3TokensRecord = S3Tokens & XataRecord; + export type Scrobbles = InferredTypes["scrobbles"]; export type ScrobblesRecord = Scrobbles & XataRecord; +export type Sftp = InferredTypes["sftp"]; +export type SftpRecord = Sftp & XataRecord; + +export type SftpAccess = InferredTypes["sftp_access"]; +export type SftpAccessRecord = SftpAccess & XataRecord; + +export type SftpPath = InferredTypes["sftp_path"]; +export type SftpPathRecord = SftpPath & XataRecord; + export type ShoutLikes = InferredTypes["shout_likes"]; export type ShoutLikesRecord = ShoutLikes & XataRecord; @@ -2942,12 +4001,25 @@ export type DatabaseSchema = { artist_tags: ArtistTagsRecord; artist_tracks: ArtistTracksRecord; artists: ArtistsRecord; + builtin_storage_paths: BuiltinStoragePathsRecord; + dropbox: DropboxRecord; + dropbox_paths: DropboxPathsRecord; + dropbox_tokens: DropboxTokensRecord; + google_drive: GoogleDriveRecord; + google_drive_paths: GoogleDrivePathsRecord; + google_drive_tokens: GoogleDriveTokensRecord; loved_tracks: LovedTracksRecord; playlist_tracks: PlaylistTracksRecord; playlists: PlaylistsRecord; profile_shouts: ProfileShoutsRecord; radios: RadiosRecord; + s3_bucket: S3BucketRecord; + s3_paths: S3PathsRecord; + s3_tokens: S3TokensRecord; scrobbles: ScrobblesRecord; + sftp: SftpRecord; + sftp_access: SftpAccessRecord; + sftp_path: SftpPathRecord; shout_likes: ShoutLikesRecord; shout_reports: ShoutReportsRecord; shouts: ShoutsRecord; diff --git a/rockskyweb/bun.lock b/rockskyweb/bun.lock index 572c3dae..8199a4e3 100644 --- a/rockskyweb/bun.lock +++ b/rockskyweb/bun.lock @@ -7,6 +7,7 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@hookform/resolvers": "^4.0.0", + "@iconify-json/teenyicons": "^1.2.2", "@styled-icons/bootstrap": "^10.47.0", "@styled-icons/boxicons-logos": "^10.47.0", "@styled-icons/boxicons-regular": "^10.47.0", @@ -244,6 +245,10 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.1", "", {}, "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA=="], + "@iconify-json/teenyicons": ["@iconify-json/teenyicons@1.2.2", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-Do08DrvNpT+pKVeyFqn7nZiIviAAY8KbduSfpNKzE1bgVekAIJ/AAJtOBSUFpV4vTk+hXw195+jmCv8/0cJSKA=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + "@joshwooding/vite-plugin-react-docgen-typescript": ["@joshwooding/vite-plugin-react-docgen-typescript@0.4.2", "", { "dependencies": { "magic-string": "^0.27.0", "react-docgen-typescript": "^2.2.2" }, "peerDependencies": { "typescript": ">= 4.3.x", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["typescript"] }, "sha512-feQ+ntr+8hbVudnsTUapiMN9q8T90XA1d5jn9QzY09sNoj4iD9wi0PY1vsBFTda4ZjEaxRK9S81oarR2nj7TFQ=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], diff --git a/rockskyweb/package.json b/rockskyweb/package.json index 0a364937..b621bb3f 100644 --- a/rockskyweb/package.json +++ b/rockskyweb/package.json @@ -17,6 +17,7 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@hookform/resolvers": "^4.0.0", + "@iconify-json/teenyicons": "^1.2.2", "@styled-icons/bootstrap": "^10.47.0", "@styled-icons/boxicons-logos": "^10.47.0", "@styled-icons/boxicons-regular": "^10.47.0", diff --git a/rockskyweb/src/App.tsx b/rockskyweb/src/App.tsx index 010dadb4..9d917f3e 100644 --- a/rockskyweb/src/App.tsx +++ b/rockskyweb/src/App.tsx @@ -1,6 +1,8 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; import AlbumPage from "./pages/album"; import ArtistPage from "./pages/artist"; +import Dropbox from "./pages/dropbox"; +import GoogleDrive from "./pages/googledrive"; import HomePage from "./pages/home"; import PlaylistPage from "./pages/playlist"; import ProfilePage from "./pages/profile"; @@ -20,6 +22,8 @@ function App() { element={} /> } /> + } /> + } /> ); diff --git a/rockskyweb/src/components/Icons/Dropbox.tsx b/rockskyweb/src/components/Icons/Dropbox.tsx new file mode 100644 index 00000000..1421879e --- /dev/null +++ b/rockskyweb/src/components/Icons/Dropbox.tsx @@ -0,0 +1,15 @@ +const Dropbox = () => ( + + + +); + +export default Dropbox; diff --git a/rockskyweb/src/components/Icons/GoogleDrive.tsx b/rockskyweb/src/components/Icons/GoogleDrive.tsx new file mode 100644 index 00000000..25ef6a3e --- /dev/null +++ b/rockskyweb/src/components/Icons/GoogleDrive.tsx @@ -0,0 +1,35 @@ +const GoogleDrive = () => ( + + + + + + + + +); + +export default GoogleDrive; diff --git a/rockskyweb/src/components/ScrobblesAreaChart/ScrobblesAreaChart.tsx b/rockskyweb/src/components/ScrobblesAreaChart/ScrobblesAreaChart.tsx index 981c87a5..ff9019ed 100644 --- a/rockskyweb/src/components/ScrobblesAreaChart/ScrobblesAreaChart.tsx +++ b/rockskyweb/src/components/ScrobblesAreaChart/ScrobblesAreaChart.tsx @@ -105,7 +105,13 @@ function ScrobblesAreaChart() { { + const did = localStorage.getItem("did"); + if (!did) { + return; + } + + const response = await fetch(`${API_URL}/googledrive/login`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + const data = await response.json(); + if (data.authUrl) { + window.location.href = data.authUrl; + } + }; + + const onSelectDropbox = async () => { + const did = localStorage.getItem("did"); + if (!did) { + return; + } + + const response = await fetch(`${API_URL}/dropbox/login`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + const data = await response.json(); + if (data.redirectUri) { + window.location.href = data.redirectUri; + } + }; + + return ( +
+ Cloud Drive + +
+ +
+
+ Google Drive +
+
+ +
+ +
+
Dropbox
+
+
+ ); +} + +export default CloudDrive; diff --git a/rockskyweb/src/layouts/CloudDrive/index.tsx b/rockskyweb/src/layouts/CloudDrive/index.tsx new file mode 100644 index 00000000..fc659cf4 --- /dev/null +++ b/rockskyweb/src/layouts/CloudDrive/index.tsx @@ -0,0 +1,3 @@ +import CloudDrive from "./CloudDrive"; + +export default CloudDrive; diff --git a/rockskyweb/src/layouts/Main.tsx b/rockskyweb/src/layouts/Main.tsx index ae060840..b42cdec8 100644 --- a/rockskyweb/src/layouts/Main.tsx +++ b/rockskyweb/src/layouts/Main.tsx @@ -10,6 +10,7 @@ import { profileAtom } from "../atoms/profile"; import ScrobblesAreaChart from "../components/ScrobblesAreaChart"; import { API_URL } from "../consts"; import useProfile from "../hooks/useProfile"; +import CloudDrive from "./CloudDrive"; import ExternalLinks from "./ExternalLinks"; import Navbar from "./Navbar"; import Search from "./Search"; @@ -148,10 +149,11 @@ function Main({ children }: { children: React.ReactNode }) { padding: 20, }} > -
+
{jwt && profile && !profile.spotifyConnected && } + {jwt && profile && } {!jwt && (
diff --git a/rockskyweb/src/layouts/SpotifyLogin/SpotifyLogin.tsx b/rockskyweb/src/layouts/SpotifyLogin/SpotifyLogin.tsx index 6d10dbee..f467f98e 100644 --- a/rockskyweb/src/layouts/SpotifyLogin/SpotifyLogin.tsx +++ b/rockskyweb/src/layouts/SpotifyLogin/SpotifyLogin.tsx @@ -112,8 +112,6 @@ function SpotifyLogin() { <>
{ + return
dropbox
; +}; + +export default Dropbox; diff --git a/rockskyweb/src/pages/dropbox/index.tsx b/rockskyweb/src/pages/dropbox/index.tsx new file mode 100644 index 00000000..77d0232f --- /dev/null +++ b/rockskyweb/src/pages/dropbox/index.tsx @@ -0,0 +1,3 @@ +import Dropbox from "./Dropbox"; + +export default Dropbox; diff --git a/rockskyweb/src/pages/googledrive/GoogleDrive.tsx b/rockskyweb/src/pages/googledrive/GoogleDrive.tsx new file mode 100644 index 00000000..73a557d1 --- /dev/null +++ b/rockskyweb/src/pages/googledrive/GoogleDrive.tsx @@ -0,0 +1,7 @@ +import Main from "../../layouts/Main"; + +const GoogleDrive = () => { + return
Google drive
; +}; + +export default GoogleDrive; diff --git a/rockskyweb/src/pages/googledrive/index.tsx b/rockskyweb/src/pages/googledrive/index.tsx new file mode 100644 index 00000000..7b289ff0 --- /dev/null +++ b/rockskyweb/src/pages/googledrive/index.tsx @@ -0,0 +1,3 @@ +import GoogleDrive from "./GoogleDrive"; + +export default GoogleDrive; -- 2.51.2