saas-backend-kit 1.0.0
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.
- package/CHANGELOG.md +31 -0
- package/PUBLISHING.md +133 -0
- package/README.md +459 -0
- package/copy-dts.js +255 -0
- package/dist/auth/index.d.ts +58 -0
- package/dist/auth/index.js +584 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/index.mjs +569 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/config/index.d.ts +22 -0
- package/dist/config/index.js +106 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +100 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +1303 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1281 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger/index.d.ts +18 -0
- package/dist/logger/index.js +188 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/index.mjs +178 -0
- package/dist/logger/index.mjs.map +1 -0
- package/dist/notifications/index.d.ts +35 -0
- package/dist/notifications/index.js +339 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/index.mjs +328 -0
- package/dist/notifications/index.mjs.map +1 -0
- package/dist/queue/index.d.ts +33 -0
- package/dist/queue/index.js +306 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/index.mjs +293 -0
- package/dist/queue/index.mjs.map +1 -0
- package/dist/rate-limit/index.d.ts +11 -0
- package/dist/rate-limit/index.js +290 -0
- package/dist/rate-limit/index.js.map +1 -0
- package/dist/rate-limit/index.mjs +286 -0
- package/dist/rate-limit/index.mjs.map +1 -0
- package/dist/response/index.d.ts +29 -0
- package/dist/response/index.js +120 -0
- package/dist/response/index.js.map +1 -0
- package/dist/response/index.mjs +114 -0
- package/dist/response/index.mjs.map +1 -0
- package/examples/express/.env.example +41 -0
- package/examples/express/app.ts +203 -0
- package/package.json +109 -0
- package/src/auth/express.ts +250 -0
- package/src/auth/fastify.ts +65 -0
- package/src/auth/index.ts +6 -0
- package/src/auth/jwt.ts +47 -0
- package/src/auth/oauth.ts +117 -0
- package/src/auth/rbac.ts +82 -0
- package/src/auth/types.ts +69 -0
- package/src/config/index.ts +120 -0
- package/src/index.ts +16 -0
- package/src/logger/index.ts +110 -0
- package/src/notifications/index.ts +262 -0
- package/src/plugin.ts +192 -0
- package/src/queue/index.ts +208 -0
- package/src/rate-limit/express.ts +144 -0
- package/src/rate-limit/fastify.ts +47 -0
- package/src/rate-limit/index.ts +2 -0
- package/src/response/index.ts +197 -0
- package/src/utils/index.ts +180 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +24 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
// src/logger/index.ts
|
|
5
|
+
var envSchema = z.object({
|
|
6
|
+
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
|
7
|
+
PORT: z.string().default("3000"),
|
|
8
|
+
DATABASE_URL: z.string().optional(),
|
|
9
|
+
REDIS_URL: z.string().default("redis://localhost:6379"),
|
|
10
|
+
JWT_SECRET: z.string().min(32).optional(),
|
|
11
|
+
JWT_EXPIRES_IN: z.string().default("7d"),
|
|
12
|
+
JWT_REFRESH_SECRET: z.string().min(32).optional(),
|
|
13
|
+
JWT_REFRESH_EXPIRES_IN: z.string().default("30d"),
|
|
14
|
+
GOOGLE_CLIENT_ID: z.string().optional(),
|
|
15
|
+
GOOGLE_CLIENT_SECRET: z.string().optional(),
|
|
16
|
+
GOOGLE_REDIRECT_URI: z.string().optional(),
|
|
17
|
+
SMTP_HOST: z.string().optional(),
|
|
18
|
+
SMTP_PORT: z.string().default("587"),
|
|
19
|
+
SMTP_USER: z.string().optional(),
|
|
20
|
+
SMTP_PASS: z.string().optional(),
|
|
21
|
+
SMTP_FROM: z.string().optional(),
|
|
22
|
+
TWILIO_ACCOUNT_SID: z.string().optional(),
|
|
23
|
+
TWILIO_AUTH_TOKEN: z.string().optional(),
|
|
24
|
+
TWILIO_PHONE_NUMBER: z.string().optional(),
|
|
25
|
+
SLACK_WEBHOOK_URL: z.string().optional(),
|
|
26
|
+
RATE_LIMIT_WINDOW: z.string().default("1m"),
|
|
27
|
+
RATE_LIMIT_LIMIT: z.string().default("100"),
|
|
28
|
+
LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info")
|
|
29
|
+
});
|
|
30
|
+
var ConfigManager = class {
|
|
31
|
+
config = null;
|
|
32
|
+
schema;
|
|
33
|
+
validate;
|
|
34
|
+
constructor(options = {}) {
|
|
35
|
+
this.schema = options.schema || envSchema;
|
|
36
|
+
this.validate = options.validate ?? true;
|
|
37
|
+
}
|
|
38
|
+
load() {
|
|
39
|
+
if (this.config) return this.config;
|
|
40
|
+
const env = {};
|
|
41
|
+
for (const key of Object.keys(this.schema.shape)) {
|
|
42
|
+
env[key] = process.env[key];
|
|
43
|
+
}
|
|
44
|
+
if (this.validate) {
|
|
45
|
+
const result = this.schema.safeParse(env);
|
|
46
|
+
if (!result.success) {
|
|
47
|
+
const errors = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
48
|
+
throw new Error(`Config validation failed: ${errors}`);
|
|
49
|
+
}
|
|
50
|
+
this.config = result.data;
|
|
51
|
+
} else {
|
|
52
|
+
this.config = env;
|
|
53
|
+
}
|
|
54
|
+
return this.config;
|
|
55
|
+
}
|
|
56
|
+
get(key) {
|
|
57
|
+
if (!this.config) this.load();
|
|
58
|
+
return this.config[key];
|
|
59
|
+
}
|
|
60
|
+
int(key) {
|
|
61
|
+
const value = this.get(key);
|
|
62
|
+
if (typeof value === "string") return parseInt(value, 10);
|
|
63
|
+
return Number(value);
|
|
64
|
+
}
|
|
65
|
+
bool(key) {
|
|
66
|
+
const value = this.get(key);
|
|
67
|
+
if (typeof value === "boolean") return value;
|
|
68
|
+
if (typeof value === "string") return value.toLowerCase() === "true";
|
|
69
|
+
return Boolean(value);
|
|
70
|
+
}
|
|
71
|
+
isProduction() {
|
|
72
|
+
return this.get("NODE_ENV") === "production";
|
|
73
|
+
}
|
|
74
|
+
isDevelopment() {
|
|
75
|
+
return this.get("NODE_ENV") === "development";
|
|
76
|
+
}
|
|
77
|
+
isTest() {
|
|
78
|
+
return this.get("NODE_ENV") === "test";
|
|
79
|
+
}
|
|
80
|
+
getAll() {
|
|
81
|
+
if (!this.config) this.load();
|
|
82
|
+
return this.config;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var globalConfig = new ConfigManager();
|
|
86
|
+
var config = {
|
|
87
|
+
load: () => globalConfig.load(),
|
|
88
|
+
get: (key) => globalConfig.get(key),
|
|
89
|
+
int: (key) => globalConfig.int(key),
|
|
90
|
+
bool: (key) => globalConfig.bool(key),
|
|
91
|
+
isProduction: () => globalConfig.isProduction(),
|
|
92
|
+
isDevelopment: () => globalConfig.isDevelopment(),
|
|
93
|
+
isTest: () => globalConfig.isTest(),
|
|
94
|
+
getAll: () => globalConfig.getAll(),
|
|
95
|
+
create: (options) => new ConfigManager(options)
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/logger/index.ts
|
|
99
|
+
var LoggerManager = class {
|
|
100
|
+
loggers = /* @__PURE__ */ new Map();
|
|
101
|
+
defaultLogger;
|
|
102
|
+
constructor() {
|
|
103
|
+
const level = config.get("LOG_LEVEL") || "info";
|
|
104
|
+
this.defaultLogger = pino({
|
|
105
|
+
level,
|
|
106
|
+
name: "saas-backend-kit",
|
|
107
|
+
formatters: {
|
|
108
|
+
bindings: (bindings) => ({
|
|
109
|
+
...bindings,
|
|
110
|
+
service: "saas-backend-kit"
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
createLogger(options = {}) {
|
|
116
|
+
const name = options.name || "default";
|
|
117
|
+
if (this.loggers.has(name)) {
|
|
118
|
+
return this.loggers.get(name);
|
|
119
|
+
}
|
|
120
|
+
const level = options.level || config.get("LOG_LEVEL") || "info";
|
|
121
|
+
const logger2 = pino({
|
|
122
|
+
level,
|
|
123
|
+
name: options.name,
|
|
124
|
+
...options
|
|
125
|
+
});
|
|
126
|
+
this.loggers.set(name, logger2);
|
|
127
|
+
return logger2;
|
|
128
|
+
}
|
|
129
|
+
getLogger(name) {
|
|
130
|
+
if (name) {
|
|
131
|
+
return this.loggers.get(name) || this.defaultLogger;
|
|
132
|
+
}
|
|
133
|
+
return this.defaultLogger;
|
|
134
|
+
}
|
|
135
|
+
child(bindings, options) {
|
|
136
|
+
const name = options?.name || "child";
|
|
137
|
+
const parent = options?.name ? this.getLogger(name) : this.defaultLogger;
|
|
138
|
+
return parent.child(bindings);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var loggerManager = new LoggerManager();
|
|
142
|
+
var logger = {
|
|
143
|
+
info: (message, ...args) => loggerManager.getLogger().info(message, ...args),
|
|
144
|
+
warn: (message, ...args) => loggerManager.getLogger().warn(message, ...args),
|
|
145
|
+
error: (message, ...args) => loggerManager.getLogger().error(message, ...args),
|
|
146
|
+
debug: (message, ...args) => loggerManager.getLogger().debug(message, ...args),
|
|
147
|
+
trace: (message, ...args) => loggerManager.getLogger().trace(message, ...args),
|
|
148
|
+
fatal: (message, ...args) => loggerManager.getLogger().fatal(message, ...args),
|
|
149
|
+
child: (bindings, options) => loggerManager.child(bindings, options),
|
|
150
|
+
create: (options) => loggerManager.createLogger(options),
|
|
151
|
+
get: (name) => loggerManager.getLogger(name)
|
|
152
|
+
};
|
|
153
|
+
function createRequestLogger(options = {}) {
|
|
154
|
+
options.logLevel || "info";
|
|
155
|
+
const logger2 = loggerManager.getLogger("http");
|
|
156
|
+
return function requestLogger(req, res, elapsed) {
|
|
157
|
+
const log = logger2.child({
|
|
158
|
+
method: req.method,
|
|
159
|
+
url: req.url,
|
|
160
|
+
status: res.statusCode,
|
|
161
|
+
responseTime: elapsed,
|
|
162
|
+
ip: req.headers["x-forwarded-for"] || req.headers["x-real-ip"] || "unknown",
|
|
163
|
+
userAgent: req.headers["user-agent"]
|
|
164
|
+
});
|
|
165
|
+
if (res.statusCode >= 500) {
|
|
166
|
+
log.error(`Request completed`);
|
|
167
|
+
} else if (res.statusCode >= 400) {
|
|
168
|
+
log.warn(`Request completed`);
|
|
169
|
+
} else {
|
|
170
|
+
log.info(`Request completed`);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
var logger_default = logger;
|
|
175
|
+
|
|
176
|
+
export { createRequestLogger, logger_default as default, logger };
|
|
177
|
+
//# sourceMappingURL=index.mjs.map
|
|
178
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/config/index.ts","../../src/logger/index.ts"],"names":["logger"],"mappings":"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","file":"index.mjs","sourcesContent":["import { z } from 'zod';\n\nexport const envSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z.string().default('3000'),\n DATABASE_URL: z.string().optional(),\n REDIS_URL: z.string().default('redis://localhost:6379'),\n JWT_SECRET: z.string().min(32).optional(),\n JWT_EXPIRES_IN: z.string().default('7d'),\n JWT_REFRESH_SECRET: z.string().min(32).optional(),\n JWT_REFRESH_EXPIRES_IN: z.string().default('30d'),\n GOOGLE_CLIENT_ID: z.string().optional(),\n GOOGLE_CLIENT_SECRET: z.string().optional(),\n GOOGLE_REDIRECT_URI: z.string().optional(),\n SMTP_HOST: z.string().optional(),\n SMTP_PORT: z.string().default('587'),\n SMTP_USER: z.string().optional(),\n SMTP_PASS: z.string().optional(),\n SMTP_FROM: z.string().optional(),\n TWILIO_ACCOUNT_SID: z.string().optional(),\n TWILIO_AUTH_TOKEN: z.string().optional(),\n TWILIO_PHONE_NUMBER: z.string().optional(),\n SLACK_WEBHOOK_URL: z.string().optional(),\n RATE_LIMIT_WINDOW: z.string().default('1m'),\n RATE_LIMIT_LIMIT: z.string().default('100'),\n LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),\n});\n\nexport type EnvConfig = z.infer<typeof envSchema>;\n\nexport interface ConfigOptions {\n schema?: z.ZodSchema;\n envPath?: string;\n validate?: boolean;\n}\n\nclass ConfigManager {\n private config: EnvConfig | null = null;\n private schema: z.ZodSchema;\n private validate: boolean;\n\n constructor(options: ConfigOptions = {}) {\n this.schema = options.schema || envSchema;\n this.validate = options.validate ?? true;\n }\n\n load(): EnvConfig {\n if (this.config) return this.config;\n\n const env: Record<string, string | undefined> = {};\n \n for (const key of Object.keys(this.schema.shape)) {\n env[key] = process.env[key];\n }\n\n if (this.validate) {\n const result = this.schema.safeParse(env);\n if (!result.success) {\n const errors = result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');\n throw new Error(`Config validation failed: ${errors}`);\n }\n this.config = result.data;\n } else {\n this.config = env as EnvConfig;\n }\n\n return this.config;\n }\n\n get<K extends keyof EnvConfig>(key: K): EnvConfig[K] {\n if (!this.config) this.load();\n return this.config![key];\n }\n\n int(key: keyof EnvConfig): number {\n const value = this.get(key);\n if (typeof value === 'string') return parseInt(value, 10);\n return Number(value);\n }\n\n bool(key: keyof EnvConfig): boolean {\n const value = this.get(key);\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') return value.toLowerCase() === 'true';\n return Boolean(value);\n }\n\n isProduction(): boolean {\n return this.get('NODE_ENV') === 'production';\n }\n\n isDevelopment(): boolean {\n return this.get('NODE_ENV') === 'development';\n }\n\n isTest(): boolean {\n return this.get('NODE_ENV') === 'test';\n }\n\n getAll(): EnvConfig {\n if (!this.config) this.load();\n return this.config!;\n }\n}\n\nconst globalConfig = new ConfigManager();\n\nexport const config = {\n load: () => globalConfig.load(),\n get: <K extends keyof EnvConfig>(key: K) => globalConfig.get(key),\n int: (key: keyof EnvConfig) => globalConfig.int(key),\n bool: (key: keyof EnvConfig) => globalConfig.bool(key),\n isProduction: () => globalConfig.isProduction(),\n isDevelopment: () => globalConfig.isDevelopment(),\n isTest: () => globalConfig.isTest(),\n getAll: () => globalConfig.getAll(),\n create: (options?: ConfigOptions) => new ConfigManager(options),\n};\n\nexport default config;\n","import pino, { Logger, LoggerOptions, Bindings } from 'pino';\nimport { config } from '../config';\n\nexport type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';\n\nexport interface LoggerConfig extends Partial<LoggerOptions> {\n level?: LogLevel;\n name?: string;\n prettyPrint?: boolean;\n}\n\nexport interface RequestLoggerOptions {\n logLevel?: LogLevel;\n autoLogging?: boolean;\n}\n\nclass LoggerManager {\n private loggers: Map<string, Logger> = new Map();\n private defaultLogger: Logger;\n\n constructor() {\n const level = (config.get('LOG_LEVEL') as LogLevel) || 'info';\n this.defaultLogger = pino({\n level,\n name: 'saas-backend-kit',\n formatters: {\n bindings: (bindings: Bindings) => ({\n ...bindings,\n service: 'saas-backend-kit',\n }),\n },\n });\n }\n\n createLogger(options: LoggerConfig = {}): Logger {\n const name = options.name || 'default';\n \n if (this.loggers.has(name)) {\n return this.loggers.get(name)!;\n }\n\n const level = options.level || (config.get('LOG_LEVEL') as LogLevel) || 'info';\n \n const logger = pino({\n level,\n name: options.name,\n ...options,\n });\n\n this.loggers.set(name, logger);\n return logger;\n }\n\n getLogger(name?: string): Logger {\n if (name) {\n return this.loggers.get(name) || this.defaultLogger;\n }\n return this.defaultLogger;\n }\n\n child(bindings: Bindings, options?: { name?: string }): Logger {\n const name = options?.name || 'child';\n const parent = options?.name ? this.getLogger(name) : this.defaultLogger;\n return parent.child(bindings);\n }\n}\n\nconst loggerManager = new LoggerManager();\n\nexport const logger = {\n info: (message: string, ...args: unknown[]) => loggerManager.getLogger().info(message, ...args),\n warn: (message: string, ...args: unknown[]) => loggerManager.getLogger().warn(message, ...args),\n error: (message: string, ...args: unknown[]) => loggerManager.getLogger().error(message, ...args),\n debug: (message: string, ...args: unknown[]) => loggerManager.getLogger().debug(message, ...args),\n trace: (message: string, ...args: unknown[]) => loggerManager.getLogger().trace(message, ...args),\n fatal: (message: string, ...args: unknown[]) => loggerManager.getLogger().fatal(message, ...args),\n child: (bindings: Bindings, options?: { name?: string }) => loggerManager.child(bindings, options),\n create: (options?: LoggerConfig) => loggerManager.createLogger(options),\n get: (name?: string) => loggerManager.getLogger(name),\n};\n\nexport function createRequestLogger(options: RequestLoggerOptions = {}) {\n const logLevel = options.logLevel || 'info';\n const logger = loggerManager.getLogger('http');\n\n return function requestLogger(\n req: { method: string; url: string; headers: Record<string, string | string[] | undefined> },\n res: { statusCode: number; statusMessage?: string },\n elapsed: number\n ) {\n const log = logger.child({\n method: req.method,\n url: req.url,\n status: res.statusCode,\n responseTime: elapsed,\n ip: req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || 'unknown',\n userAgent: req.headers['user-agent'],\n });\n\n if (res.statusCode >= 500) {\n log.error(`Request completed`);\n } else if (res.statusCode >= 400) {\n log.warn(`Request completed`);\n } else {\n log.info(`Request completed`);\n }\n };\n}\n\nexport default logger;\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface EmailOptions {
|
|
2
|
+
to: string | string[];
|
|
3
|
+
subject: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
html?: string;
|
|
6
|
+
template?: string;
|
|
7
|
+
templateData?: Record<string, unknown>;
|
|
8
|
+
from?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SMSOptions {
|
|
12
|
+
to: string;
|
|
13
|
+
message: string;
|
|
14
|
+
from?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface WebhookOptions {
|
|
18
|
+
url: string;
|
|
19
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH';
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
body?: unknown;
|
|
22
|
+
timeout?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SlackOptions {
|
|
26
|
+
text?: string;
|
|
27
|
+
blocks?: any[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export declare const notify: {
|
|
31
|
+
email: (options: EmailOptions) => Promise<{ messageId: string }>;
|
|
32
|
+
sms: (options: SMSOptions) => Promise<{ sid: string }>;
|
|
33
|
+
webhook: (options: WebhookOptions) => Promise<{ status: number; body: unknown }>;
|
|
34
|
+
slack: (options: SlackOptions) => Promise<{ ok: boolean }>;
|
|
35
|
+
};
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var nodemailer = require('nodemailer');
|
|
6
|
+
var zod = require('zod');
|
|
7
|
+
var pino = require('pino');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var nodemailer__default = /*#__PURE__*/_interopDefault(nodemailer);
|
|
12
|
+
var pino__default = /*#__PURE__*/_interopDefault(pino);
|
|
13
|
+
|
|
14
|
+
// src/notifications/index.ts
|
|
15
|
+
var envSchema = zod.z.object({
|
|
16
|
+
NODE_ENV: zod.z.enum(["development", "production", "test"]).default("development"),
|
|
17
|
+
PORT: zod.z.string().default("3000"),
|
|
18
|
+
DATABASE_URL: zod.z.string().optional(),
|
|
19
|
+
REDIS_URL: zod.z.string().default("redis://localhost:6379"),
|
|
20
|
+
JWT_SECRET: zod.z.string().min(32).optional(),
|
|
21
|
+
JWT_EXPIRES_IN: zod.z.string().default("7d"),
|
|
22
|
+
JWT_REFRESH_SECRET: zod.z.string().min(32).optional(),
|
|
23
|
+
JWT_REFRESH_EXPIRES_IN: zod.z.string().default("30d"),
|
|
24
|
+
GOOGLE_CLIENT_ID: zod.z.string().optional(),
|
|
25
|
+
GOOGLE_CLIENT_SECRET: zod.z.string().optional(),
|
|
26
|
+
GOOGLE_REDIRECT_URI: zod.z.string().optional(),
|
|
27
|
+
SMTP_HOST: zod.z.string().optional(),
|
|
28
|
+
SMTP_PORT: zod.z.string().default("587"),
|
|
29
|
+
SMTP_USER: zod.z.string().optional(),
|
|
30
|
+
SMTP_PASS: zod.z.string().optional(),
|
|
31
|
+
SMTP_FROM: zod.z.string().optional(),
|
|
32
|
+
TWILIO_ACCOUNT_SID: zod.z.string().optional(),
|
|
33
|
+
TWILIO_AUTH_TOKEN: zod.z.string().optional(),
|
|
34
|
+
TWILIO_PHONE_NUMBER: zod.z.string().optional(),
|
|
35
|
+
SLACK_WEBHOOK_URL: zod.z.string().optional(),
|
|
36
|
+
RATE_LIMIT_WINDOW: zod.z.string().default("1m"),
|
|
37
|
+
RATE_LIMIT_LIMIT: zod.z.string().default("100"),
|
|
38
|
+
LOG_LEVEL: zod.z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info")
|
|
39
|
+
});
|
|
40
|
+
var ConfigManager = class {
|
|
41
|
+
config = null;
|
|
42
|
+
schema;
|
|
43
|
+
validate;
|
|
44
|
+
constructor(options = {}) {
|
|
45
|
+
this.schema = options.schema || envSchema;
|
|
46
|
+
this.validate = options.validate ?? true;
|
|
47
|
+
}
|
|
48
|
+
load() {
|
|
49
|
+
if (this.config) return this.config;
|
|
50
|
+
const env = {};
|
|
51
|
+
for (const key of Object.keys(this.schema.shape)) {
|
|
52
|
+
env[key] = process.env[key];
|
|
53
|
+
}
|
|
54
|
+
if (this.validate) {
|
|
55
|
+
const result = this.schema.safeParse(env);
|
|
56
|
+
if (!result.success) {
|
|
57
|
+
const errors = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
58
|
+
throw new Error(`Config validation failed: ${errors}`);
|
|
59
|
+
}
|
|
60
|
+
this.config = result.data;
|
|
61
|
+
} else {
|
|
62
|
+
this.config = env;
|
|
63
|
+
}
|
|
64
|
+
return this.config;
|
|
65
|
+
}
|
|
66
|
+
get(key) {
|
|
67
|
+
if (!this.config) this.load();
|
|
68
|
+
return this.config[key];
|
|
69
|
+
}
|
|
70
|
+
int(key) {
|
|
71
|
+
const value = this.get(key);
|
|
72
|
+
if (typeof value === "string") return parseInt(value, 10);
|
|
73
|
+
return Number(value);
|
|
74
|
+
}
|
|
75
|
+
bool(key) {
|
|
76
|
+
const value = this.get(key);
|
|
77
|
+
if (typeof value === "boolean") return value;
|
|
78
|
+
if (typeof value === "string") return value.toLowerCase() === "true";
|
|
79
|
+
return Boolean(value);
|
|
80
|
+
}
|
|
81
|
+
isProduction() {
|
|
82
|
+
return this.get("NODE_ENV") === "production";
|
|
83
|
+
}
|
|
84
|
+
isDevelopment() {
|
|
85
|
+
return this.get("NODE_ENV") === "development";
|
|
86
|
+
}
|
|
87
|
+
isTest() {
|
|
88
|
+
return this.get("NODE_ENV") === "test";
|
|
89
|
+
}
|
|
90
|
+
getAll() {
|
|
91
|
+
if (!this.config) this.load();
|
|
92
|
+
return this.config;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var globalConfig = new ConfigManager();
|
|
96
|
+
var config = {
|
|
97
|
+
load: () => globalConfig.load(),
|
|
98
|
+
get: (key) => globalConfig.get(key),
|
|
99
|
+
int: (key) => globalConfig.int(key),
|
|
100
|
+
bool: (key) => globalConfig.bool(key),
|
|
101
|
+
isProduction: () => globalConfig.isProduction(),
|
|
102
|
+
isDevelopment: () => globalConfig.isDevelopment(),
|
|
103
|
+
isTest: () => globalConfig.isTest(),
|
|
104
|
+
getAll: () => globalConfig.getAll(),
|
|
105
|
+
create: (options) => new ConfigManager(options)
|
|
106
|
+
};
|
|
107
|
+
var LoggerManager = class {
|
|
108
|
+
loggers = /* @__PURE__ */ new Map();
|
|
109
|
+
defaultLogger;
|
|
110
|
+
constructor() {
|
|
111
|
+
const level = config.get("LOG_LEVEL") || "info";
|
|
112
|
+
this.defaultLogger = pino__default.default({
|
|
113
|
+
level,
|
|
114
|
+
name: "saas-backend-kit",
|
|
115
|
+
formatters: {
|
|
116
|
+
bindings: (bindings) => ({
|
|
117
|
+
...bindings,
|
|
118
|
+
service: "saas-backend-kit"
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
createLogger(options = {}) {
|
|
124
|
+
const name = options.name || "default";
|
|
125
|
+
if (this.loggers.has(name)) {
|
|
126
|
+
return this.loggers.get(name);
|
|
127
|
+
}
|
|
128
|
+
const level = options.level || config.get("LOG_LEVEL") || "info";
|
|
129
|
+
const logger2 = pino__default.default({
|
|
130
|
+
level,
|
|
131
|
+
name: options.name,
|
|
132
|
+
...options
|
|
133
|
+
});
|
|
134
|
+
this.loggers.set(name, logger2);
|
|
135
|
+
return logger2;
|
|
136
|
+
}
|
|
137
|
+
getLogger(name) {
|
|
138
|
+
if (name) {
|
|
139
|
+
return this.loggers.get(name) || this.defaultLogger;
|
|
140
|
+
}
|
|
141
|
+
return this.defaultLogger;
|
|
142
|
+
}
|
|
143
|
+
child(bindings, options) {
|
|
144
|
+
const name = options?.name || "child";
|
|
145
|
+
const parent = options?.name ? this.getLogger(name) : this.defaultLogger;
|
|
146
|
+
return parent.child(bindings);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var loggerManager = new LoggerManager();
|
|
150
|
+
var logger = {
|
|
151
|
+
info: (message, ...args) => loggerManager.getLogger().info(message, ...args),
|
|
152
|
+
warn: (message, ...args) => loggerManager.getLogger().warn(message, ...args),
|
|
153
|
+
error: (message, ...args) => loggerManager.getLogger().error(message, ...args),
|
|
154
|
+
debug: (message, ...args) => loggerManager.getLogger().debug(message, ...args),
|
|
155
|
+
trace: (message, ...args) => loggerManager.getLogger().trace(message, ...args),
|
|
156
|
+
fatal: (message, ...args) => loggerManager.getLogger().fatal(message, ...args),
|
|
157
|
+
child: (bindings, options) => loggerManager.child(bindings, options),
|
|
158
|
+
create: (options) => loggerManager.createLogger(options),
|
|
159
|
+
get: (name) => loggerManager.getLogger(name)
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/notifications/index.ts
|
|
163
|
+
var EmailService = class {
|
|
164
|
+
transporter = null;
|
|
165
|
+
from;
|
|
166
|
+
constructor() {
|
|
167
|
+
this.from = config.get("SMTP_FROM") || "noreply@example.com";
|
|
168
|
+
this.initialize();
|
|
169
|
+
}
|
|
170
|
+
initialize() {
|
|
171
|
+
const host = config.get("SMTP_HOST");
|
|
172
|
+
const port = parseInt(config.get("SMTP_PORT") || "587", 10);
|
|
173
|
+
const user = config.get("SMTP_USER");
|
|
174
|
+
const pass = config.get("SMTP_PASS");
|
|
175
|
+
if (host && user && pass) {
|
|
176
|
+
this.transporter = nodemailer__default.default.createTransport({
|
|
177
|
+
host,
|
|
178
|
+
port,
|
|
179
|
+
secure: port === 465,
|
|
180
|
+
auth: {
|
|
181
|
+
user,
|
|
182
|
+
pass
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
logger.info("Email service initialized");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
setTransporter(transporter) {
|
|
189
|
+
this.transporter = transporter;
|
|
190
|
+
}
|
|
191
|
+
async send(options) {
|
|
192
|
+
if (!this.transporter) {
|
|
193
|
+
logger.warn("Email transporter not configured, skipping email");
|
|
194
|
+
return { messageId: "mock-message-id" };
|
|
195
|
+
}
|
|
196
|
+
const mailOptions = {
|
|
197
|
+
from: options.from || this.from,
|
|
198
|
+
to: Array.isArray(options.to) ? options.to.join(", ") : options.to,
|
|
199
|
+
subject: options.subject,
|
|
200
|
+
text: options.text,
|
|
201
|
+
html: options.html || this.renderTemplate(options.template, options.templateData),
|
|
202
|
+
cc: options.cc,
|
|
203
|
+
bcc: options.bcc,
|
|
204
|
+
attachments: options.attachments
|
|
205
|
+
};
|
|
206
|
+
const result = await this.transporter.sendMail(mailOptions);
|
|
207
|
+
logger.info(`Email sent to ${options.to}`, { messageId: result.messageId });
|
|
208
|
+
return { messageId: result.messageId };
|
|
209
|
+
}
|
|
210
|
+
renderTemplate(templateName, data) {
|
|
211
|
+
if (!templateName || !data) return void 0;
|
|
212
|
+
const templates = {
|
|
213
|
+
welcome: (data2) => `
|
|
214
|
+
<h1>Welcome, ${data2.name || "User"}!</h1>
|
|
215
|
+
<p>Thank you for joining ${data2.appName || "our platform"}.</p>
|
|
216
|
+
<p>Get started by verifying your email.</p>
|
|
217
|
+
`,
|
|
218
|
+
passwordReset: (data2) => `
|
|
219
|
+
<h1>Password Reset</h1>
|
|
220
|
+
<p>Click <a href="${data2.resetUrl}">here</a> to reset your password.</p>
|
|
221
|
+
<p>This link expires in ${data2.expiry || "1 hour"}.</p>
|
|
222
|
+
`,
|
|
223
|
+
verification: (data2) => `
|
|
224
|
+
<h1>Verify Your Email</h1>
|
|
225
|
+
<p>Click <a href="${data2.verifyUrl}">here</a> to verify your email.</p>
|
|
226
|
+
`
|
|
227
|
+
};
|
|
228
|
+
const template = templates[templateName];
|
|
229
|
+
return template ? template(data) : void 0;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var SMSService = class {
|
|
233
|
+
accountSid;
|
|
234
|
+
authToken;
|
|
235
|
+
phoneNumber;
|
|
236
|
+
initialized = false;
|
|
237
|
+
constructor() {
|
|
238
|
+
this.accountSid = config.get("TWILIO_ACCOUNT_SID") || "";
|
|
239
|
+
this.authToken = config.get("TWILIO_AUTH_TOKEN") || "";
|
|
240
|
+
this.phoneNumber = config.get("TWILIO_PHONE_NUMBER") || "";
|
|
241
|
+
this.initialized = !!(this.accountSid && this.authToken && this.phoneNumber);
|
|
242
|
+
}
|
|
243
|
+
async send(options) {
|
|
244
|
+
if (!this.initialized) {
|
|
245
|
+
logger.warn("Twilio not configured, skipping SMS");
|
|
246
|
+
return { sid: "mock-sid" };
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
const twilio = await import('twilio');
|
|
250
|
+
const client = twilio.default(this.accountSid, this.authToken);
|
|
251
|
+
const result = await client.messages.create({
|
|
252
|
+
body: options.message,
|
|
253
|
+
from: options.from || this.phoneNumber,
|
|
254
|
+
to: options.to
|
|
255
|
+
});
|
|
256
|
+
logger.info(`SMS sent to ${options.to}`, { sid: result.sid });
|
|
257
|
+
return { sid: result.sid };
|
|
258
|
+
} catch (error) {
|
|
259
|
+
logger.error("Failed to send SMS", { error });
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
var WebhookService = class {
|
|
265
|
+
async send(options) {
|
|
266
|
+
const response = await fetch(options.url, {
|
|
267
|
+
method: options.method || "POST",
|
|
268
|
+
headers: {
|
|
269
|
+
"Content-Type": "application/json",
|
|
270
|
+
...options.headers
|
|
271
|
+
},
|
|
272
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
273
|
+
signal: options.timeout ? AbortSignal.timeout(options.timeout) : void 0
|
|
274
|
+
});
|
|
275
|
+
const body = await response.json().catch(() => null);
|
|
276
|
+
logger.info(`Webhook sent to ${options.url}`, { status: response.status });
|
|
277
|
+
return { status: response.status, body };
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
var SlackService = class {
|
|
281
|
+
webhookUrl;
|
|
282
|
+
constructor() {
|
|
283
|
+
this.webhookUrl = config.get("SLACK_WEBHOOK_URL") || "";
|
|
284
|
+
}
|
|
285
|
+
setWebhookUrl(url) {
|
|
286
|
+
this.webhookUrl = url;
|
|
287
|
+
}
|
|
288
|
+
async send(options) {
|
|
289
|
+
if (!this.webhookUrl) {
|
|
290
|
+
logger.warn("Slack webhook not configured, skipping message");
|
|
291
|
+
return { ok: false };
|
|
292
|
+
}
|
|
293
|
+
const payload = {
|
|
294
|
+
text: options.text,
|
|
295
|
+
blocks: options.blocks,
|
|
296
|
+
channel: options.channel,
|
|
297
|
+
username: options.username,
|
|
298
|
+
icon_emoji: options.iconEmoji
|
|
299
|
+
};
|
|
300
|
+
const response = await fetch(this.webhookUrl, {
|
|
301
|
+
method: "POST",
|
|
302
|
+
headers: { "Content-Type": "application/json" },
|
|
303
|
+
body: JSON.stringify(payload)
|
|
304
|
+
});
|
|
305
|
+
const ok = response.ok;
|
|
306
|
+
if (ok) {
|
|
307
|
+
logger.info("Slack message sent");
|
|
308
|
+
} else {
|
|
309
|
+
logger.error("Failed to send Slack message");
|
|
310
|
+
}
|
|
311
|
+
return { ok };
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
var NotificationService = class {
|
|
315
|
+
email;
|
|
316
|
+
sms;
|
|
317
|
+
webhook;
|
|
318
|
+
slack;
|
|
319
|
+
constructor() {
|
|
320
|
+
this.email = new EmailService();
|
|
321
|
+
this.sms = new SMSService();
|
|
322
|
+
this.webhook = new WebhookService();
|
|
323
|
+
this.slack = new SlackService();
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
var notify = new NotificationService();
|
|
327
|
+
var notification = {
|
|
328
|
+
email: (options) => notify.email.send(options),
|
|
329
|
+
sms: (options) => notify.sms.send(options),
|
|
330
|
+
webhook: (options) => notify.webhook.send(options),
|
|
331
|
+
slack: (options) => notify.slack.send(options)
|
|
332
|
+
};
|
|
333
|
+
var notifications_default = notification;
|
|
334
|
+
|
|
335
|
+
exports.default = notifications_default;
|
|
336
|
+
exports.notification = notification;
|
|
337
|
+
exports.notify = notify;
|
|
338
|
+
//# sourceMappingURL=index.js.map
|
|
339
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/config/index.ts","../../src/logger/index.ts","../../src/notifications/index.ts"],"names":["z","pino","logger","nodemailer","data"],"mappings":"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","file":"index.js","sourcesContent":["import { z } from 'zod';\n\nexport const envSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z.string().default('3000'),\n DATABASE_URL: z.string().optional(),\n REDIS_URL: z.string().default('redis://localhost:6379'),\n JWT_SECRET: z.string().min(32).optional(),\n JWT_EXPIRES_IN: z.string().default('7d'),\n JWT_REFRESH_SECRET: z.string().min(32).optional(),\n JWT_REFRESH_EXPIRES_IN: z.string().default('30d'),\n GOOGLE_CLIENT_ID: z.string().optional(),\n GOOGLE_CLIENT_SECRET: z.string().optional(),\n GOOGLE_REDIRECT_URI: z.string().optional(),\n SMTP_HOST: z.string().optional(),\n SMTP_PORT: z.string().default('587'),\n SMTP_USER: z.string().optional(),\n SMTP_PASS: z.string().optional(),\n SMTP_FROM: z.string().optional(),\n TWILIO_ACCOUNT_SID: z.string().optional(),\n TWILIO_AUTH_TOKEN: z.string().optional(),\n TWILIO_PHONE_NUMBER: z.string().optional(),\n SLACK_WEBHOOK_URL: z.string().optional(),\n RATE_LIMIT_WINDOW: z.string().default('1m'),\n RATE_LIMIT_LIMIT: z.string().default('100'),\n LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),\n});\n\nexport type EnvConfig = z.infer<typeof envSchema>;\n\nexport interface ConfigOptions {\n schema?: z.ZodSchema;\n envPath?: string;\n validate?: boolean;\n}\n\nclass ConfigManager {\n private config: EnvConfig | null = null;\n private schema: z.ZodSchema;\n private validate: boolean;\n\n constructor(options: ConfigOptions = {}) {\n this.schema = options.schema || envSchema;\n this.validate = options.validate ?? true;\n }\n\n load(): EnvConfig {\n if (this.config) return this.config;\n\n const env: Record<string, string | undefined> = {};\n \n for (const key of Object.keys(this.schema.shape)) {\n env[key] = process.env[key];\n }\n\n if (this.validate) {\n const result = this.schema.safeParse(env);\n if (!result.success) {\n const errors = result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');\n throw new Error(`Config validation failed: ${errors}`);\n }\n this.config = result.data;\n } else {\n this.config = env as EnvConfig;\n }\n\n return this.config;\n }\n\n get<K extends keyof EnvConfig>(key: K): EnvConfig[K] {\n if (!this.config) this.load();\n return this.config![key];\n }\n\n int(key: keyof EnvConfig): number {\n const value = this.get(key);\n if (typeof value === 'string') return parseInt(value, 10);\n return Number(value);\n }\n\n bool(key: keyof EnvConfig): boolean {\n const value = this.get(key);\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') return value.toLowerCase() === 'true';\n return Boolean(value);\n }\n\n isProduction(): boolean {\n return this.get('NODE_ENV') === 'production';\n }\n\n isDevelopment(): boolean {\n return this.get('NODE_ENV') === 'development';\n }\n\n isTest(): boolean {\n return this.get('NODE_ENV') === 'test';\n }\n\n getAll(): EnvConfig {\n if (!this.config) this.load();\n return this.config!;\n }\n}\n\nconst globalConfig = new ConfigManager();\n\nexport const config = {\n load: () => globalConfig.load(),\n get: <K extends keyof EnvConfig>(key: K) => globalConfig.get(key),\n int: (key: keyof EnvConfig) => globalConfig.int(key),\n bool: (key: keyof EnvConfig) => globalConfig.bool(key),\n isProduction: () => globalConfig.isProduction(),\n isDevelopment: () => globalConfig.isDevelopment(),\n isTest: () => globalConfig.isTest(),\n getAll: () => globalConfig.getAll(),\n create: (options?: ConfigOptions) => new ConfigManager(options),\n};\n\nexport default config;\n","import pino, { Logger, LoggerOptions, Bindings } from 'pino';\nimport { config } from '../config';\n\nexport type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';\n\nexport interface LoggerConfig extends Partial<LoggerOptions> {\n level?: LogLevel;\n name?: string;\n prettyPrint?: boolean;\n}\n\nexport interface RequestLoggerOptions {\n logLevel?: LogLevel;\n autoLogging?: boolean;\n}\n\nclass LoggerManager {\n private loggers: Map<string, Logger> = new Map();\n private defaultLogger: Logger;\n\n constructor() {\n const level = (config.get('LOG_LEVEL') as LogLevel) || 'info';\n this.defaultLogger = pino({\n level,\n name: 'saas-backend-kit',\n formatters: {\n bindings: (bindings: Bindings) => ({\n ...bindings,\n service: 'saas-backend-kit',\n }),\n },\n });\n }\n\n createLogger(options: LoggerConfig = {}): Logger {\n const name = options.name || 'default';\n \n if (this.loggers.has(name)) {\n return this.loggers.get(name)!;\n }\n\n const level = options.level || (config.get('LOG_LEVEL') as LogLevel) || 'info';\n \n const logger = pino({\n level,\n name: options.name,\n ...options,\n });\n\n this.loggers.set(name, logger);\n return logger;\n }\n\n getLogger(name?: string): Logger {\n if (name) {\n return this.loggers.get(name) || this.defaultLogger;\n }\n return this.defaultLogger;\n }\n\n child(bindings: Bindings, options?: { name?: string }): Logger {\n const name = options?.name || 'child';\n const parent = options?.name ? this.getLogger(name) : this.defaultLogger;\n return parent.child(bindings);\n }\n}\n\nconst loggerManager = new LoggerManager();\n\nexport const logger = {\n info: (message: string, ...args: unknown[]) => loggerManager.getLogger().info(message, ...args),\n warn: (message: string, ...args: unknown[]) => loggerManager.getLogger().warn(message, ...args),\n error: (message: string, ...args: unknown[]) => loggerManager.getLogger().error(message, ...args),\n debug: (message: string, ...args: unknown[]) => loggerManager.getLogger().debug(message, ...args),\n trace: (message: string, ...args: unknown[]) => loggerManager.getLogger().trace(message, ...args),\n fatal: (message: string, ...args: unknown[]) => loggerManager.getLogger().fatal(message, ...args),\n child: (bindings: Bindings, options?: { name?: string }) => loggerManager.child(bindings, options),\n create: (options?: LoggerConfig) => loggerManager.createLogger(options),\n get: (name?: string) => loggerManager.getLogger(name),\n};\n\nexport function createRequestLogger(options: RequestLoggerOptions = {}) {\n const logLevel = options.logLevel || 'info';\n const logger = loggerManager.getLogger('http');\n\n return function requestLogger(\n req: { method: string; url: string; headers: Record<string, string | string[] | undefined> },\n res: { statusCode: number; statusMessage?: string },\n elapsed: number\n ) {\n const log = logger.child({\n method: req.method,\n url: req.url,\n status: res.statusCode,\n responseTime: elapsed,\n ip: req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || 'unknown',\n userAgent: req.headers['user-agent'],\n });\n\n if (res.statusCode >= 500) {\n log.error(`Request completed`);\n } else if (res.statusCode >= 400) {\n log.warn(`Request completed`);\n } else {\n log.info(`Request completed`);\n }\n };\n}\n\nexport default logger;\n","import nodemailer, { Transporter, SendMailOptions } from 'nodemailer';\nimport { config } from '../config';\nimport { logger } from '../logger';\n\nexport interface EmailOptions {\n to: string | string[];\n subject: string;\n text?: string;\n html?: string;\n template?: string;\n templateData?: Record<string, unknown>;\n from?: string;\n cc?: string | string[];\n bcc?: string | string[];\n attachments?: Array<{\n filename: string;\n content?: Buffer | string;\n path?: string;\n contentType?: string;\n }>;\n}\n\nexport interface SMSOptions {\n to: string;\n message: string;\n from?: string;\n}\n\nexport interface WebhookOptions {\n url: string;\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH';\n headers?: Record<string, string>;\n body?: unknown;\n timeout?: number;\n}\n\nexport interface SlackOptions {\n text?: string;\n blocks?: Array<{\n type: string;\n text?: { type: string; text: string; emoji?: boolean };\n elements?: Array<{ type: string; text?: { type: string; text: string } }>;\n accessory?: { type: string; image_url?: string; alt_text?: string };\n }>;\n channel?: string;\n username?: string;\n iconEmoji?: string;\n}\n\nexport interface NotificationTransporter {\n email: Transporter;\n twilio?: {\n accountSid: string;\n authToken: string;\n phoneNumber: string;\n };\n slack?: {\n webhookUrl: string;\n };\n}\n\nclass EmailService {\n private transporter: Transporter | null = null;\n private from: string;\n\n constructor() {\n this.from = config.get('SMTP_FROM') || 'noreply@example.com';\n this.initialize();\n }\n\n private initialize(): void {\n const host = config.get('SMTP_HOST');\n const port = parseInt(config.get('SMTP_PORT') || '587', 10);\n const user = config.get('SMTP_USER');\n const pass = config.get('SMTP_PASS');\n\n if (host && user && pass) {\n this.transporter = nodemailer.createTransport({\n host,\n port,\n secure: port === 465,\n auth: {\n user,\n pass,\n },\n });\n logger.info('Email service initialized');\n }\n }\n\n setTransporter(transporter: Transporter): void {\n this.transporter = transporter;\n }\n\n async send(options: EmailOptions): Promise<{ messageId: string }> {\n if (!this.transporter) {\n logger.warn('Email transporter not configured, skipping email');\n return { messageId: 'mock-message-id' };\n }\n\n const mailOptions: SendMailOptions = {\n from: options.from || this.from,\n to: Array.isArray(options.to) ? options.to.join(', ') : options.to,\n subject: options.subject,\n text: options.text,\n html: options.html || this.renderTemplate(options.template, options.templateData),\n cc: options.cc,\n bcc: options.bcc,\n attachments: options.attachments,\n };\n\n const result = await this.transporter.sendMail(mailOptions);\n logger.info(`Email sent to ${options.to}`, { messageId: result.messageId });\n return { messageId: result.messageId };\n }\n\n private renderTemplate(templateName?: string, data?: Record<string, unknown>): string | undefined {\n if (!templateName || !data) return undefined;\n\n const templates: Record<string, (data: Record<string, unknown>) => string> = {\n welcome: (data) => `\n <h1>Welcome, ${data.name || 'User'}!</h1>\n <p>Thank you for joining ${data.appName || 'our platform'}.</p>\n <p>Get started by verifying your email.</p>\n `,\n passwordReset: (data) => `\n <h1>Password Reset</h1>\n <p>Click <a href=\"${data.resetUrl}\">here</a> to reset your password.</p>\n <p>This link expires in ${data.expiry || '1 hour'}.</p>\n `,\n verification: (data) => `\n <h1>Verify Your Email</h1>\n <p>Click <a href=\"${data.verifyUrl}\">here</a> to verify your email.</p>\n `,\n };\n\n const template = templates[templateName];\n return template ? template(data) : undefined;\n }\n}\n\nclass SMSService {\n private accountSid: string;\n private authToken: string;\n private phoneNumber: string;\n private initialized: boolean = false;\n\n constructor() {\n this.accountSid = config.get('TWILIO_ACCOUNT_SID') || '';\n this.authToken = config.get('TWILIO_AUTH_TOKEN') || '';\n this.phoneNumber = config.get('TWILIO_PHONE_NUMBER') || '';\n this.initialized = !!(this.accountSid && this.authToken && this.phoneNumber);\n }\n\n async send(options: SMSOptions): Promise<{ sid: string }> {\n if (!this.initialized) {\n logger.warn('Twilio not configured, skipping SMS');\n return { sid: 'mock-sid' };\n }\n\n try {\n const twilio = await import('twilio');\n const client = twilio.default(this.accountSid, this.authToken);\n\n const result = await client.messages.create({\n body: options.message,\n from: options.from || this.phoneNumber,\n to: options.to,\n });\n\n logger.info(`SMS sent to ${options.to}`, { sid: result.sid });\n return { sid: result.sid };\n } catch (error) {\n logger.error('Failed to send SMS', { error });\n throw error;\n }\n }\n}\n\nclass WebhookService {\n async send(options: WebhookOptions): Promise<{ status: number; body: unknown }> {\n const response = await fetch(options.url, {\n method: options.method || 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n body: options.body ? JSON.stringify(options.body) : undefined,\n signal: options.timeout ? AbortSignal.timeout(options.timeout) : undefined,\n });\n\n const body = await response.json().catch(() => null);\n logger.info(`Webhook sent to ${options.url}`, { status: response.status });\n return { status: response.status, body };\n }\n}\n\nclass SlackService {\n private webhookUrl: string;\n\n constructor() {\n this.webhookUrl = config.get('SLACK_WEBHOOK_URL') || '';\n }\n\n setWebhookUrl(url: string): void {\n this.webhookUrl = url;\n }\n\n async send(options: SlackOptions): Promise<{ ok: boolean }> {\n if (!this.webhookUrl) {\n logger.warn('Slack webhook not configured, skipping message');\n return { ok: false };\n }\n\n const payload = {\n text: options.text,\n blocks: options.blocks,\n channel: options.channel,\n username: options.username,\n icon_emoji: options.iconEmoji,\n };\n\n const response = await fetch(this.webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n });\n\n const ok = response.ok;\n if (ok) {\n logger.info('Slack message sent');\n } else {\n logger.error('Failed to send Slack message');\n }\n return { ok };\n }\n}\n\nclass NotificationService {\n email: EmailService;\n sms: SMSService;\n webhook: WebhookService;\n slack: SlackService;\n\n constructor() {\n this.email = new EmailService();\n this.sms = new SMSService();\n this.webhook = new WebhookService();\n this.slack = new SlackService();\n }\n}\n\nexport const notify = new NotificationService();\n\nexport const notification = {\n email: (options: EmailOptions) => notify.email.send(options),\n sms: (options: SMSOptions) => notify.sms.send(options),\n webhook: (options: WebhookOptions) => notify.webhook.send(options),\n slack: (options: SlackOptions) => notify.slack.send(options),\n};\n\nexport default notification;\n"]}
|