mango-cms 0.1.17 → 0.1.19

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.
@@ -0,0 +1,294 @@
1
+ import axios from "axios"
2
+ import Mango from "./mango"
3
+ import Swal from "sweetalert2"
4
+
5
+ // Function for setting cookies
6
+ let setCookie = function (cname, cvalue) {
7
+ var d = new Date();
8
+ d.setTime(d.getTime() + (365 * 24 * 60 * 60 * 1000));
9
+ var expires = "expires=" + d.toUTCString();
10
+ document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
11
+ }
12
+
13
+ function timeout(ms) {
14
+ return new Promise((resolve, reject) => {
15
+ setTimeout(() => {
16
+ reject(new Error(`Timeout after ${ms} ms`));
17
+ }, ms);
18
+ });
19
+ }
20
+
21
+ async function runWithTimeout(callback, ms) {
22
+ try {
23
+ let response = await Promise.race([
24
+ callback(),
25
+ timeout(ms)
26
+ ]);
27
+ console.log('timeout', response)
28
+ return response;
29
+ } catch (error) {
30
+ console.error(error.message);
31
+ }
32
+ }
33
+
34
+ export default class LocalDB {
35
+
36
+ constructor(collection, api) {
37
+
38
+ this.collection = collection
39
+
40
+ this.getDb()
41
+
42
+ this.ready = false
43
+ this.db = null
44
+ this.loading = false
45
+ this.saving = false
46
+ this.deleting = false
47
+ this.entry = null
48
+ this.entries = null
49
+
50
+ this.api = api
51
+
52
+ }
53
+
54
+ async getDb() {
55
+
56
+ let request = window.indexedDB.open(this.collection, 1);
57
+
58
+ request.onerror = e => {
59
+ this.ready = false
60
+ console.error('Error opening db', e);
61
+ };
62
+
63
+ request.onsuccess = e => {
64
+ this.db = e.target.result
65
+ this.ready = true
66
+ };
67
+
68
+ request.onupgradeneeded = e => {
69
+ console.log('onupgradeneeded');
70
+ this.db = e.target.result;
71
+ let objectStore = this.db.createObjectStore(this.collection, { autoIncrement: true, keyPath: 'id' });
72
+ };
73
+
74
+ }
75
+
76
+ async save(entry, syncing) {
77
+
78
+ if (!this.ready) return
79
+
80
+ this.saving = true;
81
+
82
+ let entryExists = !!entry.id
83
+ let onlyLocal = !isNaN(entry?.id) || !entryExists
84
+
85
+ let existingEntry
86
+
87
+ if (onlyLocal && entryExists) {
88
+ entry.id = Number(entry.id)
89
+ existingEntry = await this.get(entry.id)
90
+ } else if (entryExists) {
91
+ try {
92
+ existingEntry = await this.get(entry.id)
93
+ } catch {
94
+ // console.log('must just be in cloud')
95
+ }
96
+ }
97
+
98
+ return await new Promise(async (resolve, reject) => {
99
+
100
+ let trans = this.db.transaction([this.collection], 'readwrite');
101
+ let store = trans.objectStore(this.collection);
102
+
103
+ if (existingEntry) entry = { ...existingEntry, ...entry }
104
+
105
+ // Remove Vue Proxy stuff so indexedDB is happy
106
+ let savedImage = entry.image
107
+ delete entry.image
108
+ entry = JSON.parse(JSON.stringify(entry))
109
+ if (!syncing) entry.updatedLocally = new Date()
110
+
111
+ // Format Address for offline
112
+ // if (!entry.address?.id && entry.address?.formatted) entry.address = entry.address.formatted
113
+
114
+ // Handle images
115
+ if (savedImage?.type?.includes?.('image')) entry.image = savedImage
116
+ if (savedImage?.includes?.('http')) entry.image = savedImage
117
+
118
+
119
+ let method = entryExists ? 'put' : 'add'
120
+ let request = store[method](entry)
121
+
122
+ request.onsuccess = async () => {
123
+ this.saving = false
124
+ let localId = request.result
125
+
126
+ if (window?.offlineMode) return resolve({ ...entry, id: localId })
127
+
128
+ // If it only exists in the localDB, remove the ID for Mango
129
+ if (onlyLocal) delete entry.id
130
+
131
+ try {
132
+
133
+ // Try to upload the image
134
+ if (entry?.image?.type?.includes?.('image')) {
135
+ await runWithTimeout(async () => {
136
+ console.log('upload')
137
+ const formData = new FormData()
138
+ formData.append('file', entry.image)
139
+ const response = await axios.post(`${this.api}/upload`, formData)
140
+ console.log('upload response:', response)
141
+ const path = response?.data?.paths?.[0]
142
+ const url = `${this.api}${path}`
143
+ entry.image = url
144
+ }, 10 * 1000)
145
+ }
146
+
147
+ // Save to the cloud - this will throw if offline)
148
+
149
+ // If the entry is over a day old...
150
+ let olderThanOneDay = new Date(entry?.updatedLocally || '1/1/2024') < new Date(new Date().getTime() - 24 * 60 * 60 * 1000)
151
+ if (onlyLocal && olderThanOneDay) await this.delete(localId)
152
+
153
+ delete entry.updatedLocally
154
+
155
+ // Save to Mango (timeout if already created, else no timeout)
156
+ let response
157
+ if (onlyLocal) {
158
+ let { response: mangoResponse, warnings } = await Mango[this.collection].save(entry, null, true)
159
+ if (warnings?.length) Swal.fire('WARNING:', warnings?.join(', '), 'warning')
160
+ console.log('mangoResponse', mangoResponse)
161
+ response = mangoResponse
162
+ }
163
+ else response = await runWithTimeout(async () => await Mango[this.collection].save(entry), 5000)
164
+
165
+ // If successfull, delete from local queue
166
+ if (response?.id) {
167
+ await this.delete(localId)
168
+ resolve(response);
169
+ } else {
170
+ console.log('response', response)
171
+
172
+ // Unauthorized
173
+ if (typeof response == 'string' && response?.includes?.('not have permission')) {
174
+ // Logout if credentials are bad
175
+ window.localStorage.removeItem('user')
176
+ window.localStorage.removeItem('token')
177
+ window.localStorage.removeItem('email')
178
+ window.localStorage.removeItem('auth')
179
+ setCookie(`Authorization`, ``)
180
+ window.location.reload()
181
+ }
182
+
183
+ console.log(response, onlyLocal);
184
+ resolve({ ...entry, id: localId });
185
+ }
186
+
187
+ } catch (e) {
188
+ console.log(e.message)
189
+ resolve({ ...entry, id: localId });
190
+ }
191
+ };
192
+
193
+ request.onerror = (e) => {
194
+ this.saving = false
195
+ reject(e);
196
+ };
197
+
198
+ });
199
+
200
+ }
201
+
202
+ async get(id) {
203
+
204
+ if (!this.ready) return
205
+
206
+ this.loading = true;
207
+
208
+ return await new Promise((resolve, reject) => {
209
+ let trans = this.db.transaction([this.collection], 'readonly');
210
+ let store = trans.objectStore(this.collection);
211
+ let request = store.get(id);
212
+
213
+ request.onsuccess = () => {
214
+ this.loading = false;
215
+ resolve(request.result);
216
+ };
217
+
218
+ request.onerror = (e) => {
219
+ this.loading = false;
220
+ reject(e);
221
+ };
222
+ });
223
+
224
+ }
225
+
226
+ async getEntries({ pageIndex = 0, pageSize = 1000 } = {}) {
227
+
228
+ if (!this.ready) return []
229
+
230
+ this.loading = true;
231
+
232
+ let response = await new Promise((resolve, reject) => {
233
+ let trans = this.db.transaction([this.collection], 'readonly');
234
+ let store = trans.objectStore(this.collection);
235
+ let cursorRequest = store.openCursor();
236
+
237
+ let entries = [];
238
+ let skippedEntries = pageIndex * pageSize;
239
+
240
+ cursorRequest.onsuccess = (e) => {
241
+ let cursor = e.target.result;
242
+ if (cursor) {
243
+ if (skippedEntries > 0) {
244
+ // Skip the entries before the current page
245
+ cursor.advance(skippedEntries);
246
+ skippedEntries = 0;
247
+ } else {
248
+ entries.push(cursor.value);
249
+ if (entries.length < pageSize) {
250
+ cursor.continue();
251
+ } else {
252
+ resolve(entries);
253
+ }
254
+ }
255
+ } else {
256
+ // No more entries to read; resolve with what we have
257
+ resolve(entries);
258
+ }
259
+ };
260
+
261
+ cursorRequest.onerror = (e) => {
262
+ reject(e);
263
+ };
264
+ });
265
+
266
+ // console.log('response', response);
267
+ this.loading = false;
268
+ return response;
269
+ }
270
+
271
+ async delete(id) {
272
+
273
+ this.deleting = true;
274
+
275
+ return new Promise((resolve, reject) => {
276
+
277
+ let trans = this.db.transaction([this.collection], 'readwrite');
278
+ let store = trans.objectStore(this.collection);
279
+ let request = store.delete(id);
280
+
281
+ request.onsuccess = () => {
282
+ this.deleting = false
283
+ resolve(request.result);
284
+ };
285
+
286
+ request.onerror = (e) => {
287
+ this.deleting = false
288
+ reject(e);
289
+ };
290
+
291
+ });
292
+ }
293
+
294
+ }
@@ -0,0 +1,341 @@
1
+ // import collections from '../../../mango/config/.collections.json'
2
+ // import { algoliaAppId, algoliaSearchKey, algoliaIndex, port, domain } from '../../../mango/config/settings'
3
+ import collections from '@config/config/.collections.json'
4
+ import { algoliaAppId, algoliaSearchKey, algoliaIndex, port, domain } from '@config/config/settings'
5
+ import axios from "axios";
6
+ import { ref } from 'vue'
7
+ import algoliasearch from 'algoliasearch/lite'
8
+
9
+ let endpoints = {
10
+ authors: ['get'],
11
+ // scripture: { validate: ['post'] }
12
+ }
13
+
14
+ // console.log('collections', collections)
15
+
16
+ const client = algoliasearch(algoliaAppId, algoliaSearchKey);
17
+ const algolia = client.initIndex(algoliaIndex);
18
+
19
+ let api = `https://${domain}`
20
+ let ws = `wss://${domain}/graphql`
21
+
22
+ if (process.env.NODE_ENV != 'production') {
23
+ api = `http://localhost:${port}`
24
+ ws = `ws://localhost:${port}/graphql`
25
+ }
26
+
27
+ const Mango = collections.reduce((a, c) => {
28
+
29
+ let runQuery = ({ limit, page, search, fields, id, sort, depthLimit } = {}) => {
30
+
31
+ let fullQuery
32
+
33
+
34
+ const params = { limit, page, search, fields, sort, depthLimit }
35
+
36
+ if (params.search != undefined) params.search = JSON.stringify(params.search)
37
+ if (params.fields != undefined) params.fields = JSON.stringify(params.fields)
38
+ if (params.sort != undefined) params.sort = JSON.stringify(params.sort)
39
+
40
+ const query = Object.keys(params)
41
+ .filter(key => params[key] != undefined)
42
+ ?.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
43
+ ?.join('&') || ''
44
+ // console.log(query)
45
+
46
+ fullQuery = `${api}/${c.name}/${id || ''}?${query}`
47
+
48
+ let Authorization = window.localStorage.getItem('token')
49
+
50
+ return new Promise((resolve, reject) => {
51
+ axios.get(fullQuery, { headers: { Authorization } })
52
+ .then(response => resolve(response?.data?.response))
53
+ .catch(e => reject(e))
54
+ })
55
+
56
+ }
57
+
58
+ let runGraphql = (query) => {
59
+
60
+ let Authorization = window.localStorage.getItem('token')
61
+ query = { query }
62
+ return new Promise((resolve, reject) => {
63
+ axios.post(`${api}/graphql`, query, { headers: { Authorization } })
64
+ .then(response => resolve(response?.data?.data))
65
+ .catch(e => reject(e))
66
+ })
67
+
68
+ }
69
+
70
+ let runAlgolia = (search, query, algoliaFilters) => {
71
+
72
+ search = search || ''
73
+
74
+ let filters = `collection:${c.name}`
75
+ if (algoliaFilters) filters += ` AND ${algoliaFilters}`
76
+
77
+ let algoliaQuery = {
78
+ page: query?.page || 0,
79
+ filters,
80
+ hitsPerPage: query?.limit || 10
81
+ }
82
+
83
+ if (query?.fields) algoliaQuery.attributesToRetrieve = query.fields
84
+
85
+ return new Promise((resolve, reject) => {
86
+ algolia.search(search, algoliaQuery)
87
+ .then(({ hits, nbHits }) => {
88
+ hits.forEach(h => h.id = h.objectID)
89
+ resolve({ hits, nbHits })
90
+ })
91
+ })
92
+
93
+ }
94
+
95
+ let save = (data, options = {}) => {
96
+ let { id } = data
97
+ let method = id ? 'put' : 'post'
98
+
99
+ // // Remove _id and computed fields
100
+ delete data.collection
101
+ delete data._id
102
+ delete data.id
103
+
104
+ for (let field of c.fields) {
105
+ if (field.computed) delete data[field.name]
106
+ if (field.relationship) 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]
107
+ }
108
+ for (let name in data) {
109
+ if (name.includes('__')) delete data[name]
110
+ }
111
+
112
+ let payload = { ...data }
113
+ let Authorization = window.localStorage.getItem('token')
114
+ let headers = {
115
+ Authorization,
116
+ ...options.headers
117
+ }
118
+
119
+ return new Promise((resolve, reject) => {
120
+ axios[method](`${api}/${c.name}/${id || ''}`, payload, { headers })
121
+ .then(response => resolve(response?.data?.response))
122
+ .catch(e => reject(e))
123
+ })
124
+ }
125
+
126
+ let deleteEntry = (data) => {
127
+ let id = data.id || data
128
+
129
+ let Authorization = window.localStorage.getItem('token')
130
+
131
+ return new Promise((resolve, reject) => {
132
+ axios.delete(`${api}/${c.name}/${id || ''}`, { headers: { Authorization } })
133
+ .then(response => resolve(response?.data))
134
+ .catch(e => reject(e))
135
+ })
136
+ }
137
+
138
+ a[c.name] = runQuery
139
+ a[c.name]['save'] = save
140
+ a[c.name]['delete'] = deleteEntry
141
+ a[c.singular] = (id, query) => runQuery({ id, ...query })
142
+
143
+ a[c.name]['search'] = runAlgolia
144
+ a[c.name]['search']['init'] = (search, query, algoliaFilters) => {
145
+ let loading = ref(true)
146
+ let data = ref(null)
147
+ let error = ref(null)
148
+ let totalResults = ref(null)
149
+
150
+ let response = runAlgolia(search, query, algoliaFilters)
151
+ .then(response => {
152
+ data.value = response.hits
153
+ totalResults.value = response.nbHits
154
+ loading.value = false
155
+ })
156
+ .catch(e => {
157
+ loading.value = false
158
+ error.value = e
159
+ })
160
+
161
+ return { data, loading, error }
162
+ }
163
+
164
+ a[c.name]['init'] = ({ limit, page, search, fields, id, sort } = {}) => {
165
+
166
+ let loading = ref(true)
167
+ let data = ref(null)
168
+ let error = ref(null)
169
+
170
+ let response = runQuery({ limit, page, search, fields, id, sort })
171
+ .then(response => {
172
+ data.value = response
173
+ loading.value = false
174
+ })
175
+ .catch(e => {
176
+ loading.value = false
177
+ error.value = e
178
+ })
179
+
180
+ return { data, loading, error }
181
+
182
+ }
183
+
184
+ a[c.singular]['init'] = (id) => a[c.name]['init']({ id })
185
+
186
+ a.relationRequest = ({ limit, page, search, fields, id, sort, depthLimit, path } = {}) => {
187
+
188
+ let fullQuery
189
+
190
+
191
+ const params = { limit, page, search, fields, sort, depthLimit }
192
+
193
+ if (params.search != undefined) params.search = JSON.stringify(params.search)
194
+ if (params.fields != undefined) params.fields = JSON.stringify(params.fields)
195
+ if (params.sort != undefined) params.sort = JSON.stringify(params.sort)
196
+
197
+ const query = Object.keys(params)
198
+ .filter(key => params[key] != undefined)
199
+ ?.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
200
+ ?.join('&') || ''
201
+ // console.log(query)
202
+
203
+ fullQuery = `${api}/${path}?${query}`
204
+
205
+ let Authorization = window.localStorage.getItem('token')
206
+
207
+ return new Promise((resolve, reject) => {
208
+ axios.get(fullQuery, { headers: { Authorization } })
209
+ .then(response => resolve(response?.data?.response))
210
+ .catch(e => reject(e))
211
+ })
212
+
213
+ }
214
+ a.relationRequest.init = (query) => {
215
+
216
+ let loading = ref(true)
217
+ let data = ref(null)
218
+ let error = ref(null)
219
+
220
+ let response = a.relationRequest(query)
221
+ .then(response => {
222
+ data.value = response
223
+ loading.value = false
224
+ })
225
+ .catch(e => {
226
+ loading.value = false
227
+ error.value = e
228
+ })
229
+
230
+ return { data, loading, error }
231
+
232
+ }
233
+
234
+
235
+ a.graphql = runGraphql
236
+ a.graphql.init = (query) => {
237
+
238
+ let loading = ref(true)
239
+ let data = ref(null)
240
+ let error = ref(null)
241
+
242
+ let response = runGraphql(query)
243
+ .then(response => {
244
+ data.value = response
245
+ loading.value = false
246
+ })
247
+ .catch(e => {
248
+ loading.value = false
249
+ error.value = e
250
+ })
251
+
252
+ return { data, loading, error }
253
+
254
+ }
255
+
256
+ return a
257
+
258
+ }, {})
259
+
260
+ Mango.search = (search, query, algoliaFilters) => {
261
+
262
+ search = search || ''
263
+
264
+ let filters = ``
265
+ if (algoliaFilters) filters += `${algoliaFilters}`
266
+
267
+ let algoliaQuery = {
268
+ page: query?.page || 0,
269
+ filters,
270
+ hitsPerPage: query?.limit || 10
271
+ }
272
+
273
+ if (query?.fields) algoliaQuery.attributesToRetrieve = query.fields
274
+
275
+ return new Promise((resolve, reject) => {
276
+ algolia.search(search, algoliaQuery)
277
+ .then(({ hits }) => {
278
+ hits.forEach(h => h.id = h.objectID)
279
+ resolve(hits)
280
+ })
281
+ })
282
+
283
+ }
284
+
285
+ Mango.login = ({ email, password }) => {
286
+ return new Promise((resolve, reject) => {
287
+ axios.post(`${api}/endpoints/account/login`, { email, password })
288
+ .then(response => {
289
+ window.localStorage.setItem('token', response.data.token)
290
+ window.localStorage.setItem('user', response.data.memberId)
291
+ window.localStorage.setItem('email', email)
292
+ resolve(response.data)
293
+ })
294
+ .catch(e => reject(e))
295
+ })
296
+ }
297
+
298
+ Mango.endpoints = Object.keys(endpoints).reduce((a, c) => {
299
+
300
+ a[c] = {}
301
+
302
+ for (let method of endpoints[c]) {
303
+ a[c][method] = () => {
304
+
305
+ return new Promise((resolve, reject) => {
306
+ console.log('method', method)
307
+ console.log('`${api}/endpoints/${c}`', `${api}/endpoints/${c}`)
308
+ axios[method](`${api}/endpoints/${c}`)
309
+ .then(response => resolve(response?.data))
310
+ .catch(e => reject(e))
311
+ })
312
+
313
+ }
314
+ }
315
+
316
+ for (let method of endpoints[c]) {
317
+ a[c][method]['init'] = () => {
318
+
319
+ let loading = ref(true)
320
+ let data = ref(null)
321
+ let error = ref(null)
322
+
323
+ let response = a[c][method]().then(r => {
324
+ data.value = r
325
+ loading.value = false
326
+ })
327
+
328
+ return { data, loading, error }
329
+ }
330
+ }
331
+
332
+ return a
333
+
334
+ }, {})
335
+
336
+ Mango.collections = collections
337
+ Mango.ws = ws
338
+
339
+ Mango.online = async () => { try { return (await axios.get(`${api}/endpoints/test`))?.data?.includes('🥭') } catch (e) { return false } }
340
+
341
+ export default Mango
@@ -0,0 +1,40 @@
1
+ import { ref } from 'vue'
2
+ import Mango from './mango'
3
+
4
+ const getUser = async (user) => {
5
+
6
+ user = user || { value: {} }
7
+
8
+ if (window.user?.id) {
9
+
10
+ console.log('got it!')
11
+ user.value = window.user
12
+
13
+ /*
14
+ Gotta clear this out so next time
15
+ we try to call getUser we don't get
16
+ stale data. ;)
17
+ */
18
+ window.user = null
19
+ return user.value
20
+ }
21
+
22
+ let userId = window.localStorage.getItem('user')
23
+
24
+ if (!userId) return {}
25
+
26
+ user.value = await Mango.member(userId, { depthLimit: 0 }).then(r => user.value = r)
27
+
28
+ return user.value
29
+
30
+ }
31
+
32
+ const initUser = () => {
33
+
34
+ const user = ref({})
35
+ getUser(user)
36
+ return user
37
+
38
+ }
39
+
40
+ export { initUser, getUser }