diff --git a/apps/api/.xata/migrations/.ledger b/apps/api/.xata/migrations/.ledger index 4979d3f9..536b7991 100644 --- a/apps/api/.xata/migrations/.ledger +++ b/apps/api/.xata/migrations/.ledger @@ -276,3 +276,44 @@ mig_d0md042r9e9qaqkp2jug mig_d0md0k2r9e9qaqkp2jvg mig_d0md0vido80pli9qp7t0 mig_d0md192r9e9qaqkp2k0g +mig_d1m1n9qoqhr2s1o6vnj0 +mig_d1m1nhrj0aqdfvuvbfa0 +mig_d1m1o0qoqhr2s1o6vnk0 +mig_d1m1ob2oqhr2s1o6vnl0 +mig_d1m1of2oqhr2s1o6vnm0 +mig_d1m1pihr27un0qigpah0 +mig_d1m1q2qoqhr2s1o6vnn0 +mig_d1m1qi2oqhr2s1o6vno0 +mig_d1m1rghr27un0qigpaj0 +mig_d1m1ssrj0aqdfvuvbfi0 +mig_d1m1t8aoqhr2s1o6vnp0 +mig_d1m1tk9r27un0qigpak0 +mig_d1m1u4bj0aqdfvuvbfl0 +mig_d1m1ucrj0aqdfvuvbfm0 +mig_d1m1ut1r27un0qigpam0 +mig_d1m1vmioqhr2s1o6vnrg +mig_d1m20hpr27un0qigpan0 +mig_d1m210ioqhr2s1o6vnsg +mig_d1m21aioqhr2s1o6vntg +mig_d1m21q9r27un0qigpao0 +mig_d1m223rj0aqdfvuvbfo0 +mig_d1m22fbj0aqdfvuvbfp0 +mig_d1m22r1r27un0qigpap0 +mig_d1m23a9r27un0qigpaq0 +mig_d1m2c02oqhr2s1o6vnv0 +mig_d1m2cthr27un0qigpavg +mig_d1m2dupr27un0qigpb0g +mig_d1m2edioqhr2s1o6vo00 +sql_70e285f209c400 +sql_027411d41b3b86 +sql_2d6fff5c5e6c78 +sql_76f1105aa6ca0f +mig_d1mb1urj0aqdfvuvbhk0 +mig_d1o1f1qoqhr2s1o705dg +sql_18cc4eb60ac890 +sql_18f83793ce7a31 +sql_45a899d3597b60 +sql_d58ea73a9fcdc5 +mig_d20ot3jj0aqdfvuvdh6g +mig_d2mlh1gbs5avv4bj7f0g +mig_d3ea7abdeq0sm5b3ib8g diff --git a/apps/api/.xata/migrations/mig_d1m1n9qoqhr2s1o6vnj0.json b/apps/api/.xata/migrations/mig_d1m1n9qoqhr2s1o6vnj0.json new file mode 100644 index 00000000..efad758c --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1n9qoqhr2s1o6vnj0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1n9qoqhr2s1o6vnj0", + "operations": [ + { + "create_table": { + "name": "google_drive_directories", + "columns": [ + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_id", + "type": "text", + "check": { + "name": "google_drive_directories_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"google_drive_directories\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"google_drive_directories\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1n9qoqhr2s1o6vnj0", + "parent": "mig_d0md192r9e9qaqkp2k0g", + "schema": "public", + "startedAt": "2025-07-07T19:10:31.893881Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1nhrj0aqdfvuvbfa0.json b/apps/api/.xata/migrations/mig_d1m1nhrj0aqdfvuvbfa0.json new file mode 100644 index 00000000..dbbe2a70 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1nhrj0aqdfvuvbfa0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1nhrj0aqdfvuvbfa0", + "operations": [ + { + "create_table": { + "name": "dropbox_directories", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "dropbox_directories_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"dropbox_directories\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"dropbox_directories\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1nhrj0aqdfvuvbfa0", + "parent": "mig_d1m1n9qoqhr2s1o6vnj0", + "schema": "public", + "startedAt": "2025-07-07T19:11:05.36521Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1o0qoqhr2s1o6vnk0.json b/apps/api/.xata/migrations/mig_d1m1o0qoqhr2s1o6vnk0.json new file mode 100644 index 00000000..0315a35b --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1o0qoqhr2s1o6vnk0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1o0qoqhr2s1o6vnk0", + "operations": [ + { + "create_table": { + "name": "s3_directories", + "columns": [ + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_id", + "type": "text", + "check": { + "name": "s3_directories_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"s3_directories\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"s3_directories\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1o0qoqhr2s1o6vnk0", + "parent": "mig_d1m1nhrj0aqdfvuvbfa0", + "schema": "public", + "startedAt": "2025-07-07T19:12:03.832402Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1ob2oqhr2s1o6vnl0.json b/apps/api/.xata/migrations/mig_d1m1ob2oqhr2s1o6vnl0.json new file mode 100644 index 00000000..486a66c9 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1ob2oqhr2s1o6vnl0.json @@ -0,0 +1,57 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1ob2oqhr2s1o6vnl0", + "operations": [ + { + "create_table": { + "name": "ftp_directories", + "columns": [ + { + "name": "xata_id", + "type": "text", + "check": { + "name": "ftp_directories_xata_id_length_xata_id", + "constraint": "length(\"xata_id\") < 256" + }, + "unique": true, + "default": "'rec_' || xata_private.xid()" + }, + { + "name": "xata_version", + "type": "integer", + "default": "0" + }, + { + "name": "xata_createdat", + "type": "timestamptz", + "default": "now()" + }, + { + "name": "xata_updatedat", + "type": "timestamptz", + "default": "now()" + } + ] + } + }, + { + "sql": { + "up": "ALTER TABLE \"ftp_directories\" REPLICA IDENTITY FULL", + "onComplete": true + } + }, + { + "sql": { + "up": "CREATE TRIGGER xata_maintain_metadata_trigger_pgroll\n BEFORE INSERT OR UPDATE\n ON \"ftp_directories\"\n FOR EACH ROW\n EXECUTE FUNCTION xata_private.maintain_metadata_trigger_pgroll()", + "onComplete": true + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1ob2oqhr2s1o6vnl0", + "parent": "mig_d1m1o0qoqhr2s1o6vnk0", + "schema": "public", + "startedAt": "2025-07-07T19:12:45.561136Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1of2oqhr2s1o6vnm0.json b/apps/api/.xata/migrations/mig_d1m1of2oqhr2s1o6vnm0.json new file mode 100644 index 00000000..250a7908 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1of2oqhr2s1o6vnm0.json @@ -0,0 +1,19 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1of2oqhr2s1o6vnm0", + "operations": [ + { + "rename_table": { + "to": "sftp_directories", + "from": "ftp_directories" + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1of2oqhr2s1o6vnm0", + "parent": "mig_d1m1ob2oqhr2s1o6vnl0", + "schema": "public", + "startedAt": "2025-07-07T19:13:01.468679Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1pihr27un0qigpah0.json b/apps/api/.xata/migrations/mig_d1m1pihr27un0qigpah0.json new file mode 100644 index 00000000..f8246d79 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1pihr27un0qigpah0.json @@ -0,0 +1,25 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1pihr27un0qigpah0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "sftp_directories", + "column": { + "name": "path", + "type": "text", + "unique": true, + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1pihr27un0qigpah0", + "parent": "mig_d1m1of2oqhr2s1o6vnm0", + "schema": "public", + "startedAt": "2025-07-07T19:15:23.144073Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1q2qoqhr2s1o6vnn0.json b/apps/api/.xata/migrations/mig_d1m1q2qoqhr2s1o6vnn0.json new file mode 100644 index 00000000..e2f613c2 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1q2qoqhr2s1o6vnn0.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1q2qoqhr2s1o6vnn0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "sftp_directories", + "column": { + "name": "name", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1q2qoqhr2s1o6vnn0", + "parent": "mig_d1m1pihr27un0qigpah0", + "schema": "public", + "startedAt": "2025-07-07T19:16:28.582367Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1qi2oqhr2s1o6vno0.json b/apps/api/.xata/migrations/mig_d1m1qi2oqhr2s1o6vno0.json new file mode 100644 index 00000000..f8efeb31 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1qi2oqhr2s1o6vno0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1qi2oqhr2s1o6vno0", + "operations": [ + { + "add_column": { + "table": "sftp_directories", + "column": { + "name": "parent_id", + "type": "text", + "comment": "{\"xata.link\":\"sftp_directories\"}", + "nullable": true, + "references": { + "name": "parent_id_link", + "table": "sftp_directories", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1qi2oqhr2s1o6vno0", + "parent": "mig_d1m1q2qoqhr2s1o6vnn0", + "schema": "public", + "startedAt": "2025-07-07T19:17:29.004821Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1rghr27un0qigpaj0.json b/apps/api/.xata/migrations/mig_d1m1rghr27un0qigpaj0.json new file mode 100644 index 00000000..721d8a5b --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1rghr27un0qigpaj0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1rghr27un0qigpaj0", + "operations": [ + { + "add_column": { + "table": "sftp_path", + "column": { + "name": "directory_id", + "type": "text", + "comment": "{\"xata.link\":\"sftp_directories\"}", + "nullable": true, + "references": { + "name": "directory_id_link", + "table": "sftp_directories", + "column": "xata_id", + "on_delete": "SET NULL" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1rghr27un0qigpaj0", + "parent": "mig_d1m1qi2oqhr2s1o6vno0", + "schema": "public", + "startedAt": "2025-07-07T19:19:31.155379Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1ssrj0aqdfvuvbfi0.json b/apps/api/.xata/migrations/mig_d1m1ssrj0aqdfvuvbfi0.json new file mode 100644 index 00000000..019cda55 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1ssrj0aqdfvuvbfi0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1ssrj0aqdfvuvbfi0", + "operations": [ + { + "add_column": { + "table": "s3_paths", + "column": { + "name": "directory_id", + "type": "text", + "comment": "{\"xata.link\":\"s3_directories\"}", + "nullable": true, + "references": { + "name": "directory_id_link", + "table": "s3_directories", + "column": "xata_id", + "on_delete": "SET NULL" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1ssrj0aqdfvuvbfi0", + "parent": "mig_d1m1rghr27un0qigpaj0", + "schema": "public", + "startedAt": "2025-07-07T19:22:28.691198Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1t8aoqhr2s1o6vnp0.json b/apps/api/.xata/migrations/mig_d1m1t8aoqhr2s1o6vnp0.json new file mode 100644 index 00000000..b5dd50bc --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1t8aoqhr2s1o6vnp0.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1t8aoqhr2s1o6vnp0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_directories", + "column": { + "name": "name", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1t8aoqhr2s1o6vnp0", + "parent": "mig_d1m1ssrj0aqdfvuvbfi0", + "schema": "public", + "startedAt": "2025-07-07T19:23:14.283412Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1tk9r27un0qigpak0.json b/apps/api/.xata/migrations/mig_d1m1tk9r27un0qigpak0.json new file mode 100644 index 00000000..9e5ca7f0 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1tk9r27un0qigpak0.json @@ -0,0 +1,25 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1tk9r27un0qigpak0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_directories", + "column": { + "name": "path", + "type": "text", + "unique": true, + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1tk9r27un0qigpak0", + "parent": "mig_d1m1t8aoqhr2s1o6vnp0", + "schema": "public", + "startedAt": "2025-07-07T19:24:02.056561Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1u4bj0aqdfvuvbfl0.json b/apps/api/.xata/migrations/mig_d1m1u4bj0aqdfvuvbfl0.json new file mode 100644 index 00000000..79467a87 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1u4bj0aqdfvuvbfl0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1u4bj0aqdfvuvbfl0", + "operations": [ + { + "add_column": { + "table": "s3_directories", + "column": { + "name": "parent_id", + "type": "text", + "comment": "{\"xata.link\":\"s3_directories\"}", + "nullable": true, + "references": { + "name": "parent_id_link", + "table": "s3_directories", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1u4bj0aqdfvuvbfl0", + "parent": "mig_d1m1tk9r27un0qigpak0", + "schema": "public", + "startedAt": "2025-07-07T19:25:06.362932Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1ucrj0aqdfvuvbfm0.json b/apps/api/.xata/migrations/mig_d1m1ucrj0aqdfvuvbfm0.json new file mode 100644 index 00000000..ba82f73b --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1ucrj0aqdfvuvbfm0.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1ucrj0aqdfvuvbfm0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive_directories", + "column": { + "name": "name", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1ucrj0aqdfvuvbfm0", + "parent": "mig_d1m1u4bj0aqdfvuvbfl0", + "schema": "public", + "startedAt": "2025-07-07T19:25:39.600856Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1ut1r27un0qigpam0.json b/apps/api/.xata/migrations/mig_d1m1ut1r27un0qigpam0.json new file mode 100644 index 00000000..42c279ac --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1ut1r27un0qigpam0.json @@ -0,0 +1,25 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1ut1r27un0qigpam0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive_directories", + "column": { + "name": "path", + "type": "text", + "unique": true, + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1ut1r27un0qigpam0", + "parent": "mig_d1m1ucrj0aqdfvuvbfm0", + "schema": "public", + "startedAt": "2025-07-07T19:26:45.356723Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m1vmioqhr2s1o6vnrg.json b/apps/api/.xata/migrations/mig_d1m1vmioqhr2s1o6vnrg.json new file mode 100644 index 00000000..53eb1856 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m1vmioqhr2s1o6vnrg.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m1vmioqhr2s1o6vnrg", + "operations": [ + { + "add_column": { + "table": "google_drive_directories", + "column": { + "name": "parent_id", + "type": "text", + "comment": "{\"xata.link\":\"google_drive_directories\"}", + "nullable": true, + "references": { + "name": "parent_id_link", + "table": "google_drive_directories", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m1vmioqhr2s1o6vnrg", + "parent": "mig_d1m1ut1r27un0qigpam0", + "schema": "public", + "startedAt": "2025-07-07T19:28:27.271223Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m20hpr27un0qigpan0.json b/apps/api/.xata/migrations/mig_d1m20hpr27un0qigpan0.json new file mode 100644 index 00000000..8c6aa5dd --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m20hpr27un0qigpan0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m20hpr27un0qigpan0", + "operations": [ + { + "add_column": { + "table": "google_drive_paths", + "column": { + "name": "directory_id", + "type": "text", + "comment": "{\"xata.link\":\"google_drive_directories\"}", + "nullable": true, + "references": { + "name": "directory_id_link", + "table": "google_drive_directories", + "column": "xata_id", + "on_delete": "SET NULL" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m20hpr27un0qigpan0", + "parent": "mig_d1m1vmioqhr2s1o6vnrg", + "schema": "public", + "startedAt": "2025-07-07T19:30:15.706553Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m210ioqhr2s1o6vnsg.json b/apps/api/.xata/migrations/mig_d1m210ioqhr2s1o6vnsg.json new file mode 100644 index 00000000..d27c80b0 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m210ioqhr2s1o6vnsg.json @@ -0,0 +1,21 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m210ioqhr2s1o6vnsg", + "operations": [ + { + "drop_constraint": { + "up": "\"path\"", + "down": "\"path\"", + "name": "google_drive_directories__pgroll_new_path_key", + "table": "google_drive_directories" + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m210ioqhr2s1o6vnsg", + "parent": "mig_d1m20hpr27un0qigpan0", + "schema": "public", + "startedAt": "2025-07-07T19:31:15.285501Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m21aioqhr2s1o6vntg.json b/apps/api/.xata/migrations/mig_d1m21aioqhr2s1o6vntg.json new file mode 100644 index 00000000..c66808ed --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m21aioqhr2s1o6vntg.json @@ -0,0 +1,21 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m21aioqhr2s1o6vntg", + "operations": [ + { + "drop_constraint": { + "up": "\"path\"", + "down": "\"path\"", + "name": "s3_directories__pgroll_new_path_key", + "table": "s3_directories" + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m21aioqhr2s1o6vntg", + "parent": "mig_d1m210ioqhr2s1o6vnsg", + "schema": "public", + "startedAt": "2025-07-07T19:31:55.444336Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m21q9r27un0qigpao0.json b/apps/api/.xata/migrations/mig_d1m21q9r27un0qigpao0.json new file mode 100644 index 00000000..bb678102 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m21q9r27un0qigpao0.json @@ -0,0 +1,21 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m21q9r27un0qigpao0", + "operations": [ + { + "drop_constraint": { + "up": "\"path\"", + "down": "\"path\"", + "name": "sftp_directories__pgroll_new_path_key", + "table": "sftp_directories" + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m21q9r27un0qigpao0", + "parent": "mig_d1m21aioqhr2s1o6vntg", + "schema": "public", + "startedAt": "2025-07-07T19:32:57.960317Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m223rj0aqdfvuvbfo0.json b/apps/api/.xata/migrations/mig_d1m223rj0aqdfvuvbfo0.json new file mode 100644 index 00000000..566aabcd --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m223rj0aqdfvuvbfo0.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m223rj0aqdfvuvbfo0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox_directories", + "column": { + "name": "name", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m223rj0aqdfvuvbfo0", + "parent": "mig_d1m21q9r27un0qigpao0", + "schema": "public", + "startedAt": "2025-07-07T19:33:36.261724Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m22fbj0aqdfvuvbfp0.json b/apps/api/.xata/migrations/mig_d1m22fbj0aqdfvuvbfp0.json new file mode 100644 index 00000000..73e915da --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m22fbj0aqdfvuvbfp0.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m22fbj0aqdfvuvbfp0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox_directories", + "column": { + "name": "path", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m22fbj0aqdfvuvbfp0", + "parent": "mig_d1m223rj0aqdfvuvbfo0", + "schema": "public", + "startedAt": "2025-07-07T19:34:23.005599Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m22r1r27un0qigpap0.json b/apps/api/.xata/migrations/mig_d1m22r1r27un0qigpap0.json new file mode 100644 index 00000000..d59b8dbf --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m22r1r27un0qigpap0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m22r1r27un0qigpap0", + "operations": [ + { + "add_column": { + "table": "dropbox_directories", + "column": { + "name": "parent_id", + "type": "text", + "comment": "{\"xata.link\":\"dropbox_directories\"}", + "nullable": true, + "references": { + "name": "parent_id_link", + "table": "dropbox_directories", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m22r1r27un0qigpap0", + "parent": "mig_d1m22fbj0aqdfvuvbfp0", + "schema": "public", + "startedAt": "2025-07-07T19:35:09.120135Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m23a9r27un0qigpaq0.json b/apps/api/.xata/migrations/mig_d1m23a9r27un0qigpaq0.json new file mode 100644 index 00000000..d62f1764 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m23a9r27un0qigpaq0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m23a9r27un0qigpaq0", + "operations": [ + { + "add_column": { + "table": "dropbox_paths", + "column": { + "name": "directory_id", + "type": "text", + "comment": "{\"xata.link\":\"dropbox_directories\"}", + "nullable": true, + "references": { + "name": "directory_id_link", + "table": "dropbox_directories", + "column": "xata_id", + "on_delete": "SET NULL" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m23a9r27un0qigpaq0", + "parent": "mig_d1m22r1r27un0qigpap0", + "schema": "public", + "startedAt": "2025-07-07T19:36:10.079937Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m2c02oqhr2s1o6vnv0.json b/apps/api/.xata/migrations/mig_d1m2c02oqhr2s1o6vnv0.json new file mode 100644 index 00000000..a30a9ccb --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m2c02oqhr2s1o6vnv0.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m2c02oqhr2s1o6vnv0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive_directories", + "column": { + "name": "google_drive_id", + "type": "text", + "comment": "{\"xata.link\":\"google_drive\"}", + "references": { + "name": "google_drive_id_link", + "table": "google_drive", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m2c02oqhr2s1o6vnv0", + "parent": "mig_d1m23a9r27un0qigpaq0", + "schema": "public", + "startedAt": "2025-07-07T19:54:41.546052Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m2cthr27un0qigpavg.json b/apps/api/.xata/migrations/mig_d1m2cthr27un0qigpavg.json new file mode 100644 index 00000000..a8be5566 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m2cthr27un0qigpavg.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m2cthr27un0qigpavg", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox_directories", + "column": { + "name": "dropbox_id", + "type": "text", + "comment": "{\"xata.link\":\"dropbox\"}", + "references": { + "name": "dropbox_id_link", + "table": "dropbox", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m2cthr27un0qigpavg", + "parent": "mig_d1m2c02oqhr2s1o6vnv0", + "schema": "public", + "startedAt": "2025-07-07T19:56:39.777931Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m2dupr27un0qigpb0g.json b/apps/api/.xata/migrations/mig_d1m2dupr27un0qigpb0g.json new file mode 100644 index 00000000..6025fab1 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m2dupr27un0qigpb0g.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m2dupr27un0qigpb0g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "s3_directories", + "column": { + "name": "s3_bucket_id", + "type": "text", + "comment": "{\"xata.link\":\"s3_bucket\"}", + "references": { + "name": "s3_bucket_id_link", + "table": "s3_bucket", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m2dupr27un0qigpb0g", + "parent": "mig_d1m2cthr27un0qigpavg", + "schema": "public", + "startedAt": "2025-07-07T19:58:51.774928Z" +} diff --git a/apps/api/.xata/migrations/mig_d1m2edioqhr2s1o6vo00.json b/apps/api/.xata/migrations/mig_d1m2edioqhr2s1o6vo00.json new file mode 100644 index 00000000..0e52348b --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1m2edioqhr2s1o6vo00.json @@ -0,0 +1,30 @@ +{ + "done": true, + "migration": { + "name": "mig_d1m2edioqhr2s1o6vo00", + "operations": [ + { + "add_column": { + "up": "''", + "table": "sftp_directories", + "column": { + "name": "sftp_id", + "type": "text", + "comment": "{\"xata.link\":\"sftp\"}", + "references": { + "name": "sftp_id_link", + "table": "sftp", + "column": "xata_id", + "on_delete": "CASCADE" + } + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1m2edioqhr2s1o6vo00", + "parent": "mig_d1m2dupr27un0qigpb0g", + "schema": "public", + "startedAt": "2025-07-07T19:59:51.387907Z" +} diff --git a/apps/api/.xata/migrations/mig_d1mb1urj0aqdfvuvbhk0.json b/apps/api/.xata/migrations/mig_d1mb1urj0aqdfvuvbhk0.json new file mode 100644 index 00000000..6135eaf8 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1mb1urj0aqdfvuvbhk0.json @@ -0,0 +1,25 @@ +{ + "done": true, + "migration": { + "name": "mig_d1mb1urj0aqdfvuvbhk0", + "operations": [ + { + "add_column": { + "up": "''", + "table": "google_drive_directories", + "column": { + "name": "file_id", + "type": "text", + "unique": true, + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1mb1urj0aqdfvuvbhk0", + "parent": "sql_76f1105aa6ca0f", + "schema": "public", + "startedAt": "2025-07-08T05:47:40.211674Z" +} diff --git a/apps/api/.xata/migrations/mig_d1o1f1qoqhr2s1o705dg.json b/apps/api/.xata/migrations/mig_d1o1f1qoqhr2s1o705dg.json new file mode 100644 index 00000000..cb662b57 --- /dev/null +++ b/apps/api/.xata/migrations/mig_d1o1f1qoqhr2s1o705dg.json @@ -0,0 +1,25 @@ +{ + "done": true, + "migration": { + "name": "mig_d1o1f1qoqhr2s1o705dg", + "operations": [ + { + "add_column": { + "up": "''", + "table": "dropbox_directories", + "column": { + "name": "file_id", + "type": "text", + "unique": true, + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d1o1f1qoqhr2s1o705dg", + "parent": "mig_d1mb1urj0aqdfvuvbhk0", + "schema": "public", + "startedAt": "2025-07-10T19:42:00.932588Z" +} diff --git a/apps/api/.xata/migrations/mig_d20ot3jj0aqdfvuvdh6g.json b/apps/api/.xata/migrations/mig_d20ot3jj0aqdfvuvdh6g.json new file mode 100644 index 00000000..a1f61cbb --- /dev/null +++ b/apps/api/.xata/migrations/mig_d20ot3jj0aqdfvuvdh6g.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_d20ot3jj0aqdfvuvdh6g", + "operations": [ + { + "add_column": { + "up": "''", + "table": "queue_tracks", + "column": { + "name": "file_uri", + "type": "text", + "comment": "" + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d20ot3jj0aqdfvuvdh6g", + "parent": "sql_d58ea73a9fcdc5", + "schema": "public", + "startedAt": "2025-07-24T01:38:23.467364Z" +} diff --git a/apps/api/.xata/migrations/mig_d2mlh1gbs5avv4bj7f0g.json b/apps/api/.xata/migrations/mig_d2mlh1gbs5avv4bj7f0g.json new file mode 100644 index 00000000..152af73d --- /dev/null +++ b/apps/api/.xata/migrations/mig_d2mlh1gbs5avv4bj7f0g.json @@ -0,0 +1,21 @@ +{ + "done": true, + "migration": { + "name": "mig_d2mlh1gbs5avv4bj7f0g", + "operations": [ + { + "drop_constraint": { + "up": "\"name\"", + "down": "\"name\"", + "name": "api_keys_name_unique", + "table": "api_keys" + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d2mlh1gbs5avv4bj7f0g", + "parent": "mig_d20ot3jj0aqdfvuvdh6g", + "schema": "public", + "startedAt": "2025-08-26T06:47:35.046302Z" +} diff --git a/apps/api/.xata/migrations/mig_d3ea7abdeq0sm5b3ib8g.json b/apps/api/.xata/migrations/mig_d3ea7abdeq0sm5b3ib8g.json new file mode 100644 index 00000000..7145b94d --- /dev/null +++ b/apps/api/.xata/migrations/mig_d3ea7abdeq0sm5b3ib8g.json @@ -0,0 +1,24 @@ +{ + "done": true, + "migration": { + "name": "mig_d3ea7abdeq0sm5b3ib8g", + "operations": [ + { + "add_column": { + "table": "artists", + "column": { + "name": "genres", + "type": "text[]", + "comment": "", + "nullable": true + } + } + } + ] + }, + "migrationType": "pgroll", + "name": "mig_d3ea7abdeq0sm5b3ib8g", + "parent": "mig_d2mlh1gbs5avv4bj7f0g", + "schema": "public", + "startedAt": "2025-10-01T03:44:42.977368Z" +} diff --git a/apps/api/.xata/migrations/sql_027411d41b3b86.json b/apps/api/.xata/migrations/sql_027411d41b3b86.json new file mode 100644 index 00000000..72786037 --- /dev/null +++ b/apps/api/.xata/migrations/sql_027411d41b3b86.json @@ -0,0 +1,18 @@ +{ + "done": true, + "migration": { + "name": "sql_027411d41b3b86", + "operations": [ + { + "sql": { + "up": "CREATE UNIQUE INDEX unique_dropbox_path ON dropbox_directories USING btree (dropbox_id, path)" + } + } + ] + }, + "migrationType": "inferred", + "name": "sql_027411d41b3b86", + "parent": "sql_70e285f209c400", + "schema": "public", + "startedAt": "2025-07-07T20:30:25.496512Z" +} diff --git a/apps/api/.xata/migrations/sql_18cc4eb60ac890.json b/apps/api/.xata/migrations/sql_18cc4eb60ac890.json new file mode 100644 index 00000000..3eacbed7 --- /dev/null +++ b/apps/api/.xata/migrations/sql_18cc4eb60ac890.json @@ -0,0 +1,18 @@ +{ + "done": true, + "migration": { + "name": "sql_18cc4eb60ac890", + "operations": [ + { + "sql": { + "up": "CREATE INDEX idx_dropbox_directories_path ON dropbox_directories USING btree (path)" + } + } + ] + }, + "migrationType": "inferred", + "name": "sql_18cc4eb60ac890", + "parent": "mig_d1o1f1qoqhr2s1o705dg", + "schema": "public", + "startedAt": "2025-07-13T03:39:39.136028Z" +} diff --git a/apps/api/.xata/migrations/sql_18f83793ce7a31.json b/apps/api/.xata/migrations/sql_18f83793ce7a31.json new file mode 100644 index 00000000..5e71f3ec --- /dev/null +++ b/apps/api/.xata/migrations/sql_18f83793ce7a31.json @@ -0,0 +1,18 @@ +{ + "done": true, + "migration": { + "name": "sql_18f83793ce7a31", + "operations": [ + { + "sql": { + "up": "CREATE INDEX idx_google_drive_directories_path ON google_drive_directories USING btree (path)" + } + } + ] + }, + "migrationType": "inferred", + "name": "sql_18f83793ce7a31", + "parent": "sql_18cc4eb60ac890", + "schema": "public", + "startedAt": "2025-07-13T03:40:29.714531Z" +} diff --git a/apps/api/.xata/migrations/sql_2d6fff5c5e6c78.json b/apps/api/.xata/migrations/sql_2d6fff5c5e6c78.json new file mode 100644 index 00000000..589c628b --- /dev/null +++ b/apps/api/.xata/migrations/sql_2d6fff5c5e6c78.json @@ -0,0 +1,18 @@ +{ + "done": true, + "migration": { + "name": "sql_2d6fff5c5e6c78", + "operations": [ + { + "sql": { + "up": "CREATE UNIQUE INDEX unique_s3_path ON s3_directories USING btree (s3_bucket_id, path)" + } + } + ] + }, + "migrationType": "inferred", + "name": "sql_2d6fff5c5e6c78", + "parent": "sql_027411d41b3b86", + "schema": "public", + "startedAt": "2025-07-07T20:31:21.125842Z" +} diff --git a/apps/api/.xata/migrations/sql_45a899d3597b60.json b/apps/api/.xata/migrations/sql_45a899d3597b60.json new file mode 100644 index 00000000..8ec60ff9 --- /dev/null +++ b/apps/api/.xata/migrations/sql_45a899d3597b60.json @@ -0,0 +1,18 @@ +{ + "done": true, + "migration": { + "name": "sql_45a899d3597b60", + "operations": [ + { + "sql": { + "up": "CREATE INDEX idx_google_drive_directories_file_id ON google_drive_directories USING btree (file_id)" + } + } + ] + }, + "migrationType": "inferred", + "name": "sql_45a899d3597b60", + "parent": "sql_18f83793ce7a31", + "schema": "public", + "startedAt": "2025-07-14T07:26:40.176048Z" +} diff --git a/apps/api/.xata/migrations/sql_70e285f209c400.json b/apps/api/.xata/migrations/sql_70e285f209c400.json new file mode 100644 index 00000000..f2a7affc --- /dev/null +++ b/apps/api/.xata/migrations/sql_70e285f209c400.json @@ -0,0 +1,18 @@ +{ + "done": true, + "migration": { + "name": "sql_70e285f209c400", + "operations": [ + { + "sql": { + "up": "CREATE UNIQUE INDEX unique_drive_path ON google_drive_directories USING btree (google_drive_id, path)" + } + } + ] + }, + "migrationType": "inferred", + "name": "sql_70e285f209c400", + "parent": "mig_d1m2edioqhr2s1o6vo00", + "schema": "public", + "startedAt": "2025-07-07T20:29:49.116223Z" +} diff --git a/apps/api/.xata/migrations/sql_76f1105aa6ca0f.json b/apps/api/.xata/migrations/sql_76f1105aa6ca0f.json new file mode 100644 index 00000000..8ba32187 --- /dev/null +++ b/apps/api/.xata/migrations/sql_76f1105aa6ca0f.json @@ -0,0 +1,18 @@ +{ + "done": true, + "migration": { + "name": "sql_76f1105aa6ca0f", + "operations": [ + { + "sql": { + "up": "CREATE UNIQUE INDEX unique_sftp_path ON sftp_directories USING btree (sftp_id, path)" + } + } + ] + }, + "migrationType": "inferred", + "name": "sql_76f1105aa6ca0f", + "parent": "sql_2d6fff5c5e6c78", + "schema": "public", + "startedAt": "2025-07-07T20:31:54.518491Z" +} diff --git a/apps/api/.xata/migrations/sql_d58ea73a9fcdc5.json b/apps/api/.xata/migrations/sql_d58ea73a9fcdc5.json new file mode 100644 index 00000000..73e2baea --- /dev/null +++ b/apps/api/.xata/migrations/sql_d58ea73a9fcdc5.json @@ -0,0 +1,18 @@ +{ + "done": true, + "migration": { + "name": "sql_d58ea73a9fcdc5", + "operations": [ + { + "sql": { + "up": "CREATE INDEX idx_dropbox_directories_file_id ON dropbox_directories USING btree (file_id)" + } + } + ] + }, + "migrationType": "inferred", + "name": "sql_d58ea73a9fcdc5", + "parent": "sql_45a899d3597b60", + "schema": "public", + "startedAt": "2025-07-14T07:27:17.581342Z" +} diff --git a/apps/api/src/nowplaying/nowplaying.service.ts b/apps/api/src/nowplaying/nowplaying.service.ts index d915c105..317fbaaf 100644 --- a/apps/api/src/nowplaying/nowplaying.service.ts +++ b/apps/api/src/nowplaying/nowplaying.service.ts @@ -22,10 +22,12 @@ export async function putArtistRecord( name: string; createdAt: string; picture?: BlobRef; + tags?: string[]; } = { $type: "app.rocksky.artist", name: track.albumArtist, createdAt: new Date().toISOString(), + tags: track.genres, }; if (track.artistPicture) { @@ -162,6 +164,7 @@ export async function putSongRecord( : undefined, createdAt: new Date().toISOString(), spotifyLink: track.spotifyLink ? track.spotifyLink : undefined, + tags: track.genres, }; if (!Song.validateRecord(record).success) { diff --git a/apps/api/src/schema/artists.ts b/apps/api/src/schema/artists.ts index fc2dda1e..9220aaef 100644 --- a/apps/api/src/schema/artists.ts +++ b/apps/api/src/schema/artists.ts @@ -15,6 +15,7 @@ const artists = pgTable("artists", { spotifyLink: text("spotify_link"), tidalLink: text("tidal_link"), youtubeLink: text("youtube_link"), + genres: text("genres").array(), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), xataVersion: integer("xata_version"), diff --git a/apps/api/src/types/track.ts b/apps/api/src/types/track.ts index 678d7b9e..0eb52946 100644 --- a/apps/api/src/types/track.ts +++ b/apps/api/src/types/track.ts @@ -30,6 +30,7 @@ export const trackSchema = z.object({ appleMusicLink: z.string().optional().nullable(), deezerLink: z.string().optional().nullable(), timestamp: z.number().optional().nullable(), + genres: z.array(z.string()).optional().nullable(), }); export type Track = z.infer; diff --git a/apps/api/src/xata.ts b/apps/api/src/xata.ts index 350c3ef4..894a7915 100644 --- a/apps/api/src/xata.ts +++ b/apps/api/src/xata.ts @@ -381,7 +381,6 @@ const tables = [ name: "api_keys__pgroll_new_shared_secret_key", columns: ["shared_secret"], }, - api_keys_name_unique: { name: "api_keys_name_unique", columns: ["name"] }, }, columns: [ { @@ -412,7 +411,7 @@ const tables = [ name: "name", type: "text", notNull: true, - unique: true, + unique: false, defaultValue: null, comment: "", }, @@ -804,6 +803,14 @@ const tables = [ defaultValue: null, comment: "", }, + { + name: "genres", + type: "multiple", + notNull: false, + unique: false, + defaultValue: null, + comment: "", + }, { name: "lastfm_link", type: "text", @@ -1173,6 +1180,119 @@ const tables = [ }, ], }, + { + name: "dropbox_directories", + checkConstraints: { + dropbox_directories_xata_id_length_xata_id: { + name: "dropbox_directories_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + dropbox_id_link: { + name: "dropbox_id_link", + columns: ["dropbox_id"], + referencedTable: "dropbox", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + parent_id_link: { + name: "parent_id_link", + columns: ["parent_id"], + referencedTable: "dropbox_directories", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_dropbox_directories_xata_id_key: { + name: "_pgroll_new_dropbox_directories_xata_id_key", + columns: ["xata_id"], + }, + dropbox_directories__pgroll_new_file_id_key: { + name: "dropbox_directories__pgroll_new_file_id_key", + columns: ["file_id"], + }, + }, + columns: [ + { + name: "dropbox_id", + type: "link", + link: { table: "dropbox" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"dropbox"}', + }, + { + name: "file_id", + type: "text", + notNull: true, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "name", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "parent_id", + type: "link", + link: { table: "dropbox_directories" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"dropbox_directories"}', + }, + { + name: "path", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, { name: "dropbox_paths", checkConstraints: { @@ -1183,6 +1303,13 @@ const tables = [ }, }, foreignKeys: { + directory_id_link: { + name: "directory_id_link", + columns: ["directory_id"], + referencedTable: "dropbox_directories", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, dropbox_id_link: { name: "dropbox_id_link", columns: ["dropbox_id"], @@ -1210,6 +1337,15 @@ const tables = [ }, }, columns: [ + { + name: "directory_id", + type: "link", + link: { table: "dropbox_directories" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"dropbox_directories"}', + }, { name: "dropbox_id", type: "link", @@ -1520,6 +1656,119 @@ const tables = [ }, ], }, + { + name: "google_drive_directories", + checkConstraints: { + google_drive_directories_xata_id_length_xata_id: { + name: "google_drive_directories_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + google_drive_id_link: { + name: "google_drive_id_link", + columns: ["google_drive_id"], + referencedTable: "google_drive", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + parent_id_link: { + name: "parent_id_link", + columns: ["parent_id"], + referencedTable: "google_drive_directories", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_google_drive_directories_xata_id_key: { + name: "_pgroll_new_google_drive_directories_xata_id_key", + columns: ["xata_id"], + }, + google_drive_directories__pgroll_new_file_id_key: { + name: "google_drive_directories__pgroll_new_file_id_key", + columns: ["file_id"], + }, + }, + columns: [ + { + name: "file_id", + type: "text", + notNull: true, + unique: true, + defaultValue: null, + comment: "", + }, + { + name: "google_drive_id", + type: "link", + link: { table: "google_drive" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"google_drive"}', + }, + { + name: "name", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "parent_id", + type: "link", + link: { table: "google_drive_directories" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"google_drive_directories"}', + }, + { + name: "path", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, { name: "google_drive_paths", checkConstraints: { @@ -1530,6 +1779,13 @@ const tables = [ }, }, foreignKeys: { + directory_id_link: { + name: "directory_id_link", + columns: ["directory_id"], + referencedTable: "google_drive_directories", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, google_drive_id_link: { name: "google_drive_id_link", columns: ["google_drive_id"], @@ -1557,6 +1813,15 @@ const tables = [ }, }, columns: [ + { + name: "directory_id", + type: "link", + link: { table: "google_drive_directories" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"google_drive_directories"}', + }, { name: "file_id", type: "text", @@ -2227,6 +2492,14 @@ const tables = [ }, }, columns: [ + { + name: "file_uri", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, { name: "position", type: "int", @@ -2496,6 +2769,107 @@ const tables = [ }, ], }, + { + name: "s3_directories", + checkConstraints: { + s3_directories_xata_id_length_xata_id: { + name: "s3_directories_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + parent_id_link: { + name: "parent_id_link", + columns: ["parent_id"], + referencedTable: "s3_directories", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + s3_bucket_id_link: { + name: "s3_bucket_id_link", + columns: ["s3_bucket_id"], + referencedTable: "s3_bucket", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_s3_directories_xata_id_key: { + name: "_pgroll_new_s3_directories_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ + { + name: "name", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "parent_id", + type: "link", + link: { table: "s3_directories" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"s3_directories"}', + }, + { + name: "path", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "s3_bucket_id", + type: "link", + link: { table: "s3_bucket" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"s3_bucket"}', + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, { name: "s3_paths", checkConstraints: { @@ -2506,6 +2880,13 @@ const tables = [ }, }, foreignKeys: { + directory_id_link: { + name: "directory_id_link", + columns: ["directory_id"], + referencedTable: "s3_directories", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, s3_bucket_id_link: { name: "s3_bucket_id_link", columns: ["s3_bucket_id"], @@ -2529,6 +2910,15 @@ const tables = [ }, }, columns: [ + { + name: "directory_id", + type: "link", + link: { table: "s3_directories" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"s3_directories"}', + }, { name: "name", type: "text", @@ -2903,6 +3293,107 @@ const tables = [ }, ], }, + { + name: "sftp_directories", + checkConstraints: { + ftp_directories_xata_id_length_xata_id: { + name: "ftp_directories_xata_id_length_xata_id", + columns: ["xata_id"], + definition: "CHECK ((length(xata_id) < 256))", + }, + }, + foreignKeys: { + parent_id_link: { + name: "parent_id_link", + columns: ["parent_id"], + referencedTable: "sftp_directories", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + sftp_id_link: { + name: "sftp_id_link", + columns: ["sftp_id"], + referencedTable: "sftp", + referencedColumns: ["xata_id"], + onDelete: "CASCADE", + }, + }, + primaryKey: [], + uniqueConstraints: { + _pgroll_new_ftp_directories_xata_id_key: { + name: "_pgroll_new_ftp_directories_xata_id_key", + columns: ["xata_id"], + }, + }, + columns: [ + { + name: "name", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "parent_id", + type: "link", + link: { table: "sftp_directories" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"sftp_directories"}', + }, + { + name: "path", + type: "text", + notNull: true, + unique: false, + defaultValue: null, + comment: "", + }, + { + name: "sftp_id", + type: "link", + link: { table: "sftp" }, + notNull: true, + unique: false, + defaultValue: null, + comment: '{"xata.link":"sftp"}', + }, + { + name: "xata_createdat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_id", + type: "text", + notNull: true, + unique: true, + defaultValue: "('rec_'::text || (xata_private.xid())::text)", + comment: "", + }, + { + name: "xata_updatedat", + type: "datetime", + notNull: true, + unique: false, + defaultValue: "now()", + comment: "", + }, + { + name: "xata_version", + type: "int", + notNull: true, + unique: false, + defaultValue: "0", + comment: "", + }, + ], + }, { name: "sftp_path", checkConstraints: { @@ -2913,6 +3404,13 @@ const tables = [ }, }, foreignKeys: { + directory_id_link: { + name: "directory_id_link", + columns: ["directory_id"], + referencedTable: "sftp_directories", + referencedColumns: ["xata_id"], + onDelete: "SET NULL", + }, sftp_id_link: { name: "sftp_id_link", columns: ["sftp_id"], @@ -2936,6 +3434,15 @@ const tables = [ }, }, columns: [ + { + name: "directory_id", + type: "link", + link: { table: "sftp_directories" }, + notNull: false, + unique: false, + defaultValue: null, + comment: '{"xata.link":"sftp_directories"}', + }, { name: "name", type: "text", @@ -4569,6 +5076,9 @@ export type DropboxRecord = Dropbox & XataRecord; export type DropboxAccounts = InferredTypes["dropbox_accounts"]; export type DropboxAccountsRecord = DropboxAccounts & XataRecord; +export type DropboxDirectories = InferredTypes["dropbox_directories"]; +export type DropboxDirectoriesRecord = DropboxDirectories & XataRecord; + export type DropboxPaths = InferredTypes["dropbox_paths"]; export type DropboxPathsRecord = DropboxPaths & XataRecord; @@ -4581,6 +5091,9 @@ export type GoogleDriveRecord = GoogleDrive & XataRecord; export type GoogleDriveAccounts = InferredTypes["google_drive_accounts"]; export type GoogleDriveAccountsRecord = GoogleDriveAccounts & XataRecord; +export type GoogleDriveDirectories = InferredTypes["google_drive_directories"]; +export type GoogleDriveDirectoriesRecord = GoogleDriveDirectories & XataRecord; + export type GoogleDrivePaths = InferredTypes["google_drive_paths"]; export type GoogleDrivePathsRecord = GoogleDrivePaths & XataRecord; @@ -4611,6 +5124,9 @@ export type RadiosRecord = Radios & XataRecord; export type S3Bucket = InferredTypes["s3_bucket"]; export type S3BucketRecord = S3Bucket & XataRecord; +export type S3Directories = InferredTypes["s3_directories"]; +export type S3DirectoriesRecord = S3Directories & XataRecord; + export type S3Paths = InferredTypes["s3_paths"]; export type S3PathsRecord = S3Paths & XataRecord; @@ -4626,6 +5142,9 @@ export type SftpRecord = Sftp & XataRecord; export type SftpAccess = InferredTypes["sftp_access"]; export type SftpAccessRecord = SftpAccess & XataRecord; +export type SftpDirectories = InferredTypes["sftp_directories"]; +export type SftpDirectoriesRecord = SftpDirectories & XataRecord; + export type SftpPath = InferredTypes["sftp_path"]; export type SftpPathRecord = SftpPath & XataRecord; @@ -4683,10 +5202,12 @@ export type DatabaseSchema = { builtin_storage_paths: BuiltinStoragePathsRecord; dropbox: DropboxRecord; dropbox_accounts: DropboxAccountsRecord; + dropbox_directories: DropboxDirectoriesRecord; dropbox_paths: DropboxPathsRecord; dropbox_tokens: DropboxTokensRecord; google_drive: GoogleDriveRecord; google_drive_accounts: GoogleDriveAccountsRecord; + google_drive_directories: GoogleDriveDirectoriesRecord; google_drive_paths: GoogleDrivePathsRecord; google_drive_tokens: GoogleDriveTokensRecord; loved_tracks: LovedTracksRecord; @@ -4697,11 +5218,13 @@ export type DatabaseSchema = { queue_tracks: QueueTracksRecord; radios: RadiosRecord; s3_bucket: S3BucketRecord; + s3_directories: S3DirectoriesRecord; s3_paths: S3PathsRecord; s3_tokens: S3TokensRecord; scrobbles: ScrobblesRecord; sftp: SftpRecord; sftp_access: SftpAccessRecord; + sftp_directories: SftpDirectoriesRecord; sftp_path: SftpPathRecord; shout_likes: ShoutLikesRecord; shout_reports: ShoutReportsRecord; @@ -4732,7 +5255,7 @@ export class XataClient extends DatabaseClient { } } -let instance: XataClient | undefined; +let instance: XataClient | undefined = undefined; export const getXataClient = () => { if (instance) return instance; diff --git a/apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts b/apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts index 59b581d1..3ac00fe2 100644 --- a/apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts +++ b/apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts @@ -38,9 +38,9 @@ export default function (server: Server, ctx: Context) { pipe( scrobbleTrack(ctx, track, agent, did), Effect.tap(() => - Effect.logInfo(`Scrobble created for ${chalk.cyan(track.title)}`), - ), - ), + Effect.logInfo(`Scrobble created for ${chalk.cyan(track.title)}`) + ) + ) ), Effect.flatMap(presentation), Effect.retry({ times: 3 }), @@ -48,7 +48,7 @@ export default function (server: Server, ctx: Context) { Effect.catchAll((err) => { console.error(err); return Effect.succeed({}); - }), + }) ); server.app.rocksky.scrobble.createScrobble({ auth: ctx.authVerifier, @@ -82,11 +82,11 @@ const withAgent = ({ ctx, did, input, - })), + })) ), Match.orElse(() => { throw new Error("Authentication required to create a scrobble"); - }), + }) ), catch: (error) => new Error(`Failed to create agent: ${error}`), }); @@ -138,23 +138,23 @@ const uploadImage = (url: string, agent: Agent) => Effect.flatMap(([imageBuffer, options]) => pipe( Effect.tryPromise(() => agent.uploadBlob(imageBuffer, options)), - Effect.map((uploadResponse) => uploadResponse.data.blob), - ), + Effect.map((uploadResponse) => uploadResponse.data.blob) + ) ), - Effect.catchAll(() => Effect.succeed(undefined as BlobRef | undefined)), + Effect.catchAll(() => Effect.succeed(undefined as BlobRef | undefined)) ); const putRecord = ( agent: Agent, collection: string, record: T, - validate: (record: T) => { success: boolean }, + validate: (record: T) => { success: boolean } ) => pipe( Effect.succeed(record), Effect.filterOrFail( (rec) => validate(rec).success, - () => new Error("Invalid record"), + () => new Error("Invalid record") ), Effect.flatMap(() => pipe( @@ -167,19 +167,19 @@ const putRecord = ( rkey, record, validate: false, - }), - ), + }) + ) ), Effect.tap((res) => - Effect.logInfo(`Record created at ${res.data.uri}`), + Effect.logInfo(`Record created at ${res.data.uri}`) ), - Effect.map((res) => res.data.uri), - ), + Effect.map((res) => res.data.uri) + ) ), Effect.catchAll((error) => { console.error(`Error creating ${collection} record`, error); return Effect.succeed(null); - }), + }) ); const putArtistRecord = (track: Track, agent: Agent) => @@ -192,10 +192,11 @@ const putArtistRecord = (track: Track, agent: Agent) => name: track.albumArtist, createdAt: new Date().toISOString(), picture, + tags: track.genres, })), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.artist", record, Artist.validateRecord), - ), + putRecord(agent, "app.rocksky.artist", record, Artist.validateRecord) + ) ); const putAlbumRecord = (track: Track, agent: Agent) => @@ -203,9 +204,9 @@ const putAlbumRecord = (track: Track, agent: Agent) => Match.value(track.albumArt).pipe( Match.when( (url) => !!url, - (url) => uploadImage(url, agent), + (url) => uploadImage(url, agent) ), - Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)), + Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)) ), Effect.map((albumArt) => ({ $type: "app.rocksky.album", @@ -219,8 +220,8 @@ const putAlbumRecord = (track: Track, agent: Agent) => albumArt, })), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.album", record, Album.validateRecord), - ), + putRecord(agent, "app.rocksky.album", record, Album.validateRecord) + ) ); const putSongRecord = (track: Track, agent: Agent) => @@ -228,9 +229,9 @@ const putSongRecord = (track: Track, agent: Agent) => Match.value(track.albumArt).pipe( Match.when( (url) => !!url, - (url) => uploadImage(url, agent), + (url) => uploadImage(url, agent) ), - Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)), + Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)) ), Effect.map((albumArt) => ({ $type: "app.rocksky.song", @@ -253,8 +254,8 @@ const putSongRecord = (track: Track, agent: Agent) => spotifyLink: track.spotifyLink ?? undefined, })), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.song", record, Song.validateRecord), - ), + putRecord(agent, "app.rocksky.song", record, Song.validateRecord) + ) ); const putScrobbleRecord = (track: Track, agent: Agent) => @@ -262,9 +263,9 @@ const putScrobbleRecord = (track: Track, agent: Agent) => Match.value(track.albumArt).pipe( Match.when( (url) => !!url, - (url) => uploadImage(url, agent), + (url) => uploadImage(url, agent) ), - Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)), + Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)) ), Effect.map((albumArt) => ({ $type: "app.rocksky.scrobble", @@ -289,8 +290,8 @@ const putScrobbleRecord = (track: Track, agent: Agent) => spotifyLink: track.spotifyLink ?? undefined, })), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.scrobble", record, Scrobble.validateRecord), - ), + putRecord(agent, "app.rocksky.scrobble", record, Scrobble.validateRecord) + ) ); const getScrobble = ({ ctx, id }: { ctx: Context; id: string }) => @@ -302,12 +303,12 @@ const getScrobble = ({ ctx, id }: { ctx: Context; id: string }) => .leftJoin(tables.albums, eq(tables.albums.id, tables.scrobbles.albumId)) .leftJoin( tables.artists, - eq(tables.artists.id, tables.scrobbles.artistId), + eq(tables.artists.id, tables.scrobbles.artistId) ) .leftJoin(tables.users, eq(tables.users.id, tables.scrobbles.userId)) .where(eq(tables.scrobbles.id, id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getUserAlbum = ( @@ -317,7 +318,7 @@ const getUserAlbum = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -325,7 +326,7 @@ const getUserAlbum = ( .from(tables.userAlbums) .where(eq(tables.userAlbums.albumId, scrobble.albums.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getUserArtist = ( @@ -335,7 +336,7 @@ const getUserArtist = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -343,7 +344,7 @@ const getUserArtist = ( .from(tables.userArtists) .where(eq(tables.userArtists.id, scrobble.artists.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getUserTrack = ( @@ -353,7 +354,7 @@ const getUserTrack = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -361,7 +362,7 @@ const getUserTrack = ( .from(tables.userTracks) .where(eq(tables.userTracks.id, scrobble.tracks.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getAlbumTrack = ( @@ -371,7 +372,7 @@ const getAlbumTrack = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -379,7 +380,7 @@ const getAlbumTrack = ( .from(tables.albumTracks) .where(eq(tables.albumTracks.trackId, scrobble.tracks.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getArtistTrack = ( @@ -389,7 +390,7 @@ const getArtistTrack = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -397,7 +398,7 @@ const getArtistTrack = ( .from(tables.artistTracks) .where(eq(tables.artistTracks.trackId, scrobble.tracks.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getArtistAlbum = ( @@ -407,7 +408,7 @@ const getArtistAlbum = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -416,10 +417,10 @@ const getArtistAlbum = ( .where( and( eq(tables.artistAlbums.albumId, scrobble.albums.id), - eq(tables.artistAlbums.artistId, scrobble.artists.id), - ), + eq(tables.artistAlbums.artistId, scrobble.artists.id) + ) ) - .then(([row]) => row), + .then(([row]) => row) ); const createUserArtist = ( @@ -429,7 +430,7 @@ const createUserArtist = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => pipe( Effect.tryPromise(() => @@ -441,7 +442,7 @@ const createUserArtist = ( uri: scrobble.artists.uri, scrobbles: 1, } as InsertUserArtist) - .execute(), + .execute() ), Effect.flatMap(() => Effect.tryPromise(() => @@ -450,9 +451,9 @@ const createUserArtist = ( .from(tables.userArtists) .where(eq(tables.userArtists.artistId, scrobble.artists.id)) .execute() - .then(([row]) => row), - ), - ), + .then(([row]) => row) + ) + ) ); const createUserAlbum = ( @@ -462,7 +463,7 @@ const createUserAlbum = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => pipe( Effect.tryPromise(() => @@ -474,7 +475,7 @@ const createUserAlbum = ( uri: scrobble.albums.uri, scrobbles: 1, } as InsertUserAlbum) - .execute(), + .execute() ), Effect.flatMap(() => Effect.tryPromise(() => @@ -483,9 +484,9 @@ const createUserAlbum = ( .from(tables.userAlbums) .where(eq(tables.userAlbums.albumId, scrobble.albums.id)) .execute() - .then(([row]) => row), - ), - ), + .then(([row]) => row) + ) + ) ); const createUserTrack = ( @@ -495,7 +496,7 @@ const createUserTrack = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => pipe( Effect.tryPromise(() => @@ -507,7 +508,7 @@ const createUserTrack = ( uri: scrobble.tracks.uri, scrobbles: 1, } as InsertUserTrack) - .execute(), + .execute() ), Effect.flatMap(() => Effect.tryPromise(() => @@ -515,9 +516,9 @@ const createUserTrack = ( .select() .from(tables.userTracks) .where(eq(tables.userTracks.trackId, scrobble.tracks.id)) - .then(([row]) => row), - ), - ), + .then(([row]) => row) + ) + ) ); const publishScrobble = (ctx: Context, id: string) => @@ -688,50 +689,50 @@ const publishScrobble = (ctx: Context, id: string) => xata_updatedat: artistAlbum.updatedAt.toISOString(), xata_version: artistAlbum.xataVersion, }, - }), - ), - ), - ), - ), + }) + ) + ) + ) + ) ), Effect.flatMap((data) => Effect.try(() => ctx.nc.publish( "rocksky.scrobble", Buffer.from( - JSON.stringify(data).replaceAll("sha_256", "sha256"), - ), - ), - ), - ), - ), - ), - ), - ), + JSON.stringify(data).replaceAll("sha_256", "sha256") + ) + ) + ) + ) + ) + ) + ) + ) ); const computeTrackHash = (track: Track): Effect.Effect => Effect.succeed( createHash("sha256") .update(`${track.title} - ${track.artist} - ${track.album}`.toLowerCase()) - .digest("hex"), + .digest("hex") ); const computeAlbumHash = (track: Track): Effect.Effect => Effect.succeed( createHash("sha256") .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) - .digest("hex"), + .digest("hex") ); const computeArtistHash = (track: Track): Effect.Effect => Effect.succeed( - createHash("sha256").update(track.albumArtist.toLowerCase()).digest("hex"), + createHash("sha256").update(track.albumArtist.toLowerCase()).digest("hex") ); const fetchExistingTrack = ( ctx: Context, - trackHash: string, + trackHash: string ): Effect.Effect => Effect.tryPromise(() => ctx.db @@ -739,14 +740,14 @@ const fetchExistingTrack = ( .from(tables.tracks) .where(eq(tables.tracks.sha256, trackHash)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); // Update track metadata (album_uri and artist_uri) const updateTrackMetadata = ( ctx: Context, track: Track, - trackRecord: SelectTrack, + trackRecord: SelectTrack ) => pipe( Effect.succeed(trackRecord), @@ -761,8 +762,8 @@ const updateTrackMetadata = ( .from(tables.albums) .where(eq(tables.albums.sha256, albumHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((album) => album @@ -773,12 +774,12 @@ const updateTrackMetadata = ( albumUri: album.uri, }) .where(eq(tables.tracks.id, trackRecord.id)) - .execute(), + .execute() ) - : Effect.succeed(undefined), - ), + : Effect.succeed(undefined) + ) ) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), Effect.tap((trackRecord) => !trackRecord.artistUri @@ -791,8 +792,8 @@ const updateTrackMetadata = ( .from(tables.artists) .where(eq(tables.artists.sha256, artistHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((artist) => artist @@ -803,13 +804,13 @@ const updateTrackMetadata = ( artistUri: artist.uri, }) .where(eq(tables.tracks.id, trackRecord.id)) - .execute(), + .execute() ) - : Effect.succeed(undefined), - ), + : Effect.succeed(undefined) + ) ) - : Effect.succeed(undefined), - ), + : Effect.succeed(undefined) + ) ); // Ensure track exists or create it @@ -818,7 +819,7 @@ const ensureTrack = ( track: Track, agent: Agent, userDid: string, - existingTrack: SelectTrack | undefined, + existingTrack: SelectTrack | undefined ) => pipe( Effect.succeed(existingTrack), @@ -826,10 +827,10 @@ const ensureTrack = ( Match.value(trackOpt).pipe( Match.when( (value) => !!value, - () => updateTrackMetadata(ctx, track, trackOpt), + () => updateTrackMetadata(ctx, track, trackOpt) ), - Match.orElse(() => Effect.succeed(undefined)), - ), + Match.orElse(() => Effect.succeed(undefined)) + ) ), Effect.flatMap((trackOpt) => pipe( @@ -839,29 +840,29 @@ const ensureTrack = ( .from(tables.userTracks) .leftJoin( tables.tracks, - eq(tables.userTracks.trackId, tables.tracks.id), + eq(tables.userTracks.trackId, tables.tracks.id) ) .leftJoin( tables.users, - eq(tables.userTracks.userId, tables.users.id), + eq(tables.userTracks.userId, tables.users.id) ) .where( and( eq(tables.tracks.id, trackOpt?.id), - eq(tables.users.did, userDid), - ), + eq(tables.users.did, userDid) + ) ) .execute() - .then(([row]) => row.user_tracks), + .then(([row]) => row.user_tracks) ), Effect.flatMap((userTrack) => Option.isNone(Option.fromNullable(userTrack)) || !userTrack?.uri?.includes(userDid) ? putSongRecord(track, agent) - : Effect.succeed(null), - ), - ), - ), + : Effect.succeed(null) + ) + ) + ) ); // Ensure album exists or create it @@ -869,7 +870,7 @@ const ensureAlbum = ( ctx: Context, track: Track, agent: Agent, - userDid: string, + userDid: string ) => pipe( computeAlbumHash(track), @@ -880,8 +881,8 @@ const ensureAlbum = ( .from(tables.albums) .where(eq(tables.albums.sha256, albumHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((existingAlbum) => pipe( @@ -893,31 +894,31 @@ const ensureAlbum = ( .from(tables.userAlbums) .leftJoin( tables.albums, - eq(tables.userAlbums.albumId, tables.albums.id), + eq(tables.userAlbums.albumId, tables.albums.id) ) .leftJoin( tables.users, - eq(tables.userAlbums.userId, tables.users.id), + eq(tables.userAlbums.userId, tables.users.id) ) .where( and( eq(tables.albums.id, album.id), - eq(tables.users.did, userDid), - ), + eq(tables.users.did, userDid) + ) ) .execute() - .then(([row]) => row.user_albums), - ), + .then(([row]) => row.user_albums) + ) ), Effect.flatMap((userAlbum) => Option.isNone(Option.fromNullable(existingAlbum)) || Option.isNone(Option.fromNullable(userAlbum)) || !userAlbum?.uri?.includes(userDid) ? putAlbumRecord(track, agent) - : Effect.succeed(null), - ), - ), - ), + : Effect.succeed(null) + ) + ) + ) ); // Ensure artist exists or create it @@ -925,7 +926,7 @@ const ensureArtist = ( ctx: Context, track: Track, agent: Agent, - userDid: string, + userDid: string ) => pipe( computeArtistHash(track), @@ -936,8 +937,8 @@ const ensureArtist = ( .from(tables.artists) .where(eq(tables.artists.sha256, artistHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((existingArtist) => pipe( @@ -949,21 +950,21 @@ const ensureArtist = ( .from(tables.userArtists) .leftJoin( tables.artists, - eq(tables.userArtists.artistId, tables.artists.id), + eq(tables.userArtists.artistId, tables.artists.id) ) .leftJoin( tables.users, - eq(tables.userArtists.userId, tables.users.id), + eq(tables.userArtists.userId, tables.users.id) ) .where( and( eq(tables.artists.id, artist.id), - eq(tables.users.did, userDid), - ), + eq(tables.users.did, userDid) + ) ) .execute() - .then(([row]) => row.user_artists), - ), + .then(([row]) => row.user_artists) + ) ), Effect.flatMap((userArtist) => Effect.if( @@ -973,18 +974,18 @@ const ensureArtist = ( { onTrue: () => putArtistRecord(track, agent), onFalse: () => Effect.succeed(null), - }, - ), - ), - ), - ), + } + ) + ) + ) + ) ); // Retry fetching track until metadata is ready const retryFetchTrack = ( ctx: Context, trackHash: string, - initialTrack: SelectTrack | undefined, + initialTrack: SelectTrack | undefined ) => pipe( Effect.iterate( @@ -1000,38 +1001,38 @@ const retryFetchTrack = ( .from(tables.tracks) .where(eq(tables.tracks.sha256, trackHash)) .execute() - .then(([row]) => row), + .then(([row]) => row) ), Effect.flatMap((trackRecord) => Option.fromNullable(trackRecord).pipe( Effect.flatMap((track) => - updateTrackMetadata(ctx, track, trackRecord), - ), - ), + updateTrackMetadata(ctx, track, trackRecord) + ) + ) ), Effect.tap((trackRecord) => Effect.logInfo( trackRecord ? `Track metadata ready: ${chalk.cyan(trackRecord.id)} - ${track.title}, after ${chalk.magenta(tries + 1)} tries` - : `Retrying track fetch: ${chalk.magenta(tries + 1)}`, - ), + : `Retrying track fetch: ${chalk.magenta(tries + 1)}` + ) ), Effect.map((trackRecord) => ({ tries: tries + 1, track: trackRecord, })), - Effect.delay("1 second"), + Effect.delay("1 second") ), - }, + } ), Effect.tap(({ tries, track }) => tries >= 30 && !(track?.artistUri && track?.albumUri) ? Effect.logError( - `Track metadata not ready after ${chalk.magenta("30 tries")}`, + `Track metadata not ready after ${chalk.magenta("30 tries")}` ) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), - Effect.map(({ track }) => track), + Effect.map(({ track }) => track) ); // Retry fetching scrobble until complete @@ -1069,23 +1070,23 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => .from(tables.scrobbles) .leftJoin( tables.tracks, - eq(tables.scrobbles.trackId, tables.tracks.id), + eq(tables.scrobbles.trackId, tables.tracks.id) ) .leftJoin( tables.albums, - eq(tables.scrobbles.albumId, tables.albums.id), + eq(tables.scrobbles.albumId, tables.albums.id) ) .leftJoin( tables.artists, - eq(tables.scrobbles.artistId, tables.artists.id), + eq(tables.scrobbles.artistId, tables.artists.id) ) .leftJoin( tables.users, - eq(tables.scrobbles.userId, tables.users.id), + eq(tables.scrobbles.userId, tables.users.id) ) .where(eq(tables.scrobbles.uri, scrobbleUri)) .execute() - .then(([row]) => row), + .then(([row]) => row) ), Effect.tap((scrobble) => Effect.if( @@ -1102,11 +1103,11 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => artistUri: scrobble.artists.uri, }) .where(eq(tables.albums.id, scrobble.albums.id)) - .execute(), + .execute() ), onFalse: () => Effect.succeed(undefined), - }, - ), + } + ) ), Effect.flatMap(() => Effect.tryPromise(() => @@ -1115,24 +1116,24 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => .from(tables.scrobbles) .leftJoin( tables.tracks, - eq(tables.scrobbles.trackId, tables.tracks.id), + eq(tables.scrobbles.trackId, tables.tracks.id) ) .leftJoin( tables.albums, - eq(tables.scrobbles.albumId, tables.albums.id), + eq(tables.scrobbles.albumId, tables.albums.id) ) .leftJoin( tables.artists, - eq(tables.scrobbles.artistId, tables.artists.id), + eq(tables.scrobbles.artistId, tables.artists.id) ) .leftJoin( tables.users, - eq(tables.scrobbles.userId, tables.users.id), + eq(tables.scrobbles.userId, tables.users.id) ) .where(eq(tables.scrobbles.uri, scrobbleUri)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.map((scrobble) => ({ tries: tries + 1, @@ -1149,12 +1150,12 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => scrobble.tracks.albumUri && scrobble.scrobbles ? `Scrobble found after ${chalk.magenta(tries + 1)} tries` - : `Scrobble not found, trying again: ${chalk.magenta(tries + 1)}`, - ), + : `Scrobble not found, trying again: ${chalk.magenta(tries + 1)}` + ) ), - Effect.delay("1 second"), + Effect.delay("1 second") ), - }, + } ), Effect.tap(({ tries, scrobble }) => tries >= 30 && @@ -1168,18 +1169,18 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => scrobble.tracks.albumUri ) ? Effect.logError( - `Scrobble not found after ${chalk.magenta("30 tries")}`, + `Scrobble not found after ${chalk.magenta("30 tries")}` ) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), - Effect.map(({ scrobble }) => scrobble), + Effect.map(({ scrobble }) => scrobble) ); export const scrobbleTrack = ( ctx: Context, track: Track, agent: Agent, - userDid: string, + userDid: string ) => pipe( computeTrackHash(track), @@ -1192,7 +1193,7 @@ export const scrobbleTrack = ( Effect.flatMap(() => ensureAlbum(ctx, track, agent, userDid)), Effect.flatMap(() => ensureArtist(ctx, track, agent, userDid)), Effect.flatMap(() => - retryFetchTrack(ctx, trackHash, existingTrack), + retryFetchTrack(ctx, trackHash, existingTrack) ), Effect.flatMap(() => pipe( @@ -1212,17 +1213,17 @@ export const scrobbleTrack = ( ? pipe( publishScrobble(ctx, scrobble.scrobbles.id), Effect.tap(() => - Effect.logInfo("Scrobble published"), - ), + Effect.logInfo("Scrobble published") + ) ) - : Effect.succeed(undefined), - ), - ), - ), - ), - ), - ), - ), - ), - ), + : Effect.succeed(undefined) + ) + ) + ) + ) + ) + ) + ) + ) + ) ); diff --git a/crates/jetstream/src/repo.rs b/crates/jetstream/src/repo.rs index 1352909d..3aa1e1df 100644 --- a/crates/jetstream/src/repo.rs +++ b/crates/jetstream/src/repo.rs @@ -409,9 +409,10 @@ pub async fn save_artist( name, sha256, uri, - picture + picture, + genres ) VALUES ( - $1, $2, $3, $4 + $1, $2, $3, $4, $5 ) "#, ) @@ -419,6 +420,7 @@ pub async fn save_artist( .bind(&hash) .bind(uri) .bind(picture) + .bind(scrobble_record.tags) .execute(&mut **tx) .await?; diff --git a/crates/jetstream/src/xata/artist.rs b/crates/jetstream/src/xata/artist.rs index 25187ec5..4f330455 100644 --- a/crates/jetstream/src/xata/artist.rs +++ b/crates/jetstream/src/xata/artist.rs @@ -18,6 +18,7 @@ pub struct Artist { pub youtube_link: Option, pub apple_music_link: Option, pub uri: Option, + pub genres: Option>, #[serde(with = "chrono::serde::ts_seconds")] pub xata_createdat: DateTime, } diff --git a/crates/scrobbler/src/spotify/types.rs b/crates/scrobbler/src/spotify/types.rs index 40e95f80..c58e7d4f 100644 --- a/crates/scrobbler/src/spotify/types.rs +++ b/crates/scrobbler/src/spotify/types.rs @@ -76,6 +76,7 @@ pub struct Artist { pub kind: String, pub uri: String, pub images: Option>, + pub genres: Option>, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/scrobbler/src/types.rs b/crates/scrobbler/src/types.rs index 2e6176b3..990a20cb 100644 --- a/crates/scrobbler/src/types.rs +++ b/crates/scrobbler/src/types.rs @@ -39,6 +39,7 @@ pub struct Track { pub label: Option, pub artist_picture: Option, pub timestamp: Option, + pub genres: Option>, } impl From for Track { @@ -59,6 +60,7 @@ impl From for Track { disc_number: track.disc_number as u32, year: None, release_date: None, + genres: None, } } } @@ -173,6 +175,11 @@ impl From<&spotify::types::Track> for Track { _ => None, }, label: track.album.label.clone(), + genres: track + .album + .artists + .first() + .and_then(|artist| artist.genres.clone()), ..Default::default() } } diff --git a/crates/spotify/src/rocksky.rs b/crates/spotify/src/rocksky.rs index 1f0babc6..dd9a4121 100644 --- a/crates/spotify/src/rocksky.rs +++ b/crates/spotify/src/rocksky.rs @@ -71,13 +71,17 @@ pub async fn scrobble( None => None, }, "label": track_item.album.label, - "artistPicture": match artist { - Some(artist) => match artist.images { + "artistPicture": match &artist { + Some(artist) => match &artist.images { Some(images) => Some(images.first().map(|image| image.url.clone())), None => None }, None => None }, + "genres": match &artist { + Some(artist) => artist.genres.clone(), + None => None + }, })) .send() .await?; diff --git a/crates/spotify/src/types/currently_playing.rs b/crates/spotify/src/types/currently_playing.rs index 89ae6f45..489543ed 100644 --- a/crates/spotify/src/types/currently_playing.rs +++ b/crates/spotify/src/types/currently_playing.rs @@ -95,6 +95,7 @@ pub struct Artist { pub artist_type: String, pub uri: String, pub images: Option>, + pub genres: Option>, } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/crates/webscrobbler/src/spotify/types.rs b/crates/webscrobbler/src/spotify/types.rs index 40e95f80..c58e7d4f 100644 --- a/crates/webscrobbler/src/spotify/types.rs +++ b/crates/webscrobbler/src/spotify/types.rs @@ -76,6 +76,7 @@ pub struct Artist { pub kind: String, pub uri: String, pub images: Option>, + pub genres: Option>, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/webscrobbler/src/types.rs b/crates/webscrobbler/src/types.rs index f9cfede6..e3d8fc39 100644 --- a/crates/webscrobbler/src/types.rs +++ b/crates/webscrobbler/src/types.rs @@ -131,6 +131,7 @@ pub struct Track { pub label: Option, pub artist_picture: Option, pub timestamp: Option, + pub genres: Option>, } impl From for Track { @@ -151,6 +152,7 @@ impl From for Track { disc_number: track.disc_number as u32, year: None, release_date: None, + genres: None, } } } @@ -268,6 +270,11 @@ impl From<&spotify::types::Track> for Track { _ => None, }, label: track.album.label.clone(), + genres: track + .album + .artists + .first() + .and_then(|artist| artist.genres.clone()), ..Default::default() } }