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