import { Injectable } from '@nestjs/common';
import {
DatabaseService,
type DatabaseQueryClient,
} from '../../../infrastructure/database/database.service';
@Injectable()
export class NotificationPolicyService {
constructor(private readonly database:DatabaseService) {}
async shouldCreate(input:{
organizationId:string;
userId:string;
notificationType:string;
throttleKey?:string;
severity:string;
throttleMinutes?:number;
},client?:DatabaseQueryClient) {
const db=client??this.database;
if(['urgent','critical'].includes(input.severity)) return {allowed:true};
if(input.throttleKey&&(input.throttleMinutes??0)>0) {
const rows=await db.query<any>(
`select last_created_at
from notification_throttle_state
where organization_id=$1 and user_id=$2
and notification_type=$3 and throttle_key=$4
for update`,
[input.organizationId,input.userId,input.notificationType,input.throttleKey],
);
if(rows[0]) {
const recent=await db.query<{recent:boolean}>(
`select ($1::timestamptz+make_interval(mins=>$2::int)>now()) as recent`,
[rows[0].last_created_at,input.throttleMinutes],
);
if(recent[0]?.recent) {
await db.query(
`update notification_throttle_state
set suppressed_count=suppressed_count+1
where organization_id=$1 and user_id=$2
and notification_type=$3 and throttle_key=$4`,
[input.organizationId,input.userId,input.notificationType,input.throttleKey],
);
return {allowed:false,reason:'throttled'};
}
}
}
return {allowed:true};
}
async markCreated(input:{
organizationId:string;userId:string;notificationType:string;throttleKey?:string;
},client?:DatabaseQueryClient) {
if(!input.throttleKey) return;
const db=client??this.database;
await db.query(
`insert into notification_throttle_state(
organization_id,user_id,notification_type,throttle_key,last_created_at
) values($1,$2,$3,$4,now())
on conflict(organization_id,user_id,notification_type,throttle_key)
do update set last_created_at=now()`,
[input.organizationId,input.userId,input.notificationType,input.throttleKey],
);
}
isQuietHours(policy:any,now=new Date()) {
if(!policy?.enabled||!policy.start||!policy.end) return false;
const [sh,sm]=String(policy.start).split(':').map(Number);
const [eh,em]=String(policy.end).split(':').map(Number);
const minutes=now.getHours()*60+now.getMinutes();
const start=sh*60+sm;
const end=eh*60+em;
return start<=end?minutes>=start&&minutes<end:minutes>=start||minutes<end;
}
}
