A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz

add new services for dropbox and google drive

+4102 -8
+62
Cargo.lock
··· 1257 1257 checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 1258 1258 1259 1259 [[package]] 1260 + name = "dropbox" 1261 + version = "0.1.0" 1262 + dependencies = [ 1263 + "actix-web", 1264 + "aes", 1265 + "anyhow", 1266 + "async-nats", 1267 + "chrono", 1268 + "ctr", 1269 + "dotenv", 1270 + "hex", 1271 + "jsonwebtoken", 1272 + "owo-colors", 1273 + "redis", 1274 + "reqwest", 1275 + "serde", 1276 + "serde_json", 1277 + "sqlx", 1278 + "tokio", 1279 + "tokio-stream", 1280 + ] 1281 + 1282 + [[package]] 1260 1283 name = "duckdb" 1261 1284 version = "1.2.0" 1262 1285 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1614 1637 version = "0.3.2" 1615 1638 source = "registry+https://github.com/rust-lang/crates.io-index" 1616 1639 checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 1640 + 1641 + [[package]] 1642 + name = "googledrive" 1643 + version = "0.1.0" 1644 + dependencies = [ 1645 + "actix-web", 1646 + "aes", 1647 + "anyhow", 1648 + "async-nats", 1649 + "chrono", 1650 + "ctr", 1651 + "dotenv", 1652 + "hex", 1653 + "jsonwebtoken", 1654 + "owo-colors", 1655 + "redis", 1656 + "reqwest", 1657 + "serde", 1658 + "serde_json", 1659 + "serde_urlencoded", 1660 + "sqlx", 1661 + "tokio", 1662 + "tokio-stream", 1663 + ] 1617 1664 1618 1665 [[package]] 1619 1666 name = "h2" ··· 3528 3575 "sync_wrapper", 3529 3576 "tokio", 3530 3577 "tokio-rustls", 3578 + "tokio-util", 3531 3579 "tower", 3532 3580 "tower-service", 3533 3581 "url", 3534 3582 "wasm-bindgen", 3535 3583 "wasm-bindgen-futures", 3584 + "wasm-streams", 3536 3585 "web-sys", 3537 3586 "webpki-roots", 3538 3587 "windows-registry", ··· 4891 4940 checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 4892 4941 dependencies = [ 4893 4942 "unicode-ident", 4943 + ] 4944 + 4945 + [[package]] 4946 + name = "wasm-streams" 4947 + version = "0.4.2" 4948 + source = "registry+https://github.com/rust-lang/crates.io-index" 4949 + checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 4950 + dependencies = [ 4951 + "futures-util", 4952 + "js-sys", 4953 + "wasm-bindgen", 4954 + "wasm-bindgen-futures", 4955 + "web-sys", 4894 4956 ] 4895 4957 4896 4958 [[package]]
+1
crates/dropbox/.gitignore
··· 1 + .env
+38
crates/dropbox/Cargo.toml
··· 1 + [package] 2 + name = "dropbox" 3 + version = "0.1.0" 4 + authors.workspace = true 5 + edition.workspace = true 6 + license.workspace = true 7 + repository.workspace = true 8 + 9 + [dependencies] 10 + actix-web = "4.9.0" 11 + aes = "0.8.4" 12 + anyhow = "1.0.96" 13 + async-nats = "0.39.0" 14 + chrono = { version = "0.4.39", features = ["serde"] } 15 + ctr = "0.9.2" 16 + dotenv = "0.15.0" 17 + hex = "0.4.3" 18 + jsonwebtoken = "9.3.1" 19 + owo-colors = "4.1.0" 20 + redis = "0.29.0" 21 + reqwest = { version = "0.12.12", features = [ 22 + "rustls-tls", 23 + "json", 24 + "multipart", 25 + "stream", 26 + ], default-features = false } 27 + serde = { version = "1.0.217", features = ["derive"] } 28 + serde_json = "1.0.139" 29 + sqlx = { version = "0.8.3", features = [ 30 + "runtime-tokio", 31 + "tls-rustls", 32 + "postgres", 33 + "chrono", 34 + "derive", 35 + "macros", 36 + ] } 37 + tokio = { version = "1.43.0", features = ["full"] } 38 + tokio-stream = { version = "0.1.17", features = ["full"] }
+91
crates/dropbox/src/client.rs
··· 1 + use std::env; 2 + 3 + use actix_web::HttpResponse; 4 + use anyhow::Error; 5 + use reqwest::Client; 6 + use serde_json::json; 7 + 8 + use crate::types::{file::{EntryList, TemporaryLink}, token::AccessToken}; 9 + 10 + pub const BASE_URL: &str = "https://api.dropboxapi.com/2"; 11 + pub const CONTENT_URL: &str = "https://content.dropboxapi.com/2"; 12 + 13 + pub async fn get_access_token(refresh_token: &str) -> Result<AccessToken, Error> { 14 + let client = Client::new(); 15 + let res = client.post("https://api.dropboxapi.com/oauth2/token") 16 + .header("Content-Type", "application/x-www-form-urlencoded") 17 + .query(&[ 18 + ("grant_type", "refresh_token"), 19 + ("refresh_token", refresh_token), 20 + ("client_id", &env::var("DROPBOX_CLIENT_ID")?), 21 + ("client_secret", &env::var("DROPBOX_CLIENT_SECRET")?), 22 + ]) 23 + .send() 24 + .await?; 25 + 26 + Ok(res.json::<AccessToken>().await?) 27 + } 28 + 29 + pub struct DropboxClient { 30 + pub access_token: String, 31 + } 32 + 33 + impl DropboxClient { 34 + pub async fn new(refresh_token: &str) -> Result<Self, Error> { 35 + let res = get_access_token(refresh_token).await?; 36 + Ok(DropboxClient { 37 + access_token: res.access_token, 38 + }) 39 + } 40 + 41 + pub async fn get_files(&self, path: &str) -> Result<EntryList, Error> { 42 + let client = Client::new(); 43 + let res = client.post(&format!("{}/files/list_folder", BASE_URL)) 44 + .bearer_auth(&self.access_token) 45 + .json(&json!({ 46 + "path": path, 47 + "recursive": false, 48 + "include_media_info": true, 49 + "include_deleted": false, 50 + "include_has_explicit_shared_members": false, 51 + "include_mounted_folders": true, 52 + "include_non_downloadable_files": true, 53 + })) 54 + .send() 55 + .await?; 56 + 57 + Ok(res.json::<EntryList>().await?) 58 + } 59 + 60 + pub async fn download_file(&self, path: &str) -> Result<HttpResponse, Error> { 61 + let client = Client::new(); 62 + let res = client.post(&format!("{}/files/download", CONTENT_URL)) 63 + .bearer_auth(&self.access_token) 64 + .header("Dropbox-API-Arg", &json!({ "path": path }).to_string()) 65 + .send() 66 + .await?; 67 + 68 + let mut actix_response = HttpResponse::Ok(); 69 + 70 + // Forward headers 71 + for (key, value) in res.headers().iter() { 72 + actix_response.append_header((key.as_str(), value.to_str().unwrap_or(""))); 73 + } 74 + 75 + // Forward body 76 + let body = res.bytes_stream(); 77 + 78 + Ok(actix_response.streaming(body)) 79 + } 80 + 81 + pub async fn get_temporary_link(&self, path: &str) -> Result<TemporaryLink, Error> { 82 + let client = Client::new(); 83 + let res = client.post(&format!("{}/files/get_temporary_link", BASE_URL)) 84 + .bearer_auth(&self.access_token) 85 + .json(&json!({ "path": path })) 86 + .send() 87 + .await?; 88 + 89 + Ok(res.json::<TemporaryLink>().await?) 90 + } 91 + }
+22
crates/dropbox/src/crypto.rs
··· 1 + use std::env; 2 + 3 + use aes::{ 4 + cipher::{KeyIvInit, StreamCipher}, 5 + Aes256, 6 + }; 7 + use anyhow::Error; 8 + use hex::decode; 9 + 10 + type Aes256Ctr = ctr::Ctr64BE<Aes256>; 11 + 12 + pub fn decrypt_aes_256_ctr(encrypted_text: &str, key: &[u8]) -> Result<String, Error> { 13 + let iv = decode(env::var("SPOTIFY_ENCRYPTION_IV")?)?; 14 + let ciphertext = decode(encrypted_text)?; 15 + 16 + let mut cipher = 17 + Aes256Ctr::new_from_slices(key, &iv).map_err(|_| Error::msg("Invalid key or IV"))?; 18 + let mut decrypted_data = ciphertext.clone(); 19 + cipher.apply_keystream(&mut decrypted_data); 20 + 21 + Ok(String::from_utf8(decrypted_data)?) 22 + }
+99
crates/dropbox/src/handlers/files.rs
··· 1 + use std::{env, sync::{Arc, Mutex}}; 2 + 3 + use actix_web::{web, HttpRequest, HttpResponse}; 4 + use anyhow::Error; 5 + use sqlx::{Pool, Postgres}; 6 + use tokio_stream::StreamExt; 7 + 8 + use crate::{ 9 + client::DropboxClient, 10 + crypto::decrypt_aes_256_ctr, 11 + read_payload, 12 + repo::dropbox_token::find_dropbox_refresh_token, 13 + types::file::{DownloadFileParams, GetFilesAtParams, GetFilesParams}, 14 + }; 15 + 16 + pub const MUSIC_DIR: &str = "/Music"; 17 + 18 + pub async fn get_files(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc<Mutex<Pool<Postgres>>>) -> Result<HttpResponse, Error> { 19 + let body = read_payload!(payload); 20 + let params = serde_json::from_slice::<GetFilesParams>(&body)?; 21 + let pool = pool.lock().unwrap(); 22 + // let did = "did:plc:7vdlgi2bflelz7mmuxoqjfcr"; 23 + let refresh_token = find_dropbox_refresh_token(&pool, &params.did).await?; 24 + 25 + if refresh_token.is_none() { 26 + return Ok(HttpResponse::Unauthorized().finish()); 27 + } 28 + 29 + let refresh_token = decrypt_aes_256_ctr( 30 + &refresh_token.unwrap(), 31 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? 32 + )?; 33 + 34 + let client = DropboxClient::new(&refresh_token).await?; 35 + let entries = client.get_files(MUSIC_DIR).await?; 36 + 37 + Ok(HttpResponse::Ok().json(web::Json(entries))) 38 + } 39 + 40 + pub async fn get_files_at(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc<Mutex<Pool<Postgres>>>) -> Result<HttpResponse, Error> { 41 + let body = read_payload!(payload); 42 + let params = serde_json::from_slice::<GetFilesAtParams>(&body)?; 43 + let pool = pool.lock().unwrap(); 44 + let refresh_token = find_dropbox_refresh_token(&pool, &params.did).await?; 45 + 46 + if refresh_token.is_none() { 47 + return Ok(HttpResponse::Unauthorized().finish()); 48 + } 49 + 50 + let refresh_token = decrypt_aes_256_ctr( 51 + &refresh_token.unwrap(), 52 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? 53 + )?; 54 + 55 + let client = DropboxClient::new(&refresh_token).await?; 56 + let entries = client.get_files(&params.path).await?; 57 + 58 + Ok(HttpResponse::Ok().json(web::Json(entries))) 59 + } 60 + 61 + pub async fn download_file(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc<Mutex<Pool<Postgres>>>) -> Result<HttpResponse, Error> { 62 + let body = read_payload!(payload); 63 + let params = serde_json::from_slice::<DownloadFileParams>(&body)?; 64 + let pool = pool.lock().unwrap(); 65 + let refresh_token = find_dropbox_refresh_token(&pool, &params.did).await?; 66 + 67 + if refresh_token.is_none() { 68 + return Ok(HttpResponse::Unauthorized().finish()); 69 + } 70 + 71 + let refresh_token = decrypt_aes_256_ctr( 72 + &refresh_token.unwrap(), 73 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? 74 + )?; 75 + 76 + let client = DropboxClient::new(&refresh_token).await?; 77 + client.download_file(&params.path).await 78 + } 79 + 80 + pub async fn get_temporary_link(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc<Mutex<Pool<Postgres>>>) -> Result<HttpResponse, Error> { 81 + let body = read_payload!(payload); 82 + let params = serde_json::from_slice::<DownloadFileParams>(&body)?; 83 + let pool = pool.lock().unwrap(); 84 + let refresh_token = find_dropbox_refresh_token(&pool, &params.did).await?; 85 + 86 + if refresh_token.is_none() { 87 + return Ok(HttpResponse::Unauthorized().finish()); 88 + } 89 + 90 + let refresh_token = decrypt_aes_256_ctr( 91 + &refresh_token.unwrap(), 92 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? 93 + )?; 94 + 95 + let client = DropboxClient::new(&refresh_token).await?; 96 + let temporary_link = client.get_temporary_link(&params.path).await?; 97 + 98 + Ok(HttpResponse::Ok().json(web::Json(temporary_link))) 99 + }
+32
crates/dropbox/src/handlers/mod.rs
··· 1 + use std::sync::{Arc, Mutex}; 2 + 3 + use actix_web::{web, HttpRequest, HttpResponse}; 4 + use anyhow::Error; 5 + use files::{download_file, get_files, get_files_at, get_temporary_link}; 6 + use sqlx::{Pool, Postgres}; 7 + 8 + pub mod files; 9 + 10 + #[macro_export] 11 + macro_rules! read_payload { 12 + ($payload:expr) => {{ 13 + let mut body = Vec::new(); 14 + while let Some(chunk) = $payload.next().await { 15 + match chunk { 16 + Ok(bytes) => body.extend_from_slice(&bytes), 17 + Err(err) => return Err(err.into()), 18 + } 19 + } 20 + body 21 + }}; 22 + } 23 + 24 + pub async fn handle(method: &str, payload: &mut web::Payload, req: &HttpRequest, conn: Arc<Mutex<Pool<Postgres>>>) -> Result<HttpResponse, Error> { 25 + match method { 26 + "dropbox.getFiles" => get_files(payload, req, conn.clone()).await, 27 + "dropbox.getFilesAt" => get_files_at(payload, req, conn.clone()).await, 28 + "dropbox.downloadFile" => download_file(payload, req, conn.clone()).await, 29 + "dropbox.getTemporaryLink" => get_temporary_link(payload, req, conn.clone()).await, 30 + _ => return Err(anyhow::anyhow!("Method not found")), 31 + } 32 + }
+67
crates/dropbox/src/main.rs
··· 1 + use std::{env, sync::{Arc, Mutex}}; 2 + use actix_web::{get, post, web::{self, Data}, App, HttpRequest, HttpResponse, HttpServer, Responder}; 3 + use anyhow::Error; 4 + use dotenv::dotenv; 5 + use handlers::handle; 6 + use owo_colors::OwoColorize; 7 + use serde_json::json; 8 + use sqlx::{postgres::PgPoolOptions, Pool, Postgres}; 9 + 10 + pub mod xata; 11 + pub mod crypto; 12 + pub mod handlers; 13 + pub mod repo; 14 + pub mod types; 15 + pub mod client; 16 + 17 + #[get("/")] 18 + async fn index(_req: HttpRequest) -> HttpResponse { 19 + HttpResponse::Ok().json(json!({ 20 + "server": "Rocksky Dropbox Server", 21 + "version": "0.1.0", 22 + })) 23 + } 24 + 25 + #[post("/{method}")] 26 + async fn call_method( 27 + data: web::Data<Arc<Mutex<Pool<Postgres>>>>, 28 + mut payload: web::Payload, 29 + req: HttpRequest) -> Result<impl Responder, actix_web::Error> { 30 + let method = req.match_info().get("method").unwrap_or("unknown"); 31 + println!("Method: {}", method.bright_green()); 32 + 33 + let conn = data.get_ref().clone(); 34 + handle(method, &mut payload, &req, conn).await 35 + .map_err(actix_web::error::ErrorInternalServerError) 36 + } 37 + 38 + #[tokio::main] 39 + async fn main() -> Result<(), Box<dyn std::error::Error>> { 40 + dotenv().ok(); 41 + 42 + let host = env::var("DROPBOX_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); 43 + let port = env::var("DROPBOX_PORT").unwrap_or_else(|_| "7881".to_string()); 44 + let addr = format!("{}:{}", host, port); 45 + 46 + let url = format!("http://{}", addr); 47 + println!("Listening on {}", url.bright_green()); 48 + 49 + let pool = PgPoolOptions::new().max_connections(5).connect(&env::var("XATA_POSTGRES_URL")?).await?; 50 + let conn = Arc::new(Mutex::new(pool)); 51 + 52 + let conn = conn.clone(); 53 + HttpServer::new(move || { 54 + App::new() 55 + .app_data(Data::new(conn.clone())) 56 + .service(index) 57 + .service(call_method) 58 + }) 59 + .bind(&addr)? 60 + .run() 61 + .await 62 + .map_err(Error::new)?; 63 + 64 + 65 + Ok(()) 66 + } 67 +
+22
crates/dropbox/src/repo/dropbox_token.rs
··· 1 + use anyhow::Error; 2 + use sqlx::{Pool, Postgres}; 3 + 4 + use crate::xata::dropbox_token::DropboxTokenWithDid; 5 + 6 + pub async fn find_dropbox_refresh_token(pool: &Pool<Postgres>, did: &str) -> Result<Option<String>, Error> { 7 + let results: Vec<DropboxTokenWithDid> = sqlx::query_as(r#" 8 + SELECT * FROM dropbox d 9 + LEFT JOIN users u ON d.user_id = u.xata_id 10 + LEFT JOIN dropbox_tokens dt ON d.dropbox_token_id = dt.xata_id 11 + WHERE u.did = $1 12 + "#) 13 + .bind(did) 14 + .fetch_all(pool) 15 + .await?; 16 + 17 + if results.len() == 0 { 18 + return Ok(None); 19 + } 20 + 21 + Ok(Some(results[0].refresh_token.clone())) 22 + }
+1
crates/dropbox/src/repo/mod.rs
··· 1 + pub mod dropbox_token;
+54
crates/dropbox/src/types/file.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + #[derive(Debug, Serialize, Deserialize, Default)] 4 + pub struct Entry { 5 + #[serde(rename = ".tag")] 6 + #[serde(skip_serializing_if = "Option::is_none")] 7 + pub tag: Option<String>, 8 + pub name: String, 9 + #[serde(skip_serializing_if = "Option::is_none")] 10 + pub client_modified: Option<String>, 11 + #[serde(skip_serializing_if = "Option::is_none")] 12 + pub server_modified: Option<String>, 13 + #[serde(skip_serializing_if = "Option::is_none")] 14 + pub rev: Option<String>, 15 + #[serde(skip_serializing_if = "Option::is_none")] 16 + pub size: Option<u64>, 17 + pub path_display: String, 18 + pub path_lower: String, 19 + #[serde(skip_serializing_if = "Option::is_none")] 20 + pub content_hash: Option<String>, 21 + #[serde(skip_serializing_if = "Option::is_none")] 22 + pub is_downloadable: Option<bool>, 23 + pub id: String, 24 + } 25 + 26 + #[derive(Debug, Serialize, Deserialize, Default)] 27 + pub struct EntryList { 28 + pub entries: Vec<Entry>, 29 + pub cursor: String, 30 + pub has_more: bool, 31 + } 32 + 33 + #[derive(Debug, Serialize, Deserialize, Default)] 34 + pub struct GetFilesParams { 35 + pub did: String, 36 + } 37 + 38 + #[derive(Debug, Serialize, Deserialize, Default)] 39 + pub struct GetFilesAtParams { 40 + pub did: String, 41 + pub path: String, 42 + } 43 + 44 + #[derive(Debug, Serialize, Deserialize, Default)] 45 + pub struct DownloadFileParams { 46 + pub did: String, 47 + pub path: String, 48 + } 49 + 50 + #[derive(Debug, Serialize, Deserialize, Default)] 51 + pub struct TemporaryLink { 52 + pub metadata: Entry, 53 + pub link: String, 54 + }
+2
crates/dropbox/src/types/mod.rs
··· 1 + pub mod file; 2 + pub mod token;
+8
crates/dropbox/src/types/token.rs
··· 1 + use serde::Deserialize; 2 + 3 + #[derive(Debug, Deserialize)] 4 + pub struct AccessToken { 5 + pub access_token: String, 6 + pub token_type: String, 7 + pub expires_in: u32, 8 + }
+14
crates/dropbox/src/xata/dropbox.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 5 + pub struct Dropbox { 6 + pub xata_id: String, 7 + pub dropbox_token_id: String, 8 + pub user_id: String, 9 + pub xata_version: i32, 10 + #[serde(with = "chrono::serde::ts_seconds")] 11 + pub xata_createdat: DateTime<Utc>, 12 + #[serde(with = "chrono::serde::ts_seconds")] 13 + pub xata_updatedat: DateTime<Utc>, 14 + }
+14
crates/dropbox/src/xata/dropbox_path.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 5 + pub struct DropboxPath { 6 + pub xata_id: String, 7 + pub dropbox_id: String, 8 + pub track_id: String, 9 + pub xata_version: i32, 10 + #[serde(with = "chrono::serde::ts_seconds")] 11 + pub xata_createdat: DateTime<Utc>, 12 + #[serde(with = "chrono::serde::ts_seconds")] 13 + pub xata_updatedat: DateTime<Utc>, 14 + }
+25
crates/dropbox/src/xata/dropbox_token.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 5 + pub struct DropboxToken { 6 + pub xata_id: String, 7 + pub xata_version: i32, 8 + pub refresh_token: String, 9 + #[serde(with = "chrono::serde::ts_seconds")] 10 + pub xata_createdat: DateTime<Utc>, 11 + #[serde(with = "chrono::serde::ts_seconds")] 12 + pub xata_updatedat: DateTime<Utc>, 13 + } 14 + 15 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 16 + pub struct DropboxTokenWithDid { 17 + pub xata_id: String, 18 + pub xata_version: i32, 19 + pub refresh_token: String, 20 + pub did: String, 21 + #[serde(with = "chrono::serde::ts_seconds")] 22 + pub xata_createdat: DateTime<Utc>, 23 + #[serde(with = "chrono::serde::ts_seconds")] 24 + pub xata_updatedat: DateTime<Utc>, 25 + }
+5
crates/dropbox/src/xata/mod.rs
··· 1 + pub mod dropbox; 2 + pub mod dropbox_path; 3 + pub mod dropbox_token; 4 + pub mod track; 5 + pub mod user;
+31
crates/dropbox/src/xata/track.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(Debug, sqlx::FromRow, Serialize, Deserialize, Clone)] 5 + pub struct Track { 6 + pub xata_id: String, 7 + pub title: String, 8 + pub artist: String, 9 + pub album_artist: String, 10 + pub album_art: Option<String>, 11 + pub album: String, 12 + pub track_number: i32, 13 + pub duration: i32, 14 + pub mb_id: Option<String>, 15 + pub youtube_link: Option<String>, 16 + pub spotify_link: Option<String>, 17 + pub tidal_link: Option<String>, 18 + pub apple_music_link: Option<String>, 19 + pub sha256: String, 20 + pub lyrics: Option<String>, 21 + pub composer: Option<String>, 22 + pub genre: Option<String>, 23 + pub disc_number: i32, 24 + pub copyright_message: Option<String>, 25 + pub label: Option<String>, 26 + pub uri: Option<String>, 27 + pub artist_uri: Option<String>, 28 + pub album_uri: Option<String>, 29 + #[serde(with = "chrono::serde::ts_seconds")] 30 + pub xata_createdat: DateTime<Utc>, 31 + }
+13
crates/dropbox/src/xata/user.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, sqlx::FromRow, Deserialize, Clone)] 5 + pub struct User { 6 + pub xata_id: String, 7 + pub display_name: String, 8 + pub did: String, 9 + pub handle: String, 10 + pub avatar: String, 11 + #[serde(with = "chrono::serde::ts_seconds")] 12 + pub xata_createdat: DateTime<Utc>, 13 + }
+9
crates/googledrive/.env.example
··· 1 + JWT_SECRET="" 2 + 3 + SPOTIFY_CLIENT_ID= 4 + SPOTIFY_CLIENT_SECRET= 5 + 6 + SPOTIFY_ENCRYPTION_KEY= 7 + SPOTIFY_ENCRYPTION_IV= 8 + 9 + XATA_POSTGRES_URL=""
+1
crates/googledrive/.gitignore
··· 1 + .env
+39
crates/googledrive/Cargo.toml
··· 1 + [package] 2 + name = "googledrive" 3 + version = "0.1.0" 4 + authors.workspace = true 5 + edition.workspace = true 6 + license.workspace = true 7 + repository.workspace = true 8 + 9 + [dependencies] 10 + actix-web = "4.9.0" 11 + aes = "0.8.4" 12 + anyhow = "1.0.96" 13 + async-nats = "0.39.0" 14 + chrono = { version = "0.4.39", features = ["serde"] } 15 + ctr = "0.9.2" 16 + dotenv = "0.15.0" 17 + hex = "0.4.3" 18 + jsonwebtoken = "9.3.1" 19 + owo-colors = "4.1.0" 20 + redis = "0.29.0" 21 + reqwest = { version = "0.12.12", features = [ 22 + "rustls-tls", 23 + "json", 24 + "multipart", 25 + "stream", 26 + ], default-features = false } 27 + serde = { version = "1.0.217", features = ["derive"] } 28 + serde_json = "1.0.139" 29 + serde_urlencoded = "0.7.1" 30 + sqlx = { version = "0.8.3", features = [ 31 + "runtime-tokio", 32 + "tls-rustls", 33 + "postgres", 34 + "chrono", 35 + "derive", 36 + "macros", 37 + ] } 38 + tokio = { version = "1.43.0", features = ["full"] } 39 + tokio-stream = { version = "0.1.17", features = ["full"] }
+97
crates/googledrive/src/client.rs
··· 1 + use std::env; 2 + 3 + use actix_web::HttpResponse; 4 + use anyhow::Error; 5 + use reqwest::Client; 6 + 7 + use crate::types::{file::FileList, token::AccessToken}; 8 + 9 + pub const BASE_URL: &str = "https://www.googleapis.com/drive/v3"; 10 + 11 + pub async fn get_access_token(refresh_token: &str) -> Result<AccessToken, Error> { 12 + let client = Client::new(); 13 + 14 + let params = [ 15 + ("grant_type", "refresh_token"), 16 + ("refresh_token", refresh_token), 17 + ("client_id", &env::var("GOOGLE_CLIENT_ID")?), 18 + ("client_secret", &env::var("GOOGLE_CLIENT_SECRET")?), 19 + ]; 20 + 21 + let body = serde_urlencoded::to_string(&params)?; 22 + let res = client.post("https://oauth2.googleapis.com/token") 23 + .header("Content-Type", "application/x-www-form-urlencoded") 24 + .header("Content-Length", body.len()) 25 + .body(body) 26 + .send() 27 + .await?; 28 + 29 + Ok(res.json::<AccessToken>().await?) 30 + } 31 + 32 + pub struct GoogleDriveClient { 33 + pub access_token: String, 34 + } 35 + 36 + impl GoogleDriveClient { 37 + pub async fn new(refresh_token: &str) -> Result<Self, Error> { 38 + let res = get_access_token(refresh_token).await?; 39 + Ok(Self { 40 + access_token: res.access_token, 41 + }) 42 + } 43 + 44 + pub async fn get_files(&self, name: &str) -> Result<FileList, Error> { 45 + let client = Client::new(); 46 + let url = format!("{}/files", BASE_URL); 47 + let res = client.get(&url) 48 + .bearer_auth(&self.access_token) 49 + .query(&[ 50 + ("q", format!("name='{}' and mimeType='application/vnd.google-apps.folder'", name).as_str()), 51 + ("fields", "files(id, name, mimeType, parents)"), 52 + ]) 53 + .send() 54 + .await?; 55 + 56 + Ok(res.json::<FileList>().await?) 57 + } 58 + 59 + pub async fn get_files_in_parents(&self, parent_id: &str) -> Result<FileList, Error> { 60 + let client = Client::new(); 61 + let url = format!("{}/files", BASE_URL); 62 + let res = client.get(&url) 63 + .bearer_auth(&self.access_token) 64 + .query(&[ 65 + ("q", format!("'{}' in parents", parent_id).as_str()), 66 + ("fields", "files(id, name, mimeType, parents)"), 67 + ]) 68 + .send() 69 + .await?; 70 + Ok(res.json::<FileList>().await?) 71 + } 72 + 73 + pub async fn download_file(&self, file_id: &str) -> Result<HttpResponse, Error> { 74 + let client = Client::new(); 75 + let url = format!("{}/files/{}", BASE_URL, file_id); 76 + let res = client.get(&url) 77 + .bearer_auth(&self.access_token) 78 + .query(&[ 79 + ("alt", "media"), 80 + ]) 81 + .send() 82 + .await?; 83 + 84 + 85 + let mut actix_response = HttpResponse::Ok(); 86 + 87 + // Forward headers 88 + for (key, value) in res.headers().iter() { 89 + actix_response.append_header((key.as_str(), value.to_str().unwrap_or(""))); 90 + } 91 + 92 + // Forward body 93 + let body = res.bytes_stream(); 94 + 95 + Ok(actix_response.streaming(body)) 96 + } 97 + }
+22
crates/googledrive/src/crypto.rs
··· 1 + use std::env; 2 + 3 + use aes::{ 4 + cipher::{KeyIvInit, StreamCipher}, 5 + Aes256, 6 + }; 7 + use anyhow::Error; 8 + use hex::decode; 9 + 10 + type Aes256Ctr = ctr::Ctr64BE<Aes256>; 11 + 12 + pub fn decrypt_aes_256_ctr(encrypted_text: &str, key: &[u8]) -> Result<String, Error> { 13 + let iv = decode(env::var("SPOTIFY_ENCRYPTION_IV")?)?; 14 + let ciphertext = decode(encrypted_text)?; 15 + 16 + let mut cipher = 17 + Aes256Ctr::new_from_slices(key, &iv).map_err(|_| Error::msg("Invalid key or IV"))?; 18 + let mut decrypted_data = ciphertext.clone(); 19 + cipher.apply_keystream(&mut decrypted_data); 20 + 21 + Ok(String::from_utf8(decrypted_data)?) 22 + }
+82
crates/googledrive/src/handlers/files.rs
··· 1 + use std::{env, sync::{Arc, Mutex}}; 2 + 3 + use actix_web::{web, HttpRequest, HttpResponse}; 4 + use anyhow::Error; 5 + use sqlx::{Pool, Postgres}; 6 + use tokio_stream::StreamExt; 7 + 8 + pub const MUSIC_DIR: &str = "Music"; 9 + 10 + use crate::{ 11 + client::GoogleDriveClient, 12 + crypto::decrypt_aes_256_ctr, 13 + read_payload, 14 + repo::google_drive_token::find_google_drive_refresh_token, types::file::{DownloadFileParams, GetFilesInParentsParams, GetFilesParams} 15 + }; 16 + 17 + pub async fn get_files(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc<Mutex<Pool<Postgres>>>) -> Result<HttpResponse, Error> { 18 + let body = read_payload!(payload); 19 + let params = serde_json::from_slice::<GetFilesParams>(&body)?; 20 + 21 + let pool = pool.lock().unwrap(); 22 + let refresh_token = find_google_drive_refresh_token(&pool, &params.did).await?; 23 + 24 + if refresh_token.is_none() { 25 + return Ok(HttpResponse::Unauthorized().finish()); 26 + } 27 + 28 + let refresh_token = decrypt_aes_256_ctr( 29 + &refresh_token.unwrap(), 30 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? 31 + )?; 32 + 33 + let client = GoogleDriveClient::new(&refresh_token).await?; 34 + let files = client.get_files(MUSIC_DIR).await?; 35 + // 12qiJpTzARyls_ICbIxqagnOGalRM1yF4 36 + // 1MdKbZE2U17qr2kScr9LJ8Yrh9dGxM8wM 37 + 38 + Ok(HttpResponse::Ok().json(web::Json(files))) 39 + } 40 + 41 + pub async fn get_files_in_parents(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc<Mutex<Pool<Postgres>>>) -> Result<HttpResponse, Error> { 42 + let body = read_payload!(payload); 43 + let params = serde_json::from_slice::<GetFilesInParentsParams>(&body)?; 44 + 45 + let pool = pool.lock().unwrap(); 46 + let refresh_token = find_google_drive_refresh_token(&pool, &params.did).await?; 47 + 48 + if refresh_token.is_none() { 49 + return Ok(HttpResponse::Unauthorized().finish()); 50 + } 51 + 52 + let refresh_token = decrypt_aes_256_ctr( 53 + &refresh_token.unwrap(), 54 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? 55 + )?; 56 + 57 + let client = GoogleDriveClient::new(&refresh_token).await?; 58 + let files = client.get_files_in_parents(&params.parent_id).await?; 59 + 60 + Ok(HttpResponse::Ok().json(web::Json(files))) 61 + } 62 + 63 + 64 + pub async fn download_file(payload: &mut web::Payload, _req: &HttpRequest, pool: Arc<Mutex<Pool<Postgres>>>) -> Result<HttpResponse, Error> { 65 + let body = read_payload!(payload); 66 + let params = serde_json::from_slice::<DownloadFileParams>(&body)?; 67 + 68 + let pool = pool.lock().unwrap(); 69 + let refresh_token = find_google_drive_refresh_token(&pool, &params.did).await?; 70 + 71 + if refresh_token.is_none() { 72 + return Ok(HttpResponse::Unauthorized().finish()); 73 + } 74 + 75 + let refresh_token = decrypt_aes_256_ctr( 76 + &refresh_token.unwrap(), 77 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? 78 + )?; 79 + 80 + let client = GoogleDriveClient::new(&refresh_token).await?; 81 + client.download_file(&params.file_id).await 82 + }
+33
crates/googledrive/src/handlers/mod.rs
··· 1 + use std::sync::{Arc, Mutex}; 2 + 3 + use actix_web::{web, HttpRequest, HttpResponse}; 4 + use anyhow::Error; 5 + use files::{download_file, get_files, get_files_in_parents}; 6 + use sqlx::{Pool, Postgres}; 7 + 8 + pub mod files; 9 + 10 + 11 + #[macro_export] 12 + macro_rules! read_payload { 13 + ($payload:expr) => {{ 14 + let mut body = Vec::new(); 15 + while let Some(chunk) = $payload.next().await { 16 + // skip if None 17 + match chunk { 18 + Ok(bytes) => body.extend_from_slice(&bytes), 19 + Err(err) => return Err(err.into()), 20 + } 21 + } 22 + body 23 + }}; 24 + } 25 + 26 + pub async fn handle(method: &str, payload: &mut web::Payload, req: &HttpRequest, conn: Arc<Mutex<Pool<Postgres>>>) -> Result<HttpResponse, Error> { 27 + match method { 28 + "googledrive.getFiles" => get_files(payload, req, conn.clone()).await, 29 + "googledrive.getFilesInParents" => get_files_in_parents(payload, req, conn.clone()).await, 30 + "googledrive.downloadFile" => download_file(payload, req, conn.clone()).await, 31 + _ => return Err(anyhow::anyhow!("Method not found")), 32 + } 33 + }
+77
crates/googledrive/src/main.rs
··· 1 + use std::{env, sync::{Arc, Mutex}}; 2 + use actix_web::{get, post, web::{self, Data}, App, HttpRequest, HttpResponse, HttpServer, Responder}; 3 + use anyhow::Error; 4 + use dotenv::dotenv; 5 + use handlers::handle; 6 + use owo_colors::OwoColorize; 7 + use serde_json::json; 8 + use sqlx::{postgres::PgPoolOptions, Pool, Postgres}; 9 + 10 + pub mod xata; 11 + pub mod crypto; 12 + pub mod handlers; 13 + pub mod repo; 14 + pub mod types; 15 + pub mod client; 16 + 17 + #[get("/")] 18 + async fn index(_req: HttpRequest) -> HttpResponse { 19 + HttpResponse::Ok().json(json!({ 20 + "server": "Rocksky GoogleDrive Server", 21 + "version": "0.1.0", 22 + })) 23 + } 24 + 25 + #[post("/{method}")] 26 + async fn call_method( 27 + data: web::Data<Arc<Mutex<Pool<Postgres>>>>, 28 + mut payload: web::Payload, 29 + req: HttpRequest) -> Result<impl Responder, actix_web::Error> { 30 + let method = req.match_info().get("method").unwrap_or("unknown"); 31 + println!("Method: {}", method.bright_green()); 32 + 33 + let conn = data.get_ref().clone(); 34 + handle(method, &mut payload, &req, conn).await 35 + .map_err(actix_web::error::ErrorInternalServerError) 36 + } 37 + 38 + 39 + #[tokio::main] 40 + async fn main() -> Result<(), Box<dyn std::error::Error>> { 41 + dotenv().ok(); 42 + 43 + /* 44 + let pool = PgPoolOptions::new().max_connections(5).connect(&env::var("XATA_POSTGRES_URL")?).await?; 45 + let refresh_token = find_google_drive_refresh_token(&pool, "did:plc:7vdlgi2bflelz7mmuxoqjfcr").await?; 46 + 47 + let refresh_token = decrypt_aes_256_ctr( 48 + &refresh_token.unwrap(), 49 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)? 50 + )?; 51 + 52 + println!("Refresh token: {}", refresh_token); 53 + */ 54 + let host = env::var("GOOGLE_DRIVE_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); 55 + let port = env::var("GOOGLE_DRIVE_PORT_PORT").unwrap_or_else(|_| "7880".to_string()); 56 + let addr = format!("{}:{}", host, port); 57 + 58 + let url = format!("http://{}", addr); 59 + println!("Listening on {}", url.bright_green()); 60 + 61 + let pool = PgPoolOptions::new().max_connections(5).connect(&env::var("XATA_POSTGRES_URL")?).await?; 62 + let conn = Arc::new(Mutex::new(pool)); 63 + 64 + let conn = conn.clone(); 65 + HttpServer::new(move || { 66 + App::new() 67 + .app_data(Data::new(conn.clone())) 68 + .service(index) 69 + .service(call_method) 70 + }) 71 + .bind(&addr)? 72 + .run() 73 + .await 74 + .map_err(Error::new)?; 75 + 76 + Ok(()) 77 + }
+22
crates/googledrive/src/repo/google_drive_token.rs
··· 1 + use sqlx::{Pool, Postgres}; 2 + use anyhow::Error; 3 + 4 + use crate::xata::google_drive_token::GoogleDriveTokenWithDid; 5 + 6 + pub async fn find_google_drive_refresh_token(pool: &Pool<Postgres>, did: &str) -> Result<Option<String>, Error> { 7 + let results: Vec<GoogleDriveTokenWithDid> = sqlx::query_as(r#" 8 + SELECT * FROM google_drive gd 9 + LEFT JOIN users u ON gd.user_id = u.xata_id 10 + LEFT JOIN google_drive_tokens gt ON gd.google_drive_token_id = gt.xata_id 11 + WHERE u.did = $1 12 + "#) 13 + .bind(did) 14 + .fetch_all(pool) 15 + .await?; 16 + 17 + if results.len() == 0 { 18 + return Ok(None); 19 + } 20 + 21 + Ok(Some(results[0].refresh_token.clone())) 22 + }
+1
crates/googledrive/src/repo/mod.rs
··· 1 + pub mod google_drive_token;
+32
crates/googledrive/src/types/file.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + #[derive(Debug, Serialize, Deserialize, Default)] 4 + pub struct File { 5 + pub id: String, 6 + pub name: String, 7 + #[serde(rename = "mimeType")] 8 + pub mime_type: String, 9 + pub parents: Vec<String>, 10 + } 11 + 12 + #[derive(Debug, Serialize, Deserialize)] 13 + pub struct FileList { 14 + pub files: Vec<File>, 15 + } 16 + 17 + #[derive(Debug, Serialize, Deserialize)] 18 + pub struct GetFilesParams { 19 + pub did: String, 20 + } 21 + 22 + #[derive(Debug, Serialize, Deserialize)] 23 + pub struct GetFilesInParentsParams { 24 + pub did: String, 25 + pub parent_id: String, 26 + } 27 + 28 + #[derive(Debug, Serialize, Deserialize)] 29 + pub struct DownloadFileParams { 30 + pub did: String, 31 + pub file_id: String, 32 + }
+2
crates/googledrive/src/types/mod.rs
··· 1 + pub mod file; 2 + pub mod token;
+10
crates/googledrive/src/types/token.rs
··· 1 + use serde::Deserialize; 2 + 3 + #[derive(Debug, Deserialize)] 4 + pub struct AccessToken { 5 + pub access_token: String, 6 + pub token_type: String, 7 + pub expires_in: u32, 8 + pub scope: String, 9 + pub refresh_token_expires_in: u32, 10 + }
+14
crates/googledrive/src/xata/google_drive.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 5 + pub struct GoogleDrive { 6 + pub xata_id: String, 7 + pub user_id: String, 8 + pub google_drive_token_id: String, 9 + pub xata_version: i32, 10 + #[serde(with = "chrono::serde::ts_seconds")] 11 + pub xata_createdat: DateTime<Utc>, 12 + #[serde(with = "chrono::serde::ts_seconds")] 13 + pub xata_updatedat: DateTime<Utc>, 14 + }
+14
crates/googledrive/src/xata/google_drive_path.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 5 + pub struct GoogleDrivePath { 6 + pub xata_id: String, 7 + pub google_drive_id: String, 8 + pub track_id: String, 9 + pub xata_version: i32, 10 + #[serde(with = "chrono::serde::ts_seconds")] 11 + pub xata_createdat: DateTime<Utc>, 12 + #[serde(with = "chrono::serde::ts_seconds")] 13 + pub xata_updatedat: DateTime<Utc>, 14 + }
+25
crates/googledrive/src/xata/google_drive_token.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 5 + pub struct GoogleDriveToken { 6 + pub xata_id: String, 7 + pub refresh_token: String, 8 + pub xata_version: i32, 9 + #[serde(with = "chrono::serde::ts_seconds")] 10 + pub xata_createdat: DateTime<Utc>, 11 + #[serde(with = "chrono::serde::ts_seconds")] 12 + pub xata_updatedat: DateTime<Utc>, 13 + } 14 + 15 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 16 + pub struct GoogleDriveTokenWithDid { 17 + pub xata_id: String, 18 + pub refresh_token: String, 19 + pub xata_version: i32, 20 + pub did: String, 21 + #[serde(with = "chrono::serde::ts_seconds")] 22 + pub xata_createdat: DateTime<Utc>, 23 + #[serde(with = "chrono::serde::ts_seconds")] 24 + pub xata_updatedat: DateTime<Utc>, 25 + }
+5
crates/googledrive/src/xata/mod.rs
··· 1 + pub mod google_drive; 2 + pub mod google_drive_path; 3 + pub mod google_drive_token; 4 + pub mod track; 5 + pub mod user;
+31
crates/googledrive/src/xata/track.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(Debug, sqlx::FromRow, Serialize, Deserialize, Clone)] 5 + pub struct Track { 6 + pub xata_id: String, 7 + pub title: String, 8 + pub artist: String, 9 + pub album_artist: String, 10 + pub album_art: Option<String>, 11 + pub album: String, 12 + pub track_number: i32, 13 + pub duration: i32, 14 + pub mb_id: Option<String>, 15 + pub youtube_link: Option<String>, 16 + pub spotify_link: Option<String>, 17 + pub tidal_link: Option<String>, 18 + pub apple_music_link: Option<String>, 19 + pub sha256: String, 20 + pub lyrics: Option<String>, 21 + pub composer: Option<String>, 22 + pub genre: Option<String>, 23 + pub disc_number: i32, 24 + pub copyright_message: Option<String>, 25 + pub label: Option<String>, 26 + pub uri: Option<String>, 27 + pub artist_uri: Option<String>, 28 + pub album_uri: Option<String>, 29 + #[serde(with = "chrono::serde::ts_seconds")] 30 + pub xata_createdat: DateTime<Utc>, 31 + }
+13
crates/googledrive/src/xata/user.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, sqlx::FromRow, Deserialize, Clone)] 5 + pub struct User { 6 + pub xata_id: String, 7 + pub display_name: String, 8 + pub did: String, 9 + pub handle: String, 10 + pub avatar: String, 11 + #[serde(with = "chrono::serde::ts_seconds")] 12 + pub xata_createdat: DateTime<Utc>, 13 + }
+42
rockskyapi/rocksky-auth/.xata/migrations/.ledger
··· 182 182 mig_cv55o3d2t0po8jv9tkkg 183 183 mig_cv55o952t0po8jv9tklg 184 184 sql_7fbe41bfacc536 185 + mig_cv8tro2glbhgau6cq5j0 186 + mig_cv8ts6o1g95li4qghh5g 187 + mig_cv8tsfkld6k2hsabcgqg 188 + mig_cv8tt0g1g95li4qghh6g 189 + mig_cv8ttl4ld6k2hsabcgsg 190 + mig_cv8tu7sld6k2hsabcgtg 191 + mig_cv8tufsld6k2hsabcgug 192 + mig_cv8tutiglbhgau6cq5n0 193 + mig_cv8tv8iglbhgau6cq5p0 194 + mig_cv8u07aglbhgau6cq5q0 195 + mig_cv8u0daglbhgau6cq5r0 196 + mig_cv8u5cqglbhgau6cq61g 197 + mig_cv8u6n01g95li4qghha0 198 + mig_cv8u7uqglbhgau6cq62g 199 + mig_cv8u8j2glbhgau6cq63g 200 + mig_cv8ub7cld6k2hsabch2g 201 + mig_cv8ue4kld6k2hsabch3g 202 + mig_cv8ueoaglbhgau6cq66g 203 + mig_cv8ufnqglbhgau6cq67g 204 + mig_cv8uh4aglbhgau6cq68g 205 + mig_cv8ujhqglbhgau6cq69g 206 + mig_cv8ukrg1g95li4qghhh0 207 + mig_cv8ulakld6k2hsabch4g 208 + mig_cv8umjo1g95li4qghhk0 209 + mig_cv8un7g1g95li4qghhl0 210 + mig_cv8unpo1g95li4qghhm0 211 + mig_cv8up2aglbhgau6cq6bg 212 + mig_cv8upbcld6k2hsabch6g 213 + mig_cv8uq1sld6k2hsabch7g 214 + mig_cv8uqh2glbhgau6cq6d0 215 + mig_cv8ur1kld6k2hsabch8g 216 + mig_cv8urh4ld6k2hsabch9g 217 + mig_cv8us9g1g95li4qghhq0 218 + mig_cv8usig1g95li4qghhr0 219 + mig_cv8utv81g95li4qghhs0 220 + mig_cv8uugqglbhgau6cq6e0 221 + mig_cv8uv2iglbhgau6cq6f0 222 + mig_cv8uveiglbhgau6cq6h0 223 + mig_cv8v8gcld6k2hsabcheg 224 + mig_cv8v92aglbhgau6cq6n0 225 + mig_cv8v9faglbhgau6cq6o0 226 + mig_cv8v9sqglbhgau6cq6p0
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tro2glbhgau6cq5j0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8tro2glbhgau6cq5j0", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "dropbox", 9 + "columns": [ 10 + { 11 + "name": "xata_createdat", 12 + "type": "timestamptz", 13 + "default": "now()" 14 + }, 15 + { 16 + "name": "xata_updatedat", 17 + "type": "timestamptz", 18 + "default": "now()" 19 + }, 20 + { 21 + "name": "xata_id", 22 + "type": "text", 23 + "check": { 24 + "name": "dropbox_xata_id_length_xata_id", 25 + "constraint": "length(\"xata_id\") < 256" 26 + }, 27 + "unique": true, 28 + "default": "'rec_' || xata_private.xid()" 29 + }, 30 + { 31 + "name": "xata_version", 32 + "type": "integer", 33 + "default": "0" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"dropbox\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8tro2glbhgau6cq5j0", 54 + "parent": "sql_7fbe41bfacc536", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T19:17:53.182563Z" 57 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ts6o1g95li4qghh5g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ts6o1g95li4qghh5g", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "google_drive", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "google_drive_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"google_drive\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8ts6o1g95li4qghh5g", 54 + "parent": "mig_cv8tro2glbhgau6cq5j0", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T19:18:52.959323Z" 57 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tsfkld6k2hsabcgqg.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8tsfkld6k2hsabcgqg", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "s3", 9 + "columns": [ 10 + { 11 + "name": "xata_version", 12 + "type": "integer", 13 + "default": "0" 14 + }, 15 + { 16 + "name": "xata_createdat", 17 + "type": "timestamptz", 18 + "default": "now()" 19 + }, 20 + { 21 + "name": "xata_updatedat", 22 + "type": "timestamptz", 23 + "default": "now()" 24 + }, 25 + { 26 + "name": "xata_id", 27 + "type": "text", 28 + "check": { 29 + "name": "s3_xata_id_length_xata_id", 30 + "constraint": "length(\"xata_id\") < 256" 31 + }, 32 + "unique": true, 33 + "default": "'rec_' || xata_private.xid()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"s3\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8tsfkld6k2hsabcgqg", 54 + "parent": "mig_cv8ts6o1g95li4qghh5g", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T19:19:27.411041Z" 57 + }
+19
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tt0g1g95li4qghh6g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8tt0g1g95li4qghh6g", 5 + "operations": [ 6 + { 7 + "rename_table": { 8 + "to": "s3_bucket", 9 + "from": "s3" 10 + } 11 + } 12 + ] 13 + }, 14 + "migrationType": "pgroll", 15 + "name": "mig_cv8tt0g1g95li4qghh6g", 16 + "parent": "mig_cv8tsfkld6k2hsabcgqg", 17 + "schema": "public", 18 + "startedAt": "2025-03-12T19:20:35.26839Z" 19 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ttl4ld6k2hsabcgsg.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ttl4ld6k2hsabcgsg", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "gcs_bucket", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "gcs_bucket_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"gcs_bucket\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8ttl4ld6k2hsabcgsg", 54 + "parent": "mig_cv8tt0g1g95li4qghh6g", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T19:21:56.287049Z" 57 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tu7sld6k2hsabcgtg.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8tu7sld6k2hsabcgtg", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "google_drive_tokens", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "google_drive_tokens_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"google_drive_tokens\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8tu7sld6k2hsabcgtg", 54 + "parent": "mig_cv8ttl4ld6k2hsabcgsg", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T19:23:12.701215Z" 57 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tufsld6k2hsabcgug.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8tufsld6k2hsabcgug", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "s3_tokens", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "s3_tokens_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"s3_tokens\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8tufsld6k2hsabcgug", 54 + "parent": "mig_cv8tu7sld6k2hsabcgtg", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T19:23:44.838476Z" 57 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tutiglbhgau6cq5n0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8tutiglbhgau6cq5n0", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "dropbox_tokens", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "dropbox_tokens_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"dropbox_tokens\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8tutiglbhgau6cq5n0", 54 + "parent": "mig_cv8tufsld6k2hsabcgug", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T19:24:39.515532Z" 57 + }
+18
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8tv8iglbhgau6cq5p0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8tv8iglbhgau6cq5p0", 5 + "operations": [ 6 + { 7 + "drop_table": { 8 + "name": "gcs_bucket" 9 + } 10 + } 11 + ] 12 + }, 13 + "migrationType": "pgroll", 14 + "name": "mig_cv8tv8iglbhgau6cq5p0", 15 + "parent": "mig_cv8tutiglbhgau6cq5n0", 16 + "schema": "public", 17 + "startedAt": "2025-03-12T19:25:23.135657Z" 18 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u07aglbhgau6cq5q0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8u07aglbhgau6cq5q0", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "dropbox_paths", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "dropbox_paths_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"dropbox_paths\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8u07aglbhgau6cq5q0", 54 + "parent": "mig_cv8tv8iglbhgau6cq5p0", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T19:27:25.612995Z" 57 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u0daglbhgau6cq5r0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8u0daglbhgau6cq5r0", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "s3_paths", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "s3_paths_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"s3_paths\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8u0daglbhgau6cq5r0", 54 + "parent": "mig_cv8u07aglbhgau6cq5q0", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T19:27:49.903669Z" 57 + }
+24
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u5cqglbhgau6cq61g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8u5cqglbhgau6cq61g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "dropbox_tokens", 10 + "column": { 11 + "name": "refresh_token", 12 + "type": "text", 13 + "comment": "" 14 + } 15 + } 16 + } 17 + ] 18 + }, 19 + "migrationType": "pgroll", 20 + "name": "mig_cv8u5cqglbhgau6cq61g", 21 + "parent": "mig_cv8u0daglbhgau6cq5r0", 22 + "schema": "public", 23 + "startedAt": "2025-03-12T19:38:28.311152Z" 24 + }
+24
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u6n01g95li4qghha0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8u6n01g95li4qghha0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "google_drive_tokens", 10 + "column": { 11 + "name": "refresh_token", 12 + "type": "text", 13 + "comment": "" 14 + } 15 + } 16 + } 17 + ] 18 + }, 19 + "migrationType": "pgroll", 20 + "name": "mig_cv8u6n01g95li4qghha0", 21 + "parent": "mig_cv8u5cqglbhgau6cq61g", 22 + "schema": "public", 23 + "startedAt": "2025-03-12T19:41:16.419896Z" 24 + }
+24
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u7uqglbhgau6cq62g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8u7uqglbhgau6cq62g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "s3_bucket", 10 + "column": { 11 + "name": "name", 12 + "type": "text", 13 + "comment": "" 14 + } 15 + } 16 + } 17 + ] 18 + }, 19 + "migrationType": "pgroll", 20 + "name": "mig_cv8u7uqglbhgau6cq62g", 21 + "parent": "mig_cv8u6n01g95li4qghha0", 22 + "schema": "public", 23 + "startedAt": "2025-03-12T19:43:56.410523Z" 24 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8u8j2glbhgau6cq63g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8u8j2glbhgau6cq63g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "s3_bucket", 10 + "column": { 11 + "name": "user_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"users\"}", 14 + "references": { 15 + "name": "user_id_link", 16 + "table": "users", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8u8j2glbhgau6cq63g", 27 + "parent": "mig_cv8u7uqglbhgau6cq62g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T19:45:17.407227Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ub7cld6k2hsabch2g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ub7cld6k2hsabch2g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "s3_bucket", 10 + "column": { 11 + "name": "s3_token_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"s3_tokens\"}", 14 + "references": { 15 + "name": "s3_token_id_link", 16 + "table": "s3_tokens", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8ub7cld6k2hsabch2g", 27 + "parent": "mig_cv8u8j2glbhgau6cq63g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T19:50:53.634179Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ue4kld6k2hsabch3g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ue4kld6k2hsabch3g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "dropbox", 10 + "column": { 11 + "name": "dropbox_token_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"dropbox_tokens\"}", 14 + "references": { 15 + "name": "dropbox_token_id_link", 16 + "table": "dropbox_tokens", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8ue4kld6k2hsabch3g", 27 + "parent": "mig_cv8ub7cld6k2hsabch2g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T19:57:06.582548Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ueoaglbhgau6cq66g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ueoaglbhgau6cq66g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "dropbox", 10 + "column": { 11 + "name": "user_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"users\"}", 14 + "references": { 15 + "name": "user_id_link", 16 + "table": "users", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8ueoaglbhgau6cq66g", 27 + "parent": "mig_cv8ue4kld6k2hsabch3g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T19:58:26.520533Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ufnqglbhgau6cq67g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ufnqglbhgau6cq67g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "google_drive", 10 + "column": { 11 + "name": "google_drive_token_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"google_drive_tokens\"}", 14 + "references": { 15 + "name": "google_drive_token_id_link", 16 + "table": "google_drive_tokens", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8ufnqglbhgau6cq67g", 27 + "parent": "mig_cv8ueoaglbhgau6cq66g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:00:32.127974Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uh4aglbhgau6cq68g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8uh4aglbhgau6cq68g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "google_drive", 10 + "column": { 11 + "name": "user_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"users\"}", 14 + "references": { 15 + "name": "user_id_link", 16 + "table": "users", 17 + "column": "xata_id", 18 + "on_delete": "SET NULL" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8uh4aglbhgau6cq68g", 27 + "parent": "mig_cv8ufnqglbhgau6cq67g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:03:29.416183Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ujhqglbhgau6cq69g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ujhqglbhgau6cq69g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "dropbox_paths", 10 + "column": { 11 + "name": "dropbox_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"dropbox\"}", 14 + "references": { 15 + "name": "dropbox_id_link", 16 + "table": "dropbox", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8ujhqglbhgau6cq69g", 27 + "parent": "mig_cv8uh4aglbhgau6cq68g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:08:40.286709Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ukrg1g95li4qghhh0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ukrg1g95li4qghhh0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "dropbox_paths", 10 + "column": { 11 + "name": "track_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"tracks\"}", 14 + "references": { 15 + "name": "track_id_link", 16 + "table": "tracks", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8ukrg1g95li4qghhh0", 27 + "parent": "mig_cv8ujhqglbhgau6cq69g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:11:27.367833Z" 30 + }
+24
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ulakld6k2hsabch4g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ulakld6k2hsabch4g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "dropbox_paths", 10 + "column": { 11 + "name": "path", 12 + "type": "text", 13 + "comment": "" 14 + } 15 + } 16 + } 17 + ] 18 + }, 19 + "migrationType": "pgroll", 20 + "name": "mig_cv8ulakld6k2hsabch4g", 21 + "parent": "mig_cv8ukrg1g95li4qghhh0", 22 + "schema": "public", 23 + "startedAt": "2025-03-12T20:12:27.123143Z" 24 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8umjo1g95li4qghhk0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8umjo1g95li4qghhk0", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "google_drive_paths", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "google_drive_paths_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"google_drive_paths\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8umjo1g95li4qghhk0", 54 + "parent": "mig_cv8ulakld6k2hsabch4g", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T20:15:12.130883Z" 57 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8un7g1g95li4qghhl0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8un7g1g95li4qghhl0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "google_drive_paths", 10 + "column": { 11 + "name": "google_drive_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"google_drive\"}", 14 + "references": { 15 + "name": "google_drive_id_link", 16 + "table": "google_drive", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8un7g1g95li4qghhl0", 27 + "parent": "mig_cv8umjo1g95li4qghhk0", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:16:31.074898Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8unpo1g95li4qghhm0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8unpo1g95li4qghhm0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "google_drive_paths", 10 + "column": { 11 + "name": "track_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"tracks\"}", 14 + "references": { 15 + "name": "track_id_link", 16 + "table": "tracks", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8unpo1g95li4qghhm0", 27 + "parent": "mig_cv8un7g1g95li4qghhl0", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:17:44.010057Z" 30 + }
+25
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8up2aglbhgau6cq6bg.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8up2aglbhgau6cq6bg", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "google_drive_paths", 10 + "column": { 11 + "name": "google_drive_file_id", 12 + "type": "text", 13 + "unique": true, 14 + "comment": "" 15 + } 16 + } 17 + } 18 + ] 19 + }, 20 + "migrationType": "pgroll", 21 + "name": "mig_cv8up2aglbhgau6cq6bg", 22 + "parent": "mig_cv8unpo1g95li4qghhm0", 23 + "schema": "public", 24 + "startedAt": "2025-03-12T20:20:26.129287Z" 25 + }
+20
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8upbcld6k2hsabch6g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8upbcld6k2hsabch6g", 5 + "operations": [ 6 + { 7 + "alter_column": { 8 + "name": "file_id", 9 + "table": "google_drive_paths", 10 + "column": "google_drive_file_id" 11 + } 12 + } 13 + ] 14 + }, 15 + "migrationType": "pgroll", 16 + "name": "mig_cv8upbcld6k2hsabch6g", 17 + "parent": "mig_cv8up2aglbhgau6cq6bg", 18 + "schema": "public", 19 + "startedAt": "2025-03-12T20:21:01.574135Z" 20 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uq1sld6k2hsabch7g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8uq1sld6k2hsabch7g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "s3_paths", 10 + "column": { 11 + "name": "track_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"tracks\"}", 14 + "references": { 15 + "name": "track_id_link", 16 + "table": "tracks", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8uq1sld6k2hsabch7g", 27 + "parent": "mig_cv8upbcld6k2hsabch6g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:22:31.736456Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uqh2glbhgau6cq6d0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8uqh2glbhgau6cq6d0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "s3_paths", 10 + "column": { 11 + "name": "s3_bucket_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"s3_bucket\"}", 14 + "references": { 15 + "name": "s3_bucket_id_link", 16 + "table": "s3_bucket", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8uqh2glbhgau6cq6d0", 27 + "parent": "mig_cv8uq1sld6k2hsabch7g", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:23:32.674866Z" 30 + }
+24
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8ur1kld6k2hsabch8g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8ur1kld6k2hsabch8g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "s3_tokens", 10 + "column": { 11 + "name": "secret_access_key", 12 + "type": "text", 13 + "comment": "" 14 + } 15 + } 16 + } 17 + ] 18 + }, 19 + "migrationType": "pgroll", 20 + "name": "mig_cv8ur1kld6k2hsabch8g", 21 + "parent": "mig_cv8uqh2glbhgau6cq6d0", 22 + "schema": "public", 23 + "startedAt": "2025-03-12T20:24:39.513081Z" 24 + }
+24
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8urh4ld6k2hsabch9g.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8urh4ld6k2hsabch9g", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "s3_tokens", 10 + "column": { 11 + "name": "client_access_key", 12 + "type": "text", 13 + "comment": "" 14 + } 15 + } 16 + } 17 + ] 18 + }, 19 + "migrationType": "pgroll", 20 + "name": "mig_cv8urh4ld6k2hsabch9g", 21 + "parent": "mig_cv8ur1kld6k2hsabch8g", 22 + "schema": "public", 23 + "startedAt": "2025-03-12T20:25:41.248772Z" 24 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8us9g1g95li4qghhq0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8us9g1g95li4qghhq0", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "sftp", 9 + "columns": [ 10 + { 11 + "name": "xata_updatedat", 12 + "type": "timestamptz", 13 + "default": "now()" 14 + }, 15 + { 16 + "name": "xata_id", 17 + "type": "text", 18 + "check": { 19 + "name": "sftp_xata_id_length_xata_id", 20 + "constraint": "length(\"xata_id\") < 256" 21 + }, 22 + "unique": true, 23 + "default": "'rec_' || xata_private.xid()" 24 + }, 25 + { 26 + "name": "xata_version", 27 + "type": "integer", 28 + "default": "0" 29 + }, 30 + { 31 + "name": "xata_createdat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"sftp\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8us9g1g95li4qghhq0", 54 + "parent": "mig_cv8urh4ld6k2hsabch9g", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T20:27:19.422672Z" 57 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8usig1g95li4qghhr0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8usig1g95li4qghhr0", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "sftp_access", 9 + "columns": [ 10 + { 11 + "name": "xata_updatedat", 12 + "type": "timestamptz", 13 + "default": "now()" 14 + }, 15 + { 16 + "name": "xata_id", 17 + "type": "text", 18 + "check": { 19 + "name": "sftp_access_xata_id_length_xata_id", 20 + "constraint": "length(\"xata_id\") < 256" 21 + }, 22 + "unique": true, 23 + "default": "'rec_' || xata_private.xid()" 24 + }, 25 + { 26 + "name": "xata_version", 27 + "type": "integer", 28 + "default": "0" 29 + }, 30 + { 31 + "name": "xata_createdat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"sftp_access\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8usig1g95li4qghhr0", 54 + "parent": "mig_cv8us9g1g95li4qghhq0", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T20:27:55.896177Z" 57 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8utv81g95li4qghhs0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8utv81g95li4qghhs0", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "builtin_storage_paths", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "builtin_storage_paths_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"builtin_storage_paths\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8utv81g95li4qghhs0", 54 + "parent": "mig_cv8usig1g95li4qghhr0", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T20:30:54.210263Z" 57 + }
+25
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uugqglbhgau6cq6e0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8uugqglbhgau6cq6e0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "builtin_storage_paths", 10 + "column": { 11 + "name": "path", 12 + "type": "text", 13 + "unique": true, 14 + "comment": "" 15 + } 16 + } 17 + } 18 + ] 19 + }, 20 + "migrationType": "pgroll", 21 + "name": "mig_cv8uugqglbhgau6cq6e0", 22 + "parent": "mig_cv8utv81g95li4qghhs0", 23 + "schema": "public", 24 + "startedAt": "2025-03-12T20:32:05.065795Z" 25 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uv2iglbhgau6cq6f0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8uv2iglbhgau6cq6f0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "builtin_storage_paths", 10 + "column": { 11 + "name": "track_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"tracks\"}", 14 + "references": { 15 + "name": "track_id_link", 16 + "table": "tracks", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8uv2iglbhgau6cq6f0", 27 + "parent": "mig_cv8uugqglbhgau6cq6e0", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:33:15.916373Z" 30 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8uveiglbhgau6cq6h0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8uveiglbhgau6cq6h0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "builtin_storage_paths", 10 + "column": { 11 + "name": "user_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"users\"}", 14 + "references": { 15 + "name": "user_id_link", 16 + "table": "users", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8uveiglbhgau6cq6h0", 27 + "parent": "mig_cv8uv2iglbhgau6cq6f0", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:34:03.576836Z" 30 + }
+57
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v8gcld6k2hsabcheg.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8v8gcld6k2hsabcheg", 5 + "operations": [ 6 + { 7 + "create_table": { 8 + "name": "sftp_path", 9 + "columns": [ 10 + { 11 + "name": "xata_id", 12 + "type": "text", 13 + "check": { 14 + "name": "sftp_path_xata_id_length_xata_id", 15 + "constraint": "length(\"xata_id\") < 256" 16 + }, 17 + "unique": true, 18 + "default": "'rec_' || xata_private.xid()" 19 + }, 20 + { 21 + "name": "xata_version", 22 + "type": "integer", 23 + "default": "0" 24 + }, 25 + { 26 + "name": "xata_createdat", 27 + "type": "timestamptz", 28 + "default": "now()" 29 + }, 30 + { 31 + "name": "xata_updatedat", 32 + "type": "timestamptz", 33 + "default": "now()" 34 + } 35 + ] 36 + } 37 + }, 38 + { 39 + "sql": { 40 + "up": "ALTER TABLE \"sftp_path\" REPLICA IDENTITY FULL", 41 + "onComplete": true 42 + } 43 + }, 44 + { 45 + "sql": { 46 + "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()", 47 + "onComplete": true 48 + } 49 + } 50 + ] 51 + }, 52 + "migrationType": "pgroll", 53 + "name": "mig_cv8v8gcld6k2hsabcheg", 54 + "parent": "mig_cv8uveiglbhgau6cq6h0", 55 + "schema": "public", 56 + "startedAt": "2025-03-12T20:53:21.873024Z" 57 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v92aglbhgau6cq6n0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8v92aglbhgau6cq6n0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "sftp_path", 10 + "column": { 11 + "name": "track_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"tracks\"}", 14 + "references": { 15 + "name": "track_id_link", 16 + "table": "tracks", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8v92aglbhgau6cq6n0", 27 + "parent": "mig_cv8v8gcld6k2hsabcheg", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:54:33.889601Z" 30 + }
+24
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9faglbhgau6cq6o0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8v9faglbhgau6cq6o0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "sftp_path", 10 + "column": { 11 + "name": "path", 12 + "type": "text", 13 + "comment": "" 14 + } 15 + } 16 + } 17 + ] 18 + }, 19 + "migrationType": "pgroll", 20 + "name": "mig_cv8v9faglbhgau6cq6o0", 21 + "parent": "mig_cv8v92aglbhgau6cq6n0", 22 + "schema": "public", 23 + "startedAt": "2025-03-12T20:55:26.567965Z" 24 + }
+30
rockskyapi/rocksky-auth/.xata/migrations/mig_cv8v9sqglbhgau6cq6p0.json
··· 1 + { 2 + "done": true, 3 + "migration": { 4 + "name": "mig_cv8v9sqglbhgau6cq6p0", 5 + "operations": [ 6 + { 7 + "add_column": { 8 + "up": "''", 9 + "table": "sftp_path", 10 + "column": { 11 + "name": "sftp_id", 12 + "type": "text", 13 + "comment": "{\"xata.link\":\"sftp\"}", 14 + "references": { 15 + "name": "sftp_id_link", 16 + "table": "sftp", 17 + "column": "xata_id", 18 + "on_delete": "CASCADE" 19 + } 20 + } 21 + } 22 + } 23 + ] 24 + }, 25 + "migrationType": "pgroll", 26 + "name": "mig_cv8v9sqglbhgau6cq6p0", 27 + "parent": "mig_cv8v9faglbhgau6cq6o0", 28 + "schema": "public", 29 + "startedAt": "2025-03-12T20:56:20.296301Z" 30 + }
+55 -2
rockskyapi/rocksky-auth/src/dropbox/app.ts
··· 1 + import { equals } from "@xata.io/client"; 2 + import axios from "axios"; 3 + import { ctx } from "context"; 1 4 import { Hono } from "hono"; 5 + import jwt from "jsonwebtoken"; 6 + import { encrypt } from "lib/crypto"; 2 7 import { env } from "lib/env"; 3 8 4 9 const app = new Hono(); 5 10 6 11 app.get("/login", async (c) => { 12 + const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim(); 13 + 14 + if (!bearer || bearer === "null") { 15 + c.status(401); 16 + return c.text("Unauthorized"); 17 + } 18 + 19 + const { did } = jwt.verify(bearer, env.JWT_SECRET); 20 + 21 + const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 22 + if (!user) { 23 + c.status(401); 24 + return c.text("Unauthorized"); 25 + } 26 + 7 27 const clientId = env.DROPBOX_CLIENT_ID; 8 - const redirectUri = `https://www.dropbox.com/oauth2/authorize?client_id=${clientId}&redirect_uri=${env.DROPBOX_REDIRECT_URI}&response_type=code&token_access_type=offline`; 28 + 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}`; 9 29 return c.json({ redirectUri }); 10 30 }); 11 31 12 32 app.get("/oauth/callback", async (c) => { 13 33 const params = new URLSearchParams(c.req.url.split("?")[1]); 14 34 const entries = Object.fromEntries(params.entries()); 15 - return c.json(entries); 35 + // entries.code 36 + const response = await axios.postForm( 37 + "https://api.dropboxapi.com/oauth2/token", 38 + { 39 + code: entries.code, 40 + grant_type: "authorization_code", 41 + client_id: env.DROPBOX_CLIENT_ID, 42 + client_secret: env.DROPBOX_CLIENT_SECRET, 43 + redirect_uri: env.DROPBOX_REDIRECT_URI, 44 + } 45 + ); 46 + 47 + const dropbox = await ctx.client.db.dropbox 48 + .select(["*", "user_id.*", "dropbox_token_id.*"]) 49 + .filter("user_id.xata_id", equals(entries.state)) 50 + .getFirst(); 51 + 52 + if (dropbox) { 53 + await ctx.client.db.dropbox_tokens.delete(dropbox.dropbox_token_id.xata_id); 54 + } 55 + 56 + const newDropboxToken = await ctx.client.db.dropbox_tokens.create({ 57 + refresh_token: encrypt( 58 + response.data.refresh_token, 59 + env.SPOTIFY_ENCRYPTION_KEY 60 + ), 61 + }); 62 + 63 + await ctx.client.db.dropbox.create({ 64 + dropbox_token_id: newDropboxToken.xata_id, 65 + user_id: entries.state, 66 + }); 67 + 68 + return c.redirect(`${env.FRONTEND_URL}/dropbox`); 16 69 }); 17 70 18 71 export default app;
+60 -2
rockskyapi/rocksky-auth/src/googledrive/app.ts
··· 1 + import { equals } from "@xata.io/client"; 2 + import axios from "axios"; 3 + import { ctx } from "context"; 1 4 import fs from "fs"; 2 5 import { google } from "googleapis"; 3 6 import { Hono } from "hono"; 7 + import jwt from "jsonwebtoken"; 8 + import { encrypt } from "lib/crypto"; 4 9 import { env } from "lib/env"; 5 10 6 11 const app = new Hono(); 7 12 8 13 app.get("/login", async (c) => { 14 + const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim(); 15 + 16 + if (!bearer || bearer === "null") { 17 + c.status(401); 18 + return c.text("Unauthorized"); 19 + } 20 + 21 + const { did } = jwt.verify(bearer, env.JWT_SECRET); 22 + 23 + const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 24 + if (!user) { 25 + c.status(401); 26 + return c.text("Unauthorized"); 27 + } 28 + 9 29 const credentials = JSON.parse( 10 30 fs.readFileSync("credentials.json").toString("utf-8") 11 31 ); ··· 19 39 // Generate Auth URL 20 40 const authUrl = oAuth2Client.generateAuthUrl({ 21 41 access_type: "offline", 22 - // prompt: "consent", 42 + prompt: "consent", 23 43 scope: ["https://www.googleapis.com/auth/drive"], 44 + state: user.xata_id, 24 45 }); 25 46 return c.json({ authUrl }); 26 47 }); ··· 28 49 app.get("/oauth/callback", async (c) => { 29 50 const params = new URLSearchParams(c.req.url.split("?")[1]); 30 51 const entries = Object.fromEntries(params.entries()); 31 - return c.json(entries); 52 + 53 + const credentials = JSON.parse( 54 + fs.readFileSync("credentials.json").toString("utf-8") 55 + ); 56 + const { client_id, client_secret } = credentials.installed || credentials.web; 57 + 58 + const response = await axios.postForm("https://oauth2.googleapis.com/token", { 59 + code: entries.code, 60 + client_id, 61 + client_secret, 62 + redirect_uri: env.GOOGLE_REDIRECT_URI, 63 + grant_type: "authorization_code", 64 + }); 65 + 66 + const googledrive = await ctx.client.db.google_drive 67 + .select(["*", "user_id.*", "google_drive_token_id.*"]) 68 + .filter("user_id.xata_id", equals(entries.state)) 69 + .getFirst(); 70 + 71 + if (googledrive) { 72 + await ctx.client.db.google_drive_tokens.delete( 73 + googledrive.google_drive_token_id.xata_id 74 + ); 75 + } 76 + 77 + const newGoogleDriveToken = await ctx.client.db.google_drive_tokens.create({ 78 + refresh_token: encrypt( 79 + response.data.refresh_token, 80 + env.SPOTIFY_ENCRYPTION_KEY 81 + ), 82 + }); 83 + 84 + await ctx.client.db.google_drive.create({ 85 + google_drive_token_id: newGoogleDriveToken.xata_id, 86 + user_id: entries.state, 87 + }); 88 + 89 + return c.redirect(`${env.FRONTEND_URL}/googledrive`); 32 90 }); 33 91 34 92 export default app;
+10
rockskyapi/rocksky-auth/src/schema/dropbox-tokens.ts
··· 1 + import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 2 + 3 + const dropboxTokens = pgTable("dropbox_tokens", { 4 + id: text("xata_id").primaryKey(), 5 + refreshToken: text("refresh_token").notNull(), 6 + createdAt: timestamp("xata_createdat").defaultNow().notNull(), 7 + updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), 8 + }); 9 + 10 + export default dropboxTokens;
+10
rockskyapi/rocksky-auth/src/schema/google-drive-tokens.ts
··· 1 + import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 2 + 3 + const googleDriveTokens = pgTable("google_drive_tokens", { 4 + id: text("xata_id").primaryKey(), 5 + refreshToken: text("refresh_token").notNull(), 6 + createdAt: timestamp("xata_createdat").defaultNow().notNull(), 7 + updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), 8 + }); 9 + 10 + export default googleDriveTokens;
+1072
rockskyapi/rocksky-auth/src/xata.ts
··· 769 769 ], 770 770 }, 771 771 { 772 + name: "builtin_storage_paths", 773 + checkConstraints: { 774 + builtin_storage_paths_xata_id_length_xata_id: { 775 + name: "builtin_storage_paths_xata_id_length_xata_id", 776 + columns: ["xata_id"], 777 + definition: "CHECK ((length(xata_id) < 256))", 778 + }, 779 + }, 780 + foreignKeys: { 781 + track_id_link: { 782 + name: "track_id_link", 783 + columns: ["track_id"], 784 + referencedTable: "tracks", 785 + referencedColumns: ["xata_id"], 786 + onDelete: "CASCADE", 787 + }, 788 + user_id_link: { 789 + name: "user_id_link", 790 + columns: ["user_id"], 791 + referencedTable: "users", 792 + referencedColumns: ["xata_id"], 793 + onDelete: "CASCADE", 794 + }, 795 + }, 796 + primaryKey: [], 797 + uniqueConstraints: { 798 + _pgroll_new_builtin_storage_paths_xata_id_key: { 799 + name: "_pgroll_new_builtin_storage_paths_xata_id_key", 800 + columns: ["xata_id"], 801 + }, 802 + builtin_storage_paths__pgroll_new_path_key: { 803 + name: "builtin_storage_paths__pgroll_new_path_key", 804 + columns: ["path"], 805 + }, 806 + }, 807 + columns: [ 808 + { 809 + name: "path", 810 + type: "text", 811 + notNull: true, 812 + unique: true, 813 + defaultValue: null, 814 + comment: "", 815 + }, 816 + { 817 + name: "track_id", 818 + type: "link", 819 + link: { table: "tracks" }, 820 + notNull: true, 821 + unique: false, 822 + defaultValue: null, 823 + comment: '{"xata.link":"tracks"}', 824 + }, 825 + { 826 + name: "user_id", 827 + type: "link", 828 + link: { table: "users" }, 829 + notNull: true, 830 + unique: false, 831 + defaultValue: null, 832 + comment: '{"xata.link":"users"}', 833 + }, 834 + { 835 + name: "xata_createdat", 836 + type: "datetime", 837 + notNull: true, 838 + unique: false, 839 + defaultValue: "now()", 840 + comment: "", 841 + }, 842 + { 843 + name: "xata_id", 844 + type: "text", 845 + notNull: true, 846 + unique: true, 847 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 848 + comment: "", 849 + }, 850 + { 851 + name: "xata_updatedat", 852 + type: "datetime", 853 + notNull: true, 854 + unique: false, 855 + defaultValue: "now()", 856 + comment: "", 857 + }, 858 + { 859 + name: "xata_version", 860 + type: "int", 861 + notNull: true, 862 + unique: false, 863 + defaultValue: "0", 864 + comment: "", 865 + }, 866 + ], 867 + }, 868 + { 869 + name: "dropbox", 870 + checkConstraints: { 871 + dropbox_xata_id_length_xata_id: { 872 + name: "dropbox_xata_id_length_xata_id", 873 + columns: ["xata_id"], 874 + definition: "CHECK ((length(xata_id) < 256))", 875 + }, 876 + }, 877 + foreignKeys: { 878 + dropbox_token_id_link: { 879 + name: "dropbox_token_id_link", 880 + columns: ["dropbox_token_id"], 881 + referencedTable: "dropbox_tokens", 882 + referencedColumns: ["xata_id"], 883 + onDelete: "CASCADE", 884 + }, 885 + user_id_link: { 886 + name: "user_id_link", 887 + columns: ["user_id"], 888 + referencedTable: "users", 889 + referencedColumns: ["xata_id"], 890 + onDelete: "CASCADE", 891 + }, 892 + }, 893 + primaryKey: [], 894 + uniqueConstraints: { 895 + _pgroll_new_dropbox_xata_id_key: { 896 + name: "_pgroll_new_dropbox_xata_id_key", 897 + columns: ["xata_id"], 898 + }, 899 + }, 900 + columns: [ 901 + { 902 + name: "dropbox_token_id", 903 + type: "link", 904 + link: { table: "dropbox_tokens" }, 905 + notNull: true, 906 + unique: false, 907 + defaultValue: null, 908 + comment: '{"xata.link":"dropbox_tokens"}', 909 + }, 910 + { 911 + name: "user_id", 912 + type: "link", 913 + link: { table: "users" }, 914 + notNull: true, 915 + unique: false, 916 + defaultValue: null, 917 + comment: '{"xata.link":"users"}', 918 + }, 919 + { 920 + name: "xata_createdat", 921 + type: "datetime", 922 + notNull: true, 923 + unique: false, 924 + defaultValue: "now()", 925 + comment: "", 926 + }, 927 + { 928 + name: "xata_id", 929 + type: "text", 930 + notNull: true, 931 + unique: true, 932 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 933 + comment: "", 934 + }, 935 + { 936 + name: "xata_updatedat", 937 + type: "datetime", 938 + notNull: true, 939 + unique: false, 940 + defaultValue: "now()", 941 + comment: "", 942 + }, 943 + { 944 + name: "xata_version", 945 + type: "int", 946 + notNull: true, 947 + unique: false, 948 + defaultValue: "0", 949 + comment: "", 950 + }, 951 + ], 952 + }, 953 + { 954 + name: "dropbox_paths", 955 + checkConstraints: { 956 + dropbox_paths_xata_id_length_xata_id: { 957 + name: "dropbox_paths_xata_id_length_xata_id", 958 + columns: ["xata_id"], 959 + definition: "CHECK ((length(xata_id) < 256))", 960 + }, 961 + }, 962 + foreignKeys: { 963 + dropbox_id_link: { 964 + name: "dropbox_id_link", 965 + columns: ["dropbox_id"], 966 + referencedTable: "dropbox", 967 + referencedColumns: ["xata_id"], 968 + onDelete: "CASCADE", 969 + }, 970 + track_id_link: { 971 + name: "track_id_link", 972 + columns: ["track_id"], 973 + referencedTable: "tracks", 974 + referencedColumns: ["xata_id"], 975 + onDelete: "CASCADE", 976 + }, 977 + }, 978 + primaryKey: [], 979 + uniqueConstraints: { 980 + _pgroll_new_dropbox_paths_xata_id_key: { 981 + name: "_pgroll_new_dropbox_paths_xata_id_key", 982 + columns: ["xata_id"], 983 + }, 984 + }, 985 + columns: [ 986 + { 987 + name: "dropbox_id", 988 + type: "link", 989 + link: { table: "dropbox" }, 990 + notNull: true, 991 + unique: false, 992 + defaultValue: null, 993 + comment: '{"xata.link":"dropbox"}', 994 + }, 995 + { 996 + name: "path", 997 + type: "text", 998 + notNull: true, 999 + unique: false, 1000 + defaultValue: null, 1001 + comment: "", 1002 + }, 1003 + { 1004 + name: "track_id", 1005 + type: "link", 1006 + link: { table: "tracks" }, 1007 + notNull: true, 1008 + unique: false, 1009 + defaultValue: null, 1010 + comment: '{"xata.link":"tracks"}', 1011 + }, 1012 + { 1013 + name: "xata_createdat", 1014 + type: "datetime", 1015 + notNull: true, 1016 + unique: false, 1017 + defaultValue: "now()", 1018 + comment: "", 1019 + }, 1020 + { 1021 + name: "xata_id", 1022 + type: "text", 1023 + notNull: true, 1024 + unique: true, 1025 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1026 + comment: "", 1027 + }, 1028 + { 1029 + name: "xata_updatedat", 1030 + type: "datetime", 1031 + notNull: true, 1032 + unique: false, 1033 + defaultValue: "now()", 1034 + comment: "", 1035 + }, 1036 + { 1037 + name: "xata_version", 1038 + type: "int", 1039 + notNull: true, 1040 + unique: false, 1041 + defaultValue: "0", 1042 + comment: "", 1043 + }, 1044 + ], 1045 + }, 1046 + { 1047 + name: "dropbox_tokens", 1048 + checkConstraints: { 1049 + dropbox_tokens_xata_id_length_xata_id: { 1050 + name: "dropbox_tokens_xata_id_length_xata_id", 1051 + columns: ["xata_id"], 1052 + definition: "CHECK ((length(xata_id) < 256))", 1053 + }, 1054 + }, 1055 + foreignKeys: {}, 1056 + primaryKey: [], 1057 + uniqueConstraints: { 1058 + _pgroll_new_dropbox_tokens_xata_id_key: { 1059 + name: "_pgroll_new_dropbox_tokens_xata_id_key", 1060 + columns: ["xata_id"], 1061 + }, 1062 + }, 1063 + columns: [ 1064 + { 1065 + name: "refresh_token", 1066 + type: "text", 1067 + notNull: true, 1068 + unique: false, 1069 + defaultValue: null, 1070 + comment: "", 1071 + }, 1072 + { 1073 + name: "xata_createdat", 1074 + type: "datetime", 1075 + notNull: true, 1076 + unique: false, 1077 + defaultValue: "now()", 1078 + comment: "", 1079 + }, 1080 + { 1081 + name: "xata_id", 1082 + type: "text", 1083 + notNull: true, 1084 + unique: true, 1085 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1086 + comment: "", 1087 + }, 1088 + { 1089 + name: "xata_updatedat", 1090 + type: "datetime", 1091 + notNull: true, 1092 + unique: false, 1093 + defaultValue: "now()", 1094 + comment: "", 1095 + }, 1096 + { 1097 + name: "xata_version", 1098 + type: "int", 1099 + notNull: true, 1100 + unique: false, 1101 + defaultValue: "0", 1102 + comment: "", 1103 + }, 1104 + ], 1105 + }, 1106 + { 1107 + name: "google_drive", 1108 + checkConstraints: { 1109 + google_drive_xata_id_length_xata_id: { 1110 + name: "google_drive_xata_id_length_xata_id", 1111 + columns: ["xata_id"], 1112 + definition: "CHECK ((length(xata_id) < 256))", 1113 + }, 1114 + }, 1115 + foreignKeys: { 1116 + google_drive_token_id_link: { 1117 + name: "google_drive_token_id_link", 1118 + columns: ["google_drive_token_id"], 1119 + referencedTable: "google_drive_tokens", 1120 + referencedColumns: ["xata_id"], 1121 + onDelete: "CASCADE", 1122 + }, 1123 + user_id_link: { 1124 + name: "user_id_link", 1125 + columns: ["user_id"], 1126 + referencedTable: "users", 1127 + referencedColumns: ["xata_id"], 1128 + onDelete: "SET NULL", 1129 + }, 1130 + }, 1131 + primaryKey: [], 1132 + uniqueConstraints: { 1133 + _pgroll_new_google_drive_xata_id_key: { 1134 + name: "_pgroll_new_google_drive_xata_id_key", 1135 + columns: ["xata_id"], 1136 + }, 1137 + }, 1138 + columns: [ 1139 + { 1140 + name: "google_drive_token_id", 1141 + type: "link", 1142 + link: { table: "google_drive_tokens" }, 1143 + notNull: true, 1144 + unique: false, 1145 + defaultValue: null, 1146 + comment: '{"xata.link":"google_drive_tokens"}', 1147 + }, 1148 + { 1149 + name: "user_id", 1150 + type: "link", 1151 + link: { table: "users" }, 1152 + notNull: true, 1153 + unique: false, 1154 + defaultValue: null, 1155 + comment: '{"xata.link":"users"}', 1156 + }, 1157 + { 1158 + name: "xata_createdat", 1159 + type: "datetime", 1160 + notNull: true, 1161 + unique: false, 1162 + defaultValue: "now()", 1163 + comment: "", 1164 + }, 1165 + { 1166 + name: "xata_id", 1167 + type: "text", 1168 + notNull: true, 1169 + unique: true, 1170 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1171 + comment: "", 1172 + }, 1173 + { 1174 + name: "xata_updatedat", 1175 + type: "datetime", 1176 + notNull: true, 1177 + unique: false, 1178 + defaultValue: "now()", 1179 + comment: "", 1180 + }, 1181 + { 1182 + name: "xata_version", 1183 + type: "int", 1184 + notNull: true, 1185 + unique: false, 1186 + defaultValue: "0", 1187 + comment: "", 1188 + }, 1189 + ], 1190 + }, 1191 + { 1192 + name: "google_drive_paths", 1193 + checkConstraints: { 1194 + google_drive_paths_xata_id_length_xata_id: { 1195 + name: "google_drive_paths_xata_id_length_xata_id", 1196 + columns: ["xata_id"], 1197 + definition: "CHECK ((length(xata_id) < 256))", 1198 + }, 1199 + }, 1200 + foreignKeys: { 1201 + google_drive_id_link: { 1202 + name: "google_drive_id_link", 1203 + columns: ["google_drive_id"], 1204 + referencedTable: "google_drive", 1205 + referencedColumns: ["xata_id"], 1206 + onDelete: "CASCADE", 1207 + }, 1208 + track_id_link: { 1209 + name: "track_id_link", 1210 + columns: ["track_id"], 1211 + referencedTable: "tracks", 1212 + referencedColumns: ["xata_id"], 1213 + onDelete: "CASCADE", 1214 + }, 1215 + }, 1216 + primaryKey: [], 1217 + uniqueConstraints: { 1218 + _pgroll_new_google_drive_paths_xata_id_key: { 1219 + name: "_pgroll_new_google_drive_paths_xata_id_key", 1220 + columns: ["xata_id"], 1221 + }, 1222 + google_drive_paths__pgroll_new_google_drive_file_id_key: { 1223 + name: "google_drive_paths__pgroll_new_google_drive_file_id_key", 1224 + columns: ["file_id"], 1225 + }, 1226 + }, 1227 + columns: [ 1228 + { 1229 + name: "file_id", 1230 + type: "text", 1231 + notNull: true, 1232 + unique: true, 1233 + defaultValue: null, 1234 + comment: "", 1235 + }, 1236 + { 1237 + name: "google_drive_id", 1238 + type: "link", 1239 + link: { table: "google_drive" }, 1240 + notNull: true, 1241 + unique: false, 1242 + defaultValue: null, 1243 + comment: '{"xata.link":"google_drive"}', 1244 + }, 1245 + { 1246 + name: "track_id", 1247 + type: "link", 1248 + link: { table: "tracks" }, 1249 + notNull: true, 1250 + unique: false, 1251 + defaultValue: null, 1252 + comment: '{"xata.link":"tracks"}', 1253 + }, 1254 + { 1255 + name: "xata_createdat", 1256 + type: "datetime", 1257 + notNull: true, 1258 + unique: false, 1259 + defaultValue: "now()", 1260 + comment: "", 1261 + }, 1262 + { 1263 + name: "xata_id", 1264 + type: "text", 1265 + notNull: true, 1266 + unique: true, 1267 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1268 + comment: "", 1269 + }, 1270 + { 1271 + name: "xata_updatedat", 1272 + type: "datetime", 1273 + notNull: true, 1274 + unique: false, 1275 + defaultValue: "now()", 1276 + comment: "", 1277 + }, 1278 + { 1279 + name: "xata_version", 1280 + type: "int", 1281 + notNull: true, 1282 + unique: false, 1283 + defaultValue: "0", 1284 + comment: "", 1285 + }, 1286 + ], 1287 + }, 1288 + { 1289 + name: "google_drive_tokens", 1290 + checkConstraints: { 1291 + google_drive_tokens_xata_id_length_xata_id: { 1292 + name: "google_drive_tokens_xata_id_length_xata_id", 1293 + columns: ["xata_id"], 1294 + definition: "CHECK ((length(xata_id) < 256))", 1295 + }, 1296 + }, 1297 + foreignKeys: {}, 1298 + primaryKey: [], 1299 + uniqueConstraints: { 1300 + _pgroll_new_google_drive_tokens_xata_id_key: { 1301 + name: "_pgroll_new_google_drive_tokens_xata_id_key", 1302 + columns: ["xata_id"], 1303 + }, 1304 + }, 1305 + columns: [ 1306 + { 1307 + name: "refresh_token", 1308 + type: "text", 1309 + notNull: true, 1310 + unique: false, 1311 + defaultValue: null, 1312 + comment: "", 1313 + }, 1314 + { 1315 + name: "xata_createdat", 1316 + type: "datetime", 1317 + notNull: true, 1318 + unique: false, 1319 + defaultValue: "now()", 1320 + comment: "", 1321 + }, 1322 + { 1323 + name: "xata_id", 1324 + type: "text", 1325 + notNull: true, 1326 + unique: true, 1327 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1328 + comment: "", 1329 + }, 1330 + { 1331 + name: "xata_updatedat", 1332 + type: "datetime", 1333 + notNull: true, 1334 + unique: false, 1335 + defaultValue: "now()", 1336 + comment: "", 1337 + }, 1338 + { 1339 + name: "xata_version", 1340 + type: "int", 1341 + notNull: true, 1342 + unique: false, 1343 + defaultValue: "0", 1344 + comment: "", 1345 + }, 1346 + ], 1347 + }, 1348 + { 772 1349 name: "loved_tracks", 773 1350 checkConstraints: { 774 1351 loved_tracks_xata_id_length_xata_id: { ··· 1293 1870 ], 1294 1871 }, 1295 1872 { 1873 + name: "s3_bucket", 1874 + checkConstraints: { 1875 + s3_xata_id_length_xata_id: { 1876 + name: "s3_xata_id_length_xata_id", 1877 + columns: ["xata_id"], 1878 + definition: "CHECK ((length(xata_id) < 256))", 1879 + }, 1880 + }, 1881 + foreignKeys: { 1882 + s3_token_id_link: { 1883 + name: "s3_token_id_link", 1884 + columns: ["s3_token_id"], 1885 + referencedTable: "s3_tokens", 1886 + referencedColumns: ["xata_id"], 1887 + onDelete: "CASCADE", 1888 + }, 1889 + user_id_link: { 1890 + name: "user_id_link", 1891 + columns: ["user_id"], 1892 + referencedTable: "users", 1893 + referencedColumns: ["xata_id"], 1894 + onDelete: "CASCADE", 1895 + }, 1896 + }, 1897 + primaryKey: [], 1898 + uniqueConstraints: { 1899 + _pgroll_new_s3_xata_id_key: { 1900 + name: "_pgroll_new_s3_xata_id_key", 1901 + columns: ["xata_id"], 1902 + }, 1903 + }, 1904 + columns: [ 1905 + { 1906 + name: "name", 1907 + type: "text", 1908 + notNull: true, 1909 + unique: false, 1910 + defaultValue: null, 1911 + comment: "", 1912 + }, 1913 + { 1914 + name: "s3_token_id", 1915 + type: "link", 1916 + link: { table: "s3_tokens" }, 1917 + notNull: true, 1918 + unique: false, 1919 + defaultValue: null, 1920 + comment: '{"xata.link":"s3_tokens"}', 1921 + }, 1922 + { 1923 + name: "user_id", 1924 + type: "link", 1925 + link: { table: "users" }, 1926 + notNull: true, 1927 + unique: false, 1928 + defaultValue: null, 1929 + comment: '{"xata.link":"users"}', 1930 + }, 1931 + { 1932 + name: "xata_createdat", 1933 + type: "datetime", 1934 + notNull: true, 1935 + unique: false, 1936 + defaultValue: "now()", 1937 + comment: "", 1938 + }, 1939 + { 1940 + name: "xata_id", 1941 + type: "text", 1942 + notNull: true, 1943 + unique: true, 1944 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1945 + comment: "", 1946 + }, 1947 + { 1948 + name: "xata_updatedat", 1949 + type: "datetime", 1950 + notNull: true, 1951 + unique: false, 1952 + defaultValue: "now()", 1953 + comment: "", 1954 + }, 1955 + { 1956 + name: "xata_version", 1957 + type: "int", 1958 + notNull: true, 1959 + unique: false, 1960 + defaultValue: "0", 1961 + comment: "", 1962 + }, 1963 + ], 1964 + }, 1965 + { 1966 + name: "s3_paths", 1967 + checkConstraints: { 1968 + s3_paths_xata_id_length_xata_id: { 1969 + name: "s3_paths_xata_id_length_xata_id", 1970 + columns: ["xata_id"], 1971 + definition: "CHECK ((length(xata_id) < 256))", 1972 + }, 1973 + }, 1974 + foreignKeys: { 1975 + s3_bucket_id_link: { 1976 + name: "s3_bucket_id_link", 1977 + columns: ["s3_bucket_id"], 1978 + referencedTable: "s3_bucket", 1979 + referencedColumns: ["xata_id"], 1980 + onDelete: "CASCADE", 1981 + }, 1982 + track_id_link: { 1983 + name: "track_id_link", 1984 + columns: ["track_id"], 1985 + referencedTable: "tracks", 1986 + referencedColumns: ["xata_id"], 1987 + onDelete: "CASCADE", 1988 + }, 1989 + }, 1990 + primaryKey: [], 1991 + uniqueConstraints: { 1992 + _pgroll_new_s3_paths_xata_id_key: { 1993 + name: "_pgroll_new_s3_paths_xata_id_key", 1994 + columns: ["xata_id"], 1995 + }, 1996 + }, 1997 + columns: [ 1998 + { 1999 + name: "s3_bucket_id", 2000 + type: "link", 2001 + link: { table: "s3_bucket" }, 2002 + notNull: true, 2003 + unique: false, 2004 + defaultValue: null, 2005 + comment: '{"xata.link":"s3_bucket"}', 2006 + }, 2007 + { 2008 + name: "track_id", 2009 + type: "link", 2010 + link: { table: "tracks" }, 2011 + notNull: true, 2012 + unique: false, 2013 + defaultValue: null, 2014 + comment: '{"xata.link":"tracks"}', 2015 + }, 2016 + { 2017 + name: "xata_createdat", 2018 + type: "datetime", 2019 + notNull: true, 2020 + unique: false, 2021 + defaultValue: "now()", 2022 + comment: "", 2023 + }, 2024 + { 2025 + name: "xata_id", 2026 + type: "text", 2027 + notNull: true, 2028 + unique: true, 2029 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2030 + comment: "", 2031 + }, 2032 + { 2033 + name: "xata_updatedat", 2034 + type: "datetime", 2035 + notNull: true, 2036 + unique: false, 2037 + defaultValue: "now()", 2038 + comment: "", 2039 + }, 2040 + { 2041 + name: "xata_version", 2042 + type: "int", 2043 + notNull: true, 2044 + unique: false, 2045 + defaultValue: "0", 2046 + comment: "", 2047 + }, 2048 + ], 2049 + }, 2050 + { 2051 + name: "s3_tokens", 2052 + checkConstraints: { 2053 + s3_tokens_xata_id_length_xata_id: { 2054 + name: "s3_tokens_xata_id_length_xata_id", 2055 + columns: ["xata_id"], 2056 + definition: "CHECK ((length(xata_id) < 256))", 2057 + }, 2058 + }, 2059 + foreignKeys: {}, 2060 + primaryKey: [], 2061 + uniqueConstraints: { 2062 + _pgroll_new_s3_tokens_xata_id_key: { 2063 + name: "_pgroll_new_s3_tokens_xata_id_key", 2064 + columns: ["xata_id"], 2065 + }, 2066 + }, 2067 + columns: [ 2068 + { 2069 + name: "client_access_key", 2070 + type: "text", 2071 + notNull: true, 2072 + unique: false, 2073 + defaultValue: null, 2074 + comment: "", 2075 + }, 2076 + { 2077 + name: "secret_access_key", 2078 + type: "text", 2079 + notNull: true, 2080 + unique: false, 2081 + defaultValue: null, 2082 + comment: "", 2083 + }, 2084 + { 2085 + name: "xata_createdat", 2086 + type: "datetime", 2087 + notNull: true, 2088 + unique: false, 2089 + defaultValue: "now()", 2090 + comment: "", 2091 + }, 2092 + { 2093 + name: "xata_id", 2094 + type: "text", 2095 + notNull: true, 2096 + unique: true, 2097 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2098 + comment: "", 2099 + }, 2100 + { 2101 + name: "xata_updatedat", 2102 + type: "datetime", 2103 + notNull: true, 2104 + unique: false, 2105 + defaultValue: "now()", 2106 + comment: "", 2107 + }, 2108 + { 2109 + name: "xata_version", 2110 + type: "int", 2111 + notNull: true, 2112 + unique: false, 2113 + defaultValue: "0", 2114 + comment: "", 2115 + }, 2116 + ], 2117 + }, 2118 + { 1296 2119 name: "scrobbles", 1297 2120 checkConstraints: { 1298 2121 scrobbles_xata_id_length_xata_id: { ··· 1386 2209 unique: false, 1387 2210 defaultValue: null, 1388 2211 comment: '{"xata.link":"users"}', 2212 + }, 2213 + { 2214 + name: "xata_createdat", 2215 + type: "datetime", 2216 + notNull: true, 2217 + unique: false, 2218 + defaultValue: "now()", 2219 + comment: "", 2220 + }, 2221 + { 2222 + name: "xata_id", 2223 + type: "text", 2224 + notNull: true, 2225 + unique: true, 2226 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2227 + comment: "", 2228 + }, 2229 + { 2230 + name: "xata_updatedat", 2231 + type: "datetime", 2232 + notNull: true, 2233 + unique: false, 2234 + defaultValue: "now()", 2235 + comment: "", 2236 + }, 2237 + { 2238 + name: "xata_version", 2239 + type: "int", 2240 + notNull: true, 2241 + unique: false, 2242 + defaultValue: "0", 2243 + comment: "", 2244 + }, 2245 + ], 2246 + }, 2247 + { 2248 + name: "sftp", 2249 + checkConstraints: { 2250 + sftp_xata_id_length_xata_id: { 2251 + name: "sftp_xata_id_length_xata_id", 2252 + columns: ["xata_id"], 2253 + definition: "CHECK ((length(xata_id) < 256))", 2254 + }, 2255 + }, 2256 + foreignKeys: {}, 2257 + primaryKey: [], 2258 + uniqueConstraints: { 2259 + _pgroll_new_sftp_xata_id_key: { 2260 + name: "_pgroll_new_sftp_xata_id_key", 2261 + columns: ["xata_id"], 2262 + }, 2263 + }, 2264 + columns: [ 2265 + { 2266 + name: "xata_createdat", 2267 + type: "datetime", 2268 + notNull: true, 2269 + unique: false, 2270 + defaultValue: "now()", 2271 + comment: "", 2272 + }, 2273 + { 2274 + name: "xata_id", 2275 + type: "text", 2276 + notNull: true, 2277 + unique: true, 2278 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2279 + comment: "", 2280 + }, 2281 + { 2282 + name: "xata_updatedat", 2283 + type: "datetime", 2284 + notNull: true, 2285 + unique: false, 2286 + defaultValue: "now()", 2287 + comment: "", 2288 + }, 2289 + { 2290 + name: "xata_version", 2291 + type: "int", 2292 + notNull: true, 2293 + unique: false, 2294 + defaultValue: "0", 2295 + comment: "", 2296 + }, 2297 + ], 2298 + }, 2299 + { 2300 + name: "sftp_access", 2301 + checkConstraints: { 2302 + sftp_access_xata_id_length_xata_id: { 2303 + name: "sftp_access_xata_id_length_xata_id", 2304 + columns: ["xata_id"], 2305 + definition: "CHECK ((length(xata_id) < 256))", 2306 + }, 2307 + }, 2308 + foreignKeys: {}, 2309 + primaryKey: [], 2310 + uniqueConstraints: { 2311 + _pgroll_new_sftp_access_xata_id_key: { 2312 + name: "_pgroll_new_sftp_access_xata_id_key", 2313 + columns: ["xata_id"], 2314 + }, 2315 + }, 2316 + columns: [ 2317 + { 2318 + name: "xata_createdat", 2319 + type: "datetime", 2320 + notNull: true, 2321 + unique: false, 2322 + defaultValue: "now()", 2323 + comment: "", 2324 + }, 2325 + { 2326 + name: "xata_id", 2327 + type: "text", 2328 + notNull: true, 2329 + unique: true, 2330 + defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2331 + comment: "", 2332 + }, 2333 + { 2334 + name: "xata_updatedat", 2335 + type: "datetime", 2336 + notNull: true, 2337 + unique: false, 2338 + defaultValue: "now()", 2339 + comment: "", 2340 + }, 2341 + { 2342 + name: "xata_version", 2343 + type: "int", 2344 + notNull: true, 2345 + unique: false, 2346 + defaultValue: "0", 2347 + comment: "", 2348 + }, 2349 + ], 2350 + }, 2351 + { 2352 + name: "sftp_path", 2353 + checkConstraints: { 2354 + sftp_path_xata_id_length_xata_id: { 2355 + name: "sftp_path_xata_id_length_xata_id", 2356 + columns: ["xata_id"], 2357 + definition: "CHECK ((length(xata_id) < 256))", 2358 + }, 2359 + }, 2360 + foreignKeys: { 2361 + sftp_id_link: { 2362 + name: "sftp_id_link", 2363 + columns: ["sftp_id"], 2364 + referencedTable: "sftp", 2365 + referencedColumns: ["xata_id"], 2366 + onDelete: "CASCADE", 2367 + }, 2368 + track_id_link: { 2369 + name: "track_id_link", 2370 + columns: ["track_id"], 2371 + referencedTable: "tracks", 2372 + referencedColumns: ["xata_id"], 2373 + onDelete: "CASCADE", 2374 + }, 2375 + }, 2376 + primaryKey: [], 2377 + uniqueConstraints: { 2378 + _pgroll_new_sftp_path_xata_id_key: { 2379 + name: "_pgroll_new_sftp_path_xata_id_key", 2380 + columns: ["xata_id"], 2381 + }, 2382 + }, 2383 + columns: [ 2384 + { 2385 + name: "path", 2386 + type: "text", 2387 + notNull: true, 2388 + unique: false, 2389 + defaultValue: null, 2390 + comment: "", 2391 + }, 2392 + { 2393 + name: "sftp_id", 2394 + type: "link", 2395 + link: { table: "sftp" }, 2396 + notNull: true, 2397 + unique: false, 2398 + defaultValue: null, 2399 + comment: '{"xata.link":"sftp"}', 2400 + }, 2401 + { 2402 + name: "track_id", 2403 + type: "link", 2404 + link: { table: "tracks" }, 2405 + notNull: true, 2406 + unique: false, 2407 + defaultValue: null, 2408 + comment: '{"xata.link":"tracks"}', 1389 2409 }, 1390 2410 { 1391 2411 name: "xata_createdat", ··· 2877 3897 export type Artists = InferredTypes["artists"]; 2878 3898 export type ArtistsRecord = Artists & XataRecord; 2879 3899 3900 + export type BuiltinStoragePaths = InferredTypes["builtin_storage_paths"]; 3901 + export type BuiltinStoragePathsRecord = BuiltinStoragePaths & XataRecord; 3902 + 3903 + export type Dropbox = InferredTypes["dropbox"]; 3904 + export type DropboxRecord = Dropbox & XataRecord; 3905 + 3906 + export type DropboxPaths = InferredTypes["dropbox_paths"]; 3907 + export type DropboxPathsRecord = DropboxPaths & XataRecord; 3908 + 3909 + export type DropboxTokens = InferredTypes["dropbox_tokens"]; 3910 + export type DropboxTokensRecord = DropboxTokens & XataRecord; 3911 + 3912 + export type GoogleDrive = InferredTypes["google_drive"]; 3913 + export type GoogleDriveRecord = GoogleDrive & XataRecord; 3914 + 3915 + export type GoogleDrivePaths = InferredTypes["google_drive_paths"]; 3916 + export type GoogleDrivePathsRecord = GoogleDrivePaths & XataRecord; 3917 + 3918 + export type GoogleDriveTokens = InferredTypes["google_drive_tokens"]; 3919 + export type GoogleDriveTokensRecord = GoogleDriveTokens & XataRecord; 3920 + 2880 3921 export type LovedTracks = InferredTypes["loved_tracks"]; 2881 3922 export type LovedTracksRecord = LovedTracks & XataRecord; 2882 3923 ··· 2892 3933 export type Radios = InferredTypes["radios"]; 2893 3934 export type RadiosRecord = Radios & XataRecord; 2894 3935 3936 + export type S3Bucket = InferredTypes["s3_bucket"]; 3937 + export type S3BucketRecord = S3Bucket & XataRecord; 3938 + 3939 + export type S3Paths = InferredTypes["s3_paths"]; 3940 + export type S3PathsRecord = S3Paths & XataRecord; 3941 + 3942 + export type S3Tokens = InferredTypes["s3_tokens"]; 3943 + export type S3TokensRecord = S3Tokens & XataRecord; 3944 + 2895 3945 export type Scrobbles = InferredTypes["scrobbles"]; 2896 3946 export type ScrobblesRecord = Scrobbles & XataRecord; 3947 + 3948 + export type Sftp = InferredTypes["sftp"]; 3949 + export type SftpRecord = Sftp & XataRecord; 3950 + 3951 + export type SftpAccess = InferredTypes["sftp_access"]; 3952 + export type SftpAccessRecord = SftpAccess & XataRecord; 3953 + 3954 + export type SftpPath = InferredTypes["sftp_path"]; 3955 + export type SftpPathRecord = SftpPath & XataRecord; 2897 3956 2898 3957 export type ShoutLikes = InferredTypes["shout_likes"]; 2899 3958 export type ShoutLikesRecord = ShoutLikes & XataRecord; ··· 2942 4001 artist_tags: ArtistTagsRecord; 2943 4002 artist_tracks: ArtistTracksRecord; 2944 4003 artists: ArtistsRecord; 4004 + builtin_storage_paths: BuiltinStoragePathsRecord; 4005 + dropbox: DropboxRecord; 4006 + dropbox_paths: DropboxPathsRecord; 4007 + dropbox_tokens: DropboxTokensRecord; 4008 + google_drive: GoogleDriveRecord; 4009 + google_drive_paths: GoogleDrivePathsRecord; 4010 + google_drive_tokens: GoogleDriveTokensRecord; 2945 4011 loved_tracks: LovedTracksRecord; 2946 4012 playlist_tracks: PlaylistTracksRecord; 2947 4013 playlists: PlaylistsRecord; 2948 4014 profile_shouts: ProfileShoutsRecord; 2949 4015 radios: RadiosRecord; 4016 + s3_bucket: S3BucketRecord; 4017 + s3_paths: S3PathsRecord; 4018 + s3_tokens: S3TokensRecord; 2950 4019 scrobbles: ScrobblesRecord; 4020 + sftp: SftpRecord; 4021 + sftp_access: SftpAccessRecord; 4022 + sftp_path: SftpPathRecord; 2951 4023 shout_likes: ShoutLikesRecord; 2952 4024 shout_reports: ShoutReportsRecord; 2953 4025 shouts: ShoutsRecord;
+5
rockskyweb/bun.lock
··· 7 7 "@emotion/react": "^11.14.0", 8 8 "@emotion/styled": "^11.14.0", 9 9 "@hookform/resolvers": "^4.0.0", 10 + "@iconify-json/teenyicons": "^1.2.2", 10 11 "@styled-icons/bootstrap": "^10.47.0", 11 12 "@styled-icons/boxicons-logos": "^10.47.0", 12 13 "@styled-icons/boxicons-regular": "^10.47.0", ··· 243 244 "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 244 245 245 246 "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.1", "", {}, "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA=="], 247 + 248 + "@iconify-json/teenyicons": ["@iconify-json/teenyicons@1.2.2", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-Do08DrvNpT+pKVeyFqn7nZiIviAAY8KbduSfpNKzE1bgVekAIJ/AAJtOBSUFpV4vTk+hXw195+jmCv8/0cJSKA=="], 249 + 250 + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], 246 251 247 252 "@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=="], 248 253
+1
rockskyweb/package.json
··· 17 17 "@emotion/react": "^11.14.0", 18 18 "@emotion/styled": "^11.14.0", 19 19 "@hookform/resolvers": "^4.0.0", 20 + "@iconify-json/teenyicons": "^1.2.2", 20 21 "@styled-icons/bootstrap": "^10.47.0", 21 22 "@styled-icons/boxicons-logos": "^10.47.0", 22 23 "@styled-icons/boxicons-regular": "^10.47.0",
+4
rockskyweb/src/App.tsx
··· 1 1 import { BrowserRouter, Route, Routes } from "react-router-dom"; 2 2 import AlbumPage from "./pages/album"; 3 3 import ArtistPage from "./pages/artist"; 4 + import Dropbox from "./pages/dropbox"; 5 + import GoogleDrive from "./pages/googledrive"; 4 6 import HomePage from "./pages/home"; 5 7 import PlaylistPage from "./pages/playlist"; 6 8 import ProfilePage from "./pages/profile"; ··· 20 22 element={<PlaylistPage />} 21 23 /> 22 24 <Route path="/profile/:did" element={<ProfilePage />} /> 25 + <Route path="/dropbox" element={<Dropbox />} /> 26 + <Route path="/googledrive" element={<GoogleDrive />} /> 23 27 </Routes> 24 28 </BrowserRouter> 25 29 );
+15
rockskyweb/src/components/Icons/Dropbox.tsx
··· 1 + const Dropbox = () => ( 2 + <svg 3 + xmlns="http://www.w3.org/2000/svg" 4 + width="24" 5 + height="24" 6 + viewBox="0 0 256 218" 7 + > 8 + <path 9 + fill="#0061FF" 10 + d="M63.995 0L0 40.771l63.995 40.772L128 40.771zM192 0l-64 40.775l64 40.775l64.001-40.775zM0 122.321l63.995 40.772L128 122.321L63.995 81.55zM192 81.55l-64 40.775l64 40.774l64-40.774zM64 176.771l64.005 40.772L192 176.771L128.005 136z" 11 + /> 12 + </svg> 13 + ); 14 + 15 + export default Dropbox;
+35
rockskyweb/src/components/Icons/GoogleDrive.tsx
··· 1 + const GoogleDrive = () => ( 2 + <svg 3 + xmlns="http://www.w3.org/2000/svg" 4 + width="24" 5 + height="24" 6 + viewBox="0 0 256 229" 7 + > 8 + <path 9 + fill="#0066DA" 10 + d="m19.354 196.034l11.29 19.5c2.346 4.106 5.718 7.332 9.677 9.678q17.009-21.591 23.68-33.137q6.77-11.717 16.641-36.655q-26.604-3.502-40.32-3.502q-13.165 0-40.322 3.502c0 4.545 1.173 9.09 3.519 13.196z" 11 + /> 12 + <path 13 + fill="#EA4335" 14 + d="M215.681 225.212c3.96-2.346 7.332-5.572 9.677-9.677l4.692-8.064l22.434-38.855a26.57 26.57 0 0 0 3.518-13.196q-27.315-3.502-40.247-3.502q-13.899 0-40.248 3.502q9.754 25.075 16.422 36.655q6.724 11.683 23.752 33.137" 15 + /> 16 + <path 17 + fill="#00832D" 18 + d="M128.001 73.311q19.68-23.768 27.125-36.655q5.996-10.377 13.196-33.137C164.363 1.173 159.818 0 155.126 0h-54.25C96.184 0 91.64 1.32 87.68 3.519q9.16 26.103 15.544 37.154q7.056 12.213 24.777 32.638" 19 + /> 20 + <path 21 + fill="#2684FC" 22 + d="M175.36 155.42H80.642l-40.32 69.792c3.958 2.346 8.503 3.519 13.195 3.519h148.968c4.692 0 9.238-1.32 13.196-3.52z" 23 + /> 24 + <path 25 + fill="#00AC47" 26 + d="M128.001 73.311L87.681 3.52c-3.96 2.346-7.332 5.571-9.678 9.677L3.519 142.224A26.57 26.57 0 0 0 0 155.42h80.642z" 27 + /> 28 + <path 29 + fill="#FFBA00" 30 + d="m215.242 77.71l-37.243-64.514c-2.345-4.106-5.718-7.331-9.677-9.677l-40.32 69.792l47.358 82.109h80.496c0-4.546-1.173-9.09-3.519-13.196z" 31 + /> 32 + </svg> 33 + ); 34 + 35 + export default GoogleDrive;
+7 -1
rockskyweb/src/components/ScrobblesAreaChart/ScrobblesAreaChart.tsx
··· 105 105 <AreaChart 106 106 width={300} 107 107 height={120} 108 - data={pathname === "/" ? getScrobblesChart() : data} 108 + data={ 109 + pathname === "/" || 110 + pathname.startsWith("/dropbox") || 111 + pathname.startsWith("/googledrive") 112 + ? getScrobblesChart() 113 + : data 114 + } 109 115 margin={{ 110 116 top: 5, 111 117 right: 0,
+77
rockskyweb/src/layouts/CloudDrive/CloudDrive.tsx
··· 1 + import styled from "@emotion/styled"; 2 + import { LabelMedium } from "baseui/typography"; 3 + import Dropbox from "../../components/Icons/Dropbox"; 4 + import GoogleDrive from "../../components/Icons/GoogleDrive"; 5 + import { API_URL } from "../../consts"; 6 + 7 + const MenuItem = styled.div` 8 + display: flex; 9 + justify-content: space-between; 10 + height: 50px; 11 + align-items: center; 12 + border-radius: 8px; 13 + padding-left: 15px; 14 + padding-right: 15px; 15 + cursor: pointer; 16 + &:hover { 17 + background-color: #f7f7f7; 18 + } 19 + `; 20 + 21 + function CloudDrive() { 22 + const onSelectGoogleDrive = async () => { 23 + const did = localStorage.getItem("did"); 24 + if (!did) { 25 + return; 26 + } 27 + 28 + const response = await fetch(`${API_URL}/googledrive/login`, { 29 + headers: { 30 + Authorization: `Bearer ${localStorage.getItem("token")}`, 31 + }, 32 + }); 33 + const data = await response.json(); 34 + if (data.authUrl) { 35 + window.location.href = data.authUrl; 36 + } 37 + }; 38 + 39 + const onSelectDropbox = async () => { 40 + const did = localStorage.getItem("did"); 41 + if (!did) { 42 + return; 43 + } 44 + 45 + const response = await fetch(`${API_URL}/dropbox/login`, { 46 + headers: { 47 + Authorization: `Bearer ${localStorage.getItem("token")}`, 48 + }, 49 + }); 50 + const data = await response.json(); 51 + if (data.redirectUri) { 52 + window.location.href = data.redirectUri; 53 + } 54 + }; 55 + 56 + return ( 57 + <div style={{ marginTop: 30 }}> 58 + <LabelMedium marginBottom="15px">Cloud Drive</LabelMedium> 59 + <MenuItem onClick={onSelectGoogleDrive}> 60 + <div style={{ marginTop: 5 }}> 61 + <GoogleDrive /> 62 + </div> 63 + <div style={{ flex: 1, marginLeft: 15, marginBottom: 5 }}> 64 + Google Drive 65 + </div> 66 + </MenuItem> 67 + <MenuItem onClick={onSelectDropbox}> 68 + <div style={{ marginTop: 5 }}> 69 + <Dropbox /> 70 + </div> 71 + <div style={{ flex: 1, marginLeft: 15, marginBottom: 5 }}>Dropbox</div> 72 + </MenuItem> 73 + </div> 74 + ); 75 + } 76 + 77 + export default CloudDrive;
+3
rockskyweb/src/layouts/CloudDrive/index.tsx
··· 1 + import CloudDrive from "./CloudDrive"; 2 + 3 + export default CloudDrive;
+3 -1
rockskyweb/src/layouts/Main.tsx
··· 10 10 import ScrobblesAreaChart from "../components/ScrobblesAreaChart"; 11 11 import { API_URL } from "../consts"; 12 12 import useProfile from "../hooks/useProfile"; 13 + import CloudDrive from "./CloudDrive"; 13 14 import ExternalLinks from "./ExternalLinks"; 14 15 import Navbar from "./Navbar"; 15 16 import Search from "./Search"; ··· 148 149 padding: 20, 149 150 }} 150 151 > 151 - <div> 152 + <div style={{ marginBottom: 30 }}> 152 153 <Search /> 153 154 </div> 154 155 {jwt && profile && !profile.spotifyConnected && <SpotifyLogin />} 156 + {jwt && profile && <CloudDrive />} 155 157 {!jwt && ( 156 158 <div style={{ marginTop: 40 }}> 157 159 <div style={{ marginBottom: 20 }}>
-2
rockskyweb/src/layouts/SpotifyLogin/SpotifyLogin.tsx
··· 112 112 <> 113 113 <div 114 114 style={{ 115 - marginTop: 30, 116 - marginBottom: 30, 117 115 display: "flex", 118 116 alignItems: "center", 119 117 }}
+7
rockskyweb/src/pages/dropbox/Dropbox.tsx
··· 1 + import Main from "../../layouts/Main"; 2 + 3 + const Dropbox = () => { 4 + return <Main>dropbox</Main>; 5 + }; 6 + 7 + export default Dropbox;
+3
rockskyweb/src/pages/dropbox/index.tsx
··· 1 + import Dropbox from "./Dropbox"; 2 + 3 + export default Dropbox;
+7
rockskyweb/src/pages/googledrive/GoogleDrive.tsx
··· 1 + import Main from "../../layouts/Main"; 2 + 3 + const GoogleDrive = () => { 4 + return <Main>Google drive</Main>; 5 + }; 6 + 7 + export default GoogleDrive;
+3
rockskyweb/src/pages/googledrive/index.tsx
··· 1 + import GoogleDrive from "./GoogleDrive"; 2 + 3 + export default GoogleDrive;