mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 21:36:41 +02:00
✨ feat(mana-notify): add central notification service
NestJS notification microservice for email, push, Matrix, and webhook notifications across all ManaCore apps. Features: - Multi-channel delivery (email, push, Matrix, webhook) - Handlebars template engine with defaults - User notification preferences - BullMQ async job processing - Delivery tracking and logging - Prometheus metrics Includes @manacore/notify-client package for NestJS integration.
This commit is contained in:
parent
1495dbe476
commit
b5fa0f42b6
66 changed files with 4824 additions and 0 deletions
2
packages/notify-client/.eslintignore
Normal file
2
packages/notify-client/.eslintignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
tsup.config.ts
|
||||
dist/
|
||||
149
packages/notify-client/README.md
Normal file
149
packages/notify-client/README.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# @manacore/notify-client
|
||||
|
||||
Client SDK for the mana-notify notification service.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm add @manacore/notify-client
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { NotifyClient } from '@manacore/notify-client';
|
||||
|
||||
const notify = new NotifyClient({
|
||||
serviceUrl: 'http://localhost:3040',
|
||||
serviceKey: process.env.MANA_NOTIFY_SERVICE_KEY,
|
||||
appId: 'your-app-id',
|
||||
});
|
||||
|
||||
// Send email
|
||||
await notify.sendEmail({
|
||||
to: 'user@example.com',
|
||||
template: 'auth-password-reset',
|
||||
data: { resetUrl: 'https://...', userName: 'Max' },
|
||||
});
|
||||
|
||||
// Send push notification
|
||||
await notify.sendPush({
|
||||
userId: 'user-uuid',
|
||||
title: 'New Message',
|
||||
body: 'You have a new message',
|
||||
data: { messageId: 'xxx' },
|
||||
});
|
||||
|
||||
// Send to specific token
|
||||
await notify.sendPush({
|
||||
token: 'ExponentPushToken[xxx]',
|
||||
title: 'Hello',
|
||||
body: 'World',
|
||||
});
|
||||
|
||||
// Schedule notification
|
||||
await notify.scheduleEmail({
|
||||
to: 'user@example.com',
|
||||
template: 'calendar-reminder',
|
||||
data: { eventTitle: 'Meeting' },
|
||||
scheduledFor: new Date('2024-12-20T13:45:00Z'),
|
||||
});
|
||||
```
|
||||
|
||||
### NestJS Integration
|
||||
|
||||
```typescript
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { NotifyModule } from '@manacore/notify-client/nestjs';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
NotifyModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
serviceUrl: config.get('MANA_NOTIFY_URL', 'http://localhost:3040'),
|
||||
serviceKey: config.get('MANA_NOTIFY_SERVICE_KEY'),
|
||||
appId: config.get('APP_ID'),
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
Then inject the client:
|
||||
|
||||
```typescript
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { NOTIFY_CLIENT, NotifyClient } from '@manacore/notify-client/nestjs';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationService {
|
||||
constructor(@Inject(NOTIFY_CLIENT) private readonly notify: NotifyClient) {}
|
||||
|
||||
async sendWelcomeEmail(email: string, name: string) {
|
||||
await this.notify.sendEmail({
|
||||
to: email,
|
||||
template: 'auth-welcome',
|
||||
data: { userName: name },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### NotifyClient
|
||||
|
||||
#### Constructor
|
||||
|
||||
```typescript
|
||||
new NotifyClient({
|
||||
serviceUrl: string; // mana-notify service URL
|
||||
serviceKey: string; // Service authentication key
|
||||
appId: string; // Your application ID
|
||||
timeout?: number; // Request timeout in ms (default: 30000)
|
||||
});
|
||||
```
|
||||
|
||||
#### Methods
|
||||
|
||||
##### Email
|
||||
|
||||
- `sendEmail(options)` - Send an email immediately
|
||||
- `scheduleEmail(options)` - Schedule an email for later
|
||||
|
||||
##### Push Notifications
|
||||
|
||||
- `sendPush(options)` - Send a push notification
|
||||
- `schedulePush(options)` - Schedule a push notification
|
||||
|
||||
##### Other Channels
|
||||
|
||||
- `sendMatrix(options)` - Send a Matrix message
|
||||
- `sendWebhook(options)` - Send a webhook
|
||||
|
||||
##### Batch & Management
|
||||
|
||||
- `sendBatch(notifications)` - Send multiple notifications
|
||||
- `getNotification(id)` - Get notification status
|
||||
- `cancelNotification(id)` - Cancel a pending notification
|
||||
|
||||
##### Templates
|
||||
|
||||
- `listTemplates(appId?)` - List available templates
|
||||
- `getTemplate(slug, locale?)` - Get a template
|
||||
- `previewTemplate(slug, data, locale?)` - Preview a rendered template
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `MANA_NOTIFY_URL` | mana-notify service URL |
|
||||
| `MANA_NOTIFY_SERVICE_KEY` | Service authentication key |
|
||||
| `APP_ID` | Your application ID |
|
||||
44
packages/notify-client/package.json
Normal file
44
packages/notify-client/package.json
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "@manacore/notify-client",
|
||||
"version": "1.0.0",
|
||||
"description": "Client SDK for mana-notify notification service",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./nestjs": {
|
||||
"types": "./dist/nestjs/index.d.ts",
|
||||
"import": "./dist/nestjs/index.mjs",
|
||||
"require": "./dist/nestjs/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@nestjs/common": "^10.4.17",
|
||||
"@types/node": "^22.10.5",
|
||||
"tsup": "^8.3.5",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": ">=10.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nestjs/common": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
354
packages/notify-client/src/client.ts
Normal file
354
packages/notify-client/src/client.ts
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
import type {
|
||||
SendEmailOptions,
|
||||
SendPushOptions,
|
||||
SendMatrixOptions,
|
||||
SendWebhookOptions,
|
||||
ScheduleOptions,
|
||||
NotificationResponse,
|
||||
BatchNotificationResponse,
|
||||
Template,
|
||||
RenderedTemplate,
|
||||
} from './types';
|
||||
|
||||
export interface NotifyClientOptions {
|
||||
serviceUrl: string;
|
||||
serviceKey: string;
|
||||
appId: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export class NotifyClient {
|
||||
private readonly serviceUrl: string;
|
||||
private readonly serviceKey: string;
|
||||
private readonly appId: string;
|
||||
private readonly timeout: number;
|
||||
|
||||
constructor(options: NotifyClientOptions) {
|
||||
this.serviceUrl = options.serviceUrl.replace(/\/$/, '');
|
||||
this.serviceKey = options.serviceKey;
|
||||
this.appId = options.appId;
|
||||
this.timeout = options.timeout || 30000;
|
||||
}
|
||||
|
||||
// ==================== Notifications ====================
|
||||
|
||||
/**
|
||||
* Send an email notification
|
||||
*/
|
||||
async sendEmail(options: SendEmailOptions): Promise<NotificationResponse> {
|
||||
return this.send({
|
||||
channel: 'email',
|
||||
appId: this.appId,
|
||||
recipient: options.to,
|
||||
template: options.template,
|
||||
subject: options.subject,
|
||||
body: options.body,
|
||||
data: options.data,
|
||||
emailOptions: {
|
||||
from: options.from,
|
||||
replyTo: options.replyTo,
|
||||
},
|
||||
priority: options.priority,
|
||||
externalId: options.externalId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a push notification
|
||||
*/
|
||||
async sendPush(options: SendPushOptions): Promise<NotificationResponse> {
|
||||
return this.send({
|
||||
channel: 'push',
|
||||
appId: this.appId,
|
||||
userId: options.userId,
|
||||
recipient: options.token,
|
||||
recipients: options.tokens,
|
||||
subject: options.title,
|
||||
body: options.body,
|
||||
data: options.data,
|
||||
pushOptions: {
|
||||
sound: options.sound,
|
||||
badge: options.badge,
|
||||
channelId: options.channelId,
|
||||
},
|
||||
priority: options.priority,
|
||||
externalId: options.externalId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Matrix message
|
||||
*/
|
||||
async sendMatrix(options: SendMatrixOptions): Promise<NotificationResponse> {
|
||||
return this.send({
|
||||
channel: 'matrix',
|
||||
appId: this.appId,
|
||||
recipient: options.roomId,
|
||||
body: options.body,
|
||||
matrixOptions: {
|
||||
formattedBody: options.formattedBody,
|
||||
msgtype: options.msgtype,
|
||||
},
|
||||
priority: options.priority,
|
||||
externalId: options.externalId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a webhook notification
|
||||
*/
|
||||
async sendWebhook(options: SendWebhookOptions): Promise<NotificationResponse> {
|
||||
return this.send({
|
||||
channel: 'webhook',
|
||||
appId: this.appId,
|
||||
recipient: options.url,
|
||||
data: options.body,
|
||||
webhookOptions: {
|
||||
method: options.method,
|
||||
headers: options.headers,
|
||||
timeout: options.timeout,
|
||||
},
|
||||
priority: options.priority,
|
||||
externalId: options.externalId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an email notification
|
||||
*/
|
||||
async scheduleEmail(options: SendEmailOptions & ScheduleOptions): Promise<NotificationResponse> {
|
||||
return this.schedule({
|
||||
channel: 'email',
|
||||
appId: this.appId,
|
||||
recipient: options.to,
|
||||
template: options.template,
|
||||
subject: options.subject,
|
||||
body: options.body,
|
||||
data: options.data,
|
||||
emailOptions: {
|
||||
from: options.from,
|
||||
replyTo: options.replyTo,
|
||||
},
|
||||
priority: options.priority,
|
||||
externalId: options.externalId,
|
||||
scheduledFor:
|
||||
options.scheduledFor instanceof Date
|
||||
? options.scheduledFor.toISOString()
|
||||
: options.scheduledFor,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a push notification
|
||||
*/
|
||||
async schedulePush(options: SendPushOptions & ScheduleOptions): Promise<NotificationResponse> {
|
||||
return this.schedule({
|
||||
channel: 'push',
|
||||
appId: this.appId,
|
||||
userId: options.userId,
|
||||
recipient: options.token,
|
||||
recipients: options.tokens,
|
||||
subject: options.title,
|
||||
body: options.body,
|
||||
data: options.data,
|
||||
pushOptions: {
|
||||
sound: options.sound,
|
||||
badge: options.badge,
|
||||
channelId: options.channelId,
|
||||
},
|
||||
priority: options.priority,
|
||||
externalId: options.externalId,
|
||||
scheduledFor:
|
||||
options.scheduledFor instanceof Date
|
||||
? options.scheduledFor.toISOString()
|
||||
: options.scheduledFor,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send multiple notifications in batch
|
||||
*/
|
||||
async sendBatch(
|
||||
notifications: Array<
|
||||
| ({ type: 'email' } & SendEmailOptions)
|
||||
| ({ type: 'push' } & SendPushOptions)
|
||||
| ({ type: 'matrix' } & SendMatrixOptions)
|
||||
| ({ type: 'webhook' } & SendWebhookOptions)
|
||||
>
|
||||
): Promise<BatchNotificationResponse> {
|
||||
const mapped = notifications.map((n) => {
|
||||
if (n.type === 'email') {
|
||||
return {
|
||||
channel: 'email' as const,
|
||||
appId: this.appId,
|
||||
recipient: n.to,
|
||||
template: n.template,
|
||||
subject: n.subject,
|
||||
body: n.body,
|
||||
data: n.data,
|
||||
priority: n.priority,
|
||||
externalId: n.externalId,
|
||||
};
|
||||
} else if (n.type === 'push') {
|
||||
return {
|
||||
channel: 'push' as const,
|
||||
appId: this.appId,
|
||||
userId: n.userId,
|
||||
recipient: n.token,
|
||||
recipients: n.tokens,
|
||||
subject: n.title,
|
||||
body: n.body,
|
||||
data: n.data,
|
||||
priority: n.priority,
|
||||
externalId: n.externalId,
|
||||
};
|
||||
} else if (n.type === 'matrix') {
|
||||
return {
|
||||
channel: 'matrix' as const,
|
||||
appId: this.appId,
|
||||
recipient: n.roomId,
|
||||
body: n.body,
|
||||
priority: n.priority,
|
||||
externalId: n.externalId,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
channel: 'webhook' as const,
|
||||
appId: this.appId,
|
||||
recipient: n.url,
|
||||
data: n.body,
|
||||
priority: n.priority,
|
||||
externalId: n.externalId,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const response = await this.request<BatchNotificationResponse>('/notifications/batch', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ notifications: mapped }),
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification status
|
||||
*/
|
||||
async getNotification(id: string): Promise<NotificationResponse | null> {
|
||||
const response = await this.request<{ notification: NotificationResponse | null }>(
|
||||
`/notifications/${id}`
|
||||
);
|
||||
return response.notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a pending notification
|
||||
*/
|
||||
async cancelNotification(id: string): Promise<NotificationResponse> {
|
||||
const response = await this.request<{ notification: NotificationResponse }>(
|
||||
`/notifications/${id}`,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
return response.notification;
|
||||
}
|
||||
|
||||
// ==================== Templates ====================
|
||||
|
||||
/**
|
||||
* List all templates
|
||||
*/
|
||||
async listTemplates(appId?: string): Promise<Template[]> {
|
||||
const url = appId ? `/templates?appId=${encodeURIComponent(appId)}` : '/templates';
|
||||
const response = await this.request<{ templates: Template[] }>(url);
|
||||
return response.templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a template by slug
|
||||
*/
|
||||
async getTemplate(slug: string, locale = 'de-DE'): Promise<Template | null> {
|
||||
const response = await this.request<{ template: Template | null }>(
|
||||
`/templates/${encodeURIComponent(slug)}?locale=${encodeURIComponent(locale)}`
|
||||
);
|
||||
return response.template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview a template with data
|
||||
*/
|
||||
async previewTemplate(
|
||||
slug: string,
|
||||
data: Record<string, unknown>,
|
||||
locale = 'de-DE'
|
||||
): Promise<RenderedTemplate | null> {
|
||||
const response = await this.request<{ preview: RenderedTemplate | null }>(
|
||||
`/templates/${encodeURIComponent(slug)}/preview?locale=${encodeURIComponent(locale)}`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ data }),
|
||||
}
|
||||
);
|
||||
return response.preview;
|
||||
}
|
||||
|
||||
// ==================== Private Methods ====================
|
||||
|
||||
private async send(payload: Record<string, unknown>): Promise<NotificationResponse> {
|
||||
const response = await this.request<{ notification: NotificationResponse }>(
|
||||
'/notifications/send',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
}
|
||||
);
|
||||
return response.notification;
|
||||
}
|
||||
|
||||
private async schedule(payload: Record<string, unknown>): Promise<NotificationResponse> {
|
||||
const response = await this.request<{ notification: NotificationResponse }>(
|
||||
'/notifications/schedule',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
}
|
||||
);
|
||||
return response.notification;
|
||||
}
|
||||
|
||||
private async request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const url = `${this.serviceUrl}/api/v1${path}`;
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Service-Key': this.serviceKey,
|
||||
...options.headers,
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
const message =
|
||||
(errorData as { error?: { message?: string } }).error?.message ||
|
||||
`HTTP ${response.status}`;
|
||||
throw new Error(`NotifyClient error: ${message}`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw new Error('NotifyClient error: Request timeout');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
packages/notify-client/src/index.ts
Normal file
2
packages/notify-client/src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { NotifyClient, NotifyClientOptions } from './client';
|
||||
export * from './types';
|
||||
2
packages/notify-client/src/nestjs/constants.ts
Normal file
2
packages/notify-client/src/nestjs/constants.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export const NOTIFY_CLIENT = 'NOTIFY_CLIENT';
|
||||
export const NOTIFY_MODULE_OPTIONS = 'NOTIFY_MODULE_OPTIONS';
|
||||
2
packages/notify-client/src/nestjs/index.ts
Normal file
2
packages/notify-client/src/nestjs/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { NotifyModule, NotifyModuleOptions, NotifyModuleAsyncOptions } from './notify.module';
|
||||
export { NOTIFY_CLIENT } from './constants';
|
||||
106
packages/notify-client/src/nestjs/notify.module.ts
Normal file
106
packages/notify-client/src/nestjs/notify.module.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import {
|
||||
type DynamicModule,
|
||||
Module,
|
||||
type Provider,
|
||||
type Type,
|
||||
type InjectionToken,
|
||||
type OptionalFactoryDependency,
|
||||
} from '@nestjs/common';
|
||||
import { NotifyClient, type NotifyClientOptions } from '../client';
|
||||
import { NOTIFY_CLIENT, NOTIFY_MODULE_OPTIONS } from './constants';
|
||||
|
||||
export type NotifyModuleOptions = NotifyClientOptions;
|
||||
|
||||
export interface NotifyModuleAsyncOptions {
|
||||
imports?: DynamicModule[];
|
||||
useFactory?: (...args: unknown[]) => Promise<NotifyModuleOptions> | NotifyModuleOptions;
|
||||
inject?: (InjectionToken | OptionalFactoryDependency)[];
|
||||
useClass?: Type<NotifyModuleOptionsFactory>;
|
||||
useExisting?: Type<NotifyModuleOptionsFactory>;
|
||||
}
|
||||
|
||||
export interface NotifyModuleOptionsFactory {
|
||||
createNotifyOptions(): Promise<NotifyModuleOptions> | NotifyModuleOptions;
|
||||
}
|
||||
|
||||
@Module({})
|
||||
export class NotifyModule {
|
||||
/**
|
||||
* Register the module with static options
|
||||
*/
|
||||
static forRoot(options: NotifyModuleOptions): DynamicModule {
|
||||
const clientProvider: Provider = {
|
||||
provide: NOTIFY_CLIENT,
|
||||
useValue: new NotifyClient(options),
|
||||
};
|
||||
|
||||
return {
|
||||
module: NotifyModule,
|
||||
global: true,
|
||||
providers: [clientProvider],
|
||||
exports: [clientProvider],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the module with async options (e.g., from ConfigService)
|
||||
*/
|
||||
static forRootAsync(options: NotifyModuleAsyncOptions): DynamicModule {
|
||||
const providers = this.createAsyncProviders(options);
|
||||
|
||||
return {
|
||||
module: NotifyModule,
|
||||
global: true,
|
||||
imports: options.imports || [],
|
||||
providers: [
|
||||
...providers,
|
||||
{
|
||||
provide: NOTIFY_CLIENT,
|
||||
useFactory: (opts: NotifyModuleOptions) => new NotifyClient(opts),
|
||||
inject: [NOTIFY_MODULE_OPTIONS],
|
||||
},
|
||||
],
|
||||
exports: [NOTIFY_CLIENT],
|
||||
};
|
||||
}
|
||||
|
||||
private static createAsyncProviders(options: NotifyModuleAsyncOptions): Provider[] {
|
||||
if (options.useFactory) {
|
||||
return [
|
||||
{
|
||||
provide: NOTIFY_MODULE_OPTIONS,
|
||||
useFactory: options.useFactory,
|
||||
inject: options.inject || [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (options.useClass) {
|
||||
return [
|
||||
{
|
||||
provide: NOTIFY_MODULE_OPTIONS,
|
||||
useFactory: async (optionsFactory: NotifyModuleOptionsFactory) =>
|
||||
optionsFactory.createNotifyOptions(),
|
||||
inject: [options.useClass],
|
||||
},
|
||||
{
|
||||
provide: options.useClass,
|
||||
useClass: options.useClass,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (options.useExisting) {
|
||||
return [
|
||||
{
|
||||
provide: NOTIFY_MODULE_OPTIONS,
|
||||
useFactory: async (optionsFactory: NotifyModuleOptionsFactory) =>
|
||||
optionsFactory.createNotifyOptions(),
|
||||
inject: [options.useExisting],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
104
packages/notify-client/src/types.ts
Normal file
104
packages/notify-client/src/types.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
export type NotificationChannel = 'email' | 'push' | 'matrix' | 'webhook';
|
||||
export type NotificationPriority = 'low' | 'normal' | 'high' | 'critical';
|
||||
export type NotificationStatus = 'pending' | 'processing' | 'delivered' | 'failed' | 'cancelled';
|
||||
|
||||
export interface SendEmailOptions {
|
||||
to: string;
|
||||
template?: string;
|
||||
subject?: string;
|
||||
body?: string;
|
||||
data?: Record<string, unknown>;
|
||||
from?: string;
|
||||
replyTo?: string;
|
||||
priority?: NotificationPriority;
|
||||
externalId?: string;
|
||||
}
|
||||
|
||||
export interface SendPushOptions {
|
||||
userId?: string;
|
||||
token?: string;
|
||||
tokens?: string[];
|
||||
title: string;
|
||||
body: string;
|
||||
data?: Record<string, unknown>;
|
||||
sound?: 'default' | null;
|
||||
badge?: number;
|
||||
channelId?: string;
|
||||
priority?: NotificationPriority;
|
||||
externalId?: string;
|
||||
}
|
||||
|
||||
export interface SendMatrixOptions {
|
||||
roomId: string;
|
||||
body: string;
|
||||
formattedBody?: string;
|
||||
msgtype?: 'text' | 'notice';
|
||||
priority?: NotificationPriority;
|
||||
externalId?: string;
|
||||
}
|
||||
|
||||
export interface SendWebhookOptions {
|
||||
url: string;
|
||||
method?: 'POST' | 'PUT';
|
||||
headers?: Record<string, string>;
|
||||
body: Record<string, unknown>;
|
||||
timeout?: number;
|
||||
priority?: NotificationPriority;
|
||||
externalId?: string;
|
||||
}
|
||||
|
||||
export interface ScheduleOptions {
|
||||
scheduledFor: Date | string;
|
||||
}
|
||||
|
||||
export interface NotificationResponse {
|
||||
id: string;
|
||||
status: NotificationStatus;
|
||||
channel: NotificationChannel;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface BatchNotificationResponse {
|
||||
results: NotificationResponse[];
|
||||
succeeded: number;
|
||||
failed: number;
|
||||
}
|
||||
|
||||
export interface Template {
|
||||
id: string;
|
||||
slug: string;
|
||||
channel: NotificationChannel;
|
||||
subject?: string;
|
||||
bodyTemplate: string;
|
||||
locale: string;
|
||||
isActive: boolean;
|
||||
isSystem: boolean;
|
||||
variables?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface RenderedTemplate {
|
||||
subject: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface Device {
|
||||
id: string;
|
||||
userId: string;
|
||||
pushToken: string;
|
||||
tokenType: string;
|
||||
platform: string;
|
||||
deviceName?: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface Preferences {
|
||||
id: string;
|
||||
userId: string;
|
||||
emailEnabled: boolean;
|
||||
pushEnabled: boolean;
|
||||
quietHoursEnabled: boolean;
|
||||
quietHoursStart?: string;
|
||||
quietHoursEnd?: string;
|
||||
timezone: string;
|
||||
categoryPreferences?: Record<string, Record<string, boolean>>;
|
||||
}
|
||||
17
packages/notify-client/tsconfig.json
Normal file
17
packages/notify-client/tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
15
packages/notify-client/tsup.config.ts
Normal file
15
packages/notify-client/tsup.config.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* eslint-disable */
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
'nestjs/index': 'src/nestjs/index.ts',
|
||||
},
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
external: ['@nestjs/common', '@nestjs/core'],
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue