···5252- Docker
5353- Wasm Pack https://rustwasm.github.io/wasm-pack/installer/
5454- DuckDB https://duckdb.org/docs/installation `1.2.0`
5555+- Spotify `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET` from setup in [Spotify developer dashboard](https://developer.spotify.com/documentation/web-api/tutorials/getting-started)
55565657## 🚀 Getting Started
5758···8081 ```bash
8182 turbo db:migrate --filter=@rocksky/api
8283 ```
8383-6. Populate database (Optional):
8484+6. Setup Spotify App:
8585+ ```bash
8686+ # don't forget to set SPOTIFY_ENCRYPTION_KEY and SPOTIFY_ENCRYPTION_IV environment variables
8787+ bun run spotify <client_id> <client_secret>
8888+ ```
8989+7. Populate database (Optional):
8490 ```bash
8591 bun run db:pgpull
8692 ```
87938888-7. Start Analytics API:
9494+8. Start Analytics API:
8995 ```bash
9096 bun run dev:analytics
9197 ```
9292-8. Start jetstream:
9898+9. Start jetstream:
9399 ```bash
94100 bun run dev:jetstream
95101 ```
9696-9. Start musicbrainz:
102102+10. Start musicbrainz:
97103 ```bash
98104 bun run mb
99105 ```
100100-10. Start the development server:
106106+11. Start the development server:
101107 ```bash
102108 turbo dev --filter=@rocksky/api --filter=@rocksky/web
103109 ```
+10
apps/api/drizzle/0004_long_zzzax.sql
···11+CREATE TABLE "spotify_apps" (
22+ "xata_id" text PRIMARY KEY DEFAULT xata_id() NOT NULL,
33+ "xata_version" integer,
44+ "spotify_app_id" text NOT NULL,
55+ "xata_createdat" timestamp DEFAULT now() NOT NULL,
66+ "xata_updatedat" timestamp DEFAULT now() NOT NULL
77+);
88+--> statement-breakpoint
99+ALTER TABLE "spotify_accounts" ADD COLUMN "spotify_app_id" text NOT NULL;--> statement-breakpoint
1010+ALTER TABLE "spotify_tokens" ADD COLUMN "spotify_app_id" text NOT NULL;
+2
apps/api/drizzle/0005_same_hydra.sql
···11+ALTER TABLE "spotify_accounts" ALTER COLUMN "spotify_app_id" DROP NOT NULL;--> statement-breakpoint
22+ALTER TABLE "spotify_apps" ADD COLUMN "spotify_secret" text NOT NULL;
···1111 r#"
1212 SELECT * FROM spotify_accounts
1313 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id
1414+ LEFT JOIN spotify_apps ON spotify_accounts.spotify_app_id = spotify_apps.spotify_app_id
1415 WHERE users.did = $1
1516 "#,
1617 )
+2-1
crates/scrobbler/src/repo/spotify_token.rs
···1212 SELECT * FROM spotify_tokens
1313 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id
1414 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id
1515- WHERE is_beta_user = true
1515+ LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id
1616 WHERE users.did = $1
1717 "#,
1818 )
···3636 SELECT * FROM spotify_tokens
3737 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id
3838 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id
3939+ LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id
3940 LIMIT $1
4041 "#,
4142 )
+21-3
crates/scrobbler/src/scrobbler.rs
···188188 let mut rng = rand::rng();
189189 let random_index = rng.random_range(0..spofity_tokens.len());
190190 let spotify_token = &spofity_tokens[random_index];
191191+ let client_id = spotify_token.spotify_app_id.clone();
192192+193193+ let client_secret = decrypt_aes_256_ctr(
194194+ &spotify_token.spotify_secret,
195195+ &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?,
196196+ )?;
191197192198 let spotify_token = decrypt_aes_256_ctr(
193199 &spotify_token.refresh_token,
194200 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?,
195201 )?;
196202197197- let spotify_token = refresh_token(&spotify_token).await?;
203203+ let spotify_token = refresh_token(&spotify_token, &client_id, &client_secret).await?;
198204 let spotify_client = SpotifyClient::new(&spotify_token.access_token);
199205200206 let result = spotify_client
···362368 let mut rng = rand::rng();
363369 let random_index = rng.random_range(0..spofity_tokens.len());
364370 let spotify_token = &spofity_tokens[random_index];
371371+ let client_id = spotify_token.spotify_app_id.clone();
372372+373373+ let client_secret = decrypt_aes_256_ctr(
374374+ &spotify_token.spotify_secret,
375375+ &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?,
376376+ )?;
365377366378 let spotify_token = decrypt_aes_256_ctr(
367379 &spotify_token.refresh_token,
368380 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?,
369381 )?;
370382371371- let spotify_token = refresh_token(&spotify_token).await?;
383383+ let spotify_token = refresh_token(&spotify_token, &client_id, &client_secret).await?;
372384 let spotify_client = SpotifyClient::new(&spotify_token.access_token);
373385374386 let result = spotify_client
···615627 let random_index = rng.random_range(0..spofity_tokens.len());
616628 let spotify_token = &spofity_tokens[random_index];
617629630630+ let client_id = spotify_token.spotify_app_id.clone();
631631+ let client_secret = decrypt_aes_256_ctr(
632632+ &spotify_token.spotify_secret,
633633+ &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?,
634634+ )?;
635635+618636 let spotify_token = decrypt_aes_256_ctr(
619637 &spotify_token.refresh_token,
620638 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?,
621639 )?;
622640623623- let spotify_token = refresh_token(&spotify_token).await?;
641641+ let spotify_token = refresh_token(&spotify_token, &client_id, &client_secret).await?;
624642 let spotify_client = SpotifyClient::new(&spotify_token.access_token);
625643626644 let result = spotify_client
+5-10
crates/scrobbler/src/spotify/mod.rs
···11-use std::env;
22-31use anyhow::Error;
42use reqwest::Client;
53use types::AccessToken;
···75pub mod client;
86pub mod types;
971010-pub async fn refresh_token(token: &str) -> Result<AccessToken, Error> {
1111- if env::var("SPOTIFY_CLIENT_ID").is_err() || env::var("SPOTIFY_CLIENT_SECRET").is_err() {
1212- panic!("Please set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables");
1313- }
1414-1515- let client_id = env::var("SPOTIFY_CLIENT_ID")?;
1616- let client_secret = env::var("SPOTIFY_CLIENT_SECRET")?;
1717-88+pub async fn refresh_token(
99+ token: &str,
1010+ client_id: &str,
1111+ client_secret: &str,
1212+) -> Result<AccessToken, Error> {
1813 let client = Client::new();
19142015 let response = client
+1
crates/scrobbler/src/xata/mod.rs
···22pub mod api_key;
33pub mod artist;
44pub mod spotify_account;
55+pub mod spotify_apps;
56pub mod spotify_token;
67pub mod track;
78pub mod user;
···99 let results: Vec<SpotifyAccount> = sqlx::query_as(
1010 r#"
1111 SELECT * FROM spotify_accounts
1212+ LEFT JOIN spotify_apps ON spotify_accounts.spotify_app_id = spotify_apps.spotify_app_id
1213 WHERE user_id = $1
1314 "#,
1415 )
+2
crates/webscrobbler/src/repo/spotify_token.rs
···1212 SELECT * FROM spotify_tokens
1313 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id
1414 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id
1515+ LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id
1516 WHERE users.did = $1
1617 "#,
1718 )
···3536 SELECT * FROM spotify_tokens
3637 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id
3738 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id
3939+ LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id
3840 WHERE is_beta_user = true
3941 LIMIT $1
4042 "#,
+7-1
crates/webscrobbler/src/scrobbler.rs
···8484 let mut rng = rand::rng();
8585 let random_index = rng.random_range(0..spofity_tokens.len());
8686 let spotify_token = &spofity_tokens[random_index];
8787+ let client_id = spotify_token.spotify_app_id.clone();
8888+8989+ let client_secret = decrypt_aes_256_ctr(
9090+ &spotify_token.spotify_secret,
9191+ &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?,
9292+ )?;
87938894 let spotify_token = decrypt_aes_256_ctr(
8995 &spotify_token.refresh_token,
9096 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?,
9197 )?;
92989393- let spotify_token = refresh_token(&spotify_token).await?;
9999+ let spotify_token = refresh_token(&spotify_token, &client_id, &client_secret).await?;
94100 let spotify_client = SpotifyClient::new(&spotify_token.access_token);
9510196102 let query = match scrobble.data.song.parsed.artist.contains(" x ") {
+5-10
crates/webscrobbler/src/spotify/mod.rs
···11-use std::env;
22-31use anyhow::Error;
42use reqwest::Client;
53use types::AccessToken;
···75pub mod client;
86pub mod types;
971010-pub async fn refresh_token(token: &str) -> Result<AccessToken, Error> {
1111- if env::var("SPOTIFY_CLIENT_ID").is_err() || env::var("SPOTIFY_CLIENT_SECRET").is_err() {
1212- panic!("Please set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables");
1313- }
1414-1515- let client_id = env::var("SPOTIFY_CLIENT_ID")?;
1616- let client_secret = env::var("SPOTIFY_CLIENT_SECRET")?;
1717-88+pub async fn refresh_token(
99+ token: &str,
1010+ client_id: &str,
1111+ client_secret: &str,
1212+) -> Result<AccessToken, Error> {
1813 let client = Client::new();
19142015 let response = client
+1
crates/webscrobbler/src/xata/mod.rs
···11pub mod album;
22pub mod artist;
33pub mod spotify_account;
44+pub mod spotify_apps;
45pub mod spotify_token;
56pub mod track;
67pub mod user;