@foxtware/mineral 0.1.26 → 0.1.27
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 +5 -4
- package/api/tagalys/docs.md +1 -1
- package/api/tagalys/tagalysSearch.js +156 -0
- package/package.json +1 -1
package/.creds.yml.sample
CHANGED
|
@@ -115,10 +115,11 @@ google:
|
|
|
115
115
|
|
|
116
116
|
tagalys:
|
|
117
117
|
store:
|
|
118
|
-
BASE_URL: ____________________________
|
|
119
|
-
CLIENT_CODE: _________________________
|
|
120
|
-
API_KEY: _____________________________
|
|
121
|
-
|
|
118
|
+
BASE_URL: ____________________________
|
|
119
|
+
CLIENT_CODE: _________________________ # unused but surfaced in dashboard
|
|
120
|
+
API_KEY: _____________________________ # unused but surfaced in dashboard
|
|
121
|
+
STOREFRONT_API_KEY: __________________
|
|
122
|
+
STORE_HANDLE: ________________________
|
|
122
123
|
|
|
123
124
|
tableau:
|
|
124
125
|
PAT_NAME: ______________________________
|
package/api/tagalys/docs.md
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
- [Storefront API (v2)](https://tagalys.notion.site/Storefront-API-v2-20eacdd38c2080d58d3fd0cf6f24e435)
|
|
4
4
|
|
|
5
|
-
Region-specific HTTPS origins (`https://api-r{n}.tagalys.com`) with REST-style JSON under `/v2`. Storefront reads use GET; analytics ingest uses `POST /v2/analytics/events` with a JSON body. Browser requests authenticate with the `shop_id` query param (the shop’s `*.myshopify.com` domain). Server-side integrations should
|
|
5
|
+
Region-specific HTTPS origins (`https://api-r{n}.tagalys.com`) with REST-style JSON under `/v2`. Storefront reads use GET; analytics ingest uses `POST /v2/analytics/events` with a JSON body. Browser requests authenticate with the `shop_id` query param (the shop’s `*.myshopify.com` domain). Server-side integrations should pass `api_key`.
|
|
6
6
|
|
|
7
7
|
Endpoints: `GET /v2/collections/:collection_id`, `GET /v2/search`, `GET /v2/search_suggestions`, `GET /v2/popular_searches`, `GET /v2/recommendations/:recommendation_id`, `POST /v2/analytics/events`. Collections and search support pagination (`page`, `per_page`, capped at 10,000 products), sorting (`sort`, `include[]=sort_options`), filtering (`filter[...]`, `include[]=filters`), and scope (`scope[...]`). Use `include[]=products` or `include[]=product_ids` to control product payloads. Optional request context on collections, search, and recommendations: `country`, `language`, `segment_tag`. Errors return JSON with `error.type`, `error.code`, and `error.message`.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// https://tagalys.notion.site/Storefront-API-v2-20eacdd38c2080d58d3fd0cf6f24e435
|
|
2
|
+
|
|
3
|
+
const { credsValidator } = require('../validators');
|
|
4
|
+
const { ArgsWarden, logDeep } = require('../utils');
|
|
5
|
+
const { resolveCreds } = require('../pipelineSteps');
|
|
6
|
+
const {
|
|
7
|
+
FetchClient,
|
|
8
|
+
fetchClientCommonSteps,
|
|
9
|
+
} = require('../utils');
|
|
10
|
+
|
|
11
|
+
// NOTE: the Storefront API v2 docs authenticate with `shop_id` (the
|
|
12
|
+
// Shopify myshopify.com domain) + an optional `storefront_api_key`, and
|
|
13
|
+
// don't mention a client_code param on this endpoint at all -- that's a
|
|
14
|
+
// holdover from Tagalys' older v1 API. Mapped below as best guess:
|
|
15
|
+
// BASE_URL -> used directly (docs say it's region-specific,
|
|
16
|
+
// e.g. https://api-r1.tagalys.com -- assumed already
|
|
17
|
+
// resolved to the right region in the creds store)
|
|
18
|
+
// STORE_HANDLE -> expanded to the `shop_id` myshopify domain
|
|
19
|
+
// STOREFRONT_API_KEY -> sent as `api_key`
|
|
20
|
+
// CLIENT_CODE -> not used by /v2/search per the docs; left unused below.
|
|
21
|
+
// Flag if your account actually needs it sent somewhere.
|
|
22
|
+
const DEFAULT_INCLUDE = ['products', 'total_count'];
|
|
23
|
+
|
|
24
|
+
// Recursively flattens nested params into Rails/PHP-style bracket query
|
|
25
|
+
// params, e.g. { filter: { color: ['red'] } } -> filter[color][]=red
|
|
26
|
+
// and { filter: { price: { selected_min: 100 } } } -> filter[price][selected_min]=100
|
|
27
|
+
const appendParams = (searchParams, key, value) => {
|
|
28
|
+
if (value === undefined || value === null) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
value.forEach((item) => appendParams(searchParams, `${ key }[]`, item));
|
|
33
|
+
} else if (typeof value === 'object') {
|
|
34
|
+
Object.entries(value).forEach(([subKey, subValue]) => {
|
|
35
|
+
appendParams(searchParams, `${ key }[${ subKey }]`, subValue);
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
searchParams.append(key, value);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const useUrlAndQuery = async (state) => {
|
|
43
|
+
const { requestPayload, context } = state;
|
|
44
|
+
const { creds } = context;
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
BASE_URL,
|
|
48
|
+
STORE_HANDLE,
|
|
49
|
+
STOREFRONT_API_KEY,
|
|
50
|
+
} = creds;
|
|
51
|
+
const shopId = `${ STORE_HANDLE }.myshopify.com`;
|
|
52
|
+
const storefrontApiKey = STOREFRONT_API_KEY;
|
|
53
|
+
|
|
54
|
+
const searchParams = new URLSearchParams();
|
|
55
|
+
searchParams.append('shop_id', shopId);
|
|
56
|
+
if (storefrontApiKey) {
|
|
57
|
+
searchParams.append('api_key', storefrontApiKey);
|
|
58
|
+
}
|
|
59
|
+
Object.entries(requestPayload.query || {}).forEach(([key, value]) => {
|
|
60
|
+
appendParams(searchParams, key, value);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
requestPayload: {
|
|
65
|
+
...requestPayload,
|
|
66
|
+
url: `${ BASE_URL }${ requestPayload.url }?${ searchParams.toString() }`,
|
|
67
|
+
headers: {
|
|
68
|
+
Accept: 'application/json',
|
|
69
|
+
...requestPayload.headers,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const tagalysClient = new FetchClient({
|
|
76
|
+
pipeline: [
|
|
77
|
+
resolveCreds,
|
|
78
|
+
useUrlAndQuery,
|
|
79
|
+
'fetch',
|
|
80
|
+
fetchClientCommonSteps.exitEarlyOnNotOk,
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const argsWarden = new ArgsWarden([
|
|
85
|
+
['credsPayload', credsValidator],
|
|
86
|
+
['query'],
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const tagalysSearch = async (
|
|
90
|
+
credsPayload,
|
|
91
|
+
query,
|
|
92
|
+
{
|
|
93
|
+
include = DEFAULT_INCLUDE, // e.g. ['products', 'filters', 'sort_options', 'total_count']
|
|
94
|
+
filter, // e.g. { color: ['red'], price: { selected_min: 100, selected_max: 200 } }
|
|
95
|
+
scope, // e.g. { gender: ['female'] }
|
|
96
|
+
sort, // e.g. 'price-asc'
|
|
97
|
+
page,
|
|
98
|
+
perPage,
|
|
99
|
+
} = {},
|
|
100
|
+
) => {
|
|
101
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
102
|
+
credsPayload,
|
|
103
|
+
query,
|
|
104
|
+
});
|
|
105
|
+
if (rejectResponse) {
|
|
106
|
+
return rejectResponse;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const response = await tagalysClient.fetch({
|
|
110
|
+
context: {
|
|
111
|
+
credsPayload,
|
|
112
|
+
},
|
|
113
|
+
requestPayload: {
|
|
114
|
+
url: '/v2/search',
|
|
115
|
+
method: 'get',
|
|
116
|
+
query: {
|
|
117
|
+
query,
|
|
118
|
+
include,
|
|
119
|
+
...(filter ? { filter } : {}),
|
|
120
|
+
...(scope ? { scope } : {}),
|
|
121
|
+
...(sort ? { sort } : {}),
|
|
122
|
+
...(page ? { page } : {}),
|
|
123
|
+
...(perPage ? { per_page: perPage } : {}),
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const { ok, data, error } = response;
|
|
129
|
+
if (!ok) {
|
|
130
|
+
logDeep({ error });
|
|
131
|
+
return { ok: false, error };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// A configured redirect takes precedence over the rest of the response --
|
|
135
|
+
// surface it plainly rather than making callers dig for it.
|
|
136
|
+
if (data.redirect_url) {
|
|
137
|
+
return { ok: true, redirected: true, redirectUrl: data.redirect_url, data };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { ok: true, data };
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const funcApiConfig = {
|
|
144
|
+
argsWarden,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
tagalysSearch,
|
|
149
|
+
tagalysClient,
|
|
150
|
+
funcApiConfig,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/*
|
|
154
|
+
curl -X POST "http://localhost:8000/tagalysSearch" \
|
|
155
|
+
-d '{ "credsPayload": { "credsPath": "tagalys.au" }, "query": "gold" }'
|
|
156
|
+
*/
|