fixed notification system and added a new tenant notifications receipt table to track read status and filter messages by scope.
This commit is contained in:
@@ -386,6 +386,19 @@ export type NotificationPreferences = Record<string, boolean>;
|
||||
|
||||
export type NotificationPreferencesMeta = Record<string, never>;
|
||||
|
||||
export type NotificationLogEntry = {
|
||||
id: number;
|
||||
type: string;
|
||||
channel: string;
|
||||
recipient: string | null;
|
||||
status: string;
|
||||
context: Record<string, unknown> | null;
|
||||
sent_at: string | null;
|
||||
failed_at: string | null;
|
||||
failure_reason: string | null;
|
||||
is_read?: boolean;
|
||||
};
|
||||
|
||||
export type PaddleTransactionSummary = {
|
||||
id: string | null;
|
||||
status: string | null;
|
||||
@@ -2016,6 +2029,71 @@ export async function updateNotificationPreferences(
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeNotificationLog(entry: JsonValue): NotificationLogEntry | null {
|
||||
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const row = entry as Record<string, JsonValue>;
|
||||
|
||||
return {
|
||||
id: Number(row.id ?? 0),
|
||||
type: typeof row.type === 'string' ? row.type : '',
|
||||
channel: typeof row.channel === 'string' ? row.channel : '',
|
||||
recipient: typeof row.recipient === 'string' ? row.recipient : null,
|
||||
status: typeof row.status === 'string' ? row.status : '',
|
||||
context: (row.context && typeof row.context === 'object' && !Array.isArray(row.context)) ? (row.context as Record<string, unknown>) : null,
|
||||
sent_at: typeof row.sent_at === 'string' ? row.sent_at : null,
|
||||
failed_at: typeof row.failed_at === 'string' ? row.failed_at : null,
|
||||
failure_reason: typeof row.failure_reason === 'string' ? row.failure_reason : null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function listNotificationLogs(options?: {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
type?: string;
|
||||
status?: string;
|
||||
scope?: string;
|
||||
eventId?: number;
|
||||
}): Promise<{
|
||||
data: NotificationLogEntry[];
|
||||
meta: PaginationMeta & { unread_count?: number };
|
||||
}> {
|
||||
const params = new URLSearchParams();
|
||||
if (options?.page) params.set('page', String(options.page));
|
||||
if (options?.perPage) params.set('per_page', String(options.perPage));
|
||||
if (options?.type) params.set('type', options.type);
|
||||
if (options?.status) params.set('status', options.status);
|
||||
if (options?.scope) params.set('scope', options.scope);
|
||||
if (options?.eventId) params.set('event_id', String(options.eventId));
|
||||
|
||||
const response = await authorizedFetch(`/api/v1/tenant/notifications/logs${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
const payload = await jsonOrThrow<{ data?: JsonValue[]; meta?: Partial<PaginationMeta> }>(
|
||||
response,
|
||||
'Failed to load notification logs'
|
||||
);
|
||||
|
||||
const rows = Array.isArray(payload.data) ? payload.data : [];
|
||||
const meta = buildPagination((payload.meta ?? {}) as JsonValue, 0) as PaginationMeta & { unread_count?: number };
|
||||
if (payload.meta && typeof (payload.meta as any).unread_count === 'number') {
|
||||
meta.unread_count = (payload.meta as any).unread_count as number;
|
||||
}
|
||||
|
||||
return {
|
||||
data: rows.map((row) => normalizeNotificationLog(row)).filter((row): row is NotificationLogEntry => Boolean(row)),
|
||||
meta,
|
||||
};
|
||||
}
|
||||
|
||||
export async function markNotificationLogs(ids: number[], status: 'read' | 'dismissed'): Promise<void> {
|
||||
await authorizedFetch('/api/v1/tenant/notifications/logs/mark', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ids, status }),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTenantPaddleTransactions(cursor?: string): Promise<{
|
||||
data: PaddleTransactionSummary[];
|
||||
nextCursor: string | null;
|
||||
|
||||
Reference in New Issue
Block a user