@comasoft/nestjs 0.2.28 → 0.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,8 @@ export declare class Notification {
4
4
  batch_id: string;
5
5
  status: NOTIFICATION_STATUS;
6
6
  type: NOTIFICATION_TYPE;
7
- user_id: number;
7
+ target_type: string;
8
+ target_id: number;
8
9
  recipient: string;
9
10
  template_key: string;
10
11
  event_type: string;
@@ -1 +1 @@
1
- var e=this&&this.__decorate||function(e,t,n,o){var i,a=arguments.length,l=a<3?t:null===o?o=Object.getOwnPropertyDescriptor(t,n):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,n,o);else for(var p=e.length-1;p>=0;p--)(i=e[p])&&(l=(a<3?i(l):a>3?i(t,n,l):i(t,n))||l);return a>3&&l&&Object.defineProperty(t,n,l),l},t=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)};Object.defineProperty(exports,"__esModule",{value:!0}),exports.Notification=void 0;const n=require("typeorm"),o=require("../../enums.common");let i=class Notification{};exports.Notification=i,e([(0,n.PrimaryGeneratedColumn)({name:"id",primaryKeyConstraintName:"PK_notifications"}),t("design:type",Number)],i.prototype,"id",void 0),e([(0,n.Column)("varchar",{length:50,nullable:!0,comment:"배치 ID (1:N 발송 시 동일한 ID, null = 1:1 발송)"}),t("design:type",String)],i.prototype,"batch_id",void 0),e([(0,n.Column)("enum",{enum:o.NOTIFICATION_STATUS}),t("design:type",String)],i.prototype,"status",void 0),e([(0,n.Column)("varchar",{length:25,comment:Object.values(o.NOTIFICATION_TYPE).join(" | ")}),t("design:type",String)],i.prototype,"type",void 0),e([(0,n.Column)("int",{nullable:!0,comment:"사용자 ID (null = 배치 마스터)"}),t("design:type",Number)],i.prototype,"user_id",void 0),e([(0,n.Column)("varchar",{length:100,nullable:!0,comment:"수신자 주소 (email, phone 등)"}),t("design:type",String)],i.prototype,"recipient",void 0),e([(0,n.Column)("varchar",{length:50,comment:"템플릿 키"}),t("design:type",String)],i.prototype,"template_key",void 0),e([(0,n.Column)("varchar",{length:50,nullable:!0,comment:"이벤트 타입 (comment, pairing, notice 등)"}),t("design:type",String)],i.prototype,"event_type",void 0),e([(0,n.Column)("int",{nullable:!0,comment:"이벤트 대상 ID (post_id, schedule_id 등)"}),t("design:type",Number)],i.prototype,"event_id",void 0),e([(0,n.Column)("varchar",{length:100,nullable:!0,comment:"제목 (in_app, email에서 사용)"}),t("design:type",String)],i.prototype,"title",void 0),e([(0,n.Column)("text",{nullable:!0,comment:"내용"}),t("design:type",String)],i.prototype,"content",void 0),e([(0,n.Column)("varchar",{length:50,nullable:!0,comment:"발신자 (email: noreply@example.com, sms: 1234)"}),t("design:type",String)],i.prototype,"sender",void 0),e([(0,n.Column)("jsonb",{nullable:!0,comment:"템플릿 변수"}),t("design:type",Object)],i.prototype,"variables",void 0),e([(0,n.Column)("jsonb",{nullable:!0,comment:"추가 메타데이터 (url, image_url, 배치 정보 등)"}),t("design:type",Object)],i.prototype,"meta",void 0),e([(0,n.Column)("timestamptz",{nullable:!0,comment:"예약 발송 시각"}),t("design:type",Date)],i.prototype,"scheduled_at",void 0),e([(0,n.Column)("timestamptz",{nullable:!0,comment:"실제 발송 시각"}),t("design:type",Date)],i.prototype,"sent_at",void 0),e([(0,n.Column)("timestamptz",{nullable:!0,comment:"읽은 시각 (in_app만 사용)"}),t("design:type",Date)],i.prototype,"read_at",void 0),e([(0,n.Column)("timestamptz",{nullable:!0,comment:"발송 실패 시각"}),t("design:type",Date)],i.prototype,"failed_at",void 0),e([(0,n.Column)("varchar",{length:255,nullable:!0,comment:"실패 사유"}),t("design:type",String)],i.prototype,"reason_failed",void 0),e([(0,n.CreateDateColumn)({type:"timestamptz"}),t("design:type",Date)],i.prototype,"created_at",void 0),e([(0,n.UpdateDateColumn)({type:"timestamptz"}),t("design:type",Date)],i.prototype,"updated_at",void 0),e([(0,n.DeleteDateColumn)({type:"timestamptz"}),t("design:type",Date)],i.prototype,"deleted_at",void 0),exports.Notification=i=e([(0,n.Entity)("notifications"),(0,n.Index)("IDX_notifications",["id"],{where:"deleted_at IS NULL"}),(0,n.Index)("IDX_notifications_status",["status"]),(0,n.Index)("IDX_notifications_user_type",["user_id","type","created_at"]),(0,n.Index)("IDX_notifications_user_id",["user_id"],{where:"user_id IS NOT NULL"}),(0,n.Index)("IDX_notifications_batch",["batch_id"],{where:"batch_id IS NOT NULL"}),(0,n.Index)("IDX_notifications_event",["event_type","event_id"],{where:"event_type IS NOT NULL AND event_id IS NOT NULL"})],i);
1
+ var t=this&&this.__decorate||function(t,e,o,n){var i,a=arguments.length,l=a<3?e:null===n?n=Object.getOwnPropertyDescriptor(e,o):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(t,e,o,n);else for(var p=t.length-1;p>=0;p--)(i=t[p])&&(l=(a<3?i(l):a>3?i(e,o,l):i(e,o))||l);return a>3&&l&&Object.defineProperty(e,o,l),l},e=this&&this.__metadata||function(t,e){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(t,e)};Object.defineProperty(exports,"__esModule",{value:!0}),exports.Notification=void 0;const o=require("typeorm"),n=require("../../enums.common");let i=class Notification{};exports.Notification=i,t([(0,o.PrimaryGeneratedColumn)({name:"id",primaryKeyConstraintName:"PK_notifications"}),e("design:type",Number)],i.prototype,"id",void 0),t([(0,o.Column)("varchar",{length:50,nullable:!0}),e("design:type",String)],i.prototype,"batch_id",void 0),t([(0,o.Column)("enum",{enum:n.NOTIFICATION_STATUS}),e("design:type",String)],i.prototype,"status",void 0),t([(0,o.Column)("varchar",{length:25,comment:Object.values(n.NOTIFICATION_TYPE).join(" | ")}),e("design:type",String)],i.prototype,"type",void 0),t([(0,o.Column)("varchar",{length:20,nullable:!0}),e("design:type",String)],i.prototype,"target_type",void 0),t([(0,o.Column)("int",{nullable:!0}),e("design:type",Number)],i.prototype,"target_id",void 0),t([(0,o.Column)("varchar",{length:100,nullable:!0}),e("design:type",String)],i.prototype,"recipient",void 0),t([(0,o.Column)("varchar",{length:50}),e("design:type",String)],i.prototype,"template_key",void 0),t([(0,o.Column)("varchar",{length:50,nullable:!0}),e("design:type",String)],i.prototype,"event_type",void 0),t([(0,o.Column)("int",{nullable:!0}),e("design:type",Number)],i.prototype,"event_id",void 0),t([(0,o.Column)("varchar",{length:100,nullable:!0}),e("design:type",String)],i.prototype,"title",void 0),t([(0,o.Column)("text",{nullable:!0}),e("design:type",String)],i.prototype,"content",void 0),t([(0,o.Column)("varchar",{length:50,nullable:!0}),e("design:type",String)],i.prototype,"sender",void 0),t([(0,o.Column)("jsonb",{nullable:!0}),e("design:type",Object)],i.prototype,"variables",void 0),t([(0,o.Column)("jsonb",{nullable:!0}),e("design:type",Object)],i.prototype,"meta",void 0),t([(0,o.Column)("timestamptz",{nullable:!0}),e("design:type",Date)],i.prototype,"scheduled_at",void 0),t([(0,o.Column)("timestamptz",{nullable:!0}),e("design:type",Date)],i.prototype,"sent_at",void 0),t([(0,o.Column)("timestamptz",{nullable:!0}),e("design:type",Date)],i.prototype,"read_at",void 0),t([(0,o.Column)("timestamptz",{nullable:!0}),e("design:type",Date)],i.prototype,"failed_at",void 0),t([(0,o.Column)("varchar",{length:255,nullable:!0}),e("design:type",String)],i.prototype,"reason_failed",void 0),t([(0,o.CreateDateColumn)({type:"timestamptz"}),e("design:type",Date)],i.prototype,"created_at",void 0),t([(0,o.UpdateDateColumn)({type:"timestamptz"}),e("design:type",Date)],i.prototype,"updated_at",void 0),t([(0,o.DeleteDateColumn)({type:"timestamptz"}),e("design:type",Date)],i.prototype,"deleted_at",void 0),exports.Notification=i=t([(0,o.Entity)("notifications"),(0,o.Index)("IDX_notifications_target",["target_type","target_id","created_at"]),(0,o.Index)("IDX_notifications_batch",["batch_id"]),(0,o.Index)("IDX_notifications_event",["event_type","event_id"])],i);
@@ -1 +1 @@
1
- var e=this&&this.__decorate||function(e,t,i,n){var a,s=arguments.length,c=s<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)c=Reflect.decorate(e,t,i,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(c=(s<3?a(c):s>3?a(t,i,c):a(t,i))||c);return s>3&&c&&Object.defineProperty(t,i,c),c},t=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},i=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(a,s){function fulfilled(e){try{step(n.next(e))}catch(e){s(e)}}function rejected(e){try{step(n.throw(e))}catch(e){s(e)}}function step(e){e.done?a(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.EventNotificationService=void 0;const n=require("@nestjs/common"),a=require("../../notifications");let s=class EventNotificationService{constructor(e){this.notificationSendService=e}send(e){return i(this,void 0,void 0,function*(){const t=this.generateBatchId();return(null==e?void 0:e.in_app)&&(yield this.notificationSendService.send({batch_id:t,channels:["in_app"],recipients:e.in_app.recipients,title:e.in_app.title,content:e.in_app.content,template_key:e.in_app.template_key,variables:e.in_app.variables,event_type:e.in_app.event_type,event_id:e.in_app.event_id,meta:e.in_app.meta})),(null==e?void 0:e.mobile_push)&&(yield this.notificationSendService.send({batch_id:t,channels:["mobile_push"],recipients:e.mobile_push.recipients,title:e.mobile_push.title,content:e.mobile_push.content,template_key:e.mobile_push.template_key,variables:e.mobile_push.variables,meta:e.mobile_push.meta})),(null==e?void 0:e.web_push)&&(yield this.notificationSendService.send({batch_id:t,channels:["web_push"],recipients:e.web_push.recipients,title:e.web_push.title,content:e.web_push.content,template_key:e.web_push.template_key,variables:e.web_push.variables,meta:e.web_push.meta})),(null==e?void 0:e.email)&&(yield this.notificationSendService.send({batch_id:t,channels:["email"],recipients:e.email.recipients,title:e.email.subject,content:e.email.body,template_key:e.email.template_key,variables:e.email.variables})),(null==e?void 0:e.kakao)&&(yield this.notificationSendService.send({batch_id:t,channels:["kakao"],recipients:e.kakao.recipients,template_key:e.kakao.template_key,variables:e.kakao.variables})),(null==e?void 0:e.sms)&&(yield this.notificationSendService.send({batch_id:t,channels:["sms"],recipients:e.sms.recipients,content:e.sms.content,template_key:e.sms.template_key,variables:e.sms.variables})),(null==e?void 0:e.slack)&&(yield this.notificationSendService.send({batch_id:t,channels:["slack"],recipients:[{user_id:0,recipient:e.slack.channel}],content:e.slack.content,template_key:e.slack.template_key,variables:e.slack.variables})),{batch_id:t}})}generateBatchId(){return`BATCH_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}};exports.EventNotificationService=s,exports.EventNotificationService=s=e([(0,n.Injectable)(),t("design:paramtypes",[a.NotificationSendService])],s);
1
+ var e=this&&this.__decorate||function(e,t,i,n){var a,s=arguments.length,c=s<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)c=Reflect.decorate(e,t,i,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(c=(s<3?a(c):s>3?a(t,i,c):a(t,i))||c);return s>3&&c&&Object.defineProperty(t,i,c),c},t=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},i=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(a,s){function fulfilled(e){try{step(n.next(e))}catch(e){s(e)}}function rejected(e){try{step(n.throw(e))}catch(e){s(e)}}function step(e){e.done?a(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.EventNotificationService=void 0;const n=require("@nestjs/common"),a=require("../../notifications");let s=class EventNotificationService{constructor(e){this.notificationSendService=e}send(e){return i(this,void 0,void 0,function*(){const t=this.generateBatchId();return(null==e?void 0:e.in_app)&&(yield this.notificationSendService.send({batch_id:t,channels:["in_app"],recipients:e.in_app.recipients,target_type:e.in_app.target_type,title:e.in_app.title,content:e.in_app.content,template_key:e.in_app.template_key,variables:e.in_app.variables,event_type:e.in_app.event_type,event_id:e.in_app.event_id,meta:e.in_app.meta})),(null==e?void 0:e.mobile_push)&&(yield this.notificationSendService.send({batch_id:t,channels:["mobile_push"],recipients:e.mobile_push.recipients,target_type:e.mobile_push.target_type,title:e.mobile_push.title,content:e.mobile_push.content,template_key:e.mobile_push.template_key,variables:e.mobile_push.variables,meta:e.mobile_push.meta})),(null==e?void 0:e.web_push)&&(yield this.notificationSendService.send({batch_id:t,channels:["web_push"],recipients:e.web_push.recipients,target_type:e.web_push.target_type,title:e.web_push.title,content:e.web_push.content,template_key:e.web_push.template_key,variables:e.web_push.variables,meta:e.web_push.meta})),(null==e?void 0:e.email)&&(yield this.notificationSendService.send({batch_id:t,channels:["email"],recipients:e.email.recipients,target_type:e.email.target_type,title:e.email.subject,content:e.email.body,template_key:e.email.template_key,variables:e.email.variables})),(null==e?void 0:e.kakao)&&(yield this.notificationSendService.send({batch_id:t,channels:["kakao"],recipients:e.kakao.recipients,target_type:e.kakao.target_type,template_key:e.kakao.template_key,variables:e.kakao.variables})),(null==e?void 0:e.sms)&&(yield this.notificationSendService.send({batch_id:t,channels:["sms"],recipients:e.sms.recipients,target_type:e.sms.target_type,content:e.sms.content,template_key:e.sms.template_key,variables:e.sms.variables})),(null==e?void 0:e.slack)&&(yield this.notificationSendService.send({batch_id:t,channels:["slack"],recipients:[{target_type:"system",target_id:0,recipient:e.slack.channel}],target_type:e.slack.target_type,content:e.slack.content,template_key:e.slack.template_key,variables:e.slack.variables})),{batch_id:t}})}generateBatchId(){return`BATCH_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}};exports.EventNotificationService=s,exports.EventNotificationService=s=e([(0,n.Injectable)(),t("design:paramtypes",[a.NotificationSendService])],s);
@@ -1,7 +1,8 @@
1
1
  export * from './notification.module';
2
2
  export * from './services';
3
3
  export interface NotificationRecipient {
4
- user_id: number;
4
+ target_type: string;
5
+ target_id: number;
5
6
  recipient: string;
6
7
  variables?: Record<string, any>;
7
8
  meta?: Record<string, any>;
@@ -9,6 +10,7 @@ export interface NotificationRecipient {
9
10
  export interface NotificationOptions {
10
11
  in_app?: {
11
12
  recipients: NotificationRecipient[];
13
+ target_type: string;
12
14
  title?: string;
13
15
  content?: string;
14
16
  template_key?: string;
@@ -19,6 +21,7 @@ export interface NotificationOptions {
19
21
  };
20
22
  mobile_push?: {
21
23
  recipients: NotificationRecipient[];
24
+ target_type: string;
22
25
  title?: string;
23
26
  content?: string;
24
27
  template_key?: string;
@@ -27,6 +30,7 @@ export interface NotificationOptions {
27
30
  };
28
31
  web_push?: {
29
32
  recipients: NotificationRecipient[];
33
+ target_type: string;
30
34
  title?: string;
31
35
  content?: string;
32
36
  template_key?: string;
@@ -35,12 +39,14 @@ export interface NotificationOptions {
35
39
  };
36
40
  email?: {
37
41
  recipients: NotificationRecipient[];
42
+ target_type: string;
38
43
  subject?: string;
39
44
  body?: string;
40
45
  template_key?: string;
41
46
  variables?: Record<string, any>;
42
47
  };
43
48
  slack?: {
49
+ target_type: string;
44
50
  channel: string;
45
51
  content?: string;
46
52
  template_key?: string;
@@ -48,11 +54,13 @@ export interface NotificationOptions {
48
54
  };
49
55
  kakao?: {
50
56
  recipients: NotificationRecipient[];
57
+ target_type: string;
51
58
  template_key: string;
52
59
  variables?: Record<string, any>;
53
60
  };
54
61
  sms?: {
55
62
  recipients: NotificationRecipient[];
63
+ target_type: string;
56
64
  content?: string;
57
65
  template_key?: string;
58
66
  variables?: Record<string, any>;
@@ -62,7 +70,8 @@ export type NotificationPayload = {
62
70
  type: 'in_app';
63
71
  recipients: Array<{
64
72
  recipient: string;
65
- user_id: number;
73
+ target_type: string;
74
+ target_id: number;
66
75
  title?: string;
67
76
  content?: string;
68
77
  variables?: Record<string, any>;
@@ -79,7 +88,8 @@ export type NotificationPayload = {
79
88
  type: 'mobile_push';
80
89
  recipients: Array<{
81
90
  recipient: string;
82
- user_id: number;
91
+ target_type: string;
92
+ target_id: number;
83
93
  variables?: Record<string, any>;
84
94
  }>;
85
95
  title?: string;
@@ -91,7 +101,8 @@ export type NotificationPayload = {
91
101
  type: 'web_push';
92
102
  recipients: Array<{
93
103
  recipient: string;
94
- user_id: number;
104
+ target_type: string;
105
+ target_id: number;
95
106
  variables?: Record<string, any>;
96
107
  }>;
97
108
  title?: string;
@@ -103,7 +114,8 @@ export type NotificationPayload = {
103
114
  type: 'email';
104
115
  recipients: Array<{
105
116
  recipient: string;
106
- user_id: number;
117
+ target_type: string;
118
+ target_id: number;
107
119
  variables?: Record<string, any>;
108
120
  }>;
109
121
  subject?: string;
@@ -114,7 +126,8 @@ export type NotificationPayload = {
114
126
  type: 'sms';
115
127
  recipients: Array<{
116
128
  recipient: string;
117
- user_id: number;
129
+ target_type: string;
130
+ target_id: number;
118
131
  variables?: Record<string, any>;
119
132
  }>;
120
133
  content?: string;
@@ -124,7 +137,8 @@ export type NotificationPayload = {
124
137
  type: 'kakao';
125
138
  recipients: Array<{
126
139
  recipient: string;
127
- user_id: number;
140
+ target_type: string;
141
+ target_id: number;
128
142
  variables?: Record<string, any>;
129
143
  }>;
130
144
  template_key: string;
@@ -6,12 +6,14 @@ export declare class EmailService {
6
6
  constructor(configService: ConfigService);
7
7
  send(recipients: Array<{
8
8
  recipient: string;
9
- user_id: number;
9
+ target_type?: string;
10
+ target_id?: number;
10
11
  subject: string;
11
12
  text: string;
12
13
  variables?: Record<string, any>;
13
14
  }>): Promise<{
14
- user_id: number;
15
+ target_type: string;
16
+ target_id: number;
15
17
  recipient: string;
16
18
  success: boolean;
17
19
  content: string;
@@ -19,12 +21,14 @@ export declare class EmailService {
19
21
  }[] | import("@aws-sdk/client-ses").SendEmailCommandOutput | import("resend").CreateEmailResponse>;
20
22
  sendAws(recipients: Array<{
21
23
  recipient: string;
22
- user_id: number;
24
+ target_type?: string;
25
+ target_id?: number;
23
26
  subject: string;
24
27
  text: string;
25
28
  variables?: Record<string, any>;
26
29
  }>): Promise<{
27
- user_id: number;
30
+ target_type: string;
31
+ target_id: number;
28
32
  recipient: string;
29
33
  success: boolean;
30
34
  content: string;
@@ -32,12 +36,14 @@ export declare class EmailService {
32
36
  }[] | import("@aws-sdk/client-ses").SendEmailCommandOutput>;
33
37
  sendResend(recipients: Array<{
34
38
  recipient: string;
35
- user_id: number;
39
+ target_type?: string;
40
+ target_id?: number;
36
41
  subject: string;
37
42
  text: string;
38
43
  variables?: Record<string, any>;
39
44
  }>): Promise<{
40
- user_id: number;
45
+ target_type: string;
46
+ target_id: number;
41
47
  recipient: string;
42
48
  success: boolean;
43
49
  content: string;
@@ -1 +1 @@
1
- var e=this&&this.__decorate||function(e,t,i,n){var s,r=arguments.length,c=r<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)c=Reflect.decorate(e,t,i,n);else for(var o=e.length-1;o>=0;o--)(s=e[o])&&(c=(r<3?s(c):r>3?s(t,i,c):s(t,i))||c);return r>3&&c&&Object.defineProperty(t,i,c),c},t=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},i=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(s,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n.throw(e))}catch(e){r(e)}}function step(e){e.done?s(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.EmailService=void 0;const n=require("@aws-sdk/client-ses"),s=require("@nestjs/common"),r=require("@nestjs/config"),c=require("resend");let o=class EmailService{constructor(e){this.configService=e,"aws"===this.configService.get("EMAIL_PROVIDER")&&(this.sesClient=new n.SESClient({region:this.configService.get("AWS_REGION"),credentials:{accessKeyId:this.configService.get("AWS_ACCESS_KEY_ID"),secretAccessKey:this.configService.get("AWS_SECRET_ACCESS_KEY")}})),"resend"===this.configService.get("EMAIL_PROVIDER")&&(this.resendClient=new c.Resend(this.configService.get("RESEND_API_KEY")))}send(e){return i(this,void 0,void 0,function*(){const t=this.configService.get("EMAIL_PROVIDER");if(!t||!this.configService.get("EMAIL_FROM"))throw new Error("EMAIL_PROVIDER or EMAIL_FROM is not set");return"aws"===t?this.sendAws(e):"resend"===t?this.sendResend(e):[]})}sendAws(e){return i(this,void 0,void 0,function*(){if("production"!==process.env.NODE_ENV)return e.map(e=>({user_id:e.user_id,recipient:e.recipient,success:!0,content:e.text,status:"sent"}));for(const t of e){const e=new n.SendEmailCommand({Source:this.configService.get("EMAIL_NOREPLY_SOURCE"),Destination:{ToAddresses:[t.recipient]},Message:{Subject:{Data:t.subject,Charset:"UTF-8"},Body:{Html:{Data:t.text,Charset:"UTF-8"}}}});return yield this.sesClient.send(e)}})}sendResend(e){return i(this,void 0,void 0,function*(){if("production"!==process.env.NODE_ENV)return console.log("📧 [EMAIL] Resend 배열 발송:",{count:e.length,from:this.configService.get("EMAIL_FROM"),provider:this.configService.get("EMAIL_PROVIDER"),recipients:e.map(e=>({user_id:e.user_id,recipient:e.recipient,subject:e.subject,text:e.text.substring(0,50)+(e.text.length>50?"...":""),variables:e.variables})),timestamp:(new Date).toISOString()}),e.map(e=>({user_id:e.user_id,recipient:e.recipient,success:!0,content:e.text,status:"sent"}));for(const t of e){return yield this.resendClient.emails.send({from:this.configService.get("EMAIL_NOREPLY_SOURCE"),to:[t.recipient],subject:t.subject,html:t.text})}})}};exports.EmailService=o,exports.EmailService=o=e([(0,s.Injectable)(),t("design:paramtypes",[r.ConfigService])],o);
1
+ var e=this&&this.__decorate||function(e,t,i,n){var r,s=arguments.length,c=s<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)c=Reflect.decorate(e,t,i,n);else for(var o=e.length-1;o>=0;o--)(r=e[o])&&(c=(s<3?r(c):s>3?r(t,i,c):r(t,i))||c);return s>3&&c&&Object.defineProperty(t,i,c),c},t=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},i=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(r,s){function fulfilled(e){try{step(n.next(e))}catch(e){s(e)}}function rejected(e){try{step(n.throw(e))}catch(e){s(e)}}function step(e){e.done?r(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.EmailService=void 0;const n=require("@aws-sdk/client-ses"),r=require("@nestjs/common"),s=require("@nestjs/config"),c=require("resend");let o=class EmailService{constructor(e){this.configService=e,"aws"===this.configService.get("EMAIL_PROVIDER")&&(this.sesClient=new n.SESClient({region:this.configService.get("AWS_REGION"),credentials:{accessKeyId:this.configService.get("AWS_ACCESS_KEY_ID"),secretAccessKey:this.configService.get("AWS_SECRET_ACCESS_KEY")}})),"resend"===this.configService.get("EMAIL_PROVIDER")&&(this.resendClient=new c.Resend(this.configService.get("RESEND_API_KEY")))}send(e){return i(this,void 0,void 0,function*(){const t=this.configService.get("EMAIL_PROVIDER");if(!t||!this.configService.get("EMAIL_FROM"))throw new Error("EMAIL_PROVIDER or EMAIL_FROM is not set");return"aws"===t?this.sendAws(e):"resend"===t?this.sendResend(e):[]})}sendAws(e){return i(this,void 0,void 0,function*(){if("production"!==process.env.NODE_ENV)return e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,success:!0,content:e.text,status:"sent"}));for(const t of e){const e=new n.SendEmailCommand({Source:this.configService.get("EMAIL_NOREPLY_SOURCE"),Destination:{ToAddresses:[t.recipient]},Message:{Subject:{Data:t.subject,Charset:"UTF-8"},Body:{Html:{Data:t.text,Charset:"UTF-8"}}}});return yield this.sesClient.send(e)}})}sendResend(e){return i(this,void 0,void 0,function*(){if("production"!==process.env.NODE_ENV)return console.log("📧 [EMAIL] Resend 배열 발송:",{count:e.length,from:this.configService.get("EMAIL_FROM"),provider:this.configService.get("EMAIL_PROVIDER"),recipients:e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,subject:e.subject,text:e.text.substring(0,50)+(e.text.length>50?"...":""),variables:e.variables})),timestamp:(new Date).toISOString()}),e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,success:!0,content:e.text,status:"sent"}));for(const t of e){return yield this.resendClient.emails.send({from:this.configService.get("EMAIL_NOREPLY_SOURCE"),to:[t.recipient],subject:t.subject,html:t.text})}})}};exports.EmailService=o,exports.EmailService=o=e([(0,r.Injectable)(),t("design:paramtypes",[s.ConfigService])],o);
@@ -2,7 +2,8 @@ import { ConfigService } from '@nestjs/config';
2
2
  import { RedisService } from '../../redis';
3
3
  interface KakaoMessage {
4
4
  recipient: string;
5
- user_id: number;
5
+ target_type?: string;
6
+ target_id?: number;
6
7
  sender_key?: string;
7
8
  template_code?: string;
8
9
  template_meta?: any;
@@ -14,7 +15,8 @@ export declare class KakaoService {
14
15
  constructor(configService: ConfigService, redis: RedisService);
15
16
  send(recipients: Array<KakaoMessage>): Promise<any[]>;
16
17
  sendAligo(recipients: Array<KakaoMessage>): Promise<{
17
- user_id: number;
18
+ target_type: string;
19
+ target_id: number;
18
20
  recipient: string;
19
21
  success: boolean;
20
22
  messageId: string;
@@ -1 +1 @@
1
- var e=this&&this.__decorate||function(e,t,i,s){var n,r=arguments.length,o=r<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var c=e.length-1;c>=0;c--)(n=e[c])&&(o=(r<3?n(o):r>3?n(t,i,o):n(t,i))||o);return r>3&&o&&Object.defineProperty(t,i,o),o},t=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},i=this&&this.__awaiter||function(e,t,i,s){return new(i||(i=Promise))(function(n,r){function fulfilled(e){try{step(s.next(e))}catch(e){r(e)}}function rejected(e){try{step(s.throw(e))}catch(e){r(e)}}function step(e){e.done?n(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((s=s.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.KakaoService=void 0;const s=require("@nestjs/common"),n=require("@nestjs/config"),r=require("crypto"),o=require("../../redis");let c=class KakaoService{constructor(e,t){this.configService=e,this.redis=t}send(e){return i(this,void 0,void 0,function*(){const t=this.configService.get("KAKAO_PROVIDER");if(!t||!this.configService.get("KAKAO_FROM"))throw new Error("KAKAO_PROVIDER or KAKAO_FROM is not set");return"aligo"===t?this.sendAligo(e):"ppurio"===t?this.sendPpurio(e):"ncp"===t?this.sendNcp(e):[]})}sendAligo(e){return i(this,void 0,void 0,function*(){return console.log("📱 [KAKAO] Aligo 배열 발송:",{count:e.length,from:this.configService.get("KAKAO_FROM"),provider:"aligo",recipients:e.map(e=>({user_id:e.user_id,recipient:e.recipient,template_code:e.template_code})),timestamp:(new Date).toISOString()}),e.map(e=>({user_id:e.user_id,recipient:e.recipient,success:!0,messageId:`aligo-${Date.now()}-${e.user_id}`,status:"sent"}))})}getPpurioToken(){return i(this,void 0,void 0,function*(){if(yield this.redis.get("ppurio_token"))return yield this.redis.get("ppurio_token");const e=this.configService.get("PPURIO_ACCOUNT"),t=this.configService.get("PPURIO_PASSWORD"),i=btoa(`${e}:${t}`),s=yield fetch("https://api.bizppurio.com/v1/token",{method:"POST",headers:{"Content-Type":"application/json; charset=utf-8",Authorization:`Basic ${i}`},body:JSON.stringify({grant_type:"client_credentials"})}),n=yield s.json();if(n.accesstoken)return yield this.redis.set("ppurio_token",n.accesstoken,86e3),yield this.redis.get("ppurio_token");throw new Error(`cannot get ppurio access token: ${JSON.stringify(n)}`)})}sendPpurioMessage(e){return i(this,void 0,void 0,function*(){const t=e.recipient.trim().replace(/-/g,"");if(t.startsWith("0100")||11!==t.length)return;const i=yield this.getPpurioToken(),s=yield fetch("https://api.bizppurio.com/v3/message",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`},body:JSON.stringify({account:this.configService.get("PPURIO_ACCOUNT"),refkey:this.configService.get("PPURIO_PASSWORD"),type:"at",from:this.configService.get("SMS_FROM"),to:e.recipient,content:{at:{senderkey:e.sender_key,templatecode:e.template_code,message:e.message}}})});return yield s.json()})}sendPpurio(e){return i(this,void 0,void 0,function*(){if("production"!==process.env.NODE_ENV)return console.log("📱 [KAKAO] Ppurio 발송:",{count:e.length,from:this.configService.get("KAKAO_FROM"),provider:"ppurio",recipients:e.map(e=>({user_id:e.user_id,recipient:e.recipient,sender_key:e.sender_key,template_code:e.template_code,message:e.message})),timestamp:(new Date).toISOString()}),e.map(e=>({user_id:e.user_id,recipient:e.recipient,success:!0,messageId:`ppurio-${Date.now()}-${e.user_id}`,message:e.message,status:"sent"}));return yield Promise.all(e.map(e=>this.sendPpurioMessage(e)))})}makeNcpSignature(e,t,i){const s=this.configService.get("NCP_ACCESS_KEY"),n=this.configService.get("NCP_SECRET_KEY"),o=`${e} ${t}\n${i}\n${s}`;return r.createHmac("sha256",n).update(o).digest("base64")}sendNcpMessage(e){return i(this,void 0,void 0,function*(){const t=this.configService.get("NCP_SERVICE_ID"),i=this.configService.get("NCP_ACCESS_KEY");if(!t||!i)throw new Error("NCP_SERVICE_ID or NCP_ACCESS_KEY is not set");const s=Date.now().toString(),n=`/alimtalk/v2/services/${t}/messages`,r=this.makeNcpSignature("POST",n,s),o=e.recipient.trim().replace(/-/g,"");if(o.startsWith("0100")||11!==o.length)return;const c=yield fetch(`https://sens.apigw.ntruss.com${n}`,{method:"POST",headers:{"Content-Type":"application/json; charset=utf-8","x-ncp-apigw-timestamp":s,"x-ncp-iam-access-key":i,"x-ncp-apigw-signature-v2":r},body:JSON.stringify({plusFriendId:this.configService.get("KAKAO_FROM"),templateCode:e.template_code,messages:[Object.assign({countryCode:"82",to:o,content:e.message},e.template_meta?{template:e.template_meta}:{})]})});return yield c.json()})}sendNcp(e){return i(this,void 0,void 0,function*(){if("production"!==process.env.NODE_ENV)return console.log("📱 [KAKAO] NCP 발송:",JSON.stringify({count:e.length,recipients:e.map(e=>({user_id:e.user_id,recipient:e.recipient,sender_key:this.configService.get("KAKAO_FROM"),template_code:e.template_code,template_meta:e.template_meta,message:e.message})),timestamp:(new Date).toISOString()},null,2)),e.map(e=>({user_id:e.user_id,recipient:e.recipient,success:!0,messageId:`ncp-${Date.now()}-${e.user_id}`,status:"sent"}));return yield Promise.all(e.map(e=>this.sendNcpMessage(e)))})}};exports.KakaoService=c,exports.KakaoService=c=e([(0,s.Injectable)(),t("design:paramtypes",[n.ConfigService,o.RedisService])],c);
1
+ var e=this&&this.__decorate||function(e,t,i,r){var n,o=arguments.length,s=o<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,i,r);else for(var c=e.length-1;c>=0;c--)(n=e[c])&&(s=(o<3?n(s):o>3?n(t,i,s):n(t,i))||s);return o>3&&s&&Object.defineProperty(t,i,s),s},t=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},i=this&&this.__awaiter||function(e,t,i,r){return new(i||(i=Promise))(function(n,o){function fulfilled(e){try{step(r.next(e))}catch(e){o(e)}}function rejected(e){try{step(r.throw(e))}catch(e){o(e)}}function step(e){e.done?n(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((r=r.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.KakaoService=void 0;const r=require("@nestjs/common"),n=require("@nestjs/config"),o=require("crypto"),s=require("../../redis");let c=class KakaoService{constructor(e,t){this.configService=e,this.redis=t}send(e){return i(this,void 0,void 0,function*(){const t=this.configService.get("KAKAO_PROVIDER");if(!t||!this.configService.get("KAKAO_FROM"))throw new Error("KAKAO_PROVIDER or KAKAO_FROM is not set");return"aligo"===t?this.sendAligo(e):"ppurio"===t?this.sendPpurio(e):"ncp"===t?this.sendNcp(e):[]})}sendAligo(e){return i(this,void 0,void 0,function*(){return console.log("📱 [KAKAO] Aligo 배열 발송:",{count:e.length,from:this.configService.get("KAKAO_FROM"),provider:"aligo",recipients:e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,template_code:e.template_code})),timestamp:(new Date).toISOString()}),e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,success:!0,messageId:`aligo-${Date.now()}-${e.target_id}`,status:"sent"}))})}getPpurioToken(){return i(this,void 0,void 0,function*(){if(yield this.redis.get("ppurio_token"))return yield this.redis.get("ppurio_token");const e=this.configService.get("PPURIO_ACCOUNT"),t=this.configService.get("PPURIO_PASSWORD"),i=btoa(`${e}:${t}`),r=yield fetch("https://api.bizppurio.com/v1/token",{method:"POST",headers:{"Content-Type":"application/json; charset=utf-8",Authorization:`Basic ${i}`},body:JSON.stringify({grant_type:"client_credentials"})}),n=yield r.json();if(n.accesstoken)return yield this.redis.set("ppurio_token",n.accesstoken,86e3),yield this.redis.get("ppurio_token");throw new Error(`cannot get ppurio access token: ${JSON.stringify(n)}`)})}sendPpurioMessage(e){return i(this,void 0,void 0,function*(){const t=e.recipient.trim().replace(/-/g,"");if(t.startsWith("0100")||11!==t.length)return;const i=yield this.getPpurioToken(),r=yield fetch("https://api.bizppurio.com/v3/message",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`},body:JSON.stringify({account:this.configService.get("PPURIO_ACCOUNT"),refkey:this.configService.get("PPURIO_PASSWORD"),type:"at",from:this.configService.get("SMS_FROM"),to:e.recipient,content:{at:{senderkey:e.sender_key,templatecode:e.template_code,message:e.message}}})});return yield r.json()})}sendPpurio(e){return i(this,void 0,void 0,function*(){if("production"!==process.env.NODE_ENV)return console.log("📱 [KAKAO] Ppurio 발송:",{count:e.length,from:this.configService.get("KAKAO_FROM"),provider:"ppurio",recipients:e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,sender_key:e.sender_key,template_code:e.template_code,message:e.message})),timestamp:(new Date).toISOString()}),e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,success:!0,messageId:`ppurio-${Date.now()}-${e.target_id}`,message:e.message,status:"sent"}));return yield Promise.all(e.map(e=>this.sendPpurioMessage(e)))})}makeNcpSignature(e,t,i){const r=this.configService.get("NCP_ACCESS_KEY"),n=this.configService.get("NCP_SECRET_KEY"),s=`${e} ${t}\n${i}\n${r}`;return o.createHmac("sha256",n).update(s).digest("base64")}sendNcpMessage(e){return i(this,void 0,void 0,function*(){var t;const i=this.configService.get("NCP_SERVICE_ID"),r=this.configService.get("NCP_ACCESS_KEY");if(!i||!r)throw new Error("NCP_SERVICE_ID or NCP_ACCESS_KEY is not set");const n=Date.now().toString(),o=`/alimtalk/v2/services/${i}/messages`,s=this.makeNcpSignature("POST",o,n),c=e.recipient.trim().replace(/-/g,"");if(c.startsWith("0100")||11!==c.length)return;const a=yield fetch(`https://sens.apigw.ntruss.com${o}`,{method:"POST",headers:{"Content-Type":"application/json; charset=utf-8","x-ncp-apigw-timestamp":n,"x-ncp-iam-access-key":r,"x-ncp-apigw-signature-v2":s},body:JSON.stringify({plusFriendId:this.configService.get("KAKAO_FROM"),templateCode:e.template_code,messages:[Object.assign({countryCode:"82",to:c,content:e.message},null!==(t=e.template_meta)&&void 0!==t?t:{})]})});return yield a.json()})}sendNcp(e){return i(this,void 0,void 0,function*(){if("production"!==process.env.NODE_ENV)return console.log("📱 [KAKAO] NCP 발송:",JSON.stringify({count:e.length,recipients:e.map(e=>e),timestamp:(new Date).toISOString()},null,2)),e.map(e=>e);return yield Promise.all(e.map(e=>this.sendNcpMessage(e)))})}};exports.KakaoService=c,exports.KakaoService=c=e([(0,r.Injectable)(),t("design:paramtypes",[n.ConfigService,s.RedisService])],c);
@@ -7,11 +7,13 @@ import { SmsService } from './sms.service';
7
7
  export interface SendNotificationDto {
8
8
  batch_id?: string;
9
9
  recipients: Array<{
10
- user_id: number;
10
+ target_type: string;
11
+ target_id: number;
11
12
  recipient?: string;
12
13
  variables?: Record<string, any>;
13
14
  }>;
14
15
  channels: Array<'in_app' | 'mobile_push' | 'web_push' | 'email' | 'sms' | 'kakao' | 'slack'>;
16
+ target_type: string;
15
17
  template_key?: string;
16
18
  title?: string;
17
19
  content?: string;
@@ -1 +1 @@
1
- var e,t=this&&this.__decorate||function(e,t,i,n){var r,s=arguments.length,a=s<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,i,n);else for(var o=e.length-1;o>=0;o--)(r=e[o])&&(a=(s<3?r(a):s>3?r(t,i,a):r(t,i))||a);return s>3&&a&&Object.defineProperty(t,i,a),a},i=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},n=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(r,s){function fulfilled(e){try{step(n.next(e))}catch(e){s(e)}}function rejected(e){try{step(n.throw(e))}catch(e){s(e)}}function step(e){e.done?r(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.NotificationSendService=void 0;const r=require("@nestjs/common"),s=require("../../enums.common"),a=require("./email.service"),o=require("./kakao.service"),c=require("./message-template.service"),l=require("./notification.service"),d=require("./slack.service"),p=require("./sms.service");let h=e=class NotificationSendService{constructor(t,i,n,s,a,o){this.notificationService=t,this.emailService=i,this.smsService=n,this.kakaoService=s,this.slackService=a,this.templateService=o,this.logger=new r.Logger(e.name)}send(e){return n(this,void 0,void 0,function*(){const{title:t,content:i,template_code:n,template_meta:r}=yield this.renderTemplate(e);return 1===e.recipients.length?yield this.sendSingle(e,t,i,n,r):(this.logger.log(`1:N 배치 알림 발송 시작: ${t} (${e.recipients.length}명)`),yield this.sendBatch(e,t,i,n,r))})}sendSingle(e,t,i,r,a){return n(this,void 0,void 0,function*(){const n=e.recipients[0],o=[];for(const c of e.channels){let l;try{"in_app"===c?(l=yield this.notificationService.createSingle({user_id:n.user_id,recipient:n.recipient,type:"in_app",template_key:e.template_key,event_type:e.event_type,event_id:e.event_id,title:t,content:i,variables:Object.assign(Object.assign({},e.variables),n.variables),meta:Object.assign(Object.assign({},e.meta),{template_code:r||"",template_meta:a||{}})}),l.status=s.NOTIFICATION_STATUS.SENT,l.sent_at=new Date,yield this.notificationService.notificationRepository.save(l)):(l=yield this.notificationService.createSingle({user_id:n.user_id,recipient:n.recipient,type:c,template_key:e.template_key,event_type:e.event_type,event_id:e.event_id,title:t,content:i,variables:Object.assign(Object.assign({},e.variables),n.variables),meta:Object.assign({},e.meta)}),yield this.sendToChannel(c,{recipient:n.recipient,title:t,content:i,user_id:n.user_id,template_code:r,template_meta:a,meta:e.meta}),l.status=s.NOTIFICATION_STATUS.SENT,l.sent_at=new Date,yield this.notificationService.notificationRepository.save(l),this.logger.log(`${c} 발송 성공: user_id=${n.user_id}`)),o.push({channel:c,status:"success",notification:l})}catch(e){this.logger.error(`${c} 발송 실패: ${e.message}`,e.stack),l&&(l.status=s.NOTIFICATION_STATUS.FAILED,l.failed_at=new Date,l.reason_failed=e.message,yield this.notificationService.notificationRepository.save(l)),o.push({channel:c,status:"failed",error:e.message})}}return o})}sendBatch(e,t,i,r,a){return n(this,void 0,void 0,function*(){const{batch_id:n,total_count:r}=yield this.notificationService.createBatch({batch_id:e.batch_id,recipients:e.recipients,type:"in_app",template_key:e.template_key,event_type:e.event_type,event_id:e.event_id,title:e.title,content:e.content,variables:e.variables,meta:e.meta,channels:e.channels});this.logger.log(`배치 생성 완료: ${n} (${r}건)`);const a=e.channels.filter(e=>"in_app"!==e),o=[];for(const r of a)try{yield this.sendBatchToChannel(r,{recipients:e.recipients,title:t,content:i,meta:e.meta}),yield this.notificationService.notificationRepository.update({batch_id:n,type:r},{status:s.NOTIFICATION_STATUS.SENT,sent_at:new Date}),o.push({channel:r,status:"sent"}),this.logger.log(`${r} 배치 발송 성공: ${e.recipients.length}명`)}catch(e){this.logger.error(`${r} 배치 발송 실패: ${e.message}`,e.stack),yield this.notificationService.notificationRepository.update({batch_id:n,type:r},{status:s.NOTIFICATION_STATUS.FAILED,failed_at:new Date,reason_failed:e.message}),o.push({channel:r,status:"failed",error:e.message})}return{batch_id:n,total_count:r,send_results:o}})}sendToChannel(e,t){return n(this,void 0,void 0,function*(){switch(e){case"mobile_push":throw this.logger.warn("mobile_push 채널은 아직 구현되지 않았습니다"),new Error("mobile_push not implemented yet");case"web_push":throw this.logger.warn("web_push 채널은 아직 구현되지 않았습니다"),new Error("web_push not implemented yet");case"email":return yield this.emailService.send([{recipient:t.recipient,user_id:t.user_id,subject:t.title,text:t.content}]);case"sms":return yield this.smsService.send([{recipient:t.recipient,user_id:t.user_id,message:t.content}]);case"kakao":return yield this.kakaoService.send([{recipient:t.recipient,user_id:t.user_id,sender_key:t.sender_key,template_code:t.template_code,template_meta:t.template_meta,message:t.content}]);case"slack":return yield this.slackService.send(t.recipient,t.content);default:throw new Error(`Unknown channel: ${e}`)}})}sendBatchToChannel(e,t){return n(this,void 0,void 0,function*(){switch(e){case"mobile_push":throw this.logger.warn("mobile_push 배치 발송은 아직 구현되지 않았습니다"),new Error("mobile_push batch sending not implemented yet");case"web_push":throw this.logger.warn("web_push 배치 발송은 아직 구현되지 않았습니다"),new Error("web_push batch sending not implemented yet");case"email":{const e=t.recipients.map(e=>({recipient:e.recipient,user_id:e.user_id,subject:t.title,text:t.content})).filter(e=>e.recipient);if(0===e.length)throw new Error("No email addresses provided");return yield this.emailService.send(e)}case"sms":{const e=t.recipients.map(e=>({recipient:e.recipient,user_id:e.user_id,message:t.content})).filter(e=>e.recipient);if(0===e.length)throw new Error("No phone numbers provided");return yield this.smsService.send(e)}case"kakao":{const e=t.recipients.map(e=>{var i,n;return{recipient:e.recipient,user_id:e.user_id,sender_key:null===(i=t.meta)||void 0===i?void 0:i.sender_key,template_code:null===(n=t.meta)||void 0===n?void 0:n.template_code,message:t.content}}).filter(e=>e.recipient);if(0===e.length)throw new Error("No kakao recipients provided");return yield this.kakaoService.send(e)}default:throw new Error(`Batch sending not supported for ${e}`)}})}resendBatch(e,t){return n(this,void 0,void 0,function*(){this.logger.log(`배치 재발송 시작: ${e}, ${t}`);const i=yield this.notificationService.notificationRepository.find({where:{batch_id:e,type:t}});if(0===i.length)throw new Error(`Batch records not found: ${e}, ${t}`);const n=i.map(e=>({user_id:e.user_id,recipient:e.recipient,variables:e.variables})),r=i[0];try{return yield this.sendBatchToChannel(t,{recipients:n,title:r.title,content:r.content,meta:r.meta}),yield this.notificationService.notificationRepository.update({batch_id:e,type:t},{status:s.NOTIFICATION_STATUS.SENT,sent_at:new Date}),this.logger.log(`배치 재발송 성공: ${e}`),{success:!0,batch_id:e,channel:t,count:n.length}}catch(i){throw this.logger.error(`배치 재발송 실패: ${i.message}`,i.stack),yield this.notificationService.notificationRepository.update({batch_id:e,type:t},{status:s.NOTIFICATION_STATUS.FAILED,failed_at:new Date,reason_failed:i.message}),i}})}renderTemplate(e){return n(this,void 0,void 0,function*(){var t;if(e.template_key){const i=e.channels[0],n=yield this.templateService.getTemplate(i,e.template_key);if(!n)throw new Error(`Template not found: ${e.template_key} for ${i}`);const r=Object.assign(Object.assign({},e.variables),(null===(t=e.recipients[0])||void 0===t?void 0:t.variables)||{}),s=this.templateService.renderTemplate(n,r);return{title:s.title||e.title||"",content:s.content,template_code:n.template_code,template_meta:n.template_meta}}if(!e.title||!e.content)throw new Error("title and content are required when template_key is not provided");return{title:e.title,content:e.content}})}};exports.NotificationSendService=h,exports.NotificationSendService=h=e=t([(0,r.Injectable)(),i("design:paramtypes",[l.NotificationService,a.EmailService,p.SmsService,o.KakaoService,d.SlackService,c.MessageTemplateService])],h);
1
+ var e,t=this&&this.__decorate||function(e,t,i,n){var r,a=arguments.length,s=a<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,i,n);else for(var o=e.length-1;o>=0;o--)(r=e[o])&&(s=(a<3?r(s):a>3?r(t,i,s):r(t,i))||s);return a>3&&s&&Object.defineProperty(t,i,s),s},i=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},n=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))(function(r,a){function fulfilled(e){try{step(n.next(e))}catch(e){a(e)}}function rejected(e){try{step(n.throw(e))}catch(e){a(e)}}function step(e){e.done?r(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.NotificationSendService=void 0;const r=require("@nestjs/common"),a=require("../../enums.common"),s=require("./email.service"),o=require("./kakao.service"),c=require("./message-template.service"),l=require("./notification.service"),p=require("./slack.service"),d=require("./sms.service");let h=e=class NotificationSendService{constructor(t,i,n,a,s,o){this.notificationService=t,this.emailService=i,this.smsService=n,this.kakaoService=a,this.slackService=s,this.templateService=o,this.logger=new r.Logger(e.name)}send(e){return n(this,void 0,void 0,function*(){const{title:t,content:i,template_code:n,template_meta:r}=yield this.renderTemplate(e);return 1===e.recipients.length?yield this.sendSingle(e,t,i,n,r):(this.logger.log(`1:N 배치 알림 발송 시작: ${t} (${e.recipients.length}명)`),yield this.sendBatch(e,t,i,n,r))})}sendSingle(e,t,i,r,s){return n(this,void 0,void 0,function*(){const n=e.recipients[0],o=[];for(const c of e.channels){let l;try{"in_app"===c?(l=yield this.notificationService.createSingle({target_type:e.target_type,target_id:n.target_id,recipient:n.recipient,type:"in_app",template_key:e.template_key,event_type:e.event_type,event_id:e.event_id,title:t,content:i,variables:Object.assign(Object.assign({},e.variables),n.variables),meta:Object.assign(Object.assign({},e.meta),{template_code:r||"",template_meta:s||{}})}),l.status=a.NOTIFICATION_STATUS.SENT,l.sent_at=new Date,yield this.notificationService.notificationRepository.save(l)):(l=yield this.notificationService.createSingle({target_type:e.target_type,target_id:n.target_id,recipient:n.recipient,type:c,template_key:e.template_key,event_type:e.event_type,event_id:e.event_id,title:t,content:i,variables:Object.assign(Object.assign({},e.variables),n.variables),meta:Object.assign({},e.meta)}),yield this.sendToChannel(c,{recipient:n.recipient,title:t,content:i,target_type:e.target_type,target_id:n.target_id,template_code:r,template_meta:s,meta:e.meta}),l.status=a.NOTIFICATION_STATUS.SENT,l.sent_at=new Date,yield this.notificationService.notificationRepository.save(l),this.logger.log(`${c} 발송 성공: target_type=${e.target_type}, target_id=${n.target_id}`)),o.push({channel:c,status:"success",notification:l})}catch(e){this.logger.error(`${c} 발송 실패: ${e.message}`,e.stack),l&&(l.status=a.NOTIFICATION_STATUS.FAILED,l.failed_at=new Date,l.reason_failed=e.message,yield this.notificationService.notificationRepository.save(l)),o.push({channel:c,status:"failed",error:e.message})}}return o})}sendBatch(e,t,i,r,s){return n(this,void 0,void 0,function*(){const{batch_id:n,total_count:r}=yield this.notificationService.createBatch({batch_id:e.batch_id,recipients:e.recipients,type:"in_app",template_key:e.template_key,event_type:e.event_type,event_id:e.event_id,title:e.title,content:e.content,variables:e.variables,meta:e.meta,channels:e.channels});this.logger.log(`배치 생성 완료: ${n} (${r}건)`);const s=e.channels.filter(e=>"in_app"!==e),o=[];for(const r of s)try{yield this.sendBatchToChannel(r,{recipients:e.recipients,title:t,content:i,meta:e.meta}),yield this.notificationService.notificationRepository.update({batch_id:n,type:r},{status:a.NOTIFICATION_STATUS.SENT,sent_at:new Date}),o.push({channel:r,status:"sent"}),this.logger.log(`${r} 배치 발송 성공: ${e.recipients.length}건`)}catch(e){this.logger.error(`${r} 배치 발송 실패: ${e.message}`,e.stack),yield this.notificationService.notificationRepository.update({batch_id:n,type:r},{status:a.NOTIFICATION_STATUS.FAILED,failed_at:new Date,reason_failed:e.message}),o.push({channel:r,status:"failed",error:e.message})}return{batch_id:n,total_count:r,send_results:o}})}sendToChannel(e,t){return n(this,void 0,void 0,function*(){switch(e){case"mobile_push":throw this.logger.warn("mobile_push 채널은 아직 구현되지 않았습니다"),new Error("mobile_push not implemented yet");case"web_push":throw this.logger.warn("web_push 채널은 아직 구현되지 않았습니다"),new Error("web_push not implemented yet");case"email":return yield this.emailService.send([{recipient:t.recipient,target_type:t.target_type,target_id:t.target_id,subject:t.title,text:t.content}]);case"sms":return yield this.smsService.send([{recipient:t.recipient,target_type:t.target_type,target_id:t.target_id,message:t.content}]);case"kakao":return yield this.kakaoService.send([{recipient:t.recipient,target_type:t.target_type,target_id:t.target_id,sender_key:t.sender_key,template_code:t.template_code,template_meta:t.template_meta,message:t.content}]);case"slack":return yield this.slackService.send(t.recipient,t.content);default:throw new Error(`Unknown channel: ${e}`)}})}sendBatchToChannel(e,t){return n(this,void 0,void 0,function*(){switch(e){case"mobile_push":throw this.logger.warn("mobile_push 배치 발송은 아직 구현되지 않았습니다"),new Error("mobile_push batch sending not implemented yet");case"web_push":throw this.logger.warn("web_push 배치 발송은 아직 구현되지 않았습니다"),new Error("web_push batch sending not implemented yet");case"email":{const e=t.recipients.map(e=>({recipient:e.recipient,target_type:e.target_type,target_id:e.target_id,subject:t.title,text:t.content})).filter(e=>e.recipient);if(0===e.length)throw new Error("No email addresses provided");return yield this.emailService.send(e)}case"sms":{const e=t.recipients.map(e=>({recipient:e.recipient,target_type:e.target_type,target_id:e.target_id,message:t.content})).filter(e=>e.recipient);if(0===e.length)throw new Error("No phone numbers provided");return yield this.smsService.send(e)}case"kakao":{const e=t.recipients.map(e=>{var i,n;return{recipient:e.recipient,target_type:e.target_type,target_id:e.target_id,sender_key:null===(i=t.meta)||void 0===i?void 0:i.sender_key,template_code:null===(n=t.meta)||void 0===n?void 0:n.template_code,message:t.content}}).filter(e=>e.recipient);if(0===e.length)throw new Error("No kakao recipients provided");return yield this.kakaoService.send(e)}default:throw new Error(`Batch sending not supported for ${e}`)}})}resendBatch(e,t){return n(this,void 0,void 0,function*(){this.logger.log(`배치 재발송 시작: ${e}, ${t}`);const i=yield this.notificationService.notificationRepository.find({where:{batch_id:e,type:t}});if(0===i.length)throw new Error(`Batch records not found: ${e}, ${t}`);const n=i.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,variables:e.variables})),r=i[0];try{return yield this.sendBatchToChannel(t,{recipients:n,title:r.title,content:r.content,meta:r.meta}),yield this.notificationService.notificationRepository.update({batch_id:e,type:t},{status:a.NOTIFICATION_STATUS.SENT,sent_at:new Date}),this.logger.log(`배치 재발송 성공: ${e}`),{success:!0,batch_id:e,channel:t,count:n.length}}catch(i){throw this.logger.error(`배치 재발송 실패: ${i.message}`,i.stack),yield this.notificationService.notificationRepository.update({batch_id:e,type:t},{status:a.NOTIFICATION_STATUS.FAILED,failed_at:new Date,reason_failed:i.message}),i}})}renderTemplate(e){return n(this,void 0,void 0,function*(){var t;if(e.template_key){const i=e.channels[0],n=yield this.templateService.getTemplate(i,e.template_key);if(!n)throw new Error(`Template not found: ${e.template_key} for ${i}`);const r=Object.assign(Object.assign({},e.variables),(null===(t=e.recipients[0])||void 0===t?void 0:t.variables)||{}),a=this.templateService.renderTemplate(n,r);return{title:a.title||e.title||"",content:a.content,template_code:n.template_code,template_meta:n.template_meta}}if(!e.title||!e.content)throw new Error("title and content are required when template_key is not provided");return{title:e.title,content:e.content}})}};exports.NotificationSendService=h,exports.NotificationSendService=h=e=t([(0,r.Injectable)(),i("design:paramtypes",[l.NotificationService,s.EmailService,d.SmsService,o.KakaoService,p.SlackService,c.MessageTemplateService])],h);
@@ -4,8 +4,9 @@ import { NOTIFICATION_STATUS } from '../../enums.common';
4
4
  export declare class NotificationService {
5
5
  readonly notificationRepository: Repository<Notification>;
6
6
  constructor(notificationRepository: Repository<Notification>);
7
- createSingle({ user_id, recipient, type, template_key, event_type, event_id, title, content, variables, meta, }: {
8
- user_id: number;
7
+ createSingle({ target_type, target_id, recipient, type, template_key, event_type, event_id, title, content, variables, meta, }: {
8
+ target_type: string;
9
+ target_id: number;
9
10
  recipient?: string;
10
11
  type: string;
11
12
  template_key: string;
@@ -16,7 +17,8 @@ export declare class NotificationService {
16
17
  variables?: Record<string, any>;
17
18
  meta?: Record<string, any>;
18
19
  }): Promise<{
19
- user_id: number;
20
+ target_type: string;
21
+ target_id: number;
20
22
  recipient: string;
21
23
  type: any;
22
24
  template_key: string;
@@ -31,7 +33,8 @@ export declare class NotificationService {
31
33
  createBatch({ batch_id, recipients, type, template_key, event_type, event_id, title, content, variables, meta, channels, }: {
32
34
  batch_id?: string;
33
35
  recipients: Array<{
34
- user_id: number;
36
+ target_type: string;
37
+ target_id: number;
35
38
  recipient?: string;
36
39
  variables?: Record<string, any>;
37
40
  }>;
@@ -49,18 +52,18 @@ export declare class NotificationService {
49
52
  total_count: number;
50
53
  notifications: any[];
51
54
  }>;
52
- findByUser(user_id: number, options?: {
55
+ findByTarget(target_type: string, target_id: number, options?: {
53
56
  limit?: number;
54
57
  offset?: number;
55
58
  unread_only?: boolean;
56
59
  }): Promise<Notification[]>;
57
- countUnread(user_id: number): Promise<number>;
58
- markAsRead(id: number, user_id: number): Promise<import("typeorm").UpdateResult>;
59
- markAllAsRead(user_id: number): Promise<import("typeorm").UpdateResult>;
60
- delete(id: number, user_id: number): Promise<import("typeorm").UpdateResult>;
60
+ countUnread(target_type: string, target_id: number): Promise<number>;
61
+ markAsRead(id: number, target_type: string, target_id: number): Promise<import("typeorm").UpdateResult>;
62
+ markAllAsRead(target_type: string, target_id: number): Promise<import("typeorm").UpdateResult>;
63
+ delete(id: number, target_type: string, target_id: number): Promise<import("typeorm").UpdateResult>;
61
64
  markBatchAsSent(batch_id: string, channel: string): Promise<import("typeorm").UpdateResult>;
62
65
  markBatchAsFailed(batch_id: string, channel: string, reason: string): Promise<import("typeorm").UpdateResult>;
63
- findBatchMaster(batch_id: string, channel: string): Promise<Notification>;
66
+ findBatchRecords(batch_id: string, channel: string): Promise<Notification>;
64
67
  findBatchRecipients(batch_id: string): Promise<Notification[]>;
65
68
  getBatchStats(batch_id: string): Promise<{
66
69
  total: number;
@@ -1 +1 @@
1
- var t=this&&this.__decorate||function(t,e,i,n){var r,a=arguments.length,o=a<3?e:null===n?n=Object.getOwnPropertyDescriptor(e,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(t,e,i,n);else for(var s=t.length-1;s>=0;s--)(r=t[s])&&(o=(a<3?r(o):a>3?r(e,i,o):r(e,i))||o);return a>3&&o&&Object.defineProperty(e,i,o),o},e=this&&this.__metadata||function(t,e){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(t,e)},i=this&&this.__param||function(t,e){return function(i,n){e(i,n,t)}},n=this&&this.__awaiter||function(t,e,i,n){return new(i||(i=Promise))(function(r,a){function fulfilled(t){try{step(n.next(t))}catch(t){a(t)}}function rejected(t){try{step(n.throw(t))}catch(t){a(t)}}function step(t){t.done?r(t.value):function adopt(t){return t instanceof i?t:new i(function(e){e(t)})}(t.value).then(fulfilled,rejected)}step((n=n.apply(t,e||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.NotificationService=void 0;const r=require("@nestjs/common"),a=require("@nestjs/typeorm"),o=require("typeorm"),s=require("../../database/entities"),d=require("../../enums.common");let c=class NotificationService{constructor(t){this.notificationRepository=t}createSingle(t){return n(this,arguments,void 0,function*({user_id:t,recipient:e,type:i,template_key:n,event_type:r,event_id:a,title:o,content:s,variables:c,meta:u}){return yield this.notificationRepository.save({user_id:t,recipient:e,type:i,template_key:n,event_type:r,event_id:a,title:o,content:s,variables:c,meta:u,status:d.NOTIFICATION_STATUS.PENDING})})}createBatch(t){return n(this,arguments,void 0,function*({batch_id:t,recipients:e,type:i,template_key:n,event_type:r,event_id:a,title:o,content:s,variables:c,meta:u,channels:p=["in_app","mobile_push"]}){const l=t||this.generateBatchId(),_=[];for(const t of p){const i=e.map(e=>({user_id:e.user_id,recipient:e.recipient,type:t,template_key:n,event_type:r,event_id:a,title:o,content:s,variables:Object.assign(Object.assign({},c),e.variables),meta:u,batch_id:l,status:"in_app"===t?d.NOTIFICATION_STATUS.SENT:d.NOTIFICATION_STATUS.PENDING,sent_at:"in_app"===t?new Date:null}));_.push(...i)}const f=yield this.notificationRepository.save(_);return{batch_id:l,total_count:f.length,notifications:f}})}findByUser(t,e){return n(this,void 0,void 0,function*(){const i=this.notificationRepository.createQueryBuilder("n").where("n.user_id = :user_id",{user_id:t}).andWhere("n.type = :type",{type:"in_app"}).andWhere("n.deleted_at IS NULL");return(null==e?void 0:e.unread_only)&&i.andWhere("n.read_at IS NULL"),i.orderBy("n.created_at","DESC").limit((null==e?void 0:e.limit)||20).offset((null==e?void 0:e.offset)||0),yield i.getMany()})}countUnread(t){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.count({where:{user_id:t,type:"in_app",read_at:(0,o.IsNull)(),deleted_at:(0,o.IsNull)()}})})}markAsRead(t,e){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.update({id:t,user_id:e,type:"in_app"},{status:d.NOTIFICATION_STATUS.READ,read_at:new Date})})}markAllAsRead(t){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.update({user_id:t,type:"in_app",read_at:(0,o.IsNull)()},{status:d.NOTIFICATION_STATUS.READ,read_at:new Date})})}delete(t,e){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.softDelete({id:t,user_id:e,type:"in_app"})})}markBatchAsSent(t,e){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.update({batch_id:t,type:e,user_id:(0,o.IsNull)()},{status:d.NOTIFICATION_STATUS.SENT,sent_at:new Date})})}markBatchAsFailed(t,e,i){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.update({batch_id:t,type:e,user_id:(0,o.IsNull)()},{status:d.NOTIFICATION_STATUS.FAILED,failed_at:new Date,reason_failed:i})})}findBatchMaster(t,e){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.findOne({where:{batch_id:t,type:e,user_id:(0,o.IsNull)()}})})}findBatchRecipients(t){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.find({where:{batch_id:t,type:"in_app",user_id:(0,o.Not)((0,o.IsNull)())},order:{created_at:"DESC"}})})}getBatchStats(t){return n(this,void 0,void 0,function*(){const e=yield this.notificationRepository.createQueryBuilder("n").select("COUNT(*)","total").addSelect("COUNT(n.read_at)","read").addSelect("COUNT(*) - COUNT(n.read_at)","unread").addSelect("ROUND(COUNT(n.read_at) * 100.0 / COUNT(*), 2)","read_rate").where("n.batch_id = :batch_id",{batch_id:t}).andWhere("n.type = :type",{type:"in_app"}).andWhere("n.user_id IS NOT NULL").getRawOne();return{total:parseInt(e.total),read:parseInt(e.read),unread:parseInt(e.unread),read_rate:parseFloat(e.read_rate)}})}generateBatchId(){return`BATCH_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}};exports.NotificationService=c,exports.NotificationService=c=t([(0,r.Injectable)(),i(0,(0,a.InjectRepository)(s.Notification)),e("design:paramtypes",[o.Repository])],c);
1
+ var t=this&&this.__decorate||function(t,e,i,n){var a,r=arguments.length,o=r<3?e:null===n?n=Object.getOwnPropertyDescriptor(e,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(t,e,i,n);else for(var d=t.length-1;d>=0;d--)(a=t[d])&&(o=(r<3?a(o):r>3?a(e,i,o):a(e,i))||o);return r>3&&o&&Object.defineProperty(e,i,o),o},e=this&&this.__metadata||function(t,e){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(t,e)},i=this&&this.__param||function(t,e){return function(i,n){e(i,n,t)}},n=this&&this.__awaiter||function(t,e,i,n){return new(i||(i=Promise))(function(a,r){function fulfilled(t){try{step(n.next(t))}catch(t){r(t)}}function rejected(t){try{step(n.throw(t))}catch(t){r(t)}}function step(t){t.done?a(t.value):function adopt(t){return t instanceof i?t:new i(function(e){e(t)})}(t.value).then(fulfilled,rejected)}step((n=n.apply(t,e||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.NotificationService=void 0;const a=require("@nestjs/common"),r=require("@nestjs/typeorm"),o=require("typeorm"),d=require("../../database/entities"),s=require("../../enums.common");let c=class NotificationService{constructor(t){this.notificationRepository=t}createSingle(t){return n(this,arguments,void 0,function*({target_type:t,target_id:e,recipient:i,type:n,template_key:a,event_type:r,event_id:o,title:d,content:c,variables:p,meta:u}){return yield this.notificationRepository.save({target_type:t,target_id:e,recipient:i,type:n,template_key:a,event_type:r,event_id:o,title:d,content:c,variables:p,meta:u,status:s.NOTIFICATION_STATUS.PENDING})})}createBatch(t){return n(this,arguments,void 0,function*({batch_id:t,recipients:e,type:i,template_key:n,event_type:a,event_id:r,title:o,content:d,variables:c,meta:p,channels:u=["in_app","mobile_push"]}){const _=t||this.generateBatchId(),l=[];for(const t of u){const i=e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,type:t,template_key:n,event_type:a,event_id:r,title:o,content:d,variables:Object.assign(Object.assign({},c),e.variables),meta:p,batch_id:_,status:"in_app"===t?s.NOTIFICATION_STATUS.SENT:s.NOTIFICATION_STATUS.PENDING,sent_at:"in_app"===t?new Date:null}));l.push(...i)}const y=yield this.notificationRepository.save(l);return{batch_id:_,total_count:y.length,notifications:y}})}findByTarget(t,e,i){return n(this,void 0,void 0,function*(){const n=this.notificationRepository.createQueryBuilder("n").where("n.target_type = :target_type",{target_type:t}).andWhere("n.target_id = :target_id",{target_id:e}).andWhere("n.type = :type",{type:"in_app"}).andWhere("n.deleted_at IS NULL");return(null==i?void 0:i.unread_only)&&n.andWhere("n.read_at IS NULL"),n.orderBy("n.created_at","DESC").limit((null==i?void 0:i.limit)||20).offset((null==i?void 0:i.offset)||0),yield n.getMany()})}countUnread(t,e){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.count({where:{target_type:t,target_id:e,type:"in_app",read_at:(0,o.IsNull)(),deleted_at:(0,o.IsNull)()}})})}markAsRead(t,e,i){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.update({id:t,target_type:e,target_id:i,type:"in_app"},{status:s.NOTIFICATION_STATUS.READ,read_at:new Date})})}markAllAsRead(t,e){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.update({target_type:t,target_id:e,type:"in_app",read_at:(0,o.IsNull)()},{status:s.NOTIFICATION_STATUS.READ,read_at:new Date})})}delete(t,e,i){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.softDelete({id:t,target_type:e,target_id:i,type:"in_app"})})}markBatchAsSent(t,e){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.update({batch_id:t,type:e},{status:s.NOTIFICATION_STATUS.SENT,sent_at:new Date})})}markBatchAsFailed(t,e,i){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.update({batch_id:t,type:e},{status:s.NOTIFICATION_STATUS.FAILED,failed_at:new Date,reason_failed:i})})}findBatchRecords(t,e){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.findOne({where:{batch_id:t,type:e}})})}findBatchRecipients(t){return n(this,void 0,void 0,function*(){return yield this.notificationRepository.find({where:{batch_id:t,type:"in_app"},order:{created_at:"DESC"}})})}getBatchStats(t){return n(this,void 0,void 0,function*(){const e=yield this.notificationRepository.createQueryBuilder("n").select("COUNT(*)","total").addSelect("COUNT(n.read_at)","read").addSelect("COUNT(*) - COUNT(n.read_at)","unread").addSelect("ROUND(COUNT(n.read_at) * 100.0 / COUNT(*), 2)","read_rate").where("n.batch_id = :batch_id",{batch_id:t}).andWhere("n.type = :type",{type:"in_app"}).getRawOne();return{total:parseInt(e.total),read:parseInt(e.read),unread:parseInt(e.unread),read_rate:parseFloat(e.read_rate)}})}generateBatchId(){return`BATCH_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}};exports.NotificationService=c,exports.NotificationService=c=t([(0,a.Injectable)(),i(0,(0,r.InjectRepository)(d.Notification)),e("design:paramtypes",[o.Repository])],c);
@@ -6,11 +6,13 @@ export declare class SmsService {
6
6
  constructor(configService: ConfigService, notificationService: NotificationService);
7
7
  send(recipients: Array<{
8
8
  recipient: string;
9
- user_id: number;
9
+ target_type?: string;
10
+ target_id?: number;
10
11
  message: string;
11
12
  variables?: Record<string, any>;
12
13
  }>): Promise<{
13
- user_id: number;
14
+ target_type: string;
15
+ target_id: number;
14
16
  recipient: string;
15
17
  success: boolean;
16
18
  messageId: string;
@@ -1 +1 @@
1
- var e=this&&this.__decorate||function(e,t,i,r){var n,c=arguments.length,o=c<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var s=e.length-1;s>=0;s--)(n=e[s])&&(o=(c<3?n(o):c>3?n(t,i,o):n(t,i))||o);return c>3&&o&&Object.defineProperty(t,i,o),o},t=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},i=this&&this.__awaiter||function(e,t,i,r){return new(i||(i=Promise))(function(n,c){function fulfilled(e){try{step(r.next(e))}catch(e){c(e)}}function rejected(e){try{step(r.throw(e))}catch(e){c(e)}}function step(e){e.done?n(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((r=r.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.SmsService=void 0;const r=require("@nestjs/common"),n=require("@nestjs/config"),c=require("./notification.service");let o=class SmsService{constructor(e,t){this.configService=e,this.notificationService=t}send(e){return i(this,void 0,void 0,function*(){if(!this.configService.get("SMS_FROM"))throw new Error("SMS_FROM is not set");return console.log("📱 [SMS] 배열 발송:",{count:e.length,from:this.configService.get("SMS_FROM"),provider:this.configService.get("SMS_PROVIDER")||"default",recipients:e.map(e=>({user_id:e.user_id,recipient:e.recipient,message:e.message,variables:e.variables})),timestamp:(new Date).toISOString()}),e.map(e=>({user_id:e.user_id,recipient:e.recipient,success:!0,messageId:`sms-${Date.now()}-${e.user_id}`,status:"sent"}))})}};exports.SmsService=o,exports.SmsService=o=e([(0,r.Injectable)(),t("design:paramtypes",[n.ConfigService,c.NotificationService])],o);
1
+ var e=this&&this.__decorate||function(e,t,i,r){var n,c=arguments.length,o=c<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var s=e.length-1;s>=0;s--)(n=e[s])&&(o=(c<3?n(o):c>3?n(t,i,o):n(t,i))||o);return c>3&&o&&Object.defineProperty(t,i,o),o},t=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)},i=this&&this.__awaiter||function(e,t,i,r){return new(i||(i=Promise))(function(n,c){function fulfilled(e){try{step(r.next(e))}catch(e){c(e)}}function rejected(e){try{step(r.throw(e))}catch(e){c(e)}}function step(e){e.done?n(e.value):function adopt(e){return e instanceof i?e:new i(function(t){t(e)})}(e.value).then(fulfilled,rejected)}step((r=r.apply(e,t||[])).next())})};Object.defineProperty(exports,"__esModule",{value:!0}),exports.SmsService=void 0;const r=require("@nestjs/common"),n=require("@nestjs/config"),c=require("./notification.service");let o=class SmsService{constructor(e,t){this.configService=e,this.notificationService=t}send(e){return i(this,void 0,void 0,function*(){if(!this.configService.get("SMS_FROM"))throw new Error("SMS_FROM is not set");return console.log("📱 [SMS] 배열 발송:",{count:e.length,from:this.configService.get("SMS_FROM"),provider:this.configService.get("SMS_PROVIDER")||"default",recipients:e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,message:e.message,variables:e.variables})),timestamp:(new Date).toISOString()}),e.map(e=>({target_type:e.target_type,target_id:e.target_id,recipient:e.recipient,success:!0,messageId:`sms-${Date.now()}-${e.target_id}`,status:"sent"}))})}};exports.SmsService=o,exports.SmsService=o=e([(0,r.Injectable)(),t("design:paramtypes",[n.ConfigService,c.NotificationService])],o);
@@ -1 +1 @@
1
- function validateAndParseDate(e){if(!/^\d{8}T\d{6}Z$/.test(e))throw new Error("Invalid date format. Use YYYYMMDDTHHMMSSZ (e.g., 20250122T150000Z)");const n=e.substring(0,4),t=e.substring(4,6),r=e.substring(6,8),i=e.substring(9,11),a=e.substring(11,13),s=e.substring(13,15),l=new Date(`${n}-${t}-${r}T${i}:${a}:${s}Z`);if(isNaN(l.getTime()))throw new Error("Invalid date");return l}Object.defineProperty(exports,"__esModule",{value:!0}),exports.applyFiltersAndSorting=function applyFiltersAndSorting(n,t){const{_keyword:r,_group:i,_sort:a,aliasName:s="a",keywordFields:l=["name"],groupFields:$=[]}=t;return function applyKeywordFilter(e,n,t,r){if(n){const i=[],a={};r.length>0&&(r.forEach((e,r)=>{if(e.endsWith("*")){const s=e.slice(0,-1);i.push(`${t}.${s} = :keyword${r}`),a[`keyword${r}`]=n}else i.push(`${t}.${e} LIKE :keyword${r}`),a[`keyword${r}`]=`%${n}%`}),e.andWhere(`(${i.join(" OR ")})`,a))}}(n,r,s,l),function applyGroupFilter(n,t,r,i){if(!t)return;if(0===i.length)return;const a=t.split("|");if(a.some(e=>{const[n]=e.split("-");return!i.includes(n)}))throw new Error("Invalid field detected in group filter");const s=[],l={};a.forEach((n,t)=>{const[i,a]=n.split(/-(.+)/),$=i.includes(".")?i:`${r}.${i}`;let o;o=a.includes("__")?i.includes("_at")||i.includes("_date")?e.handleDateRange($,a,t):e.handleNumberRange($,a,t):e.handleEquals($,a,t),o.condition&&(s.push(o.condition),Object.assign(l,o.parameters))}),s.length>0&&n.andWhere(`(${s.join(" AND ")})`,l)}(n,i,s,$),function applySorting(e,n,t){if(n){n.split("|").forEach((n,r)=>{const[i,a]=n.split("-"),s=a.toUpperCase();if("ASC"!==s&&"DESC"!==s)throw new Error(`Invalid sort order: ${a}`);0===r?e.orderBy(`${t}.${i}`,s):e.addOrderBy(`${t}.${i}`,s)})}else{Object.keys(e.expressionMap.orderBys).length>0||e.orderBy(`${t}.id`,"DESC")}}(n,a,s),n};const e={handleEquals(e,n,t){if(n.includes(",")){const r=n.split(",").map(e=>e.trim()).filter(e=>e);return{condition:`${e} IN (:...${e}${t})`,parameters:{[`${e}${t}`]:r}}}return{condition:`${e} = :${e}${t}`,parameters:{[`${e}${t}`]:n}}},handleNumberRange(e,n,t){const[r,i]=n.split("__").map(e=>""===e?null:Number(e)),a=[],s={};return null!==r&&null!==i?(a.push(`${e} BETWEEN :${e}Min${t} AND :${e}Max${t}`),s[`${e}Min${t}`]=r,s[`${e}Max${t}`]=i):null!==r?(a.push(`${e} >= :${e}Min${t}`),s[`${e}Min${t}`]=r):null!==i&&(a.push(`${e} <= :${e}Max${t}`),s[`${e}Max${t}`]=i),{condition:a.join(" AND "),parameters:s}},handleDateRange(e,n,t){const[r,i]=n.split("__").map(e=>""===e?null:e),a=[],s={};return null!==r&&null!==i?(a.push(`${e} BETWEEN :${e}Min${t} AND :${e}Max${t}`),s[`${e}Min${t}`]=validateAndParseDate(r),s[`${e}Max${t}`]=validateAndParseDate(i)):null!==r?(a.push(`${e} >= :${e}Min${t}`),s[`${e}Min${t}`]=validateAndParseDate(r)):null!==i&&(a.push(`${e} <= :${e}Max${t}`),s[`${e}Max${t}`]=validateAndParseDate(i)),{condition:a.join(" AND "),parameters:s}}};
1
+ function validateAndParseDate(e){if(!/^\d{8}T\d{6}Z$/.test(e))throw new Error("Invalid date format. Use YYYYMMDDTHHMMSSZ (e.g., 20250122T150000Z)");const n=e.substring(0,4),t=e.substring(4,6),r=e.substring(6,8),i=e.substring(9,11),s=e.substring(11,13),a=e.substring(13,15),l=new Date(`${n}-${t}-${r}T${i}:${s}:${a}Z`);if(isNaN(l.getTime()))throw new Error("Invalid date");return l}Object.defineProperty(exports,"__esModule",{value:!0}),exports.applyFiltersAndSorting=function applyFiltersAndSorting(n,t){const{_keyword:r,_group:i,_sort:s,aliasName:a="a",keywordFields:l=["name"],groupFields:$=[]}=t;return function applyKeywordFilter(e,n,t,r){if(n){const i=[],s={};r.length>0&&(r.forEach((e,r)=>{if(e.endsWith("*")){const a=e.slice(0,-1),l=a.includes(".")?a:`${t}.${a}`;i.push(`${l} = :keyword${r}`),s[`keyword${r}`]=n}else{const a=e.includes(".")?e:`${t}.${e}`;i.push(`${a} ILIKE :keyword${r}`),s[`keyword${r}`]=`%${n}%`}}),e.andWhere(`(${i.join(" OR ")})`,s))}}(n,r,a,l),function applyGroupFilter(n,t,r,i){if(!t)return;if(0===i.length)return;const s=t.split("|");if(s.some(e=>{const[n]=e.split("-");return!i.includes(n)}))throw new Error("Invalid field detected in group filter");const a=[],l={};s.forEach((n,t)=>{const[i,s]=n.split(/-(.+)/),$=i.includes(".")?i:`${r}.${i}`;let o;o=s.includes("__")?i.includes("_at")||i.includes("_date")?e.handleDateRange($,s,t):e.handleNumberRange($,s,t):e.handleEquals($,s,t),o.condition&&(a.push(o.condition),Object.assign(l,o.parameters))}),a.length>0&&n.andWhere(`(${a.join(" AND ")})`,l)}(n,i,a,$),function applySorting(e,n,t){if(n){n.split("|").forEach((n,r)=>{const[i,s]=n.split("-"),a=s.toUpperCase();if("ASC"!==a&&"DESC"!==a)throw new Error(`Invalid sort order: ${s}`);0===r?e.orderBy(`${t}.${i}`,a):e.addOrderBy(`${t}.${i}`,a)})}else{Object.keys(e.expressionMap.orderBys).length>0||e.orderBy(`${t}.id`,"DESC")}}(n,s,a),n};const e={handleEquals(e,n,t){if(n.includes(",")){const r=n.split(",").map(e=>e.trim()).filter(e=>e);return{condition:`${e} IN (:...${e}${t})`,parameters:{[`${e}${t}`]:r}}}return{condition:`${e} = :${e}${t}`,parameters:{[`${e}${t}`]:n}}},handleNumberRange(e,n,t){const[r,i]=n.split("__").map(e=>""===e?null:Number(e)),s=[],a={};return null!==r&&null!==i?(s.push(`${e} BETWEEN :${e}Min${t} AND :${e}Max${t}`),a[`${e}Min${t}`]=r,a[`${e}Max${t}`]=i):null!==r?(s.push(`${e} >= :${e}Min${t}`),a[`${e}Min${t}`]=r):null!==i&&(s.push(`${e} <= :${e}Max${t}`),a[`${e}Max${t}`]=i),{condition:s.join(" AND "),parameters:a}},handleDateRange(e,n,t){const[r,i]=n.split("__").map(e=>""===e?null:e),s=[],a={};return null!==r&&null!==i?(s.push(`${e} BETWEEN :${e}Min${t} AND :${e}Max${t}`),a[`${e}Min${t}`]=validateAndParseDate(r),a[`${e}Max${t}`]=validateAndParseDate(i)):null!==r?(s.push(`${e} >= :${e}Min${t}`),a[`${e}Min${t}`]=validateAndParseDate(r)):null!==i&&(s.push(`${e} <= :${e}Max${t}`),a[`${e}Max${t}`]=validateAndParseDate(i)),{condition:s.join(" AND "),parameters:a}}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comasoft/nestjs",
3
- "version": "0.2.28",
3
+ "version": "0.2.30",
4
4
  "description": "",
5
5
  "private": false,
6
6
  "exports": {