Last active
October 31, 2025 12:20
-
-
Save woss/3a5fd937e22688eff361378150ec011f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "nodes": [ | |
| { | |
| "parameters": { | |
| "rule": { | |
| "interval": [ | |
| { | |
| "field": "hours", | |
| "hoursInterval": 6, | |
| "triggerAtMinute": 31 | |
| } | |
| ] | |
| } | |
| }, | |
| "type": "n8n-nodes-base.scheduleTrigger", | |
| "typeVersion": 1.2, | |
| "position": [ | |
| -1472, | |
| 1184 | |
| ], | |
| "id": "634b5228-5b1a-4871-a8ac-f919327bfaf3", | |
| "name": "Schedule Trigger" | |
| }, | |
| { | |
| "parameters": { | |
| "mode": "runOnceForEachItem", | |
| "jsCode": "const { track, track_id} = $input.item.json\nreturn { track, track_id}" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 3248, | |
| 1968 | |
| ], | |
| "id": "9155754e-6f79-4209-9441-591bc87866e4", | |
| "name": "clean payload" | |
| }, | |
| { | |
| "parameters": { | |
| "url": "=https://api.spotify.com/v1/users/{{ $json.id.id }}/playlists", | |
| "authentication": "predefinedCredentialType", | |
| "nodeCredentialType": "spotifyOAuth2Api", | |
| "sendQuery": true, | |
| "queryParameters": { | |
| "parameters": [ | |
| { | |
| "name": "fields", | |
| "value": "next,items(id,snapshot_id,tracks.total)" | |
| }, | |
| { | |
| "name": "limit", | |
| "value": "50" | |
| } | |
| ] | |
| }, | |
| "options": { | |
| "response": { | |
| "response": { | |
| "responseFormat": "json" | |
| } | |
| }, | |
| "pagination": { | |
| "pagination": { | |
| "paginationMode": "responseContainsNextURL", | |
| "nextURL": "={{ $response.body.next }}", | |
| "paginationCompleteWhen": "other", | |
| "completeExpression": "={{$response.body.next === null}}" | |
| } | |
| } | |
| } | |
| }, | |
| "type": "n8n-nodes-base.httpRequest", | |
| "typeVersion": 4.2, | |
| "position": [ | |
| 64, | |
| 1840 | |
| ], | |
| "id": "52db77f5-eb95-4ba0-b4d2-4d38434787ce", | |
| "name": "my playlists", | |
| "alwaysOutputData": false, | |
| "credentials": { | |
| "spotifyOAuth2Api": { | |
| "id": "spotify-cred-id", | |
| "name": "Spotify account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "query", | |
| "query": "=select id, snapshot_id from playlist:{{ $json.id }};", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 624, | |
| 1872 | |
| ], | |
| "id": "f2976ec0-37e8-45dc-9b95-a9025802c9d7", | |
| "name": "synced playlists", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "mode": "combine", | |
| "fieldsToMatchString": "snapshot_id", | |
| "joinMode": "keepNonMatches", | |
| "outputDataFrom": "input1", | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.merge", | |
| "typeVersion": 3.2, | |
| "position": [ | |
| 816, | |
| 1632 | |
| ], | |
| "id": "44ea9012-7930-4523-8cc5-1caa4e9e6968", | |
| "name": "missing playlists", | |
| "alwaysOutputData": false | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "query", | |
| "query": "=select id, snapshot_id from playlist:{{ $json.id }} where snapshot_id != \"{{ $json.snapshot_id }}\";", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 976, | |
| 1952 | |
| ], | |
| "id": "bb40bbd3-bce1-427a-be88-ab80b74e7fb0", | |
| "name": "query playlist with last snapshot", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "const isObjectEmpty = (objectName) => {\n return Object.keys(objectName).length === 0\n}\nlet changedPlaylist = []\nfor (const item of $input.all()) {\n if(!isObjectEmpty(item.json)){\n changedPlaylist.push({\n id:item.json.id.id,\n snapshot_id: item.json.snapshot_id\n })\n }\n}\n\n\nreturn changedPlaylist;" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 1312, | |
| 2096 | |
| ], | |
| "id": "181f4f5c-142a-41e4-a54f-d28929dbefd8", | |
| "name": "playlists that need sync", | |
| "alwaysOutputData": false | |
| }, | |
| { | |
| "parameters": { | |
| "url": "=https://api.spotify.com/v1/me", | |
| "authentication": "predefinedCredentialType", | |
| "nodeCredentialType": "spotifyOAuth2Api", | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.httpRequest", | |
| "typeVersion": 4.2, | |
| "position": [ | |
| -1008, | |
| 1184 | |
| ], | |
| "id": "b82fcdef-63c8-4865-b018-297224e7390f", | |
| "name": "get me", | |
| "credentials": { | |
| "spotifyOAuth2Api": { | |
| "id": "spotify-cred-id", | |
| "name": "Spotify account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "upsertRecord", | |
| "table": "user", | |
| "id": "={{ $json.id }}", | |
| "data": "={{ $json }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| -784, | |
| 1184 | |
| ], | |
| "id": "8945a030-4c39-4957-aeb0-59cb2fcbedf3", | |
| "name": "upsert me", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "fieldToSplitOut": "items", | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.splitOut", | |
| "typeVersion": 1, | |
| "position": [ | |
| 256, | |
| 2080 | |
| ], | |
| "id": "cef37ccb-5f76-4850-8621-4a86f7f0cfb3", | |
| "name": "combine all calls" | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "query", | |
| "query": "=select count() from playlist_track where in = playlist:{{ $json.id }} group all;", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 624, | |
| 2080 | |
| ], | |
| "id": "18bf2cee-b7c3-4f7b-af2b-ca4ef77734cf", | |
| "name": "query playlist_tracks", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "numberInputs": 3 | |
| }, | |
| "type": "n8n-nodes-base.merge", | |
| "typeVersion": 3.2, | |
| "position": [ | |
| 1056, | |
| 2336 | |
| ], | |
| "id": "f9add17f-2fc9-4b35-9b9e-afbf21e29edc", | |
| "name": "missing track for playlists" | |
| }, | |
| { | |
| "parameters": { | |
| "fieldsToAggregate": { | |
| "fieldToAggregate": [ | |
| { | |
| "fieldToAggregate": "count" | |
| } | |
| ] | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.aggregate", | |
| "typeVersion": 1, | |
| "position": [ | |
| 880, | |
| 2112 | |
| ], | |
| "id": "82a78312-4dcd-478e-b082-005bc49bd46a", | |
| "name": "Aggregate db count" | |
| }, | |
| { | |
| "parameters": { | |
| "fieldsToAggregate": { | |
| "fieldToAggregate": [ | |
| { | |
| "fieldToAggregate": "tracks.total", | |
| "renameField": true, | |
| "outputFieldName": "spotify_totals" | |
| } | |
| ] | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.aggregate", | |
| "typeVersion": 1, | |
| "position": [ | |
| 624, | |
| 2272 | |
| ], | |
| "id": "eb650346-5aae-429e-877b-9739231af9ad", | |
| "name": "Aggregate spotify data" | |
| }, | |
| { | |
| "parameters": { | |
| "fieldsToAggregate": { | |
| "fieldToAggregate": [ | |
| { | |
| "fieldToAggregate": "id", | |
| "renameField": true, | |
| "outputFieldName": "playlist_ids" | |
| } | |
| ] | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.aggregate", | |
| "typeVersion": 1, | |
| "position": [ | |
| 624, | |
| 2464 | |
| ], | |
| "id": "67a03a6f-3646-4c26-aca6-f35ea90a22f9", | |
| "name": "Aggregate spotify ids" | |
| }, | |
| { | |
| "parameters": { | |
| "url": "=https://api.spotify.com/v1/playlists/{{ $json.id }}/tracks?offset=0&limit=100", | |
| "authentication": "predefinedCredentialType", | |
| "nodeCredentialType": "spotifyOAuth2Api", | |
| "options": { | |
| "response": { | |
| "response": { | |
| "responseFormat": "json" | |
| } | |
| }, | |
| "pagination": { | |
| "pagination": { | |
| "paginationMode": "responseContainsNextURL", | |
| "nextURL": "={{ $response.body.next }}", | |
| "paginationCompleteWhen": "other", | |
| "completeExpression": "={{$response.body.next === null}}", | |
| "requestInterval": 100 | |
| } | |
| } | |
| } | |
| }, | |
| "type": "n8n-nodes-base.httpRequest", | |
| "typeVersion": 4.2, | |
| "position": [ | |
| 1984, | |
| 2272 | |
| ], | |
| "id": "a8773504-3663-475c-b57d-06f7caf9c526", | |
| "name": "get all tracks for playlist", | |
| "alwaysOutputData": false, | |
| "credentials": { | |
| "spotifyOAuth2Api": { | |
| "id": "spotify-cred-id", | |
| "name": "Spotify account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "let data = {\n count: [0],\n spotify_totals: [0],\n playlist_ids: ['']\n}\n\nfor (const input of $input.all()) {\n if (input.json.count) {\n data.count = input.json.count\n }\n if (input.json.spotify_totals) {\n data.spotify_totals = input.json.spotify_totals\n }\n if (input.json.playlist_ids) {\n data.playlist_ids = input.json.playlist_ids\n }\n}\n\nlet retData = []\n\nfor (let index = 0; index < data.playlist_ids.length; index++) {\n const playlist_id = data.playlist_ids[index];\n const dbCount = data.count[index];\n const spotifyTotal = data.spotify_totals[index];\n if(dbCount !== spotifyTotal) {\n retData.push({\n id:playlist_id,\n dbCount,\n spotifyTotal,\n diff: spotifyTotal - dbCount\n })\n }\n}\n\nreturn retData;" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 1264, | |
| 2320 | |
| ], | |
| "id": "3d7e9a5f-a1ec-41f1-b756-b4507537b220", | |
| "name": "filter out synced playlists" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "upsertRecord", | |
| "table": "album", | |
| "id": "={{ $json.album_id }}", | |
| "data": "={{ $json.album }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 3472, | |
| 2160 | |
| ], | |
| "id": "c07771d1-c811-4812-a1aa-931316c1ae5e", | |
| "name": "Upsert album", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.splitInBatches", | |
| "typeVersion": 3, | |
| "position": [ | |
| 1712, | |
| 2064 | |
| ], | |
| "id": "2b9a3f57-4678-4c2c-8215-7b74a87c2631", | |
| "name": "Loop Over Items" | |
| }, | |
| { | |
| "parameters": { | |
| "amount": 0.5 | |
| }, | |
| "type": "n8n-nodes-base.wait", | |
| "typeVersion": 1.1, | |
| "position": [ | |
| 3936, | |
| 2352 | |
| ], | |
| "id": "3c92d837-8a21-45b2-9448-3c8dcd4042af", | |
| "name": "Wait", | |
| "webhookId": "120a548b-67b4-428f-b457-c30c6023181f" | |
| }, | |
| { | |
| "parameters": { | |
| "sortFieldsUi": { | |
| "sortField": [ | |
| { | |
| "fieldName": "diff" | |
| } | |
| ] | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.sort", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1488, | |
| 2320 | |
| ], | |
| "id": "de0e99c6-8989-434b-9848-07bf6213051a", | |
| "name": "Sort by diff ASC" | |
| }, | |
| { | |
| "parameters": { | |
| "numberInputs": 7 | |
| }, | |
| "type": "n8n-nodes-base.merge", | |
| "typeVersion": 3.2, | |
| "position": [ | |
| 3696, | |
| 1984 | |
| ], | |
| "id": "8988cf7d-9c5f-43e0-91be-8bebecef09d4", | |
| "name": "Merge" | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "relationship", | |
| "fromRecordId": "=album:{{ $json.album_id }}", | |
| "relationshipType": "album_track", | |
| "toRecordId": "=track:{{ $json.track_id }}", | |
| "options": {}, | |
| "connectionPooling": { | |
| "retryAttempts": 0 | |
| } | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 3472, | |
| 2352 | |
| ], | |
| "id": "df731b6a-370c-4dd7-9624-3aaf22e7d914", | |
| "name": "album_track", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| }, | |
| "onError": "continueErrorOutput" | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "query", | |
| "query": "=DELETE from playlist_track where in=playlist:{{ $('Loop Over Items').item.json.id }};", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 2432, | |
| 1984 | |
| ], | |
| "id": "0c2fbd25-2e83-4b53-9406-c3c4e0f09252", | |
| "name": "delete all playlist_track items for playlist", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "query", | |
| "query": "=RELATE playlist:{{ $json.playlist_id }}->playlist_track->track:{{ $json.track_id }} SET added_at = d'{{ $json.added_at }}';", | |
| "options": {}, | |
| "connectionPooling": { | |
| "retryAttempts": 0 | |
| } | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 3472, | |
| 1776 | |
| ], | |
| "id": "b7eee354-6cc7-473b-b338-63557952ec4d", | |
| "name": "playlist_track", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| }, | |
| "onError": "continueErrorOutput" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "upsertRecord", | |
| "table": "track", | |
| "id": "={{ $json.track_id }}", | |
| "data": "={{ $json.track }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 3472, | |
| 1968 | |
| ], | |
| "id": "b8e1bf62-cf8a-4345-bba1-6a503ed82712", | |
| "name": "Upsert track from playlist", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| }, | |
| "onError": "continueErrorOutput" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "upsertRecord", | |
| "table": "playlist", | |
| "id": "={{ $json.playlist_id }}", | |
| "data": "={{ $json.playlist }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 608, | |
| -256 | |
| ], | |
| "id": "2a197992-94cb-4036-aa40-c3b25a80846f", | |
| "name": "Upsert playlist", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "playlist", | |
| "operation": "getUserPlaylists", | |
| "returnAll": true | |
| }, | |
| "type": "n8n-nodes-base.spotify", | |
| "typeVersion": 1, | |
| "position": [ | |
| 64, | |
| 208 | |
| ], | |
| "id": "9cfaaf92-e1e6-474c-8b7a-3cca9f8c847d", | |
| "name": "Get a user's playlists", | |
| "executeOnce": false, | |
| "credentials": { | |
| "spotifyOAuth2Api": { | |
| "id": "spotify-cred-id", | |
| "name": "Spotify account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "relationship", | |
| "fromRecordId": "=user:`{{ $('upsert me').item.json.id.id }}`", | |
| "relationshipType": "has_playlist", | |
| "toRecordId": "=playlist:{{ $json.playlist_id }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 608, | |
| -64 | |
| ], | |
| "id": "c60344bd-0c9e-4532-90e2-b21a791164bc", | |
| "name": "has_playlist", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| }, | |
| "onError": "continueErrorOutput" | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "relationship", | |
| "fromRecordId": "=user:`{{ $json.owner_id }}`", | |
| "relationshipType": "playlist_owner", | |
| "toRecordId": "=playlist:{{ $json.playlist_id }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 608, | |
| 128 | |
| ], | |
| "id": "c2e3801c-c1fc-4f90-bee0-9f7e8eb455ce", | |
| "name": "playlist_owner", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| }, | |
| "onError": "continueErrorOutput" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "upsertRecord", | |
| "table": "user", | |
| "id": "={{ $json.owner_id }}", | |
| "data": "={{ $json.owner }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 608, | |
| 320 | |
| ], | |
| "id": "ff96e6a3-c5cc-489a-bf83-914a65aac95f", | |
| "name": "upsert_user as owner", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "const payload = []\nfor (const item of $input.all()) {\n const {owner, tracks, ...playlistRest} = item.json\n const {id, ...playlist} = playlistRest\n const {id: owner_id, ...ownerRest} = owner\n payload.push({owner:ownerRest, tracks,playlist, playlist_id:id, owner_id })\n}\n\nreturn payload;" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 288, | |
| 208 | |
| ], | |
| "id": "9170ea5c-c9fd-4cf9-921c-5c0f66b2674e", | |
| "name": "remap keys and restructure" | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "library", | |
| "returnAll": true | |
| }, | |
| "type": "n8n-nodes-base.spotify", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1184, | |
| 1248 | |
| ], | |
| "id": "fb4e5977-077b-49d0-bffc-909953195702", | |
| "name": "Get liked tracks", | |
| "credentials": { | |
| "spotifyOAuth2Api": { | |
| "id": "spotify-cred-id", | |
| "name": "Spotify account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "upsertRecord", | |
| "table": "track", | |
| "id": "={{ $json.track_id }}", | |
| "data": "={{ $json.track }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1856, | |
| 864 | |
| ], | |
| "id": "dde0f6b3-ddce-4ded-b30c-1fb29e39cec6", | |
| "name": "Upsert track", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "query", | |
| "query": "=RELATE {{ $('upsert me').item.json.id.tb }}:{{ $('upsert me').item.json.id.id }}->likes_track->track:{{ $json.track_id }} SET added_at = d'{{ $json.added_at }}';", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1856, | |
| 640 | |
| ], | |
| "id": "bd64308f-1bc8-45d0-8d47-04cf350470a0", | |
| "name": "me likes", | |
| "notesInFlow": false, | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| }, | |
| "onError": "continueErrorOutput" | |
| }, | |
| { | |
| "parameters": {}, | |
| "type": "n8n-nodes-base.noOp", | |
| "typeVersion": 1, | |
| "position": [ | |
| 960, | |
| 672 | |
| ], | |
| "id": "1574cb4e-bf5d-4df5-beb6-aa5c0d1c0469", | |
| "name": "Liked songs in sync" | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "return [{run:1}];" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 960, | |
| 1072 | |
| ], | |
| "id": "8a7d18e0-c985-43c0-b3e9-2d83d37c7a91", | |
| "name": "dummy single run" | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "return {message: \"Sync success\"}" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 2080, | |
| 928 | |
| ], | |
| "id": "5d094c3c-370d-472a-851f-e5f14987fb2d", | |
| "name": "success" | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "return {message: \"Sync error\"}" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 2080, | |
| 1120 | |
| ], | |
| "id": "2138387c-dd76-40a4-b7d1-91600e1b7a63", | |
| "name": "error" | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "const retArray = []\n\nfor (const item of $input.all()) {\n const {id, album, ...restTrack} = item.json.track\n const {id: album_id, ...restAlbum} = album\n \n retArray.push({\n track_id:id, \n album_id, \n album:restAlbum, \n track: restTrack,\n added_at:item.json.added_at\n })\n}\n\nreturn retArray;" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 1360, | |
| 1248 | |
| ], | |
| "id": "53ae34b0-28e0-4dde-bfa0-6da213635748", | |
| "name": "modify output" | |
| }, | |
| { | |
| "parameters": { | |
| "mode": "combine", | |
| "advanced": true, | |
| "mergeByFields": { | |
| "values": [ | |
| { | |
| "field1": "out.id", | |
| "field2": "track_id" | |
| } | |
| ] | |
| }, | |
| "joinMode": "keepNonMatches", | |
| "outputDataFrom": "input2", | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.merge", | |
| "typeVersion": 3.2, | |
| "position": [ | |
| 1552, | |
| 928 | |
| ], | |
| "id": "0a67c772-e42a-4f26-9536-589e45aa2b21", | |
| "name": "match only missing tracks" | |
| }, | |
| { | |
| "parameters": { | |
| "url": "https://api.spotify.com/v1/me/tracks", | |
| "authentication": "predefinedCredentialType", | |
| "nodeCredentialType": "spotifyOAuth2Api", | |
| "sendQuery": true, | |
| "queryParameters": { | |
| "parameters": [ | |
| { | |
| "name": "fields", | |
| "value": "=total" | |
| } | |
| ] | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.httpRequest", | |
| "typeVersion": 4.2, | |
| "position": [ | |
| 272, | |
| 1088 | |
| ], | |
| "id": "ffcab92b-ff2b-47f7-8e37-ed62f2427497", | |
| "name": "get tracks total", | |
| "credentials": { | |
| "spotifyOAuth2Api": { | |
| "id": "spotify-cred-id", | |
| "name": "Spotify account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "relationship", | |
| "fromRecordId": "=album:{{ $json.album_id }}", | |
| "relationshipType": "album_track", | |
| "toRecordId": "=track:{{ $json.track_id }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1856, | |
| 1264 | |
| ], | |
| "id": "9de5abc5-202b-49a1-8c4f-18571b930dbb", | |
| "name": "album_track1", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| }, | |
| "onError": "continueErrorOutput" | |
| }, | |
| { | |
| "parameters": {}, | |
| "type": "n8n-nodes-base.noOp", | |
| "typeVersion": 1, | |
| "position": [ | |
| 64, | |
| 880 | |
| ], | |
| "id": "7a31f56e-12de-46f2-a622-930e05ad96f5", | |
| "name": "passthrough" | |
| }, | |
| { | |
| "parameters": { | |
| "mode": "combine", | |
| "advanced": true, | |
| "mergeByFields": { | |
| "values": [ | |
| { | |
| "field1": "dbTotal", | |
| "field2": "total" | |
| } | |
| ] | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.merge", | |
| "typeVersion": 3.2, | |
| "position": [ | |
| 512, | |
| 880 | |
| ], | |
| "id": "378265c5-6ecd-4a65-857f-0fc442696e4f", | |
| "name": "merge local & spotify", | |
| "alwaysOutputData": true | |
| }, | |
| { | |
| "parameters": { | |
| "content": "# Sync liked tracks", | |
| "height": 848, | |
| "width": 2304, | |
| "color": 6 | |
| }, | |
| "type": "n8n-nodes-base.stickyNote", | |
| "typeVersion": 1, | |
| "position": [ | |
| -16, | |
| 624 | |
| ], | |
| "id": "1cc7511a-555b-48fb-a260-f38294f3639e", | |
| "name": "Sticky Note" | |
| }, | |
| { | |
| "parameters": { | |
| "content": "# Sync playlists", | |
| "height": 848, | |
| "width": 912, | |
| "color": 7 | |
| }, | |
| "type": "n8n-nodes-base.stickyNote", | |
| "typeVersion": 1, | |
| "position": [ | |
| -16, | |
| -320 | |
| ], | |
| "id": "404958a8-c58c-44cc-af79-1254ffab9d3f", | |
| "name": "Sticky Note2" | |
| }, | |
| { | |
| "parameters": { | |
| "content": "# Sync playlists tracks", | |
| "height": 1232, | |
| "width": 4288, | |
| "color": 4 | |
| }, | |
| "type": "n8n-nodes-base.stickyNote", | |
| "typeVersion": 1, | |
| "position": [ | |
| -16, | |
| 1568 | |
| ], | |
| "id": "38e75dbd-ce7f-491e-8b64-85b560ce2100", | |
| "name": "Sticky Note1" | |
| }, | |
| { | |
| "parameters": { | |
| "conditions": { | |
| "options": { | |
| "caseSensitive": true, | |
| "leftValue": "", | |
| "typeValidation": "strict", | |
| "version": 2 | |
| }, | |
| "conditions": [ | |
| { | |
| "id": "3478646e-57f4-49e0-a241-29aefe83332e", | |
| "leftValue": "={{$json}}", | |
| "rightValue": "", | |
| "operator": { | |
| "type": "object", | |
| "operation": "notEmpty", | |
| "singleValue": true | |
| } | |
| } | |
| ], | |
| "combinator": "and" | |
| }, | |
| "options": {} | |
| }, | |
| "type": "n8n-nodes-base.if", | |
| "typeVersion": 2.2, | |
| "position": [ | |
| 2192, | |
| 2128 | |
| ], | |
| "id": "ab52f96a-99ab-4228-8a81-2c424ec62d86", | |
| "name": "with result" | |
| }, | |
| { | |
| "parameters": { | |
| "mode": "chooseBranch", | |
| "useDataOfInput": 2 | |
| }, | |
| "type": "n8n-nodes-base.merge", | |
| "typeVersion": 3.2, | |
| "position": [ | |
| 2672, | |
| 2176 | |
| ], | |
| "id": "359246a9-338f-4dd7-90f9-e47814158560", | |
| "name": "fwd input 2" | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "function slugify(str) {\n return str\n .toLowerCase()\n .trim()\n .replace(/[\\s\\W-]+/g, '_') // Replace spaces and non-word chars with -\n .replace(/^-+|-+$/g, ''); // Remove leading/trailing -\n}\n\nfunction idFromLocal(uri) {\n const onlyValue = uri.replace(\"spotify:local:\", \"\");\n // const parts = onlyValue.split(\":\");\n // const artist = parts[0];\n // const album = parts[2];\n // const track = parts[1];\n // const duration = parts[3];\n return slugify(decodeURIComponent(onlyValue));\n}\n\nconst url = $input.first().json.href;\nconst match = url.match(/\\/playlists\\/([^/]+)(?:\\/|$)/);\nconst playlist_id = match ? match[1] : null;\nlet allItems = [];\n\n// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n for (const i of item.json.items) {\n if (i.track) {\n const {\n id: origId,\n album: { id: album_id, ...restAlbum },\n ...restItem\n } = i.track;\n let id = origId;\n if (!origId) {\n // need to create a meaningful id\n id = idFromLocal(restItem.uri);\n }\n\n const _i = Object.assign(\n {},\n i,\n { track: restItem },\n {\n album: restAlbum,\n album_id,\n playlist_id,\n track_id: id,\n }\n );\n allItems.push(_i);\n } else {\n console.log(\"Missing track\", item);\n }\n }\n}\n\nreturn allItems;\n" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 2848, | |
| 2048 | |
| ], | |
| "id": "db2ebc1f-8dc5-4b89-b62a-d57754606f41", | |
| "name": "restructure payload" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "upsertRecord", | |
| "table": "album", | |
| "id": "={{ $json.album_id }}", | |
| "data": "={{ $json.album }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1856, | |
| 1072 | |
| ], | |
| "id": "f33f059a-fdd5-4a11-9b73-786bc75fa47f", | |
| "name": "Upsert album from liked tracks", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "query", | |
| "query": "=select count() as dbTotal from likes_track where in = {{$json.id.tb}}:{{ $json.id.id }} group all;", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 288, | |
| 752 | |
| ], | |
| "id": "cd6aead8-ea11-4560-b00b-a97e2445d56a", | |
| "name": "get liked tracks count", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "query", | |
| "query": "=select * from likes_track where in = {{$('upsert me').item.json.id.tb}}:{{ $('upsert me').item.json.id.id }};", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1184, | |
| 944 | |
| ], | |
| "id": "09b652da-9ebc-41da-a2b3-728c2b5ef41d", | |
| "name": "get all liked tracks", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": {}, | |
| "type": "n8n-nodes-base.noOp", | |
| "typeVersion": 1, | |
| "position": [ | |
| 1968, | |
| 1856 | |
| ], | |
| "id": "901b412c-9fb7-45cf-88a5-267fb7086277", | |
| "name": "done iterating" | |
| }, | |
| { | |
| "parameters": { | |
| "conditions": { | |
| "options": { | |
| "caseSensitive": true, | |
| "leftValue": "", | |
| "typeValidation": "loose", | |
| "version": 2 | |
| }, | |
| "conditions": [ | |
| { | |
| "id": "cc28f07a-1fff-41e4-92ec-69e99084fe96", | |
| "leftValue": "={{ $json }}", | |
| "rightValue": "", | |
| "operator": { | |
| "type": "object", | |
| "operation": "notEmpty", | |
| "singleValue": true | |
| } | |
| }, | |
| { | |
| "id": "e418c917-e9cb-4493-b074-74f426f7f760", | |
| "leftValue": "={{ $json.total }}", | |
| "rightValue": "={{ $json.dbTotal }}", | |
| "operator": { | |
| "type": "string", | |
| "operation": "equals" | |
| } | |
| } | |
| ], | |
| "combinator": "and" | |
| }, | |
| "looseTypeValidation": true, | |
| "options": { | |
| "ignoreCase": false | |
| } | |
| }, | |
| "type": "n8n-nodes-base.if", | |
| "typeVersion": 2.2, | |
| "position": [ | |
| 736, | |
| 880 | |
| ], | |
| "id": "7c0d65c7-6006-4734-8d30-1f869bbadffe", | |
| "name": "in sync" | |
| }, | |
| { | |
| "parameters": { | |
| "operation": "upsertRecord", | |
| "table": "artist", | |
| "id": "={{ $json.item.artistId }}", | |
| "data": "={{ $json.item }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 448, | |
| -720 | |
| ], | |
| "id": "164964c6-e01c-4623-a9f8-c1a7bdf12b83", | |
| "name": "Upsert artists", | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "myData", | |
| "returnAll": true | |
| }, | |
| "type": "n8n-nodes-base.spotify", | |
| "typeVersion": 1, | |
| "position": [ | |
| 32, | |
| -720 | |
| ], | |
| "id": "707e2586-f9d3-4847-beee-56d2083cef32", | |
| "name": "Get your followed artists", | |
| "credentials": { | |
| "spotifyOAuth2Api": { | |
| "id": "spotify-cred-id", | |
| "name": "Spotify account" | |
| } | |
| } | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "relationship", | |
| "fromRecordId": "={{$('upsert me').item.json.id.tb}}:{{$('upsert me').item.json.id.id}}", | |
| "relationshipType": "follows", | |
| "toRecordId": "={{ $json.id.tb }}:{{ $json.id.id }}", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| 688, | |
| -720 | |
| ], | |
| "id": "82396689-12d8-4282-a7be-d79a2d19ca62", | |
| "name": "Create a relationship", | |
| "notesInFlow": false, | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| }, | |
| "onError": "continueErrorOutput" | |
| }, | |
| { | |
| "parameters": { | |
| "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nconst ret = []\nfor (const item of $input.all()) {\n ret.push({item: {...item.json, artistId: item.json.id}})\n}\n\nreturn ret;" | |
| }, | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [ | |
| 240, | |
| -720 | |
| ], | |
| "id": "3b3139b3-0d72-4c42-a6d7-cd6e03174fea", | |
| "name": "add artistId" | |
| }, | |
| { | |
| "parameters": { | |
| "content": "# Sync following artists", | |
| "height": 432, | |
| "width": 1072, | |
| "color": 2 | |
| }, | |
| "type": "n8n-nodes-base.stickyNote", | |
| "typeVersion": 1, | |
| "position": [ | |
| -16, | |
| -848 | |
| ], | |
| "id": "a9ac44c1-f319-4a81-b141-4b564ef6fabb", | |
| "name": "Sticky Note3" | |
| }, | |
| { | |
| "parameters": { | |
| "resource": "query", | |
| "query": "DEFINE TABLE IF NOT EXISTS album TYPE ANY SCHEMALESS PERMISSIONS NONE; DEFINE INDEX IF NOT EXISTS album_id_idx ON album FIELDS id UNIQUE; DEFINE INDEX IF NOT EXISTS album_uri_idx ON album FIELDS uri UNIQUE; DEFINE TABLE IF NOT EXISTS artist TYPE ANY SCHEMALESS PERMISSIONS NONE; DEFINE INDEX IF NOT EXISTS artist_id_idx ON artist FIELDS id UNIQUE; DEFINE INDEX IF NOT EXISTS artist_uri_idx ON artist FIELDS uri UNIQUE; DEFINE TABLE IF NOT EXISTS playlist TYPE ANY SCHEMALESS PERMISSIONS NONE; DEFINE INDEX IF NOT EXISTS snapshot_id ON playlist FIELDS snapshot_id; DEFINE INDEX IF NOT EXISTS playlist_uri_idx ON playlist FIELDS uri UNIQUE; DEFINE TABLE IF NOT EXISTS track TYPE NORMAL SCHEMALESS PERMISSIONS NONE; DEFINE INDEX IF NOT EXISTS track_index ON track FIELDS id UNIQUE; DEFINE INDEX IF NOT EXISTS track_uri_idx ON track FIELDS uri UNIQUE; DEFINE TABLE IF NOT EXISTS user TYPE NORMAL SCHEMALESS PERMISSIONS NONE; DEFINE INDEX IF NOT EXISTS user_idx ON user FIELDS id UNIQUE; DEFINE INDEX IF NOT EXISTS user_uri_idx ON user FIELDS uri UNIQUE; DEFINE TABLE IF NOT EXISTS album_track TYPE RELATION IN album OUT track SCHEMAFULL PERMISSIONS NONE; DEFINE INDEX IF NOT EXISTS album_track ON album_track FIELDS in, out UNIQUE; DEFINE TABLE IF NOT EXISTS follows TYPE RELATION IN user OUT artist SCHEMAFULL PERMISSIONS NONE; DEFINE INDEX IF NOT EXISTS user_follow_artist ON follows FIELDS in, out UNIQUE; DEFINE TABLE IF NOT EXISTS has_playlist TYPE RELATION IN user OUT playlist SCHEMALESS PERMISSIONS NONE; DEFINE INDEX IF NOT EXISTS user_playlist ON has_playlist FIELDS in, out UNIQUE; DEFINE TABLE IF NOT EXISTS likes_track TYPE RELATION IN user OUT track SCHEMALESS PERMISSIONS NONE; DEFINE FIELD IF NOT EXISTS added_at ON likes_track TYPE datetime PERMISSIONS FULL; DEFINE INDEX IF NOT EXISTS user_likes_track ON likes_track FIELDS in, out UNIQUE; DEFINE TABLE IF NOT EXISTS playlist_owner TYPE RELATION IN user OUT playlist SCHEMAFULL PERMISSIONS NONE; DEFINE INDEX IF NOT EXISTS playlist_owner_idx ON playlist_owner FIELDS in, out UNIQUE; DEFINE TABLE IF NOT EXISTS playlist_track TYPE RELATION IN playlist OUT track SCHEMALESS PERMISSIONS NONE; DEFINE FIELD IF NOT EXISTS added_at ON playlist_track TYPE datetime PERMISSIONS FULL; DEFINE INDEX IF NOT EXISTS playlist_track_id_idx ON playlist_track FIELDS id UNIQUE;", | |
| "options": {}, | |
| "connectionPooling": {} | |
| }, | |
| "type": "n8n-nodes-surrealdb.surrealDb", | |
| "typeVersion": 1, | |
| "position": [ | |
| -1216, | |
| 1184 | |
| ], | |
| "id": "78bc580e-7544-4f91-981b-5c9b36295d30", | |
| "name": "setup database", | |
| "alwaysOutputData": true, | |
| "credentials": { | |
| "surrealDbApi": { | |
| "id": "surrealdb-cred-id", | |
| "name": "SurrealDB cloud account" | |
| } | |
| } | |
| } | |
| ], | |
| "connections": { | |
| "Schedule Trigger": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "setup database", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "clean payload": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Upsert track from playlist", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "my playlists": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "combine all calls", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "synced playlists": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "missing playlists", | |
| "type": "main", | |
| "index": 1 | |
| } | |
| ] | |
| ] | |
| }, | |
| "missing playlists": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Loop Over Items", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "query playlist with last snapshot": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "playlists that need sync", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "playlists that need sync": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Loop Over Items", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "get me": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "upsert me", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "upsert me": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "my playlists", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "Get a user's playlists", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "passthrough", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "Get your followed artists", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "combine all calls": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "synced playlists", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "missing playlists", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "query playlist with last snapshot", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "query playlist_tracks", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "Aggregate spotify data", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "Aggregate spotify ids", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "query playlist_tracks": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Aggregate db count", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "missing track for playlists": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "filter out synced playlists", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Aggregate db count": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "missing track for playlists", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Aggregate spotify data": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "missing track for playlists", | |
| "type": "main", | |
| "index": 1 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Aggregate spotify ids": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "missing track for playlists", | |
| "type": "main", | |
| "index": 2 | |
| } | |
| ] | |
| ] | |
| }, | |
| "get all tracks for playlist": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "with result", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "fwd input 2", | |
| "type": "main", | |
| "index": 1 | |
| } | |
| ] | |
| ] | |
| }, | |
| "filter out synced playlists": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Sort by diff ASC", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Upsert album": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Merge", | |
| "type": "main", | |
| "index": 4 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Loop Over Items": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "done iterating", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "get all tracks for playlist", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Wait": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Loop Over Items", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Sort by diff ASC": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Loop Over Items", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Merge": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Wait", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "album_track": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Merge", | |
| "type": "main", | |
| "index": 6 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "Merge", | |
| "type": "main", | |
| "index": 5 | |
| } | |
| ] | |
| ] | |
| }, | |
| "delete all playlist_track items for playlist": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "fwd input 2", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "playlist_track": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Merge", | |
| "type": "main", | |
| "index": 2 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "Merge", | |
| "type": "main", | |
| "index": 3 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Upsert track from playlist": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Merge", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "Merge", | |
| "type": "main", | |
| "index": 1 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Get a user's playlists": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "remap keys and restructure", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "remap keys and restructure": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Upsert playlist", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "has_playlist", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "playlist_owner", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "upsert_user as owner", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Get liked tracks": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "modify output", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Upsert track": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "success", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "me likes": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "success", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "error", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "dummy single run": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Get liked tracks", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "get all liked tracks", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "modify output": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "match only missing tracks", | |
| "type": "main", | |
| "index": 1 | |
| } | |
| ] | |
| ] | |
| }, | |
| "match only missing tracks": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "me likes", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "Upsert track", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "Upsert album from liked tracks", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "album_track1", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "get tracks total": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "merge local & spotify", | |
| "type": "main", | |
| "index": 1 | |
| } | |
| ] | |
| ] | |
| }, | |
| "album_track1": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "success", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "error", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "passthrough": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "get liked tracks count", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "get tracks total", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "merge local & spotify": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "in sync", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "with result": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "delete all playlist_track items for playlist", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "fwd input 2": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "restructure payload", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "restructure payload": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "album_track", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "Upsert album", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "clean payload", | |
| "type": "main", | |
| "index": 0 | |
| }, | |
| { | |
| "node": "playlist_track", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Upsert album from liked tracks": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "success", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "get liked tracks count": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "merge local & spotify", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "get all liked tracks": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "match only missing tracks", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "in sync": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Liked songs in sync", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "dummy single run", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Upsert artists": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Create a relationship", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Get your followed artists": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "add artistId", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "add artistId": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Upsert artists", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "setup database": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "get me", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment