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

feat: Enhance Spotify integration with app-specific tokens

- Updated Spotify token retrieval to include app-specific client IDs and secrets.
- Modified database queries to join with `spotify_apps` for fetching app-related information.
- Refactored token refresh logic to utilize app-specific credentials.
- Adjusted functions across the codebase to accommodate new parameters for client ID and secret.
- Improved error handling and logging for Spotify-related operations.
- Removed deprecated environment variable checks for client credentials.

+7038 -369
+10
apps/api/drizzle/0004_long_zzzax.sql
··· 1 + CREATE TABLE "spotify_apps" ( 2 + "xata_id" text PRIMARY KEY DEFAULT xata_id() NOT NULL, 3 + "xata_version" integer, 4 + "spotify_app_id" text NOT NULL, 5 + "xata_createdat" timestamp DEFAULT now() NOT NULL, 6 + "xata_updatedat" timestamp DEFAULT now() NOT NULL 7 + ); 8 + --> statement-breakpoint 9 + ALTER TABLE "spotify_accounts" ADD COLUMN "spotify_app_id" text NOT NULL;--> statement-breakpoint 10 + ALTER TABLE "spotify_tokens" ADD COLUMN "spotify_app_id" text NOT NULL;
+2
apps/api/drizzle/0005_same_hydra.sql
··· 1 + ALTER TABLE "spotify_accounts" ALTER COLUMN "spotify_app_id" DROP NOT NULL;--> statement-breakpoint 2 + ALTER TABLE "spotify_apps" ADD COLUMN "spotify_secret" text NOT NULL;
+3224
apps/api/drizzle/meta/0004_snapshot.json
··· 1 + { 2 + "id": "3294c136-37d7-4c61-8633-26d17c05a059", 3 + "prevId": "015093fe-a66e-4ec3-b5b6-5466c6266a39", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.album_tracks": { 8 + "name": "album_tracks", 9 + "schema": "", 10 + "columns": { 11 + "xata_id": { 12 + "name": "xata_id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "album_id": { 19 + "name": "album_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": true 23 + }, 24 + "track_id": { 25 + "name": "track_id", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + }, 30 + "xata_createdat": { 31 + "name": "xata_createdat", 32 + "type": "timestamp", 33 + "primaryKey": false, 34 + "notNull": true, 35 + "default": "now()" 36 + }, 37 + "xata_updatedat": { 38 + "name": "xata_updatedat", 39 + "type": "timestamp", 40 + "primaryKey": false, 41 + "notNull": true, 42 + "default": "now()" 43 + }, 44 + "xata_version": { 45 + "name": "xata_version", 46 + "type": "integer", 47 + "primaryKey": false, 48 + "notNull": false 49 + } 50 + }, 51 + "indexes": {}, 52 + "foreignKeys": { 53 + "album_tracks_album_id_albums_xata_id_fk": { 54 + "name": "album_tracks_album_id_albums_xata_id_fk", 55 + "tableFrom": "album_tracks", 56 + "tableTo": "albums", 57 + "columnsFrom": [ 58 + "album_id" 59 + ], 60 + "columnsTo": [ 61 + "xata_id" 62 + ], 63 + "onDelete": "no action", 64 + "onUpdate": "no action" 65 + }, 66 + "album_tracks_track_id_tracks_xata_id_fk": { 67 + "name": "album_tracks_track_id_tracks_xata_id_fk", 68 + "tableFrom": "album_tracks", 69 + "tableTo": "tracks", 70 + "columnsFrom": [ 71 + "track_id" 72 + ], 73 + "columnsTo": [ 74 + "xata_id" 75 + ], 76 + "onDelete": "no action", 77 + "onUpdate": "no action" 78 + } 79 + }, 80 + "compositePrimaryKeys": {}, 81 + "uniqueConstraints": {}, 82 + "policies": {}, 83 + "checkConstraints": {}, 84 + "isRLSEnabled": false 85 + }, 86 + "public.albums": { 87 + "name": "albums", 88 + "schema": "", 89 + "columns": { 90 + "xata_id": { 91 + "name": "xata_id", 92 + "type": "text", 93 + "primaryKey": true, 94 + "notNull": true, 95 + "default": "xata_id()" 96 + }, 97 + "title": { 98 + "name": "title", 99 + "type": "text", 100 + "primaryKey": false, 101 + "notNull": true 102 + }, 103 + "artist": { 104 + "name": "artist", 105 + "type": "text", 106 + "primaryKey": false, 107 + "notNull": true 108 + }, 109 + "release_date": { 110 + "name": "release_date", 111 + "type": "text", 112 + "primaryKey": false, 113 + "notNull": false 114 + }, 115 + "year": { 116 + "name": "year", 117 + "type": "integer", 118 + "primaryKey": false, 119 + "notNull": false 120 + }, 121 + "album_art": { 122 + "name": "album_art", 123 + "type": "text", 124 + "primaryKey": false, 125 + "notNull": false 126 + }, 127 + "uri": { 128 + "name": "uri", 129 + "type": "text", 130 + "primaryKey": false, 131 + "notNull": false 132 + }, 133 + "artist_uri": { 134 + "name": "artist_uri", 135 + "type": "text", 136 + "primaryKey": false, 137 + "notNull": false 138 + }, 139 + "apple_music_link": { 140 + "name": "apple_music_link", 141 + "type": "text", 142 + "primaryKey": false, 143 + "notNull": false 144 + }, 145 + "spotify_link": { 146 + "name": "spotify_link", 147 + "type": "text", 148 + "primaryKey": false, 149 + "notNull": false 150 + }, 151 + "tidal_link": { 152 + "name": "tidal_link", 153 + "type": "text", 154 + "primaryKey": false, 155 + "notNull": false 156 + }, 157 + "youtube_link": { 158 + "name": "youtube_link", 159 + "type": "text", 160 + "primaryKey": false, 161 + "notNull": false 162 + }, 163 + "sha256": { 164 + "name": "sha256", 165 + "type": "text", 166 + "primaryKey": false, 167 + "notNull": true 168 + }, 169 + "xata_createdat": { 170 + "name": "xata_createdat", 171 + "type": "timestamp", 172 + "primaryKey": false, 173 + "notNull": true, 174 + "default": "now()" 175 + }, 176 + "xata_updatedat": { 177 + "name": "xata_updatedat", 178 + "type": "timestamp", 179 + "primaryKey": false, 180 + "notNull": true, 181 + "default": "now()" 182 + }, 183 + "xata_version": { 184 + "name": "xata_version", 185 + "type": "integer", 186 + "primaryKey": false, 187 + "notNull": false 188 + } 189 + }, 190 + "indexes": {}, 191 + "foreignKeys": {}, 192 + "compositePrimaryKeys": {}, 193 + "uniqueConstraints": { 194 + "albums_uri_unique": { 195 + "name": "albums_uri_unique", 196 + "nullsNotDistinct": false, 197 + "columns": [ 198 + "uri" 199 + ] 200 + }, 201 + "albums_apple_music_link_unique": { 202 + "name": "albums_apple_music_link_unique", 203 + "nullsNotDistinct": false, 204 + "columns": [ 205 + "apple_music_link" 206 + ] 207 + }, 208 + "albums_spotify_link_unique": { 209 + "name": "albums_spotify_link_unique", 210 + "nullsNotDistinct": false, 211 + "columns": [ 212 + "spotify_link" 213 + ] 214 + }, 215 + "albums_tidal_link_unique": { 216 + "name": "albums_tidal_link_unique", 217 + "nullsNotDistinct": false, 218 + "columns": [ 219 + "tidal_link" 220 + ] 221 + }, 222 + "albums_youtube_link_unique": { 223 + "name": "albums_youtube_link_unique", 224 + "nullsNotDistinct": false, 225 + "columns": [ 226 + "youtube_link" 227 + ] 228 + }, 229 + "albums_sha256_unique": { 230 + "name": "albums_sha256_unique", 231 + "nullsNotDistinct": false, 232 + "columns": [ 233 + "sha256" 234 + ] 235 + } 236 + }, 237 + "policies": {}, 238 + "checkConstraints": {}, 239 + "isRLSEnabled": false 240 + }, 241 + "public.api_keys": { 242 + "name": "api_keys", 243 + "schema": "", 244 + "columns": { 245 + "xata_id": { 246 + "name": "xata_id", 247 + "type": "text", 248 + "primaryKey": true, 249 + "notNull": true, 250 + "default": "xata_id()" 251 + }, 252 + "name": { 253 + "name": "name", 254 + "type": "text", 255 + "primaryKey": false, 256 + "notNull": true 257 + }, 258 + "api_key": { 259 + "name": "api_key", 260 + "type": "text", 261 + "primaryKey": false, 262 + "notNull": true 263 + }, 264 + "shared_secret": { 265 + "name": "shared_secret", 266 + "type": "text", 267 + "primaryKey": false, 268 + "notNull": true 269 + }, 270 + "description": { 271 + "name": "description", 272 + "type": "text", 273 + "primaryKey": false, 274 + "notNull": false 275 + }, 276 + "enabled": { 277 + "name": "enabled", 278 + "type": "boolean", 279 + "primaryKey": false, 280 + "notNull": true, 281 + "default": true 282 + }, 283 + "user_id": { 284 + "name": "user_id", 285 + "type": "text", 286 + "primaryKey": false, 287 + "notNull": true 288 + }, 289 + "xata_createdat": { 290 + "name": "xata_createdat", 291 + "type": "timestamp", 292 + "primaryKey": false, 293 + "notNull": true, 294 + "default": "now()" 295 + }, 296 + "xata_updatedat": { 297 + "name": "xata_updatedat", 298 + "type": "timestamp", 299 + "primaryKey": false, 300 + "notNull": true, 301 + "default": "now()" 302 + } 303 + }, 304 + "indexes": {}, 305 + "foreignKeys": { 306 + "api_keys_user_id_users_xata_id_fk": { 307 + "name": "api_keys_user_id_users_xata_id_fk", 308 + "tableFrom": "api_keys", 309 + "tableTo": "users", 310 + "columnsFrom": [ 311 + "user_id" 312 + ], 313 + "columnsTo": [ 314 + "xata_id" 315 + ], 316 + "onDelete": "no action", 317 + "onUpdate": "no action" 318 + } 319 + }, 320 + "compositePrimaryKeys": {}, 321 + "uniqueConstraints": {}, 322 + "policies": {}, 323 + "checkConstraints": {}, 324 + "isRLSEnabled": false 325 + }, 326 + "public.artist_albums": { 327 + "name": "artist_albums", 328 + "schema": "", 329 + "columns": { 330 + "xata_id": { 331 + "name": "xata_id", 332 + "type": "text", 333 + "primaryKey": true, 334 + "notNull": true, 335 + "default": "xata_id()" 336 + }, 337 + "artist_id": { 338 + "name": "artist_id", 339 + "type": "text", 340 + "primaryKey": false, 341 + "notNull": true 342 + }, 343 + "album_id": { 344 + "name": "album_id", 345 + "type": "text", 346 + "primaryKey": false, 347 + "notNull": true 348 + }, 349 + "xata_createdat": { 350 + "name": "xata_createdat", 351 + "type": "timestamp", 352 + "primaryKey": false, 353 + "notNull": true, 354 + "default": "now()" 355 + }, 356 + "xata_updatedat": { 357 + "name": "xata_updatedat", 358 + "type": "timestamp", 359 + "primaryKey": false, 360 + "notNull": true, 361 + "default": "now()" 362 + }, 363 + "xata_version": { 364 + "name": "xata_version", 365 + "type": "integer", 366 + "primaryKey": false, 367 + "notNull": false 368 + } 369 + }, 370 + "indexes": {}, 371 + "foreignKeys": { 372 + "artist_albums_artist_id_artists_xata_id_fk": { 373 + "name": "artist_albums_artist_id_artists_xata_id_fk", 374 + "tableFrom": "artist_albums", 375 + "tableTo": "artists", 376 + "columnsFrom": [ 377 + "artist_id" 378 + ], 379 + "columnsTo": [ 380 + "xata_id" 381 + ], 382 + "onDelete": "no action", 383 + "onUpdate": "no action" 384 + }, 385 + "artist_albums_album_id_albums_xata_id_fk": { 386 + "name": "artist_albums_album_id_albums_xata_id_fk", 387 + "tableFrom": "artist_albums", 388 + "tableTo": "albums", 389 + "columnsFrom": [ 390 + "album_id" 391 + ], 392 + "columnsTo": [ 393 + "xata_id" 394 + ], 395 + "onDelete": "no action", 396 + "onUpdate": "no action" 397 + } 398 + }, 399 + "compositePrimaryKeys": {}, 400 + "uniqueConstraints": {}, 401 + "policies": {}, 402 + "checkConstraints": {}, 403 + "isRLSEnabled": false 404 + }, 405 + "public.artist_tracks": { 406 + "name": "artist_tracks", 407 + "schema": "", 408 + "columns": { 409 + "xata_id": { 410 + "name": "xata_id", 411 + "type": "text", 412 + "primaryKey": true, 413 + "notNull": true, 414 + "default": "xata_id()" 415 + }, 416 + "artist_id": { 417 + "name": "artist_id", 418 + "type": "text", 419 + "primaryKey": false, 420 + "notNull": true 421 + }, 422 + "track_id": { 423 + "name": "track_id", 424 + "type": "text", 425 + "primaryKey": false, 426 + "notNull": true 427 + }, 428 + "xata_createdat": { 429 + "name": "xata_createdat", 430 + "type": "timestamp", 431 + "primaryKey": false, 432 + "notNull": true, 433 + "default": "now()" 434 + }, 435 + "xata_updatedat": { 436 + "name": "xata_updatedat", 437 + "type": "timestamp", 438 + "primaryKey": false, 439 + "notNull": true, 440 + "default": "now()" 441 + }, 442 + "xata_version": { 443 + "name": "xata_version", 444 + "type": "integer", 445 + "primaryKey": false, 446 + "notNull": false 447 + } 448 + }, 449 + "indexes": {}, 450 + "foreignKeys": { 451 + "artist_tracks_artist_id_artists_xata_id_fk": { 452 + "name": "artist_tracks_artist_id_artists_xata_id_fk", 453 + "tableFrom": "artist_tracks", 454 + "tableTo": "artists", 455 + "columnsFrom": [ 456 + "artist_id" 457 + ], 458 + "columnsTo": [ 459 + "xata_id" 460 + ], 461 + "onDelete": "no action", 462 + "onUpdate": "no action" 463 + }, 464 + "artist_tracks_track_id_tracks_xata_id_fk": { 465 + "name": "artist_tracks_track_id_tracks_xata_id_fk", 466 + "tableFrom": "artist_tracks", 467 + "tableTo": "tracks", 468 + "columnsFrom": [ 469 + "track_id" 470 + ], 471 + "columnsTo": [ 472 + "xata_id" 473 + ], 474 + "onDelete": "no action", 475 + "onUpdate": "no action" 476 + } 477 + }, 478 + "compositePrimaryKeys": {}, 479 + "uniqueConstraints": {}, 480 + "policies": {}, 481 + "checkConstraints": {}, 482 + "isRLSEnabled": false 483 + }, 484 + "public.artists": { 485 + "name": "artists", 486 + "schema": "", 487 + "columns": { 488 + "xata_id": { 489 + "name": "xata_id", 490 + "type": "text", 491 + "primaryKey": true, 492 + "notNull": true, 493 + "default": "xata_id()" 494 + }, 495 + "name": { 496 + "name": "name", 497 + "type": "text", 498 + "primaryKey": false, 499 + "notNull": true 500 + }, 501 + "biography": { 502 + "name": "biography", 503 + "type": "text", 504 + "primaryKey": false, 505 + "notNull": false 506 + }, 507 + "born": { 508 + "name": "born", 509 + "type": "timestamp", 510 + "primaryKey": false, 511 + "notNull": false 512 + }, 513 + "born_in": { 514 + "name": "born_in", 515 + "type": "text", 516 + "primaryKey": false, 517 + "notNull": false 518 + }, 519 + "died": { 520 + "name": "died", 521 + "type": "timestamp", 522 + "primaryKey": false, 523 + "notNull": false 524 + }, 525 + "picture": { 526 + "name": "picture", 527 + "type": "text", 528 + "primaryKey": false, 529 + "notNull": false 530 + }, 531 + "sha256": { 532 + "name": "sha256", 533 + "type": "text", 534 + "primaryKey": false, 535 + "notNull": true 536 + }, 537 + "uri": { 538 + "name": "uri", 539 + "type": "text", 540 + "primaryKey": false, 541 + "notNull": false 542 + }, 543 + "apple_music_link": { 544 + "name": "apple_music_link", 545 + "type": "text", 546 + "primaryKey": false, 547 + "notNull": false 548 + }, 549 + "spotify_link": { 550 + "name": "spotify_link", 551 + "type": "text", 552 + "primaryKey": false, 553 + "notNull": false 554 + }, 555 + "tidal_link": { 556 + "name": "tidal_link", 557 + "type": "text", 558 + "primaryKey": false, 559 + "notNull": false 560 + }, 561 + "youtube_link": { 562 + "name": "youtube_link", 563 + "type": "text", 564 + "primaryKey": false, 565 + "notNull": false 566 + }, 567 + "genres": { 568 + "name": "genres", 569 + "type": "text[]", 570 + "primaryKey": false, 571 + "notNull": false 572 + }, 573 + "xata_createdat": { 574 + "name": "xata_createdat", 575 + "type": "timestamp", 576 + "primaryKey": false, 577 + "notNull": true, 578 + "default": "now()" 579 + }, 580 + "xata_updatedat": { 581 + "name": "xata_updatedat", 582 + "type": "timestamp", 583 + "primaryKey": false, 584 + "notNull": true, 585 + "default": "now()" 586 + }, 587 + "xata_version": { 588 + "name": "xata_version", 589 + "type": "integer", 590 + "primaryKey": false, 591 + "notNull": false 592 + } 593 + }, 594 + "indexes": {}, 595 + "foreignKeys": {}, 596 + "compositePrimaryKeys": {}, 597 + "uniqueConstraints": { 598 + "artists_sha256_unique": { 599 + "name": "artists_sha256_unique", 600 + "nullsNotDistinct": false, 601 + "columns": [ 602 + "sha256" 603 + ] 604 + }, 605 + "artists_uri_unique": { 606 + "name": "artists_uri_unique", 607 + "nullsNotDistinct": false, 608 + "columns": [ 609 + "uri" 610 + ] 611 + } 612 + }, 613 + "policies": {}, 614 + "checkConstraints": {}, 615 + "isRLSEnabled": false 616 + }, 617 + "public.dropbox_accounts": { 618 + "name": "dropbox_accounts", 619 + "schema": "", 620 + "columns": { 621 + "xata_id": { 622 + "name": "xata_id", 623 + "type": "text", 624 + "primaryKey": true, 625 + "notNull": true, 626 + "default": "xata_id()" 627 + }, 628 + "email": { 629 + "name": "email", 630 + "type": "text", 631 + "primaryKey": false, 632 + "notNull": true 633 + }, 634 + "is_beta_user": { 635 + "name": "is_beta_user", 636 + "type": "boolean", 637 + "primaryKey": false, 638 + "notNull": true, 639 + "default": false 640 + }, 641 + "user_id": { 642 + "name": "user_id", 643 + "type": "text", 644 + "primaryKey": false, 645 + "notNull": true 646 + }, 647 + "xata_version": { 648 + "name": "xata_version", 649 + "type": "text", 650 + "primaryKey": false, 651 + "notNull": false 652 + }, 653 + "xata_createdat": { 654 + "name": "xata_createdat", 655 + "type": "timestamp", 656 + "primaryKey": false, 657 + "notNull": true, 658 + "default": "now()" 659 + }, 660 + "xata_updatedat": { 661 + "name": "xata_updatedat", 662 + "type": "timestamp", 663 + "primaryKey": false, 664 + "notNull": true, 665 + "default": "now()" 666 + } 667 + }, 668 + "indexes": {}, 669 + "foreignKeys": { 670 + "dropbox_accounts_user_id_users_xata_id_fk": { 671 + "name": "dropbox_accounts_user_id_users_xata_id_fk", 672 + "tableFrom": "dropbox_accounts", 673 + "tableTo": "users", 674 + "columnsFrom": [ 675 + "user_id" 676 + ], 677 + "columnsTo": [ 678 + "xata_id" 679 + ], 680 + "onDelete": "no action", 681 + "onUpdate": "no action" 682 + } 683 + }, 684 + "compositePrimaryKeys": {}, 685 + "uniqueConstraints": { 686 + "dropbox_accounts_email_unique": { 687 + "name": "dropbox_accounts_email_unique", 688 + "nullsNotDistinct": false, 689 + "columns": [ 690 + "email" 691 + ] 692 + } 693 + }, 694 + "policies": {}, 695 + "checkConstraints": {}, 696 + "isRLSEnabled": false 697 + }, 698 + "public.dropbox_directories": { 699 + "name": "dropbox_directories", 700 + "schema": "", 701 + "columns": { 702 + "xata_id": { 703 + "name": "xata_id", 704 + "type": "text", 705 + "primaryKey": true, 706 + "notNull": true, 707 + "default": "xata_id()" 708 + }, 709 + "name": { 710 + "name": "name", 711 + "type": "text", 712 + "primaryKey": false, 713 + "notNull": true 714 + }, 715 + "path": { 716 + "name": "path", 717 + "type": "text", 718 + "primaryKey": false, 719 + "notNull": true 720 + }, 721 + "parent_id": { 722 + "name": "parent_id", 723 + "type": "text", 724 + "primaryKey": false, 725 + "notNull": false 726 + }, 727 + "dropbox_id": { 728 + "name": "dropbox_id", 729 + "type": "text", 730 + "primaryKey": false, 731 + "notNull": true 732 + }, 733 + "file_id": { 734 + "name": "file_id", 735 + "type": "text", 736 + "primaryKey": false, 737 + "notNull": true 738 + }, 739 + "xata_version": { 740 + "name": "xata_version", 741 + "type": "text", 742 + "primaryKey": false, 743 + "notNull": false 744 + }, 745 + "xata_createdat": { 746 + "name": "xata_createdat", 747 + "type": "timestamp", 748 + "primaryKey": false, 749 + "notNull": true, 750 + "default": "now()" 751 + }, 752 + "xata_updatedat": { 753 + "name": "xata_updatedat", 754 + "type": "timestamp", 755 + "primaryKey": false, 756 + "notNull": true, 757 + "default": "now()" 758 + } 759 + }, 760 + "indexes": {}, 761 + "foreignKeys": { 762 + "dropbox_directories_parent_id_dropbox_directories_xata_id_fk": { 763 + "name": "dropbox_directories_parent_id_dropbox_directories_xata_id_fk", 764 + "tableFrom": "dropbox_directories", 765 + "tableTo": "dropbox_directories", 766 + "columnsFrom": [ 767 + "parent_id" 768 + ], 769 + "columnsTo": [ 770 + "xata_id" 771 + ], 772 + "onDelete": "no action", 773 + "onUpdate": "no action" 774 + } 775 + }, 776 + "compositePrimaryKeys": {}, 777 + "uniqueConstraints": { 778 + "dropbox_directories_file_id_unique": { 779 + "name": "dropbox_directories_file_id_unique", 780 + "nullsNotDistinct": false, 781 + "columns": [ 782 + "file_id" 783 + ] 784 + } 785 + }, 786 + "policies": {}, 787 + "checkConstraints": {}, 788 + "isRLSEnabled": false 789 + }, 790 + "public.dropbox_paths": { 791 + "name": "dropbox_paths", 792 + "schema": "", 793 + "columns": { 794 + "xata_id": { 795 + "name": "xata_id", 796 + "type": "text", 797 + "primaryKey": true, 798 + "notNull": true, 799 + "default": "xata_id()" 800 + }, 801 + "path": { 802 + "name": "path", 803 + "type": "text", 804 + "primaryKey": false, 805 + "notNull": true 806 + }, 807 + "name": { 808 + "name": "name", 809 + "type": "text", 810 + "primaryKey": false, 811 + "notNull": true 812 + }, 813 + "dropbox_id": { 814 + "name": "dropbox_id", 815 + "type": "text", 816 + "primaryKey": false, 817 + "notNull": true 818 + }, 819 + "track_id": { 820 + "name": "track_id", 821 + "type": "text", 822 + "primaryKey": false, 823 + "notNull": true 824 + }, 825 + "directory_id": { 826 + "name": "directory_id", 827 + "type": "text", 828 + "primaryKey": false, 829 + "notNull": false 830 + }, 831 + "file_id": { 832 + "name": "file_id", 833 + "type": "text", 834 + "primaryKey": false, 835 + "notNull": true 836 + }, 837 + "xata_version": { 838 + "name": "xata_version", 839 + "type": "text", 840 + "primaryKey": false, 841 + "notNull": false 842 + }, 843 + "xata_createdat": { 844 + "name": "xata_createdat", 845 + "type": "timestamp", 846 + "primaryKey": false, 847 + "notNull": true, 848 + "default": "now()" 849 + }, 850 + "xata_updatedat": { 851 + "name": "xata_updatedat", 852 + "type": "timestamp", 853 + "primaryKey": false, 854 + "notNull": true, 855 + "default": "now()" 856 + } 857 + }, 858 + "indexes": {}, 859 + "foreignKeys": { 860 + "dropbox_paths_directory_id_dropbox_directories_xata_id_fk": { 861 + "name": "dropbox_paths_directory_id_dropbox_directories_xata_id_fk", 862 + "tableFrom": "dropbox_paths", 863 + "tableTo": "dropbox_directories", 864 + "columnsFrom": [ 865 + "directory_id" 866 + ], 867 + "columnsTo": [ 868 + "xata_id" 869 + ], 870 + "onDelete": "no action", 871 + "onUpdate": "no action" 872 + } 873 + }, 874 + "compositePrimaryKeys": {}, 875 + "uniqueConstraints": { 876 + "dropbox_paths_file_id_unique": { 877 + "name": "dropbox_paths_file_id_unique", 878 + "nullsNotDistinct": false, 879 + "columns": [ 880 + "file_id" 881 + ] 882 + } 883 + }, 884 + "policies": {}, 885 + "checkConstraints": {}, 886 + "isRLSEnabled": false 887 + }, 888 + "public.dropbox_tokens": { 889 + "name": "dropbox_tokens", 890 + "schema": "", 891 + "columns": { 892 + "xata_id": { 893 + "name": "xata_id", 894 + "type": "text", 895 + "primaryKey": true, 896 + "notNull": true, 897 + "default": "xata_id()" 898 + }, 899 + "refresh_token": { 900 + "name": "refresh_token", 901 + "type": "text", 902 + "primaryKey": false, 903 + "notNull": true 904 + }, 905 + "xata_createdat": { 906 + "name": "xata_createdat", 907 + "type": "timestamp", 908 + "primaryKey": false, 909 + "notNull": true, 910 + "default": "now()" 911 + }, 912 + "xata_updatedat": { 913 + "name": "xata_updatedat", 914 + "type": "timestamp", 915 + "primaryKey": false, 916 + "notNull": true, 917 + "default": "now()" 918 + } 919 + }, 920 + "indexes": {}, 921 + "foreignKeys": {}, 922 + "compositePrimaryKeys": {}, 923 + "uniqueConstraints": {}, 924 + "policies": {}, 925 + "checkConstraints": {}, 926 + "isRLSEnabled": false 927 + }, 928 + "public.dropbox": { 929 + "name": "dropbox", 930 + "schema": "", 931 + "columns": { 932 + "xata_id": { 933 + "name": "xata_id", 934 + "type": "text", 935 + "primaryKey": true, 936 + "notNull": true, 937 + "default": "xata_id()" 938 + }, 939 + "user_id": { 940 + "name": "user_id", 941 + "type": "text", 942 + "primaryKey": false, 943 + "notNull": true 944 + }, 945 + "dropbox_token_id": { 946 + "name": "dropbox_token_id", 947 + "type": "text", 948 + "primaryKey": false, 949 + "notNull": true 950 + }, 951 + "xata_version": { 952 + "name": "xata_version", 953 + "type": "text", 954 + "primaryKey": false, 955 + "notNull": false 956 + }, 957 + "xata_createdat": { 958 + "name": "xata_createdat", 959 + "type": "timestamp", 960 + "primaryKey": false, 961 + "notNull": true, 962 + "default": "now()" 963 + }, 964 + "xata_updatedat": { 965 + "name": "xata_updatedat", 966 + "type": "timestamp", 967 + "primaryKey": false, 968 + "notNull": true, 969 + "default": "now()" 970 + } 971 + }, 972 + "indexes": {}, 973 + "foreignKeys": { 974 + "dropbox_user_id_users_xata_id_fk": { 975 + "name": "dropbox_user_id_users_xata_id_fk", 976 + "tableFrom": "dropbox", 977 + "tableTo": "users", 978 + "columnsFrom": [ 979 + "user_id" 980 + ], 981 + "columnsTo": [ 982 + "xata_id" 983 + ], 984 + "onDelete": "no action", 985 + "onUpdate": "no action" 986 + }, 987 + "dropbox_dropbox_token_id_dropbox_tokens_xata_id_fk": { 988 + "name": "dropbox_dropbox_token_id_dropbox_tokens_xata_id_fk", 989 + "tableFrom": "dropbox", 990 + "tableTo": "dropbox_tokens", 991 + "columnsFrom": [ 992 + "dropbox_token_id" 993 + ], 994 + "columnsTo": [ 995 + "xata_id" 996 + ], 997 + "onDelete": "no action", 998 + "onUpdate": "no action" 999 + } 1000 + }, 1001 + "compositePrimaryKeys": {}, 1002 + "uniqueConstraints": {}, 1003 + "policies": {}, 1004 + "checkConstraints": {}, 1005 + "isRLSEnabled": false 1006 + }, 1007 + "public.google_drive_accounts": { 1008 + "name": "google_drive_accounts", 1009 + "schema": "", 1010 + "columns": { 1011 + "xata_id": { 1012 + "name": "xata_id", 1013 + "type": "text", 1014 + "primaryKey": true, 1015 + "notNull": true, 1016 + "default": "xata_id()" 1017 + }, 1018 + "email": { 1019 + "name": "email", 1020 + "type": "text", 1021 + "primaryKey": false, 1022 + "notNull": true 1023 + }, 1024 + "is_beta_user": { 1025 + "name": "is_beta_user", 1026 + "type": "boolean", 1027 + "primaryKey": false, 1028 + "notNull": true, 1029 + "default": false 1030 + }, 1031 + "user_id": { 1032 + "name": "user_id", 1033 + "type": "text", 1034 + "primaryKey": false, 1035 + "notNull": true 1036 + }, 1037 + "xata_version": { 1038 + "name": "xata_version", 1039 + "type": "text", 1040 + "primaryKey": false, 1041 + "notNull": false 1042 + }, 1043 + "xata_createdat": { 1044 + "name": "xata_createdat", 1045 + "type": "timestamp", 1046 + "primaryKey": false, 1047 + "notNull": true, 1048 + "default": "now()" 1049 + }, 1050 + "xata_updatedat": { 1051 + "name": "xata_updatedat", 1052 + "type": "timestamp", 1053 + "primaryKey": false, 1054 + "notNull": true, 1055 + "default": "now()" 1056 + } 1057 + }, 1058 + "indexes": {}, 1059 + "foreignKeys": { 1060 + "google_drive_accounts_user_id_users_xata_id_fk": { 1061 + "name": "google_drive_accounts_user_id_users_xata_id_fk", 1062 + "tableFrom": "google_drive_accounts", 1063 + "tableTo": "users", 1064 + "columnsFrom": [ 1065 + "user_id" 1066 + ], 1067 + "columnsTo": [ 1068 + "xata_id" 1069 + ], 1070 + "onDelete": "no action", 1071 + "onUpdate": "no action" 1072 + } 1073 + }, 1074 + "compositePrimaryKeys": {}, 1075 + "uniqueConstraints": { 1076 + "google_drive_accounts_email_unique": { 1077 + "name": "google_drive_accounts_email_unique", 1078 + "nullsNotDistinct": false, 1079 + "columns": [ 1080 + "email" 1081 + ] 1082 + } 1083 + }, 1084 + "policies": {}, 1085 + "checkConstraints": {}, 1086 + "isRLSEnabled": false 1087 + }, 1088 + "public.google_drive_directories": { 1089 + "name": "google_drive_directories", 1090 + "schema": "", 1091 + "columns": { 1092 + "xata_id": { 1093 + "name": "xata_id", 1094 + "type": "text", 1095 + "primaryKey": true, 1096 + "notNull": true, 1097 + "default": "xata_id()" 1098 + }, 1099 + "name": { 1100 + "name": "name", 1101 + "type": "text", 1102 + "primaryKey": false, 1103 + "notNull": true 1104 + }, 1105 + "path": { 1106 + "name": "path", 1107 + "type": "text", 1108 + "primaryKey": false, 1109 + "notNull": true 1110 + }, 1111 + "parent_id": { 1112 + "name": "parent_id", 1113 + "type": "text", 1114 + "primaryKey": false, 1115 + "notNull": false 1116 + }, 1117 + "google_drive_id": { 1118 + "name": "google_drive_id", 1119 + "type": "text", 1120 + "primaryKey": false, 1121 + "notNull": true 1122 + }, 1123 + "file_id": { 1124 + "name": "file_id", 1125 + "type": "text", 1126 + "primaryKey": false, 1127 + "notNull": true 1128 + }, 1129 + "xata_version": { 1130 + "name": "xata_version", 1131 + "type": "text", 1132 + "primaryKey": false, 1133 + "notNull": false 1134 + }, 1135 + "xata_createdat": { 1136 + "name": "xata_createdat", 1137 + "type": "timestamp", 1138 + "primaryKey": false, 1139 + "notNull": true, 1140 + "default": "now()" 1141 + }, 1142 + "xata_updatedat": { 1143 + "name": "xata_updatedat", 1144 + "type": "timestamp", 1145 + "primaryKey": false, 1146 + "notNull": true, 1147 + "default": "now()" 1148 + } 1149 + }, 1150 + "indexes": {}, 1151 + "foreignKeys": { 1152 + "google_drive_directories_parent_id_google_drive_directories_xata_id_fk": { 1153 + "name": "google_drive_directories_parent_id_google_drive_directories_xata_id_fk", 1154 + "tableFrom": "google_drive_directories", 1155 + "tableTo": "google_drive_directories", 1156 + "columnsFrom": [ 1157 + "parent_id" 1158 + ], 1159 + "columnsTo": [ 1160 + "xata_id" 1161 + ], 1162 + "onDelete": "no action", 1163 + "onUpdate": "no action" 1164 + } 1165 + }, 1166 + "compositePrimaryKeys": {}, 1167 + "uniqueConstraints": { 1168 + "google_drive_directories_file_id_unique": { 1169 + "name": "google_drive_directories_file_id_unique", 1170 + "nullsNotDistinct": false, 1171 + "columns": [ 1172 + "file_id" 1173 + ] 1174 + } 1175 + }, 1176 + "policies": {}, 1177 + "checkConstraints": {}, 1178 + "isRLSEnabled": false 1179 + }, 1180 + "public.google_drive_paths": { 1181 + "name": "google_drive_paths", 1182 + "schema": "", 1183 + "columns": { 1184 + "xata_id": { 1185 + "name": "xata_id", 1186 + "type": "text", 1187 + "primaryKey": true, 1188 + "notNull": true, 1189 + "default": "xata_id()" 1190 + }, 1191 + "google_drive_id": { 1192 + "name": "google_drive_id", 1193 + "type": "text", 1194 + "primaryKey": false, 1195 + "notNull": true 1196 + }, 1197 + "track_id": { 1198 + "name": "track_id", 1199 + "type": "text", 1200 + "primaryKey": false, 1201 + "notNull": true 1202 + }, 1203 + "name": { 1204 + "name": "name", 1205 + "type": "text", 1206 + "primaryKey": false, 1207 + "notNull": true 1208 + }, 1209 + "directory_id": { 1210 + "name": "directory_id", 1211 + "type": "text", 1212 + "primaryKey": false, 1213 + "notNull": false 1214 + }, 1215 + "file_id": { 1216 + "name": "file_id", 1217 + "type": "text", 1218 + "primaryKey": false, 1219 + "notNull": true 1220 + }, 1221 + "xata_version": { 1222 + "name": "xata_version", 1223 + "type": "text", 1224 + "primaryKey": false, 1225 + "notNull": false 1226 + }, 1227 + "xata_createdat": { 1228 + "name": "xata_createdat", 1229 + "type": "timestamp", 1230 + "primaryKey": false, 1231 + "notNull": true, 1232 + "default": "now()" 1233 + }, 1234 + "xata_updatedat": { 1235 + "name": "xata_updatedat", 1236 + "type": "timestamp", 1237 + "primaryKey": false, 1238 + "notNull": true, 1239 + "default": "now()" 1240 + } 1241 + }, 1242 + "indexes": {}, 1243 + "foreignKeys": { 1244 + "google_drive_paths_directory_id_google_drive_directories_xata_id_fk": { 1245 + "name": "google_drive_paths_directory_id_google_drive_directories_xata_id_fk", 1246 + "tableFrom": "google_drive_paths", 1247 + "tableTo": "google_drive_directories", 1248 + "columnsFrom": [ 1249 + "directory_id" 1250 + ], 1251 + "columnsTo": [ 1252 + "xata_id" 1253 + ], 1254 + "onDelete": "no action", 1255 + "onUpdate": "no action" 1256 + } 1257 + }, 1258 + "compositePrimaryKeys": {}, 1259 + "uniqueConstraints": { 1260 + "google_drive_paths_file_id_unique": { 1261 + "name": "google_drive_paths_file_id_unique", 1262 + "nullsNotDistinct": false, 1263 + "columns": [ 1264 + "file_id" 1265 + ] 1266 + } 1267 + }, 1268 + "policies": {}, 1269 + "checkConstraints": {}, 1270 + "isRLSEnabled": false 1271 + }, 1272 + "public.google_drive_tokens": { 1273 + "name": "google_drive_tokens", 1274 + "schema": "", 1275 + "columns": { 1276 + "xata_id": { 1277 + "name": "xata_id", 1278 + "type": "text", 1279 + "primaryKey": true, 1280 + "notNull": true, 1281 + "default": "xata_id()" 1282 + }, 1283 + "refresh_token": { 1284 + "name": "refresh_token", 1285 + "type": "text", 1286 + "primaryKey": false, 1287 + "notNull": true 1288 + }, 1289 + "xata_createdat": { 1290 + "name": "xata_createdat", 1291 + "type": "timestamp", 1292 + "primaryKey": false, 1293 + "notNull": true, 1294 + "default": "now()" 1295 + }, 1296 + "xata_updatedat": { 1297 + "name": "xata_updatedat", 1298 + "type": "timestamp", 1299 + "primaryKey": false, 1300 + "notNull": true, 1301 + "default": "now()" 1302 + } 1303 + }, 1304 + "indexes": {}, 1305 + "foreignKeys": {}, 1306 + "compositePrimaryKeys": {}, 1307 + "uniqueConstraints": {}, 1308 + "policies": {}, 1309 + "checkConstraints": {}, 1310 + "isRLSEnabled": false 1311 + }, 1312 + "public.google_drive": { 1313 + "name": "google_drive", 1314 + "schema": "", 1315 + "columns": { 1316 + "xata_id": { 1317 + "name": "xata_id", 1318 + "type": "text", 1319 + "primaryKey": true, 1320 + "notNull": true, 1321 + "default": "xata_id()" 1322 + }, 1323 + "google_drive_token_id": { 1324 + "name": "google_drive_token_id", 1325 + "type": "text", 1326 + "primaryKey": false, 1327 + "notNull": true 1328 + }, 1329 + "user_id": { 1330 + "name": "user_id", 1331 + "type": "text", 1332 + "primaryKey": false, 1333 + "notNull": true 1334 + }, 1335 + "xata_version": { 1336 + "name": "xata_version", 1337 + "type": "text", 1338 + "primaryKey": false, 1339 + "notNull": false 1340 + }, 1341 + "xata_createdat": { 1342 + "name": "xata_createdat", 1343 + "type": "timestamp", 1344 + "primaryKey": false, 1345 + "notNull": true, 1346 + "default": "now()" 1347 + }, 1348 + "xata_updatedat": { 1349 + "name": "xata_updatedat", 1350 + "type": "timestamp", 1351 + "primaryKey": false, 1352 + "notNull": true, 1353 + "default": "now()" 1354 + } 1355 + }, 1356 + "indexes": {}, 1357 + "foreignKeys": { 1358 + "google_drive_google_drive_token_id_google_drive_tokens_xata_id_fk": { 1359 + "name": "google_drive_google_drive_token_id_google_drive_tokens_xata_id_fk", 1360 + "tableFrom": "google_drive", 1361 + "tableTo": "google_drive_tokens", 1362 + "columnsFrom": [ 1363 + "google_drive_token_id" 1364 + ], 1365 + "columnsTo": [ 1366 + "xata_id" 1367 + ], 1368 + "onDelete": "no action", 1369 + "onUpdate": "no action" 1370 + }, 1371 + "google_drive_user_id_users_xata_id_fk": { 1372 + "name": "google_drive_user_id_users_xata_id_fk", 1373 + "tableFrom": "google_drive", 1374 + "tableTo": "users", 1375 + "columnsFrom": [ 1376 + "user_id" 1377 + ], 1378 + "columnsTo": [ 1379 + "xata_id" 1380 + ], 1381 + "onDelete": "no action", 1382 + "onUpdate": "no action" 1383 + } 1384 + }, 1385 + "compositePrimaryKeys": {}, 1386 + "uniqueConstraints": {}, 1387 + "policies": {}, 1388 + "checkConstraints": {}, 1389 + "isRLSEnabled": false 1390 + }, 1391 + "public.loved_tracks": { 1392 + "name": "loved_tracks", 1393 + "schema": "", 1394 + "columns": { 1395 + "xata_id": { 1396 + "name": "xata_id", 1397 + "type": "text", 1398 + "primaryKey": true, 1399 + "notNull": true, 1400 + "default": "xata_id()" 1401 + }, 1402 + "user_id": { 1403 + "name": "user_id", 1404 + "type": "text", 1405 + "primaryKey": false, 1406 + "notNull": true 1407 + }, 1408 + "track_id": { 1409 + "name": "track_id", 1410 + "type": "text", 1411 + "primaryKey": false, 1412 + "notNull": true 1413 + }, 1414 + "uri": { 1415 + "name": "uri", 1416 + "type": "text", 1417 + "primaryKey": false, 1418 + "notNull": false 1419 + }, 1420 + "xata_createdat": { 1421 + "name": "xata_createdat", 1422 + "type": "timestamp", 1423 + "primaryKey": false, 1424 + "notNull": true, 1425 + "default": "now()" 1426 + } 1427 + }, 1428 + "indexes": {}, 1429 + "foreignKeys": { 1430 + "loved_tracks_user_id_users_xata_id_fk": { 1431 + "name": "loved_tracks_user_id_users_xata_id_fk", 1432 + "tableFrom": "loved_tracks", 1433 + "tableTo": "users", 1434 + "columnsFrom": [ 1435 + "user_id" 1436 + ], 1437 + "columnsTo": [ 1438 + "xata_id" 1439 + ], 1440 + "onDelete": "no action", 1441 + "onUpdate": "no action" 1442 + }, 1443 + "loved_tracks_track_id_tracks_xata_id_fk": { 1444 + "name": "loved_tracks_track_id_tracks_xata_id_fk", 1445 + "tableFrom": "loved_tracks", 1446 + "tableTo": "tracks", 1447 + "columnsFrom": [ 1448 + "track_id" 1449 + ], 1450 + "columnsTo": [ 1451 + "xata_id" 1452 + ], 1453 + "onDelete": "no action", 1454 + "onUpdate": "no action" 1455 + } 1456 + }, 1457 + "compositePrimaryKeys": {}, 1458 + "uniqueConstraints": { 1459 + "loved_tracks_uri_unique": { 1460 + "name": "loved_tracks_uri_unique", 1461 + "nullsNotDistinct": false, 1462 + "columns": [ 1463 + "uri" 1464 + ] 1465 + } 1466 + }, 1467 + "policies": {}, 1468 + "checkConstraints": {}, 1469 + "isRLSEnabled": false 1470 + }, 1471 + "public.playlist_tracks": { 1472 + "name": "playlist_tracks", 1473 + "schema": "", 1474 + "columns": { 1475 + "xata_id": { 1476 + "name": "xata_id", 1477 + "type": "text", 1478 + "primaryKey": true, 1479 + "notNull": true, 1480 + "default": "xata_id()" 1481 + }, 1482 + "playlist_id": { 1483 + "name": "playlist_id", 1484 + "type": "text", 1485 + "primaryKey": false, 1486 + "notNull": true 1487 + }, 1488 + "track_id": { 1489 + "name": "track_id", 1490 + "type": "text", 1491 + "primaryKey": false, 1492 + "notNull": true 1493 + }, 1494 + "xata_createdat": { 1495 + "name": "xata_createdat", 1496 + "type": "timestamp", 1497 + "primaryKey": false, 1498 + "notNull": true, 1499 + "default": "now()" 1500 + } 1501 + }, 1502 + "indexes": {}, 1503 + "foreignKeys": { 1504 + "playlist_tracks_playlist_id_playlists_xata_id_fk": { 1505 + "name": "playlist_tracks_playlist_id_playlists_xata_id_fk", 1506 + "tableFrom": "playlist_tracks", 1507 + "tableTo": "playlists", 1508 + "columnsFrom": [ 1509 + "playlist_id" 1510 + ], 1511 + "columnsTo": [ 1512 + "xata_id" 1513 + ], 1514 + "onDelete": "no action", 1515 + "onUpdate": "no action" 1516 + }, 1517 + "playlist_tracks_track_id_tracks_xata_id_fk": { 1518 + "name": "playlist_tracks_track_id_tracks_xata_id_fk", 1519 + "tableFrom": "playlist_tracks", 1520 + "tableTo": "tracks", 1521 + "columnsFrom": [ 1522 + "track_id" 1523 + ], 1524 + "columnsTo": [ 1525 + "xata_id" 1526 + ], 1527 + "onDelete": "no action", 1528 + "onUpdate": "no action" 1529 + } 1530 + }, 1531 + "compositePrimaryKeys": {}, 1532 + "uniqueConstraints": {}, 1533 + "policies": {}, 1534 + "checkConstraints": {}, 1535 + "isRLSEnabled": false 1536 + }, 1537 + "public.playlists": { 1538 + "name": "playlists", 1539 + "schema": "", 1540 + "columns": { 1541 + "xata_id": { 1542 + "name": "xata_id", 1543 + "type": "text", 1544 + "primaryKey": true, 1545 + "notNull": true, 1546 + "default": "xata_id()" 1547 + }, 1548 + "name": { 1549 + "name": "name", 1550 + "type": "text", 1551 + "primaryKey": false, 1552 + "notNull": true 1553 + }, 1554 + "picture": { 1555 + "name": "picture", 1556 + "type": "text", 1557 + "primaryKey": false, 1558 + "notNull": false 1559 + }, 1560 + "description": { 1561 + "name": "description", 1562 + "type": "text", 1563 + "primaryKey": false, 1564 + "notNull": false 1565 + }, 1566 + "uri": { 1567 + "name": "uri", 1568 + "type": "text", 1569 + "primaryKey": false, 1570 + "notNull": false 1571 + }, 1572 + "spotify_link": { 1573 + "name": "spotify_link", 1574 + "type": "text", 1575 + "primaryKey": false, 1576 + "notNull": false 1577 + }, 1578 + "tidal_link": { 1579 + "name": "tidal_link", 1580 + "type": "text", 1581 + "primaryKey": false, 1582 + "notNull": false 1583 + }, 1584 + "apple_music_link": { 1585 + "name": "apple_music_link", 1586 + "type": "text", 1587 + "primaryKey": false, 1588 + "notNull": false 1589 + }, 1590 + "created_by": { 1591 + "name": "created_by", 1592 + "type": "text", 1593 + "primaryKey": false, 1594 + "notNull": true 1595 + }, 1596 + "xata_createdat": { 1597 + "name": "xata_createdat", 1598 + "type": "timestamp", 1599 + "primaryKey": false, 1600 + "notNull": true, 1601 + "default": "now()" 1602 + }, 1603 + "xata_updatedat": { 1604 + "name": "xata_updatedat", 1605 + "type": "timestamp", 1606 + "primaryKey": false, 1607 + "notNull": true, 1608 + "default": "now()" 1609 + } 1610 + }, 1611 + "indexes": {}, 1612 + "foreignKeys": { 1613 + "playlists_created_by_users_xata_id_fk": { 1614 + "name": "playlists_created_by_users_xata_id_fk", 1615 + "tableFrom": "playlists", 1616 + "tableTo": "users", 1617 + "columnsFrom": [ 1618 + "created_by" 1619 + ], 1620 + "columnsTo": [ 1621 + "xata_id" 1622 + ], 1623 + "onDelete": "no action", 1624 + "onUpdate": "no action" 1625 + } 1626 + }, 1627 + "compositePrimaryKeys": {}, 1628 + "uniqueConstraints": { 1629 + "playlists_uri_unique": { 1630 + "name": "playlists_uri_unique", 1631 + "nullsNotDistinct": false, 1632 + "columns": [ 1633 + "uri" 1634 + ] 1635 + } 1636 + }, 1637 + "policies": {}, 1638 + "checkConstraints": {}, 1639 + "isRLSEnabled": false 1640 + }, 1641 + "public.profile_shouts": { 1642 + "name": "profile_shouts", 1643 + "schema": "", 1644 + "columns": { 1645 + "xata_id": { 1646 + "name": "xata_id", 1647 + "type": "text", 1648 + "primaryKey": true, 1649 + "notNull": true, 1650 + "default": "xata_id()" 1651 + }, 1652 + "user_id": { 1653 + "name": "user_id", 1654 + "type": "text", 1655 + "primaryKey": false, 1656 + "notNull": true 1657 + }, 1658 + "shout_id": { 1659 + "name": "shout_id", 1660 + "type": "text", 1661 + "primaryKey": false, 1662 + "notNull": true 1663 + }, 1664 + "xata_createdat": { 1665 + "name": "xata_createdat", 1666 + "type": "timestamp", 1667 + "primaryKey": false, 1668 + "notNull": true, 1669 + "default": "now()" 1670 + } 1671 + }, 1672 + "indexes": {}, 1673 + "foreignKeys": { 1674 + "profile_shouts_user_id_users_xata_id_fk": { 1675 + "name": "profile_shouts_user_id_users_xata_id_fk", 1676 + "tableFrom": "profile_shouts", 1677 + "tableTo": "users", 1678 + "columnsFrom": [ 1679 + "user_id" 1680 + ], 1681 + "columnsTo": [ 1682 + "xata_id" 1683 + ], 1684 + "onDelete": "no action", 1685 + "onUpdate": "no action" 1686 + }, 1687 + "profile_shouts_shout_id_shouts_xata_id_fk": { 1688 + "name": "profile_shouts_shout_id_shouts_xata_id_fk", 1689 + "tableFrom": "profile_shouts", 1690 + "tableTo": "shouts", 1691 + "columnsFrom": [ 1692 + "shout_id" 1693 + ], 1694 + "columnsTo": [ 1695 + "xata_id" 1696 + ], 1697 + "onDelete": "no action", 1698 + "onUpdate": "no action" 1699 + } 1700 + }, 1701 + "compositePrimaryKeys": {}, 1702 + "uniqueConstraints": {}, 1703 + "policies": {}, 1704 + "checkConstraints": {}, 1705 + "isRLSEnabled": false 1706 + }, 1707 + "public.queue_tracks": { 1708 + "name": "queue_tracks", 1709 + "schema": "", 1710 + "columns": { 1711 + "xata_id": { 1712 + "name": "xata_id", 1713 + "type": "text", 1714 + "primaryKey": true, 1715 + "notNull": true, 1716 + "default": "xata_id()" 1717 + }, 1718 + "user_id": { 1719 + "name": "user_id", 1720 + "type": "text", 1721 + "primaryKey": false, 1722 + "notNull": true 1723 + }, 1724 + "track_id": { 1725 + "name": "track_id", 1726 + "type": "text", 1727 + "primaryKey": false, 1728 + "notNull": true 1729 + }, 1730 + "position": { 1731 + "name": "position", 1732 + "type": "integer", 1733 + "primaryKey": false, 1734 + "notNull": true 1735 + }, 1736 + "file_uri": { 1737 + "name": "file_uri", 1738 + "type": "text", 1739 + "primaryKey": false, 1740 + "notNull": true 1741 + }, 1742 + "xata_version": { 1743 + "name": "xata_version", 1744 + "type": "integer", 1745 + "primaryKey": false, 1746 + "notNull": true, 1747 + "default": 0 1748 + }, 1749 + "xata_createdat": { 1750 + "name": "xata_createdat", 1751 + "type": "timestamp", 1752 + "primaryKey": false, 1753 + "notNull": true, 1754 + "default": "now()" 1755 + }, 1756 + "xata_updatedat": { 1757 + "name": "xata_updatedat", 1758 + "type": "timestamp", 1759 + "primaryKey": false, 1760 + "notNull": true, 1761 + "default": "now()" 1762 + } 1763 + }, 1764 + "indexes": {}, 1765 + "foreignKeys": { 1766 + "queue_tracks_user_id_users_xata_id_fk": { 1767 + "name": "queue_tracks_user_id_users_xata_id_fk", 1768 + "tableFrom": "queue_tracks", 1769 + "tableTo": "users", 1770 + "columnsFrom": [ 1771 + "user_id" 1772 + ], 1773 + "columnsTo": [ 1774 + "xata_id" 1775 + ], 1776 + "onDelete": "no action", 1777 + "onUpdate": "no action" 1778 + }, 1779 + "queue_tracks_track_id_tracks_xata_id_fk": { 1780 + "name": "queue_tracks_track_id_tracks_xata_id_fk", 1781 + "tableFrom": "queue_tracks", 1782 + "tableTo": "tracks", 1783 + "columnsFrom": [ 1784 + "track_id" 1785 + ], 1786 + "columnsTo": [ 1787 + "xata_id" 1788 + ], 1789 + "onDelete": "no action", 1790 + "onUpdate": "no action" 1791 + } 1792 + }, 1793 + "compositePrimaryKeys": {}, 1794 + "uniqueConstraints": {}, 1795 + "policies": {}, 1796 + "checkConstraints": {}, 1797 + "isRLSEnabled": false 1798 + }, 1799 + "public.scrobbles": { 1800 + "name": "scrobbles", 1801 + "schema": "", 1802 + "columns": { 1803 + "xata_id": { 1804 + "name": "xata_id", 1805 + "type": "text", 1806 + "primaryKey": true, 1807 + "notNull": true, 1808 + "default": "xata_id()" 1809 + }, 1810 + "user_id": { 1811 + "name": "user_id", 1812 + "type": "text", 1813 + "primaryKey": false, 1814 + "notNull": false 1815 + }, 1816 + "track_id": { 1817 + "name": "track_id", 1818 + "type": "text", 1819 + "primaryKey": false, 1820 + "notNull": false 1821 + }, 1822 + "album_id": { 1823 + "name": "album_id", 1824 + "type": "text", 1825 + "primaryKey": false, 1826 + "notNull": false 1827 + }, 1828 + "artist_id": { 1829 + "name": "artist_id", 1830 + "type": "text", 1831 + "primaryKey": false, 1832 + "notNull": false 1833 + }, 1834 + "uri": { 1835 + "name": "uri", 1836 + "type": "text", 1837 + "primaryKey": false, 1838 + "notNull": false 1839 + }, 1840 + "xata_createdat": { 1841 + "name": "xata_createdat", 1842 + "type": "timestamp", 1843 + "primaryKey": false, 1844 + "notNull": true, 1845 + "default": "now()" 1846 + }, 1847 + "xata_updatedat": { 1848 + "name": "xata_updatedat", 1849 + "type": "timestamp", 1850 + "primaryKey": false, 1851 + "notNull": true, 1852 + "default": "now()" 1853 + }, 1854 + "xata_version": { 1855 + "name": "xata_version", 1856 + "type": "integer", 1857 + "primaryKey": false, 1858 + "notNull": false 1859 + }, 1860 + "timestamp": { 1861 + "name": "timestamp", 1862 + "type": "timestamp", 1863 + "primaryKey": false, 1864 + "notNull": true, 1865 + "default": "now()" 1866 + } 1867 + }, 1868 + "indexes": {}, 1869 + "foreignKeys": { 1870 + "scrobbles_user_id_users_xata_id_fk": { 1871 + "name": "scrobbles_user_id_users_xata_id_fk", 1872 + "tableFrom": "scrobbles", 1873 + "tableTo": "users", 1874 + "columnsFrom": [ 1875 + "user_id" 1876 + ], 1877 + "columnsTo": [ 1878 + "xata_id" 1879 + ], 1880 + "onDelete": "no action", 1881 + "onUpdate": "no action" 1882 + }, 1883 + "scrobbles_track_id_tracks_xata_id_fk": { 1884 + "name": "scrobbles_track_id_tracks_xata_id_fk", 1885 + "tableFrom": "scrobbles", 1886 + "tableTo": "tracks", 1887 + "columnsFrom": [ 1888 + "track_id" 1889 + ], 1890 + "columnsTo": [ 1891 + "xata_id" 1892 + ], 1893 + "onDelete": "no action", 1894 + "onUpdate": "no action" 1895 + }, 1896 + "scrobbles_album_id_albums_xata_id_fk": { 1897 + "name": "scrobbles_album_id_albums_xata_id_fk", 1898 + "tableFrom": "scrobbles", 1899 + "tableTo": "albums", 1900 + "columnsFrom": [ 1901 + "album_id" 1902 + ], 1903 + "columnsTo": [ 1904 + "xata_id" 1905 + ], 1906 + "onDelete": "no action", 1907 + "onUpdate": "no action" 1908 + }, 1909 + "scrobbles_artist_id_artists_xata_id_fk": { 1910 + "name": "scrobbles_artist_id_artists_xata_id_fk", 1911 + "tableFrom": "scrobbles", 1912 + "tableTo": "artists", 1913 + "columnsFrom": [ 1914 + "artist_id" 1915 + ], 1916 + "columnsTo": [ 1917 + "xata_id" 1918 + ], 1919 + "onDelete": "no action", 1920 + "onUpdate": "no action" 1921 + } 1922 + }, 1923 + "compositePrimaryKeys": {}, 1924 + "uniqueConstraints": { 1925 + "scrobbles_uri_unique": { 1926 + "name": "scrobbles_uri_unique", 1927 + "nullsNotDistinct": false, 1928 + "columns": [ 1929 + "uri" 1930 + ] 1931 + } 1932 + }, 1933 + "policies": {}, 1934 + "checkConstraints": {}, 1935 + "isRLSEnabled": false 1936 + }, 1937 + "public.shout_likes": { 1938 + "name": "shout_likes", 1939 + "schema": "", 1940 + "columns": { 1941 + "xata_id": { 1942 + "name": "xata_id", 1943 + "type": "text", 1944 + "primaryKey": true, 1945 + "notNull": true, 1946 + "default": "xata_id()" 1947 + }, 1948 + "user_id": { 1949 + "name": "user_id", 1950 + "type": "text", 1951 + "primaryKey": false, 1952 + "notNull": true 1953 + }, 1954 + "shout_id": { 1955 + "name": "shout_id", 1956 + "type": "text", 1957 + "primaryKey": false, 1958 + "notNull": true 1959 + }, 1960 + "xata_createdat": { 1961 + "name": "xata_createdat", 1962 + "type": "timestamp", 1963 + "primaryKey": false, 1964 + "notNull": true, 1965 + "default": "now()" 1966 + }, 1967 + "uri": { 1968 + "name": "uri", 1969 + "type": "text", 1970 + "primaryKey": false, 1971 + "notNull": true 1972 + } 1973 + }, 1974 + "indexes": {}, 1975 + "foreignKeys": { 1976 + "shout_likes_user_id_users_xata_id_fk": { 1977 + "name": "shout_likes_user_id_users_xata_id_fk", 1978 + "tableFrom": "shout_likes", 1979 + "tableTo": "users", 1980 + "columnsFrom": [ 1981 + "user_id" 1982 + ], 1983 + "columnsTo": [ 1984 + "xata_id" 1985 + ], 1986 + "onDelete": "no action", 1987 + "onUpdate": "no action" 1988 + }, 1989 + "shout_likes_shout_id_shouts_xata_id_fk": { 1990 + "name": "shout_likes_shout_id_shouts_xata_id_fk", 1991 + "tableFrom": "shout_likes", 1992 + "tableTo": "shouts", 1993 + "columnsFrom": [ 1994 + "shout_id" 1995 + ], 1996 + "columnsTo": [ 1997 + "xata_id" 1998 + ], 1999 + "onDelete": "no action", 2000 + "onUpdate": "no action" 2001 + } 2002 + }, 2003 + "compositePrimaryKeys": {}, 2004 + "uniqueConstraints": { 2005 + "shout_likes_uri_unique": { 2006 + "name": "shout_likes_uri_unique", 2007 + "nullsNotDistinct": false, 2008 + "columns": [ 2009 + "uri" 2010 + ] 2011 + } 2012 + }, 2013 + "policies": {}, 2014 + "checkConstraints": {}, 2015 + "isRLSEnabled": false 2016 + }, 2017 + "public.shout_reports": { 2018 + "name": "shout_reports", 2019 + "schema": "", 2020 + "columns": { 2021 + "xata_id": { 2022 + "name": "xata_id", 2023 + "type": "text", 2024 + "primaryKey": true, 2025 + "notNull": true, 2026 + "default": "xata_id()" 2027 + }, 2028 + "user_id": { 2029 + "name": "user_id", 2030 + "type": "text", 2031 + "primaryKey": false, 2032 + "notNull": true 2033 + }, 2034 + "shout_id": { 2035 + "name": "shout_id", 2036 + "type": "text", 2037 + "primaryKey": false, 2038 + "notNull": true 2039 + }, 2040 + "xata_createdat": { 2041 + "name": "xata_createdat", 2042 + "type": "timestamp", 2043 + "primaryKey": false, 2044 + "notNull": true, 2045 + "default": "now()" 2046 + } 2047 + }, 2048 + "indexes": {}, 2049 + "foreignKeys": { 2050 + "shout_reports_user_id_users_xata_id_fk": { 2051 + "name": "shout_reports_user_id_users_xata_id_fk", 2052 + "tableFrom": "shout_reports", 2053 + "tableTo": "users", 2054 + "columnsFrom": [ 2055 + "user_id" 2056 + ], 2057 + "columnsTo": [ 2058 + "xata_id" 2059 + ], 2060 + "onDelete": "no action", 2061 + "onUpdate": "no action" 2062 + }, 2063 + "shout_reports_shout_id_shouts_xata_id_fk": { 2064 + "name": "shout_reports_shout_id_shouts_xata_id_fk", 2065 + "tableFrom": "shout_reports", 2066 + "tableTo": "shouts", 2067 + "columnsFrom": [ 2068 + "shout_id" 2069 + ], 2070 + "columnsTo": [ 2071 + "xata_id" 2072 + ], 2073 + "onDelete": "no action", 2074 + "onUpdate": "no action" 2075 + } 2076 + }, 2077 + "compositePrimaryKeys": {}, 2078 + "uniqueConstraints": {}, 2079 + "policies": {}, 2080 + "checkConstraints": {}, 2081 + "isRLSEnabled": false 2082 + }, 2083 + "public.shouts": { 2084 + "name": "shouts", 2085 + "schema": "", 2086 + "columns": { 2087 + "xata_id": { 2088 + "name": "xata_id", 2089 + "type": "text", 2090 + "primaryKey": true, 2091 + "notNull": true, 2092 + "default": "xata_id()" 2093 + }, 2094 + "content": { 2095 + "name": "content", 2096 + "type": "text", 2097 + "primaryKey": false, 2098 + "notNull": true 2099 + }, 2100 + "track_id": { 2101 + "name": "track_id", 2102 + "type": "text", 2103 + "primaryKey": false, 2104 + "notNull": false 2105 + }, 2106 + "artist_id": { 2107 + "name": "artist_id", 2108 + "type": "text", 2109 + "primaryKey": false, 2110 + "notNull": false 2111 + }, 2112 + "album_id": { 2113 + "name": "album_id", 2114 + "type": "text", 2115 + "primaryKey": false, 2116 + "notNull": false 2117 + }, 2118 + "scrobble_id": { 2119 + "name": "scrobble_id", 2120 + "type": "text", 2121 + "primaryKey": false, 2122 + "notNull": false 2123 + }, 2124 + "uri": { 2125 + "name": "uri", 2126 + "type": "text", 2127 + "primaryKey": false, 2128 + "notNull": true 2129 + }, 2130 + "author_id": { 2131 + "name": "author_id", 2132 + "type": "text", 2133 + "primaryKey": false, 2134 + "notNull": true 2135 + }, 2136 + "parent_id": { 2137 + "name": "parent_id", 2138 + "type": "text", 2139 + "primaryKey": false, 2140 + "notNull": false 2141 + }, 2142 + "xata_createdat": { 2143 + "name": "xata_createdat", 2144 + "type": "timestamp", 2145 + "primaryKey": false, 2146 + "notNull": true, 2147 + "default": "now()" 2148 + }, 2149 + "xata_updatedat": { 2150 + "name": "xata_updatedat", 2151 + "type": "timestamp", 2152 + "primaryKey": false, 2153 + "notNull": true, 2154 + "default": "now()" 2155 + } 2156 + }, 2157 + "indexes": {}, 2158 + "foreignKeys": { 2159 + "shouts_track_id_tracks_xata_id_fk": { 2160 + "name": "shouts_track_id_tracks_xata_id_fk", 2161 + "tableFrom": "shouts", 2162 + "tableTo": "tracks", 2163 + "columnsFrom": [ 2164 + "track_id" 2165 + ], 2166 + "columnsTo": [ 2167 + "xata_id" 2168 + ], 2169 + "onDelete": "no action", 2170 + "onUpdate": "no action" 2171 + }, 2172 + "shouts_artist_id_users_xata_id_fk": { 2173 + "name": "shouts_artist_id_users_xata_id_fk", 2174 + "tableFrom": "shouts", 2175 + "tableTo": "users", 2176 + "columnsFrom": [ 2177 + "artist_id" 2178 + ], 2179 + "columnsTo": [ 2180 + "xata_id" 2181 + ], 2182 + "onDelete": "no action", 2183 + "onUpdate": "no action" 2184 + }, 2185 + "shouts_album_id_albums_xata_id_fk": { 2186 + "name": "shouts_album_id_albums_xata_id_fk", 2187 + "tableFrom": "shouts", 2188 + "tableTo": "albums", 2189 + "columnsFrom": [ 2190 + "album_id" 2191 + ], 2192 + "columnsTo": [ 2193 + "xata_id" 2194 + ], 2195 + "onDelete": "no action", 2196 + "onUpdate": "no action" 2197 + }, 2198 + "shouts_scrobble_id_scrobbles_xata_id_fk": { 2199 + "name": "shouts_scrobble_id_scrobbles_xata_id_fk", 2200 + "tableFrom": "shouts", 2201 + "tableTo": "scrobbles", 2202 + "columnsFrom": [ 2203 + "scrobble_id" 2204 + ], 2205 + "columnsTo": [ 2206 + "xata_id" 2207 + ], 2208 + "onDelete": "no action", 2209 + "onUpdate": "no action" 2210 + }, 2211 + "shouts_author_id_users_xata_id_fk": { 2212 + "name": "shouts_author_id_users_xata_id_fk", 2213 + "tableFrom": "shouts", 2214 + "tableTo": "users", 2215 + "columnsFrom": [ 2216 + "author_id" 2217 + ], 2218 + "columnsTo": [ 2219 + "xata_id" 2220 + ], 2221 + "onDelete": "no action", 2222 + "onUpdate": "no action" 2223 + }, 2224 + "shouts_parent_id_shouts_xata_id_fk": { 2225 + "name": "shouts_parent_id_shouts_xata_id_fk", 2226 + "tableFrom": "shouts", 2227 + "tableTo": "shouts", 2228 + "columnsFrom": [ 2229 + "parent_id" 2230 + ], 2231 + "columnsTo": [ 2232 + "xata_id" 2233 + ], 2234 + "onDelete": "no action", 2235 + "onUpdate": "no action" 2236 + } 2237 + }, 2238 + "compositePrimaryKeys": {}, 2239 + "uniqueConstraints": { 2240 + "shouts_uri_unique": { 2241 + "name": "shouts_uri_unique", 2242 + "nullsNotDistinct": false, 2243 + "columns": [ 2244 + "uri" 2245 + ] 2246 + } 2247 + }, 2248 + "policies": {}, 2249 + "checkConstraints": {}, 2250 + "isRLSEnabled": false 2251 + }, 2252 + "public.spotify_accounts": { 2253 + "name": "spotify_accounts", 2254 + "schema": "", 2255 + "columns": { 2256 + "xata_id": { 2257 + "name": "xata_id", 2258 + "type": "text", 2259 + "primaryKey": true, 2260 + "notNull": true, 2261 + "default": "xata_id()" 2262 + }, 2263 + "xata_version": { 2264 + "name": "xata_version", 2265 + "type": "integer", 2266 + "primaryKey": false, 2267 + "notNull": false 2268 + }, 2269 + "email": { 2270 + "name": "email", 2271 + "type": "text", 2272 + "primaryKey": false, 2273 + "notNull": true 2274 + }, 2275 + "user_id": { 2276 + "name": "user_id", 2277 + "type": "text", 2278 + "primaryKey": false, 2279 + "notNull": true 2280 + }, 2281 + "is_beta_user": { 2282 + "name": "is_beta_user", 2283 + "type": "boolean", 2284 + "primaryKey": false, 2285 + "notNull": true, 2286 + "default": false 2287 + }, 2288 + "spotify_app_id": { 2289 + "name": "spotify_app_id", 2290 + "type": "text", 2291 + "primaryKey": false, 2292 + "notNull": true 2293 + }, 2294 + "xata_createdat": { 2295 + "name": "xata_createdat", 2296 + "type": "timestamp", 2297 + "primaryKey": false, 2298 + "notNull": true, 2299 + "default": "now()" 2300 + }, 2301 + "xata_updatedat": { 2302 + "name": "xata_updatedat", 2303 + "type": "timestamp", 2304 + "primaryKey": false, 2305 + "notNull": true, 2306 + "default": "now()" 2307 + } 2308 + }, 2309 + "indexes": {}, 2310 + "foreignKeys": { 2311 + "spotify_accounts_user_id_users_xata_id_fk": { 2312 + "name": "spotify_accounts_user_id_users_xata_id_fk", 2313 + "tableFrom": "spotify_accounts", 2314 + "tableTo": "users", 2315 + "columnsFrom": [ 2316 + "user_id" 2317 + ], 2318 + "columnsTo": [ 2319 + "xata_id" 2320 + ], 2321 + "onDelete": "no action", 2322 + "onUpdate": "no action" 2323 + } 2324 + }, 2325 + "compositePrimaryKeys": {}, 2326 + "uniqueConstraints": {}, 2327 + "policies": {}, 2328 + "checkConstraints": {}, 2329 + "isRLSEnabled": false 2330 + }, 2331 + "public.spotify_apps": { 2332 + "name": "spotify_apps", 2333 + "schema": "", 2334 + "columns": { 2335 + "xata_id": { 2336 + "name": "xata_id", 2337 + "type": "text", 2338 + "primaryKey": true, 2339 + "notNull": true, 2340 + "default": "xata_id()" 2341 + }, 2342 + "xata_version": { 2343 + "name": "xata_version", 2344 + "type": "integer", 2345 + "primaryKey": false, 2346 + "notNull": false 2347 + }, 2348 + "spotify_app_id": { 2349 + "name": "spotify_app_id", 2350 + "type": "text", 2351 + "primaryKey": false, 2352 + "notNull": true 2353 + }, 2354 + "xata_createdat": { 2355 + "name": "xata_createdat", 2356 + "type": "timestamp", 2357 + "primaryKey": false, 2358 + "notNull": true, 2359 + "default": "now()" 2360 + }, 2361 + "xata_updatedat": { 2362 + "name": "xata_updatedat", 2363 + "type": "timestamp", 2364 + "primaryKey": false, 2365 + "notNull": true, 2366 + "default": "now()" 2367 + } 2368 + }, 2369 + "indexes": {}, 2370 + "foreignKeys": {}, 2371 + "compositePrimaryKeys": {}, 2372 + "uniqueConstraints": {}, 2373 + "policies": {}, 2374 + "checkConstraints": {}, 2375 + "isRLSEnabled": false 2376 + }, 2377 + "public.spotify_tokens": { 2378 + "name": "spotify_tokens", 2379 + "schema": "", 2380 + "columns": { 2381 + "xata_id": { 2382 + "name": "xata_id", 2383 + "type": "text", 2384 + "primaryKey": true, 2385 + "notNull": true, 2386 + "default": "xata_id()" 2387 + }, 2388 + "xata_version": { 2389 + "name": "xata_version", 2390 + "type": "integer", 2391 + "primaryKey": false, 2392 + "notNull": false 2393 + }, 2394 + "access_token": { 2395 + "name": "access_token", 2396 + "type": "text", 2397 + "primaryKey": false, 2398 + "notNull": true 2399 + }, 2400 + "refresh_token": { 2401 + "name": "refresh_token", 2402 + "type": "text", 2403 + "primaryKey": false, 2404 + "notNull": true 2405 + }, 2406 + "user_id": { 2407 + "name": "user_id", 2408 + "type": "text", 2409 + "primaryKey": false, 2410 + "notNull": true 2411 + }, 2412 + "spotify_app_id": { 2413 + "name": "spotify_app_id", 2414 + "type": "text", 2415 + "primaryKey": false, 2416 + "notNull": true 2417 + }, 2418 + "xata_createdat": { 2419 + "name": "xata_createdat", 2420 + "type": "timestamp", 2421 + "primaryKey": false, 2422 + "notNull": true, 2423 + "default": "now()" 2424 + }, 2425 + "xata_updatedat": { 2426 + "name": "xata_updatedat", 2427 + "type": "timestamp", 2428 + "primaryKey": false, 2429 + "notNull": true, 2430 + "default": "now()" 2431 + } 2432 + }, 2433 + "indexes": {}, 2434 + "foreignKeys": { 2435 + "spotify_tokens_user_id_users_xata_id_fk": { 2436 + "name": "spotify_tokens_user_id_users_xata_id_fk", 2437 + "tableFrom": "spotify_tokens", 2438 + "tableTo": "users", 2439 + "columnsFrom": [ 2440 + "user_id" 2441 + ], 2442 + "columnsTo": [ 2443 + "xata_id" 2444 + ], 2445 + "onDelete": "no action", 2446 + "onUpdate": "no action" 2447 + } 2448 + }, 2449 + "compositePrimaryKeys": {}, 2450 + "uniqueConstraints": {}, 2451 + "policies": {}, 2452 + "checkConstraints": {}, 2453 + "isRLSEnabled": false 2454 + }, 2455 + "public.tracks": { 2456 + "name": "tracks", 2457 + "schema": "", 2458 + "columns": { 2459 + "xata_id": { 2460 + "name": "xata_id", 2461 + "type": "text", 2462 + "primaryKey": true, 2463 + "notNull": true, 2464 + "default": "xata_id()" 2465 + }, 2466 + "title": { 2467 + "name": "title", 2468 + "type": "text", 2469 + "primaryKey": false, 2470 + "notNull": true 2471 + }, 2472 + "artist": { 2473 + "name": "artist", 2474 + "type": "text", 2475 + "primaryKey": false, 2476 + "notNull": true 2477 + }, 2478 + "album_artist": { 2479 + "name": "album_artist", 2480 + "type": "text", 2481 + "primaryKey": false, 2482 + "notNull": true 2483 + }, 2484 + "album_art": { 2485 + "name": "album_art", 2486 + "type": "text", 2487 + "primaryKey": false, 2488 + "notNull": false 2489 + }, 2490 + "album": { 2491 + "name": "album", 2492 + "type": "text", 2493 + "primaryKey": false, 2494 + "notNull": true 2495 + }, 2496 + "track_number": { 2497 + "name": "track_number", 2498 + "type": "integer", 2499 + "primaryKey": false, 2500 + "notNull": false 2501 + }, 2502 + "duration": { 2503 + "name": "duration", 2504 + "type": "integer", 2505 + "primaryKey": false, 2506 + "notNull": true 2507 + }, 2508 + "mb_id": { 2509 + "name": "mb_id", 2510 + "type": "text", 2511 + "primaryKey": false, 2512 + "notNull": false 2513 + }, 2514 + "youtube_link": { 2515 + "name": "youtube_link", 2516 + "type": "text", 2517 + "primaryKey": false, 2518 + "notNull": false 2519 + }, 2520 + "spotify_link": { 2521 + "name": "spotify_link", 2522 + "type": "text", 2523 + "primaryKey": false, 2524 + "notNull": false 2525 + }, 2526 + "apple_music_link": { 2527 + "name": "apple_music_link", 2528 + "type": "text", 2529 + "primaryKey": false, 2530 + "notNull": false 2531 + }, 2532 + "tidal_link": { 2533 + "name": "tidal_link", 2534 + "type": "text", 2535 + "primaryKey": false, 2536 + "notNull": false 2537 + }, 2538 + "sha256": { 2539 + "name": "sha256", 2540 + "type": "text", 2541 + "primaryKey": false, 2542 + "notNull": true 2543 + }, 2544 + "disc_number": { 2545 + "name": "disc_number", 2546 + "type": "integer", 2547 + "primaryKey": false, 2548 + "notNull": false 2549 + }, 2550 + "lyrics": { 2551 + "name": "lyrics", 2552 + "type": "text", 2553 + "primaryKey": false, 2554 + "notNull": false 2555 + }, 2556 + "composer": { 2557 + "name": "composer", 2558 + "type": "text", 2559 + "primaryKey": false, 2560 + "notNull": false 2561 + }, 2562 + "genre": { 2563 + "name": "genre", 2564 + "type": "text", 2565 + "primaryKey": false, 2566 + "notNull": false 2567 + }, 2568 + "label": { 2569 + "name": "label", 2570 + "type": "text", 2571 + "primaryKey": false, 2572 + "notNull": false 2573 + }, 2574 + "copyright_message": { 2575 + "name": "copyright_message", 2576 + "type": "text", 2577 + "primaryKey": false, 2578 + "notNull": false 2579 + }, 2580 + "uri": { 2581 + "name": "uri", 2582 + "type": "text", 2583 + "primaryKey": false, 2584 + "notNull": false 2585 + }, 2586 + "album_uri": { 2587 + "name": "album_uri", 2588 + "type": "text", 2589 + "primaryKey": false, 2590 + "notNull": false 2591 + }, 2592 + "artist_uri": { 2593 + "name": "artist_uri", 2594 + "type": "text", 2595 + "primaryKey": false, 2596 + "notNull": false 2597 + }, 2598 + "xata_createdat": { 2599 + "name": "xata_createdat", 2600 + "type": "timestamp", 2601 + "primaryKey": false, 2602 + "notNull": true, 2603 + "default": "now()" 2604 + }, 2605 + "xata_updatedat": { 2606 + "name": "xata_updatedat", 2607 + "type": "timestamp", 2608 + "primaryKey": false, 2609 + "notNull": true, 2610 + "default": "now()" 2611 + }, 2612 + "xata_version": { 2613 + "name": "xata_version", 2614 + "type": "integer", 2615 + "primaryKey": false, 2616 + "notNull": false 2617 + } 2618 + }, 2619 + "indexes": {}, 2620 + "foreignKeys": {}, 2621 + "compositePrimaryKeys": {}, 2622 + "uniqueConstraints": { 2623 + "tracks_mb_id_unique": { 2624 + "name": "tracks_mb_id_unique", 2625 + "nullsNotDistinct": false, 2626 + "columns": [ 2627 + "mb_id" 2628 + ] 2629 + }, 2630 + "tracks_youtube_link_unique": { 2631 + "name": "tracks_youtube_link_unique", 2632 + "nullsNotDistinct": false, 2633 + "columns": [ 2634 + "youtube_link" 2635 + ] 2636 + }, 2637 + "tracks_spotify_link_unique": { 2638 + "name": "tracks_spotify_link_unique", 2639 + "nullsNotDistinct": false, 2640 + "columns": [ 2641 + "spotify_link" 2642 + ] 2643 + }, 2644 + "tracks_apple_music_link_unique": { 2645 + "name": "tracks_apple_music_link_unique", 2646 + "nullsNotDistinct": false, 2647 + "columns": [ 2648 + "apple_music_link" 2649 + ] 2650 + }, 2651 + "tracks_tidal_link_unique": { 2652 + "name": "tracks_tidal_link_unique", 2653 + "nullsNotDistinct": false, 2654 + "columns": [ 2655 + "tidal_link" 2656 + ] 2657 + }, 2658 + "tracks_sha256_unique": { 2659 + "name": "tracks_sha256_unique", 2660 + "nullsNotDistinct": false, 2661 + "columns": [ 2662 + "sha256" 2663 + ] 2664 + }, 2665 + "tracks_uri_unique": { 2666 + "name": "tracks_uri_unique", 2667 + "nullsNotDistinct": false, 2668 + "columns": [ 2669 + "uri" 2670 + ] 2671 + } 2672 + }, 2673 + "policies": {}, 2674 + "checkConstraints": {}, 2675 + "isRLSEnabled": false 2676 + }, 2677 + "public.user_albums": { 2678 + "name": "user_albums", 2679 + "schema": "", 2680 + "columns": { 2681 + "xata_id": { 2682 + "name": "xata_id", 2683 + "type": "text", 2684 + "primaryKey": true, 2685 + "notNull": true, 2686 + "default": "xata_id()" 2687 + }, 2688 + "user_id": { 2689 + "name": "user_id", 2690 + "type": "text", 2691 + "primaryKey": false, 2692 + "notNull": true 2693 + }, 2694 + "album_id": { 2695 + "name": "album_id", 2696 + "type": "text", 2697 + "primaryKey": false, 2698 + "notNull": true 2699 + }, 2700 + "xata_createdat": { 2701 + "name": "xata_createdat", 2702 + "type": "timestamp", 2703 + "primaryKey": false, 2704 + "notNull": true, 2705 + "default": "now()" 2706 + }, 2707 + "xata_updatedat": { 2708 + "name": "xata_updatedat", 2709 + "type": "timestamp", 2710 + "primaryKey": false, 2711 + "notNull": true, 2712 + "default": "now()" 2713 + }, 2714 + "xata_version": { 2715 + "name": "xata_version", 2716 + "type": "integer", 2717 + "primaryKey": false, 2718 + "notNull": false 2719 + }, 2720 + "scrobbles": { 2721 + "name": "scrobbles", 2722 + "type": "integer", 2723 + "primaryKey": false, 2724 + "notNull": false 2725 + }, 2726 + "uri": { 2727 + "name": "uri", 2728 + "type": "text", 2729 + "primaryKey": false, 2730 + "notNull": true 2731 + } 2732 + }, 2733 + "indexes": {}, 2734 + "foreignKeys": { 2735 + "user_albums_user_id_users_xata_id_fk": { 2736 + "name": "user_albums_user_id_users_xata_id_fk", 2737 + "tableFrom": "user_albums", 2738 + "tableTo": "users", 2739 + "columnsFrom": [ 2740 + "user_id" 2741 + ], 2742 + "columnsTo": [ 2743 + "xata_id" 2744 + ], 2745 + "onDelete": "no action", 2746 + "onUpdate": "no action" 2747 + }, 2748 + "user_albums_album_id_albums_xata_id_fk": { 2749 + "name": "user_albums_album_id_albums_xata_id_fk", 2750 + "tableFrom": "user_albums", 2751 + "tableTo": "albums", 2752 + "columnsFrom": [ 2753 + "album_id" 2754 + ], 2755 + "columnsTo": [ 2756 + "xata_id" 2757 + ], 2758 + "onDelete": "no action", 2759 + "onUpdate": "no action" 2760 + } 2761 + }, 2762 + "compositePrimaryKeys": {}, 2763 + "uniqueConstraints": { 2764 + "user_albums_uri_unique": { 2765 + "name": "user_albums_uri_unique", 2766 + "nullsNotDistinct": false, 2767 + "columns": [ 2768 + "uri" 2769 + ] 2770 + } 2771 + }, 2772 + "policies": {}, 2773 + "checkConstraints": {}, 2774 + "isRLSEnabled": false 2775 + }, 2776 + "public.user_artists": { 2777 + "name": "user_artists", 2778 + "schema": "", 2779 + "columns": { 2780 + "xata_id": { 2781 + "name": "xata_id", 2782 + "type": "text", 2783 + "primaryKey": true, 2784 + "notNull": true, 2785 + "default": "xata_id()" 2786 + }, 2787 + "user_id": { 2788 + "name": "user_id", 2789 + "type": "text", 2790 + "primaryKey": false, 2791 + "notNull": true 2792 + }, 2793 + "artist_id": { 2794 + "name": "artist_id", 2795 + "type": "text", 2796 + "primaryKey": false, 2797 + "notNull": true 2798 + }, 2799 + "xata_createdat": { 2800 + "name": "xata_createdat", 2801 + "type": "timestamp", 2802 + "primaryKey": false, 2803 + "notNull": true, 2804 + "default": "now()" 2805 + }, 2806 + "xata_updatedat": { 2807 + "name": "xata_updatedat", 2808 + "type": "timestamp", 2809 + "primaryKey": false, 2810 + "notNull": true, 2811 + "default": "now()" 2812 + }, 2813 + "xata_version": { 2814 + "name": "xata_version", 2815 + "type": "integer", 2816 + "primaryKey": false, 2817 + "notNull": false 2818 + }, 2819 + "scrobbles": { 2820 + "name": "scrobbles", 2821 + "type": "integer", 2822 + "primaryKey": false, 2823 + "notNull": false 2824 + }, 2825 + "uri": { 2826 + "name": "uri", 2827 + "type": "text", 2828 + "primaryKey": false, 2829 + "notNull": true 2830 + } 2831 + }, 2832 + "indexes": {}, 2833 + "foreignKeys": { 2834 + "user_artists_user_id_users_xata_id_fk": { 2835 + "name": "user_artists_user_id_users_xata_id_fk", 2836 + "tableFrom": "user_artists", 2837 + "tableTo": "users", 2838 + "columnsFrom": [ 2839 + "user_id" 2840 + ], 2841 + "columnsTo": [ 2842 + "xata_id" 2843 + ], 2844 + "onDelete": "no action", 2845 + "onUpdate": "no action" 2846 + }, 2847 + "user_artists_artist_id_artists_xata_id_fk": { 2848 + "name": "user_artists_artist_id_artists_xata_id_fk", 2849 + "tableFrom": "user_artists", 2850 + "tableTo": "artists", 2851 + "columnsFrom": [ 2852 + "artist_id" 2853 + ], 2854 + "columnsTo": [ 2855 + "xata_id" 2856 + ], 2857 + "onDelete": "no action", 2858 + "onUpdate": "no action" 2859 + } 2860 + }, 2861 + "compositePrimaryKeys": {}, 2862 + "uniqueConstraints": { 2863 + "user_artists_uri_unique": { 2864 + "name": "user_artists_uri_unique", 2865 + "nullsNotDistinct": false, 2866 + "columns": [ 2867 + "uri" 2868 + ] 2869 + } 2870 + }, 2871 + "policies": {}, 2872 + "checkConstraints": {}, 2873 + "isRLSEnabled": false 2874 + }, 2875 + "public.user_playlists": { 2876 + "name": "user_playlists", 2877 + "schema": "", 2878 + "columns": { 2879 + "xata_id": { 2880 + "name": "xata_id", 2881 + "type": "text", 2882 + "primaryKey": true, 2883 + "notNull": true, 2884 + "default": "xata_id()" 2885 + }, 2886 + "user_id": { 2887 + "name": "user_id", 2888 + "type": "text", 2889 + "primaryKey": false, 2890 + "notNull": true 2891 + }, 2892 + "playlist_id": { 2893 + "name": "playlist_id", 2894 + "type": "text", 2895 + "primaryKey": false, 2896 + "notNull": true 2897 + }, 2898 + "xata_createdat": { 2899 + "name": "xata_createdat", 2900 + "type": "timestamp", 2901 + "primaryKey": false, 2902 + "notNull": true, 2903 + "default": "now()" 2904 + }, 2905 + "uri": { 2906 + "name": "uri", 2907 + "type": "text", 2908 + "primaryKey": false, 2909 + "notNull": false 2910 + } 2911 + }, 2912 + "indexes": {}, 2913 + "foreignKeys": { 2914 + "user_playlists_user_id_users_xata_id_fk": { 2915 + "name": "user_playlists_user_id_users_xata_id_fk", 2916 + "tableFrom": "user_playlists", 2917 + "tableTo": "users", 2918 + "columnsFrom": [ 2919 + "user_id" 2920 + ], 2921 + "columnsTo": [ 2922 + "xata_id" 2923 + ], 2924 + "onDelete": "no action", 2925 + "onUpdate": "no action" 2926 + }, 2927 + "user_playlists_playlist_id_playlists_xata_id_fk": { 2928 + "name": "user_playlists_playlist_id_playlists_xata_id_fk", 2929 + "tableFrom": "user_playlists", 2930 + "tableTo": "playlists", 2931 + "columnsFrom": [ 2932 + "playlist_id" 2933 + ], 2934 + "columnsTo": [ 2935 + "xata_id" 2936 + ], 2937 + "onDelete": "no action", 2938 + "onUpdate": "no action" 2939 + } 2940 + }, 2941 + "compositePrimaryKeys": {}, 2942 + "uniqueConstraints": { 2943 + "user_playlists_uri_unique": { 2944 + "name": "user_playlists_uri_unique", 2945 + "nullsNotDistinct": false, 2946 + "columns": [ 2947 + "uri" 2948 + ] 2949 + } 2950 + }, 2951 + "policies": {}, 2952 + "checkConstraints": {}, 2953 + "isRLSEnabled": false 2954 + }, 2955 + "public.user_tracks": { 2956 + "name": "user_tracks", 2957 + "schema": "", 2958 + "columns": { 2959 + "xata_id": { 2960 + "name": "xata_id", 2961 + "type": "text", 2962 + "primaryKey": true, 2963 + "notNull": true, 2964 + "default": "xata_id()" 2965 + }, 2966 + "user_id": { 2967 + "name": "user_id", 2968 + "type": "text", 2969 + "primaryKey": false, 2970 + "notNull": true 2971 + }, 2972 + "track_id": { 2973 + "name": "track_id", 2974 + "type": "text", 2975 + "primaryKey": false, 2976 + "notNull": true 2977 + }, 2978 + "xata_createdat": { 2979 + "name": "xata_createdat", 2980 + "type": "timestamp", 2981 + "primaryKey": false, 2982 + "notNull": true, 2983 + "default": "now()" 2984 + }, 2985 + "xata_updatedat": { 2986 + "name": "xata_updatedat", 2987 + "type": "timestamp", 2988 + "primaryKey": false, 2989 + "notNull": true, 2990 + "default": "now()" 2991 + }, 2992 + "xata_version": { 2993 + "name": "xata_version", 2994 + "type": "integer", 2995 + "primaryKey": false, 2996 + "notNull": false 2997 + }, 2998 + "uri": { 2999 + "name": "uri", 3000 + "type": "text", 3001 + "primaryKey": false, 3002 + "notNull": true 3003 + }, 3004 + "scrobbles": { 3005 + "name": "scrobbles", 3006 + "type": "integer", 3007 + "primaryKey": false, 3008 + "notNull": false 3009 + } 3010 + }, 3011 + "indexes": {}, 3012 + "foreignKeys": { 3013 + "user_tracks_user_id_users_xata_id_fk": { 3014 + "name": "user_tracks_user_id_users_xata_id_fk", 3015 + "tableFrom": "user_tracks", 3016 + "tableTo": "users", 3017 + "columnsFrom": [ 3018 + "user_id" 3019 + ], 3020 + "columnsTo": [ 3021 + "xata_id" 3022 + ], 3023 + "onDelete": "no action", 3024 + "onUpdate": "no action" 3025 + }, 3026 + "user_tracks_track_id_tracks_xata_id_fk": { 3027 + "name": "user_tracks_track_id_tracks_xata_id_fk", 3028 + "tableFrom": "user_tracks", 3029 + "tableTo": "tracks", 3030 + "columnsFrom": [ 3031 + "track_id" 3032 + ], 3033 + "columnsTo": [ 3034 + "xata_id" 3035 + ], 3036 + "onDelete": "no action", 3037 + "onUpdate": "no action" 3038 + } 3039 + }, 3040 + "compositePrimaryKeys": {}, 3041 + "uniqueConstraints": { 3042 + "user_tracks_uri_unique": { 3043 + "name": "user_tracks_uri_unique", 3044 + "nullsNotDistinct": false, 3045 + "columns": [ 3046 + "uri" 3047 + ] 3048 + } 3049 + }, 3050 + "policies": {}, 3051 + "checkConstraints": {}, 3052 + "isRLSEnabled": false 3053 + }, 3054 + "public.users": { 3055 + "name": "users", 3056 + "schema": "", 3057 + "columns": { 3058 + "xata_id": { 3059 + "name": "xata_id", 3060 + "type": "text", 3061 + "primaryKey": true, 3062 + "notNull": true, 3063 + "default": "xata_id()" 3064 + }, 3065 + "did": { 3066 + "name": "did", 3067 + "type": "text", 3068 + "primaryKey": false, 3069 + "notNull": true 3070 + }, 3071 + "display_name": { 3072 + "name": "display_name", 3073 + "type": "text", 3074 + "primaryKey": false, 3075 + "notNull": false 3076 + }, 3077 + "handle": { 3078 + "name": "handle", 3079 + "type": "text", 3080 + "primaryKey": false, 3081 + "notNull": true 3082 + }, 3083 + "avatar": { 3084 + "name": "avatar", 3085 + "type": "text", 3086 + "primaryKey": false, 3087 + "notNull": true 3088 + }, 3089 + "xata_createdat": { 3090 + "name": "xata_createdat", 3091 + "type": "timestamp", 3092 + "primaryKey": false, 3093 + "notNull": true, 3094 + "default": "now()" 3095 + }, 3096 + "xata_updatedat": { 3097 + "name": "xata_updatedat", 3098 + "type": "timestamp", 3099 + "primaryKey": false, 3100 + "notNull": true, 3101 + "default": "now()" 3102 + }, 3103 + "xata_version": { 3104 + "name": "xata_version", 3105 + "type": "integer", 3106 + "primaryKey": false, 3107 + "notNull": false 3108 + } 3109 + }, 3110 + "indexes": {}, 3111 + "foreignKeys": {}, 3112 + "compositePrimaryKeys": {}, 3113 + "uniqueConstraints": { 3114 + "users_did_unique": { 3115 + "name": "users_did_unique", 3116 + "nullsNotDistinct": false, 3117 + "columns": [ 3118 + "did" 3119 + ] 3120 + }, 3121 + "users_handle_unique": { 3122 + "name": "users_handle_unique", 3123 + "nullsNotDistinct": false, 3124 + "columns": [ 3125 + "handle" 3126 + ] 3127 + } 3128 + }, 3129 + "policies": {}, 3130 + "checkConstraints": {}, 3131 + "isRLSEnabled": false 3132 + }, 3133 + "public.webscrobblers": { 3134 + "name": "webscrobblers", 3135 + "schema": "", 3136 + "columns": { 3137 + "xata_id": { 3138 + "name": "xata_id", 3139 + "type": "text", 3140 + "primaryKey": true, 3141 + "notNull": true, 3142 + "default": "xata_id()" 3143 + }, 3144 + "name": { 3145 + "name": "name", 3146 + "type": "text", 3147 + "primaryKey": false, 3148 + "notNull": true 3149 + }, 3150 + "uuid": { 3151 + "name": "uuid", 3152 + "type": "text", 3153 + "primaryKey": false, 3154 + "notNull": true 3155 + }, 3156 + "description": { 3157 + "name": "description", 3158 + "type": "text", 3159 + "primaryKey": false, 3160 + "notNull": false 3161 + }, 3162 + "enabled": { 3163 + "name": "enabled", 3164 + "type": "boolean", 3165 + "primaryKey": false, 3166 + "notNull": true, 3167 + "default": true 3168 + }, 3169 + "user_id": { 3170 + "name": "user_id", 3171 + "type": "text", 3172 + "primaryKey": false, 3173 + "notNull": true 3174 + }, 3175 + "xata_createdat": { 3176 + "name": "xata_createdat", 3177 + "type": "timestamp", 3178 + "primaryKey": false, 3179 + "notNull": true, 3180 + "default": "now()" 3181 + }, 3182 + "xata_updatedat": { 3183 + "name": "xata_updatedat", 3184 + "type": "timestamp", 3185 + "primaryKey": false, 3186 + "notNull": true, 3187 + "default": "now()" 3188 + } 3189 + }, 3190 + "indexes": {}, 3191 + "foreignKeys": { 3192 + "webscrobblers_user_id_users_xata_id_fk": { 3193 + "name": "webscrobblers_user_id_users_xata_id_fk", 3194 + "tableFrom": "webscrobblers", 3195 + "tableTo": "users", 3196 + "columnsFrom": [ 3197 + "user_id" 3198 + ], 3199 + "columnsTo": [ 3200 + "xata_id" 3201 + ], 3202 + "onDelete": "no action", 3203 + "onUpdate": "no action" 3204 + } 3205 + }, 3206 + "compositePrimaryKeys": {}, 3207 + "uniqueConstraints": {}, 3208 + "policies": {}, 3209 + "checkConstraints": {}, 3210 + "isRLSEnabled": false 3211 + } 3212 + }, 3213 + "enums": {}, 3214 + "schemas": {}, 3215 + "sequences": {}, 3216 + "roles": {}, 3217 + "policies": {}, 3218 + "views": {}, 3219 + "_meta": { 3220 + "columns": {}, 3221 + "schemas": {}, 3222 + "tables": {} 3223 + } 3224 + }
+3230
apps/api/drizzle/meta/0005_snapshot.json
··· 1 + { 2 + "id": "b4f141d3-3e14-4aaf-b3f1-b3165d009289", 3 + "prevId": "3294c136-37d7-4c61-8633-26d17c05a059", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.album_tracks": { 8 + "name": "album_tracks", 9 + "schema": "", 10 + "columns": { 11 + "xata_id": { 12 + "name": "xata_id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "album_id": { 19 + "name": "album_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": true 23 + }, 24 + "track_id": { 25 + "name": "track_id", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + }, 30 + "xata_createdat": { 31 + "name": "xata_createdat", 32 + "type": "timestamp", 33 + "primaryKey": false, 34 + "notNull": true, 35 + "default": "now()" 36 + }, 37 + "xata_updatedat": { 38 + "name": "xata_updatedat", 39 + "type": "timestamp", 40 + "primaryKey": false, 41 + "notNull": true, 42 + "default": "now()" 43 + }, 44 + "xata_version": { 45 + "name": "xata_version", 46 + "type": "integer", 47 + "primaryKey": false, 48 + "notNull": false 49 + } 50 + }, 51 + "indexes": {}, 52 + "foreignKeys": { 53 + "album_tracks_album_id_albums_xata_id_fk": { 54 + "name": "album_tracks_album_id_albums_xata_id_fk", 55 + "tableFrom": "album_tracks", 56 + "tableTo": "albums", 57 + "columnsFrom": [ 58 + "album_id" 59 + ], 60 + "columnsTo": [ 61 + "xata_id" 62 + ], 63 + "onDelete": "no action", 64 + "onUpdate": "no action" 65 + }, 66 + "album_tracks_track_id_tracks_xata_id_fk": { 67 + "name": "album_tracks_track_id_tracks_xata_id_fk", 68 + "tableFrom": "album_tracks", 69 + "tableTo": "tracks", 70 + "columnsFrom": [ 71 + "track_id" 72 + ], 73 + "columnsTo": [ 74 + "xata_id" 75 + ], 76 + "onDelete": "no action", 77 + "onUpdate": "no action" 78 + } 79 + }, 80 + "compositePrimaryKeys": {}, 81 + "uniqueConstraints": {}, 82 + "policies": {}, 83 + "checkConstraints": {}, 84 + "isRLSEnabled": false 85 + }, 86 + "public.albums": { 87 + "name": "albums", 88 + "schema": "", 89 + "columns": { 90 + "xata_id": { 91 + "name": "xata_id", 92 + "type": "text", 93 + "primaryKey": true, 94 + "notNull": true, 95 + "default": "xata_id()" 96 + }, 97 + "title": { 98 + "name": "title", 99 + "type": "text", 100 + "primaryKey": false, 101 + "notNull": true 102 + }, 103 + "artist": { 104 + "name": "artist", 105 + "type": "text", 106 + "primaryKey": false, 107 + "notNull": true 108 + }, 109 + "release_date": { 110 + "name": "release_date", 111 + "type": "text", 112 + "primaryKey": false, 113 + "notNull": false 114 + }, 115 + "year": { 116 + "name": "year", 117 + "type": "integer", 118 + "primaryKey": false, 119 + "notNull": false 120 + }, 121 + "album_art": { 122 + "name": "album_art", 123 + "type": "text", 124 + "primaryKey": false, 125 + "notNull": false 126 + }, 127 + "uri": { 128 + "name": "uri", 129 + "type": "text", 130 + "primaryKey": false, 131 + "notNull": false 132 + }, 133 + "artist_uri": { 134 + "name": "artist_uri", 135 + "type": "text", 136 + "primaryKey": false, 137 + "notNull": false 138 + }, 139 + "apple_music_link": { 140 + "name": "apple_music_link", 141 + "type": "text", 142 + "primaryKey": false, 143 + "notNull": false 144 + }, 145 + "spotify_link": { 146 + "name": "spotify_link", 147 + "type": "text", 148 + "primaryKey": false, 149 + "notNull": false 150 + }, 151 + "tidal_link": { 152 + "name": "tidal_link", 153 + "type": "text", 154 + "primaryKey": false, 155 + "notNull": false 156 + }, 157 + "youtube_link": { 158 + "name": "youtube_link", 159 + "type": "text", 160 + "primaryKey": false, 161 + "notNull": false 162 + }, 163 + "sha256": { 164 + "name": "sha256", 165 + "type": "text", 166 + "primaryKey": false, 167 + "notNull": true 168 + }, 169 + "xata_createdat": { 170 + "name": "xata_createdat", 171 + "type": "timestamp", 172 + "primaryKey": false, 173 + "notNull": true, 174 + "default": "now()" 175 + }, 176 + "xata_updatedat": { 177 + "name": "xata_updatedat", 178 + "type": "timestamp", 179 + "primaryKey": false, 180 + "notNull": true, 181 + "default": "now()" 182 + }, 183 + "xata_version": { 184 + "name": "xata_version", 185 + "type": "integer", 186 + "primaryKey": false, 187 + "notNull": false 188 + } 189 + }, 190 + "indexes": {}, 191 + "foreignKeys": {}, 192 + "compositePrimaryKeys": {}, 193 + "uniqueConstraints": { 194 + "albums_uri_unique": { 195 + "name": "albums_uri_unique", 196 + "nullsNotDistinct": false, 197 + "columns": [ 198 + "uri" 199 + ] 200 + }, 201 + "albums_apple_music_link_unique": { 202 + "name": "albums_apple_music_link_unique", 203 + "nullsNotDistinct": false, 204 + "columns": [ 205 + "apple_music_link" 206 + ] 207 + }, 208 + "albums_spotify_link_unique": { 209 + "name": "albums_spotify_link_unique", 210 + "nullsNotDistinct": false, 211 + "columns": [ 212 + "spotify_link" 213 + ] 214 + }, 215 + "albums_tidal_link_unique": { 216 + "name": "albums_tidal_link_unique", 217 + "nullsNotDistinct": false, 218 + "columns": [ 219 + "tidal_link" 220 + ] 221 + }, 222 + "albums_youtube_link_unique": { 223 + "name": "albums_youtube_link_unique", 224 + "nullsNotDistinct": false, 225 + "columns": [ 226 + "youtube_link" 227 + ] 228 + }, 229 + "albums_sha256_unique": { 230 + "name": "albums_sha256_unique", 231 + "nullsNotDistinct": false, 232 + "columns": [ 233 + "sha256" 234 + ] 235 + } 236 + }, 237 + "policies": {}, 238 + "checkConstraints": {}, 239 + "isRLSEnabled": false 240 + }, 241 + "public.api_keys": { 242 + "name": "api_keys", 243 + "schema": "", 244 + "columns": { 245 + "xata_id": { 246 + "name": "xata_id", 247 + "type": "text", 248 + "primaryKey": true, 249 + "notNull": true, 250 + "default": "xata_id()" 251 + }, 252 + "name": { 253 + "name": "name", 254 + "type": "text", 255 + "primaryKey": false, 256 + "notNull": true 257 + }, 258 + "api_key": { 259 + "name": "api_key", 260 + "type": "text", 261 + "primaryKey": false, 262 + "notNull": true 263 + }, 264 + "shared_secret": { 265 + "name": "shared_secret", 266 + "type": "text", 267 + "primaryKey": false, 268 + "notNull": true 269 + }, 270 + "description": { 271 + "name": "description", 272 + "type": "text", 273 + "primaryKey": false, 274 + "notNull": false 275 + }, 276 + "enabled": { 277 + "name": "enabled", 278 + "type": "boolean", 279 + "primaryKey": false, 280 + "notNull": true, 281 + "default": true 282 + }, 283 + "user_id": { 284 + "name": "user_id", 285 + "type": "text", 286 + "primaryKey": false, 287 + "notNull": true 288 + }, 289 + "xata_createdat": { 290 + "name": "xata_createdat", 291 + "type": "timestamp", 292 + "primaryKey": false, 293 + "notNull": true, 294 + "default": "now()" 295 + }, 296 + "xata_updatedat": { 297 + "name": "xata_updatedat", 298 + "type": "timestamp", 299 + "primaryKey": false, 300 + "notNull": true, 301 + "default": "now()" 302 + } 303 + }, 304 + "indexes": {}, 305 + "foreignKeys": { 306 + "api_keys_user_id_users_xata_id_fk": { 307 + "name": "api_keys_user_id_users_xata_id_fk", 308 + "tableFrom": "api_keys", 309 + "tableTo": "users", 310 + "columnsFrom": [ 311 + "user_id" 312 + ], 313 + "columnsTo": [ 314 + "xata_id" 315 + ], 316 + "onDelete": "no action", 317 + "onUpdate": "no action" 318 + } 319 + }, 320 + "compositePrimaryKeys": {}, 321 + "uniqueConstraints": {}, 322 + "policies": {}, 323 + "checkConstraints": {}, 324 + "isRLSEnabled": false 325 + }, 326 + "public.artist_albums": { 327 + "name": "artist_albums", 328 + "schema": "", 329 + "columns": { 330 + "xata_id": { 331 + "name": "xata_id", 332 + "type": "text", 333 + "primaryKey": true, 334 + "notNull": true, 335 + "default": "xata_id()" 336 + }, 337 + "artist_id": { 338 + "name": "artist_id", 339 + "type": "text", 340 + "primaryKey": false, 341 + "notNull": true 342 + }, 343 + "album_id": { 344 + "name": "album_id", 345 + "type": "text", 346 + "primaryKey": false, 347 + "notNull": true 348 + }, 349 + "xata_createdat": { 350 + "name": "xata_createdat", 351 + "type": "timestamp", 352 + "primaryKey": false, 353 + "notNull": true, 354 + "default": "now()" 355 + }, 356 + "xata_updatedat": { 357 + "name": "xata_updatedat", 358 + "type": "timestamp", 359 + "primaryKey": false, 360 + "notNull": true, 361 + "default": "now()" 362 + }, 363 + "xata_version": { 364 + "name": "xata_version", 365 + "type": "integer", 366 + "primaryKey": false, 367 + "notNull": false 368 + } 369 + }, 370 + "indexes": {}, 371 + "foreignKeys": { 372 + "artist_albums_artist_id_artists_xata_id_fk": { 373 + "name": "artist_albums_artist_id_artists_xata_id_fk", 374 + "tableFrom": "artist_albums", 375 + "tableTo": "artists", 376 + "columnsFrom": [ 377 + "artist_id" 378 + ], 379 + "columnsTo": [ 380 + "xata_id" 381 + ], 382 + "onDelete": "no action", 383 + "onUpdate": "no action" 384 + }, 385 + "artist_albums_album_id_albums_xata_id_fk": { 386 + "name": "artist_albums_album_id_albums_xata_id_fk", 387 + "tableFrom": "artist_albums", 388 + "tableTo": "albums", 389 + "columnsFrom": [ 390 + "album_id" 391 + ], 392 + "columnsTo": [ 393 + "xata_id" 394 + ], 395 + "onDelete": "no action", 396 + "onUpdate": "no action" 397 + } 398 + }, 399 + "compositePrimaryKeys": {}, 400 + "uniqueConstraints": {}, 401 + "policies": {}, 402 + "checkConstraints": {}, 403 + "isRLSEnabled": false 404 + }, 405 + "public.artist_tracks": { 406 + "name": "artist_tracks", 407 + "schema": "", 408 + "columns": { 409 + "xata_id": { 410 + "name": "xata_id", 411 + "type": "text", 412 + "primaryKey": true, 413 + "notNull": true, 414 + "default": "xata_id()" 415 + }, 416 + "artist_id": { 417 + "name": "artist_id", 418 + "type": "text", 419 + "primaryKey": false, 420 + "notNull": true 421 + }, 422 + "track_id": { 423 + "name": "track_id", 424 + "type": "text", 425 + "primaryKey": false, 426 + "notNull": true 427 + }, 428 + "xata_createdat": { 429 + "name": "xata_createdat", 430 + "type": "timestamp", 431 + "primaryKey": false, 432 + "notNull": true, 433 + "default": "now()" 434 + }, 435 + "xata_updatedat": { 436 + "name": "xata_updatedat", 437 + "type": "timestamp", 438 + "primaryKey": false, 439 + "notNull": true, 440 + "default": "now()" 441 + }, 442 + "xata_version": { 443 + "name": "xata_version", 444 + "type": "integer", 445 + "primaryKey": false, 446 + "notNull": false 447 + } 448 + }, 449 + "indexes": {}, 450 + "foreignKeys": { 451 + "artist_tracks_artist_id_artists_xata_id_fk": { 452 + "name": "artist_tracks_artist_id_artists_xata_id_fk", 453 + "tableFrom": "artist_tracks", 454 + "tableTo": "artists", 455 + "columnsFrom": [ 456 + "artist_id" 457 + ], 458 + "columnsTo": [ 459 + "xata_id" 460 + ], 461 + "onDelete": "no action", 462 + "onUpdate": "no action" 463 + }, 464 + "artist_tracks_track_id_tracks_xata_id_fk": { 465 + "name": "artist_tracks_track_id_tracks_xata_id_fk", 466 + "tableFrom": "artist_tracks", 467 + "tableTo": "tracks", 468 + "columnsFrom": [ 469 + "track_id" 470 + ], 471 + "columnsTo": [ 472 + "xata_id" 473 + ], 474 + "onDelete": "no action", 475 + "onUpdate": "no action" 476 + } 477 + }, 478 + "compositePrimaryKeys": {}, 479 + "uniqueConstraints": {}, 480 + "policies": {}, 481 + "checkConstraints": {}, 482 + "isRLSEnabled": false 483 + }, 484 + "public.artists": { 485 + "name": "artists", 486 + "schema": "", 487 + "columns": { 488 + "xata_id": { 489 + "name": "xata_id", 490 + "type": "text", 491 + "primaryKey": true, 492 + "notNull": true, 493 + "default": "xata_id()" 494 + }, 495 + "name": { 496 + "name": "name", 497 + "type": "text", 498 + "primaryKey": false, 499 + "notNull": true 500 + }, 501 + "biography": { 502 + "name": "biography", 503 + "type": "text", 504 + "primaryKey": false, 505 + "notNull": false 506 + }, 507 + "born": { 508 + "name": "born", 509 + "type": "timestamp", 510 + "primaryKey": false, 511 + "notNull": false 512 + }, 513 + "born_in": { 514 + "name": "born_in", 515 + "type": "text", 516 + "primaryKey": false, 517 + "notNull": false 518 + }, 519 + "died": { 520 + "name": "died", 521 + "type": "timestamp", 522 + "primaryKey": false, 523 + "notNull": false 524 + }, 525 + "picture": { 526 + "name": "picture", 527 + "type": "text", 528 + "primaryKey": false, 529 + "notNull": false 530 + }, 531 + "sha256": { 532 + "name": "sha256", 533 + "type": "text", 534 + "primaryKey": false, 535 + "notNull": true 536 + }, 537 + "uri": { 538 + "name": "uri", 539 + "type": "text", 540 + "primaryKey": false, 541 + "notNull": false 542 + }, 543 + "apple_music_link": { 544 + "name": "apple_music_link", 545 + "type": "text", 546 + "primaryKey": false, 547 + "notNull": false 548 + }, 549 + "spotify_link": { 550 + "name": "spotify_link", 551 + "type": "text", 552 + "primaryKey": false, 553 + "notNull": false 554 + }, 555 + "tidal_link": { 556 + "name": "tidal_link", 557 + "type": "text", 558 + "primaryKey": false, 559 + "notNull": false 560 + }, 561 + "youtube_link": { 562 + "name": "youtube_link", 563 + "type": "text", 564 + "primaryKey": false, 565 + "notNull": false 566 + }, 567 + "genres": { 568 + "name": "genres", 569 + "type": "text[]", 570 + "primaryKey": false, 571 + "notNull": false 572 + }, 573 + "xata_createdat": { 574 + "name": "xata_createdat", 575 + "type": "timestamp", 576 + "primaryKey": false, 577 + "notNull": true, 578 + "default": "now()" 579 + }, 580 + "xata_updatedat": { 581 + "name": "xata_updatedat", 582 + "type": "timestamp", 583 + "primaryKey": false, 584 + "notNull": true, 585 + "default": "now()" 586 + }, 587 + "xata_version": { 588 + "name": "xata_version", 589 + "type": "integer", 590 + "primaryKey": false, 591 + "notNull": false 592 + } 593 + }, 594 + "indexes": {}, 595 + "foreignKeys": {}, 596 + "compositePrimaryKeys": {}, 597 + "uniqueConstraints": { 598 + "artists_sha256_unique": { 599 + "name": "artists_sha256_unique", 600 + "nullsNotDistinct": false, 601 + "columns": [ 602 + "sha256" 603 + ] 604 + }, 605 + "artists_uri_unique": { 606 + "name": "artists_uri_unique", 607 + "nullsNotDistinct": false, 608 + "columns": [ 609 + "uri" 610 + ] 611 + } 612 + }, 613 + "policies": {}, 614 + "checkConstraints": {}, 615 + "isRLSEnabled": false 616 + }, 617 + "public.dropbox_accounts": { 618 + "name": "dropbox_accounts", 619 + "schema": "", 620 + "columns": { 621 + "xata_id": { 622 + "name": "xata_id", 623 + "type": "text", 624 + "primaryKey": true, 625 + "notNull": true, 626 + "default": "xata_id()" 627 + }, 628 + "email": { 629 + "name": "email", 630 + "type": "text", 631 + "primaryKey": false, 632 + "notNull": true 633 + }, 634 + "is_beta_user": { 635 + "name": "is_beta_user", 636 + "type": "boolean", 637 + "primaryKey": false, 638 + "notNull": true, 639 + "default": false 640 + }, 641 + "user_id": { 642 + "name": "user_id", 643 + "type": "text", 644 + "primaryKey": false, 645 + "notNull": true 646 + }, 647 + "xata_version": { 648 + "name": "xata_version", 649 + "type": "text", 650 + "primaryKey": false, 651 + "notNull": false 652 + }, 653 + "xata_createdat": { 654 + "name": "xata_createdat", 655 + "type": "timestamp", 656 + "primaryKey": false, 657 + "notNull": true, 658 + "default": "now()" 659 + }, 660 + "xata_updatedat": { 661 + "name": "xata_updatedat", 662 + "type": "timestamp", 663 + "primaryKey": false, 664 + "notNull": true, 665 + "default": "now()" 666 + } 667 + }, 668 + "indexes": {}, 669 + "foreignKeys": { 670 + "dropbox_accounts_user_id_users_xata_id_fk": { 671 + "name": "dropbox_accounts_user_id_users_xata_id_fk", 672 + "tableFrom": "dropbox_accounts", 673 + "tableTo": "users", 674 + "columnsFrom": [ 675 + "user_id" 676 + ], 677 + "columnsTo": [ 678 + "xata_id" 679 + ], 680 + "onDelete": "no action", 681 + "onUpdate": "no action" 682 + } 683 + }, 684 + "compositePrimaryKeys": {}, 685 + "uniqueConstraints": { 686 + "dropbox_accounts_email_unique": { 687 + "name": "dropbox_accounts_email_unique", 688 + "nullsNotDistinct": false, 689 + "columns": [ 690 + "email" 691 + ] 692 + } 693 + }, 694 + "policies": {}, 695 + "checkConstraints": {}, 696 + "isRLSEnabled": false 697 + }, 698 + "public.dropbox_directories": { 699 + "name": "dropbox_directories", 700 + "schema": "", 701 + "columns": { 702 + "xata_id": { 703 + "name": "xata_id", 704 + "type": "text", 705 + "primaryKey": true, 706 + "notNull": true, 707 + "default": "xata_id()" 708 + }, 709 + "name": { 710 + "name": "name", 711 + "type": "text", 712 + "primaryKey": false, 713 + "notNull": true 714 + }, 715 + "path": { 716 + "name": "path", 717 + "type": "text", 718 + "primaryKey": false, 719 + "notNull": true 720 + }, 721 + "parent_id": { 722 + "name": "parent_id", 723 + "type": "text", 724 + "primaryKey": false, 725 + "notNull": false 726 + }, 727 + "dropbox_id": { 728 + "name": "dropbox_id", 729 + "type": "text", 730 + "primaryKey": false, 731 + "notNull": true 732 + }, 733 + "file_id": { 734 + "name": "file_id", 735 + "type": "text", 736 + "primaryKey": false, 737 + "notNull": true 738 + }, 739 + "xata_version": { 740 + "name": "xata_version", 741 + "type": "text", 742 + "primaryKey": false, 743 + "notNull": false 744 + }, 745 + "xata_createdat": { 746 + "name": "xata_createdat", 747 + "type": "timestamp", 748 + "primaryKey": false, 749 + "notNull": true, 750 + "default": "now()" 751 + }, 752 + "xata_updatedat": { 753 + "name": "xata_updatedat", 754 + "type": "timestamp", 755 + "primaryKey": false, 756 + "notNull": true, 757 + "default": "now()" 758 + } 759 + }, 760 + "indexes": {}, 761 + "foreignKeys": { 762 + "dropbox_directories_parent_id_dropbox_directories_xata_id_fk": { 763 + "name": "dropbox_directories_parent_id_dropbox_directories_xata_id_fk", 764 + "tableFrom": "dropbox_directories", 765 + "tableTo": "dropbox_directories", 766 + "columnsFrom": [ 767 + "parent_id" 768 + ], 769 + "columnsTo": [ 770 + "xata_id" 771 + ], 772 + "onDelete": "no action", 773 + "onUpdate": "no action" 774 + } 775 + }, 776 + "compositePrimaryKeys": {}, 777 + "uniqueConstraints": { 778 + "dropbox_directories_file_id_unique": { 779 + "name": "dropbox_directories_file_id_unique", 780 + "nullsNotDistinct": false, 781 + "columns": [ 782 + "file_id" 783 + ] 784 + } 785 + }, 786 + "policies": {}, 787 + "checkConstraints": {}, 788 + "isRLSEnabled": false 789 + }, 790 + "public.dropbox_paths": { 791 + "name": "dropbox_paths", 792 + "schema": "", 793 + "columns": { 794 + "xata_id": { 795 + "name": "xata_id", 796 + "type": "text", 797 + "primaryKey": true, 798 + "notNull": true, 799 + "default": "xata_id()" 800 + }, 801 + "path": { 802 + "name": "path", 803 + "type": "text", 804 + "primaryKey": false, 805 + "notNull": true 806 + }, 807 + "name": { 808 + "name": "name", 809 + "type": "text", 810 + "primaryKey": false, 811 + "notNull": true 812 + }, 813 + "dropbox_id": { 814 + "name": "dropbox_id", 815 + "type": "text", 816 + "primaryKey": false, 817 + "notNull": true 818 + }, 819 + "track_id": { 820 + "name": "track_id", 821 + "type": "text", 822 + "primaryKey": false, 823 + "notNull": true 824 + }, 825 + "directory_id": { 826 + "name": "directory_id", 827 + "type": "text", 828 + "primaryKey": false, 829 + "notNull": false 830 + }, 831 + "file_id": { 832 + "name": "file_id", 833 + "type": "text", 834 + "primaryKey": false, 835 + "notNull": true 836 + }, 837 + "xata_version": { 838 + "name": "xata_version", 839 + "type": "text", 840 + "primaryKey": false, 841 + "notNull": false 842 + }, 843 + "xata_createdat": { 844 + "name": "xata_createdat", 845 + "type": "timestamp", 846 + "primaryKey": false, 847 + "notNull": true, 848 + "default": "now()" 849 + }, 850 + "xata_updatedat": { 851 + "name": "xata_updatedat", 852 + "type": "timestamp", 853 + "primaryKey": false, 854 + "notNull": true, 855 + "default": "now()" 856 + } 857 + }, 858 + "indexes": {}, 859 + "foreignKeys": { 860 + "dropbox_paths_directory_id_dropbox_directories_xata_id_fk": { 861 + "name": "dropbox_paths_directory_id_dropbox_directories_xata_id_fk", 862 + "tableFrom": "dropbox_paths", 863 + "tableTo": "dropbox_directories", 864 + "columnsFrom": [ 865 + "directory_id" 866 + ], 867 + "columnsTo": [ 868 + "xata_id" 869 + ], 870 + "onDelete": "no action", 871 + "onUpdate": "no action" 872 + } 873 + }, 874 + "compositePrimaryKeys": {}, 875 + "uniqueConstraints": { 876 + "dropbox_paths_file_id_unique": { 877 + "name": "dropbox_paths_file_id_unique", 878 + "nullsNotDistinct": false, 879 + "columns": [ 880 + "file_id" 881 + ] 882 + } 883 + }, 884 + "policies": {}, 885 + "checkConstraints": {}, 886 + "isRLSEnabled": false 887 + }, 888 + "public.dropbox_tokens": { 889 + "name": "dropbox_tokens", 890 + "schema": "", 891 + "columns": { 892 + "xata_id": { 893 + "name": "xata_id", 894 + "type": "text", 895 + "primaryKey": true, 896 + "notNull": true, 897 + "default": "xata_id()" 898 + }, 899 + "refresh_token": { 900 + "name": "refresh_token", 901 + "type": "text", 902 + "primaryKey": false, 903 + "notNull": true 904 + }, 905 + "xata_createdat": { 906 + "name": "xata_createdat", 907 + "type": "timestamp", 908 + "primaryKey": false, 909 + "notNull": true, 910 + "default": "now()" 911 + }, 912 + "xata_updatedat": { 913 + "name": "xata_updatedat", 914 + "type": "timestamp", 915 + "primaryKey": false, 916 + "notNull": true, 917 + "default": "now()" 918 + } 919 + }, 920 + "indexes": {}, 921 + "foreignKeys": {}, 922 + "compositePrimaryKeys": {}, 923 + "uniqueConstraints": {}, 924 + "policies": {}, 925 + "checkConstraints": {}, 926 + "isRLSEnabled": false 927 + }, 928 + "public.dropbox": { 929 + "name": "dropbox", 930 + "schema": "", 931 + "columns": { 932 + "xata_id": { 933 + "name": "xata_id", 934 + "type": "text", 935 + "primaryKey": true, 936 + "notNull": true, 937 + "default": "xata_id()" 938 + }, 939 + "user_id": { 940 + "name": "user_id", 941 + "type": "text", 942 + "primaryKey": false, 943 + "notNull": true 944 + }, 945 + "dropbox_token_id": { 946 + "name": "dropbox_token_id", 947 + "type": "text", 948 + "primaryKey": false, 949 + "notNull": true 950 + }, 951 + "xata_version": { 952 + "name": "xata_version", 953 + "type": "text", 954 + "primaryKey": false, 955 + "notNull": false 956 + }, 957 + "xata_createdat": { 958 + "name": "xata_createdat", 959 + "type": "timestamp", 960 + "primaryKey": false, 961 + "notNull": true, 962 + "default": "now()" 963 + }, 964 + "xata_updatedat": { 965 + "name": "xata_updatedat", 966 + "type": "timestamp", 967 + "primaryKey": false, 968 + "notNull": true, 969 + "default": "now()" 970 + } 971 + }, 972 + "indexes": {}, 973 + "foreignKeys": { 974 + "dropbox_user_id_users_xata_id_fk": { 975 + "name": "dropbox_user_id_users_xata_id_fk", 976 + "tableFrom": "dropbox", 977 + "tableTo": "users", 978 + "columnsFrom": [ 979 + "user_id" 980 + ], 981 + "columnsTo": [ 982 + "xata_id" 983 + ], 984 + "onDelete": "no action", 985 + "onUpdate": "no action" 986 + }, 987 + "dropbox_dropbox_token_id_dropbox_tokens_xata_id_fk": { 988 + "name": "dropbox_dropbox_token_id_dropbox_tokens_xata_id_fk", 989 + "tableFrom": "dropbox", 990 + "tableTo": "dropbox_tokens", 991 + "columnsFrom": [ 992 + "dropbox_token_id" 993 + ], 994 + "columnsTo": [ 995 + "xata_id" 996 + ], 997 + "onDelete": "no action", 998 + "onUpdate": "no action" 999 + } 1000 + }, 1001 + "compositePrimaryKeys": {}, 1002 + "uniqueConstraints": {}, 1003 + "policies": {}, 1004 + "checkConstraints": {}, 1005 + "isRLSEnabled": false 1006 + }, 1007 + "public.google_drive_accounts": { 1008 + "name": "google_drive_accounts", 1009 + "schema": "", 1010 + "columns": { 1011 + "xata_id": { 1012 + "name": "xata_id", 1013 + "type": "text", 1014 + "primaryKey": true, 1015 + "notNull": true, 1016 + "default": "xata_id()" 1017 + }, 1018 + "email": { 1019 + "name": "email", 1020 + "type": "text", 1021 + "primaryKey": false, 1022 + "notNull": true 1023 + }, 1024 + "is_beta_user": { 1025 + "name": "is_beta_user", 1026 + "type": "boolean", 1027 + "primaryKey": false, 1028 + "notNull": true, 1029 + "default": false 1030 + }, 1031 + "user_id": { 1032 + "name": "user_id", 1033 + "type": "text", 1034 + "primaryKey": false, 1035 + "notNull": true 1036 + }, 1037 + "xata_version": { 1038 + "name": "xata_version", 1039 + "type": "text", 1040 + "primaryKey": false, 1041 + "notNull": false 1042 + }, 1043 + "xata_createdat": { 1044 + "name": "xata_createdat", 1045 + "type": "timestamp", 1046 + "primaryKey": false, 1047 + "notNull": true, 1048 + "default": "now()" 1049 + }, 1050 + "xata_updatedat": { 1051 + "name": "xata_updatedat", 1052 + "type": "timestamp", 1053 + "primaryKey": false, 1054 + "notNull": true, 1055 + "default": "now()" 1056 + } 1057 + }, 1058 + "indexes": {}, 1059 + "foreignKeys": { 1060 + "google_drive_accounts_user_id_users_xata_id_fk": { 1061 + "name": "google_drive_accounts_user_id_users_xata_id_fk", 1062 + "tableFrom": "google_drive_accounts", 1063 + "tableTo": "users", 1064 + "columnsFrom": [ 1065 + "user_id" 1066 + ], 1067 + "columnsTo": [ 1068 + "xata_id" 1069 + ], 1070 + "onDelete": "no action", 1071 + "onUpdate": "no action" 1072 + } 1073 + }, 1074 + "compositePrimaryKeys": {}, 1075 + "uniqueConstraints": { 1076 + "google_drive_accounts_email_unique": { 1077 + "name": "google_drive_accounts_email_unique", 1078 + "nullsNotDistinct": false, 1079 + "columns": [ 1080 + "email" 1081 + ] 1082 + } 1083 + }, 1084 + "policies": {}, 1085 + "checkConstraints": {}, 1086 + "isRLSEnabled": false 1087 + }, 1088 + "public.google_drive_directories": { 1089 + "name": "google_drive_directories", 1090 + "schema": "", 1091 + "columns": { 1092 + "xata_id": { 1093 + "name": "xata_id", 1094 + "type": "text", 1095 + "primaryKey": true, 1096 + "notNull": true, 1097 + "default": "xata_id()" 1098 + }, 1099 + "name": { 1100 + "name": "name", 1101 + "type": "text", 1102 + "primaryKey": false, 1103 + "notNull": true 1104 + }, 1105 + "path": { 1106 + "name": "path", 1107 + "type": "text", 1108 + "primaryKey": false, 1109 + "notNull": true 1110 + }, 1111 + "parent_id": { 1112 + "name": "parent_id", 1113 + "type": "text", 1114 + "primaryKey": false, 1115 + "notNull": false 1116 + }, 1117 + "google_drive_id": { 1118 + "name": "google_drive_id", 1119 + "type": "text", 1120 + "primaryKey": false, 1121 + "notNull": true 1122 + }, 1123 + "file_id": { 1124 + "name": "file_id", 1125 + "type": "text", 1126 + "primaryKey": false, 1127 + "notNull": true 1128 + }, 1129 + "xata_version": { 1130 + "name": "xata_version", 1131 + "type": "text", 1132 + "primaryKey": false, 1133 + "notNull": false 1134 + }, 1135 + "xata_createdat": { 1136 + "name": "xata_createdat", 1137 + "type": "timestamp", 1138 + "primaryKey": false, 1139 + "notNull": true, 1140 + "default": "now()" 1141 + }, 1142 + "xata_updatedat": { 1143 + "name": "xata_updatedat", 1144 + "type": "timestamp", 1145 + "primaryKey": false, 1146 + "notNull": true, 1147 + "default": "now()" 1148 + } 1149 + }, 1150 + "indexes": {}, 1151 + "foreignKeys": { 1152 + "google_drive_directories_parent_id_google_drive_directories_xata_id_fk": { 1153 + "name": "google_drive_directories_parent_id_google_drive_directories_xata_id_fk", 1154 + "tableFrom": "google_drive_directories", 1155 + "tableTo": "google_drive_directories", 1156 + "columnsFrom": [ 1157 + "parent_id" 1158 + ], 1159 + "columnsTo": [ 1160 + "xata_id" 1161 + ], 1162 + "onDelete": "no action", 1163 + "onUpdate": "no action" 1164 + } 1165 + }, 1166 + "compositePrimaryKeys": {}, 1167 + "uniqueConstraints": { 1168 + "google_drive_directories_file_id_unique": { 1169 + "name": "google_drive_directories_file_id_unique", 1170 + "nullsNotDistinct": false, 1171 + "columns": [ 1172 + "file_id" 1173 + ] 1174 + } 1175 + }, 1176 + "policies": {}, 1177 + "checkConstraints": {}, 1178 + "isRLSEnabled": false 1179 + }, 1180 + "public.google_drive_paths": { 1181 + "name": "google_drive_paths", 1182 + "schema": "", 1183 + "columns": { 1184 + "xata_id": { 1185 + "name": "xata_id", 1186 + "type": "text", 1187 + "primaryKey": true, 1188 + "notNull": true, 1189 + "default": "xata_id()" 1190 + }, 1191 + "google_drive_id": { 1192 + "name": "google_drive_id", 1193 + "type": "text", 1194 + "primaryKey": false, 1195 + "notNull": true 1196 + }, 1197 + "track_id": { 1198 + "name": "track_id", 1199 + "type": "text", 1200 + "primaryKey": false, 1201 + "notNull": true 1202 + }, 1203 + "name": { 1204 + "name": "name", 1205 + "type": "text", 1206 + "primaryKey": false, 1207 + "notNull": true 1208 + }, 1209 + "directory_id": { 1210 + "name": "directory_id", 1211 + "type": "text", 1212 + "primaryKey": false, 1213 + "notNull": false 1214 + }, 1215 + "file_id": { 1216 + "name": "file_id", 1217 + "type": "text", 1218 + "primaryKey": false, 1219 + "notNull": true 1220 + }, 1221 + "xata_version": { 1222 + "name": "xata_version", 1223 + "type": "text", 1224 + "primaryKey": false, 1225 + "notNull": false 1226 + }, 1227 + "xata_createdat": { 1228 + "name": "xata_createdat", 1229 + "type": "timestamp", 1230 + "primaryKey": false, 1231 + "notNull": true, 1232 + "default": "now()" 1233 + }, 1234 + "xata_updatedat": { 1235 + "name": "xata_updatedat", 1236 + "type": "timestamp", 1237 + "primaryKey": false, 1238 + "notNull": true, 1239 + "default": "now()" 1240 + } 1241 + }, 1242 + "indexes": {}, 1243 + "foreignKeys": { 1244 + "google_drive_paths_directory_id_google_drive_directories_xata_id_fk": { 1245 + "name": "google_drive_paths_directory_id_google_drive_directories_xata_id_fk", 1246 + "tableFrom": "google_drive_paths", 1247 + "tableTo": "google_drive_directories", 1248 + "columnsFrom": [ 1249 + "directory_id" 1250 + ], 1251 + "columnsTo": [ 1252 + "xata_id" 1253 + ], 1254 + "onDelete": "no action", 1255 + "onUpdate": "no action" 1256 + } 1257 + }, 1258 + "compositePrimaryKeys": {}, 1259 + "uniqueConstraints": { 1260 + "google_drive_paths_file_id_unique": { 1261 + "name": "google_drive_paths_file_id_unique", 1262 + "nullsNotDistinct": false, 1263 + "columns": [ 1264 + "file_id" 1265 + ] 1266 + } 1267 + }, 1268 + "policies": {}, 1269 + "checkConstraints": {}, 1270 + "isRLSEnabled": false 1271 + }, 1272 + "public.google_drive_tokens": { 1273 + "name": "google_drive_tokens", 1274 + "schema": "", 1275 + "columns": { 1276 + "xata_id": { 1277 + "name": "xata_id", 1278 + "type": "text", 1279 + "primaryKey": true, 1280 + "notNull": true, 1281 + "default": "xata_id()" 1282 + }, 1283 + "refresh_token": { 1284 + "name": "refresh_token", 1285 + "type": "text", 1286 + "primaryKey": false, 1287 + "notNull": true 1288 + }, 1289 + "xata_createdat": { 1290 + "name": "xata_createdat", 1291 + "type": "timestamp", 1292 + "primaryKey": false, 1293 + "notNull": true, 1294 + "default": "now()" 1295 + }, 1296 + "xata_updatedat": { 1297 + "name": "xata_updatedat", 1298 + "type": "timestamp", 1299 + "primaryKey": false, 1300 + "notNull": true, 1301 + "default": "now()" 1302 + } 1303 + }, 1304 + "indexes": {}, 1305 + "foreignKeys": {}, 1306 + "compositePrimaryKeys": {}, 1307 + "uniqueConstraints": {}, 1308 + "policies": {}, 1309 + "checkConstraints": {}, 1310 + "isRLSEnabled": false 1311 + }, 1312 + "public.google_drive": { 1313 + "name": "google_drive", 1314 + "schema": "", 1315 + "columns": { 1316 + "xata_id": { 1317 + "name": "xata_id", 1318 + "type": "text", 1319 + "primaryKey": true, 1320 + "notNull": true, 1321 + "default": "xata_id()" 1322 + }, 1323 + "google_drive_token_id": { 1324 + "name": "google_drive_token_id", 1325 + "type": "text", 1326 + "primaryKey": false, 1327 + "notNull": true 1328 + }, 1329 + "user_id": { 1330 + "name": "user_id", 1331 + "type": "text", 1332 + "primaryKey": false, 1333 + "notNull": true 1334 + }, 1335 + "xata_version": { 1336 + "name": "xata_version", 1337 + "type": "text", 1338 + "primaryKey": false, 1339 + "notNull": false 1340 + }, 1341 + "xata_createdat": { 1342 + "name": "xata_createdat", 1343 + "type": "timestamp", 1344 + "primaryKey": false, 1345 + "notNull": true, 1346 + "default": "now()" 1347 + }, 1348 + "xata_updatedat": { 1349 + "name": "xata_updatedat", 1350 + "type": "timestamp", 1351 + "primaryKey": false, 1352 + "notNull": true, 1353 + "default": "now()" 1354 + } 1355 + }, 1356 + "indexes": {}, 1357 + "foreignKeys": { 1358 + "google_drive_google_drive_token_id_google_drive_tokens_xata_id_fk": { 1359 + "name": "google_drive_google_drive_token_id_google_drive_tokens_xata_id_fk", 1360 + "tableFrom": "google_drive", 1361 + "tableTo": "google_drive_tokens", 1362 + "columnsFrom": [ 1363 + "google_drive_token_id" 1364 + ], 1365 + "columnsTo": [ 1366 + "xata_id" 1367 + ], 1368 + "onDelete": "no action", 1369 + "onUpdate": "no action" 1370 + }, 1371 + "google_drive_user_id_users_xata_id_fk": { 1372 + "name": "google_drive_user_id_users_xata_id_fk", 1373 + "tableFrom": "google_drive", 1374 + "tableTo": "users", 1375 + "columnsFrom": [ 1376 + "user_id" 1377 + ], 1378 + "columnsTo": [ 1379 + "xata_id" 1380 + ], 1381 + "onDelete": "no action", 1382 + "onUpdate": "no action" 1383 + } 1384 + }, 1385 + "compositePrimaryKeys": {}, 1386 + "uniqueConstraints": {}, 1387 + "policies": {}, 1388 + "checkConstraints": {}, 1389 + "isRLSEnabled": false 1390 + }, 1391 + "public.loved_tracks": { 1392 + "name": "loved_tracks", 1393 + "schema": "", 1394 + "columns": { 1395 + "xata_id": { 1396 + "name": "xata_id", 1397 + "type": "text", 1398 + "primaryKey": true, 1399 + "notNull": true, 1400 + "default": "xata_id()" 1401 + }, 1402 + "user_id": { 1403 + "name": "user_id", 1404 + "type": "text", 1405 + "primaryKey": false, 1406 + "notNull": true 1407 + }, 1408 + "track_id": { 1409 + "name": "track_id", 1410 + "type": "text", 1411 + "primaryKey": false, 1412 + "notNull": true 1413 + }, 1414 + "uri": { 1415 + "name": "uri", 1416 + "type": "text", 1417 + "primaryKey": false, 1418 + "notNull": false 1419 + }, 1420 + "xata_createdat": { 1421 + "name": "xata_createdat", 1422 + "type": "timestamp", 1423 + "primaryKey": false, 1424 + "notNull": true, 1425 + "default": "now()" 1426 + } 1427 + }, 1428 + "indexes": {}, 1429 + "foreignKeys": { 1430 + "loved_tracks_user_id_users_xata_id_fk": { 1431 + "name": "loved_tracks_user_id_users_xata_id_fk", 1432 + "tableFrom": "loved_tracks", 1433 + "tableTo": "users", 1434 + "columnsFrom": [ 1435 + "user_id" 1436 + ], 1437 + "columnsTo": [ 1438 + "xata_id" 1439 + ], 1440 + "onDelete": "no action", 1441 + "onUpdate": "no action" 1442 + }, 1443 + "loved_tracks_track_id_tracks_xata_id_fk": { 1444 + "name": "loved_tracks_track_id_tracks_xata_id_fk", 1445 + "tableFrom": "loved_tracks", 1446 + "tableTo": "tracks", 1447 + "columnsFrom": [ 1448 + "track_id" 1449 + ], 1450 + "columnsTo": [ 1451 + "xata_id" 1452 + ], 1453 + "onDelete": "no action", 1454 + "onUpdate": "no action" 1455 + } 1456 + }, 1457 + "compositePrimaryKeys": {}, 1458 + "uniqueConstraints": { 1459 + "loved_tracks_uri_unique": { 1460 + "name": "loved_tracks_uri_unique", 1461 + "nullsNotDistinct": false, 1462 + "columns": [ 1463 + "uri" 1464 + ] 1465 + } 1466 + }, 1467 + "policies": {}, 1468 + "checkConstraints": {}, 1469 + "isRLSEnabled": false 1470 + }, 1471 + "public.playlist_tracks": { 1472 + "name": "playlist_tracks", 1473 + "schema": "", 1474 + "columns": { 1475 + "xata_id": { 1476 + "name": "xata_id", 1477 + "type": "text", 1478 + "primaryKey": true, 1479 + "notNull": true, 1480 + "default": "xata_id()" 1481 + }, 1482 + "playlist_id": { 1483 + "name": "playlist_id", 1484 + "type": "text", 1485 + "primaryKey": false, 1486 + "notNull": true 1487 + }, 1488 + "track_id": { 1489 + "name": "track_id", 1490 + "type": "text", 1491 + "primaryKey": false, 1492 + "notNull": true 1493 + }, 1494 + "xata_createdat": { 1495 + "name": "xata_createdat", 1496 + "type": "timestamp", 1497 + "primaryKey": false, 1498 + "notNull": true, 1499 + "default": "now()" 1500 + } 1501 + }, 1502 + "indexes": {}, 1503 + "foreignKeys": { 1504 + "playlist_tracks_playlist_id_playlists_xata_id_fk": { 1505 + "name": "playlist_tracks_playlist_id_playlists_xata_id_fk", 1506 + "tableFrom": "playlist_tracks", 1507 + "tableTo": "playlists", 1508 + "columnsFrom": [ 1509 + "playlist_id" 1510 + ], 1511 + "columnsTo": [ 1512 + "xata_id" 1513 + ], 1514 + "onDelete": "no action", 1515 + "onUpdate": "no action" 1516 + }, 1517 + "playlist_tracks_track_id_tracks_xata_id_fk": { 1518 + "name": "playlist_tracks_track_id_tracks_xata_id_fk", 1519 + "tableFrom": "playlist_tracks", 1520 + "tableTo": "tracks", 1521 + "columnsFrom": [ 1522 + "track_id" 1523 + ], 1524 + "columnsTo": [ 1525 + "xata_id" 1526 + ], 1527 + "onDelete": "no action", 1528 + "onUpdate": "no action" 1529 + } 1530 + }, 1531 + "compositePrimaryKeys": {}, 1532 + "uniqueConstraints": {}, 1533 + "policies": {}, 1534 + "checkConstraints": {}, 1535 + "isRLSEnabled": false 1536 + }, 1537 + "public.playlists": { 1538 + "name": "playlists", 1539 + "schema": "", 1540 + "columns": { 1541 + "xata_id": { 1542 + "name": "xata_id", 1543 + "type": "text", 1544 + "primaryKey": true, 1545 + "notNull": true, 1546 + "default": "xata_id()" 1547 + }, 1548 + "name": { 1549 + "name": "name", 1550 + "type": "text", 1551 + "primaryKey": false, 1552 + "notNull": true 1553 + }, 1554 + "picture": { 1555 + "name": "picture", 1556 + "type": "text", 1557 + "primaryKey": false, 1558 + "notNull": false 1559 + }, 1560 + "description": { 1561 + "name": "description", 1562 + "type": "text", 1563 + "primaryKey": false, 1564 + "notNull": false 1565 + }, 1566 + "uri": { 1567 + "name": "uri", 1568 + "type": "text", 1569 + "primaryKey": false, 1570 + "notNull": false 1571 + }, 1572 + "spotify_link": { 1573 + "name": "spotify_link", 1574 + "type": "text", 1575 + "primaryKey": false, 1576 + "notNull": false 1577 + }, 1578 + "tidal_link": { 1579 + "name": "tidal_link", 1580 + "type": "text", 1581 + "primaryKey": false, 1582 + "notNull": false 1583 + }, 1584 + "apple_music_link": { 1585 + "name": "apple_music_link", 1586 + "type": "text", 1587 + "primaryKey": false, 1588 + "notNull": false 1589 + }, 1590 + "created_by": { 1591 + "name": "created_by", 1592 + "type": "text", 1593 + "primaryKey": false, 1594 + "notNull": true 1595 + }, 1596 + "xata_createdat": { 1597 + "name": "xata_createdat", 1598 + "type": "timestamp", 1599 + "primaryKey": false, 1600 + "notNull": true, 1601 + "default": "now()" 1602 + }, 1603 + "xata_updatedat": { 1604 + "name": "xata_updatedat", 1605 + "type": "timestamp", 1606 + "primaryKey": false, 1607 + "notNull": true, 1608 + "default": "now()" 1609 + } 1610 + }, 1611 + "indexes": {}, 1612 + "foreignKeys": { 1613 + "playlists_created_by_users_xata_id_fk": { 1614 + "name": "playlists_created_by_users_xata_id_fk", 1615 + "tableFrom": "playlists", 1616 + "tableTo": "users", 1617 + "columnsFrom": [ 1618 + "created_by" 1619 + ], 1620 + "columnsTo": [ 1621 + "xata_id" 1622 + ], 1623 + "onDelete": "no action", 1624 + "onUpdate": "no action" 1625 + } 1626 + }, 1627 + "compositePrimaryKeys": {}, 1628 + "uniqueConstraints": { 1629 + "playlists_uri_unique": { 1630 + "name": "playlists_uri_unique", 1631 + "nullsNotDistinct": false, 1632 + "columns": [ 1633 + "uri" 1634 + ] 1635 + } 1636 + }, 1637 + "policies": {}, 1638 + "checkConstraints": {}, 1639 + "isRLSEnabled": false 1640 + }, 1641 + "public.profile_shouts": { 1642 + "name": "profile_shouts", 1643 + "schema": "", 1644 + "columns": { 1645 + "xata_id": { 1646 + "name": "xata_id", 1647 + "type": "text", 1648 + "primaryKey": true, 1649 + "notNull": true, 1650 + "default": "xata_id()" 1651 + }, 1652 + "user_id": { 1653 + "name": "user_id", 1654 + "type": "text", 1655 + "primaryKey": false, 1656 + "notNull": true 1657 + }, 1658 + "shout_id": { 1659 + "name": "shout_id", 1660 + "type": "text", 1661 + "primaryKey": false, 1662 + "notNull": true 1663 + }, 1664 + "xata_createdat": { 1665 + "name": "xata_createdat", 1666 + "type": "timestamp", 1667 + "primaryKey": false, 1668 + "notNull": true, 1669 + "default": "now()" 1670 + } 1671 + }, 1672 + "indexes": {}, 1673 + "foreignKeys": { 1674 + "profile_shouts_user_id_users_xata_id_fk": { 1675 + "name": "profile_shouts_user_id_users_xata_id_fk", 1676 + "tableFrom": "profile_shouts", 1677 + "tableTo": "users", 1678 + "columnsFrom": [ 1679 + "user_id" 1680 + ], 1681 + "columnsTo": [ 1682 + "xata_id" 1683 + ], 1684 + "onDelete": "no action", 1685 + "onUpdate": "no action" 1686 + }, 1687 + "profile_shouts_shout_id_shouts_xata_id_fk": { 1688 + "name": "profile_shouts_shout_id_shouts_xata_id_fk", 1689 + "tableFrom": "profile_shouts", 1690 + "tableTo": "shouts", 1691 + "columnsFrom": [ 1692 + "shout_id" 1693 + ], 1694 + "columnsTo": [ 1695 + "xata_id" 1696 + ], 1697 + "onDelete": "no action", 1698 + "onUpdate": "no action" 1699 + } 1700 + }, 1701 + "compositePrimaryKeys": {}, 1702 + "uniqueConstraints": {}, 1703 + "policies": {}, 1704 + "checkConstraints": {}, 1705 + "isRLSEnabled": false 1706 + }, 1707 + "public.queue_tracks": { 1708 + "name": "queue_tracks", 1709 + "schema": "", 1710 + "columns": { 1711 + "xata_id": { 1712 + "name": "xata_id", 1713 + "type": "text", 1714 + "primaryKey": true, 1715 + "notNull": true, 1716 + "default": "xata_id()" 1717 + }, 1718 + "user_id": { 1719 + "name": "user_id", 1720 + "type": "text", 1721 + "primaryKey": false, 1722 + "notNull": true 1723 + }, 1724 + "track_id": { 1725 + "name": "track_id", 1726 + "type": "text", 1727 + "primaryKey": false, 1728 + "notNull": true 1729 + }, 1730 + "position": { 1731 + "name": "position", 1732 + "type": "integer", 1733 + "primaryKey": false, 1734 + "notNull": true 1735 + }, 1736 + "file_uri": { 1737 + "name": "file_uri", 1738 + "type": "text", 1739 + "primaryKey": false, 1740 + "notNull": true 1741 + }, 1742 + "xata_version": { 1743 + "name": "xata_version", 1744 + "type": "integer", 1745 + "primaryKey": false, 1746 + "notNull": true, 1747 + "default": 0 1748 + }, 1749 + "xata_createdat": { 1750 + "name": "xata_createdat", 1751 + "type": "timestamp", 1752 + "primaryKey": false, 1753 + "notNull": true, 1754 + "default": "now()" 1755 + }, 1756 + "xata_updatedat": { 1757 + "name": "xata_updatedat", 1758 + "type": "timestamp", 1759 + "primaryKey": false, 1760 + "notNull": true, 1761 + "default": "now()" 1762 + } 1763 + }, 1764 + "indexes": {}, 1765 + "foreignKeys": { 1766 + "queue_tracks_user_id_users_xata_id_fk": { 1767 + "name": "queue_tracks_user_id_users_xata_id_fk", 1768 + "tableFrom": "queue_tracks", 1769 + "tableTo": "users", 1770 + "columnsFrom": [ 1771 + "user_id" 1772 + ], 1773 + "columnsTo": [ 1774 + "xata_id" 1775 + ], 1776 + "onDelete": "no action", 1777 + "onUpdate": "no action" 1778 + }, 1779 + "queue_tracks_track_id_tracks_xata_id_fk": { 1780 + "name": "queue_tracks_track_id_tracks_xata_id_fk", 1781 + "tableFrom": "queue_tracks", 1782 + "tableTo": "tracks", 1783 + "columnsFrom": [ 1784 + "track_id" 1785 + ], 1786 + "columnsTo": [ 1787 + "xata_id" 1788 + ], 1789 + "onDelete": "no action", 1790 + "onUpdate": "no action" 1791 + } 1792 + }, 1793 + "compositePrimaryKeys": {}, 1794 + "uniqueConstraints": {}, 1795 + "policies": {}, 1796 + "checkConstraints": {}, 1797 + "isRLSEnabled": false 1798 + }, 1799 + "public.scrobbles": { 1800 + "name": "scrobbles", 1801 + "schema": "", 1802 + "columns": { 1803 + "xata_id": { 1804 + "name": "xata_id", 1805 + "type": "text", 1806 + "primaryKey": true, 1807 + "notNull": true, 1808 + "default": "xata_id()" 1809 + }, 1810 + "user_id": { 1811 + "name": "user_id", 1812 + "type": "text", 1813 + "primaryKey": false, 1814 + "notNull": false 1815 + }, 1816 + "track_id": { 1817 + "name": "track_id", 1818 + "type": "text", 1819 + "primaryKey": false, 1820 + "notNull": false 1821 + }, 1822 + "album_id": { 1823 + "name": "album_id", 1824 + "type": "text", 1825 + "primaryKey": false, 1826 + "notNull": false 1827 + }, 1828 + "artist_id": { 1829 + "name": "artist_id", 1830 + "type": "text", 1831 + "primaryKey": false, 1832 + "notNull": false 1833 + }, 1834 + "uri": { 1835 + "name": "uri", 1836 + "type": "text", 1837 + "primaryKey": false, 1838 + "notNull": false 1839 + }, 1840 + "xata_createdat": { 1841 + "name": "xata_createdat", 1842 + "type": "timestamp", 1843 + "primaryKey": false, 1844 + "notNull": true, 1845 + "default": "now()" 1846 + }, 1847 + "xata_updatedat": { 1848 + "name": "xata_updatedat", 1849 + "type": "timestamp", 1850 + "primaryKey": false, 1851 + "notNull": true, 1852 + "default": "now()" 1853 + }, 1854 + "xata_version": { 1855 + "name": "xata_version", 1856 + "type": "integer", 1857 + "primaryKey": false, 1858 + "notNull": false 1859 + }, 1860 + "timestamp": { 1861 + "name": "timestamp", 1862 + "type": "timestamp", 1863 + "primaryKey": false, 1864 + "notNull": true, 1865 + "default": "now()" 1866 + } 1867 + }, 1868 + "indexes": {}, 1869 + "foreignKeys": { 1870 + "scrobbles_user_id_users_xata_id_fk": { 1871 + "name": "scrobbles_user_id_users_xata_id_fk", 1872 + "tableFrom": "scrobbles", 1873 + "tableTo": "users", 1874 + "columnsFrom": [ 1875 + "user_id" 1876 + ], 1877 + "columnsTo": [ 1878 + "xata_id" 1879 + ], 1880 + "onDelete": "no action", 1881 + "onUpdate": "no action" 1882 + }, 1883 + "scrobbles_track_id_tracks_xata_id_fk": { 1884 + "name": "scrobbles_track_id_tracks_xata_id_fk", 1885 + "tableFrom": "scrobbles", 1886 + "tableTo": "tracks", 1887 + "columnsFrom": [ 1888 + "track_id" 1889 + ], 1890 + "columnsTo": [ 1891 + "xata_id" 1892 + ], 1893 + "onDelete": "no action", 1894 + "onUpdate": "no action" 1895 + }, 1896 + "scrobbles_album_id_albums_xata_id_fk": { 1897 + "name": "scrobbles_album_id_albums_xata_id_fk", 1898 + "tableFrom": "scrobbles", 1899 + "tableTo": "albums", 1900 + "columnsFrom": [ 1901 + "album_id" 1902 + ], 1903 + "columnsTo": [ 1904 + "xata_id" 1905 + ], 1906 + "onDelete": "no action", 1907 + "onUpdate": "no action" 1908 + }, 1909 + "scrobbles_artist_id_artists_xata_id_fk": { 1910 + "name": "scrobbles_artist_id_artists_xata_id_fk", 1911 + "tableFrom": "scrobbles", 1912 + "tableTo": "artists", 1913 + "columnsFrom": [ 1914 + "artist_id" 1915 + ], 1916 + "columnsTo": [ 1917 + "xata_id" 1918 + ], 1919 + "onDelete": "no action", 1920 + "onUpdate": "no action" 1921 + } 1922 + }, 1923 + "compositePrimaryKeys": {}, 1924 + "uniqueConstraints": { 1925 + "scrobbles_uri_unique": { 1926 + "name": "scrobbles_uri_unique", 1927 + "nullsNotDistinct": false, 1928 + "columns": [ 1929 + "uri" 1930 + ] 1931 + } 1932 + }, 1933 + "policies": {}, 1934 + "checkConstraints": {}, 1935 + "isRLSEnabled": false 1936 + }, 1937 + "public.shout_likes": { 1938 + "name": "shout_likes", 1939 + "schema": "", 1940 + "columns": { 1941 + "xata_id": { 1942 + "name": "xata_id", 1943 + "type": "text", 1944 + "primaryKey": true, 1945 + "notNull": true, 1946 + "default": "xata_id()" 1947 + }, 1948 + "user_id": { 1949 + "name": "user_id", 1950 + "type": "text", 1951 + "primaryKey": false, 1952 + "notNull": true 1953 + }, 1954 + "shout_id": { 1955 + "name": "shout_id", 1956 + "type": "text", 1957 + "primaryKey": false, 1958 + "notNull": true 1959 + }, 1960 + "xata_createdat": { 1961 + "name": "xata_createdat", 1962 + "type": "timestamp", 1963 + "primaryKey": false, 1964 + "notNull": true, 1965 + "default": "now()" 1966 + }, 1967 + "uri": { 1968 + "name": "uri", 1969 + "type": "text", 1970 + "primaryKey": false, 1971 + "notNull": true 1972 + } 1973 + }, 1974 + "indexes": {}, 1975 + "foreignKeys": { 1976 + "shout_likes_user_id_users_xata_id_fk": { 1977 + "name": "shout_likes_user_id_users_xata_id_fk", 1978 + "tableFrom": "shout_likes", 1979 + "tableTo": "users", 1980 + "columnsFrom": [ 1981 + "user_id" 1982 + ], 1983 + "columnsTo": [ 1984 + "xata_id" 1985 + ], 1986 + "onDelete": "no action", 1987 + "onUpdate": "no action" 1988 + }, 1989 + "shout_likes_shout_id_shouts_xata_id_fk": { 1990 + "name": "shout_likes_shout_id_shouts_xata_id_fk", 1991 + "tableFrom": "shout_likes", 1992 + "tableTo": "shouts", 1993 + "columnsFrom": [ 1994 + "shout_id" 1995 + ], 1996 + "columnsTo": [ 1997 + "xata_id" 1998 + ], 1999 + "onDelete": "no action", 2000 + "onUpdate": "no action" 2001 + } 2002 + }, 2003 + "compositePrimaryKeys": {}, 2004 + "uniqueConstraints": { 2005 + "shout_likes_uri_unique": { 2006 + "name": "shout_likes_uri_unique", 2007 + "nullsNotDistinct": false, 2008 + "columns": [ 2009 + "uri" 2010 + ] 2011 + } 2012 + }, 2013 + "policies": {}, 2014 + "checkConstraints": {}, 2015 + "isRLSEnabled": false 2016 + }, 2017 + "public.shout_reports": { 2018 + "name": "shout_reports", 2019 + "schema": "", 2020 + "columns": { 2021 + "xata_id": { 2022 + "name": "xata_id", 2023 + "type": "text", 2024 + "primaryKey": true, 2025 + "notNull": true, 2026 + "default": "xata_id()" 2027 + }, 2028 + "user_id": { 2029 + "name": "user_id", 2030 + "type": "text", 2031 + "primaryKey": false, 2032 + "notNull": true 2033 + }, 2034 + "shout_id": { 2035 + "name": "shout_id", 2036 + "type": "text", 2037 + "primaryKey": false, 2038 + "notNull": true 2039 + }, 2040 + "xata_createdat": { 2041 + "name": "xata_createdat", 2042 + "type": "timestamp", 2043 + "primaryKey": false, 2044 + "notNull": true, 2045 + "default": "now()" 2046 + } 2047 + }, 2048 + "indexes": {}, 2049 + "foreignKeys": { 2050 + "shout_reports_user_id_users_xata_id_fk": { 2051 + "name": "shout_reports_user_id_users_xata_id_fk", 2052 + "tableFrom": "shout_reports", 2053 + "tableTo": "users", 2054 + "columnsFrom": [ 2055 + "user_id" 2056 + ], 2057 + "columnsTo": [ 2058 + "xata_id" 2059 + ], 2060 + "onDelete": "no action", 2061 + "onUpdate": "no action" 2062 + }, 2063 + "shout_reports_shout_id_shouts_xata_id_fk": { 2064 + "name": "shout_reports_shout_id_shouts_xata_id_fk", 2065 + "tableFrom": "shout_reports", 2066 + "tableTo": "shouts", 2067 + "columnsFrom": [ 2068 + "shout_id" 2069 + ], 2070 + "columnsTo": [ 2071 + "xata_id" 2072 + ], 2073 + "onDelete": "no action", 2074 + "onUpdate": "no action" 2075 + } 2076 + }, 2077 + "compositePrimaryKeys": {}, 2078 + "uniqueConstraints": {}, 2079 + "policies": {}, 2080 + "checkConstraints": {}, 2081 + "isRLSEnabled": false 2082 + }, 2083 + "public.shouts": { 2084 + "name": "shouts", 2085 + "schema": "", 2086 + "columns": { 2087 + "xata_id": { 2088 + "name": "xata_id", 2089 + "type": "text", 2090 + "primaryKey": true, 2091 + "notNull": true, 2092 + "default": "xata_id()" 2093 + }, 2094 + "content": { 2095 + "name": "content", 2096 + "type": "text", 2097 + "primaryKey": false, 2098 + "notNull": true 2099 + }, 2100 + "track_id": { 2101 + "name": "track_id", 2102 + "type": "text", 2103 + "primaryKey": false, 2104 + "notNull": false 2105 + }, 2106 + "artist_id": { 2107 + "name": "artist_id", 2108 + "type": "text", 2109 + "primaryKey": false, 2110 + "notNull": false 2111 + }, 2112 + "album_id": { 2113 + "name": "album_id", 2114 + "type": "text", 2115 + "primaryKey": false, 2116 + "notNull": false 2117 + }, 2118 + "scrobble_id": { 2119 + "name": "scrobble_id", 2120 + "type": "text", 2121 + "primaryKey": false, 2122 + "notNull": false 2123 + }, 2124 + "uri": { 2125 + "name": "uri", 2126 + "type": "text", 2127 + "primaryKey": false, 2128 + "notNull": true 2129 + }, 2130 + "author_id": { 2131 + "name": "author_id", 2132 + "type": "text", 2133 + "primaryKey": false, 2134 + "notNull": true 2135 + }, 2136 + "parent_id": { 2137 + "name": "parent_id", 2138 + "type": "text", 2139 + "primaryKey": false, 2140 + "notNull": false 2141 + }, 2142 + "xata_createdat": { 2143 + "name": "xata_createdat", 2144 + "type": "timestamp", 2145 + "primaryKey": false, 2146 + "notNull": true, 2147 + "default": "now()" 2148 + }, 2149 + "xata_updatedat": { 2150 + "name": "xata_updatedat", 2151 + "type": "timestamp", 2152 + "primaryKey": false, 2153 + "notNull": true, 2154 + "default": "now()" 2155 + } 2156 + }, 2157 + "indexes": {}, 2158 + "foreignKeys": { 2159 + "shouts_track_id_tracks_xata_id_fk": { 2160 + "name": "shouts_track_id_tracks_xata_id_fk", 2161 + "tableFrom": "shouts", 2162 + "tableTo": "tracks", 2163 + "columnsFrom": [ 2164 + "track_id" 2165 + ], 2166 + "columnsTo": [ 2167 + "xata_id" 2168 + ], 2169 + "onDelete": "no action", 2170 + "onUpdate": "no action" 2171 + }, 2172 + "shouts_artist_id_users_xata_id_fk": { 2173 + "name": "shouts_artist_id_users_xata_id_fk", 2174 + "tableFrom": "shouts", 2175 + "tableTo": "users", 2176 + "columnsFrom": [ 2177 + "artist_id" 2178 + ], 2179 + "columnsTo": [ 2180 + "xata_id" 2181 + ], 2182 + "onDelete": "no action", 2183 + "onUpdate": "no action" 2184 + }, 2185 + "shouts_album_id_albums_xata_id_fk": { 2186 + "name": "shouts_album_id_albums_xata_id_fk", 2187 + "tableFrom": "shouts", 2188 + "tableTo": "albums", 2189 + "columnsFrom": [ 2190 + "album_id" 2191 + ], 2192 + "columnsTo": [ 2193 + "xata_id" 2194 + ], 2195 + "onDelete": "no action", 2196 + "onUpdate": "no action" 2197 + }, 2198 + "shouts_scrobble_id_scrobbles_xata_id_fk": { 2199 + "name": "shouts_scrobble_id_scrobbles_xata_id_fk", 2200 + "tableFrom": "shouts", 2201 + "tableTo": "scrobbles", 2202 + "columnsFrom": [ 2203 + "scrobble_id" 2204 + ], 2205 + "columnsTo": [ 2206 + "xata_id" 2207 + ], 2208 + "onDelete": "no action", 2209 + "onUpdate": "no action" 2210 + }, 2211 + "shouts_author_id_users_xata_id_fk": { 2212 + "name": "shouts_author_id_users_xata_id_fk", 2213 + "tableFrom": "shouts", 2214 + "tableTo": "users", 2215 + "columnsFrom": [ 2216 + "author_id" 2217 + ], 2218 + "columnsTo": [ 2219 + "xata_id" 2220 + ], 2221 + "onDelete": "no action", 2222 + "onUpdate": "no action" 2223 + }, 2224 + "shouts_parent_id_shouts_xata_id_fk": { 2225 + "name": "shouts_parent_id_shouts_xata_id_fk", 2226 + "tableFrom": "shouts", 2227 + "tableTo": "shouts", 2228 + "columnsFrom": [ 2229 + "parent_id" 2230 + ], 2231 + "columnsTo": [ 2232 + "xata_id" 2233 + ], 2234 + "onDelete": "no action", 2235 + "onUpdate": "no action" 2236 + } 2237 + }, 2238 + "compositePrimaryKeys": {}, 2239 + "uniqueConstraints": { 2240 + "shouts_uri_unique": { 2241 + "name": "shouts_uri_unique", 2242 + "nullsNotDistinct": false, 2243 + "columns": [ 2244 + "uri" 2245 + ] 2246 + } 2247 + }, 2248 + "policies": {}, 2249 + "checkConstraints": {}, 2250 + "isRLSEnabled": false 2251 + }, 2252 + "public.spotify_accounts": { 2253 + "name": "spotify_accounts", 2254 + "schema": "", 2255 + "columns": { 2256 + "xata_id": { 2257 + "name": "xata_id", 2258 + "type": "text", 2259 + "primaryKey": true, 2260 + "notNull": true, 2261 + "default": "xata_id()" 2262 + }, 2263 + "xata_version": { 2264 + "name": "xata_version", 2265 + "type": "integer", 2266 + "primaryKey": false, 2267 + "notNull": false 2268 + }, 2269 + "email": { 2270 + "name": "email", 2271 + "type": "text", 2272 + "primaryKey": false, 2273 + "notNull": true 2274 + }, 2275 + "user_id": { 2276 + "name": "user_id", 2277 + "type": "text", 2278 + "primaryKey": false, 2279 + "notNull": true 2280 + }, 2281 + "is_beta_user": { 2282 + "name": "is_beta_user", 2283 + "type": "boolean", 2284 + "primaryKey": false, 2285 + "notNull": true, 2286 + "default": false 2287 + }, 2288 + "spotify_app_id": { 2289 + "name": "spotify_app_id", 2290 + "type": "text", 2291 + "primaryKey": false, 2292 + "notNull": false 2293 + }, 2294 + "xata_createdat": { 2295 + "name": "xata_createdat", 2296 + "type": "timestamp", 2297 + "primaryKey": false, 2298 + "notNull": true, 2299 + "default": "now()" 2300 + }, 2301 + "xata_updatedat": { 2302 + "name": "xata_updatedat", 2303 + "type": "timestamp", 2304 + "primaryKey": false, 2305 + "notNull": true, 2306 + "default": "now()" 2307 + } 2308 + }, 2309 + "indexes": {}, 2310 + "foreignKeys": { 2311 + "spotify_accounts_user_id_users_xata_id_fk": { 2312 + "name": "spotify_accounts_user_id_users_xata_id_fk", 2313 + "tableFrom": "spotify_accounts", 2314 + "tableTo": "users", 2315 + "columnsFrom": [ 2316 + "user_id" 2317 + ], 2318 + "columnsTo": [ 2319 + "xata_id" 2320 + ], 2321 + "onDelete": "no action", 2322 + "onUpdate": "no action" 2323 + } 2324 + }, 2325 + "compositePrimaryKeys": {}, 2326 + "uniqueConstraints": {}, 2327 + "policies": {}, 2328 + "checkConstraints": {}, 2329 + "isRLSEnabled": false 2330 + }, 2331 + "public.spotify_apps": { 2332 + "name": "spotify_apps", 2333 + "schema": "", 2334 + "columns": { 2335 + "xata_id": { 2336 + "name": "xata_id", 2337 + "type": "text", 2338 + "primaryKey": true, 2339 + "notNull": true, 2340 + "default": "xata_id()" 2341 + }, 2342 + "xata_version": { 2343 + "name": "xata_version", 2344 + "type": "integer", 2345 + "primaryKey": false, 2346 + "notNull": false 2347 + }, 2348 + "spotify_app_id": { 2349 + "name": "spotify_app_id", 2350 + "type": "text", 2351 + "primaryKey": false, 2352 + "notNull": true 2353 + }, 2354 + "spotify_secret": { 2355 + "name": "spotify_secret", 2356 + "type": "text", 2357 + "primaryKey": false, 2358 + "notNull": true 2359 + }, 2360 + "xata_createdat": { 2361 + "name": "xata_createdat", 2362 + "type": "timestamp", 2363 + "primaryKey": false, 2364 + "notNull": true, 2365 + "default": "now()" 2366 + }, 2367 + "xata_updatedat": { 2368 + "name": "xata_updatedat", 2369 + "type": "timestamp", 2370 + "primaryKey": false, 2371 + "notNull": true, 2372 + "default": "now()" 2373 + } 2374 + }, 2375 + "indexes": {}, 2376 + "foreignKeys": {}, 2377 + "compositePrimaryKeys": {}, 2378 + "uniqueConstraints": {}, 2379 + "policies": {}, 2380 + "checkConstraints": {}, 2381 + "isRLSEnabled": false 2382 + }, 2383 + "public.spotify_tokens": { 2384 + "name": "spotify_tokens", 2385 + "schema": "", 2386 + "columns": { 2387 + "xata_id": { 2388 + "name": "xata_id", 2389 + "type": "text", 2390 + "primaryKey": true, 2391 + "notNull": true, 2392 + "default": "xata_id()" 2393 + }, 2394 + "xata_version": { 2395 + "name": "xata_version", 2396 + "type": "integer", 2397 + "primaryKey": false, 2398 + "notNull": false 2399 + }, 2400 + "access_token": { 2401 + "name": "access_token", 2402 + "type": "text", 2403 + "primaryKey": false, 2404 + "notNull": true 2405 + }, 2406 + "refresh_token": { 2407 + "name": "refresh_token", 2408 + "type": "text", 2409 + "primaryKey": false, 2410 + "notNull": true 2411 + }, 2412 + "user_id": { 2413 + "name": "user_id", 2414 + "type": "text", 2415 + "primaryKey": false, 2416 + "notNull": true 2417 + }, 2418 + "spotify_app_id": { 2419 + "name": "spotify_app_id", 2420 + "type": "text", 2421 + "primaryKey": false, 2422 + "notNull": true 2423 + }, 2424 + "xata_createdat": { 2425 + "name": "xata_createdat", 2426 + "type": "timestamp", 2427 + "primaryKey": false, 2428 + "notNull": true, 2429 + "default": "now()" 2430 + }, 2431 + "xata_updatedat": { 2432 + "name": "xata_updatedat", 2433 + "type": "timestamp", 2434 + "primaryKey": false, 2435 + "notNull": true, 2436 + "default": "now()" 2437 + } 2438 + }, 2439 + "indexes": {}, 2440 + "foreignKeys": { 2441 + "spotify_tokens_user_id_users_xata_id_fk": { 2442 + "name": "spotify_tokens_user_id_users_xata_id_fk", 2443 + "tableFrom": "spotify_tokens", 2444 + "tableTo": "users", 2445 + "columnsFrom": [ 2446 + "user_id" 2447 + ], 2448 + "columnsTo": [ 2449 + "xata_id" 2450 + ], 2451 + "onDelete": "no action", 2452 + "onUpdate": "no action" 2453 + } 2454 + }, 2455 + "compositePrimaryKeys": {}, 2456 + "uniqueConstraints": {}, 2457 + "policies": {}, 2458 + "checkConstraints": {}, 2459 + "isRLSEnabled": false 2460 + }, 2461 + "public.tracks": { 2462 + "name": "tracks", 2463 + "schema": "", 2464 + "columns": { 2465 + "xata_id": { 2466 + "name": "xata_id", 2467 + "type": "text", 2468 + "primaryKey": true, 2469 + "notNull": true, 2470 + "default": "xata_id()" 2471 + }, 2472 + "title": { 2473 + "name": "title", 2474 + "type": "text", 2475 + "primaryKey": false, 2476 + "notNull": true 2477 + }, 2478 + "artist": { 2479 + "name": "artist", 2480 + "type": "text", 2481 + "primaryKey": false, 2482 + "notNull": true 2483 + }, 2484 + "album_artist": { 2485 + "name": "album_artist", 2486 + "type": "text", 2487 + "primaryKey": false, 2488 + "notNull": true 2489 + }, 2490 + "album_art": { 2491 + "name": "album_art", 2492 + "type": "text", 2493 + "primaryKey": false, 2494 + "notNull": false 2495 + }, 2496 + "album": { 2497 + "name": "album", 2498 + "type": "text", 2499 + "primaryKey": false, 2500 + "notNull": true 2501 + }, 2502 + "track_number": { 2503 + "name": "track_number", 2504 + "type": "integer", 2505 + "primaryKey": false, 2506 + "notNull": false 2507 + }, 2508 + "duration": { 2509 + "name": "duration", 2510 + "type": "integer", 2511 + "primaryKey": false, 2512 + "notNull": true 2513 + }, 2514 + "mb_id": { 2515 + "name": "mb_id", 2516 + "type": "text", 2517 + "primaryKey": false, 2518 + "notNull": false 2519 + }, 2520 + "youtube_link": { 2521 + "name": "youtube_link", 2522 + "type": "text", 2523 + "primaryKey": false, 2524 + "notNull": false 2525 + }, 2526 + "spotify_link": { 2527 + "name": "spotify_link", 2528 + "type": "text", 2529 + "primaryKey": false, 2530 + "notNull": false 2531 + }, 2532 + "apple_music_link": { 2533 + "name": "apple_music_link", 2534 + "type": "text", 2535 + "primaryKey": false, 2536 + "notNull": false 2537 + }, 2538 + "tidal_link": { 2539 + "name": "tidal_link", 2540 + "type": "text", 2541 + "primaryKey": false, 2542 + "notNull": false 2543 + }, 2544 + "sha256": { 2545 + "name": "sha256", 2546 + "type": "text", 2547 + "primaryKey": false, 2548 + "notNull": true 2549 + }, 2550 + "disc_number": { 2551 + "name": "disc_number", 2552 + "type": "integer", 2553 + "primaryKey": false, 2554 + "notNull": false 2555 + }, 2556 + "lyrics": { 2557 + "name": "lyrics", 2558 + "type": "text", 2559 + "primaryKey": false, 2560 + "notNull": false 2561 + }, 2562 + "composer": { 2563 + "name": "composer", 2564 + "type": "text", 2565 + "primaryKey": false, 2566 + "notNull": false 2567 + }, 2568 + "genre": { 2569 + "name": "genre", 2570 + "type": "text", 2571 + "primaryKey": false, 2572 + "notNull": false 2573 + }, 2574 + "label": { 2575 + "name": "label", 2576 + "type": "text", 2577 + "primaryKey": false, 2578 + "notNull": false 2579 + }, 2580 + "copyright_message": { 2581 + "name": "copyright_message", 2582 + "type": "text", 2583 + "primaryKey": false, 2584 + "notNull": false 2585 + }, 2586 + "uri": { 2587 + "name": "uri", 2588 + "type": "text", 2589 + "primaryKey": false, 2590 + "notNull": false 2591 + }, 2592 + "album_uri": { 2593 + "name": "album_uri", 2594 + "type": "text", 2595 + "primaryKey": false, 2596 + "notNull": false 2597 + }, 2598 + "artist_uri": { 2599 + "name": "artist_uri", 2600 + "type": "text", 2601 + "primaryKey": false, 2602 + "notNull": false 2603 + }, 2604 + "xata_createdat": { 2605 + "name": "xata_createdat", 2606 + "type": "timestamp", 2607 + "primaryKey": false, 2608 + "notNull": true, 2609 + "default": "now()" 2610 + }, 2611 + "xata_updatedat": { 2612 + "name": "xata_updatedat", 2613 + "type": "timestamp", 2614 + "primaryKey": false, 2615 + "notNull": true, 2616 + "default": "now()" 2617 + }, 2618 + "xata_version": { 2619 + "name": "xata_version", 2620 + "type": "integer", 2621 + "primaryKey": false, 2622 + "notNull": false 2623 + } 2624 + }, 2625 + "indexes": {}, 2626 + "foreignKeys": {}, 2627 + "compositePrimaryKeys": {}, 2628 + "uniqueConstraints": { 2629 + "tracks_mb_id_unique": { 2630 + "name": "tracks_mb_id_unique", 2631 + "nullsNotDistinct": false, 2632 + "columns": [ 2633 + "mb_id" 2634 + ] 2635 + }, 2636 + "tracks_youtube_link_unique": { 2637 + "name": "tracks_youtube_link_unique", 2638 + "nullsNotDistinct": false, 2639 + "columns": [ 2640 + "youtube_link" 2641 + ] 2642 + }, 2643 + "tracks_spotify_link_unique": { 2644 + "name": "tracks_spotify_link_unique", 2645 + "nullsNotDistinct": false, 2646 + "columns": [ 2647 + "spotify_link" 2648 + ] 2649 + }, 2650 + "tracks_apple_music_link_unique": { 2651 + "name": "tracks_apple_music_link_unique", 2652 + "nullsNotDistinct": false, 2653 + "columns": [ 2654 + "apple_music_link" 2655 + ] 2656 + }, 2657 + "tracks_tidal_link_unique": { 2658 + "name": "tracks_tidal_link_unique", 2659 + "nullsNotDistinct": false, 2660 + "columns": [ 2661 + "tidal_link" 2662 + ] 2663 + }, 2664 + "tracks_sha256_unique": { 2665 + "name": "tracks_sha256_unique", 2666 + "nullsNotDistinct": false, 2667 + "columns": [ 2668 + "sha256" 2669 + ] 2670 + }, 2671 + "tracks_uri_unique": { 2672 + "name": "tracks_uri_unique", 2673 + "nullsNotDistinct": false, 2674 + "columns": [ 2675 + "uri" 2676 + ] 2677 + } 2678 + }, 2679 + "policies": {}, 2680 + "checkConstraints": {}, 2681 + "isRLSEnabled": false 2682 + }, 2683 + "public.user_albums": { 2684 + "name": "user_albums", 2685 + "schema": "", 2686 + "columns": { 2687 + "xata_id": { 2688 + "name": "xata_id", 2689 + "type": "text", 2690 + "primaryKey": true, 2691 + "notNull": true, 2692 + "default": "xata_id()" 2693 + }, 2694 + "user_id": { 2695 + "name": "user_id", 2696 + "type": "text", 2697 + "primaryKey": false, 2698 + "notNull": true 2699 + }, 2700 + "album_id": { 2701 + "name": "album_id", 2702 + "type": "text", 2703 + "primaryKey": false, 2704 + "notNull": true 2705 + }, 2706 + "xata_createdat": { 2707 + "name": "xata_createdat", 2708 + "type": "timestamp", 2709 + "primaryKey": false, 2710 + "notNull": true, 2711 + "default": "now()" 2712 + }, 2713 + "xata_updatedat": { 2714 + "name": "xata_updatedat", 2715 + "type": "timestamp", 2716 + "primaryKey": false, 2717 + "notNull": true, 2718 + "default": "now()" 2719 + }, 2720 + "xata_version": { 2721 + "name": "xata_version", 2722 + "type": "integer", 2723 + "primaryKey": false, 2724 + "notNull": false 2725 + }, 2726 + "scrobbles": { 2727 + "name": "scrobbles", 2728 + "type": "integer", 2729 + "primaryKey": false, 2730 + "notNull": false 2731 + }, 2732 + "uri": { 2733 + "name": "uri", 2734 + "type": "text", 2735 + "primaryKey": false, 2736 + "notNull": true 2737 + } 2738 + }, 2739 + "indexes": {}, 2740 + "foreignKeys": { 2741 + "user_albums_user_id_users_xata_id_fk": { 2742 + "name": "user_albums_user_id_users_xata_id_fk", 2743 + "tableFrom": "user_albums", 2744 + "tableTo": "users", 2745 + "columnsFrom": [ 2746 + "user_id" 2747 + ], 2748 + "columnsTo": [ 2749 + "xata_id" 2750 + ], 2751 + "onDelete": "no action", 2752 + "onUpdate": "no action" 2753 + }, 2754 + "user_albums_album_id_albums_xata_id_fk": { 2755 + "name": "user_albums_album_id_albums_xata_id_fk", 2756 + "tableFrom": "user_albums", 2757 + "tableTo": "albums", 2758 + "columnsFrom": [ 2759 + "album_id" 2760 + ], 2761 + "columnsTo": [ 2762 + "xata_id" 2763 + ], 2764 + "onDelete": "no action", 2765 + "onUpdate": "no action" 2766 + } 2767 + }, 2768 + "compositePrimaryKeys": {}, 2769 + "uniqueConstraints": { 2770 + "user_albums_uri_unique": { 2771 + "name": "user_albums_uri_unique", 2772 + "nullsNotDistinct": false, 2773 + "columns": [ 2774 + "uri" 2775 + ] 2776 + } 2777 + }, 2778 + "policies": {}, 2779 + "checkConstraints": {}, 2780 + "isRLSEnabled": false 2781 + }, 2782 + "public.user_artists": { 2783 + "name": "user_artists", 2784 + "schema": "", 2785 + "columns": { 2786 + "xata_id": { 2787 + "name": "xata_id", 2788 + "type": "text", 2789 + "primaryKey": true, 2790 + "notNull": true, 2791 + "default": "xata_id()" 2792 + }, 2793 + "user_id": { 2794 + "name": "user_id", 2795 + "type": "text", 2796 + "primaryKey": false, 2797 + "notNull": true 2798 + }, 2799 + "artist_id": { 2800 + "name": "artist_id", 2801 + "type": "text", 2802 + "primaryKey": false, 2803 + "notNull": true 2804 + }, 2805 + "xata_createdat": { 2806 + "name": "xata_createdat", 2807 + "type": "timestamp", 2808 + "primaryKey": false, 2809 + "notNull": true, 2810 + "default": "now()" 2811 + }, 2812 + "xata_updatedat": { 2813 + "name": "xata_updatedat", 2814 + "type": "timestamp", 2815 + "primaryKey": false, 2816 + "notNull": true, 2817 + "default": "now()" 2818 + }, 2819 + "xata_version": { 2820 + "name": "xata_version", 2821 + "type": "integer", 2822 + "primaryKey": false, 2823 + "notNull": false 2824 + }, 2825 + "scrobbles": { 2826 + "name": "scrobbles", 2827 + "type": "integer", 2828 + "primaryKey": false, 2829 + "notNull": false 2830 + }, 2831 + "uri": { 2832 + "name": "uri", 2833 + "type": "text", 2834 + "primaryKey": false, 2835 + "notNull": true 2836 + } 2837 + }, 2838 + "indexes": {}, 2839 + "foreignKeys": { 2840 + "user_artists_user_id_users_xata_id_fk": { 2841 + "name": "user_artists_user_id_users_xata_id_fk", 2842 + "tableFrom": "user_artists", 2843 + "tableTo": "users", 2844 + "columnsFrom": [ 2845 + "user_id" 2846 + ], 2847 + "columnsTo": [ 2848 + "xata_id" 2849 + ], 2850 + "onDelete": "no action", 2851 + "onUpdate": "no action" 2852 + }, 2853 + "user_artists_artist_id_artists_xata_id_fk": { 2854 + "name": "user_artists_artist_id_artists_xata_id_fk", 2855 + "tableFrom": "user_artists", 2856 + "tableTo": "artists", 2857 + "columnsFrom": [ 2858 + "artist_id" 2859 + ], 2860 + "columnsTo": [ 2861 + "xata_id" 2862 + ], 2863 + "onDelete": "no action", 2864 + "onUpdate": "no action" 2865 + } 2866 + }, 2867 + "compositePrimaryKeys": {}, 2868 + "uniqueConstraints": { 2869 + "user_artists_uri_unique": { 2870 + "name": "user_artists_uri_unique", 2871 + "nullsNotDistinct": false, 2872 + "columns": [ 2873 + "uri" 2874 + ] 2875 + } 2876 + }, 2877 + "policies": {}, 2878 + "checkConstraints": {}, 2879 + "isRLSEnabled": false 2880 + }, 2881 + "public.user_playlists": { 2882 + "name": "user_playlists", 2883 + "schema": "", 2884 + "columns": { 2885 + "xata_id": { 2886 + "name": "xata_id", 2887 + "type": "text", 2888 + "primaryKey": true, 2889 + "notNull": true, 2890 + "default": "xata_id()" 2891 + }, 2892 + "user_id": { 2893 + "name": "user_id", 2894 + "type": "text", 2895 + "primaryKey": false, 2896 + "notNull": true 2897 + }, 2898 + "playlist_id": { 2899 + "name": "playlist_id", 2900 + "type": "text", 2901 + "primaryKey": false, 2902 + "notNull": true 2903 + }, 2904 + "xata_createdat": { 2905 + "name": "xata_createdat", 2906 + "type": "timestamp", 2907 + "primaryKey": false, 2908 + "notNull": true, 2909 + "default": "now()" 2910 + }, 2911 + "uri": { 2912 + "name": "uri", 2913 + "type": "text", 2914 + "primaryKey": false, 2915 + "notNull": false 2916 + } 2917 + }, 2918 + "indexes": {}, 2919 + "foreignKeys": { 2920 + "user_playlists_user_id_users_xata_id_fk": { 2921 + "name": "user_playlists_user_id_users_xata_id_fk", 2922 + "tableFrom": "user_playlists", 2923 + "tableTo": "users", 2924 + "columnsFrom": [ 2925 + "user_id" 2926 + ], 2927 + "columnsTo": [ 2928 + "xata_id" 2929 + ], 2930 + "onDelete": "no action", 2931 + "onUpdate": "no action" 2932 + }, 2933 + "user_playlists_playlist_id_playlists_xata_id_fk": { 2934 + "name": "user_playlists_playlist_id_playlists_xata_id_fk", 2935 + "tableFrom": "user_playlists", 2936 + "tableTo": "playlists", 2937 + "columnsFrom": [ 2938 + "playlist_id" 2939 + ], 2940 + "columnsTo": [ 2941 + "xata_id" 2942 + ], 2943 + "onDelete": "no action", 2944 + "onUpdate": "no action" 2945 + } 2946 + }, 2947 + "compositePrimaryKeys": {}, 2948 + "uniqueConstraints": { 2949 + "user_playlists_uri_unique": { 2950 + "name": "user_playlists_uri_unique", 2951 + "nullsNotDistinct": false, 2952 + "columns": [ 2953 + "uri" 2954 + ] 2955 + } 2956 + }, 2957 + "policies": {}, 2958 + "checkConstraints": {}, 2959 + "isRLSEnabled": false 2960 + }, 2961 + "public.user_tracks": { 2962 + "name": "user_tracks", 2963 + "schema": "", 2964 + "columns": { 2965 + "xata_id": { 2966 + "name": "xata_id", 2967 + "type": "text", 2968 + "primaryKey": true, 2969 + "notNull": true, 2970 + "default": "xata_id()" 2971 + }, 2972 + "user_id": { 2973 + "name": "user_id", 2974 + "type": "text", 2975 + "primaryKey": false, 2976 + "notNull": true 2977 + }, 2978 + "track_id": { 2979 + "name": "track_id", 2980 + "type": "text", 2981 + "primaryKey": false, 2982 + "notNull": true 2983 + }, 2984 + "xata_createdat": { 2985 + "name": "xata_createdat", 2986 + "type": "timestamp", 2987 + "primaryKey": false, 2988 + "notNull": true, 2989 + "default": "now()" 2990 + }, 2991 + "xata_updatedat": { 2992 + "name": "xata_updatedat", 2993 + "type": "timestamp", 2994 + "primaryKey": false, 2995 + "notNull": true, 2996 + "default": "now()" 2997 + }, 2998 + "xata_version": { 2999 + "name": "xata_version", 3000 + "type": "integer", 3001 + "primaryKey": false, 3002 + "notNull": false 3003 + }, 3004 + "uri": { 3005 + "name": "uri", 3006 + "type": "text", 3007 + "primaryKey": false, 3008 + "notNull": true 3009 + }, 3010 + "scrobbles": { 3011 + "name": "scrobbles", 3012 + "type": "integer", 3013 + "primaryKey": false, 3014 + "notNull": false 3015 + } 3016 + }, 3017 + "indexes": {}, 3018 + "foreignKeys": { 3019 + "user_tracks_user_id_users_xata_id_fk": { 3020 + "name": "user_tracks_user_id_users_xata_id_fk", 3021 + "tableFrom": "user_tracks", 3022 + "tableTo": "users", 3023 + "columnsFrom": [ 3024 + "user_id" 3025 + ], 3026 + "columnsTo": [ 3027 + "xata_id" 3028 + ], 3029 + "onDelete": "no action", 3030 + "onUpdate": "no action" 3031 + }, 3032 + "user_tracks_track_id_tracks_xata_id_fk": { 3033 + "name": "user_tracks_track_id_tracks_xata_id_fk", 3034 + "tableFrom": "user_tracks", 3035 + "tableTo": "tracks", 3036 + "columnsFrom": [ 3037 + "track_id" 3038 + ], 3039 + "columnsTo": [ 3040 + "xata_id" 3041 + ], 3042 + "onDelete": "no action", 3043 + "onUpdate": "no action" 3044 + } 3045 + }, 3046 + "compositePrimaryKeys": {}, 3047 + "uniqueConstraints": { 3048 + "user_tracks_uri_unique": { 3049 + "name": "user_tracks_uri_unique", 3050 + "nullsNotDistinct": false, 3051 + "columns": [ 3052 + "uri" 3053 + ] 3054 + } 3055 + }, 3056 + "policies": {}, 3057 + "checkConstraints": {}, 3058 + "isRLSEnabled": false 3059 + }, 3060 + "public.users": { 3061 + "name": "users", 3062 + "schema": "", 3063 + "columns": { 3064 + "xata_id": { 3065 + "name": "xata_id", 3066 + "type": "text", 3067 + "primaryKey": true, 3068 + "notNull": true, 3069 + "default": "xata_id()" 3070 + }, 3071 + "did": { 3072 + "name": "did", 3073 + "type": "text", 3074 + "primaryKey": false, 3075 + "notNull": true 3076 + }, 3077 + "display_name": { 3078 + "name": "display_name", 3079 + "type": "text", 3080 + "primaryKey": false, 3081 + "notNull": false 3082 + }, 3083 + "handle": { 3084 + "name": "handle", 3085 + "type": "text", 3086 + "primaryKey": false, 3087 + "notNull": true 3088 + }, 3089 + "avatar": { 3090 + "name": "avatar", 3091 + "type": "text", 3092 + "primaryKey": false, 3093 + "notNull": true 3094 + }, 3095 + "xata_createdat": { 3096 + "name": "xata_createdat", 3097 + "type": "timestamp", 3098 + "primaryKey": false, 3099 + "notNull": true, 3100 + "default": "now()" 3101 + }, 3102 + "xata_updatedat": { 3103 + "name": "xata_updatedat", 3104 + "type": "timestamp", 3105 + "primaryKey": false, 3106 + "notNull": true, 3107 + "default": "now()" 3108 + }, 3109 + "xata_version": { 3110 + "name": "xata_version", 3111 + "type": "integer", 3112 + "primaryKey": false, 3113 + "notNull": false 3114 + } 3115 + }, 3116 + "indexes": {}, 3117 + "foreignKeys": {}, 3118 + "compositePrimaryKeys": {}, 3119 + "uniqueConstraints": { 3120 + "users_did_unique": { 3121 + "name": "users_did_unique", 3122 + "nullsNotDistinct": false, 3123 + "columns": [ 3124 + "did" 3125 + ] 3126 + }, 3127 + "users_handle_unique": { 3128 + "name": "users_handle_unique", 3129 + "nullsNotDistinct": false, 3130 + "columns": [ 3131 + "handle" 3132 + ] 3133 + } 3134 + }, 3135 + "policies": {}, 3136 + "checkConstraints": {}, 3137 + "isRLSEnabled": false 3138 + }, 3139 + "public.webscrobblers": { 3140 + "name": "webscrobblers", 3141 + "schema": "", 3142 + "columns": { 3143 + "xata_id": { 3144 + "name": "xata_id", 3145 + "type": "text", 3146 + "primaryKey": true, 3147 + "notNull": true, 3148 + "default": "xata_id()" 3149 + }, 3150 + "name": { 3151 + "name": "name", 3152 + "type": "text", 3153 + "primaryKey": false, 3154 + "notNull": true 3155 + }, 3156 + "uuid": { 3157 + "name": "uuid", 3158 + "type": "text", 3159 + "primaryKey": false, 3160 + "notNull": true 3161 + }, 3162 + "description": { 3163 + "name": "description", 3164 + "type": "text", 3165 + "primaryKey": false, 3166 + "notNull": false 3167 + }, 3168 + "enabled": { 3169 + "name": "enabled", 3170 + "type": "boolean", 3171 + "primaryKey": false, 3172 + "notNull": true, 3173 + "default": true 3174 + }, 3175 + "user_id": { 3176 + "name": "user_id", 3177 + "type": "text", 3178 + "primaryKey": false, 3179 + "notNull": true 3180 + }, 3181 + "xata_createdat": { 3182 + "name": "xata_createdat", 3183 + "type": "timestamp", 3184 + "primaryKey": false, 3185 + "notNull": true, 3186 + "default": "now()" 3187 + }, 3188 + "xata_updatedat": { 3189 + "name": "xata_updatedat", 3190 + "type": "timestamp", 3191 + "primaryKey": false, 3192 + "notNull": true, 3193 + "default": "now()" 3194 + } 3195 + }, 3196 + "indexes": {}, 3197 + "foreignKeys": { 3198 + "webscrobblers_user_id_users_xata_id_fk": { 3199 + "name": "webscrobblers_user_id_users_xata_id_fk", 3200 + "tableFrom": "webscrobblers", 3201 + "tableTo": "users", 3202 + "columnsFrom": [ 3203 + "user_id" 3204 + ], 3205 + "columnsTo": [ 3206 + "xata_id" 3207 + ], 3208 + "onDelete": "no action", 3209 + "onUpdate": "no action" 3210 + } 3211 + }, 3212 + "compositePrimaryKeys": {}, 3213 + "uniqueConstraints": {}, 3214 + "policies": {}, 3215 + "checkConstraints": {}, 3216 + "isRLSEnabled": false 3217 + } 3218 + }, 3219 + "enums": {}, 3220 + "schemas": {}, 3221 + "sequences": {}, 3222 + "roles": {}, 3223 + "policies": {}, 3224 + "views": {}, 3225 + "_meta": { 3226 + "columns": {}, 3227 + "schemas": {}, 3228 + "tables": {} 3229 + } 3230 + }
+14
apps/api/drizzle/meta/_journal.json
··· 29 29 "when": 1759819936859, 30 30 "tag": "0003_same_rocket_racer", 31 31 "breakpoints": true 32 + }, 33 + { 34 + "idx": 4, 35 + "version": "7", 36 + "when": 1761572359505, 37 + "tag": "0004_long_zzzax", 38 + "breakpoints": true 39 + }, 40 + { 41 + "idx": 5, 42 + "version": "7", 43 + "when": 1761625200550, 44 + "tag": "0005_same_hydra", 45 + "breakpoints": true 32 46 } 33 47 ] 34 48 }
+2
apps/api/src/schema/index.ts
··· 23 23 import shoutReports from "./shout-reports"; 24 24 import shouts from "./shouts"; 25 25 import spotifyAccounts from "./spotify-accounts"; 26 + import spotifyApps from "./spotify-apps"; 26 27 import spotifyTokens from "./spotify-tokens"; 27 28 import tracks from "./tracks"; 28 29 import userAlbums from "./user-albums"; ··· 54 55 lovedTracks, 55 56 spotifyAccounts, 56 57 spotifyTokens, 58 + spotifyApps, 57 59 artistTracks, 58 60 artistAlbums, 59 61 dropboxAccounts,
+4 -1
apps/api/src/schema/spotify-accounts.ts
··· 9 9 import users from "./users"; 10 10 11 11 const spotifyAccounts = pgTable("spotify_accounts", { 12 - id: text("xata_id").primaryKey().default(sql`xata_id()`), 12 + id: text("xata_id") 13 + .primaryKey() 14 + .default(sql`xata_id()`), 13 15 xataVersion: integer("xata_version"), 14 16 email: text("email").notNull(), 15 17 userId: text("user_id") 16 18 .notNull() 17 19 .references(() => users.id), 18 20 isBetaUser: boolean("is_beta_user").default(false).notNull(), 21 + spotifyAppId: text("spotify_app_id"), 19 22 createdAt: timestamp("xata_createdat").defaultNow().notNull(), 20 23 updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), 21 24 });
+18
apps/api/src/schema/spotify-apps.ts
··· 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 + import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 + 4 + const spotifyApps = pgTable("spotify_apps", { 5 + id: text("xata_id") 6 + .primaryKey() 7 + .default(sql`xata_id()`), 8 + xataVersion: integer("xata_version"), 9 + spotifyAppId: text("spotify_app_id").notNull(), 10 + spotifySecret: text("spotify_secret").notNull(), 11 + createdAt: timestamp("xata_createdat").defaultNow().notNull(), 12 + updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), 13 + }); 14 + 15 + export type SelectSpotifyApp = InferSelectModel<typeof spotifyApps>; 16 + export type InsertSpotifyApp = InferInsertModel<typeof spotifyApps>; 17 + 18 + export default spotifyApps;
+4 -1
apps/api/src/schema/spotify-tokens.ts
··· 3 3 import users from "./users"; 4 4 5 5 const spotifyTokens = pgTable("spotify_tokens", { 6 - id: text("xata_id").primaryKey().default(sql`xata_id()`), 6 + id: text("xata_id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 7 9 xataVersion: integer("xata_version"), 8 10 accessToken: text("access_token").notNull(), 9 11 refreshToken: text("refresh_token").notNull(), 10 12 userId: text("user_id") 11 13 .notNull() 12 14 .references(() => users.id), 15 + spotifyAppId: text("spotify_app_id").notNull(), 13 16 createdAt: timestamp("xata_createdat").defaultNow().notNull(), 14 17 updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), 15 18 });
+17 -8
apps/api/src/scripts/genres.ts
··· 11 11 .from(tables.spotifyTokens) 12 12 .leftJoin( 13 13 tables.spotifyAccounts, 14 - eq(tables.spotifyAccounts.userId, tables.spotifyTokens.userId), 14 + eq(tables.spotifyAccounts.userId, tables.spotifyTokens.userId) 15 + ) 16 + .leftJoin( 17 + tables.spotifyApps, 18 + eq(tables.spotifyApps.spotifyAppId, tables.spotifyTokens.spotifyAppId) 15 19 ) 16 20 .where(eq(tables.spotifyAccounts.isBetaUser, true)) 17 - .execute() 18 - .then((res) => res.map(({ spotify_tokens }) => spotify_tokens)); 21 + .execute(); 19 22 20 23 const record = 21 24 spotifyTokens[Math.floor(Math.random() * spotifyTokens.length)]; 22 - const refreshToken = decrypt(record.refreshToken, env.SPOTIFY_ENCRYPTION_KEY); 25 + const refreshToken = decrypt( 26 + record.spotify_tokens.refreshToken, 27 + env.SPOTIFY_ENCRYPTION_KEY 28 + ); 23 29 24 30 const accessToken = await fetch("https://accounts.spotify.com/api/token", { 25 31 method: "POST", ··· 29 35 body: new URLSearchParams({ 30 36 grant_type: "refresh_token", 31 37 refresh_token: refreshToken, 32 - client_id: env.SPOTIFY_CLIENT_ID, 33 - client_secret: env.SPOTIFY_CLIENT_SECRET, 38 + client_id: record.spotify_apps.spotifyAppId, 39 + client_secret: decrypt( 40 + record.spotify_apps.spotifySecret, 41 + env.SPOTIFY_ENCRYPTION_KEY 42 + ), 34 43 }), 35 44 }) 36 45 .then((res) => res.json() as Promise<{ access_token: string }>) ··· 51 60 headers: { 52 61 Authorization: `Bearer ${token}`, 53 62 }, 54 - }, 63 + } 55 64 ) 56 65 .then( 57 66 (res) => ··· 64 73 images: Array<{ url: string }>; 65 74 }>; 66 75 }; 67 - }>, 76 + }> 68 77 ) 69 78 .then(async (data) => _.get(data, "artists.items.0")); 70 79
+140 -39
apps/api/src/spotify/app.ts
··· 1 1 import { ctx } from "context"; 2 - import { and, eq, or } from "drizzle-orm"; 2 + import { and, eq, or, sql } from "drizzle-orm"; 3 3 import { Hono } from "hono"; 4 4 import jwt from "jsonwebtoken"; 5 5 import { decrypt, encrypt } from "lib/crypto"; 6 6 import { env } from "lib/env"; 7 + import _ from "lodash"; 7 8 import { requestCounter } from "metrics"; 8 9 import crypto, { createHash } from "node:crypto"; 9 10 import { rateLimiter } from "ratelimiter"; 10 11 import lovedTracks from "schema/loved-tracks"; 11 12 import spotifyAccounts from "schema/spotify-accounts"; 13 + import spotifyApps from "schema/spotify-apps"; 12 14 import spotifyTokens from "schema/spotify-tokens"; 13 15 import tracks from "schema/tracks"; 14 16 import users from "schema/users"; ··· 50 52 return c.text("Unauthorized"); 51 53 } 52 54 55 + const spotifyAccount = await ctx.db 56 + .select() 57 + .from(spotifyAccounts) 58 + .leftJoin(users, eq(spotifyAccounts.userId, users.id)) 59 + .leftJoin( 60 + spotifyApps, 61 + eq(spotifyAccounts.spotifyAppId, spotifyApps.spotifyAppId) 62 + ) 63 + .where( 64 + and( 65 + eq(spotifyAccounts.userId, user.id), 66 + eq(spotifyAccounts.isBetaUser, true) 67 + ) 68 + ) 69 + .limit(1) 70 + .then((rows) => rows[0]); 71 + 53 72 const state = crypto.randomBytes(16).toString("hex"); 54 73 ctx.kv.set(state, did); 55 - const redirectUrl = `https://accounts.spotify.com/en/authorize?client_id=${env.SPOTIFY_CLIENT_ID}&response_type=code&redirect_uri=${env.SPOTIFY_REDIRECT_URI}&scope=user-read-private%20user-read-email%20user-read-playback-state%20user-read-currently-playing%20user-modify-playback-state%20playlist-modify-public%20playlist-modify-private%20playlist-read-private%20playlist-read-collaborative&state=${state}`; 74 + const redirectUrl = `https://accounts.spotify.com/en/authorize?client_id=${spotifyAccount?.spotify_apps?.spotifyAppId}&response_type=code&redirect_uri=${env.SPOTIFY_REDIRECT_URI}&scope=user-read-private%20user-read-email%20user-read-playback-state%20user-read-currently-playing%20user-modify-playback-state%20playlist-modify-public%20playlist-modify-private%20playlist-read-private%20playlist-read-collaborative&state=${state}`; 56 75 c.header( 57 76 "Set-Cookie", 58 77 `session-id=${state}; Path=/; HttpOnly; SameSite=Strict; Secure` ··· 65 84 const params = new URLSearchParams(c.req.url.split("?")[1]); 66 85 const { code, state } = Object.fromEntries(params.entries()); 67 86 68 - const response = await fetch("https://accounts.spotify.com/api/token", { 69 - method: "POST", 70 - headers: { 71 - "Content-Type": "application/x-www-form-urlencoded", 72 - }, 73 - body: new URLSearchParams({ 74 - grant_type: "authorization_code", 75 - code, 76 - redirect_uri: env.SPOTIFY_REDIRECT_URI, 77 - client_id: env.SPOTIFY_CLIENT_ID, 78 - client_secret: env.SPOTIFY_CLIENT_SECRET, 79 - }), 80 - }); 81 - const { 82 - access_token, 83 - refresh_token, 84 - }: { 85 - access_token: string; 86 - refresh_token: string; 87 - } = await response.json(); 88 - 89 87 if (!state) { 90 88 return c.redirect(env.FRONTEND_URL); 91 89 } ··· 107 105 return c.redirect(env.FRONTEND_URL); 108 106 } 109 107 108 + const spotifyAccount = await ctx.db 109 + .select() 110 + .from(spotifyAccounts) 111 + .where( 112 + and( 113 + eq(spotifyAccounts.userId, user.id), 114 + eq(spotifyAccounts.isBetaUser, true) 115 + ) 116 + ) 117 + .limit(1) 118 + .then((rows) => rows[0]); 119 + 120 + const spotifyAppId = spotifyAccount.spotifyAppId 121 + ? spotifyAccount.spotifyAppId 122 + : env.SPOTIFY_CLIENT_ID; 123 + 124 + const spotifyAppToken = await ctx.db 125 + .select() 126 + .from(spotifyTokens) 127 + .leftJoin( 128 + spotifyApps, 129 + eq(spotifyTokens.spotifyAppId, spotifyApps.spotifyAppId) 130 + ) 131 + .where(eq(spotifyTokens.spotifyAppId, spotifyAppId)) 132 + .limit(1) 133 + .then((rows) => rows[0]); 134 + 135 + const response = await fetch("https://accounts.spotify.com/api/token", { 136 + method: "POST", 137 + headers: { 138 + "Content-Type": "application/x-www-form-urlencoded", 139 + }, 140 + body: new URLSearchParams({ 141 + grant_type: "authorization_code", 142 + code, 143 + redirect_uri: env.SPOTIFY_REDIRECT_URI, 144 + client_id: spotifyAppId, 145 + client_secret: spotifyAppToken?.spotify_apps 146 + ? decrypt( 147 + spotifyAppToken.spotify_apps.spotifySecret, 148 + env.SPOTIFY_ENCRYPTION_KEY 149 + ) 150 + : env.SPOTIFY_CLIENT_SECRET, 151 + }), 152 + }); 153 + const { 154 + access_token, 155 + refresh_token, 156 + }: { 157 + access_token: string; 158 + refresh_token: string; 159 + } = await response.json(); 160 + 110 161 const existingSpotifyToken = await ctx.db 111 162 .select() 112 163 .from(spotifyTokens) ··· 127 178 userId: user.id, 128 179 accessToken: encrypt(access_token, env.SPOTIFY_ENCRYPTION_KEY), 129 180 refreshToken: encrypt(refresh_token, env.SPOTIFY_ENCRYPTION_KEY), 181 + spotifyAppId, 130 182 }); 131 183 } 132 184 ··· 179 231 180 232 if (parsed.error) { 181 233 c.status(400); 182 - return c.text("Invalid email: " + parsed.error.message); 234 + return c.text(`Invalid email: ${parsed.error.message}`); 183 235 } 236 + 237 + const apps = await ctx.db 238 + .select({ 239 + appId: spotifyApps.id, 240 + spotifyAppId: spotifyApps.spotifyAppId, 241 + accountCount: sql<number>`COUNT(${spotifyAccounts.id})`.as( 242 + "account_count" 243 + ), 244 + }) 245 + .from(spotifyApps) 246 + .leftJoin(spotifyAccounts, eq(spotifyApps.id, spotifyAccounts.spotifyAppId)) 247 + .groupBy(spotifyApps.id) 248 + .having(sql`COUNT(${spotifyAccounts.id}) < 25`); 184 249 185 250 const { email } = parsed.data; 186 251 ··· 189 254 userId: user.id, 190 255 email, 191 256 isBetaUser: false, 257 + spotifyAppId: _.get(apps, "[0].spotifyAppId"), 192 258 }); 193 259 } catch (e) { 194 260 if (!e.message.includes("duplicate key value violates unique constraint")) { ··· 326 392 const spotifyToken = await ctx.db 327 393 .select() 328 394 .from(spotifyTokens) 395 + .leftJoin( 396 + spotifyApps, 397 + eq(spotifyTokens.spotifyAppId, spotifyApps.spotifyAppId) 398 + ) 329 399 .where(eq(spotifyTokens.userId, user.id)) 330 400 .limit(1) 331 401 .then((rows) => rows[0]); ··· 336 406 } 337 407 338 408 const refreshToken = decrypt( 339 - spotifyToken.refreshToken, 409 + spotifyToken.spotify_tokens.refreshToken, 340 410 env.SPOTIFY_ENCRYPTION_KEY 341 411 ); 342 412 ··· 349 419 body: new URLSearchParams({ 350 420 grant_type: "refresh_token", 351 421 refresh_token: refreshToken, 352 - client_id: env.SPOTIFY_CLIENT_ID, 353 - client_secret: env.SPOTIFY_CLIENT_SECRET, 422 + client_id: spotifyToken.spotify_apps.spotifyAppId, 423 + client_secret: decrypt( 424 + spotifyToken.spotify_apps.spotifySecret, 425 + env.SPOTIFY_ENCRYPTION_KEY 426 + ), 354 427 }), 355 428 }); 356 429 ··· 402 475 const spotifyToken = await ctx.db 403 476 .select() 404 477 .from(spotifyTokens) 478 + .leftJoin( 479 + spotifyApps, 480 + eq(spotifyTokens.spotifyAppId, spotifyApps.spotifyAppId) 481 + ) 405 482 .where(eq(spotifyTokens.userId, user.id)) 406 483 .limit(1) 407 484 .then((rows) => rows[0]); ··· 412 489 } 413 490 414 491 const refreshToken = decrypt( 415 - spotifyToken.refreshToken, 492 + spotifyToken.spotify_tokens.refreshToken, 416 493 env.SPOTIFY_ENCRYPTION_KEY 417 494 ); 418 495 ··· 425 502 body: new URLSearchParams({ 426 503 grant_type: "refresh_token", 427 504 refresh_token: refreshToken, 428 - client_id: env.SPOTIFY_CLIENT_ID, 429 - client_secret: env.SPOTIFY_CLIENT_SECRET, 505 + client_id: spotifyToken.spotify_apps.spotifyAppId, 506 + client_secret: decrypt( 507 + spotifyToken.spotify_apps.spotifySecret, 508 + env.SPOTIFY_ENCRYPTION_KEY 509 + ), 430 510 }), 431 511 }); 432 512 ··· 478 558 const spotifyToken = await ctx.db 479 559 .select() 480 560 .from(spotifyTokens) 561 + .leftJoin( 562 + spotifyApps, 563 + eq(spotifyTokens.spotifyAppId, spotifyApps.spotifyAppId) 564 + ) 481 565 .where(eq(spotifyTokens.userId, user.id)) 482 566 .limit(1) 483 567 .then((rows) => rows[0]); ··· 488 572 } 489 573 490 574 const refreshToken = decrypt( 491 - spotifyToken.refreshToken, 575 + spotifyToken.spotify_tokens.refreshToken, 492 576 env.SPOTIFY_ENCRYPTION_KEY 493 577 ); 494 578 ··· 501 585 body: new URLSearchParams({ 502 586 grant_type: "refresh_token", 503 587 refresh_token: refreshToken, 504 - client_id: env.SPOTIFY_CLIENT_ID, 505 - client_secret: env.SPOTIFY_CLIENT_SECRET, 588 + client_id: spotifyToken.spotify_apps.spotifyAppId, 589 + client_secret: decrypt( 590 + spotifyToken.spotify_apps.spotifySecret, 591 + env.SPOTIFY_ENCRYPTION_KEY 592 + ), 506 593 }), 507 594 }); 508 595 ··· 554 641 const spotifyToken = await ctx.db 555 642 .select() 556 643 .from(spotifyTokens) 644 + .leftJoin( 645 + spotifyApps, 646 + eq(spotifyTokens.spotifyAppId, spotifyApps.spotifyAppId) 647 + ) 557 648 .where(eq(spotifyTokens.userId, user.id)) 558 649 .limit(1) 559 650 .then((rows) => rows[0]); ··· 564 655 } 565 656 566 657 const refreshToken = decrypt( 567 - spotifyToken.refreshToken, 658 + spotifyToken.spotify_tokens.refreshToken, 568 659 env.SPOTIFY_ENCRYPTION_KEY 569 660 ); 570 661 ··· 577 668 body: new URLSearchParams({ 578 669 grant_type: "refresh_token", 579 670 refresh_token: refreshToken, 580 - client_id: env.SPOTIFY_CLIENT_ID, 581 - client_secret: env.SPOTIFY_CLIENT_SECRET, 671 + client_id: spotifyToken.spotify_apps.spotifyAppId, 672 + client_secret: decrypt( 673 + spotifyToken.spotify_apps.spotifySecret, 674 + env.SPOTIFY_ENCRYPTION_KEY 675 + ), 582 676 }), 583 677 }); 584 678 ··· 633 727 const spotifyToken = await ctx.db 634 728 .select() 635 729 .from(spotifyTokens) 730 + .leftJoin( 731 + spotifyApps, 732 + eq(spotifyTokens.spotifyAppId, spotifyApps.spotifyAppId) 733 + ) 636 734 .where(eq(spotifyTokens.userId, user.id)) 637 735 .limit(1) 638 736 .then((rows) => rows[0]); ··· 643 741 } 644 742 645 743 const refreshToken = decrypt( 646 - spotifyToken.refreshToken, 744 + spotifyToken.spotify_tokens.refreshToken, 647 745 env.SPOTIFY_ENCRYPTION_KEY 648 746 ); 649 747 ··· 656 754 body: new URLSearchParams({ 657 755 grant_type: "refresh_token", 658 756 refresh_token: refreshToken, 659 - client_id: env.SPOTIFY_CLIENT_ID, 660 - client_secret: env.SPOTIFY_CLIENT_SECRET, 757 + client_id: spotifyToken.spotify_apps.spotifyAppId, 758 + client_secret: decrypt( 759 + spotifyToken.spotify_apps.spotifySecret, 760 + env.SPOTIFY_ENCRYPTION_KEY 761 + ), 661 762 }), 662 763 }); 663 764
+25 -11
apps/api/src/xrpc/app/rocksky/spotify/next.ts
··· 23 23 Effect.catchAll((err) => { 24 24 console.error(err); 25 25 return Effect.succeed({}); 26 - }), 26 + }) 27 27 ); 28 28 server.app.rocksky.spotify.next({ 29 29 auth: ctx.authVerifier, ··· 69 69 ctx.db 70 70 .select() 71 71 .from(tables.spotifyTokens) 72 + .leftJoin( 73 + tables.spotifyApps, 74 + eq(tables.spotifyTokens.spotifyAppId, tables.spotifyApps.spotifyAppId) 75 + ) 72 76 .where(eq(tables.spotifyTokens.userId, user.id)) 73 77 .execute() 74 - .then(([spotifyToken]) => 75 - decrypt(spotifyToken.refreshToken, env.SPOTIFY_ENCRYPTION_KEY), 76 - ) 77 - .then((refreshToken) => ({ 78 - user, 79 - ctx, 78 + .then(([spotifyToken]) => [ 79 + decrypt( 80 + spotifyToken.spotify_tokens.refreshToken, 81 + env.SPOTIFY_ENCRYPTION_KEY 82 + ), 83 + decrypt( 84 + spotifyToken.spotify_apps.spotifySecret, 85 + env.SPOTIFY_ENCRYPTION_KEY 86 + ), 87 + spotifyToken.spotify_apps.spotifyAppId, 88 + ]) 89 + .then(([refreshToken, clientSecret, clientId]) => ({ 80 90 refreshToken, 91 + clientId, 92 + clientSecret, 81 93 })), 82 94 catch: (error) => 83 95 new Error(`Failed to retrieve Spotify Refresh token: ${error}`), ··· 86 98 87 99 const withSpotifyToken = ({ 88 100 refreshToken, 89 - ctx, 101 + clientId, 102 + clientSecret, 90 103 }: { 91 104 refreshToken: string; 92 - ctx: Context; 105 + clientId: string; 106 + clientSecret; 93 107 }) => { 94 108 return Effect.tryPromise({ 95 109 try: () => ··· 101 115 body: new URLSearchParams({ 102 116 grant_type: "refresh_token", 103 117 refresh_token: refreshToken, 104 - client_id: env.SPOTIFY_CLIENT_ID, 105 - client_secret: env.SPOTIFY_CLIENT_SECRET, 118 + client_id: clientId, 119 + client_secret: clientSecret, 106 120 }), 107 121 }) 108 122 .then((res) => res.json() as Promise<{ access_token: string }>)
+25 -10
apps/api/src/xrpc/app/rocksky/spotify/pause.ts
··· 23 23 Effect.catchAll((err) => { 24 24 console.error(err); 25 25 return Effect.succeed({}); 26 - }), 26 + }) 27 27 ); 28 28 server.app.rocksky.spotify.pause({ 29 29 auth: ctx.authVerifier, ··· 69 69 ctx.db 70 70 .select() 71 71 .from(tables.spotifyTokens) 72 + .leftJoin( 73 + tables.spotifyApps, 74 + eq(tables.spotifyTokens.spotifyAppId, tables.spotifyApps.spotifyAppId) 75 + ) 72 76 .where(eq(tables.spotifyTokens.userId, user.id)) 73 77 .execute() 74 - .then(([spotifyToken]) => 75 - decrypt(spotifyToken.refreshToken, env.SPOTIFY_ENCRYPTION_KEY), 76 - ) 77 - .then((refreshToken) => ({ 78 - user, 79 - ctx, 78 + .then(([spotifyToken]) => [ 79 + decrypt( 80 + spotifyToken.spotify_tokens.refreshToken, 81 + env.SPOTIFY_ENCRYPTION_KEY 82 + ), 83 + decrypt( 84 + spotifyToken.spotify_apps.spotifySecret, 85 + env.SPOTIFY_ENCRYPTION_KEY 86 + ), 87 + spotifyToken.spotify_apps.spotifyAppId, 88 + ]) 89 + .then(([refreshToken, clientSecret, clientId]) => ({ 80 90 refreshToken, 91 + clientId, 92 + clientSecret, 81 93 })), 82 94 catch: (error) => 83 95 new Error(`Failed to retrieve Spotify Refresh token: ${error}`), ··· 86 98 87 99 const withSpotifyToken = ({ 88 100 refreshToken, 101 + clientId, 102 + clientSecret, 89 103 }: { 90 104 refreshToken: string; 91 - ctx: Context; 105 + clientId: string; 106 + clientSecret; 92 107 }) => { 93 108 return Effect.tryPromise({ 94 109 try: () => ··· 100 115 body: new URLSearchParams({ 101 116 grant_type: "refresh_token", 102 117 refresh_token: refreshToken, 103 - client_id: env.SPOTIFY_CLIENT_ID, 104 - client_secret: env.SPOTIFY_CLIENT_SECRET, 118 + client_id: clientId, 119 + client_secret: clientSecret, 105 120 }), 106 121 }) 107 122 .then((res) => res.json() as Promise<{ access_token: string }>)
+25 -11
apps/api/src/xrpc/app/rocksky/spotify/play.ts
··· 23 23 Effect.catchAll((err) => { 24 24 console.error(err); 25 25 return Effect.succeed({}); 26 - }), 26 + }) 27 27 ); 28 28 server.app.rocksky.spotify.play({ 29 29 auth: ctx.authVerifier, ··· 69 69 ctx.db 70 70 .select() 71 71 .from(tables.spotifyTokens) 72 + .leftJoin( 73 + tables.spotifyApps, 74 + eq(tables.spotifyTokens.spotifyAppId, tables.spotifyApps.spotifyAppId) 75 + ) 72 76 .where(eq(tables.spotifyTokens.userId, user.id)) 73 77 .execute() 74 - .then(([spotifyToken]) => 75 - decrypt(spotifyToken.refreshToken, env.SPOTIFY_ENCRYPTION_KEY), 76 - ) 77 - .then((refreshToken) => ({ 78 - user, 79 - ctx, 78 + .then(([spotifyToken]) => [ 79 + decrypt( 80 + spotifyToken.spotify_tokens.refreshToken, 81 + env.SPOTIFY_ENCRYPTION_KEY 82 + ), 83 + decrypt( 84 + spotifyToken.spotify_apps.spotifySecret, 85 + env.SPOTIFY_ENCRYPTION_KEY 86 + ), 87 + spotifyToken.spotify_apps.spotifyAppId, 88 + ]) 89 + .then(([refreshToken, clientSecret, clientId]) => ({ 80 90 refreshToken, 91 + clientId, 92 + clientSecret, 81 93 })), 82 94 catch: (error) => 83 95 new Error(`Failed to retrieve Spotify Refresh token: ${error}`), ··· 86 98 87 99 const withSpotifyToken = ({ 88 100 refreshToken, 89 - ctx, 101 + clientId, 102 + clientSecret, 90 103 }: { 91 104 refreshToken: string; 92 - ctx: Context; 105 + clientId: string; 106 + clientSecret; 93 107 }) => { 94 108 return Effect.tryPromise({ 95 109 try: () => ··· 101 115 body: new URLSearchParams({ 102 116 grant_type: "refresh_token", 103 117 refresh_token: refreshToken, 104 - client_id: env.SPOTIFY_CLIENT_ID, 105 - client_secret: env.SPOTIFY_CLIENT_SECRET, 118 + client_id: clientId, 119 + client_secret: clientSecret, 106 120 }), 107 121 }) 108 122 .then((res) => res.json() as Promise<{ access_token: string }>)
+30 -8
apps/api/src/xrpc/app/rocksky/spotify/previous.ts
··· 23 23 Effect.catchAll((err) => { 24 24 console.error(err); 25 25 return Effect.succeed({}); 26 - }), 26 + }) 27 27 ); 28 28 server.app.rocksky.spotify.previous({ 29 29 auth: ctx.authVerifier, ··· 69 69 ctx.db 70 70 .select() 71 71 .from(tables.spotifyTokens) 72 + .leftJoin( 73 + tables.spotifyApps, 74 + eq(tables.spotifyTokens.spotifyAppId, tables.spotifyApps.spotifyAppId) 75 + ) 72 76 .where(eq(tables.spotifyTokens.userId, user.id)) 73 77 .execute() 74 - .then(([spotifyToken]) => 75 - decrypt(spotifyToken.refreshToken, env.SPOTIFY_ENCRYPTION_KEY), 76 - ) 77 - .then((refreshToken) => ({ 78 + .then(([spotifyToken]) => [ 79 + decrypt( 80 + spotifyToken.spotify_tokens.refreshToken, 81 + env.SPOTIFY_ENCRYPTION_KEY 82 + ), 83 + decrypt( 84 + spotifyToken.spotify_apps.spotifySecret, 85 + env.SPOTIFY_ENCRYPTION_KEY 86 + ), 87 + spotifyToken.spotify_apps.spotifyAppId, 88 + ]) 89 + .then(([refreshToken, clientSecret, clientId]) => ({ 78 90 refreshToken, 91 + clientId, 92 + clientSecret, 79 93 })), 80 94 catch: (error) => 81 95 new Error(`Failed to retrieve Spotify Refresh token: ${error}`), 82 96 }); 83 97 }; 84 98 85 - const withSpotifyToken = ({ refreshToken }: { refreshToken: string }) => { 99 + const withSpotifyToken = ({ 100 + refreshToken, 101 + clientId, 102 + clientSecret, 103 + }: { 104 + refreshToken: string; 105 + clientId: string; 106 + clientSecret; 107 + }) => { 86 108 return Effect.tryPromise({ 87 109 try: () => 88 110 fetch("https://accounts.spotify.com/api/token", { ··· 93 115 body: new URLSearchParams({ 94 116 grant_type: "refresh_token", 95 117 refresh_token: refreshToken, 96 - client_id: env.SPOTIFY_CLIENT_ID, 97 - client_secret: env.SPOTIFY_CLIENT_SECRET, 118 + client_id: clientId, 119 + client_secret: clientSecret, 98 120 }), 99 121 }) 100 122 .then((res) => res.json() as Promise<{ access_token: string }>)
+26 -8
apps/api/src/xrpc/app/rocksky/spotify/seek.ts
··· 23 23 Effect.catchAll((err) => { 24 24 console.error(err); 25 25 return Effect.succeed({}); 26 - }), 26 + }) 27 27 ); 28 28 server.app.rocksky.spotify.seek({ 29 29 auth: ctx.authVerifier, ··· 72 72 ctx.db 73 73 .select() 74 74 .from(tables.spotifyTokens) 75 + .leftJoin( 76 + tables.spotifyApps, 77 + eq(tables.spotifyTokens.spotifyAppId, tables.spotifyApps.spotifyAppId) 78 + ) 75 79 .where(eq(tables.spotifyTokens.userId, user.id)) 76 80 .execute() 77 - .then(([spotifyToken]) => 78 - decrypt(spotifyToken.refreshToken, env.SPOTIFY_ENCRYPTION_KEY), 79 - ) 80 - .then((refreshToken) => ({ 81 + .then(([spotifyToken]) => [ 82 + decrypt( 83 + spotifyToken.spotify_tokens.refreshToken, 84 + env.SPOTIFY_ENCRYPTION_KEY 85 + ), 86 + decrypt( 87 + spotifyToken.spotify_apps.spotifySecret, 88 + env.SPOTIFY_ENCRYPTION_KEY 89 + ), 90 + spotifyToken.spotify_apps.spotifyAppId, 91 + ]) 92 + .then(([refreshToken, clientSecret, clientId]) => ({ 81 93 refreshToken, 94 + clientId, 95 + clientSecret, 82 96 params, 83 97 })), 84 98 catch: (error) => ··· 88 102 89 103 const withSpotifyToken = ({ 90 104 refreshToken, 105 + clientSecret, 106 + clientId, 91 107 params, 92 108 }: { 93 109 refreshToken: string; 110 + clientSecret: string; 111 + clientId: string; 94 112 params: QueryParams; 95 113 }) => { 96 114 return Effect.tryPromise({ ··· 103 121 body: new URLSearchParams({ 104 122 grant_type: "refresh_token", 105 123 refresh_token: refreshToken, 106 - client_id: env.SPOTIFY_CLIENT_ID, 107 - client_secret: env.SPOTIFY_CLIENT_SECRET, 124 + client_id: clientId, 125 + client_secret: clientSecret, 108 126 }), 109 127 }) 110 128 .then((res) => res.json() as Promise<{ access_token: string }>) ··· 132 150 headers: { 133 151 Authorization: `Bearer ${accessToken}`, 134 152 }, 135 - }, 153 + } 136 154 ).then((res) => res.status), 137 155 catch: (error) => new Error(`Failed to handle next action: ${error}`), 138 156 });
+9 -1
crates/playlists/src/core.rs
··· 136 136 pool: &Pool<Postgres>, 137 137 offset: usize, 138 138 limit: usize, 139 - ) -> Result<Vec<(String, String, String, String)>, Error> { 139 + ) -> Result<Vec<(String, String, String, String, String, String)>, Error> { 140 140 let results: Vec<SpotifyTokenWithEmail> = sqlx::query_as( 141 141 r#" 142 142 SELECT * FROM spotify_tokens 143 143 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id 144 144 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id 145 + LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id 146 + WHERE spotify_accounts.is_beta_user = true 145 147 LIMIT $1 OFFSET $2 146 148 "#, 147 149 ) ··· 157 159 &result.refresh_token, 158 160 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 159 161 )?; 162 + let spotify_secret = decrypt_aes_256_ctr( 163 + &result.spotify_secret, 164 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 165 + )?; 160 166 user_tokens.push(( 161 167 result.email.clone(), 162 168 token, 163 169 result.did.clone(), 164 170 result.user_id.clone(), 171 + result.spotify_app_id.clone(), 172 + spotify_secret.clone(), 165 173 )); 166 174 } 167 175
+3 -1
crates/playlists/src/lib.rs
··· 53 53 let token = user.1.clone(); 54 54 let did = user.2.clone(); 55 55 let user_id = user.3.clone(); 56 - let playlists = get_user_playlists(token).await?; 56 + let client_id = user.4.clone(); 57 + let client_secret = user.5.clone(); 58 + let playlists = get_user_playlists(token, client_id, client_secret).await?; 57 59 save_playlists(&pool, conn.clone(), nc.clone(), playlists, &user_id, &did).await?; 58 60 } 59 61
+11 -10
crates/playlists/src/spotify.rs
··· 5 5 6 6 use crate::types::{self, token::AccessToken}; 7 7 8 - pub async fn refresh_token(token: &str) -> Result<AccessToken, Error> { 9 - if env::var("SPOTIFY_CLIENT_ID").is_err() || env::var("SPOTIFY_CLIENT_SECRET").is_err() { 10 - panic!("Please set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables"); 11 - } 12 - 13 - let client_id = env::var("SPOTIFY_CLIENT_ID")?; 14 - let client_secret = env::var("SPOTIFY_CLIENT_SECRET")?; 15 - 8 + pub async fn refresh_token( 9 + token: &str, 10 + client_id: &str, 11 + client_secret: &str, 12 + ) -> Result<AccessToken, Error> { 16 13 let client = Client::new(); 17 14 18 15 let response = client ··· 29 26 Ok(token) 30 27 } 31 28 32 - pub async fn get_user_playlists(token: String) -> Result<Vec<types::playlist::Playlist>, Error> { 33 - let token = refresh_token(&token).await?; 29 + pub async fn get_user_playlists( 30 + token: String, 31 + client_id: String, 32 + client_secret: String, 33 + ) -> Result<Vec<types::playlist::Playlist>, Error> { 34 + let token = refresh_token(&token, &client_id, &client_secret).await?; 34 35 let client = Client::new(); 35 36 let response = client 36 37 .get("https://api.spotify.com/v1/me/playlists")
+4
crates/playlists/src/types/spotify_token.rs
··· 12 12 pub user_id: String, 13 13 pub access_token: String, 14 14 pub refresh_token: String, 15 + pub spotify_app_id: String, 16 + pub spotify_secret: String, 15 17 } 16 18 17 19 #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] ··· 27 29 pub refresh_token: String, 28 30 pub email: String, 29 31 pub did: String, 32 + pub spotify_app_id: String, 33 + pub spotify_secret: String, 30 34 }
+1
crates/scrobbler/src/repo/spotify_account.rs
··· 11 11 r#" 12 12 SELECT * FROM spotify_accounts 13 13 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id 14 + LEFT JOIN spotify_apps ON spotify_accounts.spotify_app_id = spotify_apps.spotify_app_id 14 15 WHERE users.did = $1 15 16 "#, 16 17 )
+2
crates/scrobbler/src/repo/spotify_token.rs
··· 12 12 SELECT * FROM spotify_tokens 13 13 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id 14 14 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id 15 + LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id 15 16 WHERE users.did = $1 16 17 "#, 17 18 ) ··· 35 36 SELECT * FROM spotify_tokens 36 37 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id 37 38 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id 39 + LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id 38 40 LIMIT $1 39 41 "#, 40 42 )
+21 -3
crates/scrobbler/src/scrobbler.rs
··· 188 188 let mut rng = rand::rng(); 189 189 let random_index = rng.random_range(0..spofity_tokens.len()); 190 190 let spotify_token = &spofity_tokens[random_index]; 191 + let client_id = spotify_token.spotify_app_id.clone(); 192 + 193 + let client_secret = decrypt_aes_256_ctr( 194 + &spotify_token.spotify_secret, 195 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 196 + )?; 191 197 192 198 let spotify_token = decrypt_aes_256_ctr( 193 199 &spotify_token.refresh_token, 194 200 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 195 201 )?; 196 202 197 - let spotify_token = refresh_token(&spotify_token).await?; 203 + let spotify_token = refresh_token(&spotify_token, &client_id, &client_secret).await?; 198 204 let spotify_client = SpotifyClient::new(&spotify_token.access_token); 199 205 200 206 let result = spotify_client ··· 362 368 let mut rng = rand::rng(); 363 369 let random_index = rng.random_range(0..spofity_tokens.len()); 364 370 let spotify_token = &spofity_tokens[random_index]; 371 + let client_id = spotify_token.spotify_app_id.clone(); 372 + 373 + let client_secret = decrypt_aes_256_ctr( 374 + &spotify_token.spotify_secret, 375 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 376 + )?; 365 377 366 378 let spotify_token = decrypt_aes_256_ctr( 367 379 &spotify_token.refresh_token, 368 380 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 369 381 )?; 370 382 371 - let spotify_token = refresh_token(&spotify_token).await?; 383 + let spotify_token = refresh_token(&spotify_token, &client_id, &client_secret).await?; 372 384 let spotify_client = SpotifyClient::new(&spotify_token.access_token); 373 385 374 386 let result = spotify_client ··· 615 627 let random_index = rng.random_range(0..spofity_tokens.len()); 616 628 let spotify_token = &spofity_tokens[random_index]; 617 629 630 + let client_id = spotify_token.spotify_app_id.clone(); 631 + let client_secret = decrypt_aes_256_ctr( 632 + &spotify_token.spotify_secret, 633 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 634 + )?; 635 + 618 636 let spotify_token = decrypt_aes_256_ctr( 619 637 &spotify_token.refresh_token, 620 638 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 621 639 )?; 622 640 623 - let spotify_token = refresh_token(&spotify_token).await?; 641 + let spotify_token = refresh_token(&spotify_token, &client_id, &client_secret).await?; 624 642 let spotify_client = SpotifyClient::new(&spotify_token.access_token); 625 643 626 644 let result = spotify_client
+5 -10
crates/scrobbler/src/spotify/mod.rs
··· 1 - use std::env; 2 - 3 1 use anyhow::Error; 4 2 use reqwest::Client; 5 3 use types::AccessToken; ··· 7 5 pub mod client; 8 6 pub mod types; 9 7 10 - pub async fn refresh_token(token: &str) -> Result<AccessToken, Error> { 11 - if env::var("SPOTIFY_CLIENT_ID").is_err() || env::var("SPOTIFY_CLIENT_SECRET").is_err() { 12 - panic!("Please set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables"); 13 - } 14 - 15 - let client_id = env::var("SPOTIFY_CLIENT_ID")?; 16 - let client_secret = env::var("SPOTIFY_CLIENT_SECRET")?; 17 - 8 + pub async fn refresh_token( 9 + token: &str, 10 + client_id: &str, 11 + client_secret: &str, 12 + ) -> Result<AccessToken, Error> { 18 13 let client = Client::new(); 19 14 20 15 let response = client
+1
crates/scrobbler/src/xata/mod.rs
··· 2 2 pub mod api_key; 3 3 pub mod artist; 4 4 pub mod spotify_account; 5 + pub mod spotify_apps; 5 6 pub mod spotify_token; 6 7 pub mod track; 7 8 pub mod user;
+2
crates/scrobbler/src/xata/spotify_account.rs
··· 12 12 pub email: String, 13 13 pub user_id: String, 14 14 pub is_beta_user: bool, 15 + pub spotify_app_id: Option<String>, 16 + pub spotify_secret: Option<String>, 15 17 }
+14
crates/scrobbler/src/xata/spotify_apps.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 5 + pub struct SpotifyApp { 6 + pub xata_id: String, 7 + pub xata_version: i32, 8 + #[serde(with = "chrono::serde::ts_seconds")] 9 + pub xata_createdat: DateTime<Utc>, 10 + #[serde(with = "chrono::serde::ts_seconds")] 11 + pub xata_updatedat: DateTime<Utc>, 12 + pub spotify_app_id: String, 13 + pub spotify_secret: String, 14 + }
+4
crates/scrobbler/src/xata/spotify_token.rs
··· 12 12 pub user_id: String, 13 13 pub access_token: String, 14 14 pub refresh_token: String, 15 + pub spotify_app_id: String, 16 + pub spotify_secret: String, 15 17 } 16 18 17 19 #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] ··· 27 29 pub refresh_token: String, 28 30 pub email: String, 29 31 pub did: String, 32 + pub spotify_app_id: String, 33 + pub spotify_secret: String, 30 34 }
+109 -23
crates/spotify/src/lib.rs
··· 58 58 let email = user.0.clone(); 59 59 let token = user.1.clone(); 60 60 let did = user.2.clone(); 61 + let client_id = user.3.clone(); 62 + let client_secret = user.4.clone(); 61 63 let stop_flag = Arc::new(AtomicBool::new(false)); 62 64 let cache = cache.clone(); 63 65 let nc = nc.clone(); ··· 71 73 thread::spawn(move || { 72 74 let rt = tokio::runtime::Runtime::new().unwrap(); 73 75 match rt.block_on(async { 74 - watch_currently_playing(email.clone(), token, did, stop_flag, cache.clone()) 75 - .await?; 76 + watch_currently_playing( 77 + email.clone(), 78 + token, 79 + did, 80 + stop_flag, 81 + cache.clone(), 82 + client_id, 83 + client_secret, 84 + ) 85 + .await?; 76 86 Ok::<(), Error>(()) 77 87 }) { 78 88 Ok(_) => {} ··· 140 150 let email = user.0.clone(); 141 151 let token = user.1.clone(); 142 152 let did = user.2.clone(); 153 + let client_id = user.3.clone(); 154 + let client_secret = user.4.clone(); 143 155 let cache = cache.clone(); 144 156 145 157 thread::spawn(move || { ··· 151 163 did, 152 164 new_stop_flag, 153 165 cache.clone(), 166 + client_id, 167 + client_secret, 154 168 ) 155 169 .await?; 156 170 Ok::<(), Error>(()) ··· 178 192 let email = user.0.clone(); 179 193 let token = user.1.clone(); 180 194 let did = user.2.clone(); 195 + let client_id = user.3.clone(); 196 + let client_secret = user.4.clone(); 181 197 let stop_flag = Arc::new(AtomicBool::new(false)); 182 198 let cache = cache.clone(); 183 199 let nc = nc.clone(); ··· 193 209 did, 194 210 stop_flag, 195 211 cache.clone(), 212 + client_id, 213 + client_secret, 196 214 ) 197 215 .await?; 198 216 Ok::<(), Error>(()) ··· 227 245 Ok(()) 228 246 } 229 247 230 - pub async fn refresh_token(token: &str) -> Result<AccessToken, Error> { 231 - if env::var("SPOTIFY_CLIENT_ID").is_err() || env::var("SPOTIFY_CLIENT_SECRET").is_err() { 232 - panic!("Please set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables"); 233 - } 234 - 235 - let client_id = env::var("SPOTIFY_CLIENT_ID")?; 236 - let client_secret = env::var("SPOTIFY_CLIENT_SECRET")?; 237 - 248 + pub async fn refresh_token( 249 + token: &str, 250 + client_id: &str, 251 + client_secret: &str, 252 + ) -> Result<AccessToken, Error> { 238 253 let client = Client::new(); 239 254 240 255 let response = client ··· 255 270 cache: Cache, 256 271 user_id: &str, 257 272 token: &str, 273 + client_id: &str, 274 + client_secret: &str, 258 275 ) -> Result<Option<(CurrentlyPlaying, bool)>, Error> { 259 276 if let Ok(Some(data)) = cache.get(user_id) { 260 277 println!( ··· 329 346 return Ok(Some((data, changed))); 330 347 } 331 348 332 - let token = refresh_token(token).await?; 349 + let token = refresh_token(token, client_id, client_secret).await?; 333 350 let client = Client::new(); 334 351 let response = client 335 352 .get(format!("{}/me/player/currently-playing", BASE_URL)) ··· 529 546 cache: Cache, 530 547 artist_id: &str, 531 548 token: &str, 549 + client_id: &str, 550 + client_secret: &str, 532 551 ) -> Result<Option<Artist>, Error> { 533 552 if let Ok(Some(data)) = cache.get(artist_id) { 534 553 return Ok(Some(serde_json::from_str(&data)?)); 535 554 } 536 555 537 - let token = refresh_token(token).await?; 556 + let token = refresh_token(token, client_id, client_secret).await?; 538 557 let client = Client::new(); 539 558 let response = client 540 559 .get(&format!("{}/artists/{}", BASE_URL, artist_id)) ··· 569 588 Ok(Some(serde_json::from_str(&data)?)) 570 589 } 571 590 572 - pub async fn get_album(cache: Cache, album_id: &str, token: &str) -> Result<Option<Album>, Error> { 591 + pub async fn get_album( 592 + cache: Cache, 593 + album_id: &str, 594 + token: &str, 595 + client_id: &str, 596 + client_secret: &str, 597 + ) -> Result<Option<Album>, Error> { 573 598 if let Ok(Some(data)) = cache.get(album_id) { 574 599 return Ok(Some(serde_json::from_str(&data)?)); 575 600 } 576 601 577 - let token = refresh_token(token).await?; 602 + let token = refresh_token(token, client_id, client_secret).await?; 578 603 let client = Client::new(); 579 604 let response = client 580 605 .get(&format!("{}/albums/{}", BASE_URL, album_id)) ··· 613 638 cache: Cache, 614 639 album_id: &str, 615 640 token: &str, 641 + client_id: &str, 642 + client_secret: &str, 616 643 ) -> Result<AlbumTracks, Error> { 617 644 if let Ok(Some(data)) = cache.get(&format!("{}:tracks", album_id)) { 618 645 return Ok(serde_json::from_str(&data)?); 619 646 } 620 647 621 - let token = refresh_token(token).await?; 648 + let token = refresh_token(token, client_id, client_secret).await?; 622 649 let client = Client::new(); 623 650 let mut all_tracks = Vec::new(); 624 651 let mut offset = 0; ··· 678 705 pool: &Pool<Postgres>, 679 706 offset: usize, 680 707 limit: usize, 681 - ) -> Result<Vec<(String, String, String, String)>, Error> { 708 + ) -> Result<Vec<(String, String, String, String, String, String)>, Error> { 682 709 let results: Vec<SpotifyTokenWithEmail> = sqlx::query_as( 683 710 r#" 684 711 SELECT * FROM spotify_tokens 685 712 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id 686 713 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id 714 + LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id 687 715 LIMIT $1 OFFSET $2 688 716 "#, 689 717 ) ··· 699 727 &result.refresh_token, 700 728 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 701 729 )?; 730 + let spotify_secret = decrypt_aes_256_ctr( 731 + &result.spotify_secret, 732 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 733 + )?; 702 734 user_tokens.push(( 703 735 result.email.clone(), 704 736 token, 705 737 result.did.clone(), 706 738 result.user_id.clone(), 739 + result.spotify_app_id.clone(), 740 + spotify_secret, 707 741 )); 708 742 } 709 743 ··· 713 747 pub async fn find_spotify_user( 714 748 pool: &Pool<Postgres>, 715 749 email: &str, 716 - ) -> Result<Option<(String, String, String)>, Error> { 750 + ) -> Result<Option<(String, String, String, String, String)>, Error> { 717 751 let result: Vec<SpotifyTokenWithEmail> = sqlx::query_as( 718 752 r#" 719 753 SELECT * FROM spotify_tokens 720 754 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id 721 755 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id 756 + LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id 722 757 WHERE spotify_accounts.email = $1 723 758 "#, 724 759 ) ··· 732 767 &result.refresh_token, 733 768 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 734 769 )?; 735 - Ok(Some((result.email.clone(), token, result.did.clone()))) 770 + let spotify_secret = decrypt_aes_256_ctr( 771 + &result.spotify_secret, 772 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 773 + )?; 774 + Ok(Some(( 775 + result.email.clone(), 776 + token, 777 + result.did.clone(), 778 + result.spotify_app_id.clone(), 779 + spotify_secret, 780 + ))) 736 781 } 737 782 None => Ok(None), 738 783 } ··· 744 789 did: String, 745 790 stop_flag: Arc<AtomicBool>, 746 791 cache: Cache, 792 + client_id: String, 793 + client_secret: String, 747 794 ) -> Result<(), Error> { 748 795 println!( 749 796 "{} {}", ··· 832 879 let token = token.clone(); 833 880 let did = did.clone(); 834 881 let cache = cache.clone(); 882 + let client_id = client_id.clone(); 883 + let client_secret = client_secret.clone(); 835 884 836 - let currently_playing = get_currently_playing(cache.clone(), &spotify_email, &token).await; 885 + let currently_playing = get_currently_playing( 886 + cache.clone(), 887 + &spotify_email, 888 + &token, 889 + &client_id, 890 + &client_secret, 891 + ) 892 + .await; 837 893 let currently_playing = match currently_playing { 838 894 Ok(currently_playing) => currently_playing, 839 895 Err(e) => { ··· 867 923 ); 868 924 869 925 if changed { 870 - scrobble(cache.clone(), &spotify_email, &did, &token).await?; 926 + scrobble( 927 + cache.clone(), 928 + &spotify_email, 929 + &did, 930 + &token, 931 + &client_id, 932 + &client_secret, 933 + ) 934 + .await?; 871 935 872 936 thread::spawn(move || { 873 937 let rt = tokio::runtime::Runtime::new().unwrap(); 874 938 match rt.block_on(async { 875 - get_album_tracks(cache.clone(), &data_item.album.id, &token).await?; 876 - get_album(cache.clone(), &data_item.album.id, &token).await?; 877 - update_library(cache.clone(), &spotify_email, &did, &token).await?; 939 + get_album_tracks( 940 + cache.clone(), 941 + &data_item.album.id, 942 + &token, 943 + &client_id, 944 + &client_secret, 945 + ) 946 + .await?; 947 + get_album( 948 + cache.clone(), 949 + &data_item.album.id, 950 + &token, 951 + &client_id, 952 + &client_secret, 953 + ) 954 + .await?; 955 + update_library( 956 + cache.clone(), 957 + &spotify_email, 958 + &did, 959 + &token, 960 + &client_id, 961 + &client_secret, 962 + ) 963 + .await?; 878 964 Ok::<(), Error>(()) 879 965 }) { 880 966 Ok(_) => {}
-212
crates/spotify/src/main.rs
··· 1 - use std::{ 2 - collections::HashMap, 3 - env, 4 - sync::{atomic::AtomicBool, Arc, Mutex}, 5 - thread, 6 - }; 7 - 8 - use anyhow::Error; 9 - use async_nats::connect; 10 - use dotenv::dotenv; 11 - use owo_colors::OwoColorize; 12 - use rocksky_spotify::cache::Cache; 13 - use rocksky_spotify::{find_spotify_user, find_spotify_users, watch_currently_playing}; 14 - use sqlx::postgres::PgPoolOptions; 15 - use tokio_stream::StreamExt; 16 - 17 - #[tokio::main] 18 - async fn main() -> Result<(), Box<dyn std::error::Error>> { 19 - dotenv().ok(); 20 - let cache = Cache::new()?; 21 - let pool = PgPoolOptions::new() 22 - .max_connections(5) 23 - .connect(&env::var("XATA_POSTGRES_URL")?) 24 - .await?; 25 - 26 - let addr = env::var("NATS_URL").unwrap_or_else(|_| "nats://localhost:4222".to_string()); 27 - let nc = connect(&addr).await?; 28 - println!("Connected to NATS server at {}", addr.bright_green()); 29 - 30 - let mut sub = nc.subscribe("rocksky.spotify.user".to_string()).await?; 31 - println!("Subscribed to {}", "rocksky.spotify.user".bright_green()); 32 - 33 - let users = find_spotify_users(&pool, 0, 100).await?; 34 - println!("Found {} users", users.len().bright_green()); 35 - 36 - // Shared HashMap to manage threads and their stop flags 37 - let thread_map: Arc<Mutex<HashMap<String, Arc<AtomicBool>>>> = 38 - Arc::new(Mutex::new(HashMap::new())); 39 - 40 - // Start threads for all users 41 - for user in users { 42 - let email = user.0.clone(); 43 - let token = user.1.clone(); 44 - let did = user.2.clone(); 45 - let stop_flag = Arc::new(AtomicBool::new(false)); 46 - let cache = cache.clone(); 47 - let nc = nc.clone(); 48 - let thread_map = Arc::clone(&thread_map); 49 - 50 - thread_map 51 - .lock() 52 - .unwrap() 53 - .insert(email.clone(), Arc::clone(&stop_flag)); 54 - 55 - thread::spawn(move || { 56 - let rt = tokio::runtime::Runtime::new().unwrap(); 57 - match rt.block_on(async { 58 - watch_currently_playing(email.clone(), token, did, stop_flag, cache.clone()) 59 - .await?; 60 - Ok::<(), Error>(()) 61 - }) { 62 - Ok(_) => {} 63 - Err(e) => { 64 - println!( 65 - "{} Error starting thread for user: {} - {}", 66 - format!("[{}]", email).bright_green(), 67 - email.bright_green(), 68 - e.to_string().bright_red() 69 - ); 70 - 71 - // If there's an error, publish a message to restart the thread 72 - match rt.block_on(nc.publish("rocksky.spotify.user", email.clone().into())) { 73 - Ok(_) => { 74 - println!( 75 - "{} Published message to restart thread for user: {}", 76 - format!("[{}]", email).bright_green(), 77 - email.bright_green() 78 - ); 79 - } 80 - Err(e) => { 81 - println!( 82 - "{} Error publishing message to restart thread: {}", 83 - format!("[{}]", email).bright_green(), 84 - e.to_string().bright_red() 85 - ); 86 - } 87 - } 88 - } 89 - } 90 - }); 91 - } 92 - 93 - // Handle subscription messages 94 - while let Some(message) = sub.next().await { 95 - let user_id = String::from_utf8(message.payload.to_vec()).unwrap(); 96 - println!( 97 - "Received message to restart thread for user: {}", 98 - user_id.bright_green() 99 - ); 100 - 101 - let mut thread_map = thread_map.lock().unwrap(); 102 - 103 - // Check if the user exists in the thread map 104 - if let Some(stop_flag) = thread_map.get(&user_id) { 105 - // Stop the existing thread 106 - stop_flag.store(true, std::sync::atomic::Ordering::Relaxed); 107 - 108 - // Create a new stop flag and restart the thread 109 - let new_stop_flag = Arc::new(AtomicBool::new(false)); 110 - thread_map.insert(user_id.clone(), Arc::clone(&new_stop_flag)); 111 - 112 - let user = find_spotify_user(&pool, &user_id).await?; 113 - 114 - if user.is_none() { 115 - println!( 116 - "Spotify user not found: {}, skipping", 117 - user_id.bright_green() 118 - ); 119 - continue; 120 - } 121 - 122 - let user = user.unwrap(); 123 - 124 - let email = user.0.clone(); 125 - let token = user.1.clone(); 126 - let did = user.2.clone(); 127 - let cache = cache.clone(); 128 - 129 - thread::spawn(move || { 130 - let rt = tokio::runtime::Runtime::new().unwrap(); 131 - match rt.block_on(async { 132 - watch_currently_playing( 133 - email.clone(), 134 - token, 135 - did, 136 - new_stop_flag, 137 - cache.clone(), 138 - ) 139 - .await?; 140 - Ok::<(), Error>(()) 141 - }) { 142 - Ok(_) => {} 143 - Err(e) => { 144 - println!( 145 - "{} Error restarting thread for user: {} - {}", 146 - format!("[{}]", email).bright_green(), 147 - email.bright_green(), 148 - e.to_string().bright_red() 149 - ); 150 - } 151 - } 152 - }); 153 - 154 - println!("Restarted thread for user: {}", user_id.bright_green()); 155 - } else { 156 - println!( 157 - "No thread found for user: {}, starting new thread", 158 - user_id.bright_green() 159 - ); 160 - let user = find_spotify_user(&pool, &user_id).await?; 161 - if let Some(user) = user { 162 - let email = user.0.clone(); 163 - let token = user.1.clone(); 164 - let did = user.2.clone(); 165 - let stop_flag = Arc::new(AtomicBool::new(false)); 166 - let cache = cache.clone(); 167 - let nc = nc.clone(); 168 - 169 - thread_map.insert(email.clone(), Arc::clone(&stop_flag)); 170 - 171 - thread::spawn(move || { 172 - let rt = tokio::runtime::Runtime::new().unwrap(); 173 - match rt.block_on(async { 174 - watch_currently_playing( 175 - email.clone(), 176 - token, 177 - did, 178 - stop_flag, 179 - cache.clone(), 180 - ) 181 - .await?; 182 - Ok::<(), Error>(()) 183 - }) { 184 - Ok(_) => {} 185 - Err(e) => { 186 - println!( 187 - "{} Error starting thread for user: {} - {}", 188 - format!("[{}]", email).bright_green(), 189 - email.bright_green(), 190 - e.to_string().bright_red() 191 - ); 192 - match rt 193 - .block_on(nc.publish("rocksky.spotify.user", email.clone().into())) 194 - { 195 - Ok(_) => {} 196 - Err(e) => { 197 - println!( 198 - "{} Error publishing message to restart thread: {}", 199 - format!("[{}]", email).bright_green(), 200 - e.to_string().bright_red() 201 - ); 202 - } 203 - } 204 - } 205 - } 206 - }); 207 - } 208 - } 209 - } 210 - 211 - Ok(()) 212 - }
+14 -1
crates/spotify/src/rocksky.rs
··· 18 18 spotify_email: &str, 19 19 did: &str, 20 20 refresh_token: &str, 21 + client_id: &str, 22 + client_secret: &str, 21 23 ) -> Result<(), Error> { 22 24 let cached = cache.get(spotify_email)?; 23 25 if cached.is_none() { ··· 40 42 cache.clone(), 41 43 &track_item.artists.first().unwrap().id, 42 44 &refresh_token, 45 + &client_id, 46 + &client_secret, 43 47 ) 44 48 .await?; 45 49 ··· 98 102 spotify_email: &str, 99 103 did: &str, 100 104 refresh_token: &str, 105 + client_id: &str, 106 + client_secret: &str, 101 107 ) -> Result<(), Error> { 102 108 let cached = cache.get(spotify_email)?; 103 109 if cached.is_none() { ··· 105 111 "No currently playing song is cached for {}, refreshing", 106 112 spotify_email 107 113 ); 108 - get_currently_playing(cache.clone(), &spotify_email, &refresh_token).await?; 114 + get_currently_playing( 115 + cache.clone(), 116 + &spotify_email, 117 + &refresh_token, 118 + client_id, 119 + client_secret, 120 + ) 121 + .await?; 109 122 } 110 123 111 124 let cached = cache.get(spotify_email)?;
+2
crates/spotify/src/types/spotify_account.rs
··· 12 12 pub email: String, 13 13 pub user_id: String, 14 14 pub is_beta_user: bool, 15 + pub spotify_app_id: Option<String>, 16 + pub spotify_secret: Option<String>, 15 17 }
+4
crates/spotify/src/types/spotify_token.rs
··· 12 12 pub user_id: String, 13 13 pub access_token: String, 14 14 pub refresh_token: String, 15 + pub spotify_app_id: String, 16 + pub spotify_secret: String, 15 17 } 16 18 17 19 #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] ··· 27 29 pub refresh_token: String, 28 30 pub email: String, 29 31 pub did: String, 32 + pub spotify_app_id: String, 33 + pub spotify_secret: String, 30 34 }
+1
crates/webscrobbler/src/repo/spotify_account.rs
··· 9 9 let results: Vec<SpotifyAccount> = sqlx::query_as( 10 10 r#" 11 11 SELECT * FROM spotify_accounts 12 + LEFT JOIN spotify_apps ON spotify_accounts.spotify_app_id = spotify_apps.spotify_app_id 12 13 WHERE user_id = $1 13 14 "#, 14 15 )
+2
crates/webscrobbler/src/repo/spotify_token.rs
··· 12 12 SELECT * FROM spotify_tokens 13 13 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id 14 14 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id 15 + LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id 15 16 WHERE users.did = $1 16 17 "#, 17 18 ) ··· 35 36 SELECT * FROM spotify_tokens 36 37 LEFT JOIN spotify_accounts ON spotify_tokens.user_id = spotify_accounts.user_id 37 38 LEFT JOIN users ON spotify_accounts.user_id = users.xata_id 39 + LEFT JOIN spotify_apps ON spotify_tokens.spotify_app_id = spotify_apps.spotify_app_id 38 40 LIMIT $1 39 41 "#, 40 42 )
+7 -1
crates/webscrobbler/src/scrobbler.rs
··· 82 82 let mut rng = rand::rng(); 83 83 let random_index = rng.random_range(0..spofity_tokens.len()); 84 84 let spotify_token = &spofity_tokens[random_index]; 85 + let client_id = spotify_token.spotify_app_id.clone(); 86 + 87 + let client_secret = decrypt_aes_256_ctr( 88 + &spotify_token.spotify_secret, 89 + &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 90 + )?; 85 91 86 92 let spotify_token = decrypt_aes_256_ctr( 87 93 &spotify_token.refresh_token, 88 94 &hex::decode(env::var("SPOTIFY_ENCRYPTION_KEY")?)?, 89 95 )?; 90 96 91 - let spotify_token = refresh_token(&spotify_token).await?; 97 + let spotify_token = refresh_token(&spotify_token, &client_id, &client_secret).await?; 92 98 let spotify_client = SpotifyClient::new(&spotify_token.access_token); 93 99 94 100 let query = match scrobble.data.song.parsed.artist.contains(" x ") {
+5 -10
crates/webscrobbler/src/spotify/mod.rs
··· 1 - use std::env; 2 - 3 1 use anyhow::Error; 4 2 use reqwest::Client; 5 3 use types::AccessToken; ··· 7 5 pub mod client; 8 6 pub mod types; 9 7 10 - pub async fn refresh_token(token: &str) -> Result<AccessToken, Error> { 11 - if env::var("SPOTIFY_CLIENT_ID").is_err() || env::var("SPOTIFY_CLIENT_SECRET").is_err() { 12 - panic!("Please set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables"); 13 - } 14 - 15 - let client_id = env::var("SPOTIFY_CLIENT_ID")?; 16 - let client_secret = env::var("SPOTIFY_CLIENT_SECRET")?; 17 - 8 + pub async fn refresh_token( 9 + token: &str, 10 + client_id: &str, 11 + client_secret: &str, 12 + ) -> Result<AccessToken, Error> { 18 13 let client = Client::new(); 19 14 20 15 let response = client
+1
crates/webscrobbler/src/xata/mod.rs
··· 1 1 pub mod album; 2 2 pub mod artist; 3 3 pub mod spotify_account; 4 + pub mod spotify_apps; 4 5 pub mod spotify_token; 5 6 pub mod track; 6 7 pub mod user;
+2
crates/webscrobbler/src/xata/spotify_account.rs
··· 12 12 pub email: String, 13 13 pub user_id: String, 14 14 pub is_beta_user: bool, 15 + pub spotify_app_id: Option<String>, 16 + pub spotify_secret: Option<String>, 15 17 }
+14
crates/webscrobbler/src/xata/spotify_apps.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::Deserialize; 3 + 4 + #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] 5 + pub struct SpotifyApp { 6 + pub xata_id: String, 7 + pub xata_version: i32, 8 + #[serde(with = "chrono::serde::ts_seconds")] 9 + pub xata_createdat: DateTime<Utc>, 10 + #[serde(with = "chrono::serde::ts_seconds")] 11 + pub xata_updatedat: DateTime<Utc>, 12 + pub spotify_app_id: String, 13 + pub spotify_secret: String, 14 + }
+4
crates/webscrobbler/src/xata/spotify_token.rs
··· 12 12 pub user_id: String, 13 13 pub access_token: String, 14 14 pub refresh_token: String, 15 + pub spotify_app_id: String, 16 + pub spotify_secret: String, 15 17 } 16 18 17 19 #[derive(Debug, Deserialize, sqlx::FromRow, Default, Clone)] ··· 27 29 pub refresh_token: String, 28 30 pub email: String, 29 31 pub did: String, 32 + pub spotify_app_id: String, 33 + pub spotify_secret: String, 30 34 }