mango-cms 0.2.32 → 0.2.41
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/cli.js +1 -1
- package/default/mango/config/.endpoints.json +14 -0
- package/default/mango/config/settings.json +1 -0
- package/default/mango/helpers/syncDbWithLocal.sh +31 -0
- package/default/mango/hooks/subscribe.js +2 -1
- package/default/package.json +1 -1
- package/default/src/helpers/mango.js +543 -506
- package/default/vite.config.js +6 -3
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -696,7 +696,7 @@ program
|
|
|
696
696
|
fs.removeSync(srcDir);
|
|
697
697
|
}
|
|
698
698
|
|
|
699
|
-
const zipFileName = `src-v${version}.zip`;
|
|
699
|
+
const zipFileName = `src-v${version}.zip?v=${Date.now()}`;
|
|
700
700
|
console.log(`Downloading Mango CMS source files (version ${version})...`);
|
|
701
701
|
|
|
702
702
|
const response = await axios({
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Get the directory of this script
|
|
4
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
5
|
+
SETTINGS_FILE="$SCRIPT_DIR/../config/settings.json"
|
|
6
|
+
|
|
7
|
+
# Read serverIp from settings.json
|
|
8
|
+
SERVER_IP=$(grep -o '"serverIp"[[:space:]]*:[[:space:]]*"[^"]*"' "$SETTINGS_FILE" | sed 's/.*"\([^"]*\)".*/\1/')
|
|
9
|
+
|
|
10
|
+
# Check if serverIp is null or not set
|
|
11
|
+
if grep -q '"serverIp"[[:space:]]*:[[:space:]]*null' "$SETTINGS_FILE"; then
|
|
12
|
+
echo "Error: serverIp is set to null in settings.json"
|
|
13
|
+
echo "Please configure the server IP address in $SETTINGS_FILE"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if [ -z "$SERVER_IP" ]; then
|
|
18
|
+
echo "Error: serverIp not found in settings.json"
|
|
19
|
+
echo "Please add serverIp to $SETTINGS_FILE"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
echo "Using server IP: $SERVER_IP"
|
|
24
|
+
|
|
25
|
+
cd ~/Downloads;
|
|
26
|
+
ssh root@$SERVER_IP 'rm -rf dump.zip; mongodump; zip -r dump.zip dump'
|
|
27
|
+
rsync root@$SERVER_IP:~/Downloads/dump.zip ~/Downloads/dump.zip;
|
|
28
|
+
unzip -o dump.zip;
|
|
29
|
+
mongorestore --drop dump;
|
|
30
|
+
rm -rf dump;
|
|
31
|
+
rm -rf dump.zip;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const subscribe = ({ io, collection, document, request, individual, originalDocument }) => {
|
|
2
2
|
let method = request.method
|
|
3
|
-
if (collection
|
|
3
|
+
if (collection?.subscribe && method != 'read' && individual) {
|
|
4
4
|
|
|
5
5
|
const subscription = io.of(collection.name);
|
|
6
6
|
|
|
@@ -10,6 +10,7 @@ const subscribe = ({ io, collection, document, request, individual, originalDocu
|
|
|
10
10
|
subscription.to((document?.id||originalDocument?.id)).emit(`${collection.name}:${method}d`, payload);
|
|
11
11
|
|
|
12
12
|
// Send to each custom room
|
|
13
|
+
console.log(collection.subscribe)
|
|
13
14
|
for (let room of (collection.subscribe?.rooms||[])) {
|
|
14
15
|
let keys = room.split('.')
|
|
15
16
|
let target = document
|
package/default/package.json
CHANGED
|
@@ -1,553 +1,590 @@
|
|
|
1
1
|
// import collections from '../../../mango/config/.collections.json'
|
|
2
2
|
// import { algoliaAppId, algoliaSearchKey, algoliaIndex, port, domain } from '../../../mango/config/settings'
|
|
3
3
|
import collections from '@collections'
|
|
4
|
+
import endpointsData from '@endpoints'
|
|
4
5
|
import { algoliaAppId, algoliaSearchKey, algoliaIndex, port, mangoDomain, useDevAPI } from '@settings'
|
|
5
|
-
import axios from
|
|
6
|
+
import axios from 'axios'
|
|
6
7
|
import { ref } from 'vue'
|
|
7
8
|
import algoliasearch from 'algoliasearch/dist/algoliasearch-lite.esm.browser'
|
|
8
9
|
import LocalDB from './localDB'
|
|
9
|
-
import { io } from 'socket.io-client'
|
|
10
|
+
import { io } from 'socket.io-client'
|
|
10
11
|
|
|
11
12
|
import { useRoute } from 'vue-router'
|
|
12
13
|
let route = useRoute()
|
|
13
14
|
|
|
14
|
-
let endpoints = {
|
|
15
|
-
authors: ['get'],
|
|
16
|
-
// scripture: { validate: ['post'] }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
// console.log('collections', collections)
|
|
20
16
|
|
|
21
|
-
const client = algoliasearch(algoliaAppId, algoliaSearchKey)
|
|
22
|
-
const algolia = client.initIndex(algoliaIndex)
|
|
17
|
+
const client = algoliasearch(algoliaAppId, algoliaSearchKey)
|
|
18
|
+
const algolia = client.initIndex(algoliaIndex)
|
|
23
19
|
|
|
24
20
|
let api = `https://${mangoDomain}`
|
|
25
21
|
let ws = `wss://${mangoDomain}/graphql`
|
|
26
22
|
|
|
27
23
|
if (process.env.NODE_ENV != 'production' && useDevAPI) {
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
api = `http://localhost:${port}`
|
|
25
|
+
ws = `ws://localhost:${port}/graphql`
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
function getQuery(params) {
|
|
29
|
+
if (params.search != undefined) params.search = JSON.stringify(params.search)
|
|
30
|
+
if (params.fields != undefined) params.fields = JSON.stringify(params.fields)
|
|
31
|
+
if (params.sort != undefined) params.sort = JSON.stringify(params.sort)
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Object.keys(params)
|
|
42
|
-
.filter((key) => params[key] != undefined)
|
|
43
|
-
?.map(
|
|
44
|
-
(key) =>
|
|
45
|
-
`${encodeURIComponent(key)}=${encodeURIComponent(
|
|
46
|
-
params[key]
|
|
47
|
-
)}`
|
|
48
|
-
)
|
|
49
|
-
?.join("&") || "";
|
|
50
|
-
|
|
51
|
-
return query
|
|
33
|
+
const query =
|
|
34
|
+
Object.keys(params)
|
|
35
|
+
.filter((key) => params[key] != undefined)
|
|
36
|
+
?.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
|
37
|
+
?.join('&') || ''
|
|
38
|
+
|
|
39
|
+
return query
|
|
52
40
|
}
|
|
53
41
|
|
|
54
42
|
const Mango = collections.reduce((a, c) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
43
|
+
let localDB = new LocalDB(c.name, api)
|
|
44
|
+
|
|
45
|
+
let runQuery = ({ limit, page, search, fields, id, sort, depthLimit, verbose } = {}) => {
|
|
46
|
+
let fullQuery
|
|
47
|
+
|
|
48
|
+
const query = getQuery({ limit, page, search, fields, sort, depthLimit, verbose })
|
|
49
|
+
|
|
50
|
+
fullQuery = `${api}/${c.name}/${id || ''}?${query}`
|
|
51
|
+
|
|
52
|
+
let Authorization = window.localStorage.getItem('token')
|
|
53
|
+
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
// If it's a local entry
|
|
56
|
+
if (id && !isNaN(id)) {
|
|
57
|
+
id = Number(id)
|
|
58
|
+
let request = window.indexedDB.open(c.name, 1)
|
|
59
|
+
request.onsuccess = (e) => {
|
|
60
|
+
let db = e.target.result
|
|
61
|
+
let transaction = db.transaction([c.name], 'readwrite')
|
|
62
|
+
let store = transaction.objectStore(c.name)
|
|
63
|
+
let entryRequest = store.get(id)
|
|
64
|
+
entryRequest.onsuccess = (e) => {
|
|
65
|
+
let result = entryRequest.result
|
|
66
|
+
if (result) resolve(result)
|
|
67
|
+
else reject('No entry found')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
axios
|
|
72
|
+
.get(fullQuery, { headers: { Authorization } })
|
|
73
|
+
.then((response) => resolve(verbose ? response?.data : response?.data?.response))
|
|
74
|
+
.catch((e) => reject(e))
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let runGraphql = (query) => {
|
|
80
|
+
let Authorization = window.localStorage.getItem('token')
|
|
81
|
+
query = { query }
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
axios
|
|
84
|
+
.post(`${api}/graphql`, query, { headers: { Authorization } })
|
|
85
|
+
.then((response) => resolve(response?.data?.data))
|
|
86
|
+
.catch((e) => reject(e))
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let runAlgolia = (search, query, algoliaFilters) => {
|
|
91
|
+
search = search || ''
|
|
92
|
+
|
|
93
|
+
let filters = `collection:${c.name}`
|
|
94
|
+
if (algoliaFilters) filters += ` AND ${algoliaFilters}`
|
|
95
|
+
|
|
96
|
+
let algoliaQuery = {
|
|
97
|
+
page: query?.page || 0,
|
|
98
|
+
filters,
|
|
99
|
+
hitsPerPage: query?.limit || 10,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (query?.fields) algoliaQuery.attributesToRetrieve = query.fields
|
|
103
|
+
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
algolia.search(search, algoliaQuery).then(({ hits, nbHits }) => {
|
|
106
|
+
hits.forEach((h) => (h.id = h.objectID))
|
|
107
|
+
resolve({ hits, nbHits })
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let runNativeSearch = (search, query = {}) => {
|
|
113
|
+
search = search || ''
|
|
114
|
+
|
|
115
|
+
if (search) {
|
|
116
|
+
query.search = {
|
|
117
|
+
...(query.search || {}),
|
|
118
|
+
$or: [
|
|
119
|
+
{ title: { $regex: search, $options: 'i' } },
|
|
120
|
+
{ name: { $regex: search, $options: 'i' } },
|
|
121
|
+
{ content: { $regex: search, $options: 'i' } },
|
|
122
|
+
{ summary: { $regex: search, $options: 'i' } },
|
|
123
|
+
{ description: { $regex: search, $options: 'i' } },
|
|
124
|
+
{ body: { $regex: search, $options: 'i' } },
|
|
125
|
+
],
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
query.verbose = true
|
|
130
|
+
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
runQuery(query).then((response) => {
|
|
133
|
+
let res = { hits: response?.response, nbHits: response?.count }
|
|
134
|
+
console.log('res', res)
|
|
135
|
+
resolve(res)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let search = (search, query, algoliaFilters) => {
|
|
141
|
+
if (algoliaAppId) return runAlgolia(search, query, algoliaFilters)
|
|
142
|
+
return runNativeSearch(search, query)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let mangoSave = (data, options = {}) => {
|
|
146
|
+
let { id } = data
|
|
147
|
+
let method = id ? 'put' : 'post'
|
|
148
|
+
|
|
149
|
+
// // Remove _id and computed fields
|
|
150
|
+
delete data.collection
|
|
151
|
+
delete data._id
|
|
152
|
+
delete data.id
|
|
153
|
+
|
|
154
|
+
for (let field of c.fields) {
|
|
155
|
+
if (field.computed) delete data[field.name]
|
|
156
|
+
if (field.relationship)
|
|
157
|
+
data[field.name] = Array.isArray(data[field.name]) ? data[field.name].map((r) => r?.id || r) : data[field.name]?.id ? data[field.name].id : data[field.name]
|
|
158
|
+
}
|
|
159
|
+
for (let name in data) {
|
|
160
|
+
if (name.includes('__')) delete data[name]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let payload = { ...data }
|
|
164
|
+
let Authorization = window.localStorage.getItem('token')
|
|
165
|
+
let headers = {
|
|
166
|
+
Authorization,
|
|
167
|
+
...options.headers,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
axios[method](`${api}/${c.name}/${id || ''}`, payload, { headers })
|
|
172
|
+
.then((response) => resolve(response?.data?.response))
|
|
173
|
+
.catch((e) => reject(e))
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let save = (data, options = { bypassLocal: true }) => {
|
|
178
|
+
if (!options.bypassLocal) return localDB.save(save, data, options)
|
|
179
|
+
else return mangoSave(data, options)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let deleteEntry = (data) => {
|
|
183
|
+
let id = data.id || data
|
|
184
|
+
|
|
185
|
+
let Authorization = window.localStorage.getItem('token')
|
|
186
|
+
|
|
187
|
+
return new Promise((resolve, reject) => {
|
|
188
|
+
axios
|
|
189
|
+
.delete(`${api}/${c.name}/${id || ''}`, { headers: { Authorization } })
|
|
190
|
+
.then((response) => resolve(response?.data))
|
|
191
|
+
.catch((e) => reject(e))
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let sync = () => {
|
|
196
|
+
let remainingEntries = ref([])
|
|
197
|
+
let syncedEntries = ref([])
|
|
198
|
+
let online = ref(navigator.onLine)
|
|
199
|
+
let syncing = ref(false)
|
|
200
|
+
|
|
201
|
+
setInterval(async () => {
|
|
202
|
+
// console.log('syncing', syncing.value, route?.params?.id)
|
|
203
|
+
online.value = navigator.onLine
|
|
204
|
+
|
|
205
|
+
if (syncing.value) return
|
|
206
|
+
|
|
207
|
+
syncing.value = true
|
|
208
|
+
|
|
209
|
+
let entries = await localDB.getEntries()
|
|
210
|
+
remainingEntries.value = entries?.filter((e) => new Date() - new Date(e.updatedLocally) > 30 * 1000)
|
|
211
|
+
|
|
212
|
+
for (let [index, entry] of remainingEntries.value.entries()) {
|
|
213
|
+
// Don't sync what we're actively working on
|
|
214
|
+
if (route?.params?.id == entry.id || (!isNaN(entry.id) && window.location.pathname.includes(`/${entry.id}`))) {
|
|
215
|
+
console.log('skipping', entry.id)
|
|
216
|
+
continue
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
let response = await save(entry, { syncing: true })
|
|
221
|
+
if (response?.id) remainingEntries.value.splice(index, 1)
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.log('Error saving entry', e, entry)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
await new Promise((resolve) => setTimeout(resolve, 10 * 1000))
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
syncing.value = false
|
|
230
|
+
syncedEntries.value = []
|
|
231
|
+
}, 500)
|
|
232
|
+
|
|
233
|
+
return { remainingEntries, syncedEntries, online, syncing }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let subscribe = ({ target, triggers, room } = {}) => {
|
|
237
|
+
let socket = io(`${api}/${c.name}`, { transports: ['websocket'] })
|
|
238
|
+
let userId = window.localStorage.getItem('token').split(':')[1]
|
|
239
|
+
|
|
240
|
+
room = room || userId
|
|
241
|
+
socket.emit('subscribeToThread', room)
|
|
242
|
+
|
|
243
|
+
triggers = triggers || {}
|
|
244
|
+
let defaultTriggers = {
|
|
245
|
+
created: (data) => {
|
|
246
|
+
if (Array.isArray(target)) target.push(data)
|
|
247
|
+
else Object.assign(target, data)
|
|
248
|
+
},
|
|
249
|
+
updated: (data) => {
|
|
250
|
+
if (Array.isArray(target)) {
|
|
251
|
+
const index = target.findIndex((t) => t.id == data.id)
|
|
252
|
+
if (index !== -1) {
|
|
253
|
+
Object.assign(target[index], data)
|
|
254
|
+
}
|
|
255
|
+
} else if (target.id == data.id) Object.assign(target, data)
|
|
256
|
+
},
|
|
257
|
+
deleted: (data) => {
|
|
258
|
+
if (Array.isArray(target)) {
|
|
259
|
+
const index = target.findIndex((t) => t.id == data.id)
|
|
260
|
+
if (index !== -1) {
|
|
261
|
+
target.splice(index, 1) // Mutates the existing array
|
|
262
|
+
}
|
|
263
|
+
} else if (target.id == data.id) {
|
|
264
|
+
// Clear the object properties while maintaining reactivity
|
|
265
|
+
Object.keys(target).forEach((key) => delete target[key])
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let combinedTriggers = { ...defaultTriggers, ...triggers }
|
|
271
|
+
|
|
272
|
+
for (let trigger of Object.keys(combinedTriggers)) {
|
|
273
|
+
socket.on(`${c.name}:${trigger}`, combinedTriggers[trigger])
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
a[c.name] = runQuery
|
|
278
|
+
a[c.name]['save'] = save
|
|
279
|
+
a[c.name]['delete'] = deleteEntry
|
|
280
|
+
a[c.name]['subscribe'] = subscribe
|
|
281
|
+
a[c.singular] = (id, query) => runQuery({ id, ...query })
|
|
282
|
+
a[c.singular]['save'] = save
|
|
283
|
+
a[c.singular]['subscribe'] = (id, callback, message) => {
|
|
284
|
+
if (!id) return console.error('No id provided')
|
|
285
|
+
return subscribe(id, message, callback)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
a[c.name]['local'] = localDB.getEntries
|
|
289
|
+
a[c.singular]['local'] = localDB.get
|
|
290
|
+
a[c.singular]['local']['delete'] = localDB.delete
|
|
291
|
+
a[c.name]['sync'] = sync
|
|
292
|
+
|
|
293
|
+
a[c.name]['search'] = search
|
|
294
|
+
a[c.name]['search']['init'] = (search, query, algoliaFilters) => {
|
|
295
|
+
let loading = ref(true)
|
|
296
|
+
let data = ref(null)
|
|
297
|
+
let error = ref(null)
|
|
298
|
+
let totalResults = ref(null)
|
|
299
|
+
|
|
300
|
+
let response = search(search, query, algoliaFilters)
|
|
301
|
+
.then((response) => {
|
|
302
|
+
data.value = response.hits
|
|
303
|
+
totalResults.value = response.nbHits
|
|
304
|
+
loading.value = false
|
|
305
|
+
})
|
|
306
|
+
.catch((e) => {
|
|
307
|
+
loading.value = false
|
|
308
|
+
error.value = e
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
return { data, loading, error }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
a[c.name]['init'] = ({ limit, page, search, fields, id, sort } = {}) => {
|
|
315
|
+
let loading = ref(true)
|
|
316
|
+
let data = ref(null)
|
|
317
|
+
let error = ref(null)
|
|
318
|
+
|
|
319
|
+
let response = runQuery({ limit, page, search, fields, id, sort })
|
|
320
|
+
.then((response) => {
|
|
321
|
+
data.value = response
|
|
322
|
+
loading.value = false
|
|
323
|
+
})
|
|
324
|
+
.catch((e) => {
|
|
325
|
+
loading.value = false
|
|
326
|
+
error.value = e
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
return { data, loading, error }
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
a[c.singular]['init'] = (id) => a[c.name]['init']({ id })
|
|
333
|
+
|
|
334
|
+
a.relationRequest = ({ limit, page, search, fields, id, sort, depthLimit, path } = {}) => {
|
|
335
|
+
let fullQuery
|
|
336
|
+
|
|
337
|
+
const params = { limit, page, search, fields, sort, depthLimit }
|
|
338
|
+
|
|
339
|
+
if (params.search != undefined) params.search = JSON.stringify(params.search)
|
|
340
|
+
if (params.fields != undefined) params.fields = JSON.stringify(params.fields)
|
|
341
|
+
if (params.sort != undefined) params.sort = JSON.stringify(params.sort)
|
|
342
|
+
|
|
343
|
+
const query =
|
|
344
|
+
Object.keys(params)
|
|
345
|
+
.filter((key) => params[key] != undefined)
|
|
346
|
+
?.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
|
347
|
+
?.join('&') || ''
|
|
348
|
+
// console.log(query)
|
|
349
|
+
|
|
350
|
+
fullQuery = `${api}/${path}?${query}`
|
|
351
|
+
|
|
352
|
+
let Authorization = window.localStorage.getItem('token')
|
|
353
|
+
|
|
354
|
+
return new Promise((resolve, reject) => {
|
|
355
|
+
axios
|
|
356
|
+
.get(fullQuery, { headers: { Authorization } })
|
|
357
|
+
.then((response) => resolve(response?.data?.response))
|
|
358
|
+
.catch((e) => reject(e))
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
a.relationRequest.init = (query) => {
|
|
362
|
+
let loading = ref(true)
|
|
363
|
+
let data = ref(null)
|
|
364
|
+
let error = ref(null)
|
|
365
|
+
|
|
366
|
+
let response = a
|
|
367
|
+
.relationRequest(query)
|
|
368
|
+
.then((response) => {
|
|
369
|
+
data.value = response
|
|
370
|
+
loading.value = false
|
|
371
|
+
})
|
|
372
|
+
.catch((e) => {
|
|
373
|
+
loading.value = false
|
|
374
|
+
error.value = e
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
return { data, loading, error }
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
a.graphql = runGraphql
|
|
381
|
+
a.graphql.init = (query) => {
|
|
382
|
+
let loading = ref(true)
|
|
383
|
+
let data = ref(null)
|
|
384
|
+
let error = ref(null)
|
|
385
|
+
|
|
386
|
+
let response = runGraphql(query)
|
|
387
|
+
.then((response) => {
|
|
388
|
+
data.value = response
|
|
389
|
+
loading.value = false
|
|
390
|
+
})
|
|
391
|
+
.catch((e) => {
|
|
392
|
+
loading.value = false
|
|
393
|
+
error.value = e
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
return { data, loading, error }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return a
|
|
404
400
|
}, {})
|
|
405
401
|
|
|
406
402
|
Mango.search = (search, query, algoliaFilters) => {
|
|
403
|
+
search = search || ''
|
|
407
404
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
let filters = ``
|
|
411
|
-
if (algoliaFilters) filters += `${algoliaFilters}`
|
|
412
|
-
|
|
413
|
-
let algoliaQuery = {
|
|
414
|
-
page: query?.page || 0,
|
|
415
|
-
filters,
|
|
416
|
-
hitsPerPage: query?.limit || 10
|
|
417
|
-
}
|
|
405
|
+
let filters = ``
|
|
406
|
+
if (algoliaFilters) filters += `${algoliaFilters}`
|
|
418
407
|
|
|
419
|
-
|
|
408
|
+
let algoliaQuery = {
|
|
409
|
+
page: query?.page || 0,
|
|
410
|
+
filters,
|
|
411
|
+
hitsPerPage: query?.limit || 10,
|
|
412
|
+
}
|
|
420
413
|
|
|
421
|
-
|
|
422
|
-
algolia.search(search, algoliaQuery)
|
|
423
|
-
.then(({ hits }) => {
|
|
424
|
-
hits.forEach(h => h.id = h.objectID)
|
|
425
|
-
resolve(hits)
|
|
426
|
-
})
|
|
427
|
-
})
|
|
414
|
+
if (query?.fields) algoliaQuery.attributesToRetrieve = query.fields
|
|
428
415
|
|
|
416
|
+
return new Promise((resolve, reject) => {
|
|
417
|
+
algolia.search(search, algoliaQuery).then(({ hits }) => {
|
|
418
|
+
hits.forEach((h) => (h.id = h.objectID))
|
|
419
|
+
resolve(hits)
|
|
420
|
+
})
|
|
421
|
+
})
|
|
429
422
|
}
|
|
430
423
|
|
|
431
424
|
Mango.login = ({ email, password }) => {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
425
|
+
return new Promise((resolve, reject) => {
|
|
426
|
+
axios
|
|
427
|
+
.post(`${api}/endpoints/account/login`, { email, password })
|
|
428
|
+
.then((response) => {
|
|
429
|
+
window.localStorage.setItem('token', response.data.token)
|
|
430
|
+
window.localStorage.setItem('user', response.data.memberId)
|
|
431
|
+
window.localStorage.setItem('email', email)
|
|
432
|
+
resolve(response.data)
|
|
433
|
+
})
|
|
434
|
+
.catch((e) => reject(e))
|
|
435
|
+
})
|
|
442
436
|
}
|
|
443
437
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
438
|
+
// Build endpoints from the generated .endpoints.json
|
|
439
|
+
Mango.endpoints = Object.keys(endpointsData).reduce((acc, path) => {
|
|
440
|
+
const methods = endpointsData[path]
|
|
441
|
+
|
|
442
|
+
// Parse the path into nested object structure
|
|
443
|
+
// e.g., "account/login" -> acc.account.login
|
|
444
|
+
const parts = path.split('/')
|
|
445
|
+
let current = acc
|
|
446
|
+
|
|
447
|
+
for (let i = 0; i < parts.length; i++) {
|
|
448
|
+
const part = parts[i]
|
|
449
|
+
|
|
450
|
+
// Skip empty parts (from leading slashes)
|
|
451
|
+
if (!part) continue
|
|
452
|
+
|
|
453
|
+
// If this is the last part, add the methods
|
|
454
|
+
if (i === parts.length - 1) {
|
|
455
|
+
if (!current[part]) current[part] = {}
|
|
456
|
+
|
|
457
|
+
// Add each HTTP method
|
|
458
|
+
for (let method of methods) {
|
|
459
|
+
current[part][method] = (data, options = {}) => {
|
|
460
|
+
let Authorization = window.localStorage.getItem('token')
|
|
461
|
+
let headers = {
|
|
462
|
+
Authorization,
|
|
463
|
+
...options.headers,
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return new Promise((resolve, reject) => {
|
|
467
|
+
const config = { headers }
|
|
468
|
+
|
|
469
|
+
// For GET and DELETE, data goes in params
|
|
470
|
+
if (method === 'get' || method === 'delete') {
|
|
471
|
+
if (data) config.params = data
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// For POST, PUT, PATCH, data is the body
|
|
475
|
+
const requestArgs = (method === 'get' || method === 'delete')
|
|
476
|
+
? [`${api}/endpoints/${path}`, config]
|
|
477
|
+
: [`${api}/endpoints/${path}`, data, config]
|
|
478
|
+
|
|
479
|
+
axios[method](...requestArgs)
|
|
480
|
+
.then((response) => resolve(response?.data))
|
|
481
|
+
.catch((e) => reject(e))
|
|
482
|
+
})
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Add init wrapper for reactive data
|
|
486
|
+
current[part][method]['init'] = (data, options) => {
|
|
487
|
+
let loading = ref(true)
|
|
488
|
+
let response = ref(null)
|
|
489
|
+
let error = ref(null)
|
|
490
|
+
|
|
491
|
+
current[part][method](data, options)
|
|
492
|
+
.then((r) => {
|
|
493
|
+
response.value = r
|
|
494
|
+
loading.value = false
|
|
495
|
+
})
|
|
496
|
+
.catch((e) => {
|
|
497
|
+
error.value = e
|
|
498
|
+
loading.value = false
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
return { data: response, loading, error }
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} else {
|
|
505
|
+
// Create intermediate object if it doesn't exist
|
|
506
|
+
if (!current[part]) current[part] = {}
|
|
507
|
+
current = current[part]
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return acc
|
|
480
512
|
}, {})
|
|
481
513
|
|
|
482
514
|
Mango.upload = async (file) => {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
})
|
|
515
|
+
return new Promise((resolve, reject) => {
|
|
516
|
+
const formData = new FormData()
|
|
517
|
+
|
|
518
|
+
let uploading = true
|
|
519
|
+
let filename = file.name
|
|
520
|
+
let progress = 0
|
|
521
|
+
let url
|
|
522
|
+
let error
|
|
523
|
+
|
|
524
|
+
// // Compress the image
|
|
525
|
+
// if (file.type.includes('image')) {
|
|
526
|
+
// let results = await compress.compress([file], {
|
|
527
|
+
// quality: .75, // the quality of the image, max is 1,
|
|
528
|
+
// maxWidth: 1920, // the max width of the output image, defaults to 1920px
|
|
529
|
+
// maxHeight: 1920, // the max height of the output image, defaults to 1920px
|
|
530
|
+
// resize: true, // defaults to true, set false if you do not want to resize the image width and height
|
|
531
|
+
// rotate: false, // See the rotation section below
|
|
532
|
+
// })
|
|
533
|
+
// const img1 = results[0]
|
|
534
|
+
// const base64str = img1.data
|
|
535
|
+
// const imgExt = img1.ext
|
|
536
|
+
// const filename = file.name
|
|
537
|
+
// file = Compress.convertBase64ToFile(base64str, imgExt)
|
|
538
|
+
// file = new File([file], filename, { type: file.type });
|
|
539
|
+
// console.log('file', file)
|
|
540
|
+
// }
|
|
541
|
+
|
|
542
|
+
formData.append('file', file)
|
|
543
|
+
|
|
544
|
+
const xhr = new XMLHttpRequest()
|
|
545
|
+
|
|
546
|
+
xhr.open('POST', `${api}/upload`, true)
|
|
547
|
+
|
|
548
|
+
xhr.upload.onprogress = (event) => {
|
|
549
|
+
if (event.lengthComputable) {
|
|
550
|
+
progress = (event.loaded / event.total) * 100
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
xhr.onload = () => {
|
|
555
|
+
if (xhr.status === 200) {
|
|
556
|
+
const json = JSON.parse(xhr.response)
|
|
557
|
+
const path = json.paths[0]
|
|
558
|
+
const url = api + path
|
|
559
|
+
uploading = false
|
|
560
|
+
progress = 0
|
|
561
|
+
resolve(url)
|
|
562
|
+
} else {
|
|
563
|
+
error = 'Error while uploading file'
|
|
564
|
+
uploading = false
|
|
565
|
+
reject(error)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
xhr.onerror = () => {
|
|
570
|
+
error = 'Error while uploading file'
|
|
571
|
+
uploading = false
|
|
572
|
+
reject(error)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
xhr.send(formData)
|
|
576
|
+
})
|
|
546
577
|
}
|
|
547
578
|
|
|
548
579
|
Mango.collections = collections
|
|
549
580
|
Mango.ws = ws
|
|
550
581
|
|
|
551
|
-
Mango.online = async () => {
|
|
582
|
+
Mango.online = async () => {
|
|
583
|
+
try {
|
|
584
|
+
return (await axios.get(`${api}/endpoints/test`))?.data?.includes('🥭')
|
|
585
|
+
} catch (e) {
|
|
586
|
+
return false
|
|
587
|
+
}
|
|
588
|
+
}
|
|
552
589
|
|
|
553
590
|
export default Mango
|
package/default/vite.config.js
CHANGED
|
@@ -20,6 +20,7 @@ if (!fs.existsSync(configPath)) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const collectionsPath = path.resolve(configPath, 'config/.collections.json')
|
|
23
|
+
const endpointsPath = path.resolve(configPath, 'config/.endpoints.json')
|
|
23
24
|
|
|
24
25
|
// https://vitejs.dev/config/
|
|
25
26
|
export default defineConfig({
|
|
@@ -40,11 +41,12 @@ export default defineConfig({
|
|
|
40
41
|
// }
|
|
41
42
|
// }),
|
|
42
43
|
{
|
|
43
|
-
name: 'watch-collections',
|
|
44
|
+
name: 'watch-collections-and-endpoints',
|
|
44
45
|
configureServer(server) {
|
|
45
46
|
server.watcher.add(collectionsPath)
|
|
47
|
+
server.watcher.add(endpointsPath)
|
|
46
48
|
server.watcher.on('change', (file) => {
|
|
47
|
-
if (file === collectionsPath) {
|
|
49
|
+
if (file === collectionsPath || file === endpointsPath) {
|
|
48
50
|
server.ws.send({
|
|
49
51
|
type: 'full-reload',
|
|
50
52
|
})
|
|
@@ -62,11 +64,12 @@ export default defineConfig({
|
|
|
62
64
|
'@mango': configPath,
|
|
63
65
|
'@settings': path.resolve(configPath, 'config/settings.json'),
|
|
64
66
|
'@collections': path.resolve(configPath, 'config/.collections.json'),
|
|
67
|
+
'@endpoints': path.resolve(configPath, 'config/.endpoints.json'),
|
|
65
68
|
'@plugins': path.resolve(configPath, 'plugins'),
|
|
66
69
|
vue: path.resolve(__dirname, 'node_modules/vue'),
|
|
67
70
|
},
|
|
68
71
|
},
|
|
69
72
|
optimizeDeps: {
|
|
70
|
-
exclude: ['vue', '@collections', '@settings'], // Prevent Vite from optimizing Vue separately
|
|
73
|
+
exclude: ['vue', '@collections', '@settings', '@endpoints'], // Prevent Vite from optimizing Vue separately
|
|
71
74
|
},
|
|
72
75
|
})
|