diff --git a/API.manifestattachment.md b/API.manifestattachment.md index ffcc058..e154463 100644 --- a/API.manifestattachment.md +++ b/API.manifestattachment.md @@ -19,6 +19,9 @@ Attaches a file to an existing manifest. | `input.name` | `string` | The file name. | | `input.content` | `Buffer` | The file content. | | `[input.sort]` | `number` | Optional display sort order. | +| `[input.size]` | `number` | Optional file size in bytes (defaults to content length). | +| `[input.created]` | `Date|string` | Optional creation timestamp (defaults to current date). | +| `[input.modified]` | `Date|string` | Optional modified timestamp (defaults to created date). | | `[input.hash]` | `string` | The file hash (auto-computed SHA-256 if omitted). | | `[input.thumbavif]` | `Buffer` | Optional AVIF thumbnail buffer. | | `[input.thumbx]` | `number` | Optional thumbnail width. | @@ -26,7 +29,7 @@ Attaches a file to an existing manifest. **Returns** -`Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>` — The created attachment record. +`Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, size: number, created: Date, modified: Date, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>` — The created attachment record. **Throws** @@ -40,7 +43,7 @@ const attachment = await store.manifestattachment.add(42, 1, { name: 'invoice.pdf', content: fileBuffer, }); -// => { id, manifestId, fileId, sort, hash, mimetype, name, thumbavif, thumbx, thumby } +// => { id, manifestId, fileId, sort, hash, mimetype, name, size, created, modified, thumbavif, thumbx, thumby } ``` ## `manifestattachment.getById` @@ -59,7 +62,7 @@ Gets an attachment for a manifest by its attachment ID or file ID, including ful **Returns** -`Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, content: Buffer, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>` — The attachment record with binary content. +`Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, size: number, created: Date, modified: Date, content: Buffer, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>` — The attachment record with binary content. **Throws** @@ -69,7 +72,7 @@ Gets an attachment for a manifest by its attachment ID or file ID, including ful ```js const attachment = await store.manifestattachment.getById(42, 1, 5); -// => { id, manifestId, fileId, sort, hash, mimetype, name, content, thumbavif, thumbx, thumby } +// => { id, manifestId, fileId, sort, hash, mimetype, name, size, created, modified, content, thumbavif, thumbx, thumby } ``` ## `manifestattachment.listByManifestId` @@ -87,7 +90,7 @@ Lists all attachments for a given manifest, without loading their full binary co **Returns** -`Promise>` — A list of attachment metadata objects. +`Promise>` — A list of attachment metadata objects. **Throws** @@ -97,7 +100,7 @@ Lists all attachments for a given manifest, without loading their full binary co ```js const attachments = await store.manifestattachment.listByManifestId(42, 1); -// => [{ id, manifestId, fileId, sort, hash, mimetype, name, size, thumbavif, thumbx, thumby }] +// => [{ id, manifestId, fileId, sort, hash, mimetype, name, size, created, modified, thumbavif, thumbx, thumby }] ``` ## `manifestattachment.order` diff --git a/API.md b/API.md index e0aac71..6dfa892 100644 --- a/API.md +++ b/API.md @@ -123,9 +123,9 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met - `list(googleAuthId: number): Promise>` - `update(googleAuthId: number, id: number, patch: {number?: string, date?: string, entityId?: number, externalNumber?: string, lines?: Array<{sku?: string, name?: string, pu?: number, amount: number, net: number, rebate?: number, taxrate: number, comment?: string|null}>}): Promise<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>` - [manifestattachment](API.manifestattachment.md) - - `add(googleAuthId: number, manifestId: number, input: {mimetype: string, name: string, content: Buffer, sort?: number, hash?: string, thumbavif?: Buffer, thumbx?: number, thumby?: number}): Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>` - - `getById(googleAuthId: number, manifestId: number, id: number): Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, content: Buffer, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>` - - `listByManifestId(googleAuthId: number, manifestId: number): Promise>` + - `add(googleAuthId: number, manifestId: number, input: {mimetype: string, name: string, content: Buffer, sort?: number, size?: number, created?: Date|string, modified?: Date|string, hash?: string, thumbavif?: Buffer, thumbx?: number, thumby?: number}): Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, size: number, created: Date, modified: Date, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>` + - `getById(googleAuthId: number, manifestId: number, id: number): Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, size: number, created: Date, modified: Date, content: Buffer, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>` + - `listByManifestId(googleAuthId: number, manifestId: number): Promise>` - `order(googleAuthId: number, manifestId: number, input: {ids?: Array}): Promise<{ids: Array}>` - `remove(googleAuthId: number, manifestId: number, id: number): Promise` - [posterminal](API.posterminal.md) diff --git a/schema-graph.dot b/schema-graph.dot index a755cc1..11cdd68 100644 --- a/schema-graph.dot +++ b/schema-graph.dot @@ -55,7 +55,7 @@ digraph schema { pic [label=<
pic
idINT PK
wINT
hINT
sizeBIGINT
hashNVARCHAR(64)
thumbwINT
thumbhINT
thumbhashNVARCHAR(64)
sourceNVARCHAR(MAX)
fullbinaryVARBINARY(MAX)
thumbbinaryVARBINARY(MAX)
>]; brandpic [label=<
brandpic
idINT PK
brand_idINT FK
pic_idINT FK
sortINT
>]; articlepic [label=<
articlepic
idINT PK
article_idINT FK
pic_idINT FK
sortINT
>]; - file [label=<
file
idINT PK
hashNVARCHAR(64)
mimetypeNVARCHAR(255)
nameNVARCHAR(255)
contentVARBINARY(MAX)
thumbavif?VARBINARY(MAX)
thumbx?INT
thumby?INT
>]; + file [label=<
file
idINT PK
hashNVARCHAR(64)
mimetypeNVARCHAR(255)
nameNVARCHAR(255)
sizeBIGINT
createdDATETIME2
modifiedDATETIME2
contentVARBINARY(MAX)
thumbavif?VARBINARY(MAX)
thumbx?INT
thumby?INT
>]; } subgraph cluster_manifests { label="Documents"; labeljust="l"; fontsize=15; @@ -115,12 +115,12 @@ digraph schema { articlehistory:article_id:ne -> article:in3:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_articlehistory_article", weight=3]; articlehistory:companyuser_id:w -> companyuser:in1:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_articlehistory_companyuser", constraint=false, weight=1]; articleean:article_id:sw -> article:in6:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_articleean_article", weight=3]; - articlepic:article_id:e -> article:in9:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_articlepic_article", weight=3]; + articlepic:article_id:e -> article:in8:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_articlepic_article", weight=3]; articlepic:pic_id:w -> pic:in2:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_articlepic_pic", constraint=false, weight=1]; brandpic:brand_id:w -> brand:in1:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_brandpic_brand", weight=3]; brandpic:pic_id:ne -> pic:in1:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="FK_brandpic_pic", constraint=false, weight=1]; articlelistarticle:articlelist_id:sw -> articlelist:in1:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="FK_articlelistarticle_articlelist", weight=3]; - articlelistarticle:article_id:w -> article:in8:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="FK_articlelistarticle_article", constraint=false, weight=1]; + articlelistarticle:article_id:w -> article:in9:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="FK_articlelistarticle_article", constraint=false, weight=1]; optionslistarticle:optionslist_id:sw -> optionslist:in0:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="FK_optionslistarticle_optionslist", weight=3]; optionslistarticle:article_id:se -> article:in2:n [color="#58d18a", fontcolor="#58d18a", tooltip="FK_optionslistarticle_article", constraint=false, weight=1]; categoryarticle:category_id:se -> category:in3:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="FK_categoryarticle_category", weight=3]; diff --git a/schema-graph.svg b/schema-graph.svg index 908dc0e..948a7b3 100644 --- a/schema-graph.svg +++ b/schema-graph.svg @@ -1277,8 +1277,8 @@ articlelistarticle:w->article:n - - + + @@ -1580,8 +1580,8 @@ articlepic:e->article:n - - + + @@ -1597,43 +1597,55 @@ file - - - -file - - -id -INT -PK - - -hash -NVARCHAR(64) - - -mimetype -NVARCHAR(255) - - -name -NVARCHAR(255) - - -content -VARBINARY(MAX) - - -thumbavif? -VARBINARY(MAX) - - -thumbx? -INT - - -thumby? -INT + + + +file + + +id +INT +PK + + +hash +NVARCHAR(64) + + +mimetype +NVARCHAR(255) + + +name +NVARCHAR(255) + + +size +BIGINT + + +created +DATETIME2 + + +modified +DATETIME2 + + +content +VARBINARY(MAX) + + +thumbavif? +VARBINARY(MAX) + + +thumbx? +INT + + +thumby? +INT @@ -1829,8 +1841,8 @@ manifestattachment:e->file:n - - + + diff --git a/src/schema.js b/src/schema.js index b51f134..9a32242 100644 --- a/src/schema.js +++ b/src/schema.js @@ -776,6 +776,9 @@ BEGIN hash NVARCHAR(64) NOT NULL, mimetype NVARCHAR(255) NOT NULL, name NVARCHAR(255) NOT NULL, + size BIGINT NOT NULL, + created DATETIME2 NOT NULL CONSTRAINT DF_file_created DEFAULT SYSUTCDATETIME(), + modified DATETIME2 NOT NULL CONSTRAINT DF_file_modified DEFAULT SYSUTCDATETIME(), content VARBINARY(MAX) NOT NULL, thumbavif VARBINARY(MAX) NULL, thumbx INT NULL, diff --git a/src/sql/manifestattachment.js b/src/sql/manifestattachment.js index 892c1cd..f1b0845 100644 --- a/src/sql/manifestattachment.js +++ b/src/sql/manifestattachment.js @@ -5,9 +5,9 @@ WHERE id = @manifestId AND company_id = @companyId `; export const CREATE_FILE = ` -INSERT INTO [file] (hash, mimetype, name, content, thumbavif, thumbx, thumby) +INSERT INTO [file] (hash, mimetype, name, size, created, modified, content, thumbavif, thumbx, thumby) OUTPUT INSERTED.id -VALUES (@hash, @mimetype, @name, @content, CONVERT(VARBINARY(MAX), @thumbavif), @thumbx, @thumby) +VALUES (@hash, @mimetype, @name, @size, @created, @modified, @content, CONVERT(VARBINARY(MAX), @thumbavif), @thumbx, @thumby) `; export const GET_NEXT_SORT = ` @@ -31,7 +31,9 @@ SELECT f.hash, f.mimetype, f.name, - DATALENGTH(f.content) AS size, + f.size, + f.created, + f.modified, f.thumbavif, f.thumbx, f.thumby @@ -50,6 +52,9 @@ SELECT f.hash, f.mimetype, f.name, + f.size, + f.created, + f.modified, f.content, f.thumbavif, f.thumbx, diff --git a/src/store/manifestattachment/add.js b/src/store/manifestattachment/add.js index d703857..6417d9e 100644 --- a/src/store/manifestattachment/add.js +++ b/src/store/manifestattachment/add.js @@ -23,11 +23,14 @@ export default function add(pool) { * @param {string} input.name - The file name. * @param {Buffer} input.content - The file content. * @param {number} [input.sort] - Optional display sort order. + * @param {number} [input.size] - Optional file size in bytes (defaults to content length). + * @param {Date|string} [input.created] - Optional creation timestamp (defaults to current date). + * @param {Date|string} [input.modified] - Optional modified timestamp (defaults to created date). * @param {string} [input.hash] - The file hash (auto-computed SHA-256 if omitted). * @param {Buffer} [input.thumbavif] - Optional AVIF thumbnail buffer. * @param {number} [input.thumbx] - Optional thumbnail width. * @param {number} [input.thumby] - Optional thumbnail height. - * @returns {Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>} The created attachment record. + * @returns {Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, size: number, created: Date, modified: Date, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>} The created attachment record. * @throws {Error} If the manifest is not found or file input is invalid. * * @example @@ -36,10 +39,10 @@ export default function add(pool) { * name: 'invoice.pdf', * content: fileBuffer, * }); - * // => { id, manifestId, fileId, sort, hash, mimetype, name, thumbavif, thumbx, thumby } + * // => { id, manifestId, fileId, sort, hash, mimetype, name, size, created, modified, thumbavif, thumbx, thumby } */ return async (googleAuthId, manifestId, input, ...rest) => { - let rawMimetype, rawName, rawContent, rawHash, rawThumbavif, rawThumbx, rawThumby, rawSort; + let rawMimetype, rawName, rawContent, rawHash, rawThumbavif, rawThumbx, rawThumby, rawSort, rawSize, rawCreated, rawModified; if (typeof input === 'object' && input !== null && !Buffer.isBuffer(input)) { rawMimetype = input.mimetype; rawName = input.name; @@ -49,6 +52,9 @@ export default function add(pool) { rawThumbx = input.thumbx ?? input.thumbw; rawThumby = input.thumby ?? input.thumbh; rawSort = input.sort; + rawSize = input.size; + rawCreated = input.created ?? input.createdAt; + rawModified = input.modified ?? input.modifiedAt ?? input.lastModified; } else { rawMimetype = input; rawName = rest[0]; @@ -78,6 +84,27 @@ export default function add(pool) { } } + const size = + Number.isFinite(rawSize) && rawSize >= 0 + ? Number(rawSize) + : content.length; + + const parsedCreated = + rawCreated instanceof Date && !isNaN(rawCreated) + ? rawCreated + : typeof rawCreated === 'string' || typeof rawCreated === 'number' + ? new Date(rawCreated) + : null; + const created = parsedCreated && !isNaN(parsedCreated) ? parsedCreated : new Date(); + + const parsedModified = + rawModified instanceof Date && !isNaN(rawModified) + ? rawModified + : typeof rawModified === 'string' || typeof rawModified === 'number' + ? new Date(rawModified) + : null; + const modified = parsedModified && !isNaN(parsedModified) ? parsedModified : created; + const hash = typeof rawHash === 'string' && rawHash.trim() ? rawHash.trim() @@ -134,6 +161,9 @@ export default function add(pool) { .input('hash', hash) .input('mimetype', mimetype) .input('name', name) + .input('size', size) + .input('created', created) + .input('modified', modified) .input('content', content) .input('thumbavif', thumbavif) .input('thumbx', thumbx) @@ -160,6 +190,9 @@ export default function add(pool) { hash, mimetype, name, + size, + created, + modified, thumbavif, thumbx, thumby, diff --git a/src/store/manifestattachment/getById.js b/src/store/manifestattachment/getById.js index de31007..697fadc 100644 --- a/src/store/manifestattachment/getById.js +++ b/src/store/manifestattachment/getById.js @@ -13,12 +13,12 @@ export default function getById(pool) { * @param {number} googleAuthId - The id of the Google auth user. * @param {number} manifestId - The manifest ID. * @param {number} id - The attachment ID or file ID. - * @returns {Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, content: Buffer, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>} The attachment record with binary content. + * @returns {Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, size: number, created: Date, modified: Date, content: Buffer, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>} The attachment record with binary content. * @throws {Error} If the manifest or attachment is not found, or user is not authorized. * * @example * const attachment = await store.manifestattachment.getById(42, 1, 5); - * // => { id, manifestId, fileId, sort, hash, mimetype, name, content, thumbavif, thumbx, thumby } + * // => { id, manifestId, fileId, sort, hash, mimetype, name, size, created, modified, content, thumbavif, thumbx, thumby } */ return async (googleAuthId, manifestId, id) => { if (!Number.isFinite(id)) { @@ -46,6 +46,9 @@ export default function getById(pool) { hash: row.hash, mimetype: row.mimetype, name: row.name, + size: Number(row.size), + created: row.created, + modified: row.modified, content: row.content, thumbavif: row.thumbavif ?? null, thumbx: row.thumbx ?? null, diff --git a/src/store/manifestattachment/listByManifestId.js b/src/store/manifestattachment/listByManifestId.js index f8dbeb9..19d454b 100644 --- a/src/store/manifestattachment/listByManifestId.js +++ b/src/store/manifestattachment/listByManifestId.js @@ -12,7 +12,9 @@ function shape(row) { hash: row.hash, mimetype: row.mimetype, name: row.name, - size: row.size, + size: Number(row.size), + created: row.created, + modified: row.modified, thumbavif: row.thumbavif ?? null, thumbx: row.thumbx ?? null, thumby: row.thumby ?? null, @@ -28,12 +30,12 @@ export default function listByManifestId(pool) { * * @param {number} googleAuthId - The id of the Google auth user. * @param {number} manifestId - The manifest ID. - * @returns {Promise>} A list of attachment metadata objects. + * @returns {Promise>} A list of attachment metadata objects. * @throws {Error} If the manifest is not found or the user is not authorized. * * @example * const attachments = await store.manifestattachment.listByManifestId(42, 1); - * // => [{ id, manifestId, fileId, sort, hash, mimetype, name, size, thumbavif, thumbx, thumby }] + * // => [{ id, manifestId, fileId, sort, hash, mimetype, name, size, created, modified, thumbavif, thumbx, thumby }] */ return async (googleAuthId, manifestId) => { const { companyId } = await access(googleAuthId);