390 lines
21 KiB
JavaScript
390 lines
21 KiB
JavaScript
"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];
|
|
}
|
|
});
|
|
});
|
|
}
|