diff options
Diffstat (limited to 'lib/knox-api/realtime-notification/realtime-notification.ts')
| -rw-r--r-- | lib/knox-api/realtime-notification/realtime-notification.ts | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/lib/knox-api/realtime-notification/realtime-notification.ts b/lib/knox-api/realtime-notification/realtime-notification.ts new file mode 100644 index 00000000..a26f5b55 --- /dev/null +++ b/lib/knox-api/realtime-notification/realtime-notification.ts @@ -0,0 +1,333 @@ +"use server" + +import { z } from "zod" + +// 타입 정의 +const ColorSchema = z.object({ + r: z.number().min(0).max(255), + g: z.number().min(0).max(255), + b: z.number().min(0).max(255), +}) + +const TextSchema = z.object({ + content: z.string().min(1), + contentglobal: z.string().optional(), + size: z.number().min(0).max(1024).optional(), + pos: z.number().min(1).max(3), + exColorVO: ColorSchema.optional(), + style: z.string().optional(), +}) + +const LinkSchema = z.object({ + rel: z.string().max(10).optional(), + href: z.string().url().optional(), + args: z.string().optional(), +}) + +const VisualSchema = z.object({ + template: z.string().max(10).optional(), + skin: z.string().max(10).optional(), + global: z.enum(["Y", "N"]).optional(), + logo: z.enum(["Y", "N"]).optional(), + logourl: z.string().url().optional(), + exTextVOList: z.array(TextSchema), +}) + +const ActionsSchema = z.object({ + popup: z.enum(["Y", "N"]).optional(), + snooze: z.enum(["Y", "N"]).optional(), + clickable: z.enum(["Y", "N"]).optional(), + hint: z.string().optional(), + exLinksVOList: z.array(LinkSchema).optional(), +}) + +const NotificationRequestSchema = z.object({ + targetAddress: z.array(z.string().email()).max(100), + ntype: z.enum(["NEW", "RECALL"]), + messageid: z.string().optional(), + systemname: z.string().min(1), + from: z.string().min(1), + fromGlobal: z.string().min(1), + exVisualVO: VisualSchema, + exActionsVO: ActionsSchema, +}) + +export type NotificationRequest = z.infer<typeof NotificationRequestSchema> +export type NotificationColor = z.infer<typeof ColorSchema> +export type NotificationText = z.infer<typeof TextSchema> +export type NotificationLink = z.infer<typeof LinkSchema> +export type NotificationVisual = z.infer<typeof VisualSchema> +export type NotificationActions = z.infer<typeof ActionsSchema> + +// 응답 타입 +export interface NotificationResponse { + result: string + errorCode: string | null + message: string +} + +// 에러 타입 +export interface NotificationError { + httpStatus: number + errorCode: string + message: string +} + +// 환경 변수 검증 +const getApiConfig = () => { + const baseUrl = process.env.KNOX_API_BASE_URL + const systemId = process.env.KNOX_SYSTEM_ID + + if (!baseUrl || !systemId) { + throw new Error("Knox API configuration missing: KNOX_API_BASE_URL and KNOX_SYSTEM_ID are required") + } + + return { baseUrl, systemId } +} + +/** + * Knox Suite 실시간 토스트 알림 전송 + */ +export async function sendNotification( + request: NotificationRequest +): Promise<NotificationResponse> { + try { + // 요청 데이터 검증 + const validatedRequest = NotificationRequestSchema.parse(request) + + const { baseUrl, systemId } = getApiConfig() + + const response = await fetch(`${baseUrl}/notification/api/v2.0/sendnotification`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "System-ID": systemId, + "hint": validatedRequest.exActionsVO.hint || "multibrowser", + }, + body: JSON.stringify(validatedRequest), + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(`API Error: ${errorData.message || response.statusText}`) + } + + const result: NotificationResponse = await response.json() + return result + + } catch (error) { + console.error("Knox notification error:", error) + throw error + } +} + +/** + * 간단한 토스트 알림 전송 (기본 설정 사용) + */ +export async function sendSimpleNotification( + targetEmails: string[], + title: string, + titleGlobal: string, + systemName: string, + link?: string +): Promise<NotificationResponse> { + const notification: NotificationRequest = { + targetAddress: targetEmails, + ntype: "NEW", + systemname: systemName, + from: title, + fromGlobal: titleGlobal, + exVisualVO: { + template: "content", + skin: "White", + global: "N", + logo: "Y", + logourl: "", + exTextVOList: [ + { + content: title, + contentglobal: titleGlobal, + size: 14, + pos: 1, + exColorVO: { + r: 0, + g: 0, + b: 0, + }, + style: "BOLD", + }, + ], + }, + exActionsVO: { + popup: "Y", + clickable: "Y", + hint: "multibrowser", + exLinksVOList: link ? [ + { + rel: "popup", + href: link, + args: "", + }, + ] : [], + }, + } + + return await sendNotification(notification) +} + +/** + * 알림 취소 (RECALL) + */ +export async function recallNotification( + targetEmails: string[], + systemName: string, + messageId: string +): Promise<NotificationResponse> { + const notification: NotificationRequest = { + targetAddress: targetEmails, + ntype: "RECALL", + messageid: messageId, + systemname: systemName, + from: "알림 취소", + fromGlobal: "Notification Recall", + exVisualVO: { + template: "content", + skin: "White", + global: "N", + logo: "Y", + logourl: "", + exTextVOList: [ + { + content: "알림이 취소되었습니다", + contentglobal: "Notification has been recalled", + size: 14, + pos: 1, + exColorVO: { + r: 255, + g: 0, + b: 0, + }, + style: "BOLD", + }, + ], + }, + exActionsVO: { + popup: "N", + clickable: "N", + hint: "multibrowser", + }, + } + + return await sendNotification(notification) +} + +/** + * 사용자 정의 스타일 토스트 알림 + */ +export async function sendCustomNotification( + targetEmails: string[], + systemName: string, + title: string, + titleGlobal: string, + options: { + template?: string + skin?: string + logoUrl?: string + textColor?: NotificationColor + textSize?: number + textStyle?: string + link?: string + linkRel?: string + } = {} +): Promise<NotificationResponse> { + const notification: NotificationRequest = { + targetAddress: targetEmails, + ntype: "NEW", + systemname: systemName, + from: title, + fromGlobal: titleGlobal, + exVisualVO: { + template: options.template || "content", + skin: options.skin || "White", + global: "N", + logo: options.logoUrl ? "Y" : "Y", + logourl: options.logoUrl || "", + exTextVOList: [ + { + content: title, + contentglobal: titleGlobal, + size: options.textSize || 14, + pos: 1, + exColorVO: options.textColor || { + r: 0, + g: 0, + b: 0, + }, + style: options.textStyle || "BOLD", + }, + ], + }, + exActionsVO: { + popup: options.link ? "Y" : "N", + clickable: options.link ? "Y" : "N", + hint: "multibrowser", + exLinksVOList: options.link ? [ + { + rel: options.linkRel || "popup", + href: options.link, + args: "", + }, + ] : [], + }, + } + + return await sendNotification(notification) +} + +/** + * 헬퍼 함수: 알림 ID 추출 + */ +export async function extractNotificationId(response: NotificationResponse): Promise<string | null> { + if (response.result === "OK" && response.message) { + const match = response.message.match(/uid\s*:\s*([a-f0-9]+)/i) + return match ? match[1] : null + } + return null +} + +/** + * 헬퍼 함수: 여러 사용자에게 배치 알림 전송 + */ +export async function sendBatchNotifications( + notifications: Array<{ + targetEmails: string[] + title: string + titleGlobal: string + systemName: string + link?: string + }> +): Promise<NotificationResponse[]> { + const results: NotificationResponse[] = [] + + for (const notification of notifications) { + try { + const result = await sendSimpleNotification( + notification.targetEmails, + notification.title, + notification.titleGlobal, + notification.systemName, + notification.link + ) + results.push(result) + } catch (error) { + console.error("Batch notification error:", error) + results.push({ + result: "ERROR", + errorCode: "BATCH_ERROR", + message: error instanceof Error ? error.message : "Unknown error", + }) + } + } + + return results +} |
