diff --git a/backend/src/models/record-share.model.ts b/backend/src/models/record-share.model.ts index 07c5523..c61d7f6 100644 --- a/backend/src/models/record-share.model.ts +++ b/backend/src/models/record-share.model.ts @@ -9,7 +9,7 @@ export interface RecordShareAccessLevel { export class RecordShare extends BaseModel { static tableName = 'record_shares'; - // Disable automatic snake_case conversion for this table + // Don't use snake_case mapping since DB columns are already camelCase static get columnNameMappers() { return { parse(obj: any) { @@ -21,6 +21,15 @@ export class RecordShare extends BaseModel { }; } + // Override BaseModel hooks to prevent automatic timestamp handling + $beforeInsert(queryContext: any) { + // Don't set timestamps - let database defaults handle it + } + + $beforeUpdate(opt: any, queryContext: any) { + // Don't set timestamps - let database defaults handle it + } + id!: string; objectDefinitionId!: string; recordId!: string; @@ -30,6 +39,7 @@ export class RecordShare extends BaseModel { expiresAt?: Date; revokedAt?: Date; createdAt!: Date; + updatedAt!: Date; static get jsonSchema() { return { @@ -49,8 +59,22 @@ export class RecordShare extends BaseModel { canDelete: { type: 'boolean' }, }, }, - expiresAt: { type: ['string', 'null'], format: 'date-time' }, - revokedAt: { type: ['string', 'null'], format: 'date-time' }, + expiresAt: { + anyOf: [ + { type: 'string', format: 'date-time' }, + { type: 'null' }, + { type: 'object' } // Allow Date objects + ] + }, + revokedAt: { + anyOf: [ + { type: 'string', format: 'date-time' }, + { type: 'null' }, + { type: 'object' } // Allow Date objects + ] + }, + createdAt: { type: ['string', 'object'], format: 'date-time' }, + updatedAt: { type: ['string', 'object'], format: 'date-time' }, }, }; } diff --git a/backend/src/rbac/record-sharing.controller.ts b/backend/src/rbac/record-sharing.controller.ts index 04e0102..e67726d 100644 --- a/backend/src/rbac/record-sharing.controller.ts +++ b/backend/src/rbac/record-sharing.controller.ts @@ -147,14 +147,16 @@ export class RecordSharingController { if (existingShare) { // Update existing share - await RecordShare.query(knex) - .patchAndFetchById(existingShare.id, { - accessLevel: { + await knex('record_shares') + .where({ id: existingShare.id }) + .update({ + accessLevel: JSON.stringify({ canRead: data.canRead, canEdit: data.canEdit, canDelete: data.canDelete, - }, - expiresAt: data.expiresAt ? new Date(data.expiresAt) : null, + }), + expiresAt: data.expiresAt ? data.expiresAt : null, + updatedAt: knex.fn.now(), }); return RecordShare.query(knex) @@ -163,21 +165,21 @@ export class RecordSharingController { } // Create new share - const share = await RecordShare.query(knex).insert({ + const [shareId] = await knex('record_shares').insert({ objectDefinitionId: objectDef.id, recordId, granteeUserId: data.granteeUserId, grantedByUserId: currentUser.userId, - accessLevel: { + accessLevel: JSON.stringify({ canRead: data.canRead, canEdit: data.canEdit, canDelete: data.canDelete, - }, - expiresAt: data.expiresAt ? new Date(data.expiresAt) : null, + }), + expiresAt: data.expiresAt ? data.expiresAt : null, }); return RecordShare.query(knex) - .findById(share.id) + .findById(shareId) .withGraphFetched('[granteeUser]'); } @@ -233,9 +235,11 @@ export class RecordSharingController { } // Revoke the share (soft delete) - await RecordShare.query(knex) - .patchAndFetchById(shareId, { - revokedAt: new Date(), + await knex('record_shares') + .where({ id: shareId }) + .update({ + revokedAt: knex.fn.now(), + updatedAt: knex.fn.now(), }); return { success: true };