feat: add size, created, and modified columns to file table and manifestattachment APIs

This commit is contained in:
seb
2026-09-18 01:45:27 +02:00
parent cfb7f86f0e
commit e3d30b296b
9 changed files with 127 additions and 66 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<Array<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, size: number, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null}>>} A list of attachment metadata objects.
* @returns {Promise<Array<{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}>>} 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);