prostgles-server 4.2.625 → 4.2.626
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/Audit/AuditTable.d.ts +2 -1
- package/dist/Audit/AuditTable.d.ts.map +1 -1
- package/dist/Audit/AuditTable.js.map +1 -1
- package/dist/Audit/getAuditTableRules.d.ts.map +1 -1
- package/dist/Audit/getAuditTableRules.js +3 -3
- package/dist/Audit/getAuditTableRules.js.map +1 -1
- package/dist/DBSchemaBuilder/getClientDBGeneratedSchemas.d.ts.map +1 -1
- package/dist/DBSchemaBuilder/getClientDBGeneratedSchemas.js +7 -1
- package/dist/DBSchemaBuilder/getClientDBGeneratedSchemas.js.map +1 -1
- package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.d.ts +3 -1
- package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.d.ts.map +1 -1
- package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js +19 -5
- package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js.map +1 -1
- package/dist/DboBuilder/TableHandler/updateFile.d.ts +2 -39
- package/dist/DboBuilder/TableHandler/updateFile.d.ts.map +1 -1
- package/dist/DboBuilder/TableHandler/updateFile.js +1 -0
- package/dist/DboBuilder/TableHandler/updateFile.js.map +1 -1
- package/dist/DboBuilder/TableHandler/uploadFile.d.ts +2 -1
- package/dist/DboBuilder/TableHandler/uploadFile.d.ts.map +1 -1
- package/dist/DboBuilder/TableHandler/uploadFile.js +2 -1
- package/dist/DboBuilder/TableHandler/uploadFile.js.map +1 -1
- package/dist/DboBuilder/ViewHandler/getRawInfo.d.ts.map +1 -1
- package/dist/DboBuilder/ViewHandler/getRawInfo.js +6 -2
- package/dist/DboBuilder/ViewHandler/getRawInfo.js.map +1 -1
- package/dist/DboBuilder/getColumns.d.ts.map +1 -1
- package/dist/DboBuilder/getColumns.js +3 -0
- package/dist/DboBuilder/getColumns.js.map +1 -1
- package/dist/ProstglesTypes.d.ts +10 -0
- package/dist/ProstglesTypes.d.ts.map +1 -1
- package/dist/ProstglesTypes.js.map +1 -1
- package/dist/PublishParser/PublishParser.d.ts.map +1 -1
- package/dist/PublishParser/PublishParser.js +7 -0
- package/dist/PublishParser/PublishParser.js.map +1 -1
- package/dist/PublishParser/getFileTableRules.d.ts.map +1 -1
- package/dist/PublishParser/getFileTableRules.js +4 -1
- package/dist/PublishParser/getFileTableRules.js.map +1 -1
- package/dist/PublishParser/getFileVersionTableRules.d.ts +7 -0
- package/dist/PublishParser/getFileVersionTableRules.d.ts.map +1 -0
- package/dist/PublishParser/getFileVersionTableRules.js +77 -0
- package/dist/PublishParser/getFileVersionTableRules.js.map +1 -0
- package/dist/PublishParser/getFileVersionTableRules.spec.d.ts +2 -0
- package/dist/PublishParser/getFileVersionTableRules.spec.d.ts.map +1 -0
- package/dist/PublishParser/getFileVersionTableRules.spec.js +50 -0
- package/dist/PublishParser/getFileVersionTableRules.spec.js.map +1 -0
- package/dist/PublishParser/getPublishedTableNames.d.ts.map +1 -1
- package/dist/PublishParser/getPublishedTableNames.js +11 -4
- package/dist/PublishParser/getPublishedTableNames.js.map +1 -1
- package/dist/StorageClient/deleteUnreferencedFile.d.ts.map +1 -1
- package/dist/StorageClient/deleteUnreferencedFile.js +17 -1
- package/dist/StorageClient/deleteUnreferencedFile.js.map +1 -1
- package/dist/StorageClient/fileTableDefinitions.d.ts +54 -0
- package/dist/StorageClient/fileTableDefinitions.d.ts.map +1 -0
- package/dist/StorageClient/fileTableDefinitions.js +38 -0
- package/dist/StorageClient/fileTableDefinitions.js.map +1 -0
- package/dist/StorageClient/fileVersionUtils.d.ts +18 -0
- package/dist/StorageClient/fileVersionUtils.d.ts.map +1 -0
- package/dist/StorageClient/fileVersionUtils.js +22 -0
- package/dist/StorageClient/fileVersionUtils.js.map +1 -0
- package/dist/StorageClient/getFileStorageKey.d.ts +3 -1
- package/dist/StorageClient/getFileStorageKey.d.ts.map +1 -1
- package/dist/StorageClient/getFileStorageKey.js +9 -1
- package/dist/StorageClient/getFileStorageKey.js.map +1 -1
- package/dist/StorageClient/getFileTableConfig.d.ts +1 -23
- package/dist/StorageClient/getFileTableConfig.d.ts.map +1 -1
- package/dist/StorageClient/getFileTableConfig.js +63 -25
- package/dist/StorageClient/getFileTableConfig.js.map +1 -1
- package/dist/StorageClient/getFileTableConfig.spec.js +6 -1
- package/dist/StorageClient/getFileTableConfig.spec.js.map +1 -1
- package/dist/StorageClient/saveFileVersion.d.ts +7 -0
- package/dist/StorageClient/saveFileVersion.d.ts.map +1 -0
- package/dist/StorageClient/saveFileVersion.js +71 -0
- package/dist/StorageClient/saveFileVersion.js.map +1 -0
- package/dist/StorageClient/setupFileServeHandler.d.ts.map +1 -1
- package/dist/StorageClient/setupFileServeHandler.js +38 -21
- package/dist/StorageClient/setupFileServeHandler.js.map +1 -1
- package/dist/TableConfig/TableRowFromColumnDefinitions.d.ts +11 -4
- package/dist/TableConfig/TableRowFromColumnDefinitions.d.ts.map +1 -1
- package/dist/TableConfig/initTableConfig.d.ts.map +1 -1
- package/dist/TableConfig/initTableConfig.js +9 -6
- package/dist/TableConfig/initTableConfig.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/lib/Audit/AuditTable.ts +4 -1
- package/lib/Audit/getAuditTableRules.ts +5 -1
- package/lib/DBSchemaBuilder/getClientDBGeneratedSchemas.ts +9 -1
- package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +31 -5
- package/lib/DboBuilder/TableHandler/updateFile.ts +1 -0
- package/lib/DboBuilder/TableHandler/uploadFile.ts +3 -0
- package/lib/DboBuilder/ViewHandler/getRawInfo.ts +6 -2
- package/lib/DboBuilder/getColumns.ts +2 -0
- package/lib/ProstglesTypes.ts +11 -0
- package/lib/PublishParser/PublishParser.ts +25 -7
- package/lib/PublishParser/getFileTableRules.ts +7 -1
- package/lib/PublishParser/getFileVersionTableRules.spec.ts +57 -0
- package/lib/PublishParser/getFileVersionTableRules.ts +106 -0
- package/lib/PublishParser/getPublishedTableNames.ts +14 -5
- package/lib/StorageClient/deleteUnreferencedFile.ts +20 -2
- package/lib/StorageClient/fileTableDefinitions.ts +52 -0
- package/lib/StorageClient/fileVersionUtils.ts +37 -0
- package/lib/StorageClient/getFileStorageKey.ts +11 -2
- package/lib/StorageClient/getFileTableConfig.spec.ts +6 -1
- package/lib/StorageClient/getFileTableConfig.ts +85 -30
- package/lib/StorageClient/saveFileVersion.ts +82 -0
- package/lib/StorageClient/setupFileServeHandler.ts +51 -28
- package/lib/TableConfig/TableRowFromColumnDefinitions.ts +37 -13
- package/lib/TableConfig/initTableConfig.ts +10 -6
- package/lib/index.ts +2 -1
- package/package.json +1 -1
|
@@ -1,37 +1,27 @@
|
|
|
1
|
-
import { omitKeys } from "prostgles-types";
|
|
1
|
+
import { asName, omitKeys } from "prostgles-types";
|
|
2
|
+
import { md5 } from "prostgles-types/dist/md5";
|
|
2
3
|
import { onDeleteFromFileTable } from "../DboBuilder/TableHandler/onDeleteFromFileTable";
|
|
3
4
|
import { updateFile } from "../DboBuilder/TableHandler/updateFile";
|
|
4
5
|
import { assertFileObjectValid, uploadFile } from "../DboBuilder/TableHandler/uploadFile";
|
|
5
6
|
import type { Prostgles } from "../Prostgles";
|
|
6
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
AfterEachTsTrigger,
|
|
9
|
+
BeforeEachTsTrigger,
|
|
10
|
+
} from "../PublishParser/publishTypesAndUtils";
|
|
7
11
|
import type { TableConfig } from "../TableConfig/TableConfigTypes";
|
|
8
|
-
import type { TableRowFromColumnDefinitions } from "../TableConfig/TableRowFromColumnDefinitions";
|
|
9
12
|
import type { TableHooks } from "../TableHooks/TableHooks";
|
|
10
13
|
import { setupFileServeHandler } from "./setupFileServeHandler";
|
|
11
14
|
import type { TableHandlers } from "../DboBuilder/DboBuilder";
|
|
15
|
+
import { getFileTableHandler, getFileVersionTableName } from "./fileVersionUtils";
|
|
16
|
+
import { backfillFileVersions, saveFileVersion } from "./saveFileVersion";
|
|
17
|
+
import {
|
|
18
|
+
FILE_TABLE_COLUMN_DEFINITIONS,
|
|
19
|
+
FILE_TABLE_VERSION_COLUMN_DEFINITIONS,
|
|
20
|
+
FILE_VERSION_TABLE_COLUMN_DEFINITIONS,
|
|
21
|
+
type FileTableRow,
|
|
22
|
+
} from "./fileTableDefinitions";
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
id: `UUID PRIMARY KEY DEFAULT gen_random_uuid()`,
|
|
15
|
-
storage_key: `UUID UNIQUE`, // NULL for legacy objects stored under their file ID.
|
|
16
|
-
extension: `TEXT NOT NULL DEFAULT ''`,
|
|
17
|
-
content_type: `TEXT NOT NULL DEFAULT ''`,
|
|
18
|
-
content_length: `BIGINT NOT NULL DEFAULT 0`,
|
|
19
|
-
etag: `TEXT NOT NULL DEFAULT ''`,
|
|
20
|
-
original_name: `TEXT NOT NULL`,
|
|
21
|
-
original_last_modified: `TIMESTAMPTZ`,
|
|
22
|
-
description: `TEXT`,
|
|
23
|
-
url: `TEXT NOT NULL DEFAULT ''`,
|
|
24
|
-
cloud_url: `TEXT`,
|
|
25
|
-
signed_url: `TEXT`,
|
|
26
|
-
signed_url_expires: `BIGINT`,
|
|
27
|
-
added: `TIMESTAMP NOT NULL DEFAULT NOW()`,
|
|
28
|
-
updated: `TIMESTAMP NOT NULL DEFAULT NOW()`,
|
|
29
|
-
deleted: `TIMESTAMPTZ`,
|
|
30
|
-
deleted_from_storage: `TIMESTAMPTZ`,
|
|
31
|
-
data: `BYTEA NOT NULL CHECK (data = decode('01', 'hex'))`, // Used as a placeholder to ensure insert types are correct. Actual data is uploaded to storageClient and not stored in the DB
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
export type FileTableRow = TableRowFromColumnDefinitions<typeof FILE_TABLE_COLUMN_DEFINITIONS>;
|
|
24
|
+
export type { FileTableInsertRow, FileTableRow } from "./fileTableDefinitions";
|
|
35
25
|
|
|
36
26
|
export const getFileTableConfig = (
|
|
37
27
|
prg: Prostgles,
|
|
@@ -44,7 +34,30 @@ export const getFileTableConfig = (
|
|
|
44
34
|
const { expressApp } = fileTable;
|
|
45
35
|
|
|
46
36
|
const { tableName: fileTableName, storageClient } = fileTable;
|
|
37
|
+
const versionTableName = getFileVersionTableName(fileTable);
|
|
38
|
+
const versionConstraintName = `prostgles_file_version_${md5(JSON.stringify([versionTableName, fileTableName]))}`;
|
|
39
|
+
const versionIndexName = `prostgles_file_version_${md5(versionTableName)}_key`;
|
|
40
|
+
if (fileTable.versioning) {
|
|
41
|
+
if (versionTableName === fileTableName) {
|
|
42
|
+
throw new Error("File version table name must differ from the file table name");
|
|
43
|
+
}
|
|
44
|
+
if (
|
|
45
|
+
fileTable.versioning.maxVersions !== undefined &&
|
|
46
|
+
(!Number.isInteger(fileTable.versioning.maxVersions) || fileTable.versioning.maxVersions < 1)
|
|
47
|
+
) {
|
|
48
|
+
throw new Error("fileTable.versioning.maxVersions must be a positive integer");
|
|
49
|
+
}
|
|
50
|
+
if (tableConfig?.[versionTableName]) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`File version table name (${versionTableName}) is managed and cannot be configured in tableConfig`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
47
56
|
|
|
57
|
+
const fileColumnDefinitions = {
|
|
58
|
+
...FILE_TABLE_COLUMN_DEFINITIONS,
|
|
59
|
+
...(fileTable.versioning && FILE_TABLE_VERSION_COLUMN_DEFINITIONS),
|
|
60
|
+
};
|
|
48
61
|
const userFileTableConfig = tableConfig?.[fileTableName];
|
|
49
62
|
if (userFileTableConfig) {
|
|
50
63
|
if ("isLookupTable" in userFileTableConfig) {
|
|
@@ -59,12 +72,21 @@ export const getFileTableConfig = (
|
|
|
59
72
|
}
|
|
60
73
|
if ("columns" in userFileTableConfig && userFileTableConfig.columns) {
|
|
61
74
|
const userCols = new Set(Object.keys(userFileTableConfig.columns));
|
|
62
|
-
const
|
|
75
|
+
const clashingFileColumns = Object.keys(fileColumnDefinitions).filter((col) =>
|
|
63
76
|
userCols.has(col),
|
|
64
77
|
);
|
|
65
|
-
if (
|
|
78
|
+
if (clashingFileColumns.length) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`FileManager table name (${fileTableName}) has clashing column names in tableConfig: ${clashingFileColumns}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const clashingFileRevisionColumns = Object.keys(FILE_VERSION_TABLE_COLUMN_DEFINITIONS).filter(
|
|
85
|
+
(col) => userCols.has(col),
|
|
86
|
+
);
|
|
87
|
+
if (clashingFileRevisionColumns.length) {
|
|
66
88
|
throw new Error(
|
|
67
|
-
`FileManager table name (${fileTableName}) has clashing column names in tableConfig: ${
|
|
89
|
+
`FileManager table name (${fileTableName}) has clashing column names with file revision table in tableConfig: ${clashingFileRevisionColumns}`,
|
|
68
90
|
);
|
|
69
91
|
}
|
|
70
92
|
}
|
|
@@ -74,7 +96,7 @@ export const getFileTableConfig = (
|
|
|
74
96
|
[fileTableName]: {
|
|
75
97
|
...userFileTableConfig,
|
|
76
98
|
columns: {
|
|
77
|
-
...
|
|
99
|
+
...fileColumnDefinitions,
|
|
78
100
|
...userFileTableConfig?.columns,
|
|
79
101
|
},
|
|
80
102
|
onMount: ({ _db }) => {
|
|
@@ -89,6 +111,24 @@ export const getFileTableConfig = (
|
|
|
89
111
|
};
|
|
90
112
|
},
|
|
91
113
|
},
|
|
114
|
+
...(fileTable.versioning && {
|
|
115
|
+
[versionTableName]: {
|
|
116
|
+
/** Mirror user columns in the file revision table */
|
|
117
|
+
columns: { ...FILE_VERSION_TABLE_COLUMN_DEFINITIONS, ...userFileTableConfig?.columns },
|
|
118
|
+
constraints: {
|
|
119
|
+
[versionConstraintName]: `FOREIGN KEY (file_id) REFERENCES ${asName(fileTableName)}(id) ON DELETE CASCADE`,
|
|
120
|
+
},
|
|
121
|
+
indexes: {
|
|
122
|
+
[versionIndexName]: {
|
|
123
|
+
unique: true,
|
|
124
|
+
columns: "file_id, version",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
onMount: async ({ dbo }) => {
|
|
128
|
+
await backfillFileVersions(dbo, fileTable);
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
92
132
|
...omitKeys(tableConfig ?? {}, [fileTableName]),
|
|
93
133
|
};
|
|
94
134
|
|
|
@@ -156,9 +196,24 @@ export const getFileTableConfig = (
|
|
|
156
196
|
} satisfies BeforeEachTsTrigger<FileTableRow, TableHandlers>,
|
|
157
197
|
...(userFileTableHooks?.beforeEach || []),
|
|
158
198
|
],
|
|
159
|
-
|
|
199
|
+
afterEach: [
|
|
200
|
+
...(userFileTableHooks?.afterEach || []),
|
|
201
|
+
{
|
|
202
|
+
commands: { insert: 1, update: 1 },
|
|
203
|
+
validate: async ({ row, dbx, onCommit }) => {
|
|
204
|
+
const fileRow = await getFileTableHandler(dbx, fileTable).findOne({ id: row.id });
|
|
205
|
+
if (!fileRow) throw new Error(`File not found after write: ${row.id}`);
|
|
206
|
+
await saveFileVersion(fileTable, fileRow, dbx, onCommit);
|
|
207
|
+
},
|
|
208
|
+
} satisfies AfterEachTsTrigger<FileTableRow, TableHandlers> as AfterEachTsTrigger<
|
|
209
|
+
any,
|
|
210
|
+
TableHandlers
|
|
211
|
+
>,
|
|
212
|
+
],
|
|
213
|
+
onInsteadOfDelete: async ({ onCommit, dbx, tx, returningQuery, isOneOrNone, filterOpts }) => {
|
|
160
214
|
return onDeleteFromFileTable(fileTable, {
|
|
161
215
|
onCommit,
|
|
216
|
+
dbx,
|
|
162
217
|
t: tx,
|
|
163
218
|
returningQuery,
|
|
164
219
|
isOneOrNone,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { pickKeys } from "prostgles-types";
|
|
2
|
+
import type { TableHandlers } from "../DboBuilder/DboBuilder";
|
|
3
|
+
import type { TableHandler } from "../DboBuilder/TableHandler/TableHandler";
|
|
4
|
+
import type { FileTableConfig } from "../ProstglesTypes";
|
|
5
|
+
import type { OnCommit } from "../PublishParser/publishTypesAndUtils";
|
|
6
|
+
import { deleteUnreferencedFile } from "./deleteUnreferencedFile";
|
|
7
|
+
import type { FileTableRow, FileVersionTableInsertRow } from "./fileTableDefinitions";
|
|
8
|
+
import { getFileRevisionsTableHandler, getFileTableHandler } from "./fileVersionUtils";
|
|
9
|
+
import { getFileStorageKey } from "./getFileStorageKey";
|
|
10
|
+
|
|
11
|
+
export const saveFileVersion = async (
|
|
12
|
+
config: FileTableConfig,
|
|
13
|
+
row: FileTableRow,
|
|
14
|
+
dbx: TableHandlers,
|
|
15
|
+
onCommit: OnCommit,
|
|
16
|
+
) => {
|
|
17
|
+
if (!config.versioning) return;
|
|
18
|
+
const version = Number(row.version);
|
|
19
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
20
|
+
throw new Error(`Invalid file version: ${row.version}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const versions = getFileRevisionsTableHandler(dbx, config);
|
|
24
|
+
const versionColumns = (versions as TableHandler).column_names;
|
|
25
|
+
await versions.insert(getFileVersionRow(row, versionColumns), {
|
|
26
|
+
onConflict: {
|
|
27
|
+
action: "DoNothing",
|
|
28
|
+
conflictColumns: ["file_id", "version"],
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const { maxVersions } = config.versioning;
|
|
33
|
+
if (maxVersions === undefined) return;
|
|
34
|
+
const removed = (await versions.find(
|
|
35
|
+
{ file_id: row.id },
|
|
36
|
+
{
|
|
37
|
+
select: { id: 1, storage_key: 1 },
|
|
38
|
+
orderBy: { version: -1 },
|
|
39
|
+
offset: maxVersions,
|
|
40
|
+
limit: null,
|
|
41
|
+
},
|
|
42
|
+
)) as { id: string; storage_key: string }[];
|
|
43
|
+
if (!removed.length) return;
|
|
44
|
+
|
|
45
|
+
await versions.delete({ id: { $in: removed.map(({ id }) => id) } });
|
|
46
|
+
for (const storageKey of new Set(removed.map(({ storage_key }) => storage_key))) {
|
|
47
|
+
onCommit(({ db }) => deleteUnreferencedFile(db, config, storageKey));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const backfillFileVersions = async (dbo: TableHandlers, config: FileTableConfig) => {
|
|
52
|
+
if (!config.versioning) return;
|
|
53
|
+
const files = getFileTableHandler(dbo, config);
|
|
54
|
+
const fileRows = await files.find({}, { limit: null });
|
|
55
|
+
if (!fileRows.length) return;
|
|
56
|
+
console.log(`Backfilling file versions for ${fileRows.length} files`);
|
|
57
|
+
const fileRevisionsTableHandler = getFileRevisionsTableHandler(dbo, config);
|
|
58
|
+
const fileRevisionColumns = (fileRevisionsTableHandler as TableHandler).column_names;
|
|
59
|
+
await fileRevisionsTableHandler.insertMany(
|
|
60
|
+
fileRows.map((fileRow) => getFileVersionRow(fileRow, fileRevisionColumns)),
|
|
61
|
+
{
|
|
62
|
+
onConflict: {
|
|
63
|
+
action: "DoNothing",
|
|
64
|
+
conflictColumns: ["file_id", "version"],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Must ensure we copy over any columns that might have been created by the user */
|
|
71
|
+
const getFileVersionRow = (row: FileTableRow, cols: string[]): FileVersionTableInsertRow => {
|
|
72
|
+
const { id, ...insertRow } = pickKeys(row, cols as (keyof FileTableRow)[]);
|
|
73
|
+
const version = Number(row.version ?? 1);
|
|
74
|
+
return {
|
|
75
|
+
...insertRow,
|
|
76
|
+
file_id: id,
|
|
77
|
+
version,
|
|
78
|
+
storage_key: getFileStorageKey(row),
|
|
79
|
+
url: `${row.url}?version=${version}`,
|
|
80
|
+
created: row.updated,
|
|
81
|
+
};
|
|
82
|
+
};
|
|
@@ -6,9 +6,11 @@ import type { DB } from "../initProstgles";
|
|
|
6
6
|
import type { Prostgles } from "../Prostgles";
|
|
7
7
|
import type { FileTableConfig } from "../ProstglesTypes";
|
|
8
8
|
import { runClientRequest } from "../runClientRequest";
|
|
9
|
-
import type {
|
|
10
|
-
import
|
|
9
|
+
import type { FileVersionTableRow } from "./fileTableDefinitions";
|
|
10
|
+
import { getFileVersionTableName } from "./fileVersionUtils";
|
|
11
11
|
import { getFileStorageKey } from "./getFileStorageKey";
|
|
12
|
+
import type { FileTableRow } from "./getFileTableConfig";
|
|
13
|
+
import type { StorageClient } from "./StorageClientTypes";
|
|
12
14
|
|
|
13
15
|
export const getFileServeRoute = (config: FileTableConfig) => {
|
|
14
16
|
const fileTableName = config.tableName;
|
|
@@ -36,22 +38,39 @@ export const setupFileServeHandler = (
|
|
|
36
38
|
if (typeof id !== "string" || !id) {
|
|
37
39
|
throw "Invalid media name";
|
|
38
40
|
}
|
|
41
|
+
const versionParam = req.query.version;
|
|
42
|
+
const version = typeof versionParam === "string" ? Number(versionParam) : undefined;
|
|
43
|
+
if (
|
|
44
|
+
versionParam !== undefined &&
|
|
45
|
+
(typeof versionParam !== "string" ||
|
|
46
|
+
!Number.isInteger(version) ||
|
|
47
|
+
version! < 1 ||
|
|
48
|
+
String(version) !== versionParam)
|
|
49
|
+
) {
|
|
50
|
+
throw "Invalid file version";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (version && !config.versioning) {
|
|
54
|
+
throw "File version requested but versioning is not enabled";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const select = {
|
|
58
|
+
id: 1,
|
|
59
|
+
storage_key: 1,
|
|
60
|
+
signed_url: 1,
|
|
61
|
+
signed_url_expires: 1,
|
|
62
|
+
content_type: 1,
|
|
63
|
+
} satisfies
|
|
64
|
+
Partial<Record<keyof FileTableRow, 1>> | Partial<Record<keyof FileVersionTableRow, 1>>;
|
|
39
65
|
|
|
40
|
-
const selectParams = {
|
|
41
|
-
select: {
|
|
42
|
-
id: 1,
|
|
43
|
-
storage_key: 1,
|
|
44
|
-
signed_url: 1,
|
|
45
|
-
signed_url_expires: 1,
|
|
46
|
-
content_type: 1,
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
66
|
const file = (await runClientRequest.bind(prg)(
|
|
50
67
|
{
|
|
51
68
|
command: "findOne",
|
|
52
|
-
tableName: fileTableName,
|
|
53
|
-
param1: { id },
|
|
54
|
-
param2:
|
|
69
|
+
tableName: version === undefined ? fileTableName : getFileVersionTableName(config),
|
|
70
|
+
param1: version === undefined ? { id } : { file_id: id, version },
|
|
71
|
+
param2: {
|
|
72
|
+
select,
|
|
73
|
+
},
|
|
55
74
|
param3: undefined,
|
|
56
75
|
},
|
|
57
76
|
{
|
|
@@ -59,16 +78,18 @@ export const setupFileServeHandler = (
|
|
|
59
78
|
httpReq: req,
|
|
60
79
|
},
|
|
61
80
|
undefined,
|
|
62
|
-
)) as FileTableRow | undefined;
|
|
81
|
+
)) as Pick<FileTableRow, keyof typeof select> | undefined;
|
|
63
82
|
|
|
64
83
|
if (!file) {
|
|
65
|
-
res
|
|
84
|
+
res
|
|
85
|
+
.status(HTTP_FAIL_CODES.NOT_FOUND)
|
|
86
|
+
.send(version === undefined ? "File not found or not allowed" : "File version not found");
|
|
66
87
|
return;
|
|
67
88
|
}
|
|
68
89
|
|
|
69
90
|
if (storageClient.type === "cloud") {
|
|
70
|
-
let url = file.signed_url;
|
|
71
|
-
const expires = +(file.signed_url_expires || 0);
|
|
91
|
+
let url = version === undefined ? file.signed_url : undefined;
|
|
92
|
+
const expires = version === undefined ? +(file.signed_url_expires || 0) : 0;
|
|
72
93
|
|
|
73
94
|
const HOUR = 3600 * 1000;
|
|
74
95
|
const EXPIRES = Date.now() + HOUR;
|
|
@@ -76,20 +97,22 @@ export const setupFileServeHandler = (
|
|
|
76
97
|
url = await storageClient.getSignedUrlForDownload(getFileStorageKey(file), 60 * 60);
|
|
77
98
|
|
|
78
99
|
// Match the storage key to avoid caching a stale URL after replacement; NULL matches legacy files.
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
if (version === undefined) {
|
|
101
|
+
await db.any(
|
|
102
|
+
"UPDATE ${fileTableName:name} \
|
|
81
103
|
SET signed_url = ${signed_url}, \
|
|
82
104
|
signed_url_expires = ${signed_url_expires} \
|
|
83
105
|
WHERE id = ${id} \
|
|
84
106
|
AND storage_key IS NOT DISTINCT FROM ${storage_key}::uuid",
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
{
|
|
108
|
+
fileTableName,
|
|
109
|
+
id: file.id,
|
|
110
|
+
storage_key: file.storage_key,
|
|
111
|
+
signed_url: url,
|
|
112
|
+
signed_url_expires: EXPIRES,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
}
|
|
93
116
|
}
|
|
94
117
|
|
|
95
118
|
res.redirect(url);
|
|
@@ -1,25 +1,49 @@
|
|
|
1
1
|
import type { JSONB } from "prostgles-types";
|
|
2
2
|
|
|
3
|
+
type ColumnDefinition =
|
|
4
|
+
| string
|
|
5
|
+
| { enum: readonly (string | number)[]; nullable?: boolean }
|
|
6
|
+
| { jsonbSchema: JSONB.JSONBSchema; nullable?: boolean };
|
|
7
|
+
|
|
3
8
|
/** Row types for the column definitions used by managed tables. */
|
|
4
9
|
export type TableRowFromColumnDefinitions<
|
|
5
|
-
T extends Record<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
T extends Record<string, ColumnDefinition>,
|
|
11
|
+
> = Omit<TableRow<T>, GeneratedColumnNames<T>> &
|
|
12
|
+
Partial<Pick<TableRow<T>, GeneratedColumnNames<T>>>;
|
|
13
|
+
|
|
14
|
+
type TableRow<T extends Record<string, ColumnDefinition>> = {
|
|
15
|
+
[K in keyof T]: T[K] extends {
|
|
16
|
+
jsonbSchema: infer S extends JSONB.JSONBSchema;
|
|
17
|
+
}
|
|
13
18
|
? JSONB.GetType<S> | (T[K] extends { nullable: true } ? null : never)
|
|
14
19
|
: T[K] extends { enum: readonly (infer V)[] }
|
|
15
20
|
? V | (T[K] extends { nullable: true } ? null : never)
|
|
16
|
-
: |
|
|
17
|
-
? Buffer
|
|
18
|
-
: T[K] extends `JSONB${string}`
|
|
19
|
-
? Record<string, unknown>
|
|
20
|
-
: string)
|
|
21
|
+
: | ColumnValue<T[K]>
|
|
21
22
|
| (T[K] extends
|
|
22
23
|
`${string} NOT NULL${string}` | `${string}PRIMARY KEY${string}`
|
|
23
24
|
? never
|
|
24
25
|
: null);
|
|
25
26
|
};
|
|
27
|
+
|
|
28
|
+
type GeneratedColumnNames<T extends Record<string, ColumnDefinition>> = {
|
|
29
|
+
[K in keyof T]: T[K] extends string
|
|
30
|
+
? T[K] extends
|
|
31
|
+
| `${string}DEFAULT${string}`
|
|
32
|
+
| `${string}SERIAL${string}`
|
|
33
|
+
| `${string}GENERATED${string}AS IDENTITY${string}`
|
|
34
|
+
? K
|
|
35
|
+
: never
|
|
36
|
+
: never;
|
|
37
|
+
}[keyof T];
|
|
38
|
+
|
|
39
|
+
type ColumnValue<T> = T extends `BYTEA${string}`
|
|
40
|
+
? Buffer
|
|
41
|
+
: T extends `JSONB${string}`
|
|
42
|
+
? Record<string, unknown>
|
|
43
|
+
: T extends `${"SMALLINT" | "INTEGER" | "SERIAL"}${string}`
|
|
44
|
+
? number
|
|
45
|
+
: T extends `${"BIGINT" | "BIGSERIAL" | "NUMERIC" | "DECIMAL"}${string}`
|
|
46
|
+
? string | number
|
|
47
|
+
: T extends `${"REAL" | "DOUBLE PRECISION"}${string}`
|
|
48
|
+
? number
|
|
49
|
+
: string;
|
|
@@ -183,6 +183,8 @@ export const initTableConfig = async function (this: TableConfigurator) {
|
|
|
183
183
|
});
|
|
184
184
|
if (tableColumnDefinition?.isCreate) {
|
|
185
185
|
queries.push(...(constraintDefs?.map((c) => c.alterQuery) ?? []));
|
|
186
|
+
await runQueries(queries);
|
|
187
|
+
await this.prostgles.rebuildDBO();
|
|
186
188
|
} else if (tableColumnDefinition) {
|
|
187
189
|
const fullSchema = await getFutureTableSchema({
|
|
188
190
|
db: this.db,
|
|
@@ -215,17 +217,19 @@ export const initTableConfig = async function (this: TableConfigurator) {
|
|
|
215
217
|
|
|
216
218
|
/** Add missing named constraints */
|
|
217
219
|
constraintDefs?.forEach((c) => {
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
if (!c.name) return;
|
|
221
|
+
const fc = futureCons.find((nc) => nc.name === c.name);
|
|
222
|
+
if (
|
|
223
|
+
fc &&
|
|
224
|
+
!currCons.some((cc) => cc.name === c.name && cc.definition === fc.definition)
|
|
225
|
+
) {
|
|
226
|
+
queries.push(`${ALTER_TABLE_Q} ADD CONSTRAINT ${asName(c.name)} ${c.content};`);
|
|
223
227
|
}
|
|
224
228
|
});
|
|
225
229
|
|
|
226
230
|
/** Add remaining missing constraints */
|
|
227
231
|
futureCons
|
|
228
|
-
.filter((nc) => !currCons.some((c) => c.definition === nc.definition))
|
|
232
|
+
.filter((nc) => !nc.isNamed && !currCons.some((c) => c.definition === nc.definition))
|
|
229
233
|
.forEach((c) => {
|
|
230
234
|
queries.push(`${ALTER_TABLE_Q} ADD ${c.definition};`);
|
|
231
235
|
});
|
package/lib/index.ts
CHANGED
|
@@ -52,11 +52,12 @@ export type * from "./TableHooks/TableHooks";
|
|
|
52
52
|
export * from "./Auth/utils/upsertNamedExpressMiddleware";
|
|
53
53
|
export type { RequestWithUser } from "./Auth/middleware/userContextMiddleware";
|
|
54
54
|
export default prostgles;
|
|
55
|
-
export type { FileTableRow } from "./StorageClient/getFileTableConfig";
|
|
55
|
+
export type { FileTableInsertRow, FileTableRow } from "./StorageClient/getFileTableConfig";
|
|
56
56
|
export { defineJoin };
|
|
57
57
|
export {
|
|
58
58
|
AUDIT_TABLE_COLUMN_DEFINITIONS,
|
|
59
59
|
AUDIT_TABLE_COLUMN_NAMES,
|
|
60
60
|
AUDIT_TABLE_COLUMNS,
|
|
61
|
+
type AuditTableInsertRow,
|
|
61
62
|
type AuditTableRow,
|
|
62
63
|
} from "./Audit/AuditTable";
|