A decentralized music tracking and discovery platform built on AT Protocol 🎵

Merge pull request #5 from tsirysndr/remove-xata-client

remove xata client, replace with drizzle orm

authored by tsiry-sandratraina.com and committed by

GitHub 00de9ec8 da509a2b

+5603 -6793
-7
README.md
··· 54 54 55 55 ## 🚀 Getting Started 56 56 57 - > [!IMPORTANT] 58 - > 59 - > Self-hosting or running Rocksky locally is still **difficult** at this stage. 60 - > Parts of the API are still tied to [**Xata Postgres**](https://xata.io), and a full migration to standard **Postgres** is ongoing. 61 - > Until this migration is complete, expect setup to be non-trivial. 62 - 63 - 64 57 1. Clone the repository: 65 58 ```bash 66 59 git clone git@tangled.sh:rocksky.app/rocksky
+34
apps/api/drizzle/0003_same_rocket_racer.sql
··· 1 + ALTER TABLE "album_tracks" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 2 + ALTER TABLE "albums" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 3 + ALTER TABLE "api_keys" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 4 + ALTER TABLE "artist_albums" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 5 + ALTER TABLE "artist_tracks" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 6 + ALTER TABLE "artists" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 7 + ALTER TABLE "dropbox_accounts" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 8 + ALTER TABLE "dropbox_directories" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 9 + ALTER TABLE "dropbox_paths" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 10 + ALTER TABLE "dropbox_tokens" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 11 + ALTER TABLE "dropbox" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 12 + ALTER TABLE "google_drive_accounts" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 13 + ALTER TABLE "google_drive_directories" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 14 + ALTER TABLE "google_drive_paths" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 15 + ALTER TABLE "google_drive_tokens" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 16 + ALTER TABLE "google_drive" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 17 + ALTER TABLE "loved_tracks" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 18 + ALTER TABLE "playlist_tracks" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 19 + ALTER TABLE "playlists" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 20 + ALTER TABLE "profile_shouts" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 21 + ALTER TABLE "queue_tracks" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 22 + ALTER TABLE "scrobbles" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 23 + ALTER TABLE "shout_likes" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 24 + ALTER TABLE "shout_reports" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 25 + ALTER TABLE "shouts" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 26 + ALTER TABLE "spotify_accounts" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 27 + ALTER TABLE "spotify_tokens" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 28 + ALTER TABLE "tracks" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 29 + ALTER TABLE "user_albums" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 30 + ALTER TABLE "user_artists" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 31 + ALTER TABLE "user_playlists" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 32 + ALTER TABLE "user_tracks" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 33 + ALTER TABLE "users" ALTER COLUMN "xata_id" SET DEFAULT xata_id();--> statement-breakpoint 34 + ALTER TABLE "webscrobblers" ALTER COLUMN "xata_id" SET DEFAULT xata_id();
+3166
apps/api/drizzle/meta/0003_snapshot.json
··· 1 + { 2 + "id": "015093fe-a66e-4ec3-b5b6-5466c6266a39", 3 + "prevId": "3ef49661-fdf0-4245-8943-ff69567a09b9", 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 + "xata_createdat": { 2289 + "name": "xata_createdat", 2290 + "type": "timestamp", 2291 + "primaryKey": false, 2292 + "notNull": true, 2293 + "default": "now()" 2294 + }, 2295 + "xata_updatedat": { 2296 + "name": "xata_updatedat", 2297 + "type": "timestamp", 2298 + "primaryKey": false, 2299 + "notNull": true, 2300 + "default": "now()" 2301 + } 2302 + }, 2303 + "indexes": {}, 2304 + "foreignKeys": { 2305 + "spotify_accounts_user_id_users_xata_id_fk": { 2306 + "name": "spotify_accounts_user_id_users_xata_id_fk", 2307 + "tableFrom": "spotify_accounts", 2308 + "tableTo": "users", 2309 + "columnsFrom": [ 2310 + "user_id" 2311 + ], 2312 + "columnsTo": [ 2313 + "xata_id" 2314 + ], 2315 + "onDelete": "no action", 2316 + "onUpdate": "no action" 2317 + } 2318 + }, 2319 + "compositePrimaryKeys": {}, 2320 + "uniqueConstraints": {}, 2321 + "policies": {}, 2322 + "checkConstraints": {}, 2323 + "isRLSEnabled": false 2324 + }, 2325 + "public.spotify_tokens": { 2326 + "name": "spotify_tokens", 2327 + "schema": "", 2328 + "columns": { 2329 + "xata_id": { 2330 + "name": "xata_id", 2331 + "type": "text", 2332 + "primaryKey": true, 2333 + "notNull": true, 2334 + "default": "xata_id()" 2335 + }, 2336 + "xata_version": { 2337 + "name": "xata_version", 2338 + "type": "integer", 2339 + "primaryKey": false, 2340 + "notNull": false 2341 + }, 2342 + "access_token": { 2343 + "name": "access_token", 2344 + "type": "text", 2345 + "primaryKey": false, 2346 + "notNull": true 2347 + }, 2348 + "refresh_token": { 2349 + "name": "refresh_token", 2350 + "type": "text", 2351 + "primaryKey": false, 2352 + "notNull": true 2353 + }, 2354 + "user_id": { 2355 + "name": "user_id", 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 + "spotify_tokens_user_id_users_xata_id_fk": { 2378 + "name": "spotify_tokens_user_id_users_xata_id_fk", 2379 + "tableFrom": "spotify_tokens", 2380 + "tableTo": "users", 2381 + "columnsFrom": [ 2382 + "user_id" 2383 + ], 2384 + "columnsTo": [ 2385 + "xata_id" 2386 + ], 2387 + "onDelete": "no action", 2388 + "onUpdate": "no action" 2389 + } 2390 + }, 2391 + "compositePrimaryKeys": {}, 2392 + "uniqueConstraints": {}, 2393 + "policies": {}, 2394 + "checkConstraints": {}, 2395 + "isRLSEnabled": false 2396 + }, 2397 + "public.tracks": { 2398 + "name": "tracks", 2399 + "schema": "", 2400 + "columns": { 2401 + "xata_id": { 2402 + "name": "xata_id", 2403 + "type": "text", 2404 + "primaryKey": true, 2405 + "notNull": true, 2406 + "default": "xata_id()" 2407 + }, 2408 + "title": { 2409 + "name": "title", 2410 + "type": "text", 2411 + "primaryKey": false, 2412 + "notNull": true 2413 + }, 2414 + "artist": { 2415 + "name": "artist", 2416 + "type": "text", 2417 + "primaryKey": false, 2418 + "notNull": true 2419 + }, 2420 + "album_artist": { 2421 + "name": "album_artist", 2422 + "type": "text", 2423 + "primaryKey": false, 2424 + "notNull": true 2425 + }, 2426 + "album_art": { 2427 + "name": "album_art", 2428 + "type": "text", 2429 + "primaryKey": false, 2430 + "notNull": false 2431 + }, 2432 + "album": { 2433 + "name": "album", 2434 + "type": "text", 2435 + "primaryKey": false, 2436 + "notNull": true 2437 + }, 2438 + "track_number": { 2439 + "name": "track_number", 2440 + "type": "integer", 2441 + "primaryKey": false, 2442 + "notNull": false 2443 + }, 2444 + "duration": { 2445 + "name": "duration", 2446 + "type": "integer", 2447 + "primaryKey": false, 2448 + "notNull": true 2449 + }, 2450 + "mb_id": { 2451 + "name": "mb_id", 2452 + "type": "text", 2453 + "primaryKey": false, 2454 + "notNull": false 2455 + }, 2456 + "youtube_link": { 2457 + "name": "youtube_link", 2458 + "type": "text", 2459 + "primaryKey": false, 2460 + "notNull": false 2461 + }, 2462 + "spotify_link": { 2463 + "name": "spotify_link", 2464 + "type": "text", 2465 + "primaryKey": false, 2466 + "notNull": false 2467 + }, 2468 + "apple_music_link": { 2469 + "name": "apple_music_link", 2470 + "type": "text", 2471 + "primaryKey": false, 2472 + "notNull": false 2473 + }, 2474 + "tidal_link": { 2475 + "name": "tidal_link", 2476 + "type": "text", 2477 + "primaryKey": false, 2478 + "notNull": false 2479 + }, 2480 + "sha256": { 2481 + "name": "sha256", 2482 + "type": "text", 2483 + "primaryKey": false, 2484 + "notNull": true 2485 + }, 2486 + "disc_number": { 2487 + "name": "disc_number", 2488 + "type": "integer", 2489 + "primaryKey": false, 2490 + "notNull": false 2491 + }, 2492 + "lyrics": { 2493 + "name": "lyrics", 2494 + "type": "text", 2495 + "primaryKey": false, 2496 + "notNull": false 2497 + }, 2498 + "composer": { 2499 + "name": "composer", 2500 + "type": "text", 2501 + "primaryKey": false, 2502 + "notNull": false 2503 + }, 2504 + "genre": { 2505 + "name": "genre", 2506 + "type": "text", 2507 + "primaryKey": false, 2508 + "notNull": false 2509 + }, 2510 + "label": { 2511 + "name": "label", 2512 + "type": "text", 2513 + "primaryKey": false, 2514 + "notNull": false 2515 + }, 2516 + "copyright_message": { 2517 + "name": "copyright_message", 2518 + "type": "text", 2519 + "primaryKey": false, 2520 + "notNull": false 2521 + }, 2522 + "uri": { 2523 + "name": "uri", 2524 + "type": "text", 2525 + "primaryKey": false, 2526 + "notNull": false 2527 + }, 2528 + "album_uri": { 2529 + "name": "album_uri", 2530 + "type": "text", 2531 + "primaryKey": false, 2532 + "notNull": false 2533 + }, 2534 + "artist_uri": { 2535 + "name": "artist_uri", 2536 + "type": "text", 2537 + "primaryKey": false, 2538 + "notNull": false 2539 + }, 2540 + "xata_createdat": { 2541 + "name": "xata_createdat", 2542 + "type": "timestamp", 2543 + "primaryKey": false, 2544 + "notNull": true, 2545 + "default": "now()" 2546 + }, 2547 + "xata_updatedat": { 2548 + "name": "xata_updatedat", 2549 + "type": "timestamp", 2550 + "primaryKey": false, 2551 + "notNull": true, 2552 + "default": "now()" 2553 + }, 2554 + "xata_version": { 2555 + "name": "xata_version", 2556 + "type": "integer", 2557 + "primaryKey": false, 2558 + "notNull": false 2559 + } 2560 + }, 2561 + "indexes": {}, 2562 + "foreignKeys": {}, 2563 + "compositePrimaryKeys": {}, 2564 + "uniqueConstraints": { 2565 + "tracks_mb_id_unique": { 2566 + "name": "tracks_mb_id_unique", 2567 + "nullsNotDistinct": false, 2568 + "columns": [ 2569 + "mb_id" 2570 + ] 2571 + }, 2572 + "tracks_youtube_link_unique": { 2573 + "name": "tracks_youtube_link_unique", 2574 + "nullsNotDistinct": false, 2575 + "columns": [ 2576 + "youtube_link" 2577 + ] 2578 + }, 2579 + "tracks_spotify_link_unique": { 2580 + "name": "tracks_spotify_link_unique", 2581 + "nullsNotDistinct": false, 2582 + "columns": [ 2583 + "spotify_link" 2584 + ] 2585 + }, 2586 + "tracks_apple_music_link_unique": { 2587 + "name": "tracks_apple_music_link_unique", 2588 + "nullsNotDistinct": false, 2589 + "columns": [ 2590 + "apple_music_link" 2591 + ] 2592 + }, 2593 + "tracks_tidal_link_unique": { 2594 + "name": "tracks_tidal_link_unique", 2595 + "nullsNotDistinct": false, 2596 + "columns": [ 2597 + "tidal_link" 2598 + ] 2599 + }, 2600 + "tracks_sha256_unique": { 2601 + "name": "tracks_sha256_unique", 2602 + "nullsNotDistinct": false, 2603 + "columns": [ 2604 + "sha256" 2605 + ] 2606 + }, 2607 + "tracks_uri_unique": { 2608 + "name": "tracks_uri_unique", 2609 + "nullsNotDistinct": false, 2610 + "columns": [ 2611 + "uri" 2612 + ] 2613 + } 2614 + }, 2615 + "policies": {}, 2616 + "checkConstraints": {}, 2617 + "isRLSEnabled": false 2618 + }, 2619 + "public.user_albums": { 2620 + "name": "user_albums", 2621 + "schema": "", 2622 + "columns": { 2623 + "xata_id": { 2624 + "name": "xata_id", 2625 + "type": "text", 2626 + "primaryKey": true, 2627 + "notNull": true, 2628 + "default": "xata_id()" 2629 + }, 2630 + "user_id": { 2631 + "name": "user_id", 2632 + "type": "text", 2633 + "primaryKey": false, 2634 + "notNull": true 2635 + }, 2636 + "album_id": { 2637 + "name": "album_id", 2638 + "type": "text", 2639 + "primaryKey": false, 2640 + "notNull": true 2641 + }, 2642 + "xata_createdat": { 2643 + "name": "xata_createdat", 2644 + "type": "timestamp", 2645 + "primaryKey": false, 2646 + "notNull": true, 2647 + "default": "now()" 2648 + }, 2649 + "xata_updatedat": { 2650 + "name": "xata_updatedat", 2651 + "type": "timestamp", 2652 + "primaryKey": false, 2653 + "notNull": true, 2654 + "default": "now()" 2655 + }, 2656 + "xata_version": { 2657 + "name": "xata_version", 2658 + "type": "integer", 2659 + "primaryKey": false, 2660 + "notNull": false 2661 + }, 2662 + "scrobbles": { 2663 + "name": "scrobbles", 2664 + "type": "integer", 2665 + "primaryKey": false, 2666 + "notNull": false 2667 + }, 2668 + "uri": { 2669 + "name": "uri", 2670 + "type": "text", 2671 + "primaryKey": false, 2672 + "notNull": true 2673 + } 2674 + }, 2675 + "indexes": {}, 2676 + "foreignKeys": { 2677 + "user_albums_user_id_users_xata_id_fk": { 2678 + "name": "user_albums_user_id_users_xata_id_fk", 2679 + "tableFrom": "user_albums", 2680 + "tableTo": "users", 2681 + "columnsFrom": [ 2682 + "user_id" 2683 + ], 2684 + "columnsTo": [ 2685 + "xata_id" 2686 + ], 2687 + "onDelete": "no action", 2688 + "onUpdate": "no action" 2689 + }, 2690 + "user_albums_album_id_albums_xata_id_fk": { 2691 + "name": "user_albums_album_id_albums_xata_id_fk", 2692 + "tableFrom": "user_albums", 2693 + "tableTo": "albums", 2694 + "columnsFrom": [ 2695 + "album_id" 2696 + ], 2697 + "columnsTo": [ 2698 + "xata_id" 2699 + ], 2700 + "onDelete": "no action", 2701 + "onUpdate": "no action" 2702 + } 2703 + }, 2704 + "compositePrimaryKeys": {}, 2705 + "uniqueConstraints": { 2706 + "user_albums_uri_unique": { 2707 + "name": "user_albums_uri_unique", 2708 + "nullsNotDistinct": false, 2709 + "columns": [ 2710 + "uri" 2711 + ] 2712 + } 2713 + }, 2714 + "policies": {}, 2715 + "checkConstraints": {}, 2716 + "isRLSEnabled": false 2717 + }, 2718 + "public.user_artists": { 2719 + "name": "user_artists", 2720 + "schema": "", 2721 + "columns": { 2722 + "xata_id": { 2723 + "name": "xata_id", 2724 + "type": "text", 2725 + "primaryKey": true, 2726 + "notNull": true, 2727 + "default": "xata_id()" 2728 + }, 2729 + "user_id": { 2730 + "name": "user_id", 2731 + "type": "text", 2732 + "primaryKey": false, 2733 + "notNull": true 2734 + }, 2735 + "artist_id": { 2736 + "name": "artist_id", 2737 + "type": "text", 2738 + "primaryKey": false, 2739 + "notNull": true 2740 + }, 2741 + "xata_createdat": { 2742 + "name": "xata_createdat", 2743 + "type": "timestamp", 2744 + "primaryKey": false, 2745 + "notNull": true, 2746 + "default": "now()" 2747 + }, 2748 + "xata_updatedat": { 2749 + "name": "xata_updatedat", 2750 + "type": "timestamp", 2751 + "primaryKey": false, 2752 + "notNull": true, 2753 + "default": "now()" 2754 + }, 2755 + "xata_version": { 2756 + "name": "xata_version", 2757 + "type": "integer", 2758 + "primaryKey": false, 2759 + "notNull": false 2760 + }, 2761 + "scrobbles": { 2762 + "name": "scrobbles", 2763 + "type": "integer", 2764 + "primaryKey": false, 2765 + "notNull": false 2766 + }, 2767 + "uri": { 2768 + "name": "uri", 2769 + "type": "text", 2770 + "primaryKey": false, 2771 + "notNull": true 2772 + } 2773 + }, 2774 + "indexes": {}, 2775 + "foreignKeys": { 2776 + "user_artists_user_id_users_xata_id_fk": { 2777 + "name": "user_artists_user_id_users_xata_id_fk", 2778 + "tableFrom": "user_artists", 2779 + "tableTo": "users", 2780 + "columnsFrom": [ 2781 + "user_id" 2782 + ], 2783 + "columnsTo": [ 2784 + "xata_id" 2785 + ], 2786 + "onDelete": "no action", 2787 + "onUpdate": "no action" 2788 + }, 2789 + "user_artists_artist_id_artists_xata_id_fk": { 2790 + "name": "user_artists_artist_id_artists_xata_id_fk", 2791 + "tableFrom": "user_artists", 2792 + "tableTo": "artists", 2793 + "columnsFrom": [ 2794 + "artist_id" 2795 + ], 2796 + "columnsTo": [ 2797 + "xata_id" 2798 + ], 2799 + "onDelete": "no action", 2800 + "onUpdate": "no action" 2801 + } 2802 + }, 2803 + "compositePrimaryKeys": {}, 2804 + "uniqueConstraints": { 2805 + "user_artists_uri_unique": { 2806 + "name": "user_artists_uri_unique", 2807 + "nullsNotDistinct": false, 2808 + "columns": [ 2809 + "uri" 2810 + ] 2811 + } 2812 + }, 2813 + "policies": {}, 2814 + "checkConstraints": {}, 2815 + "isRLSEnabled": false 2816 + }, 2817 + "public.user_playlists": { 2818 + "name": "user_playlists", 2819 + "schema": "", 2820 + "columns": { 2821 + "xata_id": { 2822 + "name": "xata_id", 2823 + "type": "text", 2824 + "primaryKey": true, 2825 + "notNull": true, 2826 + "default": "xata_id()" 2827 + }, 2828 + "user_id": { 2829 + "name": "user_id", 2830 + "type": "text", 2831 + "primaryKey": false, 2832 + "notNull": true 2833 + }, 2834 + "playlist_id": { 2835 + "name": "playlist_id", 2836 + "type": "text", 2837 + "primaryKey": false, 2838 + "notNull": true 2839 + }, 2840 + "xata_createdat": { 2841 + "name": "xata_createdat", 2842 + "type": "timestamp", 2843 + "primaryKey": false, 2844 + "notNull": true, 2845 + "default": "now()" 2846 + }, 2847 + "uri": { 2848 + "name": "uri", 2849 + "type": "text", 2850 + "primaryKey": false, 2851 + "notNull": false 2852 + } 2853 + }, 2854 + "indexes": {}, 2855 + "foreignKeys": { 2856 + "user_playlists_user_id_users_xata_id_fk": { 2857 + "name": "user_playlists_user_id_users_xata_id_fk", 2858 + "tableFrom": "user_playlists", 2859 + "tableTo": "users", 2860 + "columnsFrom": [ 2861 + "user_id" 2862 + ], 2863 + "columnsTo": [ 2864 + "xata_id" 2865 + ], 2866 + "onDelete": "no action", 2867 + "onUpdate": "no action" 2868 + }, 2869 + "user_playlists_playlist_id_playlists_xata_id_fk": { 2870 + "name": "user_playlists_playlist_id_playlists_xata_id_fk", 2871 + "tableFrom": "user_playlists", 2872 + "tableTo": "playlists", 2873 + "columnsFrom": [ 2874 + "playlist_id" 2875 + ], 2876 + "columnsTo": [ 2877 + "xata_id" 2878 + ], 2879 + "onDelete": "no action", 2880 + "onUpdate": "no action" 2881 + } 2882 + }, 2883 + "compositePrimaryKeys": {}, 2884 + "uniqueConstraints": { 2885 + "user_playlists_uri_unique": { 2886 + "name": "user_playlists_uri_unique", 2887 + "nullsNotDistinct": false, 2888 + "columns": [ 2889 + "uri" 2890 + ] 2891 + } 2892 + }, 2893 + "policies": {}, 2894 + "checkConstraints": {}, 2895 + "isRLSEnabled": false 2896 + }, 2897 + "public.user_tracks": { 2898 + "name": "user_tracks", 2899 + "schema": "", 2900 + "columns": { 2901 + "xata_id": { 2902 + "name": "xata_id", 2903 + "type": "text", 2904 + "primaryKey": true, 2905 + "notNull": true, 2906 + "default": "xata_id()" 2907 + }, 2908 + "user_id": { 2909 + "name": "user_id", 2910 + "type": "text", 2911 + "primaryKey": false, 2912 + "notNull": true 2913 + }, 2914 + "track_id": { 2915 + "name": "track_id", 2916 + "type": "text", 2917 + "primaryKey": false, 2918 + "notNull": true 2919 + }, 2920 + "xata_createdat": { 2921 + "name": "xata_createdat", 2922 + "type": "timestamp", 2923 + "primaryKey": false, 2924 + "notNull": true, 2925 + "default": "now()" 2926 + }, 2927 + "xata_updatedat": { 2928 + "name": "xata_updatedat", 2929 + "type": "timestamp", 2930 + "primaryKey": false, 2931 + "notNull": true, 2932 + "default": "now()" 2933 + }, 2934 + "xata_version": { 2935 + "name": "xata_version", 2936 + "type": "integer", 2937 + "primaryKey": false, 2938 + "notNull": false 2939 + }, 2940 + "uri": { 2941 + "name": "uri", 2942 + "type": "text", 2943 + "primaryKey": false, 2944 + "notNull": true 2945 + }, 2946 + "scrobbles": { 2947 + "name": "scrobbles", 2948 + "type": "integer", 2949 + "primaryKey": false, 2950 + "notNull": false 2951 + } 2952 + }, 2953 + "indexes": {}, 2954 + "foreignKeys": { 2955 + "user_tracks_user_id_users_xata_id_fk": { 2956 + "name": "user_tracks_user_id_users_xata_id_fk", 2957 + "tableFrom": "user_tracks", 2958 + "tableTo": "users", 2959 + "columnsFrom": [ 2960 + "user_id" 2961 + ], 2962 + "columnsTo": [ 2963 + "xata_id" 2964 + ], 2965 + "onDelete": "no action", 2966 + "onUpdate": "no action" 2967 + }, 2968 + "user_tracks_track_id_tracks_xata_id_fk": { 2969 + "name": "user_tracks_track_id_tracks_xata_id_fk", 2970 + "tableFrom": "user_tracks", 2971 + "tableTo": "tracks", 2972 + "columnsFrom": [ 2973 + "track_id" 2974 + ], 2975 + "columnsTo": [ 2976 + "xata_id" 2977 + ], 2978 + "onDelete": "no action", 2979 + "onUpdate": "no action" 2980 + } 2981 + }, 2982 + "compositePrimaryKeys": {}, 2983 + "uniqueConstraints": { 2984 + "user_tracks_uri_unique": { 2985 + "name": "user_tracks_uri_unique", 2986 + "nullsNotDistinct": false, 2987 + "columns": [ 2988 + "uri" 2989 + ] 2990 + } 2991 + }, 2992 + "policies": {}, 2993 + "checkConstraints": {}, 2994 + "isRLSEnabled": false 2995 + }, 2996 + "public.users": { 2997 + "name": "users", 2998 + "schema": "", 2999 + "columns": { 3000 + "xata_id": { 3001 + "name": "xata_id", 3002 + "type": "text", 3003 + "primaryKey": true, 3004 + "notNull": true, 3005 + "default": "xata_id()" 3006 + }, 3007 + "did": { 3008 + "name": "did", 3009 + "type": "text", 3010 + "primaryKey": false, 3011 + "notNull": true 3012 + }, 3013 + "display_name": { 3014 + "name": "display_name", 3015 + "type": "text", 3016 + "primaryKey": false, 3017 + "notNull": false 3018 + }, 3019 + "handle": { 3020 + "name": "handle", 3021 + "type": "text", 3022 + "primaryKey": false, 3023 + "notNull": true 3024 + }, 3025 + "avatar": { 3026 + "name": "avatar", 3027 + "type": "text", 3028 + "primaryKey": false, 3029 + "notNull": true 3030 + }, 3031 + "xata_createdat": { 3032 + "name": "xata_createdat", 3033 + "type": "timestamp", 3034 + "primaryKey": false, 3035 + "notNull": true, 3036 + "default": "now()" 3037 + }, 3038 + "xata_updatedat": { 3039 + "name": "xata_updatedat", 3040 + "type": "timestamp", 3041 + "primaryKey": false, 3042 + "notNull": true, 3043 + "default": "now()" 3044 + }, 3045 + "xata_version": { 3046 + "name": "xata_version", 3047 + "type": "integer", 3048 + "primaryKey": false, 3049 + "notNull": false 3050 + } 3051 + }, 3052 + "indexes": {}, 3053 + "foreignKeys": {}, 3054 + "compositePrimaryKeys": {}, 3055 + "uniqueConstraints": { 3056 + "users_did_unique": { 3057 + "name": "users_did_unique", 3058 + "nullsNotDistinct": false, 3059 + "columns": [ 3060 + "did" 3061 + ] 3062 + }, 3063 + "users_handle_unique": { 3064 + "name": "users_handle_unique", 3065 + "nullsNotDistinct": false, 3066 + "columns": [ 3067 + "handle" 3068 + ] 3069 + } 3070 + }, 3071 + "policies": {}, 3072 + "checkConstraints": {}, 3073 + "isRLSEnabled": false 3074 + }, 3075 + "public.webscrobblers": { 3076 + "name": "webscrobblers", 3077 + "schema": "", 3078 + "columns": { 3079 + "xata_id": { 3080 + "name": "xata_id", 3081 + "type": "text", 3082 + "primaryKey": true, 3083 + "notNull": true, 3084 + "default": "xata_id()" 3085 + }, 3086 + "name": { 3087 + "name": "name", 3088 + "type": "text", 3089 + "primaryKey": false, 3090 + "notNull": true 3091 + }, 3092 + "uuid": { 3093 + "name": "uuid", 3094 + "type": "text", 3095 + "primaryKey": false, 3096 + "notNull": true 3097 + }, 3098 + "description": { 3099 + "name": "description", 3100 + "type": "text", 3101 + "primaryKey": false, 3102 + "notNull": false 3103 + }, 3104 + "enabled": { 3105 + "name": "enabled", 3106 + "type": "boolean", 3107 + "primaryKey": false, 3108 + "notNull": true, 3109 + "default": true 3110 + }, 3111 + "user_id": { 3112 + "name": "user_id", 3113 + "type": "text", 3114 + "primaryKey": false, 3115 + "notNull": true 3116 + }, 3117 + "xata_createdat": { 3118 + "name": "xata_createdat", 3119 + "type": "timestamp", 3120 + "primaryKey": false, 3121 + "notNull": true, 3122 + "default": "now()" 3123 + }, 3124 + "xata_updatedat": { 3125 + "name": "xata_updatedat", 3126 + "type": "timestamp", 3127 + "primaryKey": false, 3128 + "notNull": true, 3129 + "default": "now()" 3130 + } 3131 + }, 3132 + "indexes": {}, 3133 + "foreignKeys": { 3134 + "webscrobblers_user_id_users_xata_id_fk": { 3135 + "name": "webscrobblers_user_id_users_xata_id_fk", 3136 + "tableFrom": "webscrobblers", 3137 + "tableTo": "users", 3138 + "columnsFrom": [ 3139 + "user_id" 3140 + ], 3141 + "columnsTo": [ 3142 + "xata_id" 3143 + ], 3144 + "onDelete": "no action", 3145 + "onUpdate": "no action" 3146 + } 3147 + }, 3148 + "compositePrimaryKeys": {}, 3149 + "uniqueConstraints": {}, 3150 + "policies": {}, 3151 + "checkConstraints": {}, 3152 + "isRLSEnabled": false 3153 + } 3154 + }, 3155 + "enums": {}, 3156 + "schemas": {}, 3157 + "sequences": {}, 3158 + "roles": {}, 3159 + "policies": {}, 3160 + "views": {}, 3161 + "_meta": { 3162 + "columns": {}, 3163 + "schemas": {}, 3164 + "tables": {} 3165 + } 3166 + }
+7
apps/api/drizzle/meta/_journal.json
··· 22 22 "when": 1759747596050, 23 23 "tag": "0002_sweet_randall_flagg", 24 24 "breakpoints": true 25 + }, 26 + { 27 + "idx": 3, 28 + "version": "7", 29 + "when": 1759819936859, 30 + "tag": "0003_same_rocket_racer", 31 + "breakpoints": true 25 32 } 26 33 ] 27 34 }
-1
apps/api/package.json
··· 45 45 "@opentelemetry/sdk-node": "^0.200.0", 46 46 "@opentelemetry/semantic-conventions": "^1.32.0", 47 47 "@pyroscope/nodejs": "^0.4.5", 48 - "@xata.io/client": "^0.0.0-next.va121e4207b94bfe0a3c025fc00b247b923880930", 49 48 "assert": "^2.1.0", 50 49 "axios": "^1.7.9", 51 50 "better-sqlite3": "^11.8.1",
+65 -37
apps/api/src/apikeys/app.ts
··· 1 - import { equals } from "@xata.io/client"; 2 1 import { ctx } from "context"; 3 2 import { and, eq } from "drizzle-orm"; 4 3 import { Hono } from "hono"; ··· 6 5 import { env } from "lib/env"; 7 6 import crypto from "node:crypto"; 8 7 import * as R from "ramda"; 9 - import tables from "schema"; 8 + import apiKeys from "schema/api-keys"; 9 + import users from "schema/users"; 10 10 import { apiKeySchema } from "types/apikey"; 11 11 12 12 const app = new Hono(); ··· 23 23 ignoreExpiration: true, 24 24 }); 25 25 26 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 26 + const user = await ctx.db 27 + .select() 28 + .from(users) 29 + .where(eq(users.did, did)) 30 + .limit(1) 31 + .then((rows) => rows[0]); 32 + 27 33 if (!user) { 28 34 c.status(401); 29 35 return c.text("Unauthorized"); ··· 32 38 const size = +c.req.query("size") || 20; 33 39 const offset = +c.req.query("offset") || 0; 34 40 35 - const apikeys = await ctx.db 41 + const apikeysData = await ctx.db 36 42 .select() 37 - .from(tables.apiKeys) 38 - .where(eq(tables.apiKeys.userId, user.xata_id)) 43 + .from(apiKeys) 44 + .where(eq(apiKeys.userId, user.id)) 39 45 .limit(size) 40 - .offset(offset) 41 - .execute(); 46 + .offset(offset); 42 47 43 - return c.json(apikeys.map((x) => R.omit(["userId"])(x))); 48 + return c.json(apikeysData.map((x) => R.omit(["userId"])(x))); 44 49 }); 45 50 46 51 app.post("/", async (c) => { ··· 55 60 ignoreExpiration: true, 56 61 }); 57 62 58 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 63 + const user = await ctx.db 64 + .select() 65 + .from(users) 66 + .where(eq(users.did, did)) 67 + .limit(1) 68 + .then((rows) => rows[0]); 69 + 59 70 if (!user) { 60 71 c.status(401); 61 72 return c.text("Unauthorized"); ··· 70 81 } 71 82 const newApiKey = parsed.data; 72 83 73 - const api_key = crypto.randomBytes(16).toString("hex"); 74 - const shared_secret = crypto.randomBytes(16).toString("hex"); 84 + if (!newApiKey.name) { 85 + c.status(400); 86 + return c.text("Missing required field: name"); 87 + } 75 88 76 - const record = await ctx.client.db.api_keys.create({ 77 - ...newApiKey, 78 - api_key, 79 - shared_secret, 80 - user_id: user.xata_id, 81 - }); 89 + const apiKey = crypto.randomBytes(16).toString("hex"); 90 + const sharedSecret = crypto.randomBytes(16).toString("hex"); 91 + 92 + const [record] = await ctx.db 93 + .insert(apiKeys) 94 + .values({ 95 + name: newApiKey.name, 96 + description: newApiKey.description ?? "", 97 + enabled: newApiKey.enabled ?? true, 98 + apiKey, 99 + sharedSecret, 100 + userId: user.id, 101 + }) 102 + .returning(); 82 103 83 104 return c.json({ 84 - id: record.xata_id, 105 + id: record.id, 85 106 name: record.name, 86 107 description: record.description, 87 - api_key: record.api_key, 88 - shared_secret: record.shared_secret, 108 + api_key: record.apiKey, 109 + shared_secret: record.sharedSecret, 89 110 }); 90 111 }); 91 112 ··· 101 122 ignoreExpiration: true, 102 123 }); 103 124 104 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 125 + const user = await ctx.db 126 + .select() 127 + .from(users) 128 + .where(eq(users.did, did)) 129 + .limit(1) 130 + .then((rows) => rows[0]); 131 + 105 132 if (!user) { 106 133 c.status(401); 107 134 return c.text("Unauthorized"); ··· 110 137 const data = await c.req.json(); 111 138 const id = c.req.param("id"); 112 139 113 - const record = await ctx.db 114 - .update(tables.apiKeys) 140 + const [record] = await ctx.db 141 + .update(apiKeys) 115 142 .set(data) 116 - .where( 117 - and(eq(tables.apiKeys.id, id), eq(tables.apiKeys.userId, user.xata_id)), 118 - ) 119 - .execute(); 143 + .where(and(eq(apiKeys.id, id), eq(apiKeys.userId, user.id))) 144 + .returning(); 120 145 121 146 return c.json({ 122 - id: record.xata_id, 147 + id: record.id, 123 148 name: record.name, 124 149 description: record.description, 125 - api_key: record.api_key, 126 - shared_secret: record.shared_secret, 150 + api_key: record.apiKey, 151 + shared_secret: record.sharedSecret, 127 152 }); 128 153 }); 129 154 ··· 139 164 ignoreExpiration: true, 140 165 }); 141 166 142 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 167 + const user = await ctx.db 168 + .select() 169 + .from(users) 170 + .where(eq(users.did, did)) 171 + .limit(1) 172 + .then((rows) => rows[0]); 173 + 143 174 if (!user) { 144 175 c.status(401); 145 176 return c.text("Unauthorized"); ··· 148 179 const id = c.req.param("id"); 149 180 150 181 await ctx.db 151 - .delete(tables.apiKeys) 152 - .where( 153 - and(eq(tables.apiKeys.id, id), eq(tables.apiKeys.userId, user.xata_id)), 154 - ) 155 - .execute(); 182 + .delete(apiKeys) 183 + .where(and(eq(apiKeys.id, id), eq(apiKeys.userId, user.id))); 156 184 157 185 return c.json({ success: true }); 158 186 });
+71 -35
apps/api/src/bsky/app.ts
··· 1 1 import type { BlobRef } from "@atproto/lexicon"; 2 2 import { isValidHandle } from "@atproto/syntax"; 3 - import { equals } from "@xata.io/client"; 4 3 import { ctx } from "context"; 5 - import { desc, eq } from "drizzle-orm"; 4 + import { and, desc, eq } from "drizzle-orm"; 6 5 import { Hono } from "hono"; 7 6 import jwt from "jsonwebtoken"; 8 7 import * as Profile from "lexicon/types/app/bsky/actor/profile"; 8 + import { deepSnakeCaseKeys } from "lib"; 9 9 import { createAgent } from "lib/agent"; 10 10 import { env } from "lib/env"; 11 11 import { requestCounter } from "metrics"; 12 + import dropboxAccounts from "schema/dropbox-accounts"; 13 + import googleDriveAccounts from "schema/google-drive-accounts"; 14 + import spotifyAccounts from "schema/spotify-accounts"; 15 + import spotifyTokens from "schema/spotify-tokens"; 12 16 import users from "schema/users"; 13 17 14 18 const app = new Hono(); ··· 61 65 app.get("/oauth/callback", async (c) => { 62 66 requestCounter.add(1, { method: "GET", route: "/oauth/callback" }); 63 67 const params = new URLSearchParams(c.req.url.split("?")[1]); 64 - let did, cli; 68 + let did: string, cli: string; 65 69 66 70 try { 67 71 const { session } = await ctx.oauthClient.callback(params); ··· 77 81 ? Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365 * 1000 78 82 : Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7, 79 83 }, 80 - env.JWT_SECRET, 84 + env.JWT_SECRET 81 85 ); 82 86 ctx.kv.set(did, token); 83 87 } catch (err) { ··· 85 89 return c.redirect(`${env.FRONTEND_URL}?error=1`); 86 90 } 87 91 88 - const spotifyUser = await ctx.client.db.spotify_accounts 89 - .filter("user_id.did", equals(did)) 90 - .filter("is_beta_user", equals(true)) 91 - .getFirst(); 92 + const [spotifyUser] = await ctx.db 93 + .select() 94 + .from(spotifyAccounts) 95 + .where( 96 + and(eq(spotifyAccounts.userId, did), eq(spotifyAccounts.isBetaUser, true)) 97 + ) 98 + .limit(1) 99 + .execute(); 92 100 93 101 if (spotifyUser?.email) { 94 102 ctx.nc.publish("rocksky.spotify.user", Buffer.from(spotifyUser.email)); ··· 134 142 135 143 if (profile.handle) { 136 144 try { 137 - await ctx.client.db.users.create({ 138 - did, 139 - handle, 140 - display_name: profile.displayName, 141 - avatar: `https://cdn.bsky.app/img/avatar/plain/${did}/${profile.avatar.ref.toString()}@jpeg`, 142 - }); 145 + await ctx.db 146 + .insert(users) 147 + .values({ 148 + did, 149 + handle, 150 + displayName: profile.displayName, 151 + avatar: `https://cdn.bsky.app/img/avatar/plain/${did}/${profile.avatar.ref.toString()}@jpeg`, 152 + }) 153 + .execute(); 143 154 } catch (e) { 144 155 if (!e.message.includes("invalid record: column [did]: is not unique")) { 145 156 console.error(e.message); ··· 156 167 } 157 168 } 158 169 159 - const [user, lastUser, previousLastUser] = await Promise.all([ 160 - ctx.client.db.users.select(["*"]).filter("did", equals(did)).getFirst(), 170 + const [user, lastUser] = await Promise.all([ 171 + ctx.db.select().from(users).where(eq(users.did, did)).limit(1).execute(), 161 172 ctx.db 162 173 .select() 163 174 .from(users) 164 175 .orderBy(desc(users.createdAt)) 165 176 .limit(1) 166 177 .execute(), 167 - ctx.kv.get("lastUser"), 168 178 ]); 169 179 170 - ctx.nc.publish("rocksky.user", Buffer.from(JSON.stringify(user))); 180 + ctx.nc.publish( 181 + "rocksky.user", 182 + Buffer.from(JSON.stringify(deepSnakeCaseKeys(user))) 183 + ); 171 184 172 185 await ctx.kv.set("lastUser", lastUser[0].id); 173 - // if (lastUser[0].id !== previousLastUser) { 174 - // ctx.nc.publish("rocksky.user", Buffer.from(JSON.stringify(user))); 175 - // } 176 186 } 177 187 178 188 const [spotifyUser, spotifyToken, googledrive, dropbox] = await Promise.all([ 179 - ctx.client.db.spotify_accounts 180 - .select(["user_id.*", "email", "is_beta_user"]) 181 - .filter("user_id.did", equals(did)) 182 - .getFirst(), 183 - ctx.client.db.spotify_tokens.filter("user_id.did", equals(did)).getFirst(), 184 - ctx.client.db.google_drive_accounts 185 - .select(["user_id.*", "email", "is_beta_user"]) 186 - .filter("user_id.did", equals(did)) 187 - .getFirst(), 188 - ctx.client.db.dropbox_accounts 189 - .select(["user_id.*", "email", "is_beta_user"]) 190 - .filter("user_id.did", equals(did)) 191 - .getFirst(), 192 - ]); 189 + ctx.db 190 + .select() 191 + .from(spotifyAccounts) 192 + .where( 193 + and( 194 + eq(spotifyAccounts.userId, did), 195 + eq(spotifyAccounts.isBetaUser, true) 196 + ) 197 + ) 198 + .limit(1) 199 + .execute(), 200 + ctx.db 201 + .select() 202 + .from(spotifyTokens) 203 + .where(eq(spotifyTokens.userId, did)) 204 + .limit(1) 205 + .execute(), 206 + ctx.db 207 + .select() 208 + .from(googleDriveAccounts) 209 + .where( 210 + and( 211 + eq(googleDriveAccounts.userId, did), 212 + eq(googleDriveAccounts.isBetaUser, true) 213 + ) 214 + ) 215 + .limit(1) 216 + .execute(), 217 + ctx.db 218 + .select() 219 + .from(dropboxAccounts) 220 + .where( 221 + and( 222 + eq(dropboxAccounts.userId, did), 223 + eq(dropboxAccounts.isBetaUser, true) 224 + ) 225 + ) 226 + .limit(1) 227 + .execute(), 228 + ]).then(([s, t, g, d]) => deepSnakeCaseKeys([s[0], t[0], g[0], d[0]])); 193 229 194 230 return c.json({ 195 231 ...profile,
-4
apps/api/src/context.ts
··· 9 9 import redis from "redis"; 10 10 import sqliteKv from "sqliteKv"; 11 11 import { createStorage } from "unstorage"; 12 - import { getXataClient } from "xata"; 13 12 14 13 const { DB_PATH } = env; 15 14 export const db = createDb(DB_PATH); ··· 21 20 22 21 const baseIdResolver = createIdResolver(kv); 23 22 24 - const client = getXataClient(); 25 - 26 23 export const ctx = { 27 24 oauthClient: await createClient(db), 28 25 resolver: createBidirectionalResolver(baseIdResolver), 29 26 baseIdResolver, 30 27 kv: new Map<string, string>(), 31 - client, 32 28 db: drizzle.db, 33 29 nc: await connect({ servers: env.NATS_URL }), 34 30 analytics: axios.create({ baseURL: env.ANALYTICS }),
+104 -32
apps/api/src/dropbox/app.ts
··· 1 - import { equals } from "@xata.io/client"; 2 1 import axios from "axios"; 3 2 import { ctx } from "context"; 3 + import { eq } from "drizzle-orm"; 4 4 import { Hono } from "hono"; 5 5 import jwt from "jsonwebtoken"; 6 6 import { encrypt } from "lib/crypto"; 7 7 import { env } from "lib/env"; 8 8 import { requestCounter } from "metrics"; 9 + import tables from "schema"; 9 10 import { emailSchema } from "types/email"; 10 11 11 12 const app = new Hono(); ··· 23 24 ignoreExpiration: true, 24 25 }); 25 26 26 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 27 + const user = await ctx.db 28 + .select() 29 + .from(tables.users) 30 + .where(eq(tables.users.did, did)) 31 + .limit(1) 32 + .execute() 33 + .then((res) => res[0]); 34 + 27 35 if (!user) { 28 36 c.status(401); 29 37 return c.text("Unauthorized"); 30 38 } 31 39 32 40 const clientId = env.DROPBOX_CLIENT_ID; 33 - const redirectUri = `https://www.dropbox.com/oauth2/authorize?client_id=${clientId}&redirect_uri=${env.DROPBOX_REDIRECT_URI}&response_type=code&token_access_type=offline&state=${user.xata_id}`; 41 + const redirectUri = `https://www.dropbox.com/oauth2/authorize?client_id=${clientId}&redirect_uri=${env.DROPBOX_REDIRECT_URI}&response_type=code&token_access_type=offline&state=${user.id}`; 34 42 return c.json({ redirectUri }); 35 43 }); 36 44 ··· 47 55 client_id: env.DROPBOX_CLIENT_ID, 48 56 client_secret: env.DROPBOX_CLIENT_SECRET, 49 57 redirect_uri: env.DROPBOX_REDIRECT_URI, 50 - }, 58 + } 51 59 ); 52 60 53 - const dropbox = await ctx.client.db.dropbox 54 - .select(["*", "user_id.*", "dropbox_token_id.*"]) 55 - .filter("user_id.xata_id", equals(entries.state)) 56 - .getFirst(); 61 + const { dropbox, dropbox_tokens } = await ctx.db 62 + .select() 63 + .from(tables.dropbox) 64 + .where(eq(tables.dropbox.userId, entries.state)) 65 + .leftJoin( 66 + tables.dropboxTokens, 67 + eq(tables.dropboxTokens.id, tables.dropbox.dropboxTokenId) 68 + ) 69 + .limit(1) 70 + .execute() 71 + .then((res) => res[0]); 57 72 58 - const newDropboxToken = await ctx.client.db.dropbox_tokens.createOrUpdate( 59 - dropbox?.dropbox_token_id?.xata_id, 60 - { 61 - refresh_token: encrypt( 73 + const newDropboxToken = await ctx.db 74 + .insert(tables.dropboxTokens) 75 + .values({ 76 + id: dropbox_tokens?.id, 77 + refreshToken: encrypt( 62 78 response.data.refresh_token, 63 - env.SPOTIFY_ENCRYPTION_KEY, 79 + env.SPOTIFY_ENCRYPTION_KEY 64 80 ), 65 - }, 66 - ); 81 + }) 82 + .onConflictDoUpdate({ 83 + target: tables.dropboxTokens.id, // specify the conflict column (primary key) 84 + set: { 85 + refreshToken: encrypt( 86 + response.data.refresh_token, 87 + env.SPOTIFY_ENCRYPTION_KEY 88 + ), 89 + }, 90 + }) 91 + .returning() 92 + .execute() 93 + .then((res) => res[0]); 67 94 68 - await ctx.client.db.dropbox.createOrUpdate(dropbox?.xata_id, { 69 - dropbox_token_id: newDropboxToken.xata_id, 70 - user_id: entries.state, 71 - }); 95 + await ctx.db 96 + .insert(tables.dropbox) 97 + .values({ 98 + id: dropbox?.id, 99 + dropboxTokenId: newDropboxToken.id, 100 + userId: entries.state, 101 + }) 102 + .onConflictDoUpdate({ 103 + target: tables.dropbox.id, 104 + set: { 105 + dropboxTokenId: newDropboxToken.id, 106 + userId: entries.state, 107 + }, 108 + }) 109 + .execute(); 72 110 73 111 return c.redirect(`${env.FRONTEND_URL}/dropbox`); 74 112 }); ··· 86 124 ignoreExpiration: true, 87 125 }); 88 126 89 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 127 + const user = await ctx.db 128 + .select() 129 + .from(tables.users) 130 + .where(eq(tables.users.did, did)) 131 + .limit(1) 132 + .execute() 133 + .then((res) => res[0]); 90 134 if (!user) { 91 135 c.status(401); 92 136 return c.text("Unauthorized"); ··· 103 147 const { email } = parsed.data; 104 148 105 149 try { 106 - await ctx.client.db.dropbox_accounts.create({ 107 - user_id: user.xata_id, 108 - email, 109 - is_beta_user: false, 110 - }); 150 + await ctx.db 151 + .insert(tables.dropboxAccounts) 152 + .values({ 153 + userId: user.id, 154 + email, 155 + isBetaUser: false, 156 + }) 157 + .execute(); 111 158 } catch (e) { 112 159 if ( 113 160 !e.message.includes("invalid record: column [user_id]: is not unique") ··· 143 190 ignoreExpiration: true, 144 191 }); 145 192 146 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 193 + const [user] = await ctx.db 194 + .select() 195 + .from(tables.users) 196 + .where(eq(tables.users.did, did)) 197 + .limit(1) 198 + .execute(); 147 199 if (!user) { 148 200 c.status(401); 149 201 return c.text("Unauthorized"); ··· 190 242 ignoreExpiration: true, 191 243 }); 192 244 193 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 245 + const [user] = await ctx.db 246 + .select() 247 + .from(tables.users) 248 + .where(eq(tables.users.did, did)) 249 + .limit(1) 250 + .execute(); 194 251 if (!user) { 195 252 c.status(401); 196 253 return c.text("Unauthorized"); ··· 223 280 ignoreExpiration: true, 224 281 }); 225 282 226 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 283 + const [user] = await ctx.db 284 + .select() 285 + .from(tables.users) 286 + .where(eq(tables.users.did, did)) 287 + .limit(1) 288 + .execute(); 227 289 if (!user) { 228 290 c.status(401); 229 291 return c.text("Unauthorized"); ··· 252 314 ignoreExpiration: true, 253 315 }); 254 316 255 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 317 + const [user] = await ctx.db 318 + .select() 319 + .from(tables.users) 320 + .where(eq(tables.users.did, did)) 321 + .limit(1) 322 + .execute(); 256 323 if (!user) { 257 324 c.status(401); 258 325 return c.text("Unauthorized"); ··· 286 353 ignoreExpiration: true, 287 354 }); 288 355 289 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 356 + const [user] = await ctx.db 357 + .select() 358 + .from(tables.users) 359 + .where(eq(tables.users.did, did)) 360 + .limit(1) 361 + .execute(); 290 362 if (!user) { 291 363 c.status(401); 292 364 return c.text("Unauthorized"); ··· 305 377 306 378 c.header( 307 379 "Content-Type", 308 - response.headers["content-type"] || "application/octet-stream", 380 + response.headers["content-type"] || "application/octet-stream" 309 381 ); 310 382 c.header( 311 383 "Content-Disposition", 312 - response.headers["content-disposition"] || "attachment", 384 + response.headers["content-disposition"] || "attachment" 313 385 ); 314 386 315 387 return new Response(response.data, {
+107 -39
apps/api/src/googledrive/app.ts
··· 1 - import { equals } from "@xata.io/client"; 2 1 import axios from "axios"; 3 2 import { ctx } from "context"; 3 + import { eq } from "drizzle-orm"; 4 4 import fs from "fs"; 5 5 import { google } from "googleapis"; 6 6 import { Hono } from "hono"; ··· 8 8 import { encrypt } from "lib/crypto"; 9 9 import { env } from "lib/env"; 10 10 import { requestCounter } from "metrics"; 11 + import googleDriveAccounts from "schema/google-drive-accounts"; 12 + import googleDriveTokens from "schema/google-drive-tokens"; 13 + import googleDrive from "schema/googledrive"; 14 + import users from "schema/users"; 11 15 import { emailSchema } from "types/email"; 12 16 13 17 const app = new Hono(); ··· 25 29 ignoreExpiration: true, 26 30 }); 27 31 28 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 32 + const user = await ctx.db 33 + .select() 34 + .from(users) 35 + .where(eq(users.did, did)) 36 + .limit(1) 37 + .then((rows) => rows[0]); 38 + 29 39 if (!user) { 30 40 c.status(401); 31 41 return c.text("Unauthorized"); 32 42 } 33 43 34 44 const credentials = JSON.parse( 35 - fs.readFileSync("credentials.json").toString("utf-8"), 45 + fs.readFileSync("credentials.json").toString("utf-8") 36 46 ); 37 47 const { client_id, client_secret } = credentials.installed || credentials.web; 38 48 const oAuth2Client = new google.auth.OAuth2( 39 49 client_id, 40 50 client_secret, 41 - env.GOOGLE_REDIRECT_URI, 51 + env.GOOGLE_REDIRECT_URI 42 52 ); 43 53 44 54 // Generate Auth URL ··· 46 56 access_type: "offline", 47 57 prompt: "consent", 48 58 scope: ["https://www.googleapis.com/auth/drive"], 49 - state: user.xata_id, 59 + state: user.id, 50 60 }); 51 61 return c.json({ authUrl }); 52 62 }); ··· 60 70 const entries = Object.fromEntries(params.entries()); 61 71 62 72 const credentials = JSON.parse( 63 - fs.readFileSync("credentials.json").toString("utf-8"), 73 + fs.readFileSync("credentials.json").toString("utf-8") 64 74 ); 65 75 const { client_id, client_secret } = credentials.installed || credentials.web; 66 76 ··· 72 82 grant_type: "authorization_code", 73 83 }); 74 84 75 - const googledrive = await ctx.client.db.google_drive 76 - .select(["*", "user_id.*", "google_drive_token_id.*"]) 77 - .filter("user_id.xata_id", equals(entries.state)) 78 - .getFirst(); 85 + const existingGoogleDrive = await ctx.db 86 + .select({ 87 + googleDrive: googleDrive, 88 + user: users, 89 + token: googleDriveTokens, 90 + }) 91 + .from(googleDrive) 92 + .innerJoin(users, eq(googleDrive.userId, users.id)) 93 + .leftJoin( 94 + googleDriveTokens, 95 + eq(googleDrive.googleDriveTokenId, googleDriveTokens.id) 96 + ) 97 + .where(eq(users.id, entries.state)) 98 + .limit(1) 99 + .then((rows) => rows[0]); 79 100 80 - const newGoogleDriveToken = 81 - await ctx.client.db.google_drive_tokens.createOrUpdate( 82 - googledrive?.google_drive_token_id?.xata_id, 83 - { 84 - refresh_token: encrypt( 101 + let tokenId: string; 102 + if (existingGoogleDrive?.token) { 103 + const [updatedToken] = await ctx.db 104 + .update(googleDriveTokens) 105 + .set({ 106 + refreshToken: encrypt( 85 107 response.data.refresh_token, 86 - env.SPOTIFY_ENCRYPTION_KEY, 108 + env.SPOTIFY_ENCRYPTION_KEY 87 109 ), 88 - }, 89 - ); 110 + }) 111 + .where(eq(googleDriveTokens.id, existingGoogleDrive.token.id)) 112 + .returning(); 113 + tokenId = updatedToken.id; 114 + } else { 115 + const [newToken] = await ctx.db 116 + .insert(googleDriveTokens) 117 + .values({ 118 + refreshToken: encrypt( 119 + response.data.refresh_token, 120 + env.SPOTIFY_ENCRYPTION_KEY 121 + ), 122 + }) 123 + .returning(); 124 + tokenId = newToken.id; 125 + } 90 126 91 - await ctx.client.db.google_drive.createOrUpdate(googledrive?.xata_id, { 92 - google_drive_token_id: newGoogleDriveToken.xata_id, 93 - user_id: entries.state, 94 - }); 127 + if (existingGoogleDrive?.googleDrive) { 128 + await ctx.db 129 + .update(googleDrive) 130 + .set({ 131 + googleDriveTokenId: tokenId, 132 + userId: entries.state, 133 + }) 134 + .where(eq(googleDrive.id, existingGoogleDrive.googleDrive.id)); 135 + } else { 136 + await ctx.db.insert(googleDrive).values({ 137 + googleDriveTokenId: tokenId, 138 + userId: entries.state, 139 + }); 140 + } 95 141 96 142 return c.redirect(`${env.FRONTEND_URL}/googledrive`); 97 143 }); ··· 109 155 ignoreExpiration: true, 110 156 }); 111 157 112 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 158 + const user = await ctx.db 159 + .select() 160 + .from(users) 161 + .where(eq(users.did, did)) 162 + .limit(1) 163 + .then((rows) => rows[0]); 164 + 113 165 if (!user) { 114 166 c.status(401); 115 167 return c.text("Unauthorized"); ··· 126 178 const { email } = parsed.data; 127 179 128 180 try { 129 - await ctx.client.db.google_drive_accounts.create({ 130 - user_id: user.xata_id, 181 + await ctx.db.insert(googleDriveAccounts).values({ 182 + userId: user.id, 131 183 email, 132 - is_beta_user: false, 184 + isBetaUser: false, 133 185 }); 134 186 } catch (e) { 135 - if ( 136 - !e.message.includes("invalid record: column [user_id]: is not unique") 137 - ) { 187 + if (!e.message.includes("duplicate key value violates unique constraint")) { 138 188 console.error(e.message); 139 189 } else { 140 190 throw e; ··· 166 216 ignoreExpiration: true, 167 217 }); 168 218 169 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 219 + const user = await ctx.db 220 + .select() 221 + .from(users) 222 + .where(eq(users.did, did)) 223 + .limit(1) 224 + .then((rows) => rows[0]); 225 + 170 226 if (!user) { 171 227 c.status(401); 172 228 return c.text("Unauthorized"); ··· 181 237 { 182 238 did, 183 239 parent_id, 184 - }, 240 + } 185 241 ); 186 242 return c.json(data); 187 243 } ··· 202 258 { 203 259 did, 204 260 parent_id: response.data.files[0].id, 205 - }, 261 + } 206 262 ); 207 263 return c.json(data); 208 264 } catch (error) { ··· 210 266 console.error("Axios error:", error.response?.data || error.message); 211 267 212 268 const credentials = JSON.parse( 213 - fs.readFileSync("credentials.json").toString("utf-8"), 269 + fs.readFileSync("credentials.json").toString("utf-8") 214 270 ); 215 271 const { client_id, client_secret } = 216 272 credentials.installed || credentials.web; 217 273 const oAuth2Client = new google.auth.OAuth2( 218 274 client_id, 219 275 client_secret, 220 - env.GOOGLE_REDIRECT_URI, 276 + env.GOOGLE_REDIRECT_URI 221 277 ); 222 278 223 279 // Generate Auth URL ··· 225 281 access_type: "offline", 226 282 prompt: "consent", 227 283 scope: ["https://www.googleapis.com/auth/drive"], 228 - state: user.xata_id, 284 + state: user.id, 229 285 }); 230 286 231 287 return c.json({ ··· 249 305 ignoreExpiration: true, 250 306 }); 251 307 252 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 308 + const user = await ctx.db 309 + .select() 310 + .from(users) 311 + .where(eq(users.did, did)) 312 + .limit(1) 313 + .then((rows) => rows[0]); 314 + 253 315 if (!user) { 254 316 c.status(401); 255 317 return c.text("Unauthorized"); ··· 280 342 ignoreExpiration: true, 281 343 }); 282 344 283 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 345 + const user = await ctx.db 346 + .select() 347 + .from(users) 348 + .where(eq(users.did, did)) 349 + .limit(1) 350 + .then((rows) => rows[0]); 351 + 284 352 if (!user) { 285 353 c.status(401); 286 354 return c.text("Unauthorized"); ··· 294 362 295 363 c.header( 296 364 "Content-Type", 297 - response.headers["content-type"] || "application/octet-stream", 365 + response.headers["content-type"] || "application/octet-stream" 298 366 ); 299 367 c.header( 300 368 "Content-Disposition", 301 - response.headers["content-disposition"] || "attachment", 369 + response.headers["content-disposition"] || "attachment" 302 370 ); 303 371 304 372 return new Response(response.data, {
+152 -89
apps/api/src/index.ts
··· 1 1 import { serve } from "@hono/node-server"; 2 2 import { createNodeWebSocket } from "@hono/node-ws"; 3 3 import { trace } from "@opentelemetry/api"; 4 - import { equals } from "@xata.io/client"; 5 4 import { ctx } from "context"; 5 + import { and, desc, eq, isNotNull, or } from "drizzle-orm"; 6 6 import { Hono } from "hono"; 7 7 import { cors } from "hono/cors"; 8 8 import jwt from "jsonwebtoken"; ··· 25 25 import { env } from "./lib/env"; 26 26 import { requestCounter, requestDuration } from "./metrics"; 27 27 import "./profiling"; 28 - import search from "./search/app"; 28 + import albumTracks from "./schema/album-tracks"; 29 + import albums from "./schema/albums"; 30 + import artistTracks from "./schema/artist-tracks"; 31 + import artists from "./schema/artists"; 32 + import scrobbles from "./schema/scrobbles"; 33 + import tracks from "./schema/tracks"; 34 + import users from "./schema/users"; 29 35 import spotify from "./spotify/app"; 30 36 import "./tracing"; 31 - import users from "./users/app"; 37 + import usersApp from "./users/app"; 32 38 import webscrobbler from "./webscrobbler/app"; 33 39 34 40 subscribe(ctx); ··· 41 47 rateLimiter({ 42 48 limit: 1000, 43 49 window: 30, // 👈 30 seconds 44 - }), 50 + }) 45 51 ); 46 52 47 53 app.use("*", async (c, next) => { ··· 91 97 ignoreExpiration: true, 92 98 }); 93 99 94 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 100 + const user = await ctx.db 101 + .select() 102 + .from(users) 103 + .where(eq(users.did, did)) 104 + .limit(1) 105 + .then((rows) => rows[0]); 106 + 95 107 if (!user) { 96 108 c.status(401); 97 109 return c.text("Unauthorized"); ··· 133 145 return c.text("Unauthorized"); 134 146 } 135 147 136 - const user = await ctx.client.db.users 137 - .filter({ 138 - $any: [{ did }, { handle: did }], 139 - }) 140 - .getFirst(); 148 + const user = await ctx.db 149 + .select() 150 + .from(users) 151 + .where(or(eq(users.did, did), eq(users.handle, did))) 152 + .limit(1) 153 + .then((rows) => rows[0]); 141 154 142 155 if (!user) { 143 156 c.status(401); ··· 149 162 ctx.redis.get(`nowplaying:${user.did}:status`), 150 163 ]); 151 164 return c.json( 152 - nowPlaying ? { ...JSON.parse(nowPlaying), is_playing: status === "1" } : {}, 165 + nowPlaying ? { ...JSON.parse(nowPlaying), is_playing: status === "1" } : {} 153 166 ); 154 167 }); 155 168 ··· 180 193 }); 181 194 const agent = await createAgent(ctx.oauthClient, did); 182 195 183 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 196 + const user = await ctx.db 197 + .select() 198 + .from(users) 199 + .where(eq(users.did, did)) 200 + .limit(1) 201 + .then((rows) => rows[0]); 202 + 184 203 if (!user) { 185 204 c.status(401); 186 205 return c.text("Unauthorized"); ··· 213 232 }); 214 233 const agent = await createAgent(ctx.oauthClient, did); 215 234 216 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 235 + const user = await ctx.db 236 + .select() 237 + .from(users) 238 + .where(eq(users.did, did)) 239 + .limit(1) 240 + .then((rows) => rows[0]); 241 + 217 242 if (!user) { 218 243 c.status(401); 219 244 return c.text("Unauthorized"); ··· 237 262 ignoreExpiration: true, 238 263 }); 239 264 240 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 265 + const user = await ctx.db 266 + .select() 267 + .from(users) 268 + .where(eq(users.did, did)) 269 + .limit(1) 270 + .then((rows) => rows[0]); 271 + 241 272 if (!user) { 242 273 c.status(401); 243 274 return c.text("Unauthorized"); ··· 256 287 const size = +c.req.query("size") || 10; 257 288 const offset = +c.req.query("offset") || 0; 258 289 259 - const scrobbles = await ctx.client.db.scrobbles 260 - .select(["track_id.*", "user_id.*", "timestamp", "xata_createdat", "uri"]) 261 - .sort("timestamp", "desc") 262 - .getPaginated({ 263 - pagination: { 264 - size, 265 - offset, 266 - }, 267 - }); 290 + const scrobbleRecords = await ctx.db 291 + .select({ 292 + scrobble: scrobbles, 293 + track: tracks, 294 + user: users, 295 + }) 296 + .from(scrobbles) 297 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 298 + .innerJoin(users, eq(scrobbles.userId, users.id)) 299 + .orderBy(desc(scrobbles.timestamp)) 300 + .limit(size) 301 + .offset(offset); 268 302 269 303 return c.json( 270 - scrobbles.records.map((item) => ({ 271 - cover: item.track_id.album_art, 272 - artist: item.track_id.artist, 273 - title: item.track_id.title, 274 - date: item.timestamp, 275 - user: item.user_id.handle, 276 - uri: item.uri, 277 - albumUri: item.track_id.album_uri, 278 - artistUri: item.track_id.artist_uri, 304 + scrobbleRecords.map((item) => ({ 305 + cover: item.track.albumArt, 306 + artist: item.track.artist, 307 + title: item.track.title, 308 + date: item.scrobble.timestamp, 309 + user: item.user.handle, 310 + uri: item.scrobble.uri, 311 + albumUri: item.track.albumUri, 312 + artistUri: item.track.artistUri, 279 313 tags: [], 280 314 listeners: 1, 281 - sha256: item.track_id.sha256, 282 - id: item.xata_id, 283 - })), 315 + sha256: item.track.sha256, 316 + id: item.scrobble.id, 317 + })) 284 318 ); 285 319 }); 286 320 ··· 316 350 if (songuri) { 317 351 let uri = songuri; 318 352 if (songuri.includes("app.rocksky.scrobble")) { 319 - const scrobble = await ctx.client.db.scrobbles 320 - .select(["track_id.*", "uri"]) 321 - .filter("uri", equals(songuri)) 322 - .getFirst(); 353 + const scrobble = await ctx.db 354 + .select({ 355 + scrobble: scrobbles, 356 + track: tracks, 357 + }) 358 + .from(scrobbles) 359 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 360 + .where(eq(scrobbles.uri, songuri)) 361 + .limit(1) 362 + .then((rows) => rows[0]); 323 363 324 - uri = scrobble.track_id.uri; 364 + uri = scrobble.track.uri; 325 365 } 326 366 const chart = await ctx.analytics.post("library.getTrackScrobbles", { 327 367 track_id: uri, ··· 347 387 ignoreExpiration: true, 348 388 }); 349 389 350 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 390 + const user = await ctx.db 391 + .select() 392 + .from(users) 393 + .where(eq(users.did, did)) 394 + .limit(1) 395 + .then((rows) => rows[0]); 396 + 351 397 if (!user) { 352 398 c.status(401); 353 399 return c.text("Unauthorized"); ··· 356 402 const size = +c.req.query("size") || 10; 357 403 const offset = +c.req.query("offset") || 0; 358 404 359 - const scrobbles = await ctx.client.db.scrobbles 360 - .select(["track_id.*", "uri"]) 361 - .filter("user_id", equals(user.xata_id)) 362 - .filter({ 363 - $not: [ 364 - { 365 - uri: null, 366 - }, 367 - ], 405 + const userScrobbles = await ctx.db 406 + .select({ 407 + scrobble: scrobbles, 408 + track: tracks, 368 409 }) 369 - .sort("xata_createdat", "desc") 370 - .getPaginated({ 371 - pagination: { 372 - size, 373 - offset, 374 - }, 375 - }); 410 + .from(scrobbles) 411 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 412 + .where(and(eq(scrobbles.userId, user.id), isNotNull(scrobbles.uri))) 413 + .orderBy(desc(scrobbles.createdAt)) 414 + .limit(size) 415 + .offset(offset); 376 416 377 - return c.json(scrobbles.records); 417 + return c.json(userScrobbles); 378 418 }); 379 419 380 420 app.post("/tracks", async (c) => { ··· 391 431 ignoreExpiration: true, 392 432 }); 393 433 394 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 434 + const user = await ctx.db 435 + .select() 436 + .from(users) 437 + .where(eq(users.did, did)) 438 + .limit(1) 439 + .then((rows) => rows[0]); 440 + 395 441 if (!user) { 396 442 c.status(401); 397 443 return c.text("Unauthorized"); ··· 416 462 try { 417 463 await saveTrack(ctx, track, agent); 418 464 } catch (e) { 419 - if (!e.message.includes("invalid record: column [sha256]: is not unique")) { 420 - console.error("[spotify user]", e.message); 465 + if (!e.message.includes("duplicate key value violates unique constraint")) { 466 + console.error("[tracks]", e.message); 421 467 } 422 468 } 423 469 ··· 430 476 const size = +c.req.query("size") || 100; 431 477 const offset = +c.req.query("offset") || 0; 432 478 433 - const tracks = await ctx.analytics.post("library.getTracks", { 479 + const tracksData = await ctx.analytics.post("library.getTracks", { 434 480 pagination: { 435 481 skip: offset, 436 482 take: size, 437 483 }, 438 484 }); 439 485 440 - return c.json(tracks.data); 486 + return c.json(tracksData.data); 441 487 }); 442 488 443 489 app.get("/albums", async (c) => { ··· 446 492 const size = +c.req.query("size") || 100; 447 493 const offset = +c.req.query("offset") || 0; 448 494 449 - const albums = await ctx.analytics.post("library.getAlbums", { 495 + const albumsData = await ctx.analytics.post("library.getAlbums", { 450 496 pagination: { 451 497 skip: offset, 452 498 take: size, 453 499 }, 454 500 }); 455 501 456 - return c.json(albums.data); 502 + return c.json(albumsData.data); 457 503 }); 458 504 459 505 app.get("/artists", async (c) => { ··· 462 508 const size = +c.req.query("size") || 100; 463 509 const offset = +c.req.query("offset") || 0; 464 510 465 - const artists = await ctx.analytics.post("library.getArtists", { 511 + const artistsData = await ctx.analytics.post("library.getArtists", { 466 512 pagination: { 467 513 skip: offset, 468 514 take: size, 469 515 }, 470 516 }); 471 517 472 - return c.json(artists.data); 518 + return c.json(artistsData.data); 473 519 }); 474 520 475 521 app.get("/tracks/:sha256", async (c) => { 476 522 requestCounter.add(1, { method: "GET", route: "/tracks/:sha256" }); 477 523 478 524 const sha256 = c.req.param("sha256"); 479 - const track = await ctx.client.db.tracks 480 - .filter("sha256", equals(sha256)) 481 - .getFirst(); 525 + const track = await ctx.db 526 + .select() 527 + .from(tracks) 528 + .where(eq(tracks.sha256, sha256)) 529 + .limit(1) 530 + .then((rows) => rows[0]); 531 + 482 532 return c.json(track); 483 533 }); 484 534 ··· 486 536 requestCounter.add(1, { method: "GET", route: "/albums/:sha256" }); 487 537 488 538 const sha256 = c.req.param("sha256"); 489 - const album = await ctx.client.db.albums 490 - .filter("sha256", equals(sha256)) 491 - .getFirst(); 539 + const album = await ctx.db 540 + .select() 541 + .from(albums) 542 + .where(eq(albums.sha256, sha256)) 543 + .limit(1) 544 + .then((rows) => rows[0]); 492 545 493 546 return c.json(album); 494 547 }); ··· 497 550 requestCounter.add(1, { method: "GET", route: "/artists/:sha256" }); 498 551 499 552 const sha256 = c.req.param("sha256"); 500 - const artist = await ctx.client.db.artists 501 - .filter("sha256", equals(sha256)) 502 - .getFirst(); 553 + const artist = await ctx.db 554 + .select() 555 + .from(artists) 556 + .where(eq(artists.sha256, sha256)) 557 + .limit(1) 558 + .then((rows) => rows[0]); 503 559 504 560 return c.json(artist); 505 561 }); ··· 508 564 requestCounter.add(1, { method: "GET", route: "/artists/:sha256/tracks" }); 509 565 const sha256 = c.req.param("sha256"); 510 566 511 - const tracks = await ctx.client.db.artist_tracks 512 - .select(["track_id.*"]) 513 - .filter("artist_id.sha256", equals(sha256)) 514 - .getAll(); 567 + const artistTracksData = await ctx.db 568 + .select({ 569 + track: tracks, 570 + }) 571 + .from(artistTracks) 572 + .innerJoin(tracks, eq(artistTracks.trackId, tracks.id)) 573 + .innerJoin(artists, eq(artistTracks.artistId, artists.id)) 574 + .where(eq(artists.sha256, sha256)); 515 575 516 - return c.json(tracks); 576 + return c.json(artistTracksData.map((item) => item.track)); 517 577 }); 518 578 519 579 app.get("/albums/:sha256/tracks", async (c) => { 520 580 requestCounter.add(1, { method: "GET", route: "/albums/:sha256/tracks" }); 521 581 const sha256 = c.req.param("sha256"); 522 - const tracks = await ctx.client.db.album_tracks 523 - .select(["track_id.*"]) 524 - .filter("album_id.sha256", equals(sha256)) 525 - .getAll(); 526 582 527 - return c.json(tracks); 583 + const albumTracksData = await ctx.db 584 + .select({ 585 + track: tracks, 586 + }) 587 + .from(albumTracks) 588 + .innerJoin(tracks, eq(albumTracks.trackId, tracks.id)) 589 + .innerJoin(albums, eq(albumTracks.albumId, albums.id)) 590 + .where(eq(albums.sha256, sha256)); 591 + 592 + return c.json(albumTracksData.map((item) => item.track)); 528 593 }); 529 594 530 - app.route("/users", users); 531 - 532 - app.route("/search", search); 595 + app.route("/users", usersApp); 533 596 534 597 app.route("/webscrobbler", webscrobbler); 535 598
+269 -161
apps/api/src/lovedtracks/lovedtracks.service.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 2 import { TID } from "@atproto/common"; 3 - import { equals } from "@xata.io/client"; 4 3 import type { Context } from "context"; 4 + import { and, desc, eq, type SQLWrapper } from "drizzle-orm"; 5 5 import * as LikeLexicon from "lexicon/types/app/rocksky/like"; 6 6 import { validateMain } from "lexicon/types/com/atproto/repo/strongRef"; 7 7 import { createHash } from "node:crypto"; 8 8 import type { Track } from "types/track"; 9 + import albumTracks from "../schema/album-tracks"; 10 + import albums from "../schema/albums"; 11 + import artistAlbums from "../schema/artist-albums"; 12 + import artistTracks from "../schema/artist-tracks"; 13 + import artists from "../schema/artists"; 14 + import lovedTracks from "../schema/loved-tracks"; 15 + import tracks from "../schema/tracks"; 9 16 10 17 export async function likeTrack( 11 18 ctx: Context, 12 19 track: Track, 13 20 user, 14 - agent: Agent, 21 + agent: Agent 15 22 ) { 16 - const existingTrack = await ctx.client.db.tracks 17 - .filter( 18 - "sha256", 19 - equals( 20 - createHash("sha256") 21 - .update( 22 - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), 23 - ) 24 - .digest("hex"), 25 - ), 26 - ) 27 - .getFirst(); 23 + const trackSha256 = createHash("sha256") 24 + .update(`${track.title} - ${track.artist} - ${track.album}`.toLowerCase()) 25 + .digest("hex"); 28 26 29 - const { xata_id: track_id } = await ctx.client.db.tracks.createOrUpdate( 30 - existingTrack?.xata_id, 31 - { 32 - title: track.title, 33 - artist: track.artist, 34 - album: track.album, 35 - album_art: track.albumArt, 36 - album_artist: track.albumArtist, 37 - track_number: track.trackNumber, 38 - duration: track.duration, 39 - mb_id: track.mbId, 40 - composer: track.composer, 41 - lyrics: track.lyrics, 42 - disc_number: track.discNumber, 43 - // compute sha256 (lowercase(title + artist + album)) 44 - sha256: createHash("sha256") 45 - .update( 46 - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), 47 - ) 48 - .digest("hex"), 49 - }, 50 - ); 27 + const existingTrack = await ctx.db 28 + .select() 29 + .from(tracks) 30 + .where(eq(tracks.sha256, trackSha256)) 31 + .limit(1) 32 + .then((rows) => rows[0]); 51 33 52 - const existingArtist = await ctx.client.db.artists 53 - .filter( 54 - "sha256", 55 - equals( 56 - createHash("sha256") 57 - .update(track.albumArtist.toLocaleLowerCase()) 58 - .digest("hex"), 59 - ), 60 - ) 61 - .getFirst(); 62 - const { xata_id: artist_id } = await ctx.client.db.artists.createOrUpdate( 63 - existingArtist?.xata_id, 64 - { 65 - name: track.albumArtist, 66 - // compute sha256 (lowercase(name)) 67 - sha256: createHash("sha256") 68 - .update(track.albumArtist.toLowerCase()) 69 - .digest("hex"), 70 - }, 71 - ); 34 + let trackId: string; 35 + if (existingTrack) { 36 + const [updatedTrack] = await ctx.db 37 + .update(tracks) 38 + .set({ 39 + title: track.title, 40 + artist: track.artist, 41 + album: track.album, 42 + albumArt: track.albumArt, 43 + albumArtist: track.albumArtist, 44 + trackNumber: track.trackNumber, 45 + duration: track.duration, 46 + mbId: track.mbId, 47 + composer: track.composer, 48 + lyrics: track.lyrics, 49 + discNumber: track.discNumber, 50 + sha256: trackSha256, 51 + }) 52 + .where(eq(tracks.id, existingTrack.id)) 53 + .returning(); 54 + trackId = updatedTrack.id; 55 + } else { 56 + const [createdTrack] = await ctx.db 57 + .insert(tracks) 58 + .values({ 59 + title: track.title, 60 + artist: track.artist, 61 + album: track.album, 62 + albumArt: track.albumArt, 63 + albumArtist: track.albumArtist, 64 + trackNumber: track.trackNumber, 65 + duration: track.duration, 66 + mbId: track.mbId, 67 + composer: track.composer, 68 + lyrics: track.lyrics, 69 + discNumber: track.discNumber, 70 + sha256: trackSha256, 71 + }) 72 + .returning(); 73 + trackId = createdTrack.id; 74 + } 72 75 73 - const existingAlbum = await ctx.client.db.albums 74 - .filter( 75 - "sha256", 76 - equals( 77 - createHash("sha256") 78 - .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 79 - .digest("hex"), 80 - ), 81 - ) 82 - .getFirst(); 76 + const artistSha256 = createHash("sha256") 77 + .update(track.albumArtist.toLowerCase()) 78 + .digest("hex"); 83 79 84 - const { xata_id: album_id } = await ctx.client.db.albums.createOrUpdate( 85 - existingAlbum?.xata_id, 86 - { 87 - title: track.album, 88 - artist: track.albumArtist, 89 - album_art: track.albumArt, 90 - year: track.year, 91 - release_date: track.releaseDate 92 - ? track.releaseDate.toISOString() 93 - : undefined, 94 - // compute sha256 (lowercase(title + artist)) 95 - sha256: createHash("sha256") 96 - .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 97 - .digest("hex"), 98 - }, 99 - ); 80 + const existingArtist = await ctx.db 81 + .select() 82 + .from(artists) 83 + .where(eq(artists.sha256, artistSha256)) 84 + .limit(1) 85 + .then((rows) => rows[0]); 100 86 101 - const existingAlbumTrack = await ctx.client.db.album_tracks 102 - .filter("album_id", equals(album_id)) 103 - .filter("track_id", equals(track_id)) 104 - .getFirst(); 87 + let artistId: string; 88 + if (existingArtist) { 89 + const [updatedArtist] = await ctx.db 90 + .update(artists) 91 + .set({ 92 + name: track.albumArtist, 93 + sha256: artistSha256, 94 + }) 95 + .where(eq(artists.id, existingArtist.id)) 96 + .returning(); 97 + artistId = updatedArtist.id; 98 + } else { 99 + const [createdArtist] = await ctx.db 100 + .insert(artists) 101 + .values({ 102 + name: track.albumArtist, 103 + sha256: artistSha256, 104 + }) 105 + .returning(); 106 + artistId = createdArtist.id; 107 + } 105 108 106 - await ctx.client.db.album_tracks.createOrUpdate(existingAlbumTrack?.xata_id, { 107 - album_id, 108 - track_id, 109 - }); 109 + const albumSha256 = createHash("sha256") 110 + .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 111 + .digest("hex"); 110 112 111 - const existingArtistTrack = await ctx.client.db.artist_tracks 112 - .filter("artist_id", equals(artist_id)) 113 - .filter("track_id", equals(track_id)) 114 - .getFirst(); 113 + const existingAlbum = await ctx.db 114 + .select() 115 + .from(albums) 116 + .where(eq(albums.sha256, albumSha256)) 117 + .limit(1) 118 + .then((rows) => rows[0]); 115 119 116 - await ctx.client.db.artist_tracks.createOrUpdate( 117 - existingArtistTrack?.xata_id, 118 - { 119 - artist_id, 120 - track_id, 121 - }, 122 - ); 120 + let albumId: string; 121 + if (existingAlbum) { 122 + const [updatedAlbum] = await ctx.db 123 + .update(albums) 124 + .set({ 125 + title: track.album, 126 + artist: track.albumArtist, 127 + albumArt: track.albumArt, 128 + year: track.year, 129 + releaseDate: track.releaseDate 130 + ? track.releaseDate.toISOString() 131 + : undefined, 132 + sha256: albumSha256, 133 + }) 134 + .where(eq(albums.id, existingAlbum.id)) 135 + .returning(); 136 + albumId = updatedAlbum.id; 137 + } else { 138 + const [createdAlbum] = await ctx.db 139 + .insert(albums) 140 + .values({ 141 + title: track.album, 142 + artist: track.albumArtist, 143 + albumArt: track.albumArt, 144 + year: track.year, 145 + releaseDate: track.releaseDate 146 + ? track.releaseDate.toISOString() 147 + : undefined, 148 + sha256: albumSha256, 149 + }) 150 + .returning(); 151 + albumId = createdAlbum.id; 152 + } 123 153 124 - const existingArtistAlbum = await ctx.client.db.artist_albums 125 - .filter("artist_id", equals(artist_id)) 126 - .filter("album_id", equals(album_id)) 127 - .getFirst(); 154 + // Create or update album_tracks relationship 155 + const existingAlbumTrack = await ctx.db 156 + .select() 157 + .from(albumTracks) 158 + .where( 159 + and(eq(albumTracks.albumId, albumId), eq(albumTracks.trackId, trackId)) 160 + ) 161 + .limit(1) 162 + .then((rows) => rows[0]); 128 163 129 - await ctx.client.db.artist_albums.createOrUpdate( 130 - existingArtistAlbum?.xata_id, 131 - { 132 - artist_id, 133 - album_id, 134 - }, 135 - ); 164 + if (!existingAlbumTrack) { 165 + await ctx.db.insert(albumTracks).values({ 166 + albumId, 167 + trackId, 168 + }); 169 + } 136 170 137 - const lovedTrack = await ctx.client.db.loved_tracks 138 - .filter("user_id", equals(user.xata_id)) 139 - .filter("track_id", equals(track_id)) 140 - .getFirst(); 171 + // Create or update artist_tracks relationship 172 + const existingArtistTrack = await ctx.db 173 + .select() 174 + .from(artistTracks) 175 + .where( 176 + and( 177 + eq(artistTracks.artistId, artistId), 178 + eq(artistTracks.trackId, trackId) 179 + ) 180 + ) 181 + .limit(1) 182 + .then((rows) => rows[0]); 183 + 184 + if (!existingArtistTrack) { 185 + await ctx.db.insert(artistTracks).values({ 186 + artistId, 187 + trackId, 188 + }); 189 + } 190 + 191 + // Create or update artist_albums relationship 192 + const existingArtistAlbum = await ctx.db 193 + .select() 194 + .from(artistAlbums) 195 + .where( 196 + and( 197 + eq(artistAlbums.artistId, artistId), 198 + eq(artistAlbums.albumId, albumId) 199 + ) 200 + ) 201 + .limit(1) 202 + .then((rows) => rows[0]); 203 + 204 + if (!existingArtistAlbum) { 205 + await ctx.db.insert(artistAlbums).values({ 206 + artistId, 207 + albumId, 208 + }); 209 + } 210 + 211 + // Create or update loved track 212 + const existingLovedTrack = await ctx.db 213 + .select() 214 + .from(lovedTracks) 215 + .where( 216 + and(eq(lovedTracks.userId, user.id), eq(lovedTracks.trackId, trackId)) 217 + ) 218 + .limit(1) 219 + .then((rows) => rows[0]); 220 + 221 + let created: { id: string | SQLWrapper }; 222 + if (existingLovedTrack) { 223 + [created] = await ctx.db 224 + .update(lovedTracks) 225 + .set({ 226 + userId: user.id, 227 + trackId, 228 + }) 229 + .where(eq(lovedTracks.id, existingLovedTrack.id)) 230 + .returning(); 231 + } else { 232 + [created] = await ctx.db 233 + .insert(lovedTracks) 234 + .values({ 235 + userId: user.id, 236 + trackId, 237 + }) 238 + .returning(); 239 + } 141 240 142 - let created = await ctx.client.db.loved_tracks.createOrUpdate( 143 - lovedTrack?.xata_id, 144 - { 145 - user_id: user.xata_id, 146 - track_id, 147 - }, 148 - ); 241 + // Get the track with uri for ATProto operations 242 + const trackWithUri = await ctx.db 243 + .select() 244 + .from(tracks) 245 + .where(eq(tracks.id, trackId)) 246 + .limit(1) 247 + .then((rows) => rows[0]); 149 248 150 - if (existingTrack.uri) { 249 + if (trackWithUri?.uri) { 151 250 const rkey = TID.nextStr(); 152 251 const subjectRecord = await agent.com.atproto.repo.getRecord({ 153 - repo: existingTrack.uri 154 - .split("/") 155 - .slice(0, 3) 156 - .join("/") 157 - .split("at://")[1], 252 + repo: trackWithUri.uri.split("/").slice(0, 3).join("/").split("at://")[1], 158 253 collection: "app.rocksky.song", 159 - rkey: existingTrack.uri.split("/").pop(), 254 + rkey: trackWithUri.uri.split("/").pop(), 160 255 }); 161 256 162 257 const subjectRef = validateMain({ 163 - uri: existingTrack.uri, 258 + uri: trackWithUri.uri, 164 259 cid: subjectRecord.data.cid, 165 260 }); 166 261 if (!subjectRef.success) { ··· 188 283 }); 189 284 const uri = res.data.uri; 190 285 console.log(`Like record created at: ${uri}`); 191 - created = await ctx.client.db.loved_tracks.update(created.xata_id, { 192 - uri, 193 - }); 286 + 287 + [created] = await ctx.db 288 + .update(lovedTracks) 289 + .set({ uri }) 290 + .where(eq(lovedTracks.id, created.id)) 291 + .returning(); 194 292 } catch (e) { 195 293 console.error(`Error creating like record: ${e.message}`); 196 294 } ··· 206 304 ctx: Context, 207 305 trackSha256: string, 208 306 user, 209 - agent: Agent, 307 + agent: Agent 210 308 ) { 211 - const track = await ctx.client.db.tracks 212 - .filter("sha256", equals(trackSha256)) 213 - .getFirst(); 309 + const track = await ctx.db 310 + .select() 311 + .from(tracks) 312 + .where(eq(tracks.sha256, trackSha256)) 313 + .limit(1) 314 + .then((rows) => rows[0]); 214 315 215 316 if (!track) { 216 317 return; 217 318 } 218 319 219 - const lovedTrack = await ctx.client.db.loved_tracks 220 - .filter("user_id", equals(user.xata_id)) 221 - .filter("track_id", equals(track.xata_id)) 222 - .getFirst(); 320 + const lovedTrack = await ctx.db 321 + .select() 322 + .from(lovedTracks) 323 + .where( 324 + and(eq(lovedTracks.userId, user.id), eq(lovedTracks.trackId, track.id)) 325 + ) 326 + .limit(1) 327 + .then((rows) => rows[0]); 223 328 224 329 if (!lovedTrack) { 225 330 return; 226 331 } 227 332 228 - const rkey = lovedTrack.uri.split("/").pop(); 333 + const rkey = lovedTrack.uri?.split("/").pop(); 229 334 230 335 await Promise.all([ 231 - agent.com.atproto.repo.deleteRecord({ 232 - repo: agent.assertDid, 233 - collection: "app.rocksky.like", 234 - rkey, 235 - }), 236 - ctx.client.db.loved_tracks.delete(lovedTrack.xata_id), 336 + rkey 337 + ? agent.com.atproto.repo.deleteRecord({ 338 + repo: agent.assertDid, 339 + collection: "app.rocksky.like", 340 + rkey, 341 + }) 342 + : Promise.resolve(), 343 + ctx.db.delete(lovedTracks).where(eq(lovedTracks.id, lovedTrack.id)), 237 344 ]); 238 345 239 346 const message = JSON.stringify(lovedTrack); ··· 244 351 ctx: Context, 245 352 user, 246 353 size = 10, 247 - offset = 0, 354 + offset = 0 248 355 ) { 249 - const lovedTracks = await ctx.client.db.loved_tracks 250 - .select(["track_id.*"]) 251 - .filter("user_id", equals(user.xata_id)) 252 - .sort("xata_createdat", "desc") 253 - .getPaginated({ 254 - pagination: { 255 - size, 256 - offset, 257 - }, 258 - }); 356 + const lovedTracksData = await ctx.db 357 + .select({ 358 + lovedTrack: lovedTracks, 359 + track: tracks, 360 + }) 361 + .from(lovedTracks) 362 + .innerJoin(tracks, eq(lovedTracks.trackId, tracks.id)) 363 + .where(eq(lovedTracks.userId, user.id)) 364 + .orderBy(desc(lovedTracks.createdAt)) 365 + .limit(size) 366 + .offset(offset); 259 367 260 - return lovedTracks.records; 368 + return lovedTracksData.map((item) => item.track); 261 369 }
+343 -212
apps/api/src/nowplaying/nowplaying.service.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 2 import { TID } from "@atproto/common"; 3 - import { equals } from "@xata.io/client"; 4 3 import chalk from "chalk"; 5 4 import type { Context } from "context"; 6 5 import dayjs from "dayjs"; 6 + import { and, eq, gte, lte, or } from "drizzle-orm"; 7 7 import * as Album from "lexicon/types/app/rocksky/album"; 8 8 import * as Artist from "lexicon/types/app/rocksky/artist"; 9 9 import * as Scrobble from "lexicon/types/app/rocksky/scrobble"; 10 10 import * as Song from "lexicon/types/app/rocksky/song"; 11 + import { deepSnakeCaseKeys } from "lib"; 11 12 import { createHash } from "node:crypto"; 12 13 import type { Track } from "types/track"; 14 + import albumTracks from "../schema/album-tracks"; 15 + import albums from "../schema/albums"; 16 + import artistAlbums from "../schema/artist-albums"; 17 + import artistTracks from "../schema/artist-tracks"; 18 + import artists from "../schema/artists"; 19 + import scrobbles from "../schema/scrobbles"; 20 + import tracks from "../schema/tracks"; 21 + import userAlbums from "../schema/user-albums"; 22 + import userArtists from "../schema/user-artists"; 23 + import userTracks from "../schema/user-tracks"; 24 + import users from "../schema/users"; 13 25 14 26 export async function putArtistRecord( 15 27 track: Track, ··· 200 212 } 201 213 202 214 export async function publishScrobble(ctx: Context, id: string) { 203 - const scrobble = await ctx.client.db.scrobbles 204 - .select(["*", "track_id.*", "album_id.*", "artist_id.*", "user_id.*"]) 205 - .filter("xata_id", equals(id)) 206 - .getFirst(); 215 + const scrobble = await ctx.db 216 + .select({ 217 + scrobble: scrobbles, 218 + track: tracks, 219 + album: albums, 220 + artist: artists, 221 + user: users, 222 + }) 223 + .from(scrobbles) 224 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 225 + .innerJoin(albums, eq(scrobbles.albumId, albums.id)) 226 + .innerJoin(artists, eq(scrobbles.artistId, artists.id)) 227 + .innerJoin(users, eq(scrobbles.userId, users.id)) 228 + .where(eq(scrobbles.id, id)) 229 + .limit(1) 230 + .then((rows) => rows[0]); 207 231 208 232 const [ 209 233 _user_album, ··· 213 237 artist_track, 214 238 artist_album, 215 239 ] = await Promise.all([ 216 - ctx.client.db.user_albums 217 - .select(["*"]) 218 - .filter("album_id.xata_id", equals(scrobble.album_id.xata_id)) 219 - .getFirst(), 220 - ctx.client.db.user_artists 221 - .select(["*"]) 222 - .filter("artist_id.xata_id", equals(scrobble.artist_id.xata_id)) 223 - .getFirst(), 224 - ctx.client.db.user_tracks 225 - .select(["*"]) 226 - .filter("track_id.xata_id", equals(scrobble.track_id.xata_id)) 227 - .getFirst(), 228 - ctx.client.db.album_tracks 229 - .select(["*"]) 230 - .filter("track_id.xata_id", equals(scrobble.track_id.xata_id)) 231 - .getFirst(), 232 - ctx.client.db.artist_tracks 233 - .select(["*"]) 234 - .filter("track_id.xata_id", equals(scrobble.track_id.xata_id)) 235 - .getFirst(), 236 - ctx.client.db.artist_albums 237 - .select(["*"]) 238 - .filter("album_id.xata_id", equals(scrobble.album_id.xata_id)) 239 - .filter("artist_id.xata_id", equals(scrobble.artist_id.xata_id)) 240 - .getFirst(), 240 + ctx.db 241 + .select() 242 + .from(userAlbums) 243 + .where(eq(userAlbums.albumId, scrobble.album.id)) 244 + .limit(1) 245 + .then((rows) => rows[0]), 246 + ctx.db 247 + .select() 248 + .from(userArtists) 249 + .where(eq(userArtists.artistId, scrobble.artist.id)) 250 + .limit(1) 251 + .then((rows) => rows[0]), 252 + ctx.db 253 + .select() 254 + .from(userTracks) 255 + .where(eq(userTracks.trackId, scrobble.track.id)) 256 + .limit(1) 257 + .then((rows) => rows[0]), 258 + ctx.db 259 + .select() 260 + .from(albumTracks) 261 + .where(eq(albumTracks.trackId, scrobble.track.id)) 262 + .limit(1) 263 + .then((rows) => rows[0]), 264 + ctx.db 265 + .select() 266 + .from(artistTracks) 267 + .where(eq(artistTracks.trackId, scrobble.track.id)) 268 + .limit(1) 269 + .then((rows) => rows[0]), 270 + ctx.db 271 + .select() 272 + .from(artistAlbums) 273 + .where( 274 + and( 275 + eq(artistAlbums.albumId, scrobble.album.id), 276 + eq(artistAlbums.artistId, scrobble.artist.id) 277 + ) 278 + ) 279 + .limit(1) 280 + .then((rows) => rows[0]), 241 281 ]); 242 282 243 283 let user_artist = _user_artist; 244 284 if (!user_artist) { 245 - await ctx.client.db.user_artists.create({ 246 - user_id: scrobble.user_id.xata_id, 247 - artist_id: scrobble.artist_id.xata_id, 248 - uri: scrobble.artist_id.uri, 285 + await ctx.db.insert(userArtists).values({ 286 + userId: scrobble.user.id, 287 + artistId: scrobble.artist.id, 288 + uri: scrobble.artist.uri, 249 289 scrobbles: 1, 250 290 }); 251 - user_artist = await ctx.client.db.user_artists 252 - .select(["*"]) 253 - .filter("artist_id.xata_id", equals(scrobble.artist_id.xata_id)) 254 - .getFirst(); 291 + user_artist = await ctx.db 292 + .select() 293 + .from(userArtists) 294 + .where(eq(userArtists.artistId, scrobble.artist.id)) 295 + .limit(1) 296 + .then((rows) => rows[0]); 255 297 } 256 298 257 299 let user_album = _user_album; 258 300 if (!user_album) { 259 - await ctx.client.db.user_albums.create({ 260 - user_id: scrobble.user_id.xata_id, 261 - album_id: scrobble.album_id.xata_id, 262 - uri: scrobble.album_id.uri, 301 + await ctx.db.insert(userAlbums).values({ 302 + userId: scrobble.user.id, 303 + albumId: scrobble.album.id, 304 + uri: scrobble.album.uri, 263 305 scrobbles: 1, 264 306 }); 265 - user_album = await ctx.client.db.user_albums 266 - .select(["*"]) 267 - .filter("album_id.xata_id", equals(scrobble.album_id.xata_id)) 268 - .getFirst(); 307 + user_album = await ctx.db 308 + .select() 309 + .from(userAlbums) 310 + .where(eq(userAlbums.albumId, scrobble.album.id)) 311 + .limit(1) 312 + .then((rows) => rows[0]); 269 313 } 270 314 271 315 let user_track = _user_track; 272 316 if (!user_track) { 273 - await ctx.client.db.user_tracks.create({ 274 - user_id: scrobble.user_id.xata_id, 275 - track_id: scrobble.track_id.xata_id, 276 - uri: scrobble.track_id.uri, 317 + await ctx.db.insert(userTracks).values({ 318 + userId: scrobble.user.id, 319 + trackId: scrobble.track.id, 320 + uri: scrobble.track.uri, 277 321 scrobbles: 1, 278 322 }); 279 - user_track = await ctx.client.db.user_tracks 280 - .select(["*"]) 281 - .filter("track_id.xata_id", equals(scrobble.track_id.xata_id)) 282 - .getFirst(); 323 + user_track = await ctx.db 324 + .select() 325 + .from(userTracks) 326 + .where(eq(userTracks.trackId, scrobble.track.id)) 327 + .limit(1) 328 + .then((rows) => rows[0]); 283 329 } 284 330 285 - const message = JSON.stringify({ 286 - scrobble, 287 - user_album, 288 - user_artist, 289 - user_track, 290 - album_track, 291 - artist_track, 292 - artist_album, 293 - }); 331 + const message = JSON.stringify( 332 + deepSnakeCaseKeys({ 333 + scrobble, 334 + user_album, 335 + user_artist, 336 + user_track, 337 + album_track, 338 + artist_track, 339 + artist_album, 340 + }) 341 + ); 294 342 295 343 ctx.nc.publish("rocksky.scrobble", Buffer.from(message)); 296 344 297 - const trackMessage = JSON.stringify({ 298 - track: scrobble.track_id, 299 - album_track, 300 - artist_track, 301 - artist_album, 302 - }); 345 + const trackMessage = JSON.stringify( 346 + deepSnakeCaseKeys({ 347 + track: scrobble.track, 348 + album_track, 349 + artist_track, 350 + artist_album, 351 + }) 352 + ); 303 353 304 354 ctx.nc.publish("rocksky.track", Buffer.from(trackMessage)); 305 355 } ··· 312 362 ): Promise<void> { 313 363 // check if scrobble already exists (user did + timestamp) 314 364 const scrobbleTime = dayjs.unix(track.timestamp || dayjs().unix()); 315 - const existingScrobble = await ctx.client.db.scrobbles 316 - .filter("user_id.did", equals(userDid)) 317 - .filter("track_id.title", equals(track.title)) 318 - .filter("track_id.artist", equals(track.artist)) 319 - .filter({ 320 - $all: [ 321 - { 322 - timestamp: { 323 - $ge: scrobbleTime.subtract(5, "seconds").toISOString(), 324 - }, 325 - }, 326 - { timestamp: { $le: scrobbleTime.add(5, "seconds").toISOString() } }, 327 - ], 365 + const existingScrobble = await ctx.db 366 + .select({ 367 + scrobble: scrobbles, 368 + user: users, 369 + track: tracks, 328 370 }) 329 - .getFirst(); 371 + .from(scrobbles) 372 + .innerJoin(users, eq(scrobbles.userId, users.id)) 373 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 374 + .where( 375 + and( 376 + eq(users.did, userDid), 377 + eq(tracks.title, track.title), 378 + eq(tracks.artist, track.artist), 379 + gte(scrobbles.timestamp, scrobbleTime.subtract(5, "seconds").toDate()), 380 + lte(scrobbles.timestamp, scrobbleTime.add(5, "seconds").toDate()) 381 + ) 382 + ) 383 + .limit(1) 384 + .then((rows) => rows[0]); 330 385 331 386 if (existingScrobble) { 332 387 console.log( ··· 337 392 return; 338 393 } 339 394 340 - let existingTrack = await ctx.client.db.tracks 341 - .filter( 342 - "sha256", 343 - equals( 395 + let existingTrack = await ctx.db 396 + .select() 397 + .from(tracks) 398 + .where( 399 + eq( 400 + tracks.sha256, 344 401 createHash("sha256") 345 402 .update( 346 403 `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() ··· 348 405 .digest("hex") 349 406 ) 350 407 ) 351 - .getFirst(); 408 + .limit(1) 409 + .then((rows) => rows[0]); 352 410 353 - if (existingTrack && !existingTrack.album_uri) { 354 - const album = await ctx.client.db.albums 355 - .filter( 356 - "sha256", 357 - equals( 411 + if (existingTrack && !existingTrack.albumUri) { 412 + const album = await ctx.db 413 + .select() 414 + .from(albums) 415 + .where( 416 + eq( 417 + albums.sha256, 358 418 createHash("sha256") 359 419 .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 360 420 .digest("hex") 361 421 ) 362 422 ) 363 - .getFirst(); 423 + .limit(1) 424 + .then((rows) => rows[0]); 364 425 if (album) { 365 - await ctx.client.db.tracks.update(existingTrack.xata_id, { 366 - album_uri: album.uri, 367 - }); 426 + await ctx.db 427 + .update(tracks) 428 + .set({ albumUri: album.uri }) 429 + .where(eq(tracks.id, existingTrack.id)); 368 430 } 369 431 } 370 432 371 - if (existingTrack && !existingTrack.artist_uri) { 372 - const artist = await ctx.client.db.artists 373 - .filter( 374 - "sha256", 375 - equals( 433 + if (existingTrack && !existingTrack.artistUri) { 434 + const artist = await ctx.db 435 + .select() 436 + .from(artists) 437 + .where( 438 + eq( 439 + artists.sha256, 376 440 createHash("sha256") 377 441 .update(track.albumArtist.toLowerCase()) 378 442 .digest("hex") 379 443 ) 380 444 ) 381 - .getFirst(); 445 + .limit(1) 446 + .then((rows) => rows[0]); 382 447 if (artist) { 383 - await ctx.client.db.tracks.update(existingTrack.xata_id, { 384 - artist_uri: artist.uri, 385 - }); 448 + await ctx.db 449 + .update(tracks) 450 + .set({ artistUri: artist.uri }) 451 + .where(eq(tracks.id, existingTrack.id)); 386 452 } 387 453 } 388 454 389 - const userTrack = await ctx.client.db.user_tracks 390 - .filter({ 391 - "track_id.xata_id": existingTrack?.xata_id, 392 - "user_id.did": userDid, 455 + const userTrack = await ctx.db 456 + .select({ 457 + userTrack: userTracks, 458 + track: tracks, 459 + user: users, 393 460 }) 394 - .getFirst(); 461 + .from(userTracks) 462 + .innerJoin(tracks, eq(userTracks.trackId, tracks.id)) 463 + .innerJoin(users, eq(userTracks.userId, users.id)) 464 + .where(and(eq(tracks.id, existingTrack?.id || ""), eq(users.did, userDid))) 465 + .limit(1) 466 + .then((rows) => rows[0]); 395 467 396 - if (!existingTrack?.uri || !userTrack?.uri?.includes(userDid)) { 468 + if (!existingTrack?.uri || !userTrack?.userTrack.uri?.includes(userDid)) { 397 469 await putSongRecord(track, agent); 398 470 } 399 471 400 - const existingAlbum = await ctx.client.db.albums 401 - .filter( 402 - "sha256", 403 - equals( 472 + const existingAlbum = await ctx.db 473 + .select() 474 + .from(albums) 475 + .where( 476 + eq( 477 + albums.sha256, 404 478 createHash("sha256") 405 479 .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 406 480 .digest("hex") 407 481 ) 408 482 ) 409 - .getFirst(); 483 + .limit(1) 484 + .then((rows) => rows[0]); 410 485 411 486 let tries = 0; 412 487 while (!existingTrack && tries < 30) { 413 488 console.log(`Song not found, trying again: ${chalk.magenta(tries + 1)}`); 414 - existingTrack = await ctx.client.db.tracks 415 - .filter( 416 - "sha256", 417 - equals( 489 + existingTrack = await ctx.db 490 + .select() 491 + .from(tracks) 492 + .where( 493 + eq( 494 + tracks.sha256, 418 495 createHash("sha256") 419 496 .update( 420 497 `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() ··· 422 499 .digest("hex") 423 500 ) 424 501 ) 425 - .getFirst(); 502 + .limit(1) 503 + .then((rows) => rows[0]); 426 504 await new Promise((resolve) => setTimeout(resolve, 1000)); 427 505 tries += 1; 428 506 } ··· 433 511 434 512 if (existingTrack) { 435 513 console.log( 436 - `Song found: ${chalk.cyan(existingTrack.xata_id)} - ${track.title}, after ${chalk.magenta(tries)} tries` 514 + `Song found: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries` 437 515 ); 438 516 } 439 517 440 - const existingArtist = await ctx.client.db.artists 441 - .filter({ 442 - $any: [ 443 - { 444 - sha256: createHash("sha256") 445 - .update(track.albumArtist.toLocaleLowerCase()) 446 - .digest("hex"), 447 - }, 448 - { 449 - sha256: createHash("sha256") 450 - .update(track.artist.toLocaleLowerCase()) 451 - .digest("hex"), 452 - }, 453 - ], 454 - }) 455 - .getFirst(); 518 + const existingArtist = await ctx.db 519 + .select() 520 + .from(artists) 521 + .where( 522 + or( 523 + eq( 524 + artists.sha256, 525 + createHash("sha256") 526 + .update(track.albumArtist.toLowerCase()) 527 + .digest("hex") 528 + ), 529 + eq( 530 + artists.sha256, 531 + createHash("sha256").update(track.artist.toLowerCase()).digest("hex") 532 + ) 533 + ) 534 + ) 535 + .limit(1) 536 + .then((rows) => rows[0]); 456 537 457 - const userArtist = await ctx.client.db.user_artists 458 - .filter({ 459 - "artist_id.xata_id": existingArtist?.xata_id, 460 - "user_id.did": userDid, 538 + const userArtist = await ctx.db 539 + .select({ 540 + userArtist: userArtists, 541 + artist: artists, 542 + user: users, 461 543 }) 462 - .getFirst(); 544 + .from(userArtists) 545 + .innerJoin(artists, eq(userArtists.artistId, artists.id)) 546 + .innerJoin(users, eq(userArtists.userId, users.id)) 547 + .where( 548 + and(eq(artists.id, existingArtist?.id || ""), eq(users.did, userDid)) 549 + ) 550 + .limit(1) 551 + .then((rows) => rows[0]); 463 552 464 - if (!existingArtist?.uri || !userArtist?.uri?.includes(userDid)) { 553 + if (!existingArtist?.uri || !userArtist?.userArtist.uri?.includes(userDid)) { 465 554 await putArtistRecord(track, agent); 466 555 } 467 556 468 - const userAlbum = await ctx.client.db.user_albums 469 - .filter({ 470 - "album_id.xata_id": existingAlbum?.xata_id, 471 - "user_id.did": userDid, 557 + const userAlbum = await ctx.db 558 + .select({ 559 + userAlbum: userAlbums, 560 + album: albums, 561 + user: users, 472 562 }) 473 - .getFirst(); 563 + .from(userAlbums) 564 + .innerJoin(albums, eq(userAlbums.albumId, albums.id)) 565 + .innerJoin(users, eq(userAlbums.userId, users.id)) 566 + .where(and(eq(albums.id, existingAlbum?.id || ""), eq(users.did, userDid))) 567 + .limit(1) 568 + .then((rows) => rows[0]); 474 569 475 - if (!existingAlbum?.uri || !userAlbum?.uri?.includes(userDid)) { 570 + if (!existingAlbum?.uri || !userAlbum?.userAlbum.uri?.includes(userDid)) { 476 571 await putAlbumRecord(track, agent); 477 572 } 478 573 479 574 tries = 0; 480 - existingTrack = await ctx.client.db.tracks 481 - .filter( 482 - "sha256", 483 - equals( 575 + existingTrack = await ctx.db 576 + .select() 577 + .from(tracks) 578 + .where( 579 + eq( 580 + tracks.sha256, 484 581 createHash("sha256") 485 582 .update( 486 583 `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() ··· 488 585 .digest("hex") 489 586 ) 490 587 ) 491 - .getFirst(); 588 + .limit(1) 589 + .then((rows) => rows[0]); 492 590 493 - while ( 494 - !existingTrack?.artist_uri && 495 - !existingTrack?.album_uri && 496 - tries < 30 497 - ) { 591 + while (!existingTrack?.artistUri && !existingTrack?.albumUri && tries < 30) { 498 592 console.log( 499 593 `Artist uri not ready, trying again: ${chalk.magenta(tries + 1)}` 500 594 ); 501 - existingTrack = await ctx.client.db.tracks 502 - .filter( 503 - "sha256", 504 - equals( 595 + existingTrack = await ctx.db 596 + .select() 597 + .from(tracks) 598 + .where( 599 + eq( 600 + tracks.sha256, 505 601 createHash("sha256") 506 602 .update( 507 603 `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() ··· 509 605 .digest("hex") 510 606 ) 511 607 ) 512 - .getFirst(); 608 + .limit(1) 609 + .then((rows) => rows[0]); 513 610 514 611 // start update artist uri if it is not set 515 - if (existingTrack && !existingTrack.artist_uri) { 516 - const artist = await ctx.client.db.artists 517 - .filter( 518 - "sha256", 519 - equals( 612 + if (existingTrack && !existingTrack.artistUri) { 613 + const artist = await ctx.db 614 + .select() 615 + .from(artists) 616 + .where( 617 + eq( 618 + artists.sha256, 520 619 createHash("sha256") 521 620 .update(track.albumArtist.toLowerCase()) 522 621 .digest("hex") 523 622 ) 524 623 ) 525 - .getFirst(); 624 + .limit(1) 625 + .then((rows) => rows[0]); 526 626 if (artist) { 527 - await ctx.client.db.tracks.update(existingTrack.xata_id, { 528 - artist_uri: artist.uri, 529 - }); 627 + await ctx.db 628 + .update(tracks) 629 + .set({ artistUri: artist.uri }) 630 + .where(eq(tracks.id, existingTrack.id)); 530 631 } 531 632 } 532 633 // end update artist uri 533 634 534 635 // start update album uri if it is not set 535 - if (existingTrack && !existingTrack.album_uri) { 536 - const album = await ctx.client.db.albums 537 - .filter( 538 - "sha256", 539 - equals( 636 + if (existingTrack && !existingTrack.albumUri) { 637 + const album = await ctx.db 638 + .select() 639 + .from(albums) 640 + .where( 641 + eq( 642 + albums.sha256, 540 643 createHash("sha256") 541 644 .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 542 645 .digest("hex") 543 646 ) 544 647 ) 545 - .getFirst(); 648 + .limit(1) 649 + .then((rows) => rows[0]); 546 650 if (album) { 547 - await ctx.client.db.tracks.update(existingTrack.xata_id, { 548 - album_uri: album.uri, 549 - }); 651 + await ctx.db 652 + .update(tracks) 653 + .set({ albumUri: album.uri }) 654 + .where(eq(tracks.id, existingTrack.id)); 550 655 551 - if (!album.artist_uri && existingTrack?.artist_uri) { 552 - await ctx.client.db.albums.update(album.xata_id, { 553 - artist_uri: existingTrack.artist_uri, 554 - }); 656 + if (!album.artistUri && existingTrack?.artistUri) { 657 + await ctx.db 658 + .update(albums) 659 + .set({ artistUri: existingTrack.artistUri }) 660 + .where(eq(albums.id, album.id)); 555 661 } 556 662 } 557 663 } ··· 561 667 tries += 1; 562 668 } 563 669 564 - if (tries === 30 && !existingTrack?.artist_uri) { 670 + if (tries === 30 && !existingTrack?.artistUri) { 565 671 console.log(`Artist uri not ready after ${chalk.magenta("30 tries")}`); 566 672 } 567 673 568 - if (existingTrack?.artist_uri) { 674 + if (existingTrack?.artistUri) { 569 675 console.log( 570 - `Artist uri ready: ${chalk.cyan(existingTrack.xata_id)} - ${track.title}, after ${chalk.magenta(tries)} tries` 676 + `Artist uri ready: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries` 571 677 ); 572 678 } 573 679 ··· 577 683 tries = 0; 578 684 let scrobble = null; 579 685 while (!scrobble && tries < 30) { 580 - scrobble = await ctx.client.db.scrobbles 581 - .select(["*", "track_id.*", "album_id.*", "artist_id.*", "user_id.*"]) 582 - .filter("uri", equals(scrobbleUri)) 583 - .getFirst(); 686 + scrobble = await ctx.db 687 + .select({ 688 + scrobble: scrobbles, 689 + track: tracks, 690 + album: albums, 691 + artist: artists, 692 + user: users, 693 + }) 694 + .from(scrobbles) 695 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 696 + .innerJoin(albums, eq(scrobbles.albumId, albums.id)) 697 + .innerJoin(artists, eq(scrobbles.artistId, artists.id)) 698 + .innerJoin(users, eq(scrobbles.userId, users.id)) 699 + .where(eq(scrobbles.uri, scrobbleUri)) 700 + .limit(1) 701 + .then((rows) => rows[0]); 584 702 585 703 if ( 586 704 scrobble && 587 - scrobble.album_id && 588 - !scrobble.album_id.artist_uri && 589 - scrobble.artist_id.uri 705 + scrobble.album && 706 + !scrobble.album.artistUri && 707 + scrobble.artist.uri 590 708 ) { 591 - await ctx.client.db.albums.update(scrobble.album_id.xata_id, { 592 - artist_uri: scrobble.artist_id.uri, 593 - }); 709 + await ctx.db 710 + .update(albums) 711 + .set({ artistUri: scrobble.artist.uri }) 712 + .where(eq(albums.id, scrobble.album.id)); 594 713 } 595 714 596 - scrobble = await ctx.client.db.scrobbles 597 - .select(["*", "track_id.*", "album_id.*", "artist_id.*", "user_id.*"]) 598 - .filter("uri", equals(scrobbleUri)) 599 - .getFirst(); 715 + scrobble = await ctx.db 716 + .select({ 717 + scrobble: scrobbles, 718 + track: tracks, 719 + album: albums, 720 + artist: artists, 721 + user: users, 722 + }) 723 + .from(scrobbles) 724 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 725 + .innerJoin(albums, eq(scrobbles.albumId, albums.id)) 726 + .innerJoin(artists, eq(scrobbles.artistId, artists.id)) 727 + .innerJoin(users, eq(scrobbles.userId, users.id)) 728 + .where(eq(scrobbles.uri, scrobbleUri)) 729 + .limit(1) 730 + .then((rows) => rows[0]); 600 731 601 732 if ( 602 733 scrobble && 603 - scrobble.track_id && 604 - scrobble.album_id && 605 - scrobble.artist_id && 606 - scrobble.album_id.artist_uri && 607 - scrobble.track_id.artist_uri && 608 - scrobble.track_id.album_uri 734 + scrobble.track && 735 + scrobble.album && 736 + scrobble.artist && 737 + scrobble.album.artistUri && 738 + scrobble.track.artistUri && 739 + scrobble.track.albumUri 609 740 ) { 610 741 console.log("Scrobble found after ", chalk.magenta(tries + 1), " tries"); 611 - await publishScrobble(ctx, scrobble.xata_id); 742 + await publishScrobble(ctx, scrobble.scrobble.id); 612 743 console.log("Scrobble published"); 613 744 break; 614 745 }
+4 -2
apps/api/src/schema/album-tracks.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import albums from "./albums"; 4 4 import tracks from "./tracks"; 5 5 6 6 const albumTracks = pgTable("album_tracks", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 albumId: text("album_id") 9 11 .notNull() 10 12 .references(() => albums.id),
+4 -2
apps/api/src/schema/albums.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const albums = pgTable("albums", { 5 - id: text("xata_id").primaryKey(), 5 + id: text("xata_id") 6 + .primaryKey() 7 + .default(sql`xata_id()`), 6 8 title: text("title").notNull(), 7 9 artist: text("artist").notNull(), 8 10 releaseDate: text("release_date"),
+4 -2
apps/api/src/schema/api-keys.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import users from "./users"; 4 4 5 5 const apiKeys = pgTable("api_keys", { 6 - id: text("xata_id").primaryKey(), 6 + id: text("xata_id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 7 9 name: text("name").notNull(), 8 10 apiKey: text("api_key").notNull(), 9 11 sharedSecret: text("shared_secret").notNull(),
+4 -2
apps/api/src/schema/artist-albums.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import albums from "./albums"; 4 4 import artists from "./artists"; 5 5 6 6 const artistAlbums = pgTable("artist_albums", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 artistId: text("artist_id") 9 11 .notNull() 10 12 .references(() => artists.id),
+4 -2
apps/api/src/schema/artist-tracks.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import artists from "./artists"; 4 4 import tracks from "./tracks"; 5 5 6 6 const artistTracks = pgTable("artist_tracks", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 artistId: text("artist_id") 9 11 .notNull() 10 12 .references(() => artists.id),
+4 -2
apps/api/src/schema/artists.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const artists = pgTable("artists", { 5 - id: text("xata_id").primaryKey(), 5 + id: text("xata_id") 6 + .primaryKey() 7 + .default(sql`xata_id()`), 6 8 name: text("name").notNull(), 7 9 biography: text("biography"), 8 10 born: timestamp("born"),
+4 -2
apps/api/src/schema/dropbox-accounts.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import users from "./users"; 4 4 5 5 const dropboxAccounts = pgTable("dropbox_accounts", { 6 - id: text("xata_id").primaryKey(), 6 + id: text("xata_id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 7 9 email: text("email").unique().notNull(), 8 10 isBetaUser: boolean("is_beta_user").default(false).notNull(), 9 11 userId: text("user_id")
+4 -2
apps/api/src/schema/dropbox-directories.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const dropboxDirectories = pgTable("dropbox_directories", { 5 - id: text("xata_id").primaryKey(), 5 + id: text("xata_id") 6 + .primaryKey() 7 + .default(sql`xata_id()`), 6 8 name: text("name").notNull(), 7 9 path: text("path").notNull(), 8 10 parentId: text("parent_id").references(() => dropboxDirectories.id),
+4 -2
apps/api/src/schema/dropbox-paths.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import dropboxDirectories from "./dropbox-directories"; 4 4 5 5 const dropboxPaths = pgTable("dropbox_paths", { 6 - id: text("xata_id").primaryKey(), 6 + id: text("xata_id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 7 9 path: text("path").notNull(), 8 10 name: text("name").notNull(), 9 11 dropboxId: text("dropbox_id").notNull(),
+4 -2
apps/api/src/schema/dropbox-tokens.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const dropboxTokens = pgTable("dropbox_tokens", { 5 - id: text("xata_id").primaryKey(), 5 + id: text("xata_id") 6 + .primaryKey() 7 + .default(sql`xata_id()`), 6 8 refreshToken: text("refresh_token").notNull(), 7 9 createdAt: timestamp("xata_createdat").defaultNow().notNull(), 8 10 updatedAt: timestamp("xata_updatedat").defaultNow().notNull(),
+4 -2
apps/api/src/schema/dropbox.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import dropboxTokens from "./dropbox-tokens"; 4 4 import users from "./users"; 5 5 6 6 const dropbox = pgTable("dropbox", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -2
apps/api/src/schema/google-drive-accounts.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import users from "./users"; 4 4 5 5 const googleDriveAccounts = pgTable("google_drive_accounts", { 6 - id: text("xata_id").primaryKey(), 6 + id: text("xata_id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 7 9 email: text("email").unique().notNull(), 8 10 isBetaUser: boolean("is_beta_user").default(false).notNull(), 9 11 userId: text("user_id")
+4 -2
apps/api/src/schema/google-drive-directories.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const googleDriveDirectories = pgTable("google_drive_directories", { 5 - id: text("xata_id").primaryKey(), 5 + id: text("xata_id") 6 + .primaryKey() 7 + .default(sql`xata_id()`), 6 8 name: text("name").notNull(), 7 9 path: text("path").notNull(), 8 10 parentId: text("parent_id").references(() => googleDriveDirectories.id),
+4 -2
apps/api/src/schema/google-drive-paths.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import googleDriveDirectories from "./google-drive-directories"; 4 4 5 5 const googleDrivePaths = pgTable("google_drive_paths", { 6 - id: text("xata_id").primaryKey(), 6 + id: text("xata_id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 7 9 googleDriveId: text("google_drive_id").notNull(), 8 10 trackId: text("track_id").notNull(), 9 11 name: text("name").notNull(),
+4 -2
apps/api/src/schema/google-drive-tokens.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const googleDriveTokens = pgTable("google_drive_tokens", { 5 - id: text("xata_id").primaryKey(), 5 + id: text("xata_id") 6 + .primaryKey() 7 + .default(sql`xata_id()`), 6 8 refreshToken: text("refresh_token").notNull(), 7 9 createdAt: timestamp("xata_createdat").defaultNow().notNull(), 8 10 updatedAt: timestamp("xata_updatedat").defaultNow().notNull(),
+4 -2
apps/api/src/schema/googledrive.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import googleDriveTokens from "./google-drive-tokens"; 4 4 import users from "./users"; 5 5 6 6 const googleDrive = pgTable("google_drive", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 googleDriveTokenId: text("google_drive_token_id") 9 11 .notNull() 10 12 .references(() => googleDriveTokens.id),
+2
apps/api/src/schema/index.ts
··· 8 8 import dropboxAccounts from "./dropbox-accounts"; 9 9 import dropboxDirectories from "./dropbox-directories"; 10 10 import dropboxPaths from "./dropbox-paths"; 11 + import dropboxTokens from "./dropbox-tokens"; 11 12 import googleDriveAccounts from "./google-drive-accounts"; 12 13 import googleDriveDirectories from "./google-drive-directories"; 13 14 import googleDrivePaths from "./google-drive-paths"; ··· 62 63 googleDriveDirectories, 63 64 googleDrivePaths, 64 65 dropbox, 66 + dropboxTokens, 65 67 googleDrive, 66 68 queueTracks, 67 69 };
+4 -2
apps/api/src/schema/loved-tracks.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import tracks from "./tracks"; 4 4 import users from "./users"; 5 5 6 6 const lovedTracks = pgTable("loved_tracks", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -2
apps/api/src/schema/playlist-tracks.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import playlists from "./playlists"; 4 4 import tracks from "./tracks"; 5 5 6 6 const playlistTracks = pgTable("playlist_tracks", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 playlistId: text("playlist_id") 9 11 .notNull() 10 12 .references(() => playlists.id),
+4 -2
apps/api/src/schema/playlists.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import users from "./users"; 4 4 5 5 const playlists = pgTable("playlists", { 6 - id: text("xata_id").primaryKey(), 6 + id: text("xata_id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 7 9 name: text("name").notNull(), 8 10 picture: text("picture"), 9 11 description: text("description"),
+4 -2
apps/api/src/schema/profile-shouts.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import shouts from "./shouts"; 4 4 import users from "./users"; 5 5 6 6 const profileShouts = pgTable("profile_shouts", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -2
apps/api/src/schema/queue-tracks.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import tracks from "./tracks"; 4 4 import users from "./users"; 5 5 6 6 const queueTracks = pgTable("queue_tracks", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -3
apps/api/src/schema/scrobbles.ts
··· 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 1 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 2 - 3 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 4 3 import albums from "./albums"; 5 4 import artists from "./artists"; 6 5 import tracks from "./tracks"; 7 6 import users from "./users"; 8 7 9 8 const scrobbles = pgTable("scrobbles", { 10 - id: text("xata_id").primaryKey(), 9 + id: text("xata_id") 10 + .primaryKey() 11 + .default(sql`xata_id()`), 11 12 userId: text("user_id").references(() => users.id), 12 13 trackId: text("track_id").references(() => tracks.id), 13 14 albumId: text("album_id").references(() => albums.id),
+4 -2
apps/api/src/schema/shout-likes.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import shouts from "./shouts"; 4 4 import users from "./users"; 5 5 6 6 const shoutLikes = pgTable("shout_likes", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -2
apps/api/src/schema/shout-reports.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import shouts from "./shouts"; 4 4 import users from "./users"; 5 5 6 6 const shoutReports = pgTable("shout_reports", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -2
apps/api/src/schema/shouts.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import albums from "./albums"; 4 4 import scrobbles from "./scrobbles"; ··· 6 6 import users from "./users"; 7 7 8 8 const shouts = pgTable("shouts", { 9 - id: text("xata_id").primaryKey(), 9 + id: text("xata_id") 10 + .primaryKey() 11 + .default(sql`xata_id()`), 10 12 content: text("content").notNull(), 11 13 trackId: text("track_id").references(() => tracks.id), 12 14 artistId: text("artist_id").references(() => users.id),
+4 -2
apps/api/src/schema/spotify-accounts.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { 3 3 boolean, 4 4 integer, ··· 9 9 import users from "./users"; 10 10 11 11 const spotifyAccounts = pgTable("spotify_accounts", { 12 - id: text("xata_id").primaryKey(), 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")
+4 -2
apps/api/src/schema/spotify-tokens.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import users from "./users"; 4 4 5 5 const spotifyTokens = pgTable("spotify_tokens", { 6 - id: text("xata_id").primaryKey(), 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(),
+4 -2
apps/api/src/schema/tracks.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const tracks = pgTable("tracks", { 5 - id: text("xata_id").primaryKey(), 5 + id: text("xata_id") 6 + .primaryKey() 7 + .default(sql`xata_id()`), 6 8 title: text("title").notNull(), 7 9 artist: text("artist").notNull(), 8 10 albumArtist: text("album_artist").notNull(),
+4 -2
apps/api/src/schema/user-albums.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import albums from "./albums"; 4 4 import users from "./users"; 5 5 6 6 const userAlbums = pgTable("user_albums", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -2
apps/api/src/schema/user-artists.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import artists from "./artists"; 4 4 import users from "./users"; 5 5 6 6 const userArtists = pgTable("user_artists", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -2
apps/api/src/schema/user-playlists.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import playlists from "./playlists"; 4 4 import users from "./users"; 5 5 6 6 const userPlaylists = pgTable("user_playlists", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -2
apps/api/src/schema/user-tracks.ts
··· 1 - import type { InferInsertModel, InferSelectModel } from "drizzle-orm"; 1 + import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import tracks from "./tracks"; 4 4 import users from "./users"; 5 5 6 6 const userTracks = pgTable("user_tracks", { 7 - id: text("xata_id").primaryKey(), 7 + id: text("xata_id") 8 + .primaryKey() 9 + .default(sql`xata_id()`), 8 10 userId: text("user_id") 9 11 .notNull() 10 12 .references(() => users.id),
+4 -2
apps/api/src/schema/users.ts
··· 1 - import type { InferSelectModel } from "drizzle-orm"; 1 + import { type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const users = pgTable("users", { 5 - id: text("xata_id").primaryKey(), 5 + id: text("xata_id") 6 + .primaryKey() 7 + .default(sql`xata_id()`), 6 8 did: text("did").unique().notNull(), 7 9 displayName: text("display_name"), 8 10 handle: text("handle").unique().notNull(),
+4 -2
apps/api/src/schema/webscrobblers.ts
··· 1 - import type { InferSelectModel } from "drizzle-orm"; 1 + import { type InferSelectModel, sql } from "drizzle-orm"; 2 2 import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 import users from "./users"; 4 4 5 5 const webscrobblers = pgTable("webscrobblers", { 6 - id: text("xata_id").primaryKey(), 6 + id: text("xata_id") 7 + .primaryKey() 8 + .default(sql`xata_id()`), 7 9 name: text("name").notNull(), 8 10 uuid: text("uuid").notNull(), 9 11 description: text("description"),
+18 -12
apps/api/src/scripts/avatar.ts
··· 1 - import { equals } from "@xata.io/client"; 2 1 import { ctx } from "context"; 3 - import { eq } from "drizzle-orm"; 2 + import { eq, or } from "drizzle-orm"; 3 + import { deepSnakeCaseKeys } from "lib"; 4 4 import _ from "lodash"; 5 5 import users from "schema/users"; 6 6 7 7 const args = process.argv.slice(2); 8 8 9 9 for (const did of args) { 10 - const user = await ctx.client.db.users 11 - .filter({ 12 - $any: [{ did }, { handle: did }], 13 - }) 14 - .getFirst(); 10 + const [user] = await ctx.db 11 + .select() 12 + .from(users) 13 + .where(or(eq(users.did, did), eq(users.handle, did))) 14 + .limit(1) 15 + .execute(); 15 16 if (!user) { 16 17 console.log(`User ${did} not found`); 17 18 continue; ··· 41 42 .where(eq(users.did, user.did)) 42 43 .execute(); 43 44 44 - const u = await ctx.client.db.users 45 - .select(["*"]) 46 - .filter("did", equals(user.did)) 47 - .getFirst(); 45 + const [u] = await ctx.db 46 + .select() 47 + .from(users) 48 + .where(eq(users.did, user.did)) 49 + .limit(1) 50 + .execute(); 48 51 49 52 console.log(u); 50 53 51 - ctx.nc.publish("rocksky.user", Buffer.from(JSON.stringify(u))); 54 + ctx.nc.publish( 55 + "rocksky.user", 56 + Buffer.from(JSON.stringify(deepSnakeCaseKeys(u))) 57 + ); 52 58 } 53 59 54 60 console.log("Done");
+118 -103
apps/api/src/scripts/sync.ts
··· 1 - import { equals } from "@xata.io/client"; 2 1 import chalk from "chalk"; 3 2 import { ctx } from "context"; 3 + import { desc, eq, or } from "drizzle-orm"; 4 4 import { createHash } from "node:crypto"; 5 5 import { publishScrobble } from "nowplaying/nowplaying.service"; 6 + import albums from "../schema/albums"; 7 + import artists from "../schema/artists"; 8 + import scrobbles from "../schema/scrobbles"; 9 + import tracks from "../schema/tracks"; 10 + import users from "../schema/users"; 6 11 7 12 const args = process.argv.slice(2); 8 13 9 14 async function updateUris(did: string) { 10 - const { records } = await ctx.client.db.scrobbles 11 - .select(["track_id.*", "user_id.*"]) 12 - .filter({ 13 - $any: [{ "user_id.did": did }, { "user_id.handle": did }], 15 + // Get scrobbles with track and user data 16 + const records = await ctx.db 17 + .select({ 18 + track: tracks, 19 + user: users, 14 20 }) 15 - .getPaginated({ 16 - pagination: { 17 - size: process.env.SYNC_SIZE ? parseInt(process.env.SYNC_SIZE, 10) : 20, 18 - }, 19 - sort: [{ xata_createdat: "desc" }], 20 - }); 21 - for (const { track_id: track } of records) { 22 - const existingTrack = await ctx.client.db.tracks 23 - .filter( 24 - "sha256", 25 - equals( 26 - createHash("sha256") 27 - .update( 28 - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() 29 - ) 30 - .digest("hex") 31 - ) 32 - ) 33 - .getFirst(); 21 + .from(scrobbles) 22 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 23 + .innerJoin(users, eq(scrobbles.userId, users.id)) 24 + .where(or(eq(users.did, did), eq(users.handle, did))) 25 + .orderBy(desc(scrobbles.createdAt)) 26 + .limit(process.env.SYNC_SIZE ? parseInt(process.env.SYNC_SIZE, 10) : 20); 27 + 28 + for (const { track } of records) { 29 + const trackHash = createHash("sha256") 30 + .update(`${track.title} - ${track.artist} - ${track.album}`.toLowerCase()) 31 + .digest("hex"); 32 + 33 + const existingTrack = await ctx.db 34 + .select() 35 + .from(tracks) 36 + .where(eq(tracks.sha256, trackHash)) 37 + .limit(1) 38 + .then((rows) => rows[0]); 39 + 40 + if (existingTrack && !existingTrack.albumUri) { 41 + console.log(`Updating album uri for ${chalk.cyan(track.id)} ...`); 42 + 43 + const albumHash = createHash("sha256") 44 + .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 45 + .digest("hex"); 46 + 47 + const album = await ctx.db 48 + .select() 49 + .from(albums) 50 + .where(eq(albums.sha256, albumHash)) 51 + .limit(1) 52 + .then((rows) => rows[0]); 34 53 35 - if (existingTrack && !existingTrack.album_uri) { 36 - console.log(`Updating album uri for ${chalk.cyan(track.xata_id)} ...`); 37 - const album = await ctx.client.db.albums 38 - .filter( 39 - "sha256", 40 - equals( 41 - createHash("sha256") 42 - .update(`${track.album} - ${track.album_artist}`.toLowerCase()) 43 - .digest("hex") 44 - ) 45 - ) 46 - .getFirst(); 47 54 if (album) { 48 - await ctx.client.db.tracks.update(existingTrack.xata_id, { 49 - album_uri: album.uri, 50 - }); 55 + await ctx.db 56 + .update(tracks) 57 + .set({ albumUri: album.uri }) 58 + .where(eq(tracks.id, existingTrack.id)); 51 59 } 52 60 } 53 61 54 - if (existingTrack && !existingTrack.artist_uri) { 55 - console.log(`Updating artist uri for ${chalk.cyan(track.xata_id)} ...`); 56 - const artist = await ctx.client.db.artists 57 - .filter( 58 - "sha256", 59 - equals( 60 - createHash("sha256") 61 - .update(track.album_artist.toLowerCase()) 62 - .digest("hex") 63 - ) 64 - ) 65 - .getFirst(); 62 + if (existingTrack && !existingTrack.artistUri) { 63 + console.log(`Updating artist uri for ${chalk.cyan(track.id)} ...`); 64 + 65 + const artistHash = createHash("sha256") 66 + .update(track.albumArtist.toLowerCase()) 67 + .digest("hex"); 68 + 69 + const artist = await ctx.db 70 + .select() 71 + .from(artists) 72 + .where(eq(artists.sha256, artistHash)) 73 + .limit(1) 74 + .then((rows) => rows[0]); 75 + 66 76 if (artist) { 67 - await ctx.client.db.tracks.update(existingTrack.xata_id, { 68 - artist_uri: artist.uri, 69 - }); 77 + await ctx.db 78 + .update(tracks) 79 + .set({ artistUri: artist.uri }) 80 + .where(eq(tracks.id, existingTrack.id)); 70 81 } 71 82 } 72 83 73 - const album = await ctx.client.db.albums 74 - .filter( 75 - "sha256", 76 - equals( 77 - createHash("sha256") 78 - .update(`${track.album} - ${track.album_artist}`.toLowerCase()) 79 - .digest("hex") 80 - ) 81 - ) 82 - .getFirst(); 84 + const albumHash = createHash("sha256") 85 + .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 86 + .digest("hex"); 87 + 88 + const album = await ctx.db 89 + .select() 90 + .from(albums) 91 + .where(eq(albums.sha256, albumHash)) 92 + .limit(1) 93 + .then((rows) => rows[0]); 94 + 95 + if (existingTrack && album && !album.artistUri) { 96 + console.log(`Updating artist uri for ${chalk.cyan(album.id)} ...`); 97 + 98 + const artistHash = createHash("sha256") 99 + .update(track.albumArtist.toLowerCase()) 100 + .digest("hex"); 101 + 102 + const artist = await ctx.db 103 + .select() 104 + .from(artists) 105 + .where(eq(artists.sha256, artistHash)) 106 + .limit(1) 107 + .then((rows) => rows[0]); 83 108 84 - if (existingTrack && !album.artist_uri) { 85 - console.log(`Updating artist uri for ${chalk.cyan(album.xata_id)} ...`); 86 - const artist = await ctx.client.db.artists 87 - .filter( 88 - "sha256", 89 - equals( 90 - createHash("sha256") 91 - .update(track.album_artist.toLowerCase()) 92 - .digest("hex") 93 - ) 94 - ) 95 - .getFirst(); 96 109 if (artist) { 97 - await ctx.client.db.albums.update(album.xata_id, { 98 - artist_uri: artist.uri, 99 - }); 110 + await ctx.db 111 + .update(albums) 112 + .set({ artistUri: artist.uri }) 113 + .where(eq(albums.id, album.id)); 100 114 } 101 115 } 102 116 } ··· 111 125 await new Promise((resolve) => setTimeout(resolve, 15000)); 112 126 console.log(`Syncing scrobbles ${chalk.magenta(did)} ...`); 113 127 await updateUris(did); 114 - const { records } = await ctx.client.db.scrobbles 115 - .filter({ 116 - $any: [{ "user_id.did": did }, { "user_id.handle": did }], 128 + 129 + const records = await ctx.db 130 + .select({ 131 + scrobble: scrobbles, 117 132 }) 118 - .getPaginated({ 119 - pagination: { 120 - size: 5, 121 - }, 122 - sort: [{ xata_createdat: "desc" }], 123 - }); 124 - for (const scrobble of records) { 125 - console.log(`Syncing scrobble ${chalk.cyan(scrobble.xata_id)} ...`); 126 - await publishScrobble(ctx, scrobble.xata_id); 133 + .from(scrobbles) 134 + .innerJoin(users, eq(scrobbles.userId, users.id)) 135 + .where(or(eq(users.did, did), eq(users.handle, did))) 136 + .orderBy(desc(scrobbles.createdAt)) 137 + .limit(5); 138 + 139 + for (const { scrobble } of records) { 140 + console.log(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`); 141 + await publishScrobble(ctx, scrobble.id); 127 142 } 128 143 } 129 144 process.exit(0); ··· 133 148 console.log(`Syncing scrobbles ${chalk.magenta(arg)} ...`); 134 149 await updateUris(arg); 135 150 136 - const { records } = await ctx.client.db.scrobbles 137 - .filter({ 138 - $any: [{ "user_id.did": arg }, { "user_id.handle": arg }], 151 + const records = await ctx.db 152 + .select({ 153 + scrobble: scrobbles, 139 154 }) 140 - .getPaginated({ 141 - pagination: { 142 - size: process.env.SYNC_SIZE ? parseInt(process.env.SYNC_SIZE) : 20, 143 - }, 144 - sort: [{ xata_createdat: "desc" }], 145 - }); 146 - for (const scrobble of records) { 147 - console.log(`Syncing scrobble ${chalk.cyan(scrobble.xata_id)} ...`); 148 - await publishScrobble(ctx, scrobble.xata_id); 155 + .from(scrobbles) 156 + .innerJoin(users, eq(scrobbles.userId, users.id)) 157 + .where(or(eq(users.did, arg), eq(users.handle, arg))) 158 + .orderBy(desc(scrobbles.createdAt)) 159 + .limit(process.env.SYNC_SIZE ? parseInt(process.env.SYNC_SIZE) : 20); 160 + 161 + for (const { scrobble } of records) { 162 + console.log(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`); 163 + await publishScrobble(ctx, scrobble.id); 149 164 } 150 165 console.log(`Synced ${chalk.greenBright(records.length)} scrobbles`); 151 166 }
-50
apps/api/src/search/app.ts
··· 1 - import { ctx } from "context"; 2 - import { Hono } from "hono"; 3 - import { requestCounter } from "metrics"; 4 - 5 - const app = new Hono(); 6 - 7 - app.get("/", async (c) => { 8 - requestCounter.add(1, { method: "GET", route: "/search" }); 9 - const query = c.req.query("q"); 10 - const size = +c.req.query("size") || 10; 11 - const offset = +c.req.query("offset") || 0; 12 - 13 - if (!query) { 14 - return c.json([]); 15 - } 16 - 17 - const results = await ctx.client.search.all(query, { 18 - tables: [ 19 - { 20 - table: "users", 21 - target: ["handle"], 22 - }, 23 - { 24 - table: "albums", 25 - target: ["title"], 26 - }, 27 - { 28 - table: "artists", 29 - target: ["name"], 30 - }, 31 - { 32 - table: "tracks", 33 - target: ["title", "composer", "copyright_message"], 34 - }, 35 - { 36 - table: "playlists", 37 - target: ["name"], 38 - }, 39 - ], 40 - fuzziness: 1, 41 - prefix: "phrase", 42 - page: { 43 - size, 44 - offset, 45 - }, 46 - }); 47 - return c.json(results); 48 - }); 49 - 50 - export default app;
+169 -88
apps/api/src/shouts/shouts.service.ts
··· 1 1 import { type Agent, AtpAgent } from "@atproto/api"; 2 2 import { TID } from "@atproto/common"; 3 3 import type { Context } from "context"; 4 + import { and, eq } from "drizzle-orm"; 4 5 import * as LikeLexicon from "lexicon/types/app/rocksky/like"; 5 6 import * as ShoutLexicon from "lexicon/types/app/rocksky/shout"; 6 7 import { validateMain } from "lexicon/types/com/atproto/repo/strongRef"; 7 8 import _ from "lodash"; 8 9 import type { Shout } from "types/shout"; 10 + import albums, { type SelectAlbum } from "../schema/albums"; 11 + import artists, { type SelectArtist } from "../schema/artists"; 12 + import profileShouts from "../schema/profile-shouts"; 13 + import scrobbles, { type SelectScrobble } from "../schema/scrobbles"; 14 + import shoutLikes from "../schema/shout-likes"; 15 + import shouts from "../schema/shouts"; 16 + import tracks, { type SelectTrack } from "../schema/tracks"; 17 + import users, { type SelectUser } from "../schema/users"; 9 18 10 19 export async function createShout( 11 20 ctx: Context, 12 21 shout: Shout, 13 22 uri: string, 14 23 user, 15 - agent: Agent, 24 + agent: Agent 16 25 ) { 17 - let album, artist, track, scrobble, profile, collection; 26 + let album: SelectAlbum, 27 + artist: SelectArtist, 28 + track: SelectTrack, 29 + scrobble: { 30 + scrobble: SelectScrobble; 31 + track: SelectTrack; 32 + album: SelectAlbum; 33 + artist: SelectArtist; 34 + }, 35 + profile: SelectUser, 36 + collection: string; 37 + 18 38 if (uri.includes("app.rocksky.song")) { 19 - track = await ctx.client.db.tracks.filter("uri", uri).getFirst(); 39 + track = await ctx.db 40 + .select() 41 + .from(tracks) 42 + .where(eq(tracks.uri, uri)) 43 + .limit(1) 44 + .then((rows) => rows[0]); 20 45 collection = "app.rocksky.song"; 21 46 } else if (uri.includes("app.rocksky.album")) { 22 - album = await ctx.client.db.albums.filter("uri", uri).getFirst(); 47 + album = await ctx.db 48 + .select() 49 + .from(albums) 50 + .where(eq(albums.uri, uri)) 51 + .limit(1) 52 + .then((rows) => rows[0]); 23 53 collection = "app.rocksky.album"; 24 54 } else if (uri.includes("app.rocksky.artist")) { 25 - artist = await ctx.client.db.artists.filter("uri", uri).getFirst(); 55 + artist = await ctx.db 56 + .select() 57 + .from(artists) 58 + .where(eq(artists.uri, uri)) 59 + .limit(1) 60 + .then((rows) => rows[0]); 26 61 collection = "app.rocksky.artist"; 27 62 } else if (uri.includes("app.rocksky.scrobble")) { 28 - scrobble = await ctx.client.db.scrobbles 29 - .select(["track_id.*", "album_id.*", "artist_id.*", "uri"]) 30 - .filter("uri", uri) 31 - .getFirst(); 63 + scrobble = await ctx.db 64 + .select({ 65 + scrobble: scrobbles, 66 + track: tracks, 67 + album: albums, 68 + artist: artists, 69 + }) 70 + .from(scrobbles) 71 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 72 + .innerJoin(albums, eq(scrobbles.albumId, albums.id)) 73 + .innerJoin(artists, eq(scrobbles.artistId, artists.id)) 74 + .where(eq(scrobbles.uri, uri)) 75 + .limit(1) 76 + .then((rows) => rows[0]); 32 77 collection = "app.rocksky.scrobble"; 33 78 } else { 34 - profile = await ctx.client.db.users 35 - .filter("did", uri.split("at://").pop()) 36 - .getFirst(); 79 + profile = await ctx.db 80 + .select() 81 + .from(users) 82 + .where(eq(users.did, uri.split("at://").pop())) 83 + .limit(1) 84 + .then((rows) => rows[0]); 37 85 collection = "app.bsky.actor.profile"; 38 86 } 39 87 40 88 const subjectUri = 41 - album?.uri || track?.uri || artist?.uri || scrobble?.uri || "self"; 89 + album?.uri || track?.uri || artist?.uri || scrobble?.scrobble.uri || "self"; 42 90 const subjectRecord = await agent.com.atproto.repo.getRecord({ 43 91 repo: agent.assertDid, 44 92 collection, ··· 79 127 80 128 console.log(`Shout record created at: ${uri}`); 81 129 82 - const createdShout = await ctx.client.db.shouts.create({ 83 - content: shout.message, 84 - uri, 85 - author_id: user.xata_id, 86 - album_id: album?.xata_id, 87 - artist_id: artist?.xata_id, 88 - track_id: track?.xata_id, 89 - scrobble_id: scrobble?.xata_id, 90 - }); 130 + const createdShout = await ctx.db 131 + .insert(shouts) 132 + .values({ 133 + content: shout.message, 134 + uri, 135 + authorId: user.id, 136 + albumId: album?.id, 137 + artistId: artist?.id, 138 + trackId: track?.id, 139 + scrobbleId: scrobble?.scrobble.id, 140 + }) 141 + .returning() 142 + .then((rows) => rows[0]); 91 143 92 144 if (profile) { 93 - await ctx.client.db.profile_shouts.create({ 94 - shout_id: createdShout.xata_id, 95 - user_id: profile.xata_id, 145 + await ctx.db.insert(profileShouts).values({ 146 + shoutId: createdShout.id, 147 + userId: profile.id, 96 148 }); 97 149 } 98 150 } catch (e) { ··· 105 157 reply: Shout, 106 158 shoutUri: string, 107 159 user, 108 - agent: Agent, 160 + agent: Agent 109 161 ) { 110 - const shout = await ctx.client.db.shouts 111 - .select(["track_id.*", "album_id.*", "artist_id.*", "scrobble_id.*", "uri"]) 112 - .filter("uri", shoutUri) 113 - .getFirst(); 162 + const shout = await ctx.db 163 + .select({ 164 + shout: shouts, 165 + track: tracks, 166 + album: albums, 167 + artist: artists, 168 + scrobble: scrobbles, 169 + }) 170 + .from(shouts) 171 + .leftJoin(tracks, eq(shouts.trackId, tracks.id)) 172 + .leftJoin(albums, eq(shouts.albumId, albums.id)) 173 + .leftJoin(artists, eq(shouts.artistId, artists.id)) 174 + .leftJoin(scrobbles, eq(shouts.scrobbleId, scrobbles.id)) 175 + .where(eq(shouts.uri, shoutUri)) 176 + .limit(1) 177 + .then((rows) => rows[0]); 178 + 114 179 if (!shout) { 115 180 throw new Error("Shout not found"); 116 181 } ··· 123 188 124 189 let collection = "app.bsky.actor.profile"; 125 190 126 - if (shout.track_id) { 191 + if (shout.track) { 127 192 collection = "app.rocksky.song"; 128 193 } 129 194 130 - if (shout.album_id) { 195 + if (shout.album) { 131 196 collection = "app.rocksky.album"; 132 197 } 133 198 134 - if (shout.artist_id) { 199 + if (shout.artist) { 135 200 collection = "app.rocksky.artist"; 136 201 } 137 202 138 - if (shout.scrobble_id) { 203 + if (shout.scrobble) { 139 204 collection = "app.rocksky.scrobble"; 140 205 } 141 206 142 207 const subjectUri = 143 - shout.track_id?.uri || 144 - shout.album_id?.uri || 145 - shout.artist_id?.uri || 146 - shout.scrobble_id?.uri || 208 + shout.track?.uri || 209 + shout.album?.uri || 210 + shout.artist?.uri || 211 + shout.scrobble?.uri || 147 212 profileRecord.uri; 148 213 149 214 let service = await fetch( 150 - `https://plc.directory/${subjectUri.split("/").slice(0, 3).join("/").split("at://")[1]}`, 215 + `https://plc.directory/${subjectUri.split("/").slice(0, 3).join("/").split("at://")[1]}` 151 216 ) 152 - .then((res) => res.json()) 217 + .then((res) => res.json<{ service: { seviceEndpoint: string }[] }>()) 153 218 .then((data) => data.service); 154 219 155 220 let atpAgent = new AtpAgent({ ··· 171 236 } 172 237 173 238 service = await fetch( 174 - `https://plc.directory/${shoutUri.split("/").slice(0, 3).join("/").split("at://")[1]}`, 239 + `https://plc.directory/${shoutUri.split("/").slice(0, 3).join("/").split("at://")[1]}` 175 240 ) 176 - .then((res) => res.json()) 241 + .then((res) => res.json<{ service: { seviceEndpoint: string }[] }>()) 177 242 .then((data) => data.service); 178 243 179 244 atpAgent = new AtpAgent({ ··· 187 252 }); 188 253 189 254 const parentRef = validateMain({ 190 - uri: shout.uri, 255 + uri: shout.shout.uri, 191 256 cid: parentRecord.data.cid, 192 257 }); 193 258 if (!parentRef.success) { ··· 220 285 221 286 console.log(`Reply record created at: ${uri}`); 222 287 223 - const createdShout = await ctx.client.db.shouts.create({ 224 - content: reply.message, 225 - uri, 226 - parent_id: shout.xata_id, 227 - author_id: user.xata_id, 228 - track_id: shout.track_id?.xata_id, 229 - album_id: shout.album_id?.xata_id, 230 - artist_id: shout.artist_id?.xata_id, 231 - scrobble_id: shout.scrobble_id?.xata_id, 232 - }); 288 + const createdShout = await ctx.db 289 + .insert(shouts) 290 + .values({ 291 + content: reply.message, 292 + uri, 293 + parentId: shout.shout.id, 294 + authorId: user.id, 295 + trackId: shout.track?.id, 296 + albumId: shout.album?.id, 297 + artistId: shout.artist?.id, 298 + scrobbleId: shout.scrobble?.id, 299 + }) 300 + .returning() 301 + .then((rows) => rows[0]); 233 302 234 - if ( 235 - !shout.track_id && 236 - !shout.album_id && 237 - !shout.artist_id && 238 - !shout.scrobble_id 239 - ) { 240 - const profileShout = await ctx.client.db.profile_shouts 241 - .filter("shout_id", shout.xata_id) 242 - .getFirst(); 243 - await ctx.client.db.profile_shouts.create({ 244 - shout_id: createdShout.xata_id, 245 - user_id: profileShout.user_id, 303 + if (!shout.track && !shout.album && !shout.artist && !shout.scrobble) { 304 + const profileShout = await ctx.db 305 + .select() 306 + .from(profileShouts) 307 + .where(eq(profileShouts.shoutId, shout.shout.id)) 308 + .limit(1) 309 + .then((rows) => rows[0]); 310 + 311 + await ctx.db.insert(profileShouts).values({ 312 + shoutId: createdShout.id, 313 + userId: profileShout.userId, 246 314 }); 247 315 } 248 316 } catch (e) { ··· 254 322 ctx: Context, 255 323 shoutUri: string, 256 324 user, 257 - agent: Agent, 325 + agent: Agent 258 326 ) { 259 327 const rkey = TID.nextStr(); 260 - const likes = await ctx.client.db.shout_likes 261 - .filter({ 262 - "shout_id.uri": shoutUri, 263 - "user_id.xata_id": user.xata_id, 328 + 329 + const likes = await ctx.db 330 + .select({ 331 + like: shoutLikes, 332 + shout: shouts, 264 333 }) 265 - .getFirst(); 334 + .from(shoutLikes) 335 + .innerJoin(shouts, eq(shoutLikes.shoutId, shouts.id)) 336 + .where(and(eq(shouts.uri, shoutUri), eq(shoutLikes.userId, user.id))) 337 + .limit(1) 338 + .then((rows) => rows[0]); 266 339 267 340 if (likes) { 268 341 return; 269 342 } 270 343 271 344 const { service } = await fetch( 272 - `https://plc.directory/${shoutUri.split("/").slice(0, 3).join("/").split("at://")[1]}`, 273 - ).then((res) => res.json()); 345 + `https://plc.directory/${shoutUri.split("/").slice(0, 3).join("/").split("at://")[1]}` 346 + ).then((res) => res.json<{ service: [{ serviceEndpoint: string }] }>()); 274 347 275 348 const atpAgent = new AtpAgent({ 276 349 service: _.get(service, "0.serviceEndpoint"), ··· 311 384 }); 312 385 const uri = res.data.uri; 313 386 console.log(`Like record created at: ${uri}`); 314 - const shout = await ctx.client.db.shouts 315 - .select(["xata_id", "uri"]) 316 - .filter("uri", shoutUri) 317 - .getFirst(); 387 + 388 + const shout = await ctx.db 389 + .select() 390 + .from(shouts) 391 + .where(eq(shouts.uri, shoutUri)) 392 + .limit(1) 393 + .then((rows) => rows[0]); 394 + 318 395 if (!shout) { 319 396 throw new Error("Shout not found"); 320 397 } 321 398 322 - await ctx.client.db.shout_likes.create({ 323 - shout_id: shout.xata_id, 324 - user_id: user.xata_id, 399 + await ctx.db.insert(shoutLikes).values({ 400 + shoutId: shout.id, 401 + userId: user.id, 325 402 uri, 326 403 }); 327 404 } catch (e) { ··· 333 410 ctx: Context, 334 411 shoutUri: string, 335 412 user, 336 - agent: Agent, 413 + agent: Agent 337 414 ) { 338 - const likes = await ctx.client.db.shout_likes 339 - .filter({ 340 - "shout_id.uri": shoutUri, 341 - "user_id.xata_id": user.xata_id, 415 + const likes = await ctx.db 416 + .select({ 417 + like: shoutLikes, 418 + shout: shouts, 342 419 }) 343 - .getFirst(); 420 + .from(shoutLikes) 421 + .innerJoin(shouts, eq(shoutLikes.shoutId, shouts.id)) 422 + .where(and(eq(shouts.uri, shoutUri), eq(shoutLikes.userId, user.id))) 423 + .limit(1) 424 + .then((rows) => rows[0]); 344 425 345 426 if (!likes) { 346 427 return; 347 428 } 348 429 349 - const rkey = likes.uri.split("/").pop(); 430 + const rkey = likes.like.uri.split("/").pop(); 350 431 351 432 await Promise.all([ 352 433 agent.com.atproto.repo.deleteRecord({ ··· 354 435 collection: "app.rocksky.like", 355 436 rkey, 356 437 }), 357 - ctx.client.db.shout_likes.delete(likes.xata_id), 438 + ctx.db.delete(shoutLikes).where(eq(shoutLikes.id, likes.like.id)), 358 439 ]); 359 440 }
+192 -80
apps/api/src/spotify/app.ts
··· 1 - import { equals } from "@xata.io/client"; 2 1 import { ctx } from "context"; 2 + import { and, eq, or } from "drizzle-orm"; 3 3 import { Hono } from "hono"; 4 4 import jwt from "jsonwebtoken"; 5 5 import { decrypt, encrypt } from "lib/crypto"; ··· 7 7 import { requestCounter } from "metrics"; 8 8 import crypto, { createHash } from "node:crypto"; 9 9 import { rateLimiter } from "ratelimiter"; 10 + import lovedTracks from "schema/loved-tracks"; 11 + import spotifyAccounts from "schema/spotify-accounts"; 12 + import spotifyTokens from "schema/spotify-tokens"; 13 + import tracks from "schema/tracks"; 14 + import users from "schema/users"; 10 15 import { emailSchema } from "types/email"; 11 16 12 17 const app = new Hono(); ··· 17 22 limit: 10, // max Spotify API calls 18 23 window: 15, // per 10 seconds 19 24 keyPrefix: "spotify-ratelimit", 20 - }), 25 + }) 21 26 ); 22 27 23 28 app.get("/login", async (c) => { ··· 33 38 ignoreExpiration: true, 34 39 }); 35 40 36 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 41 + const user = await ctx.db 42 + .select() 43 + .from(users) 44 + .where(eq(users.did, did)) 45 + .limit(1) 46 + .then((rows) => rows[0]); 47 + 37 48 if (!user) { 38 49 c.status(401); 39 50 return c.text("Unauthorized"); ··· 44 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}`; 45 56 c.header( 46 57 "Set-Cookie", 47 - `session-id=${state}; Path=/; HttpOnly; SameSite=Strict; Secure`, 58 + `session-id=${state}; Path=/; HttpOnly; SameSite=Strict; Secure` 48 59 ); 49 60 return c.json({ redirectUrl }); 50 61 }); ··· 67 78 client_secret: env.SPOTIFY_CLIENT_SECRET, 68 79 }), 69 80 }); 70 - const { access_token, refresh_token } = await response.json(); 81 + const { access_token, refresh_token } = await response.json<{ 82 + access_token: string; 83 + refresh_token: string; 84 + }>(); 71 85 72 86 if (!state) { 73 87 return c.redirect(env.FRONTEND_URL); ··· 79 93 } 80 94 81 95 ctx.kv.delete(state); 82 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 96 + const user = await ctx.db 97 + .select() 98 + .from(users) 99 + .where(eq(users.did, did)) 100 + .limit(1) 101 + .then((rows) => rows[0]); 83 102 84 103 if (!user) { 85 104 return c.redirect(env.FRONTEND_URL); 86 105 } 87 106 88 - const spotifyToken = await ctx.client.db.spotify_tokens 89 - .filter("user_id", equals(user.xata_id)) 90 - .getFirst(); 107 + const existingSpotifyToken = await ctx.db 108 + .select() 109 + .from(spotifyTokens) 110 + .where(eq(spotifyTokens.userId, user.id)) 111 + .limit(1) 112 + .then((rows) => rows[0]); 91 113 92 - await ctx.client.db.spotify_tokens.createOrUpdate(spotifyToken?.xata_id, { 93 - user_id: user.xata_id, 94 - access_token: encrypt(access_token, env.SPOTIFY_ENCRYPTION_KEY), 95 - refresh_token: encrypt(refresh_token, env.SPOTIFY_ENCRYPTION_KEY), 96 - }); 114 + if (existingSpotifyToken) { 115 + await ctx.db 116 + .update(spotifyTokens) 117 + .set({ 118 + accessToken: encrypt(access_token, env.SPOTIFY_ENCRYPTION_KEY), 119 + refreshToken: encrypt(refresh_token, env.SPOTIFY_ENCRYPTION_KEY), 120 + }) 121 + .where(eq(spotifyTokens.id, existingSpotifyToken.id)); 122 + } else { 123 + await ctx.db.insert(spotifyTokens).values({ 124 + userId: user.id, 125 + accessToken: encrypt(access_token, env.SPOTIFY_ENCRYPTION_KEY), 126 + refreshToken: encrypt(refresh_token, env.SPOTIFY_ENCRYPTION_KEY), 127 + }); 128 + } 97 129 98 - const spotifyUser = await ctx.client.db.spotify_accounts 99 - .filter("user_id", equals(user.xata_id)) 100 - .filter("is_beta_user", equals(true)) 101 - .getFirst(); 130 + const spotifyUser = await ctx.db 131 + .select() 132 + .from(spotifyAccounts) 133 + .where( 134 + and( 135 + eq(spotifyAccounts.userId, user.id), 136 + eq(spotifyAccounts.isBetaUser, true) 137 + ) 138 + ) 139 + .limit(1) 140 + .then((rows) => rows[0]); 102 141 103 142 if (spotifyUser?.email) { 104 143 ctx.nc.publish("rocksky.spotify.user", Buffer.from(spotifyUser.email)); ··· 120 159 ignoreExpiration: true, 121 160 }); 122 161 123 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 162 + const user = await ctx.db 163 + .select() 164 + .from(users) 165 + .where(eq(users.did, did)) 166 + .limit(1) 167 + .then((rows) => rows[0]); 168 + 124 169 if (!user) { 125 170 c.status(401); 126 171 return c.text("Unauthorized"); ··· 137 182 const { email } = parsed.data; 138 183 139 184 try { 140 - await ctx.client.db.spotify_accounts.create({ 141 - user_id: user.xata_id, 185 + await ctx.db.insert(spotifyAccounts).values({ 186 + userId: user.id, 142 187 email, 143 - is_beta_user: false, 188 + isBetaUser: false, 144 189 }); 145 190 } catch (e) { 146 - if ( 147 - !e.message.includes("invalid record: column [user_id]: is not unique") 148 - ) { 191 + if (!e.message.includes("duplicate key value violates unique constraint")) { 149 192 console.error(e.message); 150 193 } else { 151 194 throw e; ··· 179 222 return c.text("Unauthorized"); 180 223 } 181 224 182 - const user = await ctx.client.db.users 183 - .filter({ 184 - $any: [{ did }, { handle: did }], 185 - }) 186 - .getFirst(); 225 + const user = await ctx.db 226 + .select() 227 + .from(users) 228 + .where(or(eq(users.did, did), eq(users.handle, did))) 229 + .limit(1) 230 + .then((rows) => rows[0]); 187 231 188 232 if (!user) { 189 233 c.status(401); 190 234 return c.text("Unauthorized"); 191 235 } 192 236 193 - const spotifyAccount = await ctx.client.db.spotify_accounts 194 - .filter({ 195 - $any: [{ "user_id.did": did }, { "user_id.handle": did }], 237 + const spotifyAccount = await ctx.db 238 + .select({ 239 + spotifyAccount: spotifyAccounts, 240 + user: users, 196 241 }) 197 - .getFirst(); 242 + .from(spotifyAccounts) 243 + .innerJoin(users, eq(spotifyAccounts.userId, users.id)) 244 + .where(or(eq(users.did, did), eq(users.handle, did))) 245 + .limit(1) 246 + .then((rows) => rows[0]); 198 247 199 248 if (!spotifyAccount) { 200 249 c.status(401); 201 250 return c.text("Unauthorized"); 202 251 } 203 252 204 - const cached = await ctx.redis.get(`${spotifyAccount.email}:current`); 253 + const cached = await ctx.redis.get( 254 + `${spotifyAccount.spotifyAccount.email}:current` 255 + ); 205 256 if (!cached) { 206 257 return c.json({}); 207 258 } ··· 210 261 211 262 const sha256 = createHash("sha256") 212 263 .update( 213 - `${track.item.name} - ${track.item.artists.map((x) => x.name).join(", ")} - ${track.item.album.name}`.toLowerCase(), 264 + `${track.item.name} - ${track.item.artists.map((x) => x.name).join(", ")} - ${track.item.album.name}`.toLowerCase() 214 265 ) 215 266 .digest("hex"); 216 267 217 268 const [result, liked] = await Promise.all([ 218 - ctx.client.db.tracks.filter("sha256", equals(sha256)).getFirst(), 219 - ctx.client.db.loved_tracks 220 - .filter("user_id", equals(user.xata_id)) 221 - .filter("track_id.sha256", equals(sha256)) 222 - .getFirst(), 269 + ctx.db 270 + .select() 271 + .from(tracks) 272 + .where(eq(tracks.sha256, sha256)) 273 + .limit(1) 274 + .then((rows) => rows[0]), 275 + ctx.db 276 + .select({ 277 + lovedTrack: lovedTracks, 278 + track: tracks, 279 + }) 280 + .from(lovedTracks) 281 + .innerJoin(tracks, eq(lovedTracks.trackId, tracks.id)) 282 + .where(and(eq(lovedTracks.userId, user.id), eq(tracks.sha256, sha256))) 283 + .limit(1) 284 + .then((rows) => rows[0]), 223 285 ]); 224 286 225 287 return c.json({ 226 288 ...track, 227 289 songUri: result?.uri, 228 - artistUri: result?.artist_uri, 229 - albumUri: result?.album_uri, 290 + artistUri: result?.artistUri, 291 + albumUri: result?.albumUri, 230 292 liked: !!liked, 231 293 sha256, 232 294 }); ··· 246 308 return c.text("Unauthorized"); 247 309 } 248 310 249 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 311 + const user = await ctx.db 312 + .select() 313 + .from(users) 314 + .where(eq(users.did, did)) 315 + .limit(1) 316 + .then((rows) => rows[0]); 250 317 251 318 if (!user) { 252 319 c.status(401); 253 320 return c.text("Unauthorized"); 254 321 } 255 322 256 - const spotifyToken = await ctx.client.db.spotify_tokens 257 - .filter("user_id", equals(user.xata_id)) 258 - .getFirst(); 323 + const spotifyToken = await ctx.db 324 + .select() 325 + .from(spotifyTokens) 326 + .where(eq(spotifyTokens.userId, user.id)) 327 + .limit(1) 328 + .then((rows) => rows[0]); 259 329 260 330 if (!spotifyToken) { 261 331 c.status(401); ··· 263 333 } 264 334 265 335 const refreshToken = decrypt( 266 - spotifyToken.refresh_token, 267 - env.SPOTIFY_ENCRYPTION_KEY, 336 + spotifyToken.refreshToken, 337 + env.SPOTIFY_ENCRYPTION_KEY 268 338 ); 269 339 270 340 // get new access token ··· 281 351 }), 282 352 }); 283 353 284 - const { access_token } = await newAccessToken.json(); 354 + const { access_token } = await newAccessToken.json<{ 355 + access_token: string; 356 + }>(); 285 357 286 358 const response = await fetch("https://api.spotify.com/v1/me/player/pause", { 287 359 method: "PUT", ··· 312 384 return c.text("Unauthorized"); 313 385 } 314 386 315 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 387 + const user = await ctx.db 388 + .select() 389 + .from(users) 390 + .where(eq(users.did, did)) 391 + .limit(1) 392 + .then((rows) => rows[0]); 316 393 317 394 if (!user) { 318 395 c.status(401); 319 396 return c.text("Unauthorized"); 320 397 } 321 398 322 - const spotifyToken = await ctx.client.db.spotify_tokens 323 - .filter("user_id", equals(user.xata_id)) 324 - .getFirst(); 399 + const spotifyToken = await ctx.db 400 + .select() 401 + .from(spotifyTokens) 402 + .where(eq(spotifyTokens.userId, user.id)) 403 + .limit(1) 404 + .then((rows) => rows[0]); 325 405 326 406 if (!spotifyToken) { 327 407 c.status(401); ··· 329 409 } 330 410 331 411 const refreshToken = decrypt( 332 - spotifyToken.refresh_token, 333 - env.SPOTIFY_ENCRYPTION_KEY, 412 + spotifyToken.refreshToken, 413 + env.SPOTIFY_ENCRYPTION_KEY 334 414 ); 335 415 336 416 // get new access token ··· 347 427 }), 348 428 }); 349 429 350 - const { access_token } = await newAccessToken.json(); 430 + const { access_token } = await newAccessToken.json<{ 431 + access_token: string; 432 + }>(); 351 433 352 434 const response = await fetch("https://api.spotify.com/v1/me/player/play", { 353 435 method: "PUT", ··· 378 460 return c.text("Unauthorized"); 379 461 } 380 462 381 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 463 + const user = await ctx.db 464 + .select() 465 + .from(users) 466 + .where(eq(users.did, did)) 467 + .limit(1) 468 + .then((rows) => rows[0]); 382 469 383 470 if (!user) { 384 471 c.status(401); 385 472 return c.text("Unauthorized"); 386 473 } 387 474 388 - const spotifyToken = await ctx.client.db.spotify_tokens 389 - .filter("user_id", equals(user.xata_id)) 390 - .getFirst(); 475 + const spotifyToken = await ctx.db 476 + .select() 477 + .from(spotifyTokens) 478 + .where(eq(spotifyTokens.userId, user.id)) 479 + .limit(1) 480 + .then((rows) => rows[0]); 391 481 392 482 if (!spotifyToken) { 393 483 c.status(401); ··· 395 485 } 396 486 397 487 const refreshToken = decrypt( 398 - spotifyToken.refresh_token, 399 - env.SPOTIFY_ENCRYPTION_KEY, 488 + spotifyToken.refreshToken, 489 + env.SPOTIFY_ENCRYPTION_KEY 400 490 ); 401 491 402 492 // get new access token ··· 413 503 }), 414 504 }); 415 505 416 - const { access_token } = await newAccessToken.json(); 506 + const { access_token } = await newAccessToken.json<{ 507 + access_token: string; 508 + }>(); 417 509 418 510 const response = await fetch("https://api.spotify.com/v1/me/player/next", { 419 511 method: "POST", ··· 444 536 return c.text("Unauthorized"); 445 537 } 446 538 447 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 539 + const user = await ctx.db 540 + .select() 541 + .from(users) 542 + .where(eq(users.did, did)) 543 + .limit(1) 544 + .then((rows) => rows[0]); 448 545 449 546 if (!user) { 450 547 c.status(401); 451 548 return c.text("Unauthorized"); 452 549 } 453 550 454 - const spotifyToken = await ctx.client.db.spotify_tokens 455 - .filter("user_id", equals(user.xata_id)) 456 - .getFirst(); 551 + const spotifyToken = await ctx.db 552 + .select() 553 + .from(spotifyTokens) 554 + .where(eq(spotifyTokens.userId, user.id)) 555 + .limit(1) 556 + .then((rows) => rows[0]); 457 557 458 558 if (!spotifyToken) { 459 559 c.status(401); ··· 461 561 } 462 562 463 563 const refreshToken = decrypt( 464 - spotifyToken.refresh_token, 465 - env.SPOTIFY_ENCRYPTION_KEY, 564 + spotifyToken.refreshToken, 565 + env.SPOTIFY_ENCRYPTION_KEY 466 566 ); 467 567 468 568 // get new access token ··· 479 579 }), 480 580 }); 481 581 482 - const { access_token } = await newAccessToken.json(); 582 + const { access_token } = await newAccessToken.json<{ 583 + access_token: string; 584 + }>(); 483 585 484 586 const response = await fetch( 485 587 "https://api.spotify.com/v1/me/player/previous", ··· 488 590 headers: { 489 591 Authorization: `Bearer ${access_token}`, 490 592 }, 491 - }, 593 + } 492 594 ); 493 595 494 596 if (response.status === 403) { ··· 513 615 return c.text("Unauthorized"); 514 616 } 515 617 516 - const user = await ctx.client.db.users.filter("did", equals(did)).getFirst(); 618 + const user = await ctx.db 619 + .select() 620 + .from(users) 621 + .where(eq(users.did, did)) 622 + .limit(1) 623 + .then((rows) => rows[0]); 517 624 518 625 if (!user) { 519 626 c.status(401); 520 627 return c.text("Unauthorized"); 521 628 } 522 629 523 - const spotifyToken = await ctx.client.db.spotify_tokens 524 - .filter("user_id", equals(user.xata_id)) 525 - .getFirst(); 630 + const spotifyToken = await ctx.db 631 + .select() 632 + .from(spotifyTokens) 633 + .where(eq(spotifyTokens.userId, user.id)) 634 + .limit(1) 635 + .then((rows) => rows[0]); 526 636 527 637 if (!spotifyToken) { 528 638 c.status(401); ··· 530 640 } 531 641 532 642 const refreshToken = decrypt( 533 - spotifyToken.refresh_token, 534 - env.SPOTIFY_ENCRYPTION_KEY, 643 + spotifyToken.refreshToken, 644 + env.SPOTIFY_ENCRYPTION_KEY 535 645 ); 536 646 537 647 // get new access token ··· 548 658 }), 549 659 }); 550 660 551 - const { access_token } = await newAccessToken.json(); 661 + const { access_token } = await newAccessToken.json<{ 662 + access_token: string; 663 + }>(); 552 664 553 665 const position = c.req.query("position_ms"); 554 666 const response = await fetch( ··· 558 670 headers: { 559 671 Authorization: `Bearer ${access_token}`, 560 672 }, 561 - }, 673 + } 562 674 ); 563 675 564 676 if (response.status === 403) {
+153 -112
apps/api/src/tracks/tracks.service.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 - import { equals } from "@xata.io/client"; 3 2 import type { Context } from "context"; 3 + import { and, eq } from "drizzle-orm"; 4 + import { deepSnakeCaseKeys } from "lib"; 4 5 import { createHash } from "node:crypto"; 5 6 import { 6 7 putAlbumRecord, 7 8 putArtistRecord, 8 9 putSongRecord, 9 10 } from "nowplaying/nowplaying.service"; 11 + import tables from "schema"; 10 12 import type { Track } from "types/track"; 11 13 14 + const { tracks, albums, artists, albumTracks, artistTracks, artistAlbums } = 15 + tables; 16 + 12 17 export async function saveTrack(ctx: Context, track: Track, agent: Agent) { 13 - const existingTrack = await ctx.client.db.tracks 14 - .filter( 15 - "sha256", 16 - equals( 17 - createHash("sha256") 18 - .update( 19 - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), 20 - ) 21 - .digest("hex"), 22 - ), 23 - ) 24 - .getFirst(); 18 + const trackHash = createHash("sha256") 19 + .update(`${track.title} - ${track.artist} - ${track.album}`.toLowerCase()) 20 + .digest("hex"); 21 + 22 + const existingTrack = await ctx.db 23 + .select() 24 + .from(tracks) 25 + .where(eq(tracks.sha256, trackHash)) 26 + .limit(1) 27 + .then((results) => results[0]); 25 28 26 29 let trackUri = existingTrack?.uri; 27 30 if (!existingTrack?.uri) { ··· 29 32 } 30 33 31 34 // start update existing track with album and artist uri 32 - if (existingTrack && !existingTrack.album_uri) { 33 - const album = await ctx.client.db.albums 34 - .filter( 35 - "sha256", 36 - equals( 37 - createHash("sha256") 38 - .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 39 - .digest("hex"), 40 - ), 41 - ) 42 - .getFirst(); 35 + if (existingTrack && !existingTrack.albumUri) { 36 + const albumHash = createHash("sha256") 37 + .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 38 + .digest("hex"); 39 + 40 + const album = await ctx.db 41 + .select() 42 + .from(albums) 43 + .where(eq(albums.sha256, albumHash)) 44 + .limit(1) 45 + .then((results) => results[0]); 46 + 43 47 if (album) { 44 - await ctx.client.db.tracks.update(existingTrack.xata_id, { 45 - album_uri: album.uri, 46 - }); 48 + await ctx.db 49 + .update(tracks) 50 + .set({ albumUri: album.uri }) 51 + .where(eq(tracks.id, existingTrack.id)); 47 52 } 48 53 } 49 54 50 - if (existingTrack && !existingTrack.artist_uri) { 51 - const artist = await ctx.client.db.artists 52 - .filter( 53 - "sha256", 54 - equals( 55 - createHash("sha256") 56 - .update(track.albumArtist.toLowerCase()) 57 - .digest("hex"), 58 - ), 59 - ) 60 - .getFirst(); 55 + if (existingTrack && !existingTrack.artistUri) { 56 + const artistHash = createHash("sha256") 57 + .update(track.albumArtist.toLowerCase()) 58 + .digest("hex"); 59 + 60 + const artist = await ctx.db 61 + .select() 62 + .from(artists) 63 + .where(eq(artists.sha256, artistHash)) 64 + .limit(1) 65 + .then((results) => results[0]); 66 + 61 67 if (artist) { 62 - await ctx.client.db.tracks.update(existingTrack.xata_id, { 63 - artist_uri: artist.uri, 64 - }); 68 + await ctx.db 69 + .update(tracks) 70 + .set({ artistUri: artist.uri }) 71 + .where(eq(tracks.id, existingTrack.id)); 65 72 } 66 73 } 67 74 // end 68 75 69 - const existingArtist = await ctx.client.db.artists 70 - .filter( 71 - "sha256", 72 - equals( 73 - createHash("sha256") 74 - .update(track.albumArtist.toLocaleLowerCase()) 75 - .digest("hex"), 76 - ), 77 - ) 78 - .getFirst(); 76 + const artistHash = createHash("sha256") 77 + .update(track.albumArtist.toLowerCase()) 78 + .digest("hex"); 79 + 80 + const existingArtist = await ctx.db 81 + .select() 82 + .from(artists) 83 + .where(eq(artists.sha256, artistHash)) 84 + .limit(1) 85 + .then((results) => results[0]); 79 86 80 87 let artistUri = existingArtist?.uri; 81 88 if (!existingArtist?.uri) { 82 89 artistUri = await putArtistRecord(track, agent); 83 90 } 84 91 85 - const existingAlbum = await ctx.client.db.albums 86 - .filter( 87 - "sha256", 88 - equals( 89 - createHash("sha256") 90 - .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 91 - .digest("hex"), 92 - ), 93 - ) 94 - .getFirst(); 92 + const albumHash = createHash("sha256") 93 + .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 94 + .digest("hex"); 95 + 96 + const existingAlbum = await ctx.db 97 + .select() 98 + .from(albums) 99 + .where(eq(albums.sha256, albumHash)) 100 + .limit(1) 101 + .then((results) => results[0]); 95 102 96 103 let albumUri = existingAlbum?.uri; 97 104 if (!existingAlbum?.uri) { ··· 101 108 let tries = 0; 102 109 103 110 while (tries < 15) { 104 - const track_id = await ctx.client.db.tracks 105 - .filter("uri", equals(trackUri)) 106 - .getFirst(); 111 + const track_id = await ctx.db 112 + .select() 113 + .from(tracks) 114 + .where(eq(tracks.uri, trackUri)) 115 + .limit(1) 116 + .then((results) => results[0]); 107 117 108 - const album_id = await ctx.client.db.albums 109 - .filter("uri", equals(albumUri)) 110 - .getFirst(); 118 + const album_id = await ctx.db 119 + .select() 120 + .from(albums) 121 + .where(eq(albums.uri, albumUri)) 122 + .limit(1) 123 + .then((results) => results[0]); 111 124 112 - const artist_id = await ctx.client.db.artists 113 - .filter("uri", equals(artistUri)) 114 - .getFirst(); 125 + const artist_id = await ctx.db 126 + .select() 127 + .from(artists) 128 + .where(eq(artists.uri, artistUri)) 129 + .limit(1) 130 + .then((results) => results[0]); 115 131 116 132 if (!track_id || !album_id || !artist_id) { 117 133 console.log( 118 134 "Track not yet saved (uri not saved), retrying...", 119 - tries + 1, 135 + tries + 1 120 136 ); 121 137 await new Promise((resolve) => setTimeout(resolve, 1000)); 122 138 tries += 1; 123 139 continue; 124 140 } 125 141 126 - const album_track = await ctx.client.db.album_tracks 127 - .filter("album_id", equals(album_id.xata_id)) 128 - .filter("track_id", equals(track_id.xata_id)) 129 - .getFirst(); 142 + const album_track = await ctx.db 143 + .select() 144 + .from(albumTracks) 145 + .where( 146 + and( 147 + eq(albumTracks.albumId, album_id.id), 148 + eq(albumTracks.trackId, track_id.id) 149 + ) 150 + ) 151 + .limit(1) 152 + .then((results) => results[0]); 130 153 131 - const artist_track = await ctx.client.db.artist_tracks 132 - .filter("artist_id", equals(artist_id.xata_id)) 133 - .filter("track_id", equals(track_id.xata_id)) 134 - .getFirst(); 154 + const artist_track = await ctx.db 155 + .select() 156 + .from(artistTracks) 157 + .where( 158 + and( 159 + eq(artistTracks.artistId, artist_id.id), 160 + eq(artistTracks.trackId, track_id.id) 161 + ) 162 + ) 163 + .limit(1) 164 + .then((results) => results[0]); 135 165 136 - const artist_album = await ctx.client.db.artist_albums 137 - .filter("artist_id", equals(artist_id.xata_id)) 138 - .filter("album_id", equals(album_id.xata_id)) 139 - .getFirst(); 166 + const artist_album = await ctx.db 167 + .select() 168 + .from(artistAlbums) 169 + .where( 170 + and( 171 + eq(artistAlbums.artistId, artist_id.id), 172 + eq(artistAlbums.albumId, album_id.id) 173 + ) 174 + ) 175 + .limit(1) 176 + .then((results) => results[0]); 140 177 141 178 if (!album_track) { 142 - await ctx.client.db.album_tracks.create({ 143 - album_id: album_id.xata_id, 144 - track_id: track_id.xata_id, 179 + await ctx.db.insert(albumTracks).values({ 180 + albumId: album_id.id, 181 + trackId: track_id.id, 145 182 }); 146 183 } 147 184 148 185 if (!artist_track) { 149 - await ctx.client.db.artist_tracks.create({ 150 - artist_id: artist_id.xata_id, 151 - track_id: track_id.xata_id, 186 + await ctx.db.insert(artistTracks).values({ 187 + artistId: artist_id.id, 188 + trackId: track_id.id, 152 189 }); 153 190 } 154 191 155 192 if (!artist_album) { 156 - await ctx.client.db.artist_albums.create({ 157 - artist_id: artist_id.xata_id, 158 - album_id: album_id.xata_id, 193 + await ctx.db.insert(artistAlbums).values({ 194 + artistId: artist_id.id, 195 + albumId: album_id.id, 159 196 }); 160 197 } 161 198 162 - if (track_id && !track_id.album_uri) { 163 - await ctx.client.db.tracks.update(track_id.xata_id, { 164 - album_uri: album_id.uri, 165 - }); 199 + if (track_id && !track_id.albumUri) { 200 + await ctx.db 201 + .update(tracks) 202 + .set({ albumUri: album_id.uri }) 203 + .where(eq(tracks.id, track_id.id)); 166 204 } 167 205 168 - if (track_id && !track_id.artist_uri) { 169 - await ctx.client.db.tracks.update(track_id.xata_id, { 170 - artist_uri: artist_id.uri, 171 - }); 206 + if (track_id && !track_id.artistUri) { 207 + await ctx.db 208 + .update(tracks) 209 + .set({ artistUri: artist_id.uri }) 210 + .where(eq(tracks.id, track_id.id)); 172 211 } 173 212 174 213 if ( ··· 176 215 artist_track && 177 216 artist_album && 178 217 track_id && 179 - track_id.album_uri && 180 - track_id.artist_uri 218 + track_id.albumUri && 219 + track_id.artistUri 181 220 ) { 182 221 console.log("Track saved successfully after", tries + 1, "tries"); 183 222 184 - const message = JSON.stringify({ 185 - track: track_id, 186 - album_track, 187 - artist_track, 188 - artist_album, 189 - }); 223 + const message = JSON.stringify( 224 + deepSnakeCaseKeys({ 225 + track: track_id, 226 + album_track, 227 + artist_track, 228 + artist_album, 229 + }) 230 + ); 190 231 191 232 ctx.nc.publish("rocksky.track", Buffer.from(message)); 192 233 break; ··· 194 235 195 236 tries += 1; 196 237 console.log("Track not yet saved, retrying...", tries + 1); 197 - if (tries == 15) { 238 + if (tries === 15) { 198 239 console.log(">>>"); 199 240 console.log(album_track); 200 241 console.log(artist_track); ··· 202 243 console.log(artist_id); 203 244 console.log(album_id); 204 245 console.log(track_id); 205 - console.log(track_id.album_uri); 206 - console.log(track_id.artist_uri); 246 + console.log(track_id.albumUri); 247 + console.log(track_id.artistUri); 207 248 console.log("<<<"); 208 249 } 209 250 await new Promise((resolve) => setTimeout(resolve, 1000)); 210 251 } 211 252 212 - if (tries == 15) { 253 + if (tries === 15) { 213 254 console.log("Failed to save track after 15 tries"); 214 255 } 215 256 }
+445 -360
apps/api/src/users/app.ts
··· 1 1 import type { BlobRef } from "@atproto/lexicon"; 2 - import { equals } from "@xata.io/client"; 3 2 import { ctx } from "context"; 4 3 import { 5 4 aliasedTable, 5 + and, 6 6 asc, 7 7 count, 8 8 desc, ··· 16 16 import * as Profile from "lexicon/types/app/bsky/actor/profile"; 17 17 import { createAgent } from "lib/agent"; 18 18 import { env } from "lib/env"; 19 - import _ from "lodash"; 20 19 import { likeTrack, unLikeTrack } from "lovedtracks/lovedtracks.service"; 21 20 import { requestCounter } from "metrics"; 22 21 import * as R from "ramda"; 23 22 import tables from "schema"; 23 + import { SelectUser } from "schema/users"; 24 24 import { 25 25 createShout, 26 26 likeShout, ··· 39 39 const size = +c.req.query("size") || 10; 40 40 const offset = +c.req.query("offset") || 0; 41 41 42 - const lovedTracks = await ctx.client.db.loved_tracks 43 - .select(["track_id.*", "user_id.*"]) 44 - .filter({ 45 - $any: [ 46 - { 47 - "user_id.did": did, 48 - }, 49 - { 50 - "user_id.handle": did, 51 - }, 52 - ], 53 - }) 54 - .sort("xata_createdat", "desc") 55 - .getPaginated({ 56 - pagination: { 57 - size, 58 - offset, 59 - }, 60 - }); 42 + const lovedTracks = await ctx.db 43 + .select() 44 + .from(tables.lovedTracks) 45 + .leftJoin(tables.tracks, eq(tables.lovedTracks.trackId, tables.tracks.id)) 46 + .leftJoin(tables.users, eq(tables.lovedTracks.userId, tables.users.id)) 47 + .where(or(eq(tables.users.did, did), eq(tables.users.handle, did))) 48 + .orderBy(desc(tables.lovedTracks.createdAt)) 49 + .limit(size) 50 + .offset(offset) 51 + .execute(); 52 + 61 53 return c.json(lovedTracks); 62 54 }); 63 55 ··· 131 123 data.map((item) => ({ 132 124 ...item, 133 125 tags: [], 134 - })), 126 + })) 135 127 ); 136 128 }); 137 129 ··· 161 153 results.map((x) => ({ 162 154 ...x.playlists, 163 155 trackCount: +x.trackCount, 164 - })), 156 + })) 165 157 ); 166 158 }); 167 159 ··· 174 166 const rkey = c.req.param("rkey"); 175 167 const uri = `at://${did}/app.rocksky.scrobble/${rkey}`; 176 168 177 - const scrobble = await ctx.client.db.scrobbles 178 - .select(["track_id.*", "user_id.*", "xata_createdat", "uri"]) 179 - .filter("uri", equals(uri)) 180 - .getFirst(); 169 + const scrobble = await ctx.db 170 + .select() 171 + .from(tables.scrobbles) 172 + .leftJoin(tables.tracks, eq(tables.scrobbles.trackId, tables.tracks.id)) 173 + .leftJoin(tables.users, eq(tables.scrobbles.userId, tables.users.id)) 174 + .where(eq(tables.scrobbles.uri, uri)) 175 + .limit(1) 176 + .execute() 177 + .then((rows) => rows[0]); 181 178 182 179 if (!scrobble) { 183 180 c.status(404); ··· 185 182 } 186 183 187 184 const [listeners, scrobbles] = await Promise.all([ 188 - ctx.client.db.user_tracks.select(["track_id.*"]).summarize({ 189 - filter: { 190 - "track_id.xata_id": scrobble.track_id.xata_id, 191 - }, 192 - columns: ["track_id.*"], 193 - summaries: { 194 - total: { 195 - count: "*", 196 - }, 197 - }, 198 - }), 199 - ctx.client.db.scrobbles.select(["track_id.*", "xata_createdat"]).summarize({ 200 - filter: { 201 - "track_id.xata_id": scrobble.track_id.xata_id, 202 - }, 203 - columns: ["track_id.*"], 204 - summaries: { 205 - total: { 206 - count: "*", 207 - }, 208 - }, 209 - }), 185 + ctx.db 186 + .select({ count: sql<number>`COUNT(*)` }) 187 + .from(tables.userTracks) 188 + .where(eq(tables.userTracks.trackId, scrobble.tracks.id)) 189 + .execute() 190 + .then((rows) => rows[0].count), 191 + ctx.db 192 + .select({ count: sql<number>`COUNT(*)` }) 193 + .from(tables.scrobbles) 194 + .where(eq(tables.scrobbles.trackId, scrobble.tracks.id)) 195 + .execute() 196 + .then((rows) => rows[0].count), 210 197 ]); 211 198 212 199 return c.json({ 213 - ...R.omit(["xata_id"], scrobble), 214 - id: scrobble.xata_id, 215 - listeners: _.get(listeners.summaries, "0.total", 1), 216 - scrobbles: _.get(scrobbles.summaries, "0.total", 1), 200 + ...scrobble, 201 + id: scrobble.scrobbles.id, 202 + listeners: listeners || 1, 203 + scrobbles: scrobbles || 1, 217 204 tags: [], 218 205 }); 219 206 }); ··· 227 214 const rkey = c.req.param("rkey"); 228 215 const uri = `at://${did}/app.rocksky.artist/${rkey}`; 229 216 230 - const artist = await ctx.client.db.user_artists 231 - .select(["artist_id.*"]) 232 - .filter({ 233 - $any: [{ uri }, { "artist_id.uri": uri }], 234 - }) 235 - .getFirst(); 217 + const artist = await ctx.db 218 + .select() 219 + .from(tables.userArtists) 220 + .leftJoin( 221 + tables.artists, 222 + eq(tables.userArtists.artistId, tables.artists.id) 223 + ) 224 + .where(or(eq(tables.userArtists.uri, uri), eq(tables.artists.uri, uri))) 225 + .limit(1) 226 + .execute() 227 + .then((rows) => rows[0]); 236 228 237 229 if (!artist) { 238 230 c.status(404); ··· 240 232 } 241 233 242 234 const [listeners, scrobbles] = await Promise.all([ 243 - ctx.client.db.user_artists.select(["artist_id.*"]).summarize({ 244 - filter: { 245 - "artist_id.xata_id": equals(artist.artist_id.xata_id), 246 - }, 247 - columns: ["artist_id.*"], 248 - summaries: { 249 - total: { 250 - count: "*", 251 - }, 252 - }, 253 - }), 254 - ctx.client.db.scrobbles 255 - .select(["artist_id.*", "xata_createdat"]) 256 - .summarize({ 257 - filter: { 258 - "artist_id.xata_id": artist.artist_id.xata_id, 259 - }, 260 - columns: ["artist_id.*"], 261 - summaries: { 262 - total: { 263 - count: "*", 264 - }, 265 - }, 266 - }), 235 + ctx.db 236 + .select({ count: sql<number>`COUNT(*)` }) 237 + .from(tables.userArtists) 238 + .where(eq(tables.userArtists.artistId, artist.artists.id)) 239 + .execute() 240 + .then((rows) => rows[0].count), 241 + ctx.db 242 + .select({ count: sql<number>`COUNT(*)` }) 243 + .from(tables.scrobbles) 244 + .where(eq(tables.scrobbles.artistId, artist.artists.id)) 245 + .execute() 246 + .then((rows) => rows[0].count), 267 247 ]); 268 248 269 249 return c.json({ 270 - ...R.omit(["xata_id"], artist.artist_id), 271 - id: artist.artist_id.xata_id, 272 - listeners: _.get(listeners.summaries, "0.total", 1), 273 - scrobbles: _.get(scrobbles.summaries, "0.total", 1), 250 + ...R.omit(["id"], artist.artists), 251 + id: artist.artists.id, 252 + listeners: listeners || 1, 253 + scrobbles: scrobbles || 1, 274 254 tags: [], 275 255 }); 276 256 }); ··· 285 265 const rkey = c.req.param("rkey"); 286 266 const uri = `at://${did}/app.rocksky.album/${rkey}`; 287 267 288 - const album = await ctx.client.db.user_albums 289 - .select(["album_id.*"]) 290 - .filter({ 291 - $any: [{ uri }, { "album_id.uri": uri }], 292 - }) 293 - .getFirst(); 268 + const album = await ctx.db 269 + .select() 270 + .from(tables.userAlbums) 271 + .leftJoin(tables.albums, eq(tables.userAlbums.albumId, tables.albums.id)) 272 + .where(or(eq(tables.userAlbums.uri, uri), eq(tables.albums.uri, uri))) 273 + .limit(1) 274 + .execute() 275 + .then((rows) => rows[0]); 294 276 295 277 if (!album) { 296 278 c.status(404); 297 279 return c.text("Album not found"); 298 280 } 299 281 300 - const tracks = await ctx.client.db.album_tracks 301 - .select(["track_id.*"]) 302 - .filter("album_id.xata_id", equals(album.album_id.xata_id)) 303 - .sort("track_id.track_number", "asc") 304 - .getAll(); 282 + const tracks = await ctx.db 283 + .select() 284 + .from(tables.albumTracks) 285 + .leftJoin(tables.tracks, eq(tables.albumTracks.trackId, tables.tracks.id)) 286 + .where(eq(tables.albumTracks.albumId, album.albums.id)) 287 + .orderBy(asc(tables.tracks.trackNumber)) 288 + .execute(); 305 289 306 290 const [listeners, scrobbles] = await Promise.all([ 307 - ctx.client.db.user_albums.select(["album_id.*"]).summarize({ 308 - filter: { 309 - "album_id.xata_id": equals(album.album_id.xata_id), 310 - }, 311 - columns: ["album_id.*"], 312 - summaries: { 313 - total: { 314 - count: "*", 315 - }, 316 - }, 317 - }), 318 - ctx.client.db.scrobbles.select(["album_id.*", "xata_createdat"]).summarize({ 319 - filter: { 320 - "album_id.xata_id": album.album_id.xata_id, 321 - }, 322 - columns: ["album_id.*"], 323 - summaries: { 324 - total: { 325 - count: "*", 326 - }, 327 - }, 328 - }), 291 + ctx.db 292 + .select({ count: sql<number>`COUNT(*)` }) 293 + .from(tables.userAlbums) 294 + .where(eq(tables.userAlbums.albumId, album.albums.id)) 295 + .execute() 296 + .then((rows) => rows[0].count), 297 + ctx.db 298 + .select({ count: sql<number>`COUNT(*)` }) 299 + .from(tables.scrobbles) 300 + .where(eq(tables.scrobbles.albumId, album.albums.id)) 301 + .execute() 302 + .then((rows) => rows[0].count), 329 303 ]); 330 304 331 305 return c.json({ 332 - ...R.omit(["xata_id"], album.album_id), 333 - id: album.album_id.xata_id, 334 - listeners: _.get(listeners.summaries, "0.total", 1), 335 - scrobbles: _.get(scrobbles.summaries, "0.total", 1), 336 - label: _.get(tracks, "0.track_id.label", ""), 337 - tracks: dedupeTracksKeepLyrics(tracks.map((track) => track.track_id)).sort( 338 - (a, b) => a.track_number - b.track_number, 306 + ...R.omit(["id"], album.albums), 307 + id: album.albums.id, 308 + listeners: listeners || 1, 309 + scrobbles: scrobbles || 1, 310 + label: tracks[0]?.tracks.label || "", 311 + tracks: dedupeTracksKeepLyrics(tracks.map((track) => track.tracks)).sort( 312 + (a, b) => a.track_number - b.track_number 339 313 ), 340 314 tags: [], 341 315 }); ··· 351 325 const uri = `at://${did}/app.rocksky.song/${rkey}`; 352 326 353 327 const [_track, user_track] = await Promise.all([ 354 - ctx.client.db.tracks.filter("uri", equals(uri)).getFirst(), 355 - ctx.client.db.user_tracks 356 - .select(["track_id.*"]) 357 - .filter("uri", equals(uri)) 358 - .getFirst(), 328 + ctx.db 329 + .select() 330 + .from(tables.tracks) 331 + .where(eq(tables.tracks.uri, uri)) 332 + .limit(1) 333 + .execute() 334 + .then((rows) => rows[0]), 335 + ctx.db 336 + .select() 337 + .from(tables.userTracks) 338 + .leftJoin(tables.tracks, eq(tables.userTracks.trackId, tables.tracks.id)) 339 + .where(eq(tables.userTracks.uri, uri)) 340 + .limit(1) 341 + .execute() 342 + .then((rows) => rows[0]), 359 343 ]); 360 - const track = _track || user_track.track_id; 344 + const track = _track || user_track?.tracks; 361 345 362 346 if (!track) { 363 347 c.status(404); ··· 365 349 } 366 350 367 351 const [listeners, scrobbles] = await Promise.all([ 368 - ctx.client.db.user_tracks.select(["track_id.*"]).summarize({ 369 - filter: { 370 - "track_id.xata_id": equals(track.xata_id), 371 - }, 372 - columns: ["track_id.*"], 373 - summaries: { 374 - total: { 375 - count: "*", 376 - }, 377 - }, 378 - }), 379 - ctx.client.db.scrobbles.select(["track_id.*", "xata_createdat"]).summarize({ 380 - filter: { 381 - "track_id.xata_id": track.xata_id, 382 - }, 383 - columns: ["track_id.*"], 384 - summaries: { 385 - total: { 386 - count: "*", 387 - }, 388 - }, 389 - }), 352 + ctx.db 353 + .select({ count: sql<number>`COUNT(*)` }) 354 + .from(tables.userTracks) 355 + .where(eq(tables.userTracks.trackId, track.id)) 356 + .execute() 357 + .then((rows) => rows[0].count), 358 + ctx.db 359 + .select({ count: sql<number>`COUNT(*)` }) 360 + .from(tables.scrobbles) 361 + .where(eq(tables.scrobbles.trackId, track.id)) 362 + .execute() 363 + .then((rows) => rows[0].count), 390 364 ]); 391 365 392 366 return c.json({ 393 - ...R.omit(["xata_id"], track), 394 - id: track.xata_id, 367 + ...R.omit(["id"], track), 368 + id: track.id, 395 369 tags: [], 396 - listeners: _.get(listeners.summaries, "0.total", 1), 397 - scrobbles: _.get(scrobbles.summaries, "0.total", 1), 370 + listeners: listeners || 1, 371 + scrobbles: scrobbles || 1, 398 372 }); 399 373 }); 400 374 ··· 409 383 const size = +c.req.query("size") || 10; 410 384 const offset = +c.req.query("offset") || 0; 411 385 412 - const tracks = await ctx.client.db.artist_tracks 413 - .select(["track_id.*", "xata_version"]) 414 - .filter({ 415 - "artist_id.uri": equals(uri), 416 - }) 417 - .sort("xata_version", "desc") 418 - .getPaginated({ 419 - pagination: { 420 - size, 421 - offset, 422 - }, 423 - }); 386 + const tracks = await ctx.db 387 + .select() 388 + .from(tables.artistTracks) 389 + .leftJoin(tables.tracks, eq(tables.artistTracks.trackId, tables.tracks.id)) 390 + .where(eq(tables.artistTracks.artistId, uri)) // Assuming artist_id is the URI or ID; adjust if needed 391 + .orderBy(desc(tables.artistTracks.xataVersion)) 392 + .limit(size) 393 + .offset(offset) 394 + .execute(); 395 + 424 396 return c.json( 425 - tracks.records.map((item) => ({ 426 - ...R.omit(["xata_id"], item.track_id), 427 - id: item.track_id.xata_id, 428 - xata_version: item.xata_version, 429 - })), 397 + tracks.map((item) => ({ 398 + ...R.omit(["id"], item.tracks), 399 + id: item.tracks.id, 400 + xata_version: item.artist_tracks.xataVersion, 401 + })) 430 402 ); 431 403 }); 432 404 ··· 441 413 const size = +c.req.query("size") || 10; 442 414 const offset = +c.req.query("offset") || 0; 443 415 444 - const albums = await ctx.client.db.artist_albums 445 - .select(["album_id.*", "xata_version"]) 446 - .filter({ 447 - "artist_id.uri": equals(uri), 448 - }) 449 - .sort("xata_version", "desc") 450 - .getPaginated({ 451 - pagination: { 452 - size, 453 - offset, 454 - }, 455 - }); 416 + const albums = await ctx.db 417 + .select() 418 + .from(tables.artistAlbums) 419 + .leftJoin(tables.albums, eq(tables.artistAlbums.albumId, tables.albums.id)) 420 + .where(eq(tables.artistAlbums.artistId, uri)) // Assuming artist_id is the URI or ID; adjust if needed 421 + .orderBy(desc(tables.artistAlbums.xataVersion)) 422 + .limit(size) 423 + .offset(offset) 424 + .execute(); 425 + 456 426 return c.json( 457 427 R.uniqBy( 458 428 (item) => item.id, 459 - albums.records.map((item) => ({ 460 - ...R.omit(["xata_id"], item.album_id), 461 - id: item.album_id.xata_id, 462 - xata_version: item.xata_version, 463 - })), 464 - ), 429 + albums.map((item) => ({ 430 + ...R.omit(["id"], item.albums), 431 + id: item.albums.id, 432 + xata_version: item.artist_albums.xataVersion, 433 + })) 434 + ) 465 435 ); 466 436 }); 467 437 ··· 491 461 .from(tables.playlistTracks) 492 462 .leftJoin( 493 463 tables.playlists, 494 - eq(tables.playlistTracks.playlistId, tables.playlists.id), 464 + eq(tables.playlistTracks.playlistId, tables.playlists.id) 495 465 ) 496 466 .leftJoin( 497 467 tables.tracks, 498 - eq(tables.playlistTracks.trackId, tables.tracks.id), 468 + eq(tables.playlistTracks.trackId, tables.tracks.id) 499 469 ) 500 470 .where(eq(tables.playlists.uri, uri)) 501 471 .groupBy( ··· 539 509 tables.playlists.picture, 540 510 tables.playlists.spotifyLink, 541 511 tables.playlists.tidalLink, 542 - tables.playlists.appleMusicLink, 512 + tables.playlists.appleMusicLink 543 513 ) 544 514 .orderBy(asc(tables.playlistTracks.createdAt)) 545 515 .execute(); ··· 589 559 .where(eq(tables.users.did, did)) 590 560 .execute(); 591 561 592 - const user = await ctx.client.db.users 593 - .select(["*"]) 594 - .filter("did", equals(did)) 595 - .getFirst(); 562 + const user = await ctx.db 563 + .select() 564 + .from(tables.users) 565 + .where(eq(tables.users.did, did)) 566 + .limit(1) 567 + .execute() 568 + .then((rows) => rows[0]); 596 569 597 570 ctx.nc.publish("rocksky.user", Buffer.from(JSON.stringify(user))); 598 571 } 599 572 } 600 573 } 601 574 602 - const user = await ctx.client.db.users 603 - .filter({ 604 - $any: [{ did }, { handle: did }], 605 - }) 606 - .getFirst(); 575 + const user = await ctx.db 576 + .select() 577 + .from(tables.users) 578 + .where(or(eq(tables.users.did, did), eq(tables.users.handle, did))) 579 + .limit(1) 580 + .execute() 581 + .then((rows) => rows[0]); 607 582 608 583 if (!user) { 609 584 c.status(404); ··· 630 605 }); 631 606 const agent = await createAgent(ctx.oauthClient, payload.did); 632 607 633 - const user = await ctx.client.db.users 634 - .filter("did", equals(payload.did)) 635 - .getFirst(); 608 + const user = await ctx.db 609 + .select() 610 + .from(tables.users) 611 + .where(eq(tables.users.did, payload.did)) 612 + .limit(1) 613 + .execute() 614 + .then((rows) => rows[0]); 636 615 if (!user) { 637 616 c.status(401); 638 617 return c.text("Unauthorized"); ··· 653 632 parsed.data, 654 633 `at://${did}/app.rocksky.artist/${rkey}`, 655 634 user, 656 - agent, 635 + agent 657 636 ); 658 637 return c.json({}); 659 638 }); ··· 675 654 }); 676 655 const agent = await createAgent(ctx.oauthClient, payload.did); 677 656 678 - const user = await ctx.client.db.users 679 - .filter("did", equals(payload.did)) 680 - .getFirst(); 657 + const user = await ctx.db 658 + .select() 659 + .from(tables.users) 660 + .where(eq(tables.users.did, payload.did)) 661 + .limit(1) 662 + .execute() 663 + .then((rows) => rows[0]); 681 664 if (!user) { 682 665 c.status(401); 683 666 return c.text("Unauthorized"); ··· 698 681 parsed.data, 699 682 `at://${did}/app.rocksky.album/${rkey}`, 700 683 user, 701 - agent, 684 + agent 702 685 ); 703 686 return c.json({}); 704 687 }); ··· 720 703 }); 721 704 const agent = await createAgent(ctx.oauthClient, payload.did); 722 705 723 - const user = await ctx.client.db.users 724 - .filter("did", equals(payload.did)) 725 - .getFirst(); 706 + const user = await ctx.db 707 + .select() 708 + .from(tables.users) 709 + .where(eq(tables.users.did, payload.did)) 710 + .limit(1) 711 + .execute() 712 + .then((rows) => rows[0]); 726 713 if (!user) { 727 714 c.status(401); 728 715 return c.text("Unauthorized"); ··· 743 730 parsed.data, 744 731 `at://${did}/app.rocksky.song/${rkey}`, 745 732 user, 746 - agent, 733 + agent 747 734 ); 748 735 749 736 return c.json({}); ··· 766 753 }); 767 754 const agent = await createAgent(ctx.oauthClient, payload.did); 768 755 769 - const user = await ctx.client.db.users 770 - .filter("did", equals(payload.did)) 771 - .getFirst(); 756 + const user = await ctx.db 757 + .select() 758 + .from(tables.users) 759 + .where(eq(tables.users.did, payload.did)) 760 + .limit(1) 761 + .execute() 762 + .then((rows) => rows[0]); 772 763 if (!user) { 773 764 c.status(401); 774 765 return c.text("Unauthorized"); ··· 789 780 parsed.data, 790 781 `at://${did}/app.rocksky.scrobble/${rkey}`, 791 782 user, 792 - agent, 783 + agent 793 784 ); 794 785 795 786 return c.json({}); ··· 809 800 }); 810 801 const agent = await createAgent(ctx.oauthClient, payload.did); 811 802 812 - const user = await ctx.client.db.users 813 - .filter("did", equals(payload.did)) 814 - .getFirst(); 803 + const user = await ctx.db 804 + .select() 805 + .from(tables.users) 806 + .where(eq(tables.users.did, payload.did)) 807 + .limit(1) 808 + .execute() 809 + .then((rows) => rows[0]); 815 810 if (!user) { 816 811 c.status(401); 817 812 return c.text("Unauthorized"); ··· 825 820 return c.text("Invalid shout data: " + parsed.error.message); 826 821 } 827 822 828 - const _user = await ctx.client.db.users 829 - .filter({ 830 - $any: [{ did }, { handle: did }], 831 - }) 832 - .getFirst(); 823 + const _user = await ctx.db 824 + .select() 825 + .from(tables.users) 826 + .where(or(eq(tables.users.did, did), eq(tables.users.handle, did))) 827 + .limit(1) 828 + .execute() 829 + .then((rows) => rows[0]); 833 830 834 831 if (!_user) { 835 832 c.status(404); ··· 858 855 }); 859 856 const agent = await createAgent(ctx.oauthClient, payload.did); 860 857 861 - const user = await ctx.client.db.users 862 - .filter("did", equals(payload.did)) 863 - .getFirst(); 858 + const user = await ctx.db 859 + .select() 860 + .from(tables.users) 861 + .where(eq(tables.users.did, payload.did)) 862 + .limit(1) 863 + .execute() 864 + .then((rows) => rows[0]); 864 865 if (!user) { 865 866 c.status(401); 866 867 return c.text("Unauthorized"); ··· 890 891 }); 891 892 const agent = await createAgent(ctx.oauthClient, payload.did); 892 893 893 - const user = await ctx.client.db.users 894 - .filter("did", equals(payload.did)) 895 - .getFirst(); 894 + const user = await ctx.db 895 + .select() 896 + .from(tables.users) 897 + .where(eq(tables.users.did, payload.did)) 898 + .limit(1) 899 + .execute() 900 + .then((rows) => rows[0]); 896 901 if (!user) { 897 902 c.status(401); 898 903 return c.text("Unauthorized"); ··· 923 928 }); 924 929 const agent = await createAgent(ctx.oauthClient, payload.did); 925 930 926 - const user = await ctx.client.db.users 927 - .filter("did", equals(payload.did)) 928 - .getFirst(); 931 + const user = await ctx.db 932 + .select() 933 + .from(tables.users) 934 + .where(eq(tables.users.did, payload.did)) 935 + .limit(1) 936 + .execute() 937 + .then((rows) => rows[0]); 929 938 if (!user) { 930 939 c.status(401); 931 940 return c.text("Unauthorized"); ··· 934 943 const did = c.req.param("did"); 935 944 const rkey = c.req.param("rkey"); 936 945 937 - const result = await ctx.client.db.tracks 938 - .filter("uri", equals(`at://${did}/app.rocksky.song/${rkey}`)) 939 - .getFirst(); 946 + const result = await ctx.db 947 + .select() 948 + .from(tables.tracks) 949 + .where(eq(tables.tracks.uri, `at://${did}/app.rocksky.song/${rkey}`)) 950 + .limit(1) 951 + .execute() 952 + .then((rows) => rows[0]); 940 953 941 954 const track: Track = { 942 955 title: result.title, 943 956 artist: result.artist, 944 957 album: result.album, 945 - albumArt: result.album_art, 946 - albumArtist: result.album_artist, 947 - trackNumber: result.track_number, 958 + albumArt: result.albumArt, 959 + albumArtist: result.albumArtist, 960 + trackNumber: result.trackNumber, 948 961 duration: result.duration, 949 962 composer: result.composer, 950 963 lyrics: result.lyrics, 951 - discNumber: result.disc_number, 964 + discNumber: result.discNumber, 952 965 }; 953 966 await likeTrack(ctx, track, user, agent); 954 967 ··· 972 985 }); 973 986 const agent = await createAgent(ctx.oauthClient, payload.did); 974 987 975 - const user = await ctx.client.db.users 976 - .filter("did", equals(payload.did)) 977 - .getFirst(); 988 + const user = await ctx.db 989 + .select() 990 + .from(tables.users) 991 + .where(eq(tables.users.did, payload.did)) 992 + .limit(1) 993 + .execute() 994 + .then((rows) => rows[0]); 978 995 if (!user) { 979 996 c.status(401); 980 997 return c.text("Unauthorized"); ··· 983 1000 const did = c.req.param("did"); 984 1001 const rkey = c.req.param("rkey"); 985 1002 986 - const track = await ctx.client.db.tracks 987 - .filter("uri", equals(`at://${did}/app.rocksky.song/${rkey}`)) 988 - .getFirst(); 1003 + const track = await ctx.db 1004 + .select() 1005 + .from(tables.tracks) 1006 + .where(eq(tables.tracks.uri, `at://${did}/app.rocksky.song/${rkey}`)) 1007 + .limit(1) 1008 + .execute() 1009 + .then((rows) => rows[0]); 989 1010 990 1011 if (!track) { 991 1012 c.status(404); ··· 1014 1035 }); 1015 1036 const agent = await createAgent(ctx.oauthClient, payload.did); 1016 1037 1017 - const user = await ctx.client.db.users 1018 - .filter("did", equals(payload.did)) 1019 - .getFirst(); 1038 + const user = await ctx.db 1039 + .select() 1040 + .from(tables.users) 1041 + .where(eq(tables.users.did, payload.did)) 1042 + .limit(1) 1043 + .execute() 1044 + .then((rows) => rows[0]); 1020 1045 if (!user) { 1021 1046 c.status(401); 1022 1047 return c.text("Unauthorized"); ··· 1038 1063 parsed.data, 1039 1064 `at://${did}/app.rocksky.shout/${rkey}`, 1040 1065 user, 1041 - agent, 1066 + agent 1042 1067 ); 1043 1068 return c.json({}); 1044 1069 }); ··· 1053 1078 1054 1079 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim(); 1055 1080 1056 - let user; 1081 + let user: SelectUser | undefined; 1057 1082 if (bearer && bearer !== "null") { 1058 1083 const payload = jwt.verify(bearer, env.JWT_SECRET, { 1059 1084 ignoreExpiration: true, 1060 1085 }); 1061 1086 1062 - user = await ctx.client.db.users 1063 - .filter("did", equals(payload.did)) 1064 - .getFirst(); 1087 + user = await ctx.db 1088 + .select() 1089 + .from(tables.users) 1090 + .where(eq(tables.users.did, payload.did)) 1091 + .limit(1) 1092 + .execute() 1093 + .then((rows) => rows[0]); 1065 1094 } 1066 1095 1067 1096 const shouts = await ctx.db ··· 1078 1107 EXISTS ( 1079 1108 SELECT 1 1080 1109 FROM ${tables.shoutLikes} 1081 - WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.xata_id 1082 - AND ${tables.shoutLikes}.user_id = ${user.xata_id} 1110 + WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.id 1111 + AND ${tables.shoutLikes}.user_id = ${user.id} 1083 1112 )`.as("liked"), 1084 1113 } 1085 1114 : { ··· 1103 1132 .leftJoin(tables.artists, eq(tables.shouts.artistId, tables.artists.id)) 1104 1133 .leftJoin( 1105 1134 tables.shoutLikes, 1106 - eq(tables.shouts.id, tables.shoutLikes.shoutId), 1135 + eq(tables.shouts.id, tables.shoutLikes.shoutId) 1107 1136 ) 1108 1137 .where(eq(tables.artists.uri, `at://${did}/app.rocksky.artist/${rkey}`)) 1109 1138 .groupBy( ··· 1116 1145 tables.users.did, 1117 1146 tables.users.handle, 1118 1147 tables.users.displayName, 1119 - tables.users.avatar, 1148 + tables.users.avatar 1120 1149 ) 1121 1150 .orderBy(desc(tables.shouts.createdAt)) 1122 1151 .execute(); ··· 1134 1163 1135 1164 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim(); 1136 1165 1137 - let user; 1166 + let user: SelectUser | undefined; 1138 1167 if (bearer && bearer !== "null") { 1139 1168 const payload = jwt.verify(bearer, env.JWT_SECRET, { 1140 1169 ignoreExpiration: true, 1141 1170 }); 1142 1171 1143 - user = await ctx.client.db.users 1144 - .filter("did", equals(payload.did)) 1145 - .getFirst(); 1172 + user = await ctx.db 1173 + .select() 1174 + .from(tables.users) 1175 + .where(eq(tables.users.did, payload.did)) 1176 + .limit(1) 1177 + .execute() 1178 + .then((rows) => rows[0]); 1146 1179 } 1147 1180 1148 1181 const shouts = await ctx.db ··· 1159 1192 EXISTS ( 1160 1193 SELECT 1 1161 1194 FROM ${tables.shoutLikes} 1162 - WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.xata_id 1163 - AND ${tables.shoutLikes}.user_id = ${user.xata_id} 1195 + WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.id 1196 + AND ${tables.shoutLikes}.user_id = ${user.id} 1164 1197 )`.as("liked"), 1165 1198 } 1166 1199 : { ··· 1184 1217 .leftJoin(tables.albums, eq(tables.shouts.albumId, tables.albums.id)) 1185 1218 .leftJoin( 1186 1219 tables.shoutLikes, 1187 - eq(tables.shouts.id, tables.shoutLikes.shoutId), 1220 + eq(tables.shouts.id, tables.shoutLikes.shoutId) 1188 1221 ) 1189 1222 .where(eq(tables.albums.uri, `at://${did}/app.rocksky.album/${rkey}`)) 1190 1223 .groupBy( ··· 1197 1230 tables.users.did, 1198 1231 tables.users.handle, 1199 1232 tables.users.displayName, 1200 - tables.users.avatar, 1233 + tables.users.avatar 1201 1234 ) 1202 1235 .orderBy(desc(tables.shouts.createdAt)) 1203 1236 .execute(); ··· 1215 1248 1216 1249 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim(); 1217 1250 1218 - let user; 1251 + let user: SelectUser | undefined; 1219 1252 if (bearer && bearer !== "null") { 1220 1253 const payload = jwt.verify(bearer, env.JWT_SECRET, { 1221 1254 ignoreExpiration: true, 1222 1255 }); 1223 1256 1224 - user = await ctx.client.db.users 1225 - .filter("did", equals(payload.did)) 1226 - .getFirst(); 1257 + user = await ctx.db 1258 + .select() 1259 + .from(tables.users) 1260 + .where(eq(tables.users.did, payload.did)) 1261 + .limit(1) 1262 + .execute() 1263 + .then((rows) => rows[0]); 1227 1264 } 1228 1265 1229 1266 const shouts = await ctx.db ··· 1240 1277 EXISTS ( 1241 1278 SELECT 1 1242 1279 FROM ${tables.shoutLikes} 1243 - WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.xata_id 1244 - AND ${tables.shoutLikes}.user_id = ${user.xata_id} 1280 + WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.id 1281 + AND ${tables.shoutLikes}.user_id = ${user.id} 1245 1282 )`.as("liked"), 1246 1283 } 1247 1284 : { ··· 1265 1302 .leftJoin(tables.tracks, eq(tables.shouts.trackId, tables.tracks.id)) 1266 1303 .leftJoin( 1267 1304 tables.shoutLikes, 1268 - eq(tables.shouts.id, tables.shoutLikes.shoutId), 1305 + eq(tables.shouts.id, tables.shoutLikes.shoutId) 1269 1306 ) 1270 1307 .where(eq(tables.tracks.uri, `at://${did}/app.rocksky.song/${rkey}`)) 1271 1308 .groupBy( ··· 1278 1315 tables.users.did, 1279 1316 tables.users.handle, 1280 1317 tables.users.displayName, 1281 - tables.users.avatar, 1318 + tables.users.avatar 1282 1319 ) 1283 1320 .orderBy(desc(tables.shouts.createdAt)) 1284 1321 .execute(); ··· 1296 1333 1297 1334 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim(); 1298 1335 1299 - let user; 1336 + let user: SelectUser | undefined; 1300 1337 if (bearer && bearer !== "null") { 1301 1338 const payload = jwt.verify(bearer, env.JWT_SECRET, { 1302 1339 ignoreExpiration: true, 1303 1340 }); 1304 1341 1305 - user = await ctx.client.db.users 1306 - .filter("did", equals(payload.did)) 1307 - .getFirst(); 1342 + user = await ctx.db 1343 + .select() 1344 + .from(tables.users) 1345 + .where(eq(tables.users.did, payload.did)) 1346 + .limit(1) 1347 + .execute() 1348 + .then((rows) => rows[0]); 1308 1349 } 1309 1350 1310 1351 const shouts = await ctx.db ··· 1321 1362 EXISTS ( 1322 1363 SELECT 1 1323 1364 FROM ${tables.shoutLikes} 1324 - WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.xata_id 1325 - AND ${tables.shoutLikes}.user_id = ${user.xata_id} 1365 + WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.id 1366 + AND ${tables.shoutLikes}.user_id = ${user.id} 1326 1367 )`.as("liked"), 1327 1368 } 1328 1369 : { ··· 1345 1386 .leftJoin(tables.users, eq(tables.shouts.authorId, tables.users.id)) 1346 1387 .leftJoin( 1347 1388 tables.scrobbles, 1348 - eq(tables.shouts.scrobbleId, tables.scrobbles.id), 1389 + eq(tables.shouts.scrobbleId, tables.scrobbles.id) 1349 1390 ) 1350 1391 .leftJoin( 1351 1392 tables.shoutLikes, 1352 - eq(tables.shouts.id, tables.shoutLikes.shoutId), 1393 + eq(tables.shouts.id, tables.shoutLikes.shoutId) 1353 1394 ) 1354 1395 .where(eq(tables.scrobbles.uri, `at://${did}/app.rocksky.scrobble/${rkey}`)) 1355 1396 .groupBy( ··· 1362 1403 tables.users.did, 1363 1404 tables.users.handle, 1364 1405 tables.users.displayName, 1365 - tables.users.avatar, 1406 + tables.users.avatar 1366 1407 ) 1367 1408 .orderBy(desc(tables.shouts.createdAt)) 1368 1409 .execute(); ··· 1376 1417 1377 1418 const bearer = (c.req.header("authorization") || "").split(" ")[1]?.trim(); 1378 1419 1379 - let user; 1420 + let user: SelectUser | undefined; 1380 1421 if (bearer && bearer !== "null") { 1381 1422 const payload = jwt.verify(bearer, env.JWT_SECRET, { 1382 1423 ignoreExpiration: true, 1383 1424 }); 1384 1425 1385 - user = await ctx.client.db.users 1386 - .filter("did", equals(payload.did)) 1387 - .getFirst(); 1426 + user = await ctx.db 1427 + .select() 1428 + .from(tables.users) 1429 + .where(eq(tables.users.did, payload.did)) 1430 + .limit(1) 1431 + .execute() 1432 + .then((rows) => rows[0]); 1388 1433 } 1389 1434 1390 1435 const shouts = await ctx.db ··· 1405 1450 EXISTS ( 1406 1451 SELECT 1 1407 1452 FROM ${tables.shoutLikes} 1408 - WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.xata_id 1409 - AND ${tables.shoutLikes}.user_id = ${user.xata_id} 1453 + WHERE ${tables.shoutLikes}.shout_id = ${tables.shouts}.id 1454 + AND ${tables.shoutLikes}.user_id = ${user.id} 1410 1455 )`.as("liked"), 1411 1456 reported: sql<boolean>` 1412 1457 EXISTS ( 1413 1458 SELECT 1 1414 1459 FROM ${tables.shoutReports} 1415 - WHERE ${tables.shoutReports}.shout_id = ${tables.shouts}.xata_id 1416 - AND ${tables.shoutReports}.user_id = ${user.xata_id} 1460 + WHERE ${tables.shoutReports}.shout_id = ${tables.shouts}.id 1461 + AND ${tables.shoutReports}.user_id = ${user.id} 1417 1462 )`.as("reported"), 1418 1463 } 1419 1464 : { ··· 1437 1482 .leftJoin(tables.shouts, eq(tables.profileShouts.shoutId, tables.shouts.id)) 1438 1483 .leftJoin( 1439 1484 aliasedTable(tables.users, "authors"), 1440 - eq(tables.shouts.authorId, aliasedTable(tables.users, "authors").id), 1485 + eq(tables.shouts.authorId, aliasedTable(tables.users, "authors").id) 1441 1486 ) 1442 1487 .leftJoin(tables.users, eq(tables.profileShouts.userId, tables.users.id)) 1443 1488 .leftJoin( 1444 1489 tables.shoutLikes, 1445 - eq(tables.shouts.id, tables.shoutLikes.shoutId), 1490 + eq(tables.shouts.id, tables.shoutLikes.shoutId) 1446 1491 ) 1447 1492 .groupBy( 1448 1493 tables.profileShouts.id, ··· 1461 1506 aliasedTable(tables.users, "authors").did, 1462 1507 aliasedTable(tables.users, "authors").handle, 1463 1508 aliasedTable(tables.users, "authors").displayName, 1464 - aliasedTable(tables.users, "authors").avatar, 1509 + aliasedTable(tables.users, "authors").avatar 1465 1510 ) 1466 1511 .orderBy(desc(tables.profileShouts.createdAt)) 1467 1512 .execute(); ··· 1476 1521 }); 1477 1522 const did = c.req.param("did"); 1478 1523 const rkey = c.req.param("rkey"); 1479 - const likes = await ctx.client.db.shout_likes 1480 - .select(["user_id.*", "xata_createdat"]) 1481 - .filter("shout_id.uri", `at://${did}/app.rocksky.shout/${rkey}`) 1482 - .getAll(); 1524 + const likes = await ctx.db 1525 + .select() 1526 + .from(tables.shoutLikes) 1527 + .leftJoin(tables.users, eq(tables.shoutLikes.userId, tables.users.id)) 1528 + .leftJoin(tables.shouts, eq(tables.shoutLikes.shoutId, tables.shouts.id)) 1529 + .where(eq(tables.shouts.uri, `at://${did}/app.rocksky.shout/${rkey}`)) 1530 + .execute(); 1483 1531 return c.json(likes); 1484 1532 }); 1485 1533 ··· 1490 1538 }); 1491 1539 const did = c.req.param("did"); 1492 1540 const rkey = c.req.param("rkey"); 1493 - const shouts = await ctx.client.db.shouts 1494 - .select(["author_id.*", "xata_createdat"]) 1495 - .filter("parent_id.uri", `at://${did}/app.rocksky.shout/${rkey}`) 1496 - .sort("xata_createdat", "asc") 1497 - .getAll(); 1541 + const shouts = await ctx.db 1542 + .select() 1543 + .from(tables.shouts) 1544 + .leftJoin(tables.users, eq(tables.shouts.authorId, tables.users.id)) 1545 + .where(eq(tables.shouts.parentId, `at://${did}/app.rocksky.shout/${rkey}`)) 1546 + .orderBy(asc(tables.shouts.createdAt)) 1547 + .execute(); 1498 1548 return c.json(shouts); 1499 1549 }); 1500 1550 ··· 1533 1583 const payload = jwt.verify(bearer, env.JWT_SECRET, { 1534 1584 ignoreExpiration: true, 1535 1585 }); 1536 - const shout = await ctx.client.db.shouts 1537 - .filter("uri", `at://${did}/app.rocksky.shout/${rkey}`) 1538 - .getFirst(); 1586 + const shout = await ctx.db 1587 + .select() 1588 + .from(tables.shouts) 1589 + .where(eq(tables.shouts.uri, `at://${did}/app.rocksky.shout/${rkey}`)) 1590 + .limit(1) 1591 + .execute() 1592 + .then((rows) => rows[0]); 1539 1593 1540 - const user = await ctx.client.db.users 1541 - .filter("did", equals(payload.did)) 1542 - .getFirst(); 1594 + const user = await ctx.db 1595 + .select() 1596 + .from(tables.users) 1597 + .where(eq(tables.users.did, payload.did)) 1598 + .limit(1) 1599 + .execute() 1600 + .then((rows) => rows[0]); 1543 1601 1544 1602 if (!shout) { 1545 1603 c.status(404); ··· 1551 1609 return c.text("Unauthorized"); 1552 1610 } 1553 1611 1554 - const existingReport = await ctx.client.db.shout_reports 1555 - .filter({ 1556 - user_id: user.xata_id, 1557 - shout_id: shout.xata_id, 1558 - }) 1559 - .getFirst(); 1612 + const existingReport = await ctx.db 1613 + .select() 1614 + .from(tables.shoutReports) 1615 + .where( 1616 + and( 1617 + eq(tables.shoutReports.userId, user.id), 1618 + eq(tables.shoutReports.shoutId, shout.id) 1619 + ) 1620 + ) 1621 + .limit(1) 1622 + .execute() 1623 + .then((rows) => rows[0]); 1560 1624 1561 1625 if (existingReport) { 1562 1626 return c.json(existingReport); 1563 1627 } 1564 1628 1565 - const report = await ctx.client.db.shout_reports.create({ 1566 - user_id: user.xata_id, 1567 - shout_id: shout.xata_id, 1568 - }); 1629 + const report = await ctx.db 1630 + .insert(tables.shoutReports) 1631 + .values({ 1632 + userId: user.id, 1633 + shoutId: shout.id, 1634 + }) 1635 + .returning() 1636 + .execute() 1637 + .then((rows) => rows[0]); 1569 1638 1570 1639 return c.json(report); 1571 1640 }); ··· 1588 1657 const payload = jwt.verify(bearer, env.JWT_SECRET, { 1589 1658 ignoreExpiration: true, 1590 1659 }); 1591 - const shout = await ctx.client.db.shouts 1592 - .filter("uri", `at://${did}/app.rocksky.shout/${rkey}`) 1593 - .getFirst(); 1660 + const shout = await ctx.db 1661 + .select() 1662 + .from(tables.shouts) 1663 + .where(eq(tables.shouts.uri, `at://${did}/app.rocksky.shout/${rkey}`)) 1664 + .limit(1) 1665 + .execute() 1666 + .then((rows) => rows[0]); 1594 1667 1595 - const user = await ctx.client.db.users 1596 - .filter("did", equals(payload.did)) 1597 - .getFirst(); 1668 + const user = await ctx.db 1669 + .select() 1670 + .from(tables.users) 1671 + .where(eq(tables.users.did, payload.did)) 1672 + .limit(1) 1673 + .execute() 1674 + .then((rows) => rows[0]); 1598 1675 1599 1676 if (!shout) { 1600 1677 c.status(404); ··· 1606 1683 return c.text("Unauthorized"); 1607 1684 } 1608 1685 1609 - const report = await ctx.client.db.shout_reports 1610 - .select(["user_id.*", "shout_id.*"]) 1611 - .filter({ 1612 - user_id: user.xata_id, 1613 - shout_id: shout.xata_id, 1614 - }) 1615 - .getFirst(); 1686 + const report = await ctx.db 1687 + .select() 1688 + .from(tables.shoutReports) 1689 + .where( 1690 + and( 1691 + eq(tables.shoutReports.userId, user.id), 1692 + eq(tables.shoutReports.shoutId, shout.id) 1693 + ) 1694 + ) 1695 + .limit(1) 1696 + .execute() 1697 + .then((rows) => rows[0]); 1616 1698 1617 1699 if (!report) { 1618 1700 c.status(404); 1619 1701 return c.text("Report not found"); 1620 1702 } 1621 1703 1622 - if (report.user_id.xata_id !== user.xata_id) { 1704 + if (report.userId !== user.id) { 1623 1705 c.status(403); 1624 1706 return c.text("Forbidden"); 1625 1707 } 1626 1708 1627 - await ctx.client.db.shout_reports.delete(report.xata_id); 1709 + await ctx.db 1710 + .delete(tables.shoutReports) 1711 + .where(eq(tables.shoutReports.id, report.id)) 1712 + .execute(); 1628 1713 1629 1714 return c.json(report); 1630 1715 }); ··· 1649 1734 }); 1650 1735 const agent = await createAgent(ctx.oauthClient, payload.did); 1651 1736 1652 - const user = await ctx.client.db.users 1653 - .filter("did", equals(payload.did)) 1654 - .getFirst(); 1737 + const user = await ctx.db 1738 + .select() 1739 + .from(tables.users) 1740 + .where(eq(tables.users.did, payload.did)) 1741 + .limit(1) 1742 + .execute() 1743 + .then((rows) => rows[0]); 1655 1744 1656 1745 if (!user) { 1657 1746 c.status(401); 1658 1747 return c.text("Unauthorized"); 1659 1748 } 1660 1749 1661 - const shout = await ctx.client.db.shouts 1662 - .select([ 1663 - "author_id.*", 1664 - "uri", 1665 - "content", 1666 - "xata_id", 1667 - "xata_createdat", 1668 - "parent_id", 1669 - ]) 1670 - .filter("uri", `at://${did}/app.rocksky.shout/${rkey}`) 1671 - .getFirst(); 1750 + const shout = await ctx.db 1751 + .select() 1752 + .from(tables.shouts) 1753 + .where(eq(tables.shouts.uri, `at://${did}/app.rocksky.shout/${rkey}`)) 1754 + .limit(1) 1755 + .execute() 1756 + .then((rows) => rows[0]); 1672 1757 1673 1758 if (!shout) { 1674 1759 c.status(404); 1675 1760 return c.text("Shout not found"); 1676 1761 } 1677 1762 1678 - if (shout.author_id.xata_id !== user.xata_id) { 1763 + if (shout.authorId !== user.id) { 1679 1764 c.status(403); 1680 1765 return c.text("Forbidden"); 1681 1766 } ··· 1687 1772 }, 1688 1773 }) 1689 1774 .from(tables.shouts) 1690 - .where(eq(tables.shouts.parentId, shout.xata_id)) 1775 + .where(eq(tables.shouts.parentId, shout.id)) 1691 1776 .execute(); 1692 1777 1693 1778 const replyIds = replies.map(({ replies: r }) => r.id); ··· 1705 1790 1706 1791 await ctx.db 1707 1792 .delete(tables.profileShouts) 1708 - .where(eq(tables.profileShouts.shoutId, shout.xata_id)) 1793 + .where(eq(tables.profileShouts.shoutId, shout.id)) 1709 1794 .execute(); 1710 1795 1711 1796 await ctx.db ··· 1715 1800 1716 1801 await ctx.db 1717 1802 .delete(tables.shoutLikes) 1718 - .where(eq(tables.shoutLikes.shoutId, shout.xata_id)) 1803 + .where(eq(tables.shoutLikes.shoutId, shout.id)) 1719 1804 .execute(); 1720 1805 1721 1806 await ctx.db 1722 1807 .delete(tables.shoutReports) 1723 - .where(eq(tables.shoutReports.shoutId, shout.xata_id)) 1808 + .where(eq(tables.shoutReports.shoutId, shout.id)) 1724 1809 .execute(); 1725 1810 1726 1811 await ctx.db ··· 1730 1815 1731 1816 await ctx.db 1732 1817 .delete(tables.shouts) 1733 - .where(eq(tables.shouts.id, shout.xata_id)) 1818 + .where(eq(tables.shouts.id, shout.id)) 1734 1819 .execute(); 1735 1820 1736 1821 await agent.com.atproto.repo.deleteRecord({
+52 -31
apps/api/src/webscrobbler/app.ts
··· 1 1 import { ctx } from "context"; 2 - import { eq } from "drizzle-orm"; 2 + import { eq, or } from "drizzle-orm"; 3 3 import { Hono } from "hono"; 4 4 import jwt from "jsonwebtoken"; 5 5 import { env } from "lib/env"; ··· 21 21 ignoreExpiration: true, 22 22 }); 23 23 24 - const user = await ctx.client.db.users 25 - .filter({ 26 - $any: [{ did }, { handle: did }], 27 - }) 28 - .getFirst(); 24 + const user = await ctx.db 25 + .select() 26 + .from(users) 27 + .where(or(eq(users.did, did), eq(users.handle, did))) 28 + .limit(1) 29 + .then((rows) => rows[0]); 30 + 29 31 if (!user) { 30 32 c.status(401); 31 33 return c.text("Unauthorized"); ··· 35 37 .select() 36 38 .from(webscrobblers) 37 39 .leftJoin(users, eq(webscrobblers.userId, users.id)) 38 - .where(eq(users.did, did)) 39 - .execute(); 40 + .where(eq(users.did, did)); 40 41 41 42 if (records.length === 0) { 42 - const record = await ctx.client.db.webscrobblers.create({ 43 - uuid: uuid(), 44 - user_id: user.xata_id, 45 - name: "webscrobbler", 46 - }); 43 + const [record] = await ctx.db 44 + .insert(webscrobblers) 45 + .values({ 46 + uuid: uuid(), 47 + userId: user.id, 48 + name: "webscrobbler", 49 + }) 50 + .returning(); 47 51 return c.json(record); 48 52 } 49 53 ··· 63 67 ignoreExpiration: true, 64 68 }); 65 69 66 - const user = await ctx.client.db.users 67 - .filter({ 68 - $any: [{ did }, { handle: did }], 69 - }) 70 - .getFirst(); 70 + const user = await ctx.db 71 + .select() 72 + .from(users) 73 + .where(or(eq(users.did, did), eq(users.handle, did))) 74 + .limit(1) 75 + .then((rows) => rows[0]); 76 + 71 77 if (!user) { 72 78 c.status(401); 73 79 return c.text("Unauthorized"); ··· 80 86 return c.text("Invalid id"); 81 87 } 82 88 83 - const existing = await ctx.client.db.webscrobblers 84 - .filter({ user_id: user.xata_id }) 85 - .getFirst(); 89 + const existing = await ctx.db 90 + .select() 91 + .from(webscrobblers) 92 + .where(eq(webscrobblers.userId, user.id)) 93 + .limit(1) 94 + .then((rows) => rows[0]); 86 95 87 - const record = await ctx.client.db.webscrobblers.createOrReplace( 88 - existing?.xata_id, 89 - { 90 - uuid: id, 91 - user_id: user.xata_id, 92 - name: "webscrobbler", 93 - }, 94 - ); 95 - 96 - return c.json(record); 96 + if (existing) { 97 + const [record] = await ctx.db 98 + .update(webscrobblers) 99 + .set({ 100 + uuid: id, 101 + userId: user.id, 102 + name: "webscrobbler", 103 + }) 104 + .where(eq(webscrobblers.id, existing.id)) 105 + .returning(); 106 + return c.json(record); 107 + } else { 108 + const [record] = await ctx.db 109 + .insert(webscrobblers) 110 + .values({ 111 + uuid: id, 112 + userId: user.id, 113 + name: "webscrobbler", 114 + }) 115 + .returning(); 116 + return c.json(record); 117 + } 97 118 }); 98 119 99 120 export default app;
-5265
apps/api/src/xata.ts
··· 1 - // Generated by Xata Codegen 0.30.1. Please do not edit. 2 - import { buildClient } from "@xata.io/client"; 3 - import type { 4 - BaseClientOptions, 5 - SchemaInference, 6 - XataRecord, 7 - } from "@xata.io/client"; 8 - 9 - const tables = [ 10 - { 11 - name: "album_tags", 12 - checkConstraints: { 13 - album_tags_xata_id_length_xata_id: { 14 - name: "album_tags_xata_id_length_xata_id", 15 - columns: ["xata_id"], 16 - definition: "CHECK ((length(xata_id) < 256))", 17 - }, 18 - }, 19 - foreignKeys: { 20 - album_id_link: { 21 - name: "album_id_link", 22 - columns: ["album_id"], 23 - referencedTable: "albums", 24 - referencedColumns: ["xata_id"], 25 - onDelete: "SET NULL", 26 - }, 27 - tag_id_link: { 28 - name: "tag_id_link", 29 - columns: ["tag_id"], 30 - referencedTable: "tags", 31 - referencedColumns: ["xata_id"], 32 - onDelete: "SET NULL", 33 - }, 34 - }, 35 - primaryKey: [], 36 - uniqueConstraints: { 37 - _pgroll_new_album_tags_xata_id_key: { 38 - name: "_pgroll_new_album_tags_xata_id_key", 39 - columns: ["xata_id"], 40 - }, 41 - }, 42 - columns: [ 43 - { 44 - name: "album_id", 45 - type: "link", 46 - link: { table: "albums" }, 47 - notNull: true, 48 - unique: false, 49 - defaultValue: null, 50 - comment: '{"xata.link":"albums"}', 51 - }, 52 - { 53 - name: "tag_id", 54 - type: "link", 55 - link: { table: "tags" }, 56 - notNull: true, 57 - unique: false, 58 - defaultValue: null, 59 - comment: '{"xata.link":"tags"}', 60 - }, 61 - { 62 - name: "xata_createdat", 63 - type: "datetime", 64 - notNull: true, 65 - unique: false, 66 - defaultValue: "now()", 67 - comment: "", 68 - }, 69 - { 70 - name: "xata_id", 71 - type: "text", 72 - notNull: true, 73 - unique: true, 74 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 75 - comment: "", 76 - }, 77 - { 78 - name: "xata_updatedat", 79 - type: "datetime", 80 - notNull: true, 81 - unique: false, 82 - defaultValue: "now()", 83 - comment: "", 84 - }, 85 - { 86 - name: "xata_version", 87 - type: "int", 88 - notNull: true, 89 - unique: false, 90 - defaultValue: "0", 91 - comment: "", 92 - }, 93 - ], 94 - }, 95 - { 96 - name: "album_tracks", 97 - checkConstraints: { 98 - album_tracks_xata_id_length_xata_id: { 99 - name: "album_tracks_xata_id_length_xata_id", 100 - columns: ["xata_id"], 101 - definition: "CHECK ((length(xata_id) < 256))", 102 - }, 103 - }, 104 - foreignKeys: { 105 - album_id_link: { 106 - name: "album_id_link", 107 - columns: ["album_id"], 108 - referencedTable: "albums", 109 - referencedColumns: ["xata_id"], 110 - onDelete: "SET NULL", 111 - }, 112 - track_id_link: { 113 - name: "track_id_link", 114 - columns: ["track_id"], 115 - referencedTable: "tracks", 116 - referencedColumns: ["xata_id"], 117 - onDelete: "SET NULL", 118 - }, 119 - }, 120 - primaryKey: [], 121 - uniqueConstraints: { 122 - _pgroll_new_album_tracks_xata_id_key: { 123 - name: "_pgroll_new_album_tracks_xata_id_key", 124 - columns: ["xata_id"], 125 - }, 126 - }, 127 - columns: [ 128 - { 129 - name: "album_id", 130 - type: "link", 131 - link: { table: "albums" }, 132 - notNull: true, 133 - unique: false, 134 - defaultValue: null, 135 - comment: '{"xata.link":"albums"}', 136 - }, 137 - { 138 - name: "track_id", 139 - type: "link", 140 - link: { table: "tracks" }, 141 - notNull: true, 142 - unique: false, 143 - defaultValue: null, 144 - comment: '{"xata.link":"tracks"}', 145 - }, 146 - { 147 - name: "xata_createdat", 148 - type: "datetime", 149 - notNull: true, 150 - unique: false, 151 - defaultValue: "now()", 152 - comment: "", 153 - }, 154 - { 155 - name: "xata_id", 156 - type: "text", 157 - notNull: true, 158 - unique: true, 159 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 160 - comment: "", 161 - }, 162 - { 163 - name: "xata_updatedat", 164 - type: "datetime", 165 - notNull: true, 166 - unique: false, 167 - defaultValue: "now()", 168 - comment: "", 169 - }, 170 - { 171 - name: "xata_version", 172 - type: "int", 173 - notNull: true, 174 - unique: false, 175 - defaultValue: "0", 176 - comment: "", 177 - }, 178 - ], 179 - }, 180 - { 181 - name: "albums", 182 - checkConstraints: { 183 - albums_xata_id_length_xata_id: { 184 - name: "albums_xata_id_length_xata_id", 185 - columns: ["xata_id"], 186 - definition: "CHECK ((length(xata_id) < 256))", 187 - }, 188 - }, 189 - foreignKeys: {}, 190 - primaryKey: [], 191 - uniqueConstraints: { 192 - _pgroll_new_albums_xata_id_key: { 193 - name: "_pgroll_new_albums_xata_id_key", 194 - columns: ["xata_id"], 195 - }, 196 - albums__pgroll_new_sha256_key: { 197 - name: "albums__pgroll_new_sha256_key", 198 - columns: ["sha256"], 199 - }, 200 - albums__pgroll_new_uri_key: { 201 - name: "albums__pgroll_new_uri_key", 202 - columns: ["uri"], 203 - }, 204 - albums_apple_music_link_unique: { 205 - name: "albums_apple_music_link_unique", 206 - columns: ["apple_music_link"], 207 - }, 208 - albums_spotify_link_unique: { 209 - name: "albums_spotify_link_unique", 210 - columns: ["spotify_link"], 211 - }, 212 - }, 213 - columns: [ 214 - { 215 - name: "album_art", 216 - type: "text", 217 - notNull: false, 218 - unique: false, 219 - defaultValue: null, 220 - comment: "", 221 - }, 222 - { 223 - name: "apple_music_link", 224 - type: "text", 225 - notNull: false, 226 - unique: true, 227 - defaultValue: null, 228 - comment: "", 229 - }, 230 - { 231 - name: "artist", 232 - type: "text", 233 - notNull: true, 234 - unique: false, 235 - defaultValue: null, 236 - comment: "", 237 - }, 238 - { 239 - name: "artist_uri", 240 - type: "text", 241 - notNull: false, 242 - unique: false, 243 - defaultValue: null, 244 - comment: "", 245 - }, 246 - { 247 - name: "lastfm_link", 248 - type: "text", 249 - notNull: false, 250 - unique: false, 251 - defaultValue: null, 252 - comment: "", 253 - }, 254 - { 255 - name: "release_date", 256 - type: "text", 257 - notNull: false, 258 - unique: false, 259 - defaultValue: null, 260 - comment: "", 261 - }, 262 - { 263 - name: "sha256", 264 - type: "text", 265 - notNull: true, 266 - unique: true, 267 - defaultValue: null, 268 - comment: "", 269 - }, 270 - { 271 - name: "spotify_link", 272 - type: "text", 273 - notNull: false, 274 - unique: true, 275 - defaultValue: null, 276 - comment: "", 277 - }, 278 - { 279 - name: "tidal_link", 280 - type: "text", 281 - notNull: false, 282 - unique: false, 283 - defaultValue: null, 284 - comment: "", 285 - }, 286 - { 287 - name: "title", 288 - type: "text", 289 - notNull: true, 290 - unique: false, 291 - defaultValue: null, 292 - comment: "", 293 - }, 294 - { 295 - name: "uri", 296 - type: "text", 297 - notNull: false, 298 - unique: true, 299 - defaultValue: null, 300 - comment: "", 301 - }, 302 - { 303 - name: "xata_createdat", 304 - type: "datetime", 305 - notNull: true, 306 - unique: false, 307 - defaultValue: "now()", 308 - comment: "", 309 - }, 310 - { 311 - name: "xata_id", 312 - type: "text", 313 - notNull: true, 314 - unique: true, 315 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 316 - comment: "", 317 - }, 318 - { 319 - name: "xata_updatedat", 320 - type: "datetime", 321 - notNull: true, 322 - unique: false, 323 - defaultValue: "now()", 324 - comment: "", 325 - }, 326 - { 327 - name: "xata_version", 328 - type: "int", 329 - notNull: true, 330 - unique: false, 331 - defaultValue: "0", 332 - comment: "", 333 - }, 334 - { 335 - name: "year", 336 - type: "int", 337 - notNull: false, 338 - unique: false, 339 - defaultValue: null, 340 - comment: "", 341 - }, 342 - { 343 - name: "youtube_link", 344 - type: "text", 345 - notNull: false, 346 - unique: false, 347 - defaultValue: null, 348 - comment: "", 349 - }, 350 - ], 351 - }, 352 - { 353 - name: "api_keys", 354 - checkConstraints: { 355 - api_keys_xata_id_length_xata_id: { 356 - name: "api_keys_xata_id_length_xata_id", 357 - columns: ["xata_id"], 358 - definition: "CHECK ((length(xata_id) < 256))", 359 - }, 360 - }, 361 - foreignKeys: { 362 - user_id_link: { 363 - name: "user_id_link", 364 - columns: ["user_id"], 365 - referencedTable: "users", 366 - referencedColumns: ["xata_id"], 367 - onDelete: "CASCADE", 368 - }, 369 - }, 370 - primaryKey: [], 371 - uniqueConstraints: { 372 - _pgroll_new_api_keys_xata_id_key: { 373 - name: "_pgroll_new_api_keys_xata_id_key", 374 - columns: ["xata_id"], 375 - }, 376 - api_keys__pgroll_new_api_key_key: { 377 - name: "api_keys__pgroll_new_api_key_key", 378 - columns: ["api_key"], 379 - }, 380 - api_keys__pgroll_new_shared_secret_key: { 381 - name: "api_keys__pgroll_new_shared_secret_key", 382 - columns: ["shared_secret"], 383 - }, 384 - }, 385 - columns: [ 386 - { 387 - name: "api_key", 388 - type: "text", 389 - notNull: true, 390 - unique: true, 391 - defaultValue: null, 392 - comment: "", 393 - }, 394 - { 395 - name: "description", 396 - type: "text", 397 - notNull: false, 398 - unique: false, 399 - defaultValue: null, 400 - comment: "", 401 - }, 402 - { 403 - name: "enabled", 404 - type: "bool", 405 - notNull: true, 406 - unique: false, 407 - defaultValue: "true", 408 - comment: "", 409 - }, 410 - { 411 - name: "name", 412 - type: "text", 413 - notNull: true, 414 - unique: false, 415 - defaultValue: null, 416 - comment: "", 417 - }, 418 - { 419 - name: "shared_secret", 420 - type: "text", 421 - notNull: true, 422 - unique: true, 423 - defaultValue: null, 424 - comment: "", 425 - }, 426 - { 427 - name: "user_id", 428 - type: "link", 429 - link: { table: "users" }, 430 - notNull: true, 431 - unique: false, 432 - defaultValue: null, 433 - comment: '{"xata.link":"users"}', 434 - }, 435 - { 436 - name: "xata_createdat", 437 - type: "datetime", 438 - notNull: true, 439 - unique: false, 440 - defaultValue: "now()", 441 - comment: "", 442 - }, 443 - { 444 - name: "xata_id", 445 - type: "text", 446 - notNull: true, 447 - unique: true, 448 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 449 - comment: "", 450 - }, 451 - { 452 - name: "xata_updatedat", 453 - type: "datetime", 454 - notNull: true, 455 - unique: false, 456 - defaultValue: "now()", 457 - comment: "", 458 - }, 459 - { 460 - name: "xata_version", 461 - type: "int", 462 - notNull: true, 463 - unique: false, 464 - defaultValue: "0", 465 - comment: "", 466 - }, 467 - ], 468 - }, 469 - { 470 - name: "artist_albums", 471 - checkConstraints: { 472 - artist_albums_xata_id_length_xata_id: { 473 - name: "artist_albums_xata_id_length_xata_id", 474 - columns: ["xata_id"], 475 - definition: "CHECK ((length(xata_id) < 256))", 476 - }, 477 - }, 478 - foreignKeys: { 479 - album_id_link: { 480 - name: "album_id_link", 481 - columns: ["album_id"], 482 - referencedTable: "albums", 483 - referencedColumns: ["xata_id"], 484 - onDelete: "SET NULL", 485 - }, 486 - artist_id_link: { 487 - name: "artist_id_link", 488 - columns: ["artist_id"], 489 - referencedTable: "artists", 490 - referencedColumns: ["xata_id"], 491 - onDelete: "SET NULL", 492 - }, 493 - }, 494 - primaryKey: [], 495 - uniqueConstraints: { 496 - _pgroll_new_artist_albums_xata_id_key: { 497 - name: "_pgroll_new_artist_albums_xata_id_key", 498 - columns: ["xata_id"], 499 - }, 500 - }, 501 - columns: [ 502 - { 503 - name: "album_id", 504 - type: "link", 505 - link: { table: "albums" }, 506 - notNull: true, 507 - unique: false, 508 - defaultValue: null, 509 - comment: '{"xata.link":"albums"}', 510 - }, 511 - { 512 - name: "artist_id", 513 - type: "link", 514 - link: { table: "artists" }, 515 - notNull: true, 516 - unique: false, 517 - defaultValue: null, 518 - comment: '{"xata.link":"artists"}', 519 - }, 520 - { 521 - name: "xata_createdat", 522 - type: "datetime", 523 - notNull: true, 524 - unique: false, 525 - defaultValue: "now()", 526 - comment: "", 527 - }, 528 - { 529 - name: "xata_id", 530 - type: "text", 531 - notNull: true, 532 - unique: true, 533 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 534 - comment: "", 535 - }, 536 - { 537 - name: "xata_updatedat", 538 - type: "datetime", 539 - notNull: true, 540 - unique: false, 541 - defaultValue: "now()", 542 - comment: "", 543 - }, 544 - { 545 - name: "xata_version", 546 - type: "int", 547 - notNull: true, 548 - unique: false, 549 - defaultValue: "0", 550 - comment: "", 551 - }, 552 - ], 553 - }, 554 - { 555 - name: "artist_tags", 556 - checkConstraints: { 557 - artist_tags_xata_id_length_xata_id: { 558 - name: "artist_tags_xata_id_length_xata_id", 559 - columns: ["xata_id"], 560 - definition: "CHECK ((length(xata_id) < 256))", 561 - }, 562 - }, 563 - foreignKeys: { 564 - artist_id_link: { 565 - name: "artist_id_link", 566 - columns: ["artist_id"], 567 - referencedTable: "artists", 568 - referencedColumns: ["xata_id"], 569 - onDelete: "SET NULL", 570 - }, 571 - tag_id_link: { 572 - name: "tag_id_link", 573 - columns: ["tag_id"], 574 - referencedTable: "tags", 575 - referencedColumns: ["xata_id"], 576 - onDelete: "SET NULL", 577 - }, 578 - }, 579 - primaryKey: [], 580 - uniqueConstraints: { 581 - _pgroll_new_artist_tags_xata_id_key: { 582 - name: "_pgroll_new_artist_tags_xata_id_key", 583 - columns: ["xata_id"], 584 - }, 585 - }, 586 - columns: [ 587 - { 588 - name: "artist_id", 589 - type: "link", 590 - link: { table: "artists" }, 591 - notNull: true, 592 - unique: false, 593 - defaultValue: null, 594 - comment: '{"xata.link":"artists"}', 595 - }, 596 - { 597 - name: "tag_id", 598 - type: "link", 599 - link: { table: "tags" }, 600 - notNull: true, 601 - unique: false, 602 - defaultValue: null, 603 - comment: '{"xata.link":"tags"}', 604 - }, 605 - { 606 - name: "xata_createdat", 607 - type: "datetime", 608 - notNull: true, 609 - unique: false, 610 - defaultValue: "now()", 611 - comment: "", 612 - }, 613 - { 614 - name: "xata_id", 615 - type: "text", 616 - notNull: true, 617 - unique: true, 618 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 619 - comment: "", 620 - }, 621 - { 622 - name: "xata_updatedat", 623 - type: "datetime", 624 - notNull: true, 625 - unique: false, 626 - defaultValue: "now()", 627 - comment: "", 628 - }, 629 - { 630 - name: "xata_version", 631 - type: "int", 632 - notNull: true, 633 - unique: false, 634 - defaultValue: "0", 635 - comment: "", 636 - }, 637 - ], 638 - }, 639 - { 640 - name: "artist_tracks", 641 - checkConstraints: { 642 - artist_tracks_xata_id_length_xata_id: { 643 - name: "artist_tracks_xata_id_length_xata_id", 644 - columns: ["xata_id"], 645 - definition: "CHECK ((length(xata_id) < 256))", 646 - }, 647 - }, 648 - foreignKeys: { 649 - artist_id_link: { 650 - name: "artist_id_link", 651 - columns: ["artist_id"], 652 - referencedTable: "artists", 653 - referencedColumns: ["xata_id"], 654 - onDelete: "SET NULL", 655 - }, 656 - track_id_link: { 657 - name: "track_id_link", 658 - columns: ["track_id"], 659 - referencedTable: "tracks", 660 - referencedColumns: ["xata_id"], 661 - onDelete: "SET NULL", 662 - }, 663 - }, 664 - primaryKey: [], 665 - uniqueConstraints: { 666 - _pgroll_new_artist_tracks_xata_id_key: { 667 - name: "_pgroll_new_artist_tracks_xata_id_key", 668 - columns: ["xata_id"], 669 - }, 670 - }, 671 - columns: [ 672 - { 673 - name: "artist_id", 674 - type: "link", 675 - link: { table: "artists" }, 676 - notNull: true, 677 - unique: false, 678 - defaultValue: null, 679 - comment: '{"xata.link":"artists"}', 680 - }, 681 - { 682 - name: "track_id", 683 - type: "link", 684 - link: { table: "tracks" }, 685 - notNull: true, 686 - unique: false, 687 - defaultValue: null, 688 - comment: '{"xata.link":"tracks"}', 689 - }, 690 - { 691 - name: "xata_createdat", 692 - type: "datetime", 693 - notNull: true, 694 - unique: false, 695 - defaultValue: "now()", 696 - comment: "", 697 - }, 698 - { 699 - name: "xata_id", 700 - type: "text", 701 - notNull: true, 702 - unique: true, 703 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 704 - comment: "", 705 - }, 706 - { 707 - name: "xata_updatedat", 708 - type: "datetime", 709 - notNull: true, 710 - unique: false, 711 - defaultValue: "now()", 712 - comment: "", 713 - }, 714 - { 715 - name: "xata_version", 716 - type: "int", 717 - notNull: true, 718 - unique: false, 719 - defaultValue: "0", 720 - comment: "", 721 - }, 722 - ], 723 - }, 724 - { 725 - name: "artists", 726 - checkConstraints: { 727 - artists_xata_id_length_xata_id: { 728 - name: "artists_xata_id_length_xata_id", 729 - columns: ["xata_id"], 730 - definition: "CHECK ((length(xata_id) < 256))", 731 - }, 732 - }, 733 - foreignKeys: {}, 734 - primaryKey: [], 735 - uniqueConstraints: { 736 - _pgroll_new_artists_xata_id_key: { 737 - name: "_pgroll_new_artists_xata_id_key", 738 - columns: ["xata_id"], 739 - }, 740 - artists__pgroll_new_sha256_key: { 741 - name: "artists__pgroll_new_sha256_key", 742 - columns: ["sha256"], 743 - }, 744 - artists__pgroll_new_uri_key: { 745 - name: "artists__pgroll_new_uri_key", 746 - columns: ["uri"], 747 - }, 748 - artists_apple_music_link_unique: { 749 - name: "artists_apple_music_link_unique", 750 - columns: ["apple_music_link"], 751 - }, 752 - artists_spotify_link_unique: { 753 - name: "artists_spotify_link_unique", 754 - columns: ["spotify_link"], 755 - }, 756 - artists_tidal_link_unique: { 757 - name: "artists_tidal_link_unique", 758 - columns: ["tidal_link"], 759 - }, 760 - artists_youtube_link_unique: { 761 - name: "artists_youtube_link_unique", 762 - columns: ["youtube_link"], 763 - }, 764 - }, 765 - columns: [ 766 - { 767 - name: "apple_music_link", 768 - type: "text", 769 - notNull: false, 770 - unique: true, 771 - defaultValue: null, 772 - comment: "", 773 - }, 774 - { 775 - name: "biography", 776 - type: "text", 777 - notNull: false, 778 - unique: false, 779 - defaultValue: null, 780 - comment: "", 781 - }, 782 - { 783 - name: "born", 784 - type: "datetime", 785 - notNull: false, 786 - unique: false, 787 - defaultValue: null, 788 - comment: "", 789 - }, 790 - { 791 - name: "born_in", 792 - type: "text", 793 - notNull: false, 794 - unique: false, 795 - defaultValue: null, 796 - comment: "", 797 - }, 798 - { 799 - name: "died", 800 - type: "datetime", 801 - notNull: false, 802 - unique: false, 803 - defaultValue: null, 804 - comment: "", 805 - }, 806 - { 807 - name: "genres", 808 - type: "multiple", 809 - notNull: false, 810 - unique: false, 811 - defaultValue: null, 812 - comment: "", 813 - }, 814 - { 815 - name: "lastfm_link", 816 - type: "text", 817 - notNull: false, 818 - unique: false, 819 - defaultValue: null, 820 - comment: "", 821 - }, 822 - { 823 - name: "name", 824 - type: "text", 825 - notNull: true, 826 - unique: false, 827 - defaultValue: null, 828 - comment: "", 829 - }, 830 - { 831 - name: "picture", 832 - type: "text", 833 - notNull: false, 834 - unique: false, 835 - defaultValue: null, 836 - comment: "", 837 - }, 838 - { 839 - name: "sha256", 840 - type: "text", 841 - notNull: true, 842 - unique: true, 843 - defaultValue: null, 844 - comment: "", 845 - }, 846 - { 847 - name: "spotify_link", 848 - type: "text", 849 - notNull: false, 850 - unique: true, 851 - defaultValue: null, 852 - comment: "", 853 - }, 854 - { 855 - name: "tidal_link", 856 - type: "text", 857 - notNull: false, 858 - unique: true, 859 - defaultValue: null, 860 - comment: "", 861 - }, 862 - { 863 - name: "uri", 864 - type: "text", 865 - notNull: false, 866 - unique: true, 867 - defaultValue: null, 868 - comment: "", 869 - }, 870 - { 871 - name: "xata_createdat", 872 - type: "datetime", 873 - notNull: true, 874 - unique: false, 875 - defaultValue: "now()", 876 - comment: "", 877 - }, 878 - { 879 - name: "xata_id", 880 - type: "text", 881 - notNull: true, 882 - unique: true, 883 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 884 - comment: "", 885 - }, 886 - { 887 - name: "xata_updatedat", 888 - type: "datetime", 889 - notNull: true, 890 - unique: false, 891 - defaultValue: "now()", 892 - comment: "", 893 - }, 894 - { 895 - name: "xata_version", 896 - type: "int", 897 - notNull: true, 898 - unique: false, 899 - defaultValue: "0", 900 - comment: "", 901 - }, 902 - { 903 - name: "youtube_link", 904 - type: "text", 905 - notNull: false, 906 - unique: true, 907 - defaultValue: null, 908 - comment: "", 909 - }, 910 - ], 911 - }, 912 - { 913 - name: "builtin_storage_paths", 914 - checkConstraints: { 915 - builtin_storage_paths_xata_id_length_xata_id: { 916 - name: "builtin_storage_paths_xata_id_length_xata_id", 917 - columns: ["xata_id"], 918 - definition: "CHECK ((length(xata_id) < 256))", 919 - }, 920 - }, 921 - foreignKeys: { 922 - track_id_link: { 923 - name: "track_id_link", 924 - columns: ["track_id"], 925 - referencedTable: "tracks", 926 - referencedColumns: ["xata_id"], 927 - onDelete: "CASCADE", 928 - }, 929 - user_id_link: { 930 - name: "user_id_link", 931 - columns: ["user_id"], 932 - referencedTable: "users", 933 - referencedColumns: ["xata_id"], 934 - onDelete: "CASCADE", 935 - }, 936 - }, 937 - primaryKey: [], 938 - uniqueConstraints: { 939 - _pgroll_new_builtin_storage_paths_xata_id_key: { 940 - name: "_pgroll_new_builtin_storage_paths_xata_id_key", 941 - columns: ["xata_id"], 942 - }, 943 - builtin_storage_paths__pgroll_new_path_key: { 944 - name: "builtin_storage_paths__pgroll_new_path_key", 945 - columns: ["path"], 946 - }, 947 - }, 948 - columns: [ 949 - { 950 - name: "path", 951 - type: "text", 952 - notNull: true, 953 - unique: true, 954 - defaultValue: null, 955 - comment: "", 956 - }, 957 - { 958 - name: "track_id", 959 - type: "link", 960 - link: { table: "tracks" }, 961 - notNull: true, 962 - unique: false, 963 - defaultValue: null, 964 - comment: '{"xata.link":"tracks"}', 965 - }, 966 - { 967 - name: "user_id", 968 - type: "link", 969 - link: { table: "users" }, 970 - notNull: true, 971 - unique: false, 972 - defaultValue: null, 973 - comment: '{"xata.link":"users"}', 974 - }, 975 - { 976 - name: "xata_createdat", 977 - type: "datetime", 978 - notNull: true, 979 - unique: false, 980 - defaultValue: "now()", 981 - comment: "", 982 - }, 983 - { 984 - name: "xata_id", 985 - type: "text", 986 - notNull: true, 987 - unique: true, 988 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 989 - comment: "", 990 - }, 991 - { 992 - name: "xata_updatedat", 993 - type: "datetime", 994 - notNull: true, 995 - unique: false, 996 - defaultValue: "now()", 997 - comment: "", 998 - }, 999 - { 1000 - name: "xata_version", 1001 - type: "int", 1002 - notNull: true, 1003 - unique: false, 1004 - defaultValue: "0", 1005 - comment: "", 1006 - }, 1007 - ], 1008 - }, 1009 - { 1010 - name: "dropbox", 1011 - checkConstraints: { 1012 - dropbox_xata_id_length_xata_id: { 1013 - name: "dropbox_xata_id_length_xata_id", 1014 - columns: ["xata_id"], 1015 - definition: "CHECK ((length(xata_id) < 256))", 1016 - }, 1017 - }, 1018 - foreignKeys: { 1019 - dropbox_token_id_link: { 1020 - name: "dropbox_token_id_link", 1021 - columns: ["dropbox_token_id"], 1022 - referencedTable: "dropbox_tokens", 1023 - referencedColumns: ["xata_id"], 1024 - onDelete: "CASCADE", 1025 - }, 1026 - user_id_link: { 1027 - name: "user_id_link", 1028 - columns: ["user_id"], 1029 - referencedTable: "users", 1030 - referencedColumns: ["xata_id"], 1031 - onDelete: "CASCADE", 1032 - }, 1033 - }, 1034 - primaryKey: [], 1035 - uniqueConstraints: { 1036 - _pgroll_new_dropbox_xata_id_key: { 1037 - name: "_pgroll_new_dropbox_xata_id_key", 1038 - columns: ["xata_id"], 1039 - }, 1040 - }, 1041 - columns: [ 1042 - { 1043 - name: "dropbox_token_id", 1044 - type: "link", 1045 - link: { table: "dropbox_tokens" }, 1046 - notNull: true, 1047 - unique: false, 1048 - defaultValue: null, 1049 - comment: '{"xata.link":"dropbox_tokens"}', 1050 - }, 1051 - { 1052 - name: "user_id", 1053 - type: "link", 1054 - link: { table: "users" }, 1055 - notNull: true, 1056 - unique: false, 1057 - defaultValue: null, 1058 - comment: '{"xata.link":"users"}', 1059 - }, 1060 - { 1061 - name: "xata_createdat", 1062 - type: "datetime", 1063 - notNull: true, 1064 - unique: false, 1065 - defaultValue: "now()", 1066 - comment: "", 1067 - }, 1068 - { 1069 - name: "xata_id", 1070 - type: "text", 1071 - notNull: true, 1072 - unique: true, 1073 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1074 - comment: "", 1075 - }, 1076 - { 1077 - name: "xata_updatedat", 1078 - type: "datetime", 1079 - notNull: true, 1080 - unique: false, 1081 - defaultValue: "now()", 1082 - comment: "", 1083 - }, 1084 - { 1085 - name: "xata_version", 1086 - type: "int", 1087 - notNull: true, 1088 - unique: false, 1089 - defaultValue: "0", 1090 - comment: "", 1091 - }, 1092 - ], 1093 - }, 1094 - { 1095 - name: "dropbox_accounts", 1096 - checkConstraints: { 1097 - dropbox_accounts_xata_id_length_xata_id: { 1098 - name: "dropbox_accounts_xata_id_length_xata_id", 1099 - columns: ["xata_id"], 1100 - definition: "CHECK ((length(xata_id) < 256))", 1101 - }, 1102 - }, 1103 - foreignKeys: { 1104 - user_id_link: { 1105 - name: "user_id_link", 1106 - columns: ["user_id"], 1107 - referencedTable: "users", 1108 - referencedColumns: ["xata_id"], 1109 - onDelete: "CASCADE", 1110 - }, 1111 - }, 1112 - primaryKey: [], 1113 - uniqueConstraints: { 1114 - _pgroll_new_dropbox_accounts_xata_id_key: { 1115 - name: "_pgroll_new_dropbox_accounts_xata_id_key", 1116 - columns: ["xata_id"], 1117 - }, 1118 - dropbox_accounts__pgroll_new_email_key: { 1119 - name: "dropbox_accounts__pgroll_new_email_key", 1120 - columns: ["email"], 1121 - }, 1122 - }, 1123 - columns: [ 1124 - { 1125 - name: "email", 1126 - type: "text", 1127 - notNull: true, 1128 - unique: true, 1129 - defaultValue: null, 1130 - comment: "", 1131 - }, 1132 - { 1133 - name: "is_beta_user", 1134 - type: "bool", 1135 - notNull: true, 1136 - unique: false, 1137 - defaultValue: null, 1138 - comment: "", 1139 - }, 1140 - { 1141 - name: "user_id", 1142 - type: "link", 1143 - link: { table: "users" }, 1144 - notNull: true, 1145 - unique: false, 1146 - defaultValue: null, 1147 - comment: '{"xata.link":"users"}', 1148 - }, 1149 - { 1150 - name: "xata_createdat", 1151 - type: "datetime", 1152 - notNull: true, 1153 - unique: false, 1154 - defaultValue: "now()", 1155 - comment: "", 1156 - }, 1157 - { 1158 - name: "xata_id", 1159 - type: "text", 1160 - notNull: true, 1161 - unique: true, 1162 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1163 - comment: "", 1164 - }, 1165 - { 1166 - name: "xata_updatedat", 1167 - type: "datetime", 1168 - notNull: true, 1169 - unique: false, 1170 - defaultValue: "now()", 1171 - comment: "", 1172 - }, 1173 - { 1174 - name: "xata_version", 1175 - type: "int", 1176 - notNull: true, 1177 - unique: false, 1178 - defaultValue: "0", 1179 - comment: "", 1180 - }, 1181 - ], 1182 - }, 1183 - { 1184 - name: "dropbox_directories", 1185 - checkConstraints: { 1186 - dropbox_directories_xata_id_length_xata_id: { 1187 - name: "dropbox_directories_xata_id_length_xata_id", 1188 - columns: ["xata_id"], 1189 - definition: "CHECK ((length(xata_id) < 256))", 1190 - }, 1191 - }, 1192 - foreignKeys: { 1193 - dropbox_id_link: { 1194 - name: "dropbox_id_link", 1195 - columns: ["dropbox_id"], 1196 - referencedTable: "dropbox", 1197 - referencedColumns: ["xata_id"], 1198 - onDelete: "CASCADE", 1199 - }, 1200 - parent_id_link: { 1201 - name: "parent_id_link", 1202 - columns: ["parent_id"], 1203 - referencedTable: "dropbox_directories", 1204 - referencedColumns: ["xata_id"], 1205 - onDelete: "CASCADE", 1206 - }, 1207 - }, 1208 - primaryKey: [], 1209 - uniqueConstraints: { 1210 - _pgroll_new_dropbox_directories_xata_id_key: { 1211 - name: "_pgroll_new_dropbox_directories_xata_id_key", 1212 - columns: ["xata_id"], 1213 - }, 1214 - dropbox_directories__pgroll_new_file_id_key: { 1215 - name: "dropbox_directories__pgroll_new_file_id_key", 1216 - columns: ["file_id"], 1217 - }, 1218 - }, 1219 - columns: [ 1220 - { 1221 - name: "dropbox_id", 1222 - type: "link", 1223 - link: { table: "dropbox" }, 1224 - notNull: true, 1225 - unique: false, 1226 - defaultValue: null, 1227 - comment: '{"xata.link":"dropbox"}', 1228 - }, 1229 - { 1230 - name: "file_id", 1231 - type: "text", 1232 - notNull: true, 1233 - unique: true, 1234 - defaultValue: null, 1235 - comment: "", 1236 - }, 1237 - { 1238 - name: "name", 1239 - type: "text", 1240 - notNull: true, 1241 - unique: false, 1242 - defaultValue: null, 1243 - comment: "", 1244 - }, 1245 - { 1246 - name: "parent_id", 1247 - type: "link", 1248 - link: { table: "dropbox_directories" }, 1249 - notNull: false, 1250 - unique: false, 1251 - defaultValue: null, 1252 - comment: '{"xata.link":"dropbox_directories"}', 1253 - }, 1254 - { 1255 - name: "path", 1256 - type: "text", 1257 - notNull: true, 1258 - unique: false, 1259 - defaultValue: null, 1260 - comment: "", 1261 - }, 1262 - { 1263 - name: "xata_createdat", 1264 - type: "datetime", 1265 - notNull: true, 1266 - unique: false, 1267 - defaultValue: "now()", 1268 - comment: "", 1269 - }, 1270 - { 1271 - name: "xata_id", 1272 - type: "text", 1273 - notNull: true, 1274 - unique: true, 1275 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1276 - comment: "", 1277 - }, 1278 - { 1279 - name: "xata_updatedat", 1280 - type: "datetime", 1281 - notNull: true, 1282 - unique: false, 1283 - defaultValue: "now()", 1284 - comment: "", 1285 - }, 1286 - { 1287 - name: "xata_version", 1288 - type: "int", 1289 - notNull: true, 1290 - unique: false, 1291 - defaultValue: "0", 1292 - comment: "", 1293 - }, 1294 - ], 1295 - }, 1296 - { 1297 - name: "dropbox_paths", 1298 - checkConstraints: { 1299 - dropbox_paths_xata_id_length_xata_id: { 1300 - name: "dropbox_paths_xata_id_length_xata_id", 1301 - columns: ["xata_id"], 1302 - definition: "CHECK ((length(xata_id) < 256))", 1303 - }, 1304 - }, 1305 - foreignKeys: { 1306 - directory_id_link: { 1307 - name: "directory_id_link", 1308 - columns: ["directory_id"], 1309 - referencedTable: "dropbox_directories", 1310 - referencedColumns: ["xata_id"], 1311 - onDelete: "SET NULL", 1312 - }, 1313 - dropbox_id_link: { 1314 - name: "dropbox_id_link", 1315 - columns: ["dropbox_id"], 1316 - referencedTable: "dropbox", 1317 - referencedColumns: ["xata_id"], 1318 - onDelete: "CASCADE", 1319 - }, 1320 - track_id_link: { 1321 - name: "track_id_link", 1322 - columns: ["track_id"], 1323 - referencedTable: "tracks", 1324 - referencedColumns: ["xata_id"], 1325 - onDelete: "CASCADE", 1326 - }, 1327 - }, 1328 - primaryKey: [], 1329 - uniqueConstraints: { 1330 - _pgroll_new_dropbox_paths_xata_id_key: { 1331 - name: "_pgroll_new_dropbox_paths_xata_id_key", 1332 - columns: ["xata_id"], 1333 - }, 1334 - dropbox_paths__pgroll_new_file_id_key: { 1335 - name: "dropbox_paths__pgroll_new_file_id_key", 1336 - columns: ["file_id"], 1337 - }, 1338 - }, 1339 - columns: [ 1340 - { 1341 - name: "directory_id", 1342 - type: "link", 1343 - link: { table: "dropbox_directories" }, 1344 - notNull: false, 1345 - unique: false, 1346 - defaultValue: null, 1347 - comment: '{"xata.link":"dropbox_directories"}', 1348 - }, 1349 - { 1350 - name: "dropbox_id", 1351 - type: "link", 1352 - link: { table: "dropbox" }, 1353 - notNull: true, 1354 - unique: false, 1355 - defaultValue: null, 1356 - comment: '{"xata.link":"dropbox"}', 1357 - }, 1358 - { 1359 - name: "file_id", 1360 - type: "text", 1361 - notNull: true, 1362 - unique: true, 1363 - defaultValue: null, 1364 - comment: "", 1365 - }, 1366 - { 1367 - name: "name", 1368 - type: "text", 1369 - notNull: true, 1370 - unique: false, 1371 - defaultValue: null, 1372 - comment: "", 1373 - }, 1374 - { 1375 - name: "path", 1376 - type: "text", 1377 - notNull: true, 1378 - unique: false, 1379 - defaultValue: null, 1380 - comment: "", 1381 - }, 1382 - { 1383 - name: "track_id", 1384 - type: "link", 1385 - link: { table: "tracks" }, 1386 - notNull: true, 1387 - unique: false, 1388 - defaultValue: null, 1389 - comment: '{"xata.link":"tracks"}', 1390 - }, 1391 - { 1392 - name: "xata_createdat", 1393 - type: "datetime", 1394 - notNull: true, 1395 - unique: false, 1396 - defaultValue: "now()", 1397 - comment: "", 1398 - }, 1399 - { 1400 - name: "xata_id", 1401 - type: "text", 1402 - notNull: true, 1403 - unique: true, 1404 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1405 - comment: "", 1406 - }, 1407 - { 1408 - name: "xata_updatedat", 1409 - type: "datetime", 1410 - notNull: true, 1411 - unique: false, 1412 - defaultValue: "now()", 1413 - comment: "", 1414 - }, 1415 - { 1416 - name: "xata_version", 1417 - type: "int", 1418 - notNull: true, 1419 - unique: false, 1420 - defaultValue: "0", 1421 - comment: "", 1422 - }, 1423 - ], 1424 - }, 1425 - { 1426 - name: "dropbox_tokens", 1427 - checkConstraints: { 1428 - dropbox_tokens_xata_id_length_xata_id: { 1429 - name: "dropbox_tokens_xata_id_length_xata_id", 1430 - columns: ["xata_id"], 1431 - definition: "CHECK ((length(xata_id) < 256))", 1432 - }, 1433 - }, 1434 - foreignKeys: {}, 1435 - primaryKey: [], 1436 - uniqueConstraints: { 1437 - _pgroll_new_dropbox_tokens_xata_id_key: { 1438 - name: "_pgroll_new_dropbox_tokens_xata_id_key", 1439 - columns: ["xata_id"], 1440 - }, 1441 - }, 1442 - columns: [ 1443 - { 1444 - name: "refresh_token", 1445 - type: "text", 1446 - notNull: true, 1447 - unique: false, 1448 - defaultValue: null, 1449 - comment: "", 1450 - }, 1451 - { 1452 - name: "xata_createdat", 1453 - type: "datetime", 1454 - notNull: true, 1455 - unique: false, 1456 - defaultValue: "now()", 1457 - comment: "", 1458 - }, 1459 - { 1460 - name: "xata_id", 1461 - type: "text", 1462 - notNull: true, 1463 - unique: true, 1464 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1465 - comment: "", 1466 - }, 1467 - { 1468 - name: "xata_updatedat", 1469 - type: "datetime", 1470 - notNull: true, 1471 - unique: false, 1472 - defaultValue: "now()", 1473 - comment: "", 1474 - }, 1475 - { 1476 - name: "xata_version", 1477 - type: "int", 1478 - notNull: true, 1479 - unique: false, 1480 - defaultValue: "0", 1481 - comment: "", 1482 - }, 1483 - ], 1484 - }, 1485 - { 1486 - name: "google_drive", 1487 - checkConstraints: { 1488 - google_drive_xata_id_length_xata_id: { 1489 - name: "google_drive_xata_id_length_xata_id", 1490 - columns: ["xata_id"], 1491 - definition: "CHECK ((length(xata_id) < 256))", 1492 - }, 1493 - }, 1494 - foreignKeys: { 1495 - google_drive_token_id_link: { 1496 - name: "google_drive_token_id_link", 1497 - columns: ["google_drive_token_id"], 1498 - referencedTable: "google_drive_tokens", 1499 - referencedColumns: ["xata_id"], 1500 - onDelete: "CASCADE", 1501 - }, 1502 - user_id_link: { 1503 - name: "user_id_link", 1504 - columns: ["user_id"], 1505 - referencedTable: "users", 1506 - referencedColumns: ["xata_id"], 1507 - onDelete: "SET NULL", 1508 - }, 1509 - }, 1510 - primaryKey: [], 1511 - uniqueConstraints: { 1512 - _pgroll_new_google_drive_xata_id_key: { 1513 - name: "_pgroll_new_google_drive_xata_id_key", 1514 - columns: ["xata_id"], 1515 - }, 1516 - }, 1517 - columns: [ 1518 - { 1519 - name: "google_drive_token_id", 1520 - type: "link", 1521 - link: { table: "google_drive_tokens" }, 1522 - notNull: true, 1523 - unique: false, 1524 - defaultValue: null, 1525 - comment: '{"xata.link":"google_drive_tokens"}', 1526 - }, 1527 - { 1528 - name: "user_id", 1529 - type: "link", 1530 - link: { table: "users" }, 1531 - notNull: true, 1532 - unique: false, 1533 - defaultValue: null, 1534 - comment: '{"xata.link":"users"}', 1535 - }, 1536 - { 1537 - name: "xata_createdat", 1538 - type: "datetime", 1539 - notNull: true, 1540 - unique: false, 1541 - defaultValue: "now()", 1542 - comment: "", 1543 - }, 1544 - { 1545 - name: "xata_id", 1546 - type: "text", 1547 - notNull: true, 1548 - unique: true, 1549 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1550 - comment: "", 1551 - }, 1552 - { 1553 - name: "xata_updatedat", 1554 - type: "datetime", 1555 - notNull: true, 1556 - unique: false, 1557 - defaultValue: "now()", 1558 - comment: "", 1559 - }, 1560 - { 1561 - name: "xata_version", 1562 - type: "int", 1563 - notNull: true, 1564 - unique: false, 1565 - defaultValue: "0", 1566 - comment: "", 1567 - }, 1568 - ], 1569 - }, 1570 - { 1571 - name: "google_drive_accounts", 1572 - checkConstraints: { 1573 - google_drive_accounts_xata_id_length_xata_id: { 1574 - name: "google_drive_accounts_xata_id_length_xata_id", 1575 - columns: ["xata_id"], 1576 - definition: "CHECK ((length(xata_id) < 256))", 1577 - }, 1578 - }, 1579 - foreignKeys: { 1580 - user_id_link: { 1581 - name: "user_id_link", 1582 - columns: ["user_id"], 1583 - referencedTable: "users", 1584 - referencedColumns: ["xata_id"], 1585 - onDelete: "CASCADE", 1586 - }, 1587 - }, 1588 - primaryKey: [], 1589 - uniqueConstraints: { 1590 - _pgroll_new_google_drive_accounts_xata_id_key: { 1591 - name: "_pgroll_new_google_drive_accounts_xata_id_key", 1592 - columns: ["xata_id"], 1593 - }, 1594 - google_drive_accounts__pgroll_new_email_key: { 1595 - name: "google_drive_accounts__pgroll_new_email_key", 1596 - columns: ["email"], 1597 - }, 1598 - }, 1599 - columns: [ 1600 - { 1601 - name: "email", 1602 - type: "text", 1603 - notNull: true, 1604 - unique: true, 1605 - defaultValue: null, 1606 - comment: "", 1607 - }, 1608 - { 1609 - name: "is_beta_user", 1610 - type: "bool", 1611 - notNull: true, 1612 - unique: false, 1613 - defaultValue: null, 1614 - comment: "", 1615 - }, 1616 - { 1617 - name: "user_id", 1618 - type: "link", 1619 - link: { table: "users" }, 1620 - notNull: true, 1621 - unique: false, 1622 - defaultValue: null, 1623 - comment: '{"xata.link":"users"}', 1624 - }, 1625 - { 1626 - name: "xata_createdat", 1627 - type: "datetime", 1628 - notNull: true, 1629 - unique: false, 1630 - defaultValue: "now()", 1631 - comment: "", 1632 - }, 1633 - { 1634 - name: "xata_id", 1635 - type: "text", 1636 - notNull: true, 1637 - unique: true, 1638 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1639 - comment: "", 1640 - }, 1641 - { 1642 - name: "xata_updatedat", 1643 - type: "datetime", 1644 - notNull: true, 1645 - unique: false, 1646 - defaultValue: "now()", 1647 - comment: "", 1648 - }, 1649 - { 1650 - name: "xata_version", 1651 - type: "int", 1652 - notNull: true, 1653 - unique: false, 1654 - defaultValue: "0", 1655 - comment: "", 1656 - }, 1657 - ], 1658 - }, 1659 - { 1660 - name: "google_drive_directories", 1661 - checkConstraints: { 1662 - google_drive_directories_xata_id_length_xata_id: { 1663 - name: "google_drive_directories_xata_id_length_xata_id", 1664 - columns: ["xata_id"], 1665 - definition: "CHECK ((length(xata_id) < 256))", 1666 - }, 1667 - }, 1668 - foreignKeys: { 1669 - google_drive_id_link: { 1670 - name: "google_drive_id_link", 1671 - columns: ["google_drive_id"], 1672 - referencedTable: "google_drive", 1673 - referencedColumns: ["xata_id"], 1674 - onDelete: "CASCADE", 1675 - }, 1676 - parent_id_link: { 1677 - name: "parent_id_link", 1678 - columns: ["parent_id"], 1679 - referencedTable: "google_drive_directories", 1680 - referencedColumns: ["xata_id"], 1681 - onDelete: "CASCADE", 1682 - }, 1683 - }, 1684 - primaryKey: [], 1685 - uniqueConstraints: { 1686 - _pgroll_new_google_drive_directories_xata_id_key: { 1687 - name: "_pgroll_new_google_drive_directories_xata_id_key", 1688 - columns: ["xata_id"], 1689 - }, 1690 - google_drive_directories__pgroll_new_file_id_key: { 1691 - name: "google_drive_directories__pgroll_new_file_id_key", 1692 - columns: ["file_id"], 1693 - }, 1694 - }, 1695 - columns: [ 1696 - { 1697 - name: "file_id", 1698 - type: "text", 1699 - notNull: true, 1700 - unique: true, 1701 - defaultValue: null, 1702 - comment: "", 1703 - }, 1704 - { 1705 - name: "google_drive_id", 1706 - type: "link", 1707 - link: { table: "google_drive" }, 1708 - notNull: true, 1709 - unique: false, 1710 - defaultValue: null, 1711 - comment: '{"xata.link":"google_drive"}', 1712 - }, 1713 - { 1714 - name: "name", 1715 - type: "text", 1716 - notNull: true, 1717 - unique: false, 1718 - defaultValue: null, 1719 - comment: "", 1720 - }, 1721 - { 1722 - name: "parent_id", 1723 - type: "link", 1724 - link: { table: "google_drive_directories" }, 1725 - notNull: false, 1726 - unique: false, 1727 - defaultValue: null, 1728 - comment: '{"xata.link":"google_drive_directories"}', 1729 - }, 1730 - { 1731 - name: "path", 1732 - type: "text", 1733 - notNull: true, 1734 - unique: false, 1735 - defaultValue: null, 1736 - comment: "", 1737 - }, 1738 - { 1739 - name: "xata_createdat", 1740 - type: "datetime", 1741 - notNull: true, 1742 - unique: false, 1743 - defaultValue: "now()", 1744 - comment: "", 1745 - }, 1746 - { 1747 - name: "xata_id", 1748 - type: "text", 1749 - notNull: true, 1750 - unique: true, 1751 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1752 - comment: "", 1753 - }, 1754 - { 1755 - name: "xata_updatedat", 1756 - type: "datetime", 1757 - notNull: true, 1758 - unique: false, 1759 - defaultValue: "now()", 1760 - comment: "", 1761 - }, 1762 - { 1763 - name: "xata_version", 1764 - type: "int", 1765 - notNull: true, 1766 - unique: false, 1767 - defaultValue: "0", 1768 - comment: "", 1769 - }, 1770 - ], 1771 - }, 1772 - { 1773 - name: "google_drive_paths", 1774 - checkConstraints: { 1775 - google_drive_paths_xata_id_length_xata_id: { 1776 - name: "google_drive_paths_xata_id_length_xata_id", 1777 - columns: ["xata_id"], 1778 - definition: "CHECK ((length(xata_id) < 256))", 1779 - }, 1780 - }, 1781 - foreignKeys: { 1782 - directory_id_link: { 1783 - name: "directory_id_link", 1784 - columns: ["directory_id"], 1785 - referencedTable: "google_drive_directories", 1786 - referencedColumns: ["xata_id"], 1787 - onDelete: "SET NULL", 1788 - }, 1789 - google_drive_id_link: { 1790 - name: "google_drive_id_link", 1791 - columns: ["google_drive_id"], 1792 - referencedTable: "google_drive", 1793 - referencedColumns: ["xata_id"], 1794 - onDelete: "CASCADE", 1795 - }, 1796 - track_id_link: { 1797 - name: "track_id_link", 1798 - columns: ["track_id"], 1799 - referencedTable: "tracks", 1800 - referencedColumns: ["xata_id"], 1801 - onDelete: "CASCADE", 1802 - }, 1803 - }, 1804 - primaryKey: [], 1805 - uniqueConstraints: { 1806 - _pgroll_new_google_drive_paths_xata_id_key: { 1807 - name: "_pgroll_new_google_drive_paths_xata_id_key", 1808 - columns: ["xata_id"], 1809 - }, 1810 - google_drive_paths__pgroll_new_google_drive_file_id_key: { 1811 - name: "google_drive_paths__pgroll_new_google_drive_file_id_key", 1812 - columns: ["file_id"], 1813 - }, 1814 - }, 1815 - columns: [ 1816 - { 1817 - name: "directory_id", 1818 - type: "link", 1819 - link: { table: "google_drive_directories" }, 1820 - notNull: false, 1821 - unique: false, 1822 - defaultValue: null, 1823 - comment: '{"xata.link":"google_drive_directories"}', 1824 - }, 1825 - { 1826 - name: "file_id", 1827 - type: "text", 1828 - notNull: true, 1829 - unique: true, 1830 - defaultValue: null, 1831 - comment: "", 1832 - }, 1833 - { 1834 - name: "google_drive_id", 1835 - type: "link", 1836 - link: { table: "google_drive" }, 1837 - notNull: true, 1838 - unique: false, 1839 - defaultValue: null, 1840 - comment: '{"xata.link":"google_drive"}', 1841 - }, 1842 - { 1843 - name: "name", 1844 - type: "text", 1845 - notNull: true, 1846 - unique: false, 1847 - defaultValue: null, 1848 - comment: "", 1849 - }, 1850 - { 1851 - name: "track_id", 1852 - type: "link", 1853 - link: { table: "tracks" }, 1854 - notNull: true, 1855 - unique: false, 1856 - defaultValue: null, 1857 - comment: '{"xata.link":"tracks"}', 1858 - }, 1859 - { 1860 - name: "xata_createdat", 1861 - type: "datetime", 1862 - notNull: true, 1863 - unique: false, 1864 - defaultValue: "now()", 1865 - comment: "", 1866 - }, 1867 - { 1868 - name: "xata_id", 1869 - type: "text", 1870 - notNull: true, 1871 - unique: true, 1872 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1873 - comment: "", 1874 - }, 1875 - { 1876 - name: "xata_updatedat", 1877 - type: "datetime", 1878 - notNull: true, 1879 - unique: false, 1880 - defaultValue: "now()", 1881 - comment: "", 1882 - }, 1883 - { 1884 - name: "xata_version", 1885 - type: "int", 1886 - notNull: true, 1887 - unique: false, 1888 - defaultValue: "0", 1889 - comment: "", 1890 - }, 1891 - ], 1892 - }, 1893 - { 1894 - name: "google_drive_tokens", 1895 - checkConstraints: { 1896 - google_drive_tokens_xata_id_length_xata_id: { 1897 - name: "google_drive_tokens_xata_id_length_xata_id", 1898 - columns: ["xata_id"], 1899 - definition: "CHECK ((length(xata_id) < 256))", 1900 - }, 1901 - }, 1902 - foreignKeys: {}, 1903 - primaryKey: [], 1904 - uniqueConstraints: { 1905 - _pgroll_new_google_drive_tokens_xata_id_key: { 1906 - name: "_pgroll_new_google_drive_tokens_xata_id_key", 1907 - columns: ["xata_id"], 1908 - }, 1909 - }, 1910 - columns: [ 1911 - { 1912 - name: "refresh_token", 1913 - type: "text", 1914 - notNull: true, 1915 - unique: false, 1916 - defaultValue: null, 1917 - comment: "", 1918 - }, 1919 - { 1920 - name: "xata_createdat", 1921 - type: "datetime", 1922 - notNull: true, 1923 - unique: false, 1924 - defaultValue: "now()", 1925 - comment: "", 1926 - }, 1927 - { 1928 - name: "xata_id", 1929 - type: "text", 1930 - notNull: true, 1931 - unique: true, 1932 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 1933 - comment: "", 1934 - }, 1935 - { 1936 - name: "xata_updatedat", 1937 - type: "datetime", 1938 - notNull: true, 1939 - unique: false, 1940 - defaultValue: "now()", 1941 - comment: "", 1942 - }, 1943 - { 1944 - name: "xata_version", 1945 - type: "int", 1946 - notNull: true, 1947 - unique: false, 1948 - defaultValue: "0", 1949 - comment: "", 1950 - }, 1951 - ], 1952 - }, 1953 - { 1954 - name: "loved_tracks", 1955 - checkConstraints: { 1956 - loved_tracks_xata_id_length_xata_id: { 1957 - name: "loved_tracks_xata_id_length_xata_id", 1958 - columns: ["xata_id"], 1959 - definition: "CHECK ((length(xata_id) < 256))", 1960 - }, 1961 - }, 1962 - foreignKeys: { 1963 - track_id_link: { 1964 - name: "track_id_link", 1965 - columns: ["track_id"], 1966 - referencedTable: "tracks", 1967 - referencedColumns: ["xata_id"], 1968 - onDelete: "SET NULL", 1969 - }, 1970 - user_id_link: { 1971 - name: "user_id_link", 1972 - columns: ["user_id"], 1973 - referencedTable: "users", 1974 - referencedColumns: ["xata_id"], 1975 - onDelete: "SET NULL", 1976 - }, 1977 - }, 1978 - primaryKey: [], 1979 - uniqueConstraints: { 1980 - _pgroll_new_loved_tracks_xata_id_key: { 1981 - name: "_pgroll_new_loved_tracks_xata_id_key", 1982 - columns: ["xata_id"], 1983 - }, 1984 - loved_tracks__pgroll_new_uri_key: { 1985 - name: "loved_tracks__pgroll_new_uri_key", 1986 - columns: ["uri"], 1987 - }, 1988 - }, 1989 - columns: [ 1990 - { 1991 - name: "track_id", 1992 - type: "link", 1993 - link: { table: "tracks" }, 1994 - notNull: true, 1995 - unique: false, 1996 - defaultValue: null, 1997 - comment: '{"xata.link":"tracks"}', 1998 - }, 1999 - { 2000 - name: "uri", 2001 - type: "text", 2002 - notNull: false, 2003 - unique: true, 2004 - defaultValue: null, 2005 - comment: "", 2006 - }, 2007 - { 2008 - name: "user_id", 2009 - type: "link", 2010 - link: { table: "users" }, 2011 - notNull: true, 2012 - unique: false, 2013 - defaultValue: null, 2014 - comment: '{"xata.link":"users"}', 2015 - }, 2016 - { 2017 - name: "xata_createdat", 2018 - type: "datetime", 2019 - notNull: true, 2020 - unique: false, 2021 - defaultValue: "now()", 2022 - comment: "", 2023 - }, 2024 - { 2025 - name: "xata_id", 2026 - type: "text", 2027 - notNull: true, 2028 - unique: true, 2029 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2030 - comment: "", 2031 - }, 2032 - { 2033 - name: "xata_updatedat", 2034 - type: "datetime", 2035 - notNull: true, 2036 - unique: false, 2037 - defaultValue: "now()", 2038 - comment: "", 2039 - }, 2040 - { 2041 - name: "xata_version", 2042 - type: "int", 2043 - notNull: true, 2044 - unique: false, 2045 - defaultValue: "0", 2046 - comment: "", 2047 - }, 2048 - ], 2049 - }, 2050 - { 2051 - name: "playback_state", 2052 - checkConstraints: { 2053 - playback_states_xata_id_length_xata_id: { 2054 - name: "playback_states_xata_id_length_xata_id", 2055 - columns: ["xata_id"], 2056 - definition: "CHECK ((length(xata_id) < 256))", 2057 - }, 2058 - }, 2059 - foreignKeys: { 2060 - track_id_link: { 2061 - name: "track_id_link", 2062 - columns: ["track_id"], 2063 - referencedTable: "tracks", 2064 - referencedColumns: ["xata_id"], 2065 - onDelete: "CASCADE", 2066 - }, 2067 - user_id_link: { 2068 - name: "user_id_link", 2069 - columns: ["user_id"], 2070 - referencedTable: "users", 2071 - referencedColumns: ["xata_id"], 2072 - onDelete: "CASCADE", 2073 - }, 2074 - }, 2075 - primaryKey: [], 2076 - uniqueConstraints: { 2077 - _pgroll_new_playback_states_xata_id_key: { 2078 - name: "_pgroll_new_playback_states_xata_id_key", 2079 - columns: ["xata_id"], 2080 - }, 2081 - }, 2082 - columns: [ 2083 - { 2084 - name: "progress_ms", 2085 - type: "int", 2086 - notNull: true, 2087 - unique: false, 2088 - defaultValue: null, 2089 - comment: "", 2090 - }, 2091 - { 2092 - name: "queue_position", 2093 - type: "int", 2094 - notNull: true, 2095 - unique: false, 2096 - defaultValue: null, 2097 - comment: "", 2098 - }, 2099 - { 2100 - name: "track_id", 2101 - type: "link", 2102 - link: { table: "tracks" }, 2103 - notNull: true, 2104 - unique: false, 2105 - defaultValue: null, 2106 - comment: '{"xata.link":"tracks"}', 2107 - }, 2108 - { 2109 - name: "user_id", 2110 - type: "link", 2111 - link: { table: "users" }, 2112 - notNull: true, 2113 - unique: false, 2114 - defaultValue: null, 2115 - comment: '{"xata.link":"users"}', 2116 - }, 2117 - { 2118 - name: "xata_createdat", 2119 - type: "datetime", 2120 - notNull: true, 2121 - unique: false, 2122 - defaultValue: "now()", 2123 - comment: "", 2124 - }, 2125 - { 2126 - name: "xata_id", 2127 - type: "text", 2128 - notNull: true, 2129 - unique: true, 2130 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2131 - comment: "", 2132 - }, 2133 - { 2134 - name: "xata_updatedat", 2135 - type: "datetime", 2136 - notNull: true, 2137 - unique: false, 2138 - defaultValue: "now()", 2139 - comment: "", 2140 - }, 2141 - { 2142 - name: "xata_version", 2143 - type: "int", 2144 - notNull: true, 2145 - unique: false, 2146 - defaultValue: "0", 2147 - comment: "", 2148 - }, 2149 - ], 2150 - }, 2151 - { 2152 - name: "playlist_tracks", 2153 - checkConstraints: { 2154 - playlist_tracks_xata_id_length_xata_id: { 2155 - name: "playlist_tracks_xata_id_length_xata_id", 2156 - columns: ["xata_id"], 2157 - definition: "CHECK ((length(xata_id) < 256))", 2158 - }, 2159 - }, 2160 - foreignKeys: { 2161 - playlist_id_link: { 2162 - name: "playlist_id_link", 2163 - columns: ["playlist_id"], 2164 - referencedTable: "playlists", 2165 - referencedColumns: ["xata_id"], 2166 - onDelete: "SET NULL", 2167 - }, 2168 - track_id_link: { 2169 - name: "track_id_link", 2170 - columns: ["track_id"], 2171 - referencedTable: "tracks", 2172 - referencedColumns: ["xata_id"], 2173 - onDelete: "SET NULL", 2174 - }, 2175 - }, 2176 - primaryKey: [], 2177 - uniqueConstraints: { 2178 - _pgroll_new_playlist_tracks_xata_id_key: { 2179 - name: "_pgroll_new_playlist_tracks_xata_id_key", 2180 - columns: ["xata_id"], 2181 - }, 2182 - }, 2183 - columns: [ 2184 - { 2185 - name: "playlist_id", 2186 - type: "link", 2187 - link: { table: "playlists" }, 2188 - notNull: true, 2189 - unique: false, 2190 - defaultValue: null, 2191 - comment: '{"xata.link":"playlists"}', 2192 - }, 2193 - { 2194 - name: "track_id", 2195 - type: "link", 2196 - link: { table: "tracks" }, 2197 - notNull: true, 2198 - unique: false, 2199 - defaultValue: null, 2200 - comment: '{"xata.link":"tracks"}', 2201 - }, 2202 - { 2203 - name: "xata_createdat", 2204 - type: "datetime", 2205 - notNull: true, 2206 - unique: false, 2207 - defaultValue: "now()", 2208 - comment: "", 2209 - }, 2210 - { 2211 - name: "xata_id", 2212 - type: "text", 2213 - notNull: true, 2214 - unique: true, 2215 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2216 - comment: "", 2217 - }, 2218 - { 2219 - name: "xata_updatedat", 2220 - type: "datetime", 2221 - notNull: true, 2222 - unique: false, 2223 - defaultValue: "now()", 2224 - comment: "", 2225 - }, 2226 - { 2227 - name: "xata_version", 2228 - type: "int", 2229 - notNull: true, 2230 - unique: false, 2231 - defaultValue: "0", 2232 - comment: "", 2233 - }, 2234 - ], 2235 - }, 2236 - { 2237 - name: "playlists", 2238 - checkConstraints: { 2239 - playlists_xata_id_length_xata_id: { 2240 - name: "playlists_xata_id_length_xata_id", 2241 - columns: ["xata_id"], 2242 - definition: "CHECK ((length(xata_id) < 256))", 2243 - }, 2244 - }, 2245 - foreignKeys: { 2246 - created_by_link: { 2247 - name: "created_by_link", 2248 - columns: ["created_by"], 2249 - referencedTable: "users", 2250 - referencedColumns: ["xata_id"], 2251 - onDelete: "SET NULL", 2252 - }, 2253 - }, 2254 - primaryKey: [], 2255 - uniqueConstraints: { 2256 - _pgroll_new_playlists_xata_id_key: { 2257 - name: "_pgroll_new_playlists_xata_id_key", 2258 - columns: ["xata_id"], 2259 - }, 2260 - playlists__pgroll_new_uri_key: { 2261 - name: "playlists__pgroll_new_uri_key", 2262 - columns: ["uri"], 2263 - }, 2264 - playlists_apple_music_link_unique: { 2265 - name: "playlists_apple_music_link_unique", 2266 - columns: ["apple_music_link"], 2267 - }, 2268 - playlists_spotify_link_unique: { 2269 - name: "playlists_spotify_link_unique", 2270 - columns: ["spotify_link"], 2271 - }, 2272 - playlists_tidal_link_unique: { 2273 - name: "playlists_tidal_link_unique", 2274 - columns: ["tidal_link"], 2275 - }, 2276 - }, 2277 - columns: [ 2278 - { 2279 - name: "apple_music_link", 2280 - type: "text", 2281 - notNull: false, 2282 - unique: true, 2283 - defaultValue: null, 2284 - comment: "", 2285 - }, 2286 - { 2287 - name: "created_by", 2288 - type: "link", 2289 - link: { table: "users" }, 2290 - notNull: true, 2291 - unique: false, 2292 - defaultValue: null, 2293 - comment: '{"xata.link":"users"}', 2294 - }, 2295 - { 2296 - name: "description", 2297 - type: "text", 2298 - notNull: false, 2299 - unique: false, 2300 - defaultValue: null, 2301 - comment: "", 2302 - }, 2303 - { 2304 - name: "name", 2305 - type: "text", 2306 - notNull: true, 2307 - unique: false, 2308 - defaultValue: null, 2309 - comment: "", 2310 - }, 2311 - { 2312 - name: "picture", 2313 - type: "text", 2314 - notNull: false, 2315 - unique: false, 2316 - defaultValue: null, 2317 - comment: "", 2318 - }, 2319 - { 2320 - name: "spotify_link", 2321 - type: "text", 2322 - notNull: false, 2323 - unique: true, 2324 - defaultValue: null, 2325 - comment: "", 2326 - }, 2327 - { 2328 - name: "tidal_link", 2329 - type: "text", 2330 - notNull: false, 2331 - unique: true, 2332 - defaultValue: null, 2333 - comment: "", 2334 - }, 2335 - { 2336 - name: "uri", 2337 - type: "text", 2338 - notNull: false, 2339 - unique: true, 2340 - defaultValue: null, 2341 - comment: "", 2342 - }, 2343 - { 2344 - name: "xata_createdat", 2345 - type: "datetime", 2346 - notNull: true, 2347 - unique: false, 2348 - defaultValue: "now()", 2349 - comment: "", 2350 - }, 2351 - { 2352 - name: "xata_id", 2353 - type: "text", 2354 - notNull: true, 2355 - unique: true, 2356 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2357 - comment: "", 2358 - }, 2359 - { 2360 - name: "xata_updatedat", 2361 - type: "datetime", 2362 - notNull: true, 2363 - unique: false, 2364 - defaultValue: "now()", 2365 - comment: "", 2366 - }, 2367 - { 2368 - name: "xata_version", 2369 - type: "int", 2370 - notNull: true, 2371 - unique: false, 2372 - defaultValue: "0", 2373 - comment: "", 2374 - }, 2375 - ], 2376 - }, 2377 - { 2378 - name: "profile_shouts", 2379 - checkConstraints: { 2380 - profile_shouts_xata_id_length_xata_id: { 2381 - name: "profile_shouts_xata_id_length_xata_id", 2382 - columns: ["xata_id"], 2383 - definition: "CHECK ((length(xata_id) < 256))", 2384 - }, 2385 - }, 2386 - foreignKeys: { 2387 - shout_id_link: { 2388 - name: "shout_id_link", 2389 - columns: ["shout_id"], 2390 - referencedTable: "shouts", 2391 - referencedColumns: ["xata_id"], 2392 - onDelete: "SET NULL", 2393 - }, 2394 - user_id_link: { 2395 - name: "user_id_link", 2396 - columns: ["user_id"], 2397 - referencedTable: "users", 2398 - referencedColumns: ["xata_id"], 2399 - onDelete: "SET NULL", 2400 - }, 2401 - }, 2402 - primaryKey: [], 2403 - uniqueConstraints: { 2404 - _pgroll_new_profile_shouts_xata_id_key: { 2405 - name: "_pgroll_new_profile_shouts_xata_id_key", 2406 - columns: ["xata_id"], 2407 - }, 2408 - }, 2409 - columns: [ 2410 - { 2411 - name: "shout_id", 2412 - type: "link", 2413 - link: { table: "shouts" }, 2414 - notNull: true, 2415 - unique: false, 2416 - defaultValue: null, 2417 - comment: '{"xata.link":"shouts"}', 2418 - }, 2419 - { 2420 - name: "user_id", 2421 - type: "link", 2422 - link: { table: "users" }, 2423 - notNull: true, 2424 - unique: false, 2425 - defaultValue: null, 2426 - comment: '{"xata.link":"users"}', 2427 - }, 2428 - { 2429 - name: "xata_createdat", 2430 - type: "datetime", 2431 - notNull: true, 2432 - unique: false, 2433 - defaultValue: "now()", 2434 - comment: "", 2435 - }, 2436 - { 2437 - name: "xata_id", 2438 - type: "text", 2439 - notNull: true, 2440 - unique: true, 2441 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2442 - comment: "", 2443 - }, 2444 - { 2445 - name: "xata_updatedat", 2446 - type: "datetime", 2447 - notNull: true, 2448 - unique: false, 2449 - defaultValue: "now()", 2450 - comment: "", 2451 - }, 2452 - { 2453 - name: "xata_version", 2454 - type: "int", 2455 - notNull: true, 2456 - unique: false, 2457 - defaultValue: "0", 2458 - comment: "", 2459 - }, 2460 - ], 2461 - }, 2462 - { 2463 - name: "queue_tracks", 2464 - checkConstraints: { 2465 - queue_tracks_xata_id_length_xata_id: { 2466 - name: "queue_tracks_xata_id_length_xata_id", 2467 - columns: ["xata_id"], 2468 - definition: "CHECK ((length(xata_id) < 256))", 2469 - }, 2470 - }, 2471 - foreignKeys: { 2472 - track_id_link: { 2473 - name: "track_id_link", 2474 - columns: ["track_id"], 2475 - referencedTable: "tracks", 2476 - referencedColumns: ["xata_id"], 2477 - onDelete: "SET NULL", 2478 - }, 2479 - user_id_link: { 2480 - name: "user_id_link", 2481 - columns: ["user_id"], 2482 - referencedTable: "users", 2483 - referencedColumns: ["xata_id"], 2484 - onDelete: "SET NULL", 2485 - }, 2486 - }, 2487 - primaryKey: [], 2488 - uniqueConstraints: { 2489 - _pgroll_new_queue_tracks_xata_id_key: { 2490 - name: "_pgroll_new_queue_tracks_xata_id_key", 2491 - columns: ["xata_id"], 2492 - }, 2493 - }, 2494 - columns: [ 2495 - { 2496 - name: "file_uri", 2497 - type: "text", 2498 - notNull: true, 2499 - unique: false, 2500 - defaultValue: null, 2501 - comment: "", 2502 - }, 2503 - { 2504 - name: "position", 2505 - type: "int", 2506 - notNull: true, 2507 - unique: false, 2508 - defaultValue: null, 2509 - comment: "", 2510 - }, 2511 - { 2512 - name: "track_id", 2513 - type: "link", 2514 - link: { table: "tracks" }, 2515 - notNull: true, 2516 - unique: false, 2517 - defaultValue: null, 2518 - comment: '{"xata.link":"tracks"}', 2519 - }, 2520 - { 2521 - name: "user_id", 2522 - type: "link", 2523 - link: { table: "users" }, 2524 - notNull: true, 2525 - unique: false, 2526 - defaultValue: null, 2527 - comment: '{"xata.link":"users"}', 2528 - }, 2529 - { 2530 - name: "xata_createdat", 2531 - type: "datetime", 2532 - notNull: true, 2533 - unique: false, 2534 - defaultValue: "now()", 2535 - comment: "", 2536 - }, 2537 - { 2538 - name: "xata_id", 2539 - type: "text", 2540 - notNull: true, 2541 - unique: true, 2542 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2543 - comment: "", 2544 - }, 2545 - { 2546 - name: "xata_updatedat", 2547 - type: "datetime", 2548 - notNull: true, 2549 - unique: false, 2550 - defaultValue: "now()", 2551 - comment: "", 2552 - }, 2553 - { 2554 - name: "xata_version", 2555 - type: "int", 2556 - notNull: true, 2557 - unique: false, 2558 - defaultValue: "0", 2559 - comment: "", 2560 - }, 2561 - ], 2562 - }, 2563 - { 2564 - name: "radios", 2565 - checkConstraints: { 2566 - radios_xata_id_length_xata_id: { 2567 - name: "radios_xata_id_length_xata_id", 2568 - columns: ["xata_id"], 2569 - definition: "CHECK ((length(xata_id) < 256))", 2570 - }, 2571 - }, 2572 - foreignKeys: {}, 2573 - primaryKey: [], 2574 - uniqueConstraints: { 2575 - _pgroll_new_radios_xata_id_key: { 2576 - name: "_pgroll_new_radios_xata_id_key", 2577 - columns: ["xata_id"], 2578 - }, 2579 - radios__pgroll_new_uri_key: { 2580 - name: "radios__pgroll_new_uri_key", 2581 - columns: ["uri"], 2582 - }, 2583 - radios__pgroll_new_url_key: { 2584 - name: "radios__pgroll_new_url_key", 2585 - columns: ["url"], 2586 - }, 2587 - }, 2588 - columns: [ 2589 - { 2590 - name: "description", 2591 - type: "text", 2592 - notNull: false, 2593 - unique: false, 2594 - defaultValue: null, 2595 - comment: "", 2596 - }, 2597 - { 2598 - name: "genre", 2599 - type: "text", 2600 - notNull: false, 2601 - unique: false, 2602 - defaultValue: null, 2603 - comment: "", 2604 - }, 2605 - { 2606 - name: "logo", 2607 - type: "text", 2608 - notNull: false, 2609 - unique: false, 2610 - defaultValue: null, 2611 - comment: "", 2612 - }, 2613 - { 2614 - name: "name", 2615 - type: "text", 2616 - notNull: true, 2617 - unique: false, 2618 - defaultValue: null, 2619 - comment: "", 2620 - }, 2621 - { 2622 - name: "uri", 2623 - type: "text", 2624 - notNull: false, 2625 - unique: true, 2626 - defaultValue: null, 2627 - comment: "", 2628 - }, 2629 - { 2630 - name: "url", 2631 - type: "text", 2632 - notNull: true, 2633 - unique: true, 2634 - defaultValue: null, 2635 - comment: "", 2636 - }, 2637 - { 2638 - name: "website", 2639 - type: "text", 2640 - notNull: false, 2641 - unique: false, 2642 - defaultValue: null, 2643 - comment: "", 2644 - }, 2645 - { 2646 - name: "xata_createdat", 2647 - type: "datetime", 2648 - notNull: true, 2649 - unique: false, 2650 - defaultValue: "now()", 2651 - comment: "", 2652 - }, 2653 - { 2654 - name: "xata_id", 2655 - type: "text", 2656 - notNull: true, 2657 - unique: true, 2658 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2659 - comment: "", 2660 - }, 2661 - { 2662 - name: "xata_updatedat", 2663 - type: "datetime", 2664 - notNull: true, 2665 - unique: false, 2666 - defaultValue: "now()", 2667 - comment: "", 2668 - }, 2669 - { 2670 - name: "xata_version", 2671 - type: "int", 2672 - notNull: true, 2673 - unique: false, 2674 - defaultValue: "0", 2675 - comment: "", 2676 - }, 2677 - ], 2678 - }, 2679 - { 2680 - name: "s3_bucket", 2681 - checkConstraints: { 2682 - s3_xata_id_length_xata_id: { 2683 - name: "s3_xata_id_length_xata_id", 2684 - columns: ["xata_id"], 2685 - definition: "CHECK ((length(xata_id) < 256))", 2686 - }, 2687 - }, 2688 - foreignKeys: { 2689 - s3_token_id_link: { 2690 - name: "s3_token_id_link", 2691 - columns: ["s3_token_id"], 2692 - referencedTable: "s3_tokens", 2693 - referencedColumns: ["xata_id"], 2694 - onDelete: "CASCADE", 2695 - }, 2696 - user_id_link: { 2697 - name: "user_id_link", 2698 - columns: ["user_id"], 2699 - referencedTable: "users", 2700 - referencedColumns: ["xata_id"], 2701 - onDelete: "CASCADE", 2702 - }, 2703 - }, 2704 - primaryKey: [], 2705 - uniqueConstraints: { 2706 - _pgroll_new_s3_xata_id_key: { 2707 - name: "_pgroll_new_s3_xata_id_key", 2708 - columns: ["xata_id"], 2709 - }, 2710 - }, 2711 - columns: [ 2712 - { 2713 - name: "name", 2714 - type: "text", 2715 - notNull: true, 2716 - unique: false, 2717 - defaultValue: null, 2718 - comment: "", 2719 - }, 2720 - { 2721 - name: "s3_token_id", 2722 - type: "link", 2723 - link: { table: "s3_tokens" }, 2724 - notNull: true, 2725 - unique: false, 2726 - defaultValue: null, 2727 - comment: '{"xata.link":"s3_tokens"}', 2728 - }, 2729 - { 2730 - name: "user_id", 2731 - type: "link", 2732 - link: { table: "users" }, 2733 - notNull: true, 2734 - unique: false, 2735 - defaultValue: null, 2736 - comment: '{"xata.link":"users"}', 2737 - }, 2738 - { 2739 - name: "xata_createdat", 2740 - type: "datetime", 2741 - notNull: true, 2742 - unique: false, 2743 - defaultValue: "now()", 2744 - comment: "", 2745 - }, 2746 - { 2747 - name: "xata_id", 2748 - type: "text", 2749 - notNull: true, 2750 - unique: true, 2751 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2752 - comment: "", 2753 - }, 2754 - { 2755 - name: "xata_updatedat", 2756 - type: "datetime", 2757 - notNull: true, 2758 - unique: false, 2759 - defaultValue: "now()", 2760 - comment: "", 2761 - }, 2762 - { 2763 - name: "xata_version", 2764 - type: "int", 2765 - notNull: true, 2766 - unique: false, 2767 - defaultValue: "0", 2768 - comment: "", 2769 - }, 2770 - ], 2771 - }, 2772 - { 2773 - name: "s3_directories", 2774 - checkConstraints: { 2775 - s3_directories_xata_id_length_xata_id: { 2776 - name: "s3_directories_xata_id_length_xata_id", 2777 - columns: ["xata_id"], 2778 - definition: "CHECK ((length(xata_id) < 256))", 2779 - }, 2780 - }, 2781 - foreignKeys: { 2782 - parent_id_link: { 2783 - name: "parent_id_link", 2784 - columns: ["parent_id"], 2785 - referencedTable: "s3_directories", 2786 - referencedColumns: ["xata_id"], 2787 - onDelete: "CASCADE", 2788 - }, 2789 - s3_bucket_id_link: { 2790 - name: "s3_bucket_id_link", 2791 - columns: ["s3_bucket_id"], 2792 - referencedTable: "s3_bucket", 2793 - referencedColumns: ["xata_id"], 2794 - onDelete: "CASCADE", 2795 - }, 2796 - }, 2797 - primaryKey: [], 2798 - uniqueConstraints: { 2799 - _pgroll_new_s3_directories_xata_id_key: { 2800 - name: "_pgroll_new_s3_directories_xata_id_key", 2801 - columns: ["xata_id"], 2802 - }, 2803 - }, 2804 - columns: [ 2805 - { 2806 - name: "name", 2807 - type: "text", 2808 - notNull: true, 2809 - unique: false, 2810 - defaultValue: null, 2811 - comment: "", 2812 - }, 2813 - { 2814 - name: "parent_id", 2815 - type: "link", 2816 - link: { table: "s3_directories" }, 2817 - notNull: false, 2818 - unique: false, 2819 - defaultValue: null, 2820 - comment: '{"xata.link":"s3_directories"}', 2821 - }, 2822 - { 2823 - name: "path", 2824 - type: "text", 2825 - notNull: true, 2826 - unique: false, 2827 - defaultValue: null, 2828 - comment: "", 2829 - }, 2830 - { 2831 - name: "s3_bucket_id", 2832 - type: "link", 2833 - link: { table: "s3_bucket" }, 2834 - notNull: true, 2835 - unique: false, 2836 - defaultValue: null, 2837 - comment: '{"xata.link":"s3_bucket"}', 2838 - }, 2839 - { 2840 - name: "xata_createdat", 2841 - type: "datetime", 2842 - notNull: true, 2843 - unique: false, 2844 - defaultValue: "now()", 2845 - comment: "", 2846 - }, 2847 - { 2848 - name: "xata_id", 2849 - type: "text", 2850 - notNull: true, 2851 - unique: true, 2852 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2853 - comment: "", 2854 - }, 2855 - { 2856 - name: "xata_updatedat", 2857 - type: "datetime", 2858 - notNull: true, 2859 - unique: false, 2860 - defaultValue: "now()", 2861 - comment: "", 2862 - }, 2863 - { 2864 - name: "xata_version", 2865 - type: "int", 2866 - notNull: true, 2867 - unique: false, 2868 - defaultValue: "0", 2869 - comment: "", 2870 - }, 2871 - ], 2872 - }, 2873 - { 2874 - name: "s3_paths", 2875 - checkConstraints: { 2876 - s3_paths_xata_id_length_xata_id: { 2877 - name: "s3_paths_xata_id_length_xata_id", 2878 - columns: ["xata_id"], 2879 - definition: "CHECK ((length(xata_id) < 256))", 2880 - }, 2881 - }, 2882 - foreignKeys: { 2883 - directory_id_link: { 2884 - name: "directory_id_link", 2885 - columns: ["directory_id"], 2886 - referencedTable: "s3_directories", 2887 - referencedColumns: ["xata_id"], 2888 - onDelete: "SET NULL", 2889 - }, 2890 - s3_bucket_id_link: { 2891 - name: "s3_bucket_id_link", 2892 - columns: ["s3_bucket_id"], 2893 - referencedTable: "s3_bucket", 2894 - referencedColumns: ["xata_id"], 2895 - onDelete: "CASCADE", 2896 - }, 2897 - track_id_link: { 2898 - name: "track_id_link", 2899 - columns: ["track_id"], 2900 - referencedTable: "tracks", 2901 - referencedColumns: ["xata_id"], 2902 - onDelete: "CASCADE", 2903 - }, 2904 - }, 2905 - primaryKey: [], 2906 - uniqueConstraints: { 2907 - _pgroll_new_s3_paths_xata_id_key: { 2908 - name: "_pgroll_new_s3_paths_xata_id_key", 2909 - columns: ["xata_id"], 2910 - }, 2911 - }, 2912 - columns: [ 2913 - { 2914 - name: "directory_id", 2915 - type: "link", 2916 - link: { table: "s3_directories" }, 2917 - notNull: false, 2918 - unique: false, 2919 - defaultValue: null, 2920 - comment: '{"xata.link":"s3_directories"}', 2921 - }, 2922 - { 2923 - name: "name", 2924 - type: "text", 2925 - notNull: true, 2926 - unique: false, 2927 - defaultValue: null, 2928 - comment: "", 2929 - }, 2930 - { 2931 - name: "path", 2932 - type: "text", 2933 - notNull: true, 2934 - unique: false, 2935 - defaultValue: null, 2936 - comment: "", 2937 - }, 2938 - { 2939 - name: "s3_bucket_id", 2940 - type: "link", 2941 - link: { table: "s3_bucket" }, 2942 - notNull: true, 2943 - unique: false, 2944 - defaultValue: null, 2945 - comment: '{"xata.link":"s3_bucket"}', 2946 - }, 2947 - { 2948 - name: "track_id", 2949 - type: "link", 2950 - link: { table: "tracks" }, 2951 - notNull: true, 2952 - unique: false, 2953 - defaultValue: null, 2954 - comment: '{"xata.link":"tracks"}', 2955 - }, 2956 - { 2957 - name: "xata_createdat", 2958 - type: "datetime", 2959 - notNull: true, 2960 - unique: false, 2961 - defaultValue: "now()", 2962 - comment: "", 2963 - }, 2964 - { 2965 - name: "xata_id", 2966 - type: "text", 2967 - notNull: true, 2968 - unique: true, 2969 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 2970 - comment: "", 2971 - }, 2972 - { 2973 - name: "xata_updatedat", 2974 - type: "datetime", 2975 - notNull: true, 2976 - unique: false, 2977 - defaultValue: "now()", 2978 - comment: "", 2979 - }, 2980 - { 2981 - name: "xata_version", 2982 - type: "int", 2983 - notNull: true, 2984 - unique: false, 2985 - defaultValue: "0", 2986 - comment: "", 2987 - }, 2988 - ], 2989 - }, 2990 - { 2991 - name: "s3_tokens", 2992 - checkConstraints: { 2993 - s3_tokens_xata_id_length_xata_id: { 2994 - name: "s3_tokens_xata_id_length_xata_id", 2995 - columns: ["xata_id"], 2996 - definition: "CHECK ((length(xata_id) < 256))", 2997 - }, 2998 - }, 2999 - foreignKeys: {}, 3000 - primaryKey: [], 3001 - uniqueConstraints: { 3002 - _pgroll_new_s3_tokens_xata_id_key: { 3003 - name: "_pgroll_new_s3_tokens_xata_id_key", 3004 - columns: ["xata_id"], 3005 - }, 3006 - }, 3007 - columns: [ 3008 - { 3009 - name: "client_access_key", 3010 - type: "text", 3011 - notNull: true, 3012 - unique: false, 3013 - defaultValue: null, 3014 - comment: "", 3015 - }, 3016 - { 3017 - name: "secret_access_key", 3018 - type: "text", 3019 - notNull: true, 3020 - unique: false, 3021 - defaultValue: null, 3022 - comment: "", 3023 - }, 3024 - { 3025 - name: "xata_createdat", 3026 - type: "datetime", 3027 - notNull: true, 3028 - unique: false, 3029 - defaultValue: "now()", 3030 - comment: "", 3031 - }, 3032 - { 3033 - name: "xata_id", 3034 - type: "text", 3035 - notNull: true, 3036 - unique: true, 3037 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3038 - comment: "", 3039 - }, 3040 - { 3041 - name: "xata_updatedat", 3042 - type: "datetime", 3043 - notNull: true, 3044 - unique: false, 3045 - defaultValue: "now()", 3046 - comment: "", 3047 - }, 3048 - { 3049 - name: "xata_version", 3050 - type: "int", 3051 - notNull: true, 3052 - unique: false, 3053 - defaultValue: "0", 3054 - comment: "", 3055 - }, 3056 - ], 3057 - }, 3058 - { 3059 - name: "scrobbles", 3060 - checkConstraints: { 3061 - scrobbles_xata_id_length_xata_id: { 3062 - name: "scrobbles_xata_id_length_xata_id", 3063 - columns: ["xata_id"], 3064 - definition: "CHECK ((length(xata_id) < 256))", 3065 - }, 3066 - }, 3067 - foreignKeys: { 3068 - album_id_link: { 3069 - name: "album_id_link", 3070 - columns: ["album_id"], 3071 - referencedTable: "albums", 3072 - referencedColumns: ["xata_id"], 3073 - onDelete: "SET NULL", 3074 - }, 3075 - artist_id_link: { 3076 - name: "artist_id_link", 3077 - columns: ["artist_id"], 3078 - referencedTable: "artists", 3079 - referencedColumns: ["xata_id"], 3080 - onDelete: "SET NULL", 3081 - }, 3082 - track_id_link: { 3083 - name: "track_id_link", 3084 - columns: ["track_id"], 3085 - referencedTable: "tracks", 3086 - referencedColumns: ["xata_id"], 3087 - onDelete: "SET NULL", 3088 - }, 3089 - user_id_link: { 3090 - name: "user_id_link", 3091 - columns: ["user_id"], 3092 - referencedTable: "users", 3093 - referencedColumns: ["xata_id"], 3094 - onDelete: "SET NULL", 3095 - }, 3096 - }, 3097 - primaryKey: [], 3098 - uniqueConstraints: { 3099 - _pgroll_new_scrobbles_xata_id_key: { 3100 - name: "_pgroll_new_scrobbles_xata_id_key", 3101 - columns: ["xata_id"], 3102 - }, 3103 - scrobbles_uri_unique: { name: "scrobbles_uri_unique", columns: ["uri"] }, 3104 - }, 3105 - columns: [ 3106 - { 3107 - name: "album_id", 3108 - type: "link", 3109 - link: { table: "albums" }, 3110 - notNull: false, 3111 - unique: false, 3112 - defaultValue: null, 3113 - comment: '{"xata.link":"albums"}', 3114 - }, 3115 - { 3116 - name: "artist_id", 3117 - type: "link", 3118 - link: { table: "artists" }, 3119 - notNull: false, 3120 - unique: false, 3121 - defaultValue: null, 3122 - comment: '{"xata.link":"artists"}', 3123 - }, 3124 - { 3125 - name: "timestamp", 3126 - type: "datetime", 3127 - notNull: false, 3128 - unique: false, 3129 - defaultValue: null, 3130 - comment: "", 3131 - }, 3132 - { 3133 - name: "track_id", 3134 - type: "link", 3135 - link: { table: "tracks" }, 3136 - notNull: true, 3137 - unique: false, 3138 - defaultValue: null, 3139 - comment: '{"xata.link":"tracks"}', 3140 - }, 3141 - { 3142 - name: "uri", 3143 - type: "text", 3144 - notNull: false, 3145 - unique: true, 3146 - defaultValue: null, 3147 - comment: "", 3148 - }, 3149 - { 3150 - name: "user_id", 3151 - type: "link", 3152 - link: { table: "users" }, 3153 - notNull: true, 3154 - unique: false, 3155 - defaultValue: null, 3156 - comment: '{"xata.link":"users"}', 3157 - }, 3158 - { 3159 - name: "xata_createdat", 3160 - type: "datetime", 3161 - notNull: true, 3162 - unique: false, 3163 - defaultValue: "now()", 3164 - comment: "", 3165 - }, 3166 - { 3167 - name: "xata_id", 3168 - type: "text", 3169 - notNull: true, 3170 - unique: true, 3171 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3172 - comment: "", 3173 - }, 3174 - { 3175 - name: "xata_updatedat", 3176 - type: "datetime", 3177 - notNull: true, 3178 - unique: false, 3179 - defaultValue: "now()", 3180 - comment: "", 3181 - }, 3182 - { 3183 - name: "xata_version", 3184 - type: "int", 3185 - notNull: true, 3186 - unique: false, 3187 - defaultValue: "0", 3188 - comment: "", 3189 - }, 3190 - ], 3191 - }, 3192 - { 3193 - name: "sftp", 3194 - checkConstraints: { 3195 - sftp_xata_id_length_xata_id: { 3196 - name: "sftp_xata_id_length_xata_id", 3197 - columns: ["xata_id"], 3198 - definition: "CHECK ((length(xata_id) < 256))", 3199 - }, 3200 - }, 3201 - foreignKeys: {}, 3202 - primaryKey: [], 3203 - uniqueConstraints: { 3204 - _pgroll_new_sftp_xata_id_key: { 3205 - name: "_pgroll_new_sftp_xata_id_key", 3206 - columns: ["xata_id"], 3207 - }, 3208 - }, 3209 - columns: [ 3210 - { 3211 - name: "xata_createdat", 3212 - type: "datetime", 3213 - notNull: true, 3214 - unique: false, 3215 - defaultValue: "now()", 3216 - comment: "", 3217 - }, 3218 - { 3219 - name: "xata_id", 3220 - type: "text", 3221 - notNull: true, 3222 - unique: true, 3223 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3224 - comment: "", 3225 - }, 3226 - { 3227 - name: "xata_updatedat", 3228 - type: "datetime", 3229 - notNull: true, 3230 - unique: false, 3231 - defaultValue: "now()", 3232 - comment: "", 3233 - }, 3234 - { 3235 - name: "xata_version", 3236 - type: "int", 3237 - notNull: true, 3238 - unique: false, 3239 - defaultValue: "0", 3240 - comment: "", 3241 - }, 3242 - ], 3243 - }, 3244 - { 3245 - name: "sftp_access", 3246 - checkConstraints: { 3247 - sftp_access_xata_id_length_xata_id: { 3248 - name: "sftp_access_xata_id_length_xata_id", 3249 - columns: ["xata_id"], 3250 - definition: "CHECK ((length(xata_id) < 256))", 3251 - }, 3252 - }, 3253 - foreignKeys: {}, 3254 - primaryKey: [], 3255 - uniqueConstraints: { 3256 - _pgroll_new_sftp_access_xata_id_key: { 3257 - name: "_pgroll_new_sftp_access_xata_id_key", 3258 - columns: ["xata_id"], 3259 - }, 3260 - }, 3261 - columns: [ 3262 - { 3263 - name: "xata_createdat", 3264 - type: "datetime", 3265 - notNull: true, 3266 - unique: false, 3267 - defaultValue: "now()", 3268 - comment: "", 3269 - }, 3270 - { 3271 - name: "xata_id", 3272 - type: "text", 3273 - notNull: true, 3274 - unique: true, 3275 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3276 - comment: "", 3277 - }, 3278 - { 3279 - name: "xata_updatedat", 3280 - type: "datetime", 3281 - notNull: true, 3282 - unique: false, 3283 - defaultValue: "now()", 3284 - comment: "", 3285 - }, 3286 - { 3287 - name: "xata_version", 3288 - type: "int", 3289 - notNull: true, 3290 - unique: false, 3291 - defaultValue: "0", 3292 - comment: "", 3293 - }, 3294 - ], 3295 - }, 3296 - { 3297 - name: "sftp_directories", 3298 - checkConstraints: { 3299 - ftp_directories_xata_id_length_xata_id: { 3300 - name: "ftp_directories_xata_id_length_xata_id", 3301 - columns: ["xata_id"], 3302 - definition: "CHECK ((length(xata_id) < 256))", 3303 - }, 3304 - }, 3305 - foreignKeys: { 3306 - parent_id_link: { 3307 - name: "parent_id_link", 3308 - columns: ["parent_id"], 3309 - referencedTable: "sftp_directories", 3310 - referencedColumns: ["xata_id"], 3311 - onDelete: "CASCADE", 3312 - }, 3313 - sftp_id_link: { 3314 - name: "sftp_id_link", 3315 - columns: ["sftp_id"], 3316 - referencedTable: "sftp", 3317 - referencedColumns: ["xata_id"], 3318 - onDelete: "CASCADE", 3319 - }, 3320 - }, 3321 - primaryKey: [], 3322 - uniqueConstraints: { 3323 - _pgroll_new_ftp_directories_xata_id_key: { 3324 - name: "_pgroll_new_ftp_directories_xata_id_key", 3325 - columns: ["xata_id"], 3326 - }, 3327 - }, 3328 - columns: [ 3329 - { 3330 - name: "name", 3331 - type: "text", 3332 - notNull: true, 3333 - unique: false, 3334 - defaultValue: null, 3335 - comment: "", 3336 - }, 3337 - { 3338 - name: "parent_id", 3339 - type: "link", 3340 - link: { table: "sftp_directories" }, 3341 - notNull: false, 3342 - unique: false, 3343 - defaultValue: null, 3344 - comment: '{"xata.link":"sftp_directories"}', 3345 - }, 3346 - { 3347 - name: "path", 3348 - type: "text", 3349 - notNull: true, 3350 - unique: false, 3351 - defaultValue: null, 3352 - comment: "", 3353 - }, 3354 - { 3355 - name: "sftp_id", 3356 - type: "link", 3357 - link: { table: "sftp" }, 3358 - notNull: true, 3359 - unique: false, 3360 - defaultValue: null, 3361 - comment: '{"xata.link":"sftp"}', 3362 - }, 3363 - { 3364 - name: "xata_createdat", 3365 - type: "datetime", 3366 - notNull: true, 3367 - unique: false, 3368 - defaultValue: "now()", 3369 - comment: "", 3370 - }, 3371 - { 3372 - name: "xata_id", 3373 - type: "text", 3374 - notNull: true, 3375 - unique: true, 3376 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3377 - comment: "", 3378 - }, 3379 - { 3380 - name: "xata_updatedat", 3381 - type: "datetime", 3382 - notNull: true, 3383 - unique: false, 3384 - defaultValue: "now()", 3385 - comment: "", 3386 - }, 3387 - { 3388 - name: "xata_version", 3389 - type: "int", 3390 - notNull: true, 3391 - unique: false, 3392 - defaultValue: "0", 3393 - comment: "", 3394 - }, 3395 - ], 3396 - }, 3397 - { 3398 - name: "sftp_path", 3399 - checkConstraints: { 3400 - sftp_path_xata_id_length_xata_id: { 3401 - name: "sftp_path_xata_id_length_xata_id", 3402 - columns: ["xata_id"], 3403 - definition: "CHECK ((length(xata_id) < 256))", 3404 - }, 3405 - }, 3406 - foreignKeys: { 3407 - directory_id_link: { 3408 - name: "directory_id_link", 3409 - columns: ["directory_id"], 3410 - referencedTable: "sftp_directories", 3411 - referencedColumns: ["xata_id"], 3412 - onDelete: "SET NULL", 3413 - }, 3414 - sftp_id_link: { 3415 - name: "sftp_id_link", 3416 - columns: ["sftp_id"], 3417 - referencedTable: "sftp", 3418 - referencedColumns: ["xata_id"], 3419 - onDelete: "CASCADE", 3420 - }, 3421 - track_id_link: { 3422 - name: "track_id_link", 3423 - columns: ["track_id"], 3424 - referencedTable: "tracks", 3425 - referencedColumns: ["xata_id"], 3426 - onDelete: "CASCADE", 3427 - }, 3428 - }, 3429 - primaryKey: [], 3430 - uniqueConstraints: { 3431 - _pgroll_new_sftp_path_xata_id_key: { 3432 - name: "_pgroll_new_sftp_path_xata_id_key", 3433 - columns: ["xata_id"], 3434 - }, 3435 - }, 3436 - columns: [ 3437 - { 3438 - name: "directory_id", 3439 - type: "link", 3440 - link: { table: "sftp_directories" }, 3441 - notNull: false, 3442 - unique: false, 3443 - defaultValue: null, 3444 - comment: '{"xata.link":"sftp_directories"}', 3445 - }, 3446 - { 3447 - name: "name", 3448 - type: "text", 3449 - notNull: true, 3450 - unique: false, 3451 - defaultValue: null, 3452 - comment: "", 3453 - }, 3454 - { 3455 - name: "path", 3456 - type: "text", 3457 - notNull: true, 3458 - unique: false, 3459 - defaultValue: null, 3460 - comment: "", 3461 - }, 3462 - { 3463 - name: "sftp_id", 3464 - type: "link", 3465 - link: { table: "sftp" }, 3466 - notNull: true, 3467 - unique: false, 3468 - defaultValue: null, 3469 - comment: '{"xata.link":"sftp"}', 3470 - }, 3471 - { 3472 - name: "track_id", 3473 - type: "link", 3474 - link: { table: "tracks" }, 3475 - notNull: true, 3476 - unique: false, 3477 - defaultValue: null, 3478 - comment: '{"xata.link":"tracks"}', 3479 - }, 3480 - { 3481 - name: "xata_createdat", 3482 - type: "datetime", 3483 - notNull: true, 3484 - unique: false, 3485 - defaultValue: "now()", 3486 - comment: "", 3487 - }, 3488 - { 3489 - name: "xata_id", 3490 - type: "text", 3491 - notNull: true, 3492 - unique: true, 3493 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3494 - comment: "", 3495 - }, 3496 - { 3497 - name: "xata_updatedat", 3498 - type: "datetime", 3499 - notNull: true, 3500 - unique: false, 3501 - defaultValue: "now()", 3502 - comment: "", 3503 - }, 3504 - { 3505 - name: "xata_version", 3506 - type: "int", 3507 - notNull: true, 3508 - unique: false, 3509 - defaultValue: "0", 3510 - comment: "", 3511 - }, 3512 - ], 3513 - }, 3514 - { 3515 - name: "shout_likes", 3516 - checkConstraints: { 3517 - shout_likes_xata_id_length_xata_id: { 3518 - name: "shout_likes_xata_id_length_xata_id", 3519 - columns: ["xata_id"], 3520 - definition: "CHECK ((length(xata_id) < 256))", 3521 - }, 3522 - }, 3523 - foreignKeys: { 3524 - shout_id_link: { 3525 - name: "shout_id_link", 3526 - columns: ["shout_id"], 3527 - referencedTable: "shouts", 3528 - referencedColumns: ["xata_id"], 3529 - onDelete: "SET NULL", 3530 - }, 3531 - user_id_link: { 3532 - name: "user_id_link", 3533 - columns: ["user_id"], 3534 - referencedTable: "users", 3535 - referencedColumns: ["xata_id"], 3536 - onDelete: "SET NULL", 3537 - }, 3538 - }, 3539 - primaryKey: [], 3540 - uniqueConstraints: { 3541 - _pgroll_new_shout_likes_xata_id_key: { 3542 - name: "_pgroll_new_shout_likes_xata_id_key", 3543 - columns: ["xata_id"], 3544 - }, 3545 - shout_likes__pgroll_new_uri_key: { 3546 - name: "shout_likes__pgroll_new_uri_key", 3547 - columns: ["uri"], 3548 - }, 3549 - }, 3550 - columns: [ 3551 - { 3552 - name: "shout_id", 3553 - type: "link", 3554 - link: { table: "shouts" }, 3555 - notNull: true, 3556 - unique: false, 3557 - defaultValue: null, 3558 - comment: '{"xata.link":"shouts"}', 3559 - }, 3560 - { 3561 - name: "uri", 3562 - type: "text", 3563 - notNull: true, 3564 - unique: true, 3565 - defaultValue: null, 3566 - comment: "", 3567 - }, 3568 - { 3569 - name: "user_id", 3570 - type: "link", 3571 - link: { table: "users" }, 3572 - notNull: true, 3573 - unique: false, 3574 - defaultValue: null, 3575 - comment: '{"xata.link":"users"}', 3576 - }, 3577 - { 3578 - name: "xata_createdat", 3579 - type: "datetime", 3580 - notNull: true, 3581 - unique: false, 3582 - defaultValue: "now()", 3583 - comment: "", 3584 - }, 3585 - { 3586 - name: "xata_id", 3587 - type: "text", 3588 - notNull: true, 3589 - unique: true, 3590 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3591 - comment: "", 3592 - }, 3593 - { 3594 - name: "xata_updatedat", 3595 - type: "datetime", 3596 - notNull: true, 3597 - unique: false, 3598 - defaultValue: "now()", 3599 - comment: "", 3600 - }, 3601 - { 3602 - name: "xata_version", 3603 - type: "int", 3604 - notNull: true, 3605 - unique: false, 3606 - defaultValue: "0", 3607 - comment: "", 3608 - }, 3609 - ], 3610 - }, 3611 - { 3612 - name: "shout_reports", 3613 - checkConstraints: { 3614 - shout_reports_xata_id_length_xata_id: { 3615 - name: "shout_reports_xata_id_length_xata_id", 3616 - columns: ["xata_id"], 3617 - definition: "CHECK ((length(xata_id) < 256))", 3618 - }, 3619 - }, 3620 - foreignKeys: { 3621 - shout_id_link: { 3622 - name: "shout_id_link", 3623 - columns: ["shout_id"], 3624 - referencedTable: "shouts", 3625 - referencedColumns: ["xata_id"], 3626 - onDelete: "SET NULL", 3627 - }, 3628 - user_id_link: { 3629 - name: "user_id_link", 3630 - columns: ["user_id"], 3631 - referencedTable: "users", 3632 - referencedColumns: ["xata_id"], 3633 - onDelete: "SET NULL", 3634 - }, 3635 - }, 3636 - primaryKey: [], 3637 - uniqueConstraints: { 3638 - _pgroll_new_shout_reports_xata_id_key: { 3639 - name: "_pgroll_new_shout_reports_xata_id_key", 3640 - columns: ["xata_id"], 3641 - }, 3642 - }, 3643 - columns: [ 3644 - { 3645 - name: "shout_id", 3646 - type: "link", 3647 - link: { table: "shouts" }, 3648 - notNull: true, 3649 - unique: false, 3650 - defaultValue: null, 3651 - comment: '{"xata.link":"shouts"}', 3652 - }, 3653 - { 3654 - name: "user_id", 3655 - type: "link", 3656 - link: { table: "users" }, 3657 - notNull: true, 3658 - unique: false, 3659 - defaultValue: null, 3660 - comment: '{"xata.link":"users"}', 3661 - }, 3662 - { 3663 - name: "xata_createdat", 3664 - type: "datetime", 3665 - notNull: true, 3666 - unique: false, 3667 - defaultValue: "now()", 3668 - comment: "", 3669 - }, 3670 - { 3671 - name: "xata_id", 3672 - type: "text", 3673 - notNull: true, 3674 - unique: true, 3675 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3676 - comment: "", 3677 - }, 3678 - { 3679 - name: "xata_updatedat", 3680 - type: "datetime", 3681 - notNull: true, 3682 - unique: false, 3683 - defaultValue: "now()", 3684 - comment: "", 3685 - }, 3686 - { 3687 - name: "xata_version", 3688 - type: "int", 3689 - notNull: true, 3690 - unique: false, 3691 - defaultValue: "0", 3692 - comment: "", 3693 - }, 3694 - ], 3695 - }, 3696 - { 3697 - name: "shouts", 3698 - checkConstraints: { 3699 - shouts_xata_id_length_xata_id: { 3700 - name: "shouts_xata_id_length_xata_id", 3701 - columns: ["xata_id"], 3702 - definition: "CHECK ((length(xata_id) < 256))", 3703 - }, 3704 - }, 3705 - foreignKeys: { 3706 - album_id_link: { 3707 - name: "album_id_link", 3708 - columns: ["album_id"], 3709 - referencedTable: "albums", 3710 - referencedColumns: ["xata_id"], 3711 - onDelete: "SET NULL", 3712 - }, 3713 - artist_id_link: { 3714 - name: "artist_id_link", 3715 - columns: ["artist_id"], 3716 - referencedTable: "artists", 3717 - referencedColumns: ["xata_id"], 3718 - onDelete: "SET NULL", 3719 - }, 3720 - parent_id_link: { 3721 - name: "parent_id_link", 3722 - columns: ["parent_id"], 3723 - referencedTable: "shouts", 3724 - referencedColumns: ["xata_id"], 3725 - onDelete: "SET NULL", 3726 - }, 3727 - scrobble_id_link: { 3728 - name: "scrobble_id_link", 3729 - columns: ["scrobble_id"], 3730 - referencedTable: "scrobbles", 3731 - referencedColumns: ["xata_id"], 3732 - onDelete: "SET NULL", 3733 - }, 3734 - track_id_link: { 3735 - name: "track_id_link", 3736 - columns: ["track_id"], 3737 - referencedTable: "tracks", 3738 - referencedColumns: ["xata_id"], 3739 - onDelete: "SET NULL", 3740 - }, 3741 - user_id_link: { 3742 - name: "user_id_link", 3743 - columns: ["author_id"], 3744 - referencedTable: "users", 3745 - referencedColumns: ["xata_id"], 3746 - onDelete: "SET NULL", 3747 - }, 3748 - }, 3749 - primaryKey: [], 3750 - uniqueConstraints: { 3751 - _pgroll_new_shouts_xata_id_key: { 3752 - name: "_pgroll_new_shouts_xata_id_key", 3753 - columns: ["xata_id"], 3754 - }, 3755 - shouts__pgroll_new_uri_key: { 3756 - name: "shouts__pgroll_new_uri_key", 3757 - columns: ["uri"], 3758 - }, 3759 - }, 3760 - columns: [ 3761 - { 3762 - name: "album_id", 3763 - type: "link", 3764 - link: { table: "albums" }, 3765 - notNull: false, 3766 - unique: false, 3767 - defaultValue: null, 3768 - comment: '{"xata.link":"albums"}', 3769 - }, 3770 - { 3771 - name: "artist_id", 3772 - type: "link", 3773 - link: { table: "artists" }, 3774 - notNull: false, 3775 - unique: false, 3776 - defaultValue: null, 3777 - comment: '{"xata.link":"artists"}', 3778 - }, 3779 - { 3780 - name: "author_id", 3781 - type: "link", 3782 - link: { table: "users" }, 3783 - notNull: true, 3784 - unique: false, 3785 - defaultValue: null, 3786 - comment: '{"xata.link":"users"}', 3787 - }, 3788 - { 3789 - name: "content", 3790 - type: "text", 3791 - notNull: true, 3792 - unique: false, 3793 - defaultValue: null, 3794 - comment: "", 3795 - }, 3796 - { 3797 - name: "parent_id", 3798 - type: "link", 3799 - link: { table: "shouts" }, 3800 - notNull: false, 3801 - unique: false, 3802 - defaultValue: null, 3803 - comment: '{"xata.link":"shouts"}', 3804 - }, 3805 - { 3806 - name: "scrobble_id", 3807 - type: "link", 3808 - link: { table: "scrobbles" }, 3809 - notNull: false, 3810 - unique: false, 3811 - defaultValue: null, 3812 - comment: '{"xata.link":"scrobbles"}', 3813 - }, 3814 - { 3815 - name: "track_id", 3816 - type: "link", 3817 - link: { table: "tracks" }, 3818 - notNull: false, 3819 - unique: false, 3820 - defaultValue: null, 3821 - comment: '{"xata.link":"tracks"}', 3822 - }, 3823 - { 3824 - name: "uri", 3825 - type: "text", 3826 - notNull: false, 3827 - unique: true, 3828 - defaultValue: null, 3829 - comment: "", 3830 - }, 3831 - { 3832 - name: "xata_createdat", 3833 - type: "datetime", 3834 - notNull: true, 3835 - unique: false, 3836 - defaultValue: "now()", 3837 - comment: "", 3838 - }, 3839 - { 3840 - name: "xata_id", 3841 - type: "text", 3842 - notNull: true, 3843 - unique: true, 3844 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3845 - comment: "", 3846 - }, 3847 - { 3848 - name: "xata_updatedat", 3849 - type: "datetime", 3850 - notNull: true, 3851 - unique: false, 3852 - defaultValue: "now()", 3853 - comment: "", 3854 - }, 3855 - { 3856 - name: "xata_version", 3857 - type: "int", 3858 - notNull: true, 3859 - unique: false, 3860 - defaultValue: "0", 3861 - comment: "", 3862 - }, 3863 - ], 3864 - }, 3865 - { 3866 - name: "spotify_accounts", 3867 - checkConstraints: { 3868 - spotify_accounts_xata_id_length_xata_id: { 3869 - name: "spotify_accounts_xata_id_length_xata_id", 3870 - columns: ["xata_id"], 3871 - definition: "CHECK ((length(xata_id) < 256))", 3872 - }, 3873 - }, 3874 - foreignKeys: { 3875 - user_id_link: { 3876 - name: "user_id_link", 3877 - columns: ["user_id"], 3878 - referencedTable: "users", 3879 - referencedColumns: ["xata_id"], 3880 - onDelete: "SET NULL", 3881 - }, 3882 - }, 3883 - primaryKey: [], 3884 - uniqueConstraints: { 3885 - _pgroll_new_spotify_accounts_xata_id_key: { 3886 - name: "_pgroll_new_spotify_accounts_xata_id_key", 3887 - columns: ["xata_id"], 3888 - }, 3889 - spotify_accounts__pgroll_new_email_key: { 3890 - name: "spotify_accounts__pgroll_new_email_key", 3891 - columns: ["email"], 3892 - }, 3893 - }, 3894 - columns: [ 3895 - { 3896 - name: "email", 3897 - type: "text", 3898 - notNull: true, 3899 - unique: true, 3900 - defaultValue: null, 3901 - comment: "", 3902 - }, 3903 - { 3904 - name: "is_beta_user", 3905 - type: "bool", 3906 - notNull: true, 3907 - unique: false, 3908 - defaultValue: "false", 3909 - comment: "", 3910 - }, 3911 - { 3912 - name: "user_id", 3913 - type: "link", 3914 - link: { table: "users" }, 3915 - notNull: true, 3916 - unique: false, 3917 - defaultValue: null, 3918 - comment: '{"xata.link":"users"}', 3919 - }, 3920 - { 3921 - name: "xata_createdat", 3922 - type: "datetime", 3923 - notNull: true, 3924 - unique: false, 3925 - defaultValue: "now()", 3926 - comment: "", 3927 - }, 3928 - { 3929 - name: "xata_id", 3930 - type: "text", 3931 - notNull: true, 3932 - unique: true, 3933 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 3934 - comment: "", 3935 - }, 3936 - { 3937 - name: "xata_updatedat", 3938 - type: "datetime", 3939 - notNull: true, 3940 - unique: false, 3941 - defaultValue: "now()", 3942 - comment: "", 3943 - }, 3944 - { 3945 - name: "xata_version", 3946 - type: "int", 3947 - notNull: true, 3948 - unique: false, 3949 - defaultValue: "0", 3950 - comment: "", 3951 - }, 3952 - ], 3953 - }, 3954 - { 3955 - name: "spotify_tokens", 3956 - checkConstraints: { 3957 - spotify_tokens_xata_id_length_xata_id: { 3958 - name: "spotify_tokens_xata_id_length_xata_id", 3959 - columns: ["xata_id"], 3960 - definition: "CHECK ((length(xata_id) < 256))", 3961 - }, 3962 - }, 3963 - foreignKeys: { 3964 - user_id_link: { 3965 - name: "user_id_link", 3966 - columns: ["user_id"], 3967 - referencedTable: "users", 3968 - referencedColumns: ["xata_id"], 3969 - onDelete: "SET NULL", 3970 - }, 3971 - }, 3972 - primaryKey: [], 3973 - uniqueConstraints: { 3974 - _pgroll_new_spotify_tokens_xata_id_key: { 3975 - name: "_pgroll_new_spotify_tokens_xata_id_key", 3976 - columns: ["xata_id"], 3977 - }, 3978 - }, 3979 - columns: [ 3980 - { 3981 - name: "access_token", 3982 - type: "text", 3983 - notNull: true, 3984 - unique: false, 3985 - defaultValue: null, 3986 - comment: "", 3987 - }, 3988 - { 3989 - name: "refresh_token", 3990 - type: "text", 3991 - notNull: true, 3992 - unique: false, 3993 - defaultValue: null, 3994 - comment: "", 3995 - }, 3996 - { 3997 - name: "user_id", 3998 - type: "link", 3999 - link: { table: "users" }, 4000 - notNull: true, 4001 - unique: false, 4002 - defaultValue: null, 4003 - comment: '{"xata.link":"users"}', 4004 - }, 4005 - { 4006 - name: "xata_createdat", 4007 - type: "datetime", 4008 - notNull: true, 4009 - unique: false, 4010 - defaultValue: "now()", 4011 - comment: "", 4012 - }, 4013 - { 4014 - name: "xata_id", 4015 - type: "text", 4016 - notNull: true, 4017 - unique: true, 4018 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 4019 - comment: "", 4020 - }, 4021 - { 4022 - name: "xata_updatedat", 4023 - type: "datetime", 4024 - notNull: true, 4025 - unique: false, 4026 - defaultValue: "now()", 4027 - comment: "", 4028 - }, 4029 - { 4030 - name: "xata_version", 4031 - type: "int", 4032 - notNull: true, 4033 - unique: false, 4034 - defaultValue: "0", 4035 - comment: "", 4036 - }, 4037 - ], 4038 - }, 4039 - { 4040 - name: "tags", 4041 - checkConstraints: { 4042 - tags_xata_id_length_xata_id: { 4043 - name: "tags_xata_id_length_xata_id", 4044 - columns: ["xata_id"], 4045 - definition: "CHECK ((length(xata_id) < 256))", 4046 - }, 4047 - }, 4048 - foreignKeys: {}, 4049 - primaryKey: [], 4050 - uniqueConstraints: { 4051 - _pgroll_new_tags_xata_id_key: { 4052 - name: "_pgroll_new_tags_xata_id_key", 4053 - columns: ["xata_id"], 4054 - }, 4055 - tags__pgroll_new_name_key: { 4056 - name: "tags__pgroll_new_name_key", 4057 - columns: ["name"], 4058 - }, 4059 - }, 4060 - columns: [ 4061 - { 4062 - name: "name", 4063 - type: "text", 4064 - notNull: true, 4065 - unique: true, 4066 - defaultValue: null, 4067 - comment: "", 4068 - }, 4069 - { 4070 - name: "xata_createdat", 4071 - type: "datetime", 4072 - notNull: true, 4073 - unique: false, 4074 - defaultValue: "now()", 4075 - comment: "", 4076 - }, 4077 - { 4078 - name: "xata_id", 4079 - type: "text", 4080 - notNull: true, 4081 - unique: true, 4082 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 4083 - comment: "", 4084 - }, 4085 - { 4086 - name: "xata_updatedat", 4087 - type: "datetime", 4088 - notNull: true, 4089 - unique: false, 4090 - defaultValue: "now()", 4091 - comment: "", 4092 - }, 4093 - { 4094 - name: "xata_version", 4095 - type: "int", 4096 - notNull: true, 4097 - unique: false, 4098 - defaultValue: "0", 4099 - comment: "", 4100 - }, 4101 - ], 4102 - }, 4103 - { 4104 - name: "track_tags", 4105 - checkConstraints: { 4106 - track_tags_xata_id_length_xata_id: { 4107 - name: "track_tags_xata_id_length_xata_id", 4108 - columns: ["xata_id"], 4109 - definition: "CHECK ((length(xata_id) < 256))", 4110 - }, 4111 - }, 4112 - foreignKeys: { 4113 - tag_id_link: { 4114 - name: "tag_id_link", 4115 - columns: ["tag_id"], 4116 - referencedTable: "tags", 4117 - referencedColumns: ["xata_id"], 4118 - onDelete: "SET NULL", 4119 - }, 4120 - track_id_link: { 4121 - name: "track_id_link", 4122 - columns: ["track_id"], 4123 - referencedTable: "tracks", 4124 - referencedColumns: ["xata_id"], 4125 - onDelete: "SET NULL", 4126 - }, 4127 - }, 4128 - primaryKey: [], 4129 - uniqueConstraints: { 4130 - _pgroll_new_track_tags_xata_id_key: { 4131 - name: "_pgroll_new_track_tags_xata_id_key", 4132 - columns: ["xata_id"], 4133 - }, 4134 - }, 4135 - columns: [ 4136 - { 4137 - name: "tag_id", 4138 - type: "link", 4139 - link: { table: "tags" }, 4140 - notNull: true, 4141 - unique: false, 4142 - defaultValue: null, 4143 - comment: '{"xata.link":"tags"}', 4144 - }, 4145 - { 4146 - name: "track_id", 4147 - type: "link", 4148 - link: { table: "tracks" }, 4149 - notNull: true, 4150 - unique: false, 4151 - defaultValue: null, 4152 - comment: '{"xata.link":"tracks"}', 4153 - }, 4154 - { 4155 - name: "xata_createdat", 4156 - type: "datetime", 4157 - notNull: true, 4158 - unique: false, 4159 - defaultValue: "now()", 4160 - comment: "", 4161 - }, 4162 - { 4163 - name: "xata_id", 4164 - type: "text", 4165 - notNull: true, 4166 - unique: true, 4167 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 4168 - comment: "", 4169 - }, 4170 - { 4171 - name: "xata_updatedat", 4172 - type: "datetime", 4173 - notNull: true, 4174 - unique: false, 4175 - defaultValue: "now()", 4176 - comment: "", 4177 - }, 4178 - { 4179 - name: "xata_version", 4180 - type: "int", 4181 - notNull: true, 4182 - unique: false, 4183 - defaultValue: "0", 4184 - comment: "", 4185 - }, 4186 - ], 4187 - }, 4188 - { 4189 - name: "tracks", 4190 - checkConstraints: { 4191 - tracks_xata_id_length_xata_id: { 4192 - name: "tracks_xata_id_length_xata_id", 4193 - columns: ["xata_id"], 4194 - definition: "CHECK ((length(xata_id) < 256))", 4195 - }, 4196 - }, 4197 - foreignKeys: {}, 4198 - primaryKey: [], 4199 - uniqueConstraints: { 4200 - _pgroll_new_tracks_xata_id_key: { 4201 - name: "_pgroll_new_tracks_xata_id_key", 4202 - columns: ["xata_id"], 4203 - }, 4204 - tracks__pgroll_new_mb_id_key: { 4205 - name: "tracks__pgroll_new_mb_id_key", 4206 - columns: ["mb_id"], 4207 - }, 4208 - tracks__pgroll_new_sha256_key: { 4209 - name: "tracks__pgroll_new_sha256_key", 4210 - columns: ["sha256"], 4211 - }, 4212 - tracks__pgroll_new_uri_key: { 4213 - name: "tracks__pgroll_new_uri_key", 4214 - columns: ["uri"], 4215 - }, 4216 - tracks_tidal_link_unique: { 4217 - name: "tracks_tidal_link_unique", 4218 - columns: ["tidal_link"], 4219 - }, 4220 - tracks_youtube_link_unique: { 4221 - name: "tracks_youtube_link_unique", 4222 - columns: ["youtube_link"], 4223 - }, 4224 - }, 4225 - columns: [ 4226 - { 4227 - name: "album", 4228 - type: "text", 4229 - notNull: true, 4230 - unique: false, 4231 - defaultValue: null, 4232 - comment: "", 4233 - }, 4234 - { 4235 - name: "album_art", 4236 - type: "text", 4237 - notNull: false, 4238 - unique: false, 4239 - defaultValue: null, 4240 - comment: "", 4241 - }, 4242 - { 4243 - name: "album_artist", 4244 - type: "text", 4245 - notNull: true, 4246 - unique: false, 4247 - defaultValue: null, 4248 - comment: "", 4249 - }, 4250 - { 4251 - name: "album_uri", 4252 - type: "text", 4253 - notNull: false, 4254 - unique: false, 4255 - defaultValue: null, 4256 - comment: "", 4257 - }, 4258 - { 4259 - name: "apple_music_link", 4260 - type: "text", 4261 - notNull: false, 4262 - unique: false, 4263 - defaultValue: null, 4264 - comment: "", 4265 - }, 4266 - { 4267 - name: "artist", 4268 - type: "text", 4269 - notNull: true, 4270 - unique: false, 4271 - defaultValue: null, 4272 - comment: "", 4273 - }, 4274 - { 4275 - name: "artist_uri", 4276 - type: "text", 4277 - notNull: false, 4278 - unique: false, 4279 - defaultValue: null, 4280 - comment: "", 4281 - }, 4282 - { 4283 - name: "composer", 4284 - type: "text", 4285 - notNull: false, 4286 - unique: false, 4287 - defaultValue: null, 4288 - comment: "", 4289 - }, 4290 - { 4291 - name: "copyright_message", 4292 - type: "text", 4293 - notNull: false, 4294 - unique: false, 4295 - defaultValue: null, 4296 - comment: "", 4297 - }, 4298 - { 4299 - name: "disc_number", 4300 - type: "int", 4301 - notNull: false, 4302 - unique: false, 4303 - defaultValue: null, 4304 - comment: "", 4305 - }, 4306 - { 4307 - name: "duration", 4308 - type: "int", 4309 - notNull: false, 4310 - unique: false, 4311 - defaultValue: null, 4312 - comment: "", 4313 - }, 4314 - { 4315 - name: "genre", 4316 - type: "text", 4317 - notNull: false, 4318 - unique: false, 4319 - defaultValue: null, 4320 - comment: "", 4321 - }, 4322 - { 4323 - name: "label", 4324 - type: "text", 4325 - notNull: false, 4326 - unique: false, 4327 - defaultValue: null, 4328 - comment: "", 4329 - }, 4330 - { 4331 - name: "lastfm_link", 4332 - type: "text", 4333 - notNull: false, 4334 - unique: false, 4335 - defaultValue: null, 4336 - comment: "", 4337 - }, 4338 - { 4339 - name: "lyrics", 4340 - type: "text", 4341 - notNull: false, 4342 - unique: false, 4343 - defaultValue: null, 4344 - comment: "", 4345 - }, 4346 - { 4347 - name: "mb_id", 4348 - type: "text", 4349 - notNull: false, 4350 - unique: true, 4351 - defaultValue: null, 4352 - comment: "", 4353 - }, 4354 - { 4355 - name: "sha256", 4356 - type: "text", 4357 - notNull: true, 4358 - unique: true, 4359 - defaultValue: null, 4360 - comment: "", 4361 - }, 4362 - { 4363 - name: "spotify_link", 4364 - type: "text", 4365 - notNull: false, 4366 - unique: false, 4367 - defaultValue: null, 4368 - comment: "", 4369 - }, 4370 - { 4371 - name: "tidal_link", 4372 - type: "text", 4373 - notNull: false, 4374 - unique: true, 4375 - defaultValue: null, 4376 - comment: "", 4377 - }, 4378 - { 4379 - name: "title", 4380 - type: "text", 4381 - notNull: true, 4382 - unique: false, 4383 - defaultValue: null, 4384 - comment: "", 4385 - }, 4386 - { 4387 - name: "track_number", 4388 - type: "int", 4389 - notNull: false, 4390 - unique: false, 4391 - defaultValue: null, 4392 - comment: "", 4393 - }, 4394 - { 4395 - name: "uri", 4396 - type: "text", 4397 - notNull: false, 4398 - unique: true, 4399 - defaultValue: null, 4400 - comment: "", 4401 - }, 4402 - { 4403 - name: "xata_createdat", 4404 - type: "datetime", 4405 - notNull: true, 4406 - unique: false, 4407 - defaultValue: "now()", 4408 - comment: "", 4409 - }, 4410 - { 4411 - name: "xata_id", 4412 - type: "text", 4413 - notNull: true, 4414 - unique: true, 4415 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 4416 - comment: "", 4417 - }, 4418 - { 4419 - name: "xata_updatedat", 4420 - type: "datetime", 4421 - notNull: true, 4422 - unique: false, 4423 - defaultValue: "now()", 4424 - comment: "", 4425 - }, 4426 - { 4427 - name: "xata_version", 4428 - type: "int", 4429 - notNull: true, 4430 - unique: false, 4431 - defaultValue: "0", 4432 - comment: "", 4433 - }, 4434 - { 4435 - name: "youtube_link", 4436 - type: "text", 4437 - notNull: false, 4438 - unique: true, 4439 - defaultValue: null, 4440 - comment: "", 4441 - }, 4442 - ], 4443 - }, 4444 - { 4445 - name: "user_albums", 4446 - checkConstraints: { 4447 - user_albums_xata_id_length_xata_id: { 4448 - name: "user_albums_xata_id_length_xata_id", 4449 - columns: ["xata_id"], 4450 - definition: "CHECK ((length(xata_id) < 256))", 4451 - }, 4452 - }, 4453 - foreignKeys: { 4454 - album_id_link: { 4455 - name: "album_id_link", 4456 - columns: ["album_id"], 4457 - referencedTable: "albums", 4458 - referencedColumns: ["xata_id"], 4459 - onDelete: "SET NULL", 4460 - }, 4461 - user_id_link: { 4462 - name: "user_id_link", 4463 - columns: ["user_id"], 4464 - referencedTable: "users", 4465 - referencedColumns: ["xata_id"], 4466 - onDelete: "SET NULL", 4467 - }, 4468 - }, 4469 - primaryKey: [], 4470 - uniqueConstraints: { 4471 - _pgroll_new_user_albums_xata_id_key: { 4472 - name: "_pgroll_new_user_albums_xata_id_key", 4473 - columns: ["xata_id"], 4474 - }, 4475 - }, 4476 - columns: [ 4477 - { 4478 - name: "album_id", 4479 - type: "link", 4480 - link: { table: "albums" }, 4481 - notNull: true, 4482 - unique: false, 4483 - defaultValue: null, 4484 - comment: '{"xata.link":"albums"}', 4485 - }, 4486 - { 4487 - name: "scrobbles", 4488 - type: "int", 4489 - notNull: false, 4490 - unique: false, 4491 - defaultValue: null, 4492 - comment: "", 4493 - }, 4494 - { 4495 - name: "uri", 4496 - type: "text", 4497 - notNull: false, 4498 - unique: false, 4499 - defaultValue: null, 4500 - comment: "", 4501 - }, 4502 - { 4503 - name: "user_id", 4504 - type: "link", 4505 - link: { table: "users" }, 4506 - notNull: true, 4507 - unique: false, 4508 - defaultValue: null, 4509 - comment: '{"xata.link":"users"}', 4510 - }, 4511 - { 4512 - name: "xata_createdat", 4513 - type: "datetime", 4514 - notNull: true, 4515 - unique: false, 4516 - defaultValue: "now()", 4517 - comment: "", 4518 - }, 4519 - { 4520 - name: "xata_id", 4521 - type: "text", 4522 - notNull: true, 4523 - unique: true, 4524 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 4525 - comment: "", 4526 - }, 4527 - { 4528 - name: "xata_updatedat", 4529 - type: "datetime", 4530 - notNull: true, 4531 - unique: false, 4532 - defaultValue: "now()", 4533 - comment: "", 4534 - }, 4535 - { 4536 - name: "xata_version", 4537 - type: "int", 4538 - notNull: true, 4539 - unique: false, 4540 - defaultValue: "0", 4541 - comment: "", 4542 - }, 4543 - ], 4544 - }, 4545 - { 4546 - name: "user_artists", 4547 - checkConstraints: { 4548 - user_artists_xata_id_length_xata_id: { 4549 - name: "user_artists_xata_id_length_xata_id", 4550 - columns: ["xata_id"], 4551 - definition: "CHECK ((length(xata_id) < 256))", 4552 - }, 4553 - }, 4554 - foreignKeys: { 4555 - artist_id_link: { 4556 - name: "artist_id_link", 4557 - columns: ["artist_id"], 4558 - referencedTable: "artists", 4559 - referencedColumns: ["xata_id"], 4560 - onDelete: "SET NULL", 4561 - }, 4562 - user_id_link: { 4563 - name: "user_id_link", 4564 - columns: ["user_id"], 4565 - referencedTable: "users", 4566 - referencedColumns: ["xata_id"], 4567 - onDelete: "SET NULL", 4568 - }, 4569 - }, 4570 - primaryKey: [], 4571 - uniqueConstraints: { 4572 - _pgroll_new_user_artists_xata_id_key: { 4573 - name: "_pgroll_new_user_artists_xata_id_key", 4574 - columns: ["xata_id"], 4575 - }, 4576 - }, 4577 - columns: [ 4578 - { 4579 - name: "artist_id", 4580 - type: "link", 4581 - link: { table: "artists" }, 4582 - notNull: true, 4583 - unique: false, 4584 - defaultValue: null, 4585 - comment: '{"xata.link":"artists"}', 4586 - }, 4587 - { 4588 - name: "scrobbles", 4589 - type: "int", 4590 - notNull: false, 4591 - unique: false, 4592 - defaultValue: null, 4593 - comment: "", 4594 - }, 4595 - { 4596 - name: "uri", 4597 - type: "text", 4598 - notNull: true, 4599 - unique: false, 4600 - defaultValue: null, 4601 - comment: "", 4602 - }, 4603 - { 4604 - name: "user_id", 4605 - type: "link", 4606 - link: { table: "users" }, 4607 - notNull: true, 4608 - unique: false, 4609 - defaultValue: null, 4610 - comment: '{"xata.link":"users"}', 4611 - }, 4612 - { 4613 - name: "xata_createdat", 4614 - type: "datetime", 4615 - notNull: true, 4616 - unique: false, 4617 - defaultValue: "now()", 4618 - comment: "", 4619 - }, 4620 - { 4621 - name: "xata_id", 4622 - type: "text", 4623 - notNull: true, 4624 - unique: true, 4625 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 4626 - comment: "", 4627 - }, 4628 - { 4629 - name: "xata_updatedat", 4630 - type: "datetime", 4631 - notNull: true, 4632 - unique: false, 4633 - defaultValue: "now()", 4634 - comment: "", 4635 - }, 4636 - { 4637 - name: "xata_version", 4638 - type: "int", 4639 - notNull: true, 4640 - unique: false, 4641 - defaultValue: "0", 4642 - comment: "", 4643 - }, 4644 - ], 4645 - }, 4646 - { 4647 - name: "user_playlists", 4648 - checkConstraints: { 4649 - user_playlists_xata_id_length_xata_id: { 4650 - name: "user_playlists_xata_id_length_xata_id", 4651 - columns: ["xata_id"], 4652 - definition: "CHECK ((length(xata_id) < 256))", 4653 - }, 4654 - }, 4655 - foreignKeys: { 4656 - playlist_id_link: { 4657 - name: "playlist_id_link", 4658 - columns: ["playlist_id"], 4659 - referencedTable: "playlists", 4660 - referencedColumns: ["xata_id"], 4661 - onDelete: "SET NULL", 4662 - }, 4663 - user_id_link: { 4664 - name: "user_id_link", 4665 - columns: ["user_id"], 4666 - referencedTable: "users", 4667 - referencedColumns: ["xata_id"], 4668 - onDelete: "SET NULL", 4669 - }, 4670 - }, 4671 - primaryKey: [], 4672 - uniqueConstraints: { 4673 - _pgroll_new_user_playlists_xata_id_key: { 4674 - name: "_pgroll_new_user_playlists_xata_id_key", 4675 - columns: ["xata_id"], 4676 - }, 4677 - user_playlists__pgroll_new_uri_key: { 4678 - name: "user_playlists__pgroll_new_uri_key", 4679 - columns: ["uri"], 4680 - }, 4681 - }, 4682 - columns: [ 4683 - { 4684 - name: "playlist_id", 4685 - type: "link", 4686 - link: { table: "playlists" }, 4687 - notNull: true, 4688 - unique: false, 4689 - defaultValue: null, 4690 - comment: '{"xata.link":"playlists"}', 4691 - }, 4692 - { 4693 - name: "uri", 4694 - type: "text", 4695 - notNull: false, 4696 - unique: true, 4697 - defaultValue: null, 4698 - comment: "", 4699 - }, 4700 - { 4701 - name: "user_id", 4702 - type: "link", 4703 - link: { table: "users" }, 4704 - notNull: true, 4705 - unique: false, 4706 - defaultValue: null, 4707 - comment: '{"xata.link":"users"}', 4708 - }, 4709 - { 4710 - name: "xata_createdat", 4711 - type: "datetime", 4712 - notNull: true, 4713 - unique: false, 4714 - defaultValue: "now()", 4715 - comment: "", 4716 - }, 4717 - { 4718 - name: "xata_id", 4719 - type: "text", 4720 - notNull: true, 4721 - unique: true, 4722 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 4723 - comment: "", 4724 - }, 4725 - { 4726 - name: "xata_updatedat", 4727 - type: "datetime", 4728 - notNull: true, 4729 - unique: false, 4730 - defaultValue: "now()", 4731 - comment: "", 4732 - }, 4733 - { 4734 - name: "xata_version", 4735 - type: "int", 4736 - notNull: true, 4737 - unique: false, 4738 - defaultValue: "0", 4739 - comment: "", 4740 - }, 4741 - ], 4742 - }, 4743 - { 4744 - name: "user_tracks", 4745 - checkConstraints: { 4746 - user_tracks_xata_id_length_xata_id: { 4747 - name: "user_tracks_xata_id_length_xata_id", 4748 - columns: ["xata_id"], 4749 - definition: "CHECK ((length(xata_id) < 256))", 4750 - }, 4751 - }, 4752 - foreignKeys: { 4753 - track_id_link: { 4754 - name: "track_id_link", 4755 - columns: ["track_id"], 4756 - referencedTable: "tracks", 4757 - referencedColumns: ["xata_id"], 4758 - onDelete: "SET NULL", 4759 - }, 4760 - user_id_link: { 4761 - name: "user_id_link", 4762 - columns: ["user_id"], 4763 - referencedTable: "users", 4764 - referencedColumns: ["xata_id"], 4765 - onDelete: "SET NULL", 4766 - }, 4767 - }, 4768 - primaryKey: [], 4769 - uniqueConstraints: { 4770 - _pgroll_new_user_tracks_xata_id_key: { 4771 - name: "_pgroll_new_user_tracks_xata_id_key", 4772 - columns: ["xata_id"], 4773 - }, 4774 - }, 4775 - columns: [ 4776 - { 4777 - name: "scrobbles", 4778 - type: "int", 4779 - notNull: false, 4780 - unique: false, 4781 - defaultValue: null, 4782 - comment: "", 4783 - }, 4784 - { 4785 - name: "track_id", 4786 - type: "link", 4787 - link: { table: "tracks" }, 4788 - notNull: true, 4789 - unique: false, 4790 - defaultValue: null, 4791 - comment: '{"xata.link":"tracks"}', 4792 - }, 4793 - { 4794 - name: "uri", 4795 - type: "text", 4796 - notNull: false, 4797 - unique: false, 4798 - defaultValue: null, 4799 - comment: "", 4800 - }, 4801 - { 4802 - name: "user_id", 4803 - type: "link", 4804 - link: { table: "users" }, 4805 - notNull: true, 4806 - unique: false, 4807 - defaultValue: null, 4808 - comment: '{"xata.link":"users"}', 4809 - }, 4810 - { 4811 - name: "xata_createdat", 4812 - type: "datetime", 4813 - notNull: true, 4814 - unique: false, 4815 - defaultValue: "now()", 4816 - comment: "", 4817 - }, 4818 - { 4819 - name: "xata_id", 4820 - type: "text", 4821 - notNull: true, 4822 - unique: true, 4823 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 4824 - comment: "", 4825 - }, 4826 - { 4827 - name: "xata_updatedat", 4828 - type: "datetime", 4829 - notNull: true, 4830 - unique: false, 4831 - defaultValue: "now()", 4832 - comment: "", 4833 - }, 4834 - { 4835 - name: "xata_version", 4836 - type: "int", 4837 - notNull: true, 4838 - unique: false, 4839 - defaultValue: "0", 4840 - comment: "", 4841 - }, 4842 - ], 4843 - }, 4844 - { 4845 - name: "users", 4846 - checkConstraints: { 4847 - users_xata_id_length_xata_id: { 4848 - name: "users_xata_id_length_xata_id", 4849 - columns: ["xata_id"], 4850 - definition: "CHECK ((length(xata_id) < 256))", 4851 - }, 4852 - }, 4853 - foreignKeys: {}, 4854 - primaryKey: [], 4855 - uniqueConstraints: { 4856 - _pgroll_new_users_xata_id_key: { 4857 - name: "_pgroll_new_users_xata_id_key", 4858 - columns: ["xata_id"], 4859 - }, 4860 - users__pgroll_new_did_key: { 4861 - name: "users__pgroll_new_did_key", 4862 - columns: ["did"], 4863 - }, 4864 - users__pgroll_new_handle_key: { 4865 - name: "users__pgroll_new_handle_key", 4866 - columns: ["handle"], 4867 - }, 4868 - }, 4869 - columns: [ 4870 - { 4871 - name: "avatar", 4872 - type: "text", 4873 - notNull: true, 4874 - unique: false, 4875 - defaultValue: null, 4876 - comment: "", 4877 - }, 4878 - { 4879 - name: "did", 4880 - type: "text", 4881 - notNull: true, 4882 - unique: true, 4883 - defaultValue: null, 4884 - comment: "", 4885 - }, 4886 - { 4887 - name: "display_name", 4888 - type: "text", 4889 - notNull: true, 4890 - unique: false, 4891 - defaultValue: null, 4892 - comment: "", 4893 - }, 4894 - { 4895 - name: "handle", 4896 - type: "text", 4897 - notNull: true, 4898 - unique: true, 4899 - defaultValue: null, 4900 - comment: "", 4901 - }, 4902 - { 4903 - name: "xata_createdat", 4904 - type: "datetime", 4905 - notNull: true, 4906 - unique: false, 4907 - defaultValue: "now()", 4908 - comment: "", 4909 - }, 4910 - { 4911 - name: "xata_id", 4912 - type: "text", 4913 - notNull: true, 4914 - unique: true, 4915 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 4916 - comment: "", 4917 - }, 4918 - { 4919 - name: "xata_updatedat", 4920 - type: "datetime", 4921 - notNull: true, 4922 - unique: false, 4923 - defaultValue: "now()", 4924 - comment: "", 4925 - }, 4926 - { 4927 - name: "xata_version", 4928 - type: "int", 4929 - notNull: true, 4930 - unique: false, 4931 - defaultValue: "0", 4932 - comment: "", 4933 - }, 4934 - ], 4935 - }, 4936 - { 4937 - name: "webscrobblers", 4938 - checkConstraints: { 4939 - webscrobblers_xata_id_length_xata_id: { 4940 - name: "webscrobblers_xata_id_length_xata_id", 4941 - columns: ["xata_id"], 4942 - definition: "CHECK ((length(xata_id) < 256))", 4943 - }, 4944 - }, 4945 - foreignKeys: { 4946 - user_id_link: { 4947 - name: "user_id_link", 4948 - columns: ["user_id"], 4949 - referencedTable: "users", 4950 - referencedColumns: ["xata_id"], 4951 - onDelete: "CASCADE", 4952 - }, 4953 - }, 4954 - primaryKey: [], 4955 - uniqueConstraints: { 4956 - _pgroll_new_webscrobblers_xata_id_key: { 4957 - name: "_pgroll_new_webscrobblers_xata_id_key", 4958 - columns: ["xata_id"], 4959 - }, 4960 - webscrobblers__pgroll_new_uuid_key: { 4961 - name: "webscrobblers__pgroll_new_uuid_key", 4962 - columns: ["uuid"], 4963 - }, 4964 - }, 4965 - columns: [ 4966 - { 4967 - name: "description", 4968 - type: "text", 4969 - notNull: false, 4970 - unique: false, 4971 - defaultValue: null, 4972 - comment: "", 4973 - }, 4974 - { 4975 - name: "enabled", 4976 - type: "bool", 4977 - notNull: false, 4978 - unique: false, 4979 - defaultValue: "true", 4980 - comment: "", 4981 - }, 4982 - { 4983 - name: "name", 4984 - type: "text", 4985 - notNull: true, 4986 - unique: false, 4987 - defaultValue: null, 4988 - comment: "", 4989 - }, 4990 - { 4991 - name: "user_id", 4992 - type: "link", 4993 - link: { table: "users" }, 4994 - notNull: true, 4995 - unique: false, 4996 - defaultValue: null, 4997 - comment: '{"xata.link":"users"}', 4998 - }, 4999 - { 5000 - name: "uuid", 5001 - type: "text", 5002 - notNull: true, 5003 - unique: true, 5004 - defaultValue: null, 5005 - comment: "", 5006 - }, 5007 - { 5008 - name: "xata_createdat", 5009 - type: "datetime", 5010 - notNull: true, 5011 - unique: false, 5012 - defaultValue: "now()", 5013 - comment: "", 5014 - }, 5015 - { 5016 - name: "xata_id", 5017 - type: "text", 5018 - notNull: true, 5019 - unique: true, 5020 - defaultValue: "('rec_'::text || (xata_private.xid())::text)", 5021 - comment: "", 5022 - }, 5023 - { 5024 - name: "xata_updatedat", 5025 - type: "datetime", 5026 - notNull: true, 5027 - unique: false, 5028 - defaultValue: "now()", 5029 - comment: "", 5030 - }, 5031 - { 5032 - name: "xata_version", 5033 - type: "int", 5034 - notNull: true, 5035 - unique: false, 5036 - defaultValue: "0", 5037 - comment: "", 5038 - }, 5039 - ], 5040 - }, 5041 - ] as const; 5042 - 5043 - export type SchemaTables = typeof tables; 5044 - export type InferredTypes = SchemaInference<SchemaTables>; 5045 - 5046 - export type AlbumTags = InferredTypes["album_tags"]; 5047 - export type AlbumTagsRecord = AlbumTags & XataRecord; 5048 - 5049 - export type AlbumTracks = InferredTypes["album_tracks"]; 5050 - export type AlbumTracksRecord = AlbumTracks & XataRecord; 5051 - 5052 - export type Albums = InferredTypes["albums"]; 5053 - export type AlbumsRecord = Albums & XataRecord; 5054 - 5055 - export type ApiKeys = InferredTypes["api_keys"]; 5056 - export type ApiKeysRecord = ApiKeys & XataRecord; 5057 - 5058 - export type ArtistAlbums = InferredTypes["artist_albums"]; 5059 - export type ArtistAlbumsRecord = ArtistAlbums & XataRecord; 5060 - 5061 - export type ArtistTags = InferredTypes["artist_tags"]; 5062 - export type ArtistTagsRecord = ArtistTags & XataRecord; 5063 - 5064 - export type ArtistTracks = InferredTypes["artist_tracks"]; 5065 - export type ArtistTracksRecord = ArtistTracks & XataRecord; 5066 - 5067 - export type Artists = InferredTypes["artists"]; 5068 - export type ArtistsRecord = Artists & XataRecord; 5069 - 5070 - export type BuiltinStoragePaths = InferredTypes["builtin_storage_paths"]; 5071 - export type BuiltinStoragePathsRecord = BuiltinStoragePaths & XataRecord; 5072 - 5073 - export type Dropbox = InferredTypes["dropbox"]; 5074 - export type DropboxRecord = Dropbox & XataRecord; 5075 - 5076 - export type DropboxAccounts = InferredTypes["dropbox_accounts"]; 5077 - export type DropboxAccountsRecord = DropboxAccounts & XataRecord; 5078 - 5079 - export type DropboxDirectories = InferredTypes["dropbox_directories"]; 5080 - export type DropboxDirectoriesRecord = DropboxDirectories & XataRecord; 5081 - 5082 - export type DropboxPaths = InferredTypes["dropbox_paths"]; 5083 - export type DropboxPathsRecord = DropboxPaths & XataRecord; 5084 - 5085 - export type DropboxTokens = InferredTypes["dropbox_tokens"]; 5086 - export type DropboxTokensRecord = DropboxTokens & XataRecord; 5087 - 5088 - export type GoogleDrive = InferredTypes["google_drive"]; 5089 - export type GoogleDriveRecord = GoogleDrive & XataRecord; 5090 - 5091 - export type GoogleDriveAccounts = InferredTypes["google_drive_accounts"]; 5092 - export type GoogleDriveAccountsRecord = GoogleDriveAccounts & XataRecord; 5093 - 5094 - export type GoogleDriveDirectories = InferredTypes["google_drive_directories"]; 5095 - export type GoogleDriveDirectoriesRecord = GoogleDriveDirectories & XataRecord; 5096 - 5097 - export type GoogleDrivePaths = InferredTypes["google_drive_paths"]; 5098 - export type GoogleDrivePathsRecord = GoogleDrivePaths & XataRecord; 5099 - 5100 - export type GoogleDriveTokens = InferredTypes["google_drive_tokens"]; 5101 - export type GoogleDriveTokensRecord = GoogleDriveTokens & XataRecord; 5102 - 5103 - export type LovedTracks = InferredTypes["loved_tracks"]; 5104 - export type LovedTracksRecord = LovedTracks & XataRecord; 5105 - 5106 - export type PlaybackState = InferredTypes["playback_state"]; 5107 - export type PlaybackStateRecord = PlaybackState & XataRecord; 5108 - 5109 - export type PlaylistTracks = InferredTypes["playlist_tracks"]; 5110 - export type PlaylistTracksRecord = PlaylistTracks & XataRecord; 5111 - 5112 - export type Playlists = InferredTypes["playlists"]; 5113 - export type PlaylistsRecord = Playlists & XataRecord; 5114 - 5115 - export type ProfileShouts = InferredTypes["profile_shouts"]; 5116 - export type ProfileShoutsRecord = ProfileShouts & XataRecord; 5117 - 5118 - export type QueueTracks = InferredTypes["queue_tracks"]; 5119 - export type QueueTracksRecord = QueueTracks & XataRecord; 5120 - 5121 - export type Radios = InferredTypes["radios"]; 5122 - export type RadiosRecord = Radios & XataRecord; 5123 - 5124 - export type S3Bucket = InferredTypes["s3_bucket"]; 5125 - export type S3BucketRecord = S3Bucket & XataRecord; 5126 - 5127 - export type S3Directories = InferredTypes["s3_directories"]; 5128 - export type S3DirectoriesRecord = S3Directories & XataRecord; 5129 - 5130 - export type S3Paths = InferredTypes["s3_paths"]; 5131 - export type S3PathsRecord = S3Paths & XataRecord; 5132 - 5133 - export type S3Tokens = InferredTypes["s3_tokens"]; 5134 - export type S3TokensRecord = S3Tokens & XataRecord; 5135 - 5136 - export type Scrobbles = InferredTypes["scrobbles"]; 5137 - export type ScrobblesRecord = Scrobbles & XataRecord; 5138 - 5139 - export type Sftp = InferredTypes["sftp"]; 5140 - export type SftpRecord = Sftp & XataRecord; 5141 - 5142 - export type SftpAccess = InferredTypes["sftp_access"]; 5143 - export type SftpAccessRecord = SftpAccess & XataRecord; 5144 - 5145 - export type SftpDirectories = InferredTypes["sftp_directories"]; 5146 - export type SftpDirectoriesRecord = SftpDirectories & XataRecord; 5147 - 5148 - export type SftpPath = InferredTypes["sftp_path"]; 5149 - export type SftpPathRecord = SftpPath & XataRecord; 5150 - 5151 - export type ShoutLikes = InferredTypes["shout_likes"]; 5152 - export type ShoutLikesRecord = ShoutLikes & XataRecord; 5153 - 5154 - export type ShoutReports = InferredTypes["shout_reports"]; 5155 - export type ShoutReportsRecord = ShoutReports & XataRecord; 5156 - 5157 - export type Shouts = InferredTypes["shouts"]; 5158 - export type ShoutsRecord = Shouts & XataRecord; 5159 - 5160 - export type SpotifyAccounts = InferredTypes["spotify_accounts"]; 5161 - export type SpotifyAccountsRecord = SpotifyAccounts & XataRecord; 5162 - 5163 - export type SpotifyTokens = InferredTypes["spotify_tokens"]; 5164 - export type SpotifyTokensRecord = SpotifyTokens & XataRecord; 5165 - 5166 - export type Tags = InferredTypes["tags"]; 5167 - export type TagsRecord = Tags & XataRecord; 5168 - 5169 - export type TrackTags = InferredTypes["track_tags"]; 5170 - export type TrackTagsRecord = TrackTags & XataRecord; 5171 - 5172 - export type Tracks = InferredTypes["tracks"]; 5173 - export type TracksRecord = Tracks & XataRecord; 5174 - 5175 - export type UserAlbums = InferredTypes["user_albums"]; 5176 - export type UserAlbumsRecord = UserAlbums & XataRecord; 5177 - 5178 - export type UserArtists = InferredTypes["user_artists"]; 5179 - export type UserArtistsRecord = UserArtists & XataRecord; 5180 - 5181 - export type UserPlaylists = InferredTypes["user_playlists"]; 5182 - export type UserPlaylistsRecord = UserPlaylists & XataRecord; 5183 - 5184 - export type UserTracks = InferredTypes["user_tracks"]; 5185 - export type UserTracksRecord = UserTracks & XataRecord; 5186 - 5187 - export type Users = InferredTypes["users"]; 5188 - export type UsersRecord = Users & XataRecord; 5189 - 5190 - export type Webscrobblers = InferredTypes["webscrobblers"]; 5191 - export type WebscrobblersRecord = Webscrobblers & XataRecord; 5192 - 5193 - export type DatabaseSchema = { 5194 - album_tags: AlbumTagsRecord; 5195 - album_tracks: AlbumTracksRecord; 5196 - albums: AlbumsRecord; 5197 - api_keys: ApiKeysRecord; 5198 - artist_albums: ArtistAlbumsRecord; 5199 - artist_tags: ArtistTagsRecord; 5200 - artist_tracks: ArtistTracksRecord; 5201 - artists: ArtistsRecord; 5202 - builtin_storage_paths: BuiltinStoragePathsRecord; 5203 - dropbox: DropboxRecord; 5204 - dropbox_accounts: DropboxAccountsRecord; 5205 - dropbox_directories: DropboxDirectoriesRecord; 5206 - dropbox_paths: DropboxPathsRecord; 5207 - dropbox_tokens: DropboxTokensRecord; 5208 - google_drive: GoogleDriveRecord; 5209 - google_drive_accounts: GoogleDriveAccountsRecord; 5210 - google_drive_directories: GoogleDriveDirectoriesRecord; 5211 - google_drive_paths: GoogleDrivePathsRecord; 5212 - google_drive_tokens: GoogleDriveTokensRecord; 5213 - loved_tracks: LovedTracksRecord; 5214 - playback_state: PlaybackStateRecord; 5215 - playlist_tracks: PlaylistTracksRecord; 5216 - playlists: PlaylistsRecord; 5217 - profile_shouts: ProfileShoutsRecord; 5218 - queue_tracks: QueueTracksRecord; 5219 - radios: RadiosRecord; 5220 - s3_bucket: S3BucketRecord; 5221 - s3_directories: S3DirectoriesRecord; 5222 - s3_paths: S3PathsRecord; 5223 - s3_tokens: S3TokensRecord; 5224 - scrobbles: ScrobblesRecord; 5225 - sftp: SftpRecord; 5226 - sftp_access: SftpAccessRecord; 5227 - sftp_directories: SftpDirectoriesRecord; 5228 - sftp_path: SftpPathRecord; 5229 - shout_likes: ShoutLikesRecord; 5230 - shout_reports: ShoutReportsRecord; 5231 - shouts: ShoutsRecord; 5232 - spotify_accounts: SpotifyAccountsRecord; 5233 - spotify_tokens: SpotifyTokensRecord; 5234 - tags: TagsRecord; 5235 - track_tags: TrackTagsRecord; 5236 - tracks: TracksRecord; 5237 - user_albums: UserAlbumsRecord; 5238 - user_artists: UserArtistsRecord; 5239 - user_playlists: UserPlaylistsRecord; 5240 - user_tracks: UserTracksRecord; 5241 - users: UsersRecord; 5242 - webscrobblers: WebscrobblersRecord; 5243 - }; 5244 - 5245 - const DatabaseClient = buildClient(); 5246 - 5247 - const defaultOptions = { 5248 - databaseURL: 5249 - "https://Tsiry-Sandratraina-s-workspace-b1ficn.us-east-1.xata.sh/db/rocksky", 5250 - }; 5251 - 5252 - export class XataClient extends DatabaseClient<DatabaseSchema> { 5253 - constructor(options?: BaseClientOptions) { 5254 - super({ ...defaultOptions, ...options }, tables); 5255 - } 5256 - } 5257 - 5258 - let instance: XataClient | undefined; 5259 - 5260 - export const getXataClient = () => { 5261 - if (instance) return instance; 5262 - 5263 - instance = new XataClient(); 5264 - return instance; 5265 - };
-6
bun.lock
··· 33 33 "@opentelemetry/sdk-node": "^0.200.0", 34 34 "@opentelemetry/semantic-conventions": "^1.32.0", 35 35 "@pyroscope/nodejs": "^0.4.5", 36 - "@xata.io/client": "^0.0.0-next.va121e4207b94bfe0a3c025fc00b247b923880930", 37 36 "assert": "^2.1.0", 38 37 "axios": "^1.7.9", 39 38 "better-sqlite3": "^11.8.1", ··· 327 326 "vitest": "2.1.8", 328 327 "wrangler": "^3.107.3", 329 328 }, 330 - }, 331 - "crates": { 332 - "name": "@rocksky/crates", 333 329 }, 334 330 }, 335 331 "packages": { ··· 942 938 "@rocksky/app-proxy": ["@rocksky/app-proxy@workspace:apps/app-proxy"], 943 939 944 940 "@rocksky/cli": ["@rocksky/cli@workspace:apps/cli"], 945 - 946 - "@rocksky/crates": ["@rocksky/crates@workspace:crates"], 947 941 948 942 "@rocksky/doc": ["@rocksky/doc@workspace:apps/doc"], 949 943