@opengis/fastify-table 2.3.1 → 2.3.3
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/dist/server/plugins/upload/s3.d.ts +2 -0
- package/dist/server/plugins/upload/s3.d.ts.map +1 -1
- package/dist/server/plugins/upload/s3.js +25 -0
- package/dist/server/plugins/upload/uploadChunk.d.ts.map +1 -1
- package/dist/server/plugins/upload/uploadChunk.js +15 -5
- package/dist/server/routes/table/controllers/suggest.d.ts.map +1 -1
- package/dist/server/routes/table/controllers/suggest.js +24 -5
- package/package.json +1 -1
|
@@ -54,7 +54,9 @@ export declare function uploadChunk({ id, chunk, partNumber, }: {
|
|
|
54
54
|
export declare function finishUpload({ id, }: {
|
|
55
55
|
id: string;
|
|
56
56
|
}, s3?: any): Promise<{
|
|
57
|
+
finished: boolean;
|
|
57
58
|
uploaded: any;
|
|
59
|
+
filepath: any;
|
|
58
60
|
}>;
|
|
59
61
|
export declare function abortUpload({ id, }: {
|
|
60
62
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/s3.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/s3.ts"],"names":[],"mappings":"AAiGA,wBAAsB,sBAAsB,CAAC,EAAE,MAAW,iBAwCzD;AAED,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,GAAG,EACT,GAAG,SAAqB,iBAazB;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,GAAG,SAAqB,EACxB,EAAE,MAAW,iBAsBd;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,iBA0B9D;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,gBAQ7C;AAED,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,gBAMrD;AAED,wBAAsB,mBAAmB,CAAC,EAAE,MAAW,iBAyEtD;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,MAAW;;;;;;;;GAsCrE;AAKD,wBAAsB,WAAW,CAC/B,EACE,MAAM,EACN,GAAG,EACH,WAAW,EACX,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,EACD,EAAE,MAAW;;;;;;;;;;;;;;;;GAsDd;AAED,wBAAsB,WAAW,CAC/B,EACE,EAAE,EACF,KAAK,EACL,UAAU,GACX,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB,EACD,EAAE,MAAW;;;;;;;;GA0Bd;AAED,wBAAsB,YAAY,CAChC,EACE,EAAE,GACH,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;CACZ,EACD,EAAE,MAAW;;;;GAuCd;AAED,wBAAsB,WAAW,CAC/B,EACE,EAAE,GACH,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;CACZ,EACD,EAAE,MAAW;;GAwBd;AAED,wBAAsB,eAAe,CACnC,EACE,EAAE,EACF,GAAG,EACH,MAAM,GACP,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,EACD,EAAE,MAAW;;;;;;;;;;;;;;GAoEd"}
|
|
@@ -23,6 +23,8 @@ function clearUploadTimeout(id) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
async function scheduleNonRedisCleanup(id, s3 = s3Client) {
|
|
26
|
+
if (!s3)
|
|
27
|
+
throw new Error("No S3 client");
|
|
26
28
|
if (config.redis)
|
|
27
29
|
return;
|
|
28
30
|
clearUploadTimeout(id);
|
|
@@ -71,6 +73,8 @@ async function scheduleNonRedisCleanup(id, s3 = s3Client) {
|
|
|
71
73
|
}
|
|
72
74
|
// used via onClose hook to avoid memory leaks
|
|
73
75
|
export async function cleanupNonRedisUploads(s3 = s3Client) {
|
|
76
|
+
if (!s3)
|
|
77
|
+
throw new Error("No S3 client");
|
|
74
78
|
if (config.redis)
|
|
75
79
|
return;
|
|
76
80
|
console.log("cleanupNonRedisUploads start");
|
|
@@ -116,6 +120,8 @@ export async function saveUploadMeta(id, meta, ttl = UPLOAD_TTL_SECONDS) {
|
|
|
116
120
|
]);
|
|
117
121
|
}
|
|
118
122
|
export async function refreshUploadTTL(id, ttl = UPLOAD_TTL_SECONDS, s3 = s3Client) {
|
|
123
|
+
if (!s3)
|
|
124
|
+
throw new Error("No S3 client");
|
|
119
125
|
if (!config.redis) {
|
|
120
126
|
await scheduleNonRedisCleanup(id, s3);
|
|
121
127
|
return;
|
|
@@ -162,6 +168,8 @@ export async function getResumableUploadId(key) {
|
|
|
162
168
|
return rclient.get(resumableKey(key));
|
|
163
169
|
}
|
|
164
170
|
export async function cleanupStaleUploads(s3 = s3Client) {
|
|
171
|
+
if (!s3)
|
|
172
|
+
throw new Error("No S3 client");
|
|
165
173
|
if (!config.redis) {
|
|
166
174
|
await cleanupNonRedisUploads(s3);
|
|
167
175
|
return;
|
|
@@ -216,6 +224,8 @@ export async function cleanupStaleUploads(s3 = s3Client) {
|
|
|
216
224
|
} while (cursor !== "0");
|
|
217
225
|
}
|
|
218
226
|
export async function findUpload({ id }, s3 = s3Client) {
|
|
227
|
+
if (!s3)
|
|
228
|
+
throw new Error("No S3 client");
|
|
219
229
|
const cachedInfo = await getUploadMeta(id);
|
|
220
230
|
const { Key, Bucket, fileSize: cacheSize, ContentType: cacheContentType, } = cachedInfo || {};
|
|
221
231
|
const parts = await s3
|
|
@@ -238,7 +248,12 @@ export async function findUpload({ id }, s3 = s3Client) {
|
|
|
238
248
|
})) || [],
|
|
239
249
|
};
|
|
240
250
|
}
|
|
251
|
+
// todo: optional - keep originalFileName, else - id + extension
|
|
252
|
+
// + implement rotateFilenameS3 logic, like const filename = await rotateFilenameS3({ Name: originalFilename, Bucket, Prefix }, s3Client);
|
|
253
|
+
// ! if originalFileName - check names with spaces - s3 mights throw error - encode request or at upload?
|
|
241
254
|
export async function startUpload({ Bucket, Key, ContentType, fileSize, }, s3 = s3Client) {
|
|
255
|
+
if (!s3)
|
|
256
|
+
throw new Error("No S3 client");
|
|
242
257
|
const cachedUploadId = await getResumableUploadId(Key);
|
|
243
258
|
const cachedMeta = cachedUploadId
|
|
244
259
|
? await getUploadMeta(cachedUploadId)
|
|
@@ -281,6 +296,8 @@ export async function startUpload({ Bucket, Key, ContentType, fileSize, }, s3 =
|
|
|
281
296
|
};
|
|
282
297
|
}
|
|
283
298
|
export async function uploadChunk({ id, chunk, partNumber, }, s3 = s3Client) {
|
|
299
|
+
if (!s3)
|
|
300
|
+
throw new Error("No S3 client");
|
|
284
301
|
const upload = await findUpload({ id }, s3);
|
|
285
302
|
if (!upload?.UploadId) {
|
|
286
303
|
throw new Error("upload not found");
|
|
@@ -299,6 +316,8 @@ export async function uploadChunk({ id, chunk, partNumber, }, s3 = s3Client) {
|
|
|
299
316
|
return upload;
|
|
300
317
|
}
|
|
301
318
|
export async function finishUpload({ id, }, s3 = s3Client) {
|
|
319
|
+
if (!s3)
|
|
320
|
+
throw new Error("No S3 client");
|
|
302
321
|
const redisCache = await getUploadMeta(id);
|
|
303
322
|
const { fileSize: cachedSize, Key, Bucket } = redisCache || {};
|
|
304
323
|
const size = cachedSize ? Number(cachedSize) : undefined;
|
|
@@ -319,10 +338,14 @@ export async function finishUpload({ id, }, s3 = s3Client) {
|
|
|
319
338
|
}));
|
|
320
339
|
await deleteUploadMeta(id, Key);
|
|
321
340
|
return {
|
|
341
|
+
finished: true,
|
|
322
342
|
uploaded: upload.uploaded,
|
|
343
|
+
filepath: Key.replace(config.folder, ""),
|
|
323
344
|
};
|
|
324
345
|
}
|
|
325
346
|
export async function abortUpload({ id, }, s3 = s3Client) {
|
|
347
|
+
if (!s3)
|
|
348
|
+
throw new Error("No S3 client");
|
|
326
349
|
const meta = await getUploadMeta(id);
|
|
327
350
|
if (!meta) {
|
|
328
351
|
return {
|
|
@@ -340,6 +363,8 @@ export async function abortUpload({ id, }, s3 = s3Client) {
|
|
|
340
363
|
};
|
|
341
364
|
}
|
|
342
365
|
export async function getUploadStatus({ id, Key, Bucket, }, s3 = s3Client) {
|
|
366
|
+
if (!s3)
|
|
367
|
+
throw new Error("No S3 client");
|
|
343
368
|
const redisCache = id ? await getUploadMeta(id) : undefined;
|
|
344
369
|
const { fileSize: cachedSize, Key: redisKey, Bucket: redisBucket, } = redisCache || {};
|
|
345
370
|
if (!redisKey && Key && Bucket) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/uploadChunk.ts"],"names":[],"mappings":"AAqBA,wBAA8B,WAAW,CAAC,EACxC,IAAI,EACJ,EAAE,EACF,IAAI,EACJ,MAAM,EACN,GAAG,EACH,IAAI,GACL,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,GAAG,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd,
|
|
1
|
+
{"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/uploadChunk.ts"],"names":[],"mappings":"AAqBA,wBAA8B,WAAW,CAAC,EACxC,IAAI,EACJ,EAAE,EACF,IAAI,EACJ,MAAM,EACN,GAAG,EACH,IAAI,GACL,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,GAAG,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd,gBAwIA"}
|
|
@@ -39,14 +39,16 @@ export default async function uploadChunk({ host, id, body, offset, end, size, }
|
|
|
39
39
|
return { error: "Chunk size mismatch", code: 400 };
|
|
40
40
|
}
|
|
41
41
|
const relpath = path.posix.join(uploadChunkDirectory, `${id}.${meta.extension}`);
|
|
42
|
-
const filepath = path.posix.join(config.folder, uploadChunkDirectory, `${id}.${meta.extension}`);
|
|
43
42
|
const res = await getFileSize(relpath, { fallback: false }).catch((err) => ({
|
|
44
43
|
error: err.toString(),
|
|
44
|
+
code: err.code,
|
|
45
45
|
}));
|
|
46
|
-
if (res.error &&
|
|
46
|
+
if (res.error &&
|
|
47
|
+
res.error !== "Error: file not found" &&
|
|
48
|
+
res.code !== "ENOENT") {
|
|
47
49
|
throw new Error(res.error);
|
|
48
50
|
}
|
|
49
|
-
if (
|
|
51
|
+
if (!res.error && res > meta.size) {
|
|
50
52
|
throw new Error("uploaded size exceeds file size");
|
|
51
53
|
}
|
|
52
54
|
// append chunk to existing file
|
|
@@ -67,8 +69,16 @@ export default async function uploadChunk({ host, id, body, offset, end, size, }
|
|
|
67
69
|
// save metadata
|
|
68
70
|
const fileBytes = Buffer.from(JSON.stringify(metaData, null, 2));
|
|
69
71
|
await writeFile(path.join(metaDir, `${id}.json`), fileBytes);
|
|
70
|
-
const payload = {
|
|
71
|
-
|
|
72
|
+
const payload = {
|
|
73
|
+
uploaded: meta.uploaded,
|
|
74
|
+
finished,
|
|
75
|
+
filepath: path.posix.join(meta.relativeDirpath, meta.fileName),
|
|
76
|
+
};
|
|
77
|
+
const hookData = await applyHook("afterChunkedUpload", {
|
|
78
|
+
id,
|
|
79
|
+
meta,
|
|
80
|
+
payload,
|
|
81
|
+
});
|
|
72
82
|
return hookData || payload;
|
|
73
83
|
}
|
|
74
84
|
return { uploaded: meta.uploaded, finished };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suggest.d.ts","sourceRoot":"","sources":["../../../../../server/routes/table/controllers/suggest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAuBpD,wBAAsB,kBAAkB,CAAC,EACvC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,QAAQ,EACR,UAAU,EACV,GAAG,EACH,EAAqB,GACtB,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,UAAU,CAAC;CACjB;;;;;
|
|
1
|
+
{"version":3,"file":"suggest.d.ts","sourceRoot":"","sources":["../../../../../server/routes/table/controllers/suggest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAuBpD,wBAAsB,kBAAkB,CAAC,EACvC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,QAAQ,EACR,UAAU,EACV,GAAG,EACH,EAAqB,GACtB,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,UAAU,CAAC;CACjB;;;;;UAwFA;AAED,wBAA8B,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,gBA0XzD"}
|
|
@@ -27,6 +27,14 @@ export async function getTableColumnMeta({ table, template, column, selectName,
|
|
|
27
27
|
.filter((el) => !el.disabled && el.sql && el.sql.includes(column) && false)
|
|
28
28
|
.map((el, i) => ` left join lateral (${el.sql}) ${el.name || `t${i}`} on 1=1 `)
|
|
29
29
|
?.join("") || "";
|
|
30
|
+
if (arr?.length && !arr.find((el) => el.id === "null")) {
|
|
31
|
+
arr.push({
|
|
32
|
+
color: "#B8B8B8",
|
|
33
|
+
icon: null,
|
|
34
|
+
id: "null",
|
|
35
|
+
text: "None",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
30
38
|
// w/out select
|
|
31
39
|
const searchQueryDefault = startsWith && key
|
|
32
40
|
? `(left(lower("text"::text),${key.length}) = $1 )`
|
|
@@ -176,7 +184,7 @@ export default async function suggest(req, reply) {
|
|
|
176
184
|
if (tableName && pg?.pk?.[tableName] && columnName) {
|
|
177
185
|
const qRes = await pg.queryCache(sqlCls, { table: tableName });
|
|
178
186
|
const vals = (query.count
|
|
179
|
-
? qRes.rows?.map?.((el) => el.value?.toString?.())
|
|
187
|
+
? qRes.rows?.map?.((el) => el.value?.toString?.() || null)
|
|
180
188
|
: qRes.rows?.[0]?.array_agg) || [];
|
|
181
189
|
const lower = query.key?.toLowerCase?.();
|
|
182
190
|
const data1 = query.key || query.val
|
|
@@ -185,7 +193,9 @@ export default async function suggest(req, reply) {
|
|
|
185
193
|
(el[lang] || el.text)?.toLowerCase()?.indexOf(lower) !== -1)
|
|
186
194
|
?.filter((el) => !query.val || query.val?.split?.(",")?.includes?.(el.id))
|
|
187
195
|
: arr;
|
|
188
|
-
const data2 = data1.filter((el) => el.id
|
|
196
|
+
const data2 = data1.filter((el) => el.id === "null"
|
|
197
|
+
? vals.includes(null)
|
|
198
|
+
: el.id && vals.includes(el.id.toString()));
|
|
189
199
|
const data = data2.slice(0, Math.min(query.limit || limit, limit));
|
|
190
200
|
if (data.length && query.count) {
|
|
191
201
|
const counts = qRes.rows?.reduce?.((acc, curr) => ({
|
|
@@ -245,7 +255,7 @@ export default async function suggest(req, reply) {
|
|
|
245
255
|
};
|
|
246
256
|
}
|
|
247
257
|
// select, with search by text support
|
|
248
|
-
const search = query.key ? searchQuery : null;
|
|
258
|
+
const search = query.key && typeof query.key === "string" ? searchQuery : null;
|
|
249
259
|
// filter by value
|
|
250
260
|
const val = query.val
|
|
251
261
|
? `${meta.pk}=any('{${query.val.replace(/'/g, "''")}}')`
|
|
@@ -258,13 +268,22 @@ export default async function suggest(req, reply) {
|
|
|
258
268
|
const where = [search, val, filter, meta.pk ? `${meta.pk} is not null` : null]
|
|
259
269
|
.filter(Boolean)
|
|
260
270
|
.join(" and ") || "true";
|
|
261
|
-
const sqlSuggest = `with c(id,text) as ( ${meta.original.replace(/{{parent}}/gi, parent)} where ${where} ${meta.original.toLowerCase().includes("order by") ? "" : "order by 2"}) select * from c where ${filter} limit ${Math.min(query.limit || meta.limit || limit, limit)}`.replace(/{{uid}}/g, user?.uid || "0");
|
|
271
|
+
// const sqlSuggest = `with c(id,text) as ( ${meta.original.replace(/{{parent}}/gi, parent)} where ${where} ${meta.original.toLowerCase().includes("order by") ? "" : "order by 2"}) select * from c where ${filter} limit ${Math.min(query.limit || meta.limit || limit, limit)}`.replace(/{{uid}}/g, user?.uid || "0");
|
|
272
|
+
const sqlSuggest = `${meta.original}
|
|
273
|
+
WHERE EXISTS ( SELECT 1 FROM ${tableName} o WHERE o.${column} IS NOT DISTINCT FROM c.id ) and ${val || "true"} and ${search || "true"}
|
|
274
|
+
${meta.original.toLowerCase().includes("order by") ? "" : "order by 2"}
|
|
275
|
+
LIMIT ${Math.min(query.limit || meta.limit || limit, limit)}`
|
|
276
|
+
.replace(/{{parent}}/gi, parent)
|
|
277
|
+
.replace(/{{uid}}/g, user?.uid || "0");
|
|
262
278
|
if (query.sql && debugMode) {
|
|
263
279
|
return sqlSuggest;
|
|
264
280
|
}
|
|
265
|
-
const { rows: dataNew } = await pg.query(sqlSuggest, query.key ? [query.key.toLowerCase()] : []);
|
|
281
|
+
const { rows: dataNew } = await pg.query(sqlSuggest, query.key && typeof query.key === "string" ? [query.key.toLowerCase()] : []);
|
|
266
282
|
// in case id / text = Boolean
|
|
267
283
|
const data = dataNew.filter((el) => Object.hasOwn(el, "id") && Object.hasOwn(el, "text"));
|
|
284
|
+
data
|
|
285
|
+
.filter((el) => el.id === null)
|
|
286
|
+
.forEach((el) => Object.assign(el, { id: "null", text: "None" }));
|
|
268
287
|
// table:column + select
|
|
269
288
|
if (tableName && column) {
|
|
270
289
|
const { name = selectName || column, type = "select" } = getColumnCLS(tableName, column) || {};
|