@@ -222,19 +222,6 @@ export class PoliciesService {
222222 updateData : UpdatePolicyDto ,
223223 ) {
224224 try {
225- // First check if the policy exists and belongs to the organization
226- const existingPolicy = await db . policy . findFirst ( {
227- where : {
228- id,
229- organizationId,
230- } ,
231- select : { id : true , name : true } ,
232- } ) ;
233-
234- if ( ! existingPolicy ) {
235- throw new NotFoundException ( `Policy with ID ${ id } not found` ) ;
236- }
237-
238225 // Prepare update data with special handling for status changes
239226 const updatePayload : Record < string , unknown > = { ...updateData } ;
240227
@@ -249,35 +236,70 @@ export class PoliciesService {
249236 }
250237
251238 // Coerce content to Prisma JSON[] input if provided
252- if ( Array . isArray ( updateData . content ) ) {
253- updatePayload . content = updateData . content as Prisma . InputJsonValue [ ] ;
239+ const contentValue = Array . isArray ( updateData . content )
240+ ? ( updateData . content as Prisma . InputJsonValue [ ] )
241+ : null ;
242+
243+ if ( contentValue ) {
244+ updatePayload . content = contentValue ;
254245 }
255246
256- // Update the policy
257- const updatedPolicy = await db . policy . update ( {
258- where : { id } ,
259- data : updatePayload ,
260- select : {
261- id : true ,
262- name : true ,
263- description : true ,
264- status : true ,
265- content : true ,
266- frequency : true ,
267- department : true ,
268- isRequiredToSign : true ,
269- signedBy : true ,
270- reviewDate : true ,
271- isArchived : true ,
272- createdAt : true ,
273- updatedAt : true ,
274- lastArchivedAt : true ,
275- lastPublishedAt : true ,
276- organizationId : true ,
277- assigneeId : true ,
278- approverId : true ,
279- policyTemplateId : true ,
280- } ,
247+ // All reads and writes in one transaction to prevent concurrent publish bypass
248+ const updatedPolicy = await db . $transaction ( async ( tx ) => {
249+ // Check existence and status inside the transaction
250+ const existingPolicy = await tx . policy . findFirst ( {
251+ where : { id, organizationId } ,
252+ select : { id : true , status : true } ,
253+ } ) ;
254+
255+ if ( ! existingPolicy ) {
256+ throw new NotFoundException ( `Policy with ID ${ id } not found` ) ;
257+ }
258+
259+ // Cannot update content unless policy is in draft status
260+ // This covers both 'published' and 'needs_review' states
261+ if ( contentValue && existingPolicy . status !== 'draft' ) {
262+ throw new BadRequestException (
263+ 'Cannot update content of a published policy. Create a new version to make changes.' ,
264+ ) ;
265+ }
266+
267+ const policy = await tx . policy . update ( {
268+ where : { id } ,
269+ data : updatePayload ,
270+ select : {
271+ id : true ,
272+ name : true ,
273+ description : true ,
274+ status : true ,
275+ content : true ,
276+ frequency : true ,
277+ department : true ,
278+ isRequiredToSign : true ,
279+ signedBy : true ,
280+ reviewDate : true ,
281+ isArchived : true ,
282+ createdAt : true ,
283+ updatedAt : true ,
284+ lastArchivedAt : true ,
285+ lastPublishedAt : true ,
286+ organizationId : true ,
287+ assigneeId : true ,
288+ approverId : true ,
289+ policyTemplateId : true ,
290+ currentVersionId : true ,
291+ } ,
292+ } ) ;
293+
294+ // Keep current version content in sync with policy content
295+ if ( contentValue && policy . currentVersionId ) {
296+ await tx . policyVersion . update ( {
297+ where : { id : policy . currentVersionId } ,
298+ data : { content : contentValue } ,
299+ } ) ;
300+ }
301+
302+ return policy ;
281303 } ) ;
282304
283305 this . logger . log ( `Updated policy: ${ updatedPolicy . name } (${ id } )` ) ;
@@ -361,6 +383,48 @@ export class PoliciesService {
361383 }
362384 }
363385
386+ async getVersionById (
387+ policyId : string ,
388+ versionId : string ,
389+ organizationId : string ,
390+ ) {
391+ const policy = await db . policy . findFirst ( {
392+ where : { id : policyId , organizationId } ,
393+ select : { id : true , currentVersionId : true , pendingVersionId : true } ,
394+ } ) ;
395+
396+ if ( ! policy ) {
397+ throw new NotFoundException ( `Policy with ID ${ policyId } not found` ) ;
398+ }
399+
400+ const version = await db . policyVersion . findUnique ( {
401+ where : { id : versionId } ,
402+ include : {
403+ publishedBy : {
404+ include : {
405+ user : {
406+ select : {
407+ id : true ,
408+ name : true ,
409+ image : true ,
410+ } ,
411+ } ,
412+ } ,
413+ } ,
414+ } ,
415+ } ) ;
416+
417+ if ( ! version || version . policyId !== policyId ) {
418+ throw new NotFoundException ( 'Version not found' ) ;
419+ }
420+
421+ return {
422+ version,
423+ currentVersionId : policy . currentVersionId ,
424+ pendingVersionId : policy . pendingVersionId ,
425+ } ;
426+ }
427+
364428 async getVersions ( policyId : string , organizationId : string ) {
365429 const policy = await db . policy . findFirst ( {
366430 where : { id : policyId , organizationId } ,
@@ -530,6 +594,7 @@ export class PoliciesService {
530594 select : {
531595 id : true ,
532596 organizationId : true ,
597+ status : true ,
533598 currentVersionId : true ,
534599 pendingVersionId : true ,
535600 } ,
@@ -545,7 +610,12 @@ export class PoliciesService {
545610 throw new NotFoundException ( 'Version not found' ) ;
546611 }
547612
548- if ( version . id === version . policy . currentVersionId ) {
613+ // Cannot edit the current version unless the policy is in draft status
614+ // This covers both 'published' and 'needs_review' states
615+ if (
616+ version . id === version . policy . currentVersionId &&
617+ version . policy . status !== 'draft'
618+ ) {
549619 throw new BadRequestException (
550620 'Cannot edit the published version. Create a new version to make changes.' ,
551621 ) ;
0 commit comments