"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.notificationMessageReadPayloadSchema = exports.notificationMessagePayloadSchema = exports.notificationMessageListQuerySchema = exports.NOTIFICATION_MESSAGE_TABLE = void 0; exports.ensureNotificationMessagesTable = ensureNotificationMessagesTable; exports.listNotificationMessages = listNotificationMessages; exports.getNotificationMessage = getNotificationMessage; exports.createNotificationMessage = createNotificationMessage; exports.selectNotificationMessageIdsToDelete = selectNotificationMessageIdsToDelete; exports.deleteOlderNotificationMessagesBySource = deleteOlderNotificationMessagesBySource; exports.updateNotificationMessageReadState = updateNotificationMessageReadState; exports.deleteNotificationMessage = deleteNotificationMessage; var zod_1 = require("zod"); var client_js_1 = require("../db/client.js"); exports.NOTIFICATION_MESSAGE_TABLE = 'notification_messages'; var notificationMessagePrioritySchema = zod_1.z.enum(['low', 'normal', 'high', 'urgent']); var notificationMessageListStatusSchema = zod_1.z.enum(['all', 'unread']); exports.notificationMessageListQuerySchema = zod_1.z.object({ status: notificationMessageListStatusSchema.default('all'), limit: zod_1.z.coerce.number().int().min(1).max(100).default(20), }); exports.notificationMessagePayloadSchema = zod_1.z.object({ title: zod_1.z.string().trim().min(1).max(200), body: zod_1.z.string().trim().min(1).max(20000), category: zod_1.z.string().trim().min(1).max(60).default('general'), source: zod_1.z.string().trim().min(1).max(80).default('system'), priority: notificationMessagePrioritySchema.default('normal'), metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).default({}), }); exports.notificationMessageReadPayloadSchema = zod_1.z.object({ read: zod_1.z.boolean().default(true), }); function normalizePreviewText(value) { var normalized = value .replace(/```[\s\S]*?```/g, ' ') .replace(/!\[[^\]]*\]\([^)]+\)/g, ' ') .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') .replace(/[#>*_`~-]/g, ' ') .replace(/\s+/g, ' ') .trim(); return normalized.length > 140 ? "".concat(normalized.slice(0, 137).trimEnd(), "...") : normalized; } function mapNotificationMessageRow(row) { var _a, _b, _c, _d, _e, _f, _g; var body = String((_a = row.body) !== null && _a !== void 0 ? _a : ''); var metadata = typeof row.metadata_json === 'object' && row.metadata_json ? row.metadata_json : {}; var metadataPreview = typeof metadata.previewText === 'string' ? metadata.previewText : typeof metadata.listPreviewText === 'string' ? metadata.listPreviewText : ''; return { id: Number((_b = row.id) !== null && _b !== void 0 ? _b : 0), title: String((_c = row.title) !== null && _c !== void 0 ? _c : ''), body: body, preview: normalizePreviewText(metadataPreview || body), category: String((_d = row.category) !== null && _d !== void 0 ? _d : 'general'), source: String((_e = row.source) !== null && _e !== void 0 ? _e : 'system'), priority: notificationMessagePrioritySchema.catch('normal').parse(row.priority), read: Boolean(row.is_read), readAt: row.read_at === null || row.read_at === undefined ? null : String(row.read_at), metadata: metadata, createdAt: String((_f = row.created_at) !== null && _f !== void 0 ? _f : ''), updatedAt: String((_g = row.updated_at) !== null && _g !== void 0 ? _g : ''), }; } function resolveInsertedId(result) { if (typeof result === 'number' && Number.isInteger(result) && result > 0) { return result; } if (Array.isArray(result)) { var first = result[0]; if (typeof first === 'number' && Number.isInteger(first) && first > 0) { return first; } if (first && typeof first === 'object' && 'id' in first) { var id = Number(first.id); return Number.isInteger(id) && id > 0 ? id : null; } } if (result && typeof result === 'object' && 'id' in result) { var id = Number(result.id); return Number.isInteger(id) && id > 0 ? id : null; } return null; } function supportsReturning() { var _a; var clientName = String((_a = client_js_1.db.client.config.client) !== null && _a !== void 0 ? _a : '').toLowerCase(); return ['pg', 'postgres', 'postgresql', 'sqlite3', 'better-sqlite3', 'oracledb', 'mssql'].includes(clientName); } function ensureNotificationMessagesTable() { return __awaiter(this, void 0, void 0, function () { var hasTable, requiredColumns, _loop_1, _i, requiredColumns_1, _a, columnName, createColumn; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, client_js_1.db.schema.hasTable(exports.NOTIFICATION_MESSAGE_TABLE)]; case 1: hasTable = _b.sent(); if (!!hasTable) return [3 /*break*/, 3]; return [4 /*yield*/, client_js_1.db.schema.createTable(exports.NOTIFICATION_MESSAGE_TABLE, function (table) { table.increments('id').primary(); table.string('title', 200).notNullable(); table.text('body').notNullable(); table.string('category', 60).notNullable().defaultTo('general'); table.string('source', 80).notNullable().defaultTo('system'); table.string('priority', 20).notNullable().defaultTo('normal'); table.boolean('is_read').notNullable().defaultTo(false); table.timestamp('read_at', { useTz: true }).nullable(); table.jsonb('metadata_json').notNullable().defaultTo('{}'); table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now()); table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now()); })]; case 2: _b.sent(); _b.label = 3; case 3: requiredColumns = [ ['title', function (table) { return table.string('title', 200).notNullable().defaultTo('알림'); }], ['body', function (table) { return table.text('body').notNullable().defaultTo(''); }], ['category', function (table) { return table.string('category', 60).notNullable().defaultTo('general'); }], ['source', function (table) { return table.string('source', 80).notNullable().defaultTo('system'); }], ['priority', function (table) { return table.string('priority', 20).notNullable().defaultTo('normal'); }], ['is_read', function (table) { return table.boolean('is_read').notNullable().defaultTo(false); }], ['read_at', function (table) { return table.timestamp('read_at', { useTz: true }).nullable(); }], ['metadata_json', function (table) { return table.jsonb('metadata_json').notNullable().defaultTo('{}'); }], ['created_at', function (table) { return table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now()); }], ['updated_at', function (table) { return table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now()); }], ]; _loop_1 = function (columnName, createColumn) { var hasColumn; return __generator(this, function (_c) { switch (_c.label) { case 0: return [4 /*yield*/, client_js_1.db.schema.hasColumn(exports.NOTIFICATION_MESSAGE_TABLE, columnName)]; case 1: hasColumn = _c.sent(); if (!!hasColumn) return [3 /*break*/, 3]; return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.NOTIFICATION_MESSAGE_TABLE, function (table) { createColumn(table); })]; case 2: _c.sent(); _c.label = 3; case 3: return [2 /*return*/]; } }); }; _i = 0, requiredColumns_1 = requiredColumns; _b.label = 4; case 4: if (!(_i < requiredColumns_1.length)) return [3 /*break*/, 7]; _a = requiredColumns_1[_i], columnName = _a[0], createColumn = _a[1]; return [5 /*yield**/, _loop_1(columnName, createColumn)]; case 5: _b.sent(); _b.label = 6; case 6: _i++; return [3 /*break*/, 4]; case 7: return [2 /*return*/]; } }); }); } function listNotificationMessages(query) { return __awaiter(this, void 0, void 0, function () { var parsedQuery, builder, rows, unreadCountResult; var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, ensureNotificationMessagesTable()]; case 1: _b.sent(); parsedQuery = exports.notificationMessageListQuerySchema.parse(query); builder = (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE) .select('*') .orderBy('is_read', 'asc') .orderBy('created_at', 'desc') .orderBy('id', 'desc') .limit(parsedQuery.limit); if (parsedQuery.status === 'unread') { builder.where({ is_read: false }); } return [4 /*yield*/, builder]; case 2: rows = _b.sent(); return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE) .where({ is_read: false }) .count({ count: '*' }) .first()]; case 3: unreadCountResult = _b.sent(); return [2 /*return*/, { items: rows.map(function (row) { return mapNotificationMessageRow(row); }), unreadCount: Number((_a = unreadCountResult === null || unreadCountResult === void 0 ? void 0 : unreadCountResult.count) !== null && _a !== void 0 ? _a : 0), }]; } }); }); } function getNotificationMessage(id) { return __awaiter(this, void 0, void 0, function () { var row; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, ensureNotificationMessagesTable()]; case 1: _a.sent(); return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE).where({ id: id }).first()]; case 2: row = _a.sent(); return [2 /*return*/, row ? mapNotificationMessageRow(row) : null]; } }); }); } function createNotificationMessage(payload) { return __awaiter(this, void 0, void 0, function () { var parsedPayload, insertQuery, insertResult, _a, insertedId, row; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, ensureNotificationMessagesTable()]; case 1: _b.sent(); parsedPayload = exports.notificationMessagePayloadSchema.parse(payload); insertQuery = (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE).insert({ title: parsedPayload.title, body: parsedPayload.body, category: parsedPayload.category, source: parsedPayload.source, priority: parsedPayload.priority, metadata_json: parsedPayload.metadata, is_read: false, read_at: null, created_at: client_js_1.db.fn.now(), updated_at: client_js_1.db.fn.now(), }); if (!supportsReturning()) return [3 /*break*/, 3]; return [4 /*yield*/, insertQuery.returning('id')]; case 2: _a = _b.sent(); return [3 /*break*/, 5]; case 3: return [4 /*yield*/, insertQuery]; case 4: _a = _b.sent(); _b.label = 5; case 5: insertResult = _a; insertedId = resolveInsertedId(insertResult); if (!insertedId) { throw new Error('알림 메시지 저장 후 ID를 확인하지 못했습니다.'); } return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE).where({ id: insertedId }).first()]; case 6: row = _b.sent(); if (!row) { throw new Error('저장된 알림 메시지를 다시 불러오지 못했습니다.'); } return [2 /*return*/, mapNotificationMessageRow(row)]; } }); }); } function selectNotificationMessageIdsToDelete(items, keepLatestCount) { if (keepLatestCount === void 0) { keepLatestCount = 1; } var normalizedKeepLatestCount = Number.isInteger(keepLatestCount) && keepLatestCount > 0 ? keepLatestCount : 1; return items .slice() .sort(function (left, right) { var leftCreatedAt = Date.parse(left.createdAt !== null && left.createdAt !== void 0 ? left.createdAt : ''); var rightCreatedAt = Date.parse(right.createdAt !== null && right.createdAt !== void 0 ? right.createdAt : ''); if (Number.isFinite(leftCreatedAt) && Number.isFinite(rightCreatedAt) && leftCreatedAt !== rightCreatedAt) { return rightCreatedAt - leftCreatedAt; } return right.id - left.id; }) .slice(normalizedKeepLatestCount) .map(function (item) { return item.id; }); } function deleteOlderNotificationMessagesBySource(source, keepLatestCount) { if (keepLatestCount === void 0) { keepLatestCount = 1; } return __awaiter(this, void 0, void 0, function () { var normalizedSource, rows, idsToDelete; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, ensureNotificationMessagesTable()]; case 1: _a.sent(); normalizedSource = source.trim(); if (!normalizedSource) { return [2 /*return*/, 0]; } return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE) .select('id', 'created_at') .where({ source: normalizedSource })]; case 2: rows = _a.sent(); idsToDelete = selectNotificationMessageIdsToDelete(rows.map(function (row) { return ({ id: Number(row.id !== null && row.id !== void 0 ? row.id : 0), createdAt: row.created_at === null || row.created_at === undefined ? null : String(row.created_at), }); }), keepLatestCount).filter(function (id) { return Number.isInteger(id) && id > 0; }); if (idsToDelete.length === 0) { return [2 /*return*/, 0]; } return [2 /*return*/, (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE).whereIn('id', idsToDelete).del()]; } }); }); } function updateNotificationMessageReadState(id, payload) { return __awaiter(this, void 0, void 0, function () { var parsedPayload, updatedCount, row; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, ensureNotificationMessagesTable()]; case 1: _a.sent(); parsedPayload = exports.notificationMessageReadPayloadSchema.parse(payload); return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE) .where({ id: id }) .update({ is_read: parsedPayload.read, read_at: parsedPayload.read ? client_js_1.db.fn.now() : null, updated_at: client_js_1.db.fn.now(), })]; case 2: updatedCount = _a.sent(); if (!updatedCount) { return [2 /*return*/, null]; } return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE).where({ id: id }).first()]; case 3: row = _a.sent(); return [2 /*return*/, row ? mapNotificationMessageRow(row) : null]; } }); }); } function deleteNotificationMessage(id) { return __awaiter(this, void 0, void 0, function () { var deletedCount; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, ensureNotificationMessagesTable()]; case 1: _a.sent(); return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_MESSAGE_TABLE).where({ id: id }).del()]; case 2: deletedCount = _a.sent(); return [2 /*return*/, deletedCount > 0]; } }); }); }