@foxtware/mineral 0.1.28 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.creds.yml.sample +14 -2
- package/AGENTS.md +7 -0
- package/_build_scripts/createNewFunction.js +26 -3
- package/api/dropbox/docs.md +7 -0
- package/api/dropbox/dropbox.constants.js +10 -0
- package/api/dropbox/dropbox.utils.js +67 -0
- package/api/dropbox/dropboxAccountGet.js +64 -0
- package/api/dropbox/dropboxFileCopy.js +67 -0
- package/api/dropbox/dropboxFileDelete.js +89 -0
- package/api/dropbox/dropboxFileDownload.js +139 -0
- package/api/dropbox/dropboxFileMetadataGet.js +100 -0
- package/api/dropbox/dropboxFileMove.js +67 -0
- package/api/dropbox/dropboxFileUpload.js +129 -0
- package/api/dropbox/dropboxFolderCreate.js +68 -0
- package/api/dropbox/dropboxFolderList.js +66 -0
- package/api/dropbox/dropboxGet.js +158 -0
- package/api/dropbox/dropboxSearch.js +161 -0
- package/api/dropbox/dropboxSharedLinkCreate.js +68 -0
- package/api/dropbox/dropboxSpaceUsageGet.js +55 -0
- package/api/dropbox/dropboxTemporaryLinkGet.js +75 -0
- package/api/google/docs.md +1 -0
- package/api/google/google.constants.js +1 -0
- package/api/google/google.utils.js +21 -0
- package/api/google/googleanalyticsMetadataGet.js +62 -0
- package/api/google/googleanalyticsRealtimeReportRun.js +103 -0
- package/api/google/googleanalyticsReportRun.js +114 -0
- package/api/shopify/shopify.utils.js +53 -4
- package/api/shopify/shopifyMetaobjectCreate.js +13 -6
- package/api/shopify/shopifyMetaobjectDelete.js +90 -0
- package/api/shopify/shopifyMetaobjectGet.js +80 -0
- package/api/shopify/shopifyMetaobjectUpdate.js +108 -0
- package/api/shopify/shopifyStorefrontSearch.js +158 -0
- package/api/spotify/docs.md +8 -0
- package/api/spotify/spotify.constants.js +12 -0
- package/api/spotify/spotify.utils.js +106 -0
- package/api/spotify/spotifyAlbumGet.js +76 -0
- package/api/spotify/spotifyAlbumTracksGet.js +49 -0
- package/api/spotify/spotifyArtistAlbumsGet.js +55 -0
- package/api/spotify/spotifyArtistGet.js +71 -0
- package/api/spotify/spotifyArtistTopTracksGet.js +56 -0
- package/api/spotify/spotifyGet.js +171 -0
- package/api/spotify/spotifyMeGet.js +48 -0
- package/api/spotify/spotifyPlayerCurrentlyPlayingGet.js +62 -0
- package/api/spotify/spotifyPlayerDevicesGet.js +51 -0
- package/api/spotify/spotifyPlayerGet.js +63 -0
- package/api/spotify/spotifyPlayerNext.js +55 -0
- package/api/spotify/spotifyPlayerPause.js +55 -0
- package/api/spotify/spotifyPlayerPlay.js +70 -0
- package/api/spotify/spotifyPlayerPrevious.js +55 -0
- package/api/spotify/spotifyPlaylistCreate.js +63 -0
- package/api/spotify/spotifyPlaylistGet.js +91 -0
- package/api/spotify/spotifyPlaylistItemsAdd.js +61 -0
- package/api/spotify/spotifyPlaylistItemsGet.js +58 -0
- package/api/spotify/spotifyPlaylistItemsRemove.js +67 -0
- package/api/spotify/spotifyPlaylistUpdate.js +64 -0
- package/api/spotify/spotifyPlaylistsGet.js +47 -0
- package/api/spotify/spotifySavedTracksGet.js +46 -0
- package/api/spotify/spotifySavedTracksRemove.js +59 -0
- package/api/spotify/spotifySavedTracksSave.js +59 -0
- package/api/spotify/spotifySearch.js +174 -0
- package/api/spotify/spotifyTrackGet.js +76 -0
- package/package.json +1 -1
- package/server.js +3 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// https://shopify.dev/docs/api/admin-graphql/latest/queries/metaobject
|
|
2
|
+
|
|
3
|
+
const { credsValidator } = require('../validators');
|
|
4
|
+
const { actionSingleOrMultiple, ArgsWarden } = require('../utils');
|
|
5
|
+
const { shopifyGetSingle } = require('../shopify/shopifyGetSingle');
|
|
6
|
+
|
|
7
|
+
const defaultAttrs = 'id handle type updatedAt capabilities { publishable { status } } fields { key value type }';
|
|
8
|
+
|
|
9
|
+
const argsWarden = new ArgsWarden([
|
|
10
|
+
['credsPayload', credsValidator],
|
|
11
|
+
['metaobjectId'],
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const shopifyMetaobjectGetSingle = async (
|
|
15
|
+
credsPayload,
|
|
16
|
+
metaobjectId,
|
|
17
|
+
{
|
|
18
|
+
apiVersion,
|
|
19
|
+
attrs = defaultAttrs,
|
|
20
|
+
} = {},
|
|
21
|
+
) => {
|
|
22
|
+
|
|
23
|
+
return shopifyGetSingle(
|
|
24
|
+
credsPayload,
|
|
25
|
+
'metaobject',
|
|
26
|
+
metaobjectId,
|
|
27
|
+
{
|
|
28
|
+
apiVersion,
|
|
29
|
+
attrs,
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const shopifyMetaobjectGet = async (
|
|
35
|
+
credsPayload,
|
|
36
|
+
metaobjectId,
|
|
37
|
+
{
|
|
38
|
+
queueRunOptions,
|
|
39
|
+
apiVersion,
|
|
40
|
+
attrs = defaultAttrs,
|
|
41
|
+
} = {},
|
|
42
|
+
) => {
|
|
43
|
+
|
|
44
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
45
|
+
credsPayload,
|
|
46
|
+
metaobjectId,
|
|
47
|
+
});
|
|
48
|
+
if (rejectResponse) {
|
|
49
|
+
return rejectResponse;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return actionSingleOrMultiple(
|
|
53
|
+
metaobjectId,
|
|
54
|
+
shopifyMetaobjectGetSingle,
|
|
55
|
+
(metaobjectIdItem) => ({
|
|
56
|
+
args: [credsPayload, metaobjectIdItem, { apiVersion, attrs }],
|
|
57
|
+
}),
|
|
58
|
+
{
|
|
59
|
+
...(queueRunOptions ? { queueRunOptions } : {}),
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const funcApiConfig = {
|
|
65
|
+
argsWarden,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
shopifyMetaobjectGet,
|
|
70
|
+
funcApiConfig,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
curl -X POST "http://localhost:8000/shopifyMetaobjectGet" \
|
|
75
|
+
-H "Content-Type: application/json" \
|
|
76
|
+
-d '{
|
|
77
|
+
"credsPayload": { "credsPath": "shopify.au" },
|
|
78
|
+
"metaobjectId": "207864102984"
|
|
79
|
+
}'
|
|
80
|
+
*/
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// https://shopify.dev/docs/api/admin-graphql/latest/mutations/metaobjectUpdate
|
|
2
|
+
|
|
3
|
+
const { credsValidator } = require('../validators');
|
|
4
|
+
const { actionSingleOrMultiple, everyIfArray, ArgsWarden, valueProvided } = require('../utils');
|
|
5
|
+
const { shopifyMutationDo } = require('../shopify/shopifyMutationDo');
|
|
6
|
+
|
|
7
|
+
const updatePayloadValidator = (updatePayload) => valueProvided(updatePayload)
|
|
8
|
+
&& typeof updatePayload === 'object';
|
|
9
|
+
|
|
10
|
+
const argsWarden = new ArgsWarden([
|
|
11
|
+
['credsPayload', credsValidator],
|
|
12
|
+
['metaobjectId', (i) => everyIfArray(valueProvided, i)],
|
|
13
|
+
['updatePayload', (i) => everyIfArray(updatePayloadValidator, i)],
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const defaultReturnMetaobjectAttrs = 'id handle type updatedAt capabilities { publishable { status } }';
|
|
17
|
+
|
|
18
|
+
const shopifyMetaobjectUpdateSingle = async (
|
|
19
|
+
credsPayload,
|
|
20
|
+
metaobjectId,
|
|
21
|
+
updatePayload,
|
|
22
|
+
{
|
|
23
|
+
apiVersion,
|
|
24
|
+
returnMetaobjectAttrs = defaultReturnMetaobjectAttrs,
|
|
25
|
+
} = {},
|
|
26
|
+
) => {
|
|
27
|
+
|
|
28
|
+
return shopifyMutationDo(
|
|
29
|
+
credsPayload,
|
|
30
|
+
'metaobjectUpdate',
|
|
31
|
+
{
|
|
32
|
+
mutationVariables: {
|
|
33
|
+
id: {
|
|
34
|
+
type: 'ID!',
|
|
35
|
+
value: `gid://shopify/Metaobject/${ metaobjectId }`,
|
|
36
|
+
},
|
|
37
|
+
metaobject: {
|
|
38
|
+
type: 'MetaobjectUpdateInput!',
|
|
39
|
+
value: updatePayload,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
returnSchema: `metaobject { ${ returnMetaobjectAttrs } }`,
|
|
43
|
+
apiVersion,
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const shopifyMetaobjectUpdate = async (
|
|
49
|
+
credsPayload,
|
|
50
|
+
metaobjectId,
|
|
51
|
+
updatePayload,
|
|
52
|
+
{
|
|
53
|
+
queueRunOptions,
|
|
54
|
+
apiVersion,
|
|
55
|
+
returnMetaobjectAttrs = defaultReturnMetaobjectAttrs,
|
|
56
|
+
} = {},
|
|
57
|
+
) => {
|
|
58
|
+
|
|
59
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
60
|
+
credsPayload,
|
|
61
|
+
metaobjectId,
|
|
62
|
+
updatePayload,
|
|
63
|
+
});
|
|
64
|
+
if (rejectResponse) {
|
|
65
|
+
return rejectResponse;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return actionSingleOrMultiple(
|
|
69
|
+
[metaobjectId, updatePayload],
|
|
70
|
+
shopifyMetaobjectUpdateSingle,
|
|
71
|
+
(metaobjectIdItem, updatePayloadItem) => ({
|
|
72
|
+
args: [
|
|
73
|
+
credsPayload,
|
|
74
|
+
metaobjectIdItem,
|
|
75
|
+
updatePayloadItem,
|
|
76
|
+
{ apiVersion, returnMetaobjectAttrs },
|
|
77
|
+
],
|
|
78
|
+
}),
|
|
79
|
+
{
|
|
80
|
+
...(queueRunOptions ? { queueRunOptions } : {}),
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const funcApiConfig = {
|
|
86
|
+
argsWarden,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
shopifyMetaobjectUpdate,
|
|
91
|
+
funcApiConfig,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
curl -X POST "http://localhost:8000/shopifyMetaobjectUpdate" \
|
|
96
|
+
-H "Content-Type: application/json" \
|
|
97
|
+
-d '{
|
|
98
|
+
"credsPayload": { "credsPath": "shopify.au" },
|
|
99
|
+
"metaobjectId": "207864102984",
|
|
100
|
+
"updatePayload": {
|
|
101
|
+
"capabilities": {
|
|
102
|
+
"publishable": {
|
|
103
|
+
"status": "ACTIVE"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}'
|
|
108
|
+
*/
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// https://shopify.dev/docs/api/storefront/latest/queries/search
|
|
2
|
+
|
|
3
|
+
const { credsValidator } = require('../validators');
|
|
4
|
+
const { ArgsWarden } = require('../utils');
|
|
5
|
+
const { shopifyStorefrontClient } = require('./shopify.utils');
|
|
6
|
+
const { MAX_PER_PAGE } = require('./shopify.constants');
|
|
7
|
+
|
|
8
|
+
const defaultAttrs = `
|
|
9
|
+
... on Product {
|
|
10
|
+
id
|
|
11
|
+
handle
|
|
12
|
+
title
|
|
13
|
+
}
|
|
14
|
+
... on Page {
|
|
15
|
+
id
|
|
16
|
+
handle
|
|
17
|
+
title
|
|
18
|
+
}
|
|
19
|
+
... on Article {
|
|
20
|
+
id
|
|
21
|
+
handle
|
|
22
|
+
title
|
|
23
|
+
}
|
|
24
|
+
`.trim();
|
|
25
|
+
|
|
26
|
+
const argsWarden = new ArgsWarden([
|
|
27
|
+
['credsPayload', credsValidator],
|
|
28
|
+
['query'],
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const shopifyStorefrontSearch = async (
|
|
32
|
+
credsPayload,
|
|
33
|
+
query,
|
|
34
|
+
{
|
|
35
|
+
apiVersion,
|
|
36
|
+
attrs = defaultAttrs,
|
|
37
|
+
first = MAX_PER_PAGE,
|
|
38
|
+
cursor,
|
|
39
|
+
reverse,
|
|
40
|
+
sortKey,
|
|
41
|
+
types,
|
|
42
|
+
prefix,
|
|
43
|
+
productFilters,
|
|
44
|
+
unavailableProducts,
|
|
45
|
+
fetchClient = shopifyStorefrontClient,
|
|
46
|
+
} = {},
|
|
47
|
+
) => {
|
|
48
|
+
|
|
49
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
50
|
+
credsPayload,
|
|
51
|
+
query,
|
|
52
|
+
});
|
|
53
|
+
if (rejectResponse) {
|
|
54
|
+
return rejectResponse;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const queryTypeDeclaration = [
|
|
58
|
+
'$query: String!',
|
|
59
|
+
'$first: Int!',
|
|
60
|
+
'$cursor: String',
|
|
61
|
+
...reverse !== undefined ? ['$reverse: Boolean'] : [],
|
|
62
|
+
...sortKey ? ['$sortKey: SearchSortKeys'] : [],
|
|
63
|
+
...types ? ['$types: [SearchType!]'] : [],
|
|
64
|
+
...prefix ? ['$prefix: SearchPrefixQueryType'] : [],
|
|
65
|
+
...productFilters ? ['$productFilters: [ProductFilter!]'] : [],
|
|
66
|
+
...unavailableProducts ? ['$unavailableProducts: SearchUnavailableProductsType'] : [],
|
|
67
|
+
].join('\n');
|
|
68
|
+
|
|
69
|
+
const queryVariableDeclaration = [
|
|
70
|
+
'query: $query',
|
|
71
|
+
'first: $first',
|
|
72
|
+
'after: $cursor',
|
|
73
|
+
...reverse !== undefined ? ['reverse: $reverse'] : [],
|
|
74
|
+
...sortKey ? ['sortKey: $sortKey'] : [],
|
|
75
|
+
...types ? ['types: $types'] : [],
|
|
76
|
+
...prefix ? ['prefix: $prefix'] : [],
|
|
77
|
+
...productFilters ? ['productFilters: $productFilters'] : [],
|
|
78
|
+
...unavailableProducts ? ['unavailableProducts: $unavailableProducts'] : [],
|
|
79
|
+
].join('\n');
|
|
80
|
+
|
|
81
|
+
const variables = {
|
|
82
|
+
query,
|
|
83
|
+
first,
|
|
84
|
+
cursor,
|
|
85
|
+
...reverse !== undefined && { reverse },
|
|
86
|
+
...sortKey && { sortKey },
|
|
87
|
+
...types && { types },
|
|
88
|
+
...prefix && { prefix },
|
|
89
|
+
...productFilters && { productFilters },
|
|
90
|
+
...unavailableProducts && { unavailableProducts },
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return fetchClient.fetch({
|
|
94
|
+
requestPayload: {
|
|
95
|
+
method: 'post',
|
|
96
|
+
body: {
|
|
97
|
+
query: `
|
|
98
|
+
query StorefrontSearch (
|
|
99
|
+
${ queryTypeDeclaration }
|
|
100
|
+
) {
|
|
101
|
+
search(
|
|
102
|
+
${ queryVariableDeclaration }
|
|
103
|
+
) {
|
|
104
|
+
totalCount
|
|
105
|
+
productFilters {
|
|
106
|
+
id
|
|
107
|
+
label
|
|
108
|
+
type
|
|
109
|
+
values {
|
|
110
|
+
id
|
|
111
|
+
label
|
|
112
|
+
count
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
edges {
|
|
116
|
+
node {
|
|
117
|
+
${ attrs }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
pageInfo {
|
|
121
|
+
hasNextPage
|
|
122
|
+
endCursor
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`,
|
|
127
|
+
variables,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
context: {
|
|
131
|
+
credsPayload,
|
|
132
|
+
apiVersion,
|
|
133
|
+
resultPath: 'data.search',
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const funcApiConfig = {
|
|
139
|
+
argsWarden,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
shopifyStorefrontSearch,
|
|
144
|
+
funcApiConfig,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/*
|
|
148
|
+
curl -X POST "http://localhost:8000/shopifyStorefrontSearch" \
|
|
149
|
+
-H "Content-Type: application/json" \
|
|
150
|
+
-d '{
|
|
151
|
+
"credsPayload": { "credsPath": "shopify.au" },
|
|
152
|
+
"query": "dress",
|
|
153
|
+
"options": {
|
|
154
|
+
"first": 5,
|
|
155
|
+
"types": ["PRODUCT"]
|
|
156
|
+
}
|
|
157
|
+
}'
|
|
158
|
+
*/
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Spotify
|
|
2
|
+
|
|
3
|
+
- [Web API](https://developer.spotify.com/documentation/web-api)
|
|
4
|
+
- [API reference](https://developer.spotify.com/documentation/web-api/reference)
|
|
5
|
+
- [Authorization](https://developer.spotify.com/documentation/web-api/concepts/authorization)
|
|
6
|
+
- [API calls](https://developer.spotify.com/documentation/web-api/concepts/api-calls)
|
|
7
|
+
|
|
8
|
+
REST JSON at `https://api.spotify.com/v1/...` with OAuth 2 bearer access tokens (`Authorization: Bearer`). Catalog and user endpoints use standard GET/POST/PUT/DELETE; many list responses are offset/limit paginated (`items`, `next`, `total`). User-scoped routes (playlists, library, player) need appropriate OAuth scopes; client-credentials tokens only cover public catalog data.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const SPOTIFY_API_BASE_URL = 'https://api.spotify.com/v1';
|
|
2
|
+
const SPOTIFY_ACCOUNTS_BASE_URL = 'https://accounts.spotify.com';
|
|
3
|
+
// Most list endpoints cap at 50; search max is lower (10 as of 2026 policy changes).
|
|
4
|
+
const MAX_PER_PAGE = 50;
|
|
5
|
+
const MAX_SEARCH_PER_PAGE = 10;
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
SPOTIFY_API_BASE_URL,
|
|
9
|
+
SPOTIFY_ACCOUNTS_BASE_URL,
|
|
10
|
+
MAX_PER_PAGE,
|
|
11
|
+
MAX_SEARCH_PER_PAGE,
|
|
12
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const {
|
|
2
|
+
SPOTIFY_API_BASE_URL,
|
|
3
|
+
SPOTIFY_ACCOUNTS_BASE_URL,
|
|
4
|
+
} = require('../spotify/spotify.constants');
|
|
5
|
+
const { resolveCreds, useBaseUrl } = require('../pipelineSteps');
|
|
6
|
+
const {
|
|
7
|
+
FetchClient,
|
|
8
|
+
fetchClientCommonSteps,
|
|
9
|
+
customFetch,
|
|
10
|
+
credsFromPayload,
|
|
11
|
+
} = require('../utils');
|
|
12
|
+
|
|
13
|
+
// Cache client-credentials tokens per CLIENT_ID so we are not minting on every call.
|
|
14
|
+
const clientCredentialsTokenCache = new Map();
|
|
15
|
+
|
|
16
|
+
const getClientCredentialsToken = async ({ CLIENT_ID, CLIENT_SECRET }) => {
|
|
17
|
+
const cacheKey = CLIENT_ID;
|
|
18
|
+
const cached = clientCredentialsTokenCache.get(cacheKey);
|
|
19
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
20
|
+
return cached.accessToken;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const basic = Buffer
|
|
24
|
+
.from(`${ CLIENT_ID }:${ CLIENT_SECRET }`)
|
|
25
|
+
.toString('base64');
|
|
26
|
+
|
|
27
|
+
const response = await customFetch(
|
|
28
|
+
`${ SPOTIFY_ACCOUNTS_BASE_URL }/api/token`,
|
|
29
|
+
{
|
|
30
|
+
method: 'post',
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Basic ${ basic }`,
|
|
33
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
34
|
+
},
|
|
35
|
+
body: 'grant_type=client_credentials',
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Spotify client_credentials failed: ${ JSON.stringify(response.error ?? response.data) }`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { access_token: accessToken, expires_in: expiresIn = 3600 } = response.data ?? {};
|
|
46
|
+
if (!accessToken) {
|
|
47
|
+
throw new Error('Spotify client_credentials response missing access_token');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
clientCredentialsTokenCache.set(cacheKey, {
|
|
51
|
+
accessToken,
|
|
52
|
+
// Refresh a minute early.
|
|
53
|
+
expiresAt: Date.now() + (expiresIn - 60) * 1000,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return accessToken;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const resolveAccessToken = async (creds) => {
|
|
60
|
+
if (creds?.ACCESS_TOKEN) {
|
|
61
|
+
return creds.ACCESS_TOKEN;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { CLIENT_ID, CLIENT_SECRET } = creds ?? {};
|
|
65
|
+
if (CLIENT_ID && CLIENT_SECRET) {
|
|
66
|
+
return getClientCredentialsToken({ CLIENT_ID, CLIENT_SECRET });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw new Error(
|
|
70
|
+
'spotify creds require ACCESS_TOKEN or CLIENT_ID + CLIENT_SECRET',
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const useAuthHeaders = async (state) => {
|
|
75
|
+
const { requestPayload, context } = state;
|
|
76
|
+
const { creds } = context;
|
|
77
|
+
|
|
78
|
+
const accessToken = await resolveAccessToken(creds);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
requestPayload: {
|
|
82
|
+
...requestPayload,
|
|
83
|
+
headers: {
|
|
84
|
+
Accept: 'application/json',
|
|
85
|
+
Authorization: `Bearer ${ accessToken }`,
|
|
86
|
+
...requestPayload.headers,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const spotifyClient = new FetchClient({
|
|
93
|
+
pipeline: [
|
|
94
|
+
resolveCreds,
|
|
95
|
+
useBaseUrl(SPOTIFY_API_BASE_URL),
|
|
96
|
+
useAuthHeaders,
|
|
97
|
+
'fetch',
|
|
98
|
+
fetchClientCommonSteps.exitEarlyOnNotOk,
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
spotifyClient,
|
|
104
|
+
resolveAccessToken,
|
|
105
|
+
getClientCredentialsToken,
|
|
106
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// https://developer.spotify.com/documentation/web-api/reference/get-an-album
|
|
2
|
+
|
|
3
|
+
const { ArgsWarden, actionSingleOrMultiple, logDeep } = require('../utils');
|
|
4
|
+
const { credsValidator } = require('../validators');
|
|
5
|
+
const { spotifyClient } = require('../spotify/spotify.utils');
|
|
6
|
+
|
|
7
|
+
const argsWarden = new ArgsWarden([
|
|
8
|
+
['credsPayload', credsValidator],
|
|
9
|
+
['albumId'],
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
const spotifyAlbumGetSingle = async (
|
|
13
|
+
credsPayload,
|
|
14
|
+
albumId,
|
|
15
|
+
{
|
|
16
|
+
market,
|
|
17
|
+
fetchClient = spotifyClient,
|
|
18
|
+
} = {},
|
|
19
|
+
) => {
|
|
20
|
+
const response = await fetchClient.fetch({
|
|
21
|
+
context: { credsPayload },
|
|
22
|
+
requestPayload: {
|
|
23
|
+
method: 'get',
|
|
24
|
+
url: `/albums/${ albumId }`,
|
|
25
|
+
params: {
|
|
26
|
+
...(market !== undefined && { market }),
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const { ok, data, error } = response;
|
|
32
|
+
if (!ok) {
|
|
33
|
+
logDeep({ error });
|
|
34
|
+
return { ok: false, error };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { ok: true, data };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const spotifyAlbumGet = async (
|
|
41
|
+
credsPayload,
|
|
42
|
+
albumId,
|
|
43
|
+
{
|
|
44
|
+
market,
|
|
45
|
+
queueRunOptions,
|
|
46
|
+
fetchClient,
|
|
47
|
+
} = {},
|
|
48
|
+
) => {
|
|
49
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
50
|
+
credsPayload,
|
|
51
|
+
albumId,
|
|
52
|
+
});
|
|
53
|
+
if (rejectResponse) {
|
|
54
|
+
return rejectResponse;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return actionSingleOrMultiple(
|
|
58
|
+
albumId,
|
|
59
|
+
spotifyAlbumGetSingle,
|
|
60
|
+
(albumIdItem) => ({
|
|
61
|
+
args: [credsPayload, albumIdItem, { market, fetchClient }],
|
|
62
|
+
}),
|
|
63
|
+
{
|
|
64
|
+
...(queueRunOptions ? { queueRunOptions } : {}),
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const funcApiConfig = {
|
|
70
|
+
argsWarden,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
spotifyAlbumGet,
|
|
75
|
+
funcApiConfig,
|
|
76
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// https://developer.spotify.com/documentation/web-api/reference/get-an-albums-tracks
|
|
2
|
+
|
|
3
|
+
const { ArgsWarden } = require('../utils');
|
|
4
|
+
const { credsValidator } = require('../validators');
|
|
5
|
+
const { spotifyGet } = require('../spotify/spotifyGet');
|
|
6
|
+
const { MAX_PER_PAGE } = require('../spotify/spotify.constants');
|
|
7
|
+
|
|
8
|
+
const argsWarden = new ArgsWarden([
|
|
9
|
+
['credsPayload', credsValidator],
|
|
10
|
+
['albumId'],
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const spotifyAlbumTracksGet = async (
|
|
14
|
+
credsPayload,
|
|
15
|
+
albumId,
|
|
16
|
+
{
|
|
17
|
+
market,
|
|
18
|
+
perPage = MAX_PER_PAGE,
|
|
19
|
+
fetchClient,
|
|
20
|
+
...getterOptions
|
|
21
|
+
} = {},
|
|
22
|
+
) => {
|
|
23
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
24
|
+
credsPayload,
|
|
25
|
+
albumId,
|
|
26
|
+
});
|
|
27
|
+
if (rejectResponse) {
|
|
28
|
+
return rejectResponse;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return spotifyGet(credsPayload, `/albums/${ albumId }/tracks`, {
|
|
32
|
+
params: {
|
|
33
|
+
...(market !== undefined && { market }),
|
|
34
|
+
},
|
|
35
|
+
perPage,
|
|
36
|
+
resultsKey: 'items',
|
|
37
|
+
fetchClient,
|
|
38
|
+
...getterOptions,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const funcApiConfig = {
|
|
43
|
+
argsWarden,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
spotifyAlbumTracksGet,
|
|
48
|
+
funcApiConfig,
|
|
49
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// https://developer.spotify.com/documentation/web-api/reference/get-an-artists-albums
|
|
2
|
+
|
|
3
|
+
const { ArgsWarden } = require('../utils');
|
|
4
|
+
const { credsValidator } = require('../validators');
|
|
5
|
+
const { spotifyGet } = require('../spotify/spotifyGet');
|
|
6
|
+
const { MAX_PER_PAGE } = require('../spotify/spotify.constants');
|
|
7
|
+
|
|
8
|
+
const argsWarden = new ArgsWarden([
|
|
9
|
+
['credsPayload', credsValidator],
|
|
10
|
+
['artistId'],
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const spotifyArtistAlbumsGet = async (
|
|
14
|
+
credsPayload,
|
|
15
|
+
artistId,
|
|
16
|
+
{
|
|
17
|
+
includeGroups,
|
|
18
|
+
market,
|
|
19
|
+
perPage = MAX_PER_PAGE,
|
|
20
|
+
fetchClient,
|
|
21
|
+
...getterOptions
|
|
22
|
+
} = {},
|
|
23
|
+
) => {
|
|
24
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
25
|
+
credsPayload,
|
|
26
|
+
artistId,
|
|
27
|
+
});
|
|
28
|
+
if (rejectResponse) {
|
|
29
|
+
return rejectResponse;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return spotifyGet(credsPayload, `/artists/${ artistId }/albums`, {
|
|
33
|
+
params: {
|
|
34
|
+
...(includeGroups !== undefined && {
|
|
35
|
+
include_groups: Array.isArray(includeGroups)
|
|
36
|
+
? includeGroups.join(',')
|
|
37
|
+
: includeGroups,
|
|
38
|
+
}),
|
|
39
|
+
...(market !== undefined && { market }),
|
|
40
|
+
},
|
|
41
|
+
perPage,
|
|
42
|
+
resultsKey: 'items',
|
|
43
|
+
fetchClient,
|
|
44
|
+
...getterOptions,
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const funcApiConfig = {
|
|
49
|
+
argsWarden,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
spotifyArtistAlbumsGet,
|
|
54
|
+
funcApiConfig,
|
|
55
|
+
};
|