stackkit 0.2.7 → 0.2.9

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.
Files changed (61) hide show
  1. package/README.md +91 -12
  2. package/dist/lib/fs/files.js +1 -1
  3. package/dist/lib/generation/code-generator.d.ts +2 -7
  4. package/dist/lib/generation/code-generator.js +120 -56
  5. package/dist/lib/utils/fs-helpers.d.ts +1 -1
  6. package/modules/auth/authjs/generator.json +16 -16
  7. package/modules/auth/better-auth/files/express/middlewares/authorize.ts +121 -41
  8. package/modules/auth/better-auth/files/express/modules/auth/auth.controller.ts +257 -0
  9. package/modules/auth/better-auth/files/express/modules/auth/auth.interface.ts +23 -0
  10. package/modules/auth/better-auth/files/express/modules/auth/auth.route.ts +22 -0
  11. package/modules/auth/better-auth/files/express/modules/auth/auth.service.ts +403 -0
  12. package/modules/auth/better-auth/files/express/templates/google-redirect.ejs +91 -0
  13. package/modules/auth/better-auth/files/express/templates/otp.ejs +87 -0
  14. package/modules/auth/better-auth/files/express/types/express.d.ts +6 -8
  15. package/modules/auth/better-auth/files/express/utils/cookie.ts +19 -0
  16. package/modules/auth/better-auth/files/express/utils/jwt.ts +34 -0
  17. package/modules/auth/better-auth/files/express/utils/token.ts +66 -0
  18. package/modules/auth/better-auth/files/nextjs/api/auth/[...all]/route.ts +1 -1
  19. package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +11 -1
  20. package/modules/auth/better-auth/files/nextjs/templates/email-otp.tsx +74 -0
  21. package/modules/auth/better-auth/files/shared/config/env.ts +113 -0
  22. package/modules/auth/better-auth/files/shared/lib/auth-client.ts +1 -1
  23. package/modules/auth/better-auth/files/shared/lib/auth.ts +151 -62
  24. package/modules/auth/better-auth/files/shared/prisma/schema.prisma +22 -11
  25. package/modules/auth/better-auth/files/shared/utils/email.ts +71 -0
  26. package/modules/auth/better-auth/generator.json +159 -81
  27. package/modules/database/mongoose/generator.json +18 -18
  28. package/modules/database/prisma/generator.json +44 -44
  29. package/package.json +1 -1
  30. package/templates/express/env.example +2 -2
  31. package/templates/express/eslint.config.mjs +7 -0
  32. package/templates/express/node_modules/.bin/acorn +17 -0
  33. package/templates/express/node_modules/.bin/eslint +17 -0
  34. package/templates/express/node_modules/.bin/tsc +17 -0
  35. package/templates/express/node_modules/.bin/tsserver +17 -0
  36. package/templates/express/node_modules/.bin/tsx +17 -0
  37. package/templates/express/package.json +12 -6
  38. package/templates/express/src/app.ts +15 -7
  39. package/templates/express/src/config/cors.ts +8 -7
  40. package/templates/express/src/config/env.ts +26 -5
  41. package/templates/express/src/config/logger.ts +2 -2
  42. package/templates/express/src/config/rate-limit.ts +2 -2
  43. package/templates/express/src/modules/health/health.controller.ts +13 -11
  44. package/templates/express/src/routes/index.ts +1 -6
  45. package/templates/express/src/server.ts +12 -12
  46. package/templates/express/src/shared/errors/app-error.ts +16 -0
  47. package/templates/express/src/shared/middlewares/error.middleware.ts +154 -12
  48. package/templates/express/src/shared/middlewares/not-found.middleware.ts +2 -1
  49. package/templates/express/src/shared/utils/catch-async.ts +11 -0
  50. package/templates/express/src/shared/utils/pagination.ts +6 -1
  51. package/templates/express/src/shared/utils/send-response.ts +25 -0
  52. package/templates/nextjs/lib/env.ts +19 -8
  53. package/modules/auth/better-auth/files/shared/lib/email/email-service.ts +0 -33
  54. package/modules/auth/better-auth/files/shared/lib/email/email-templates.ts +0 -89
  55. package/templates/express/eslint.config.cjs +0 -42
  56. package/templates/express/src/config/helmet.ts +0 -5
  57. package/templates/express/src/modules/health/health.service.ts +0 -6
  58. package/templates/express/src/shared/errors/error-codes.ts +0 -9
  59. package/templates/express/src/shared/logger/logger.ts +0 -20
  60. package/templates/express/src/shared/utils/async-handler.ts +0 -9
  61. package/templates/express/src/shared/utils/response.ts +0 -9
@@ -6,27 +6,33 @@
6
6
  "scripts": {
7
7
  "dev": "tsx watch src/server.ts",
8
8
  "build": "tsc",
9
- "lint": "eslint src --ext .ts",
10
- "lint:fix": "eslint src --ext .ts --fix",
9
+ "lint": "eslint ./src/**/*",
10
+ "lint:fix": "eslint ./src/**/* --fix",
11
11
  "start": "node dist/server.js"
12
12
  },
13
13
  "dependencies": {
14
+ "cookie-parser": "^1.4.7",
14
15
  "cors": "^2.8.5",
15
16
  "dotenv": "^17.2.3",
16
17
  "express": "^5.2.1",
17
18
  "helmet": "^8.1.0",
19
+ "http-status": "^2.1.0",
18
20
  "morgan": "^1.10.1",
19
- "express-rate-limit": "^6.10.0"
21
+ "express-rate-limit": "^6.10.0",
22
+ "qs": "^6.14.1",
23
+ "zod": "^4.3.6"
20
24
  },
21
25
  "devDependencies": {
26
+ "@eslint/js": "^9.39.2",
27
+ "@types/cookie-parser": "^1.4.10",
22
28
  "@types/cors": "^2.8.19",
23
29
  "@types/express": "^5.0.6",
24
30
  "@types/morgan": "^1.9.10",
25
31
  "@types/node": "^25.0.8",
26
- "@typescript-eslint/eslint-plugin": "^8.53.0",
27
- "@typescript-eslint/parser": "^8.53.0",
32
+ "@types/qs": "^6.14.0",
28
33
  "eslint": "^9.39.2",
29
34
  "tsx": "^4.21.0",
30
- "typescript": "^5.9.3"
35
+ "typescript": "^5.9.3",
36
+ "typescript-eslint": "^8.54.0"
31
37
  }
32
38
  }
@@ -1,20 +1,28 @@
1
+ import cookieParser from "cookie-parser";
1
2
  import express, { Application, Request, Response } from "express";
3
+ import helmet from "helmet";
4
+ import qs from "qs";
2
5
  import { cors } from "./config/cors";
3
- import { helmet } from "./config/helmet";
4
6
  import { logger } from "./config/logger";
5
7
  import { limiter } from "./config/rate-limit";
6
8
  import { apiRoutes } from "./routes";
7
- import { errorHandler } from "./shared/middlewares/error.middleware";
9
+ import { globalErrorHandler } from "./shared/middlewares/error.middleware";
8
10
  import { notFound } from "./shared/middlewares/not-found.middleware";
9
11
 
10
12
  // app initialization
11
13
  const app: Application = express();
12
- app.use(express.json());
13
14
 
14
- app.use(helmet);
15
+ // app settings
16
+ app.set("query parser", (str: string) => qs.parse(str));
17
+
18
+ // middlewares
19
+ app.use(express.json());
20
+ app.use(helmet());
15
21
  app.use(logger);
16
22
  app.use(cors);
17
23
  app.use(limiter);
24
+ app.use(cookieParser());
25
+ app.use(express.urlencoded({ extended: true }));
18
26
 
19
27
  // trust proxy when behind proxies (load balancers)
20
28
  if (process.env.NODE_ENV === "production") {
@@ -33,12 +41,12 @@ app.get("/", (_req: Request, res: Response) => {
33
41
  });
34
42
 
35
43
  // API routes
36
- app.use("/api", apiRoutes);
44
+ app.use("/api/v1", apiRoutes);
37
45
 
38
46
  // unhandled routes
39
47
  app.use(notFound);
40
48
 
41
49
  // Global error handler
42
- app.use(errorHandler);
50
+ app.use(globalErrorHandler);
43
51
 
44
- export default app;
52
+ export { app };
@@ -1,12 +1,13 @@
1
1
  import createCors from "cors";
2
- import { env } from "./env";
2
+ import { envVars } from "./env";
3
3
 
4
- const origin = env.isProduction
5
- ? process.env.CORS_ORIGIN
6
- ? process.env.CORS_ORIGIN.split(",").map((s) => s.trim())
7
- : false
8
- : true;
4
+ const origin = [envVars.FRONTEND_URL as string, "http://localhost:3000"];
9
5
 
10
- const cors = createCors({ origin });
6
+ const cors = createCors({
7
+ origin,
8
+ credentials: true,
9
+ methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
10
+ allowedHeaders: ["Content-Type", "Authorization"],
11
+ });
11
12
 
12
13
  export { cors };
@@ -1,12 +1,33 @@
1
1
  import dotenv from "dotenv";
2
+ import status from "http-status";
2
3
  import path from "path";
4
+ import { AppError } from "../shared/errors/app-error";
3
5
 
4
6
  dotenv.config({ path: path.join(process.cwd(), ".env") });
5
7
 
6
- const env = {
7
- port: Number(process.env.PORT) || 3000,
8
- node_env: process.env.NODE_ENV || "development",
9
- isProduction: (process.env.NODE_ENV || "development") === "production",
8
+ interface EnvConfig {
9
+ NODE_ENV: string;
10
+ PORT: string;
11
+ FRONTEND_URL?: string;
12
+ }
13
+
14
+ const loadEnvVars = (): EnvConfig => {
15
+ const requiredEnvVars = ["NODE_ENV", "PORT", "FRONTEND_URL"];
16
+
17
+ requiredEnvVars.forEach((varName) => {
18
+ if (!process.env[varName]) {
19
+ throw new AppError(
20
+ status.INTERNAL_SERVER_ERROR,
21
+ `Environment variable ${varName} is required but not set in .env file.`,
22
+ );
23
+ }
24
+ });
25
+
26
+ return {
27
+ NODE_ENV: process.env.NODE_ENV as string,
28
+ PORT: process.env.PORT as string,
29
+ FRONTEND_URL: process.env.FRONTEND_URL as string,
30
+ };
10
31
  };
11
32
 
12
- export { env };
33
+ export const envVars = loadEnvVars();
@@ -1,6 +1,6 @@
1
1
  import morgan from "morgan";
2
- import { env } from "./env";
2
+ import { envVars } from "./env";
3
3
 
4
- const logger = env.isProduction ? morgan("combined") : morgan("dev");
4
+ const logger = envVars.NODE_ENV === "production" ? morgan("combined") : morgan("dev");
5
5
 
6
6
  export { logger };
@@ -1,9 +1,9 @@
1
1
  import rateLimit from "express-rate-limit";
2
- import { env } from "./env";
2
+ import { envVars } from "./env";
3
3
 
4
4
  const limiter = rateLimit({
5
5
  windowMs: 15 * 60 * 1000, // 15 minutes
6
- max: env.isProduction ? 100 : 1000, // limit each IP
6
+ max: envVars.NODE_ENV === "production" ? 100 : 1000, // limit each IP
7
7
  standardHeaders: true,
8
8
  legacyHeaders: false,
9
9
  });
@@ -1,17 +1,19 @@
1
- import { NextFunction, Request, Response } from "express";
1
+ import { Request, Response } from "express";
2
+ import { catchAsync } from "../../shared/utils/catch-async";
3
+ import { sendResponse } from "../../shared/utils/send-response";
2
4
 
3
- const health = async (_req: Request, res: Response, next: NextFunction) => {
4
- try {
5
- res.status(200).json({
6
- success: true,
7
- message: "API is healthy!",
5
+ const health = catchAsync(async (_req: Request, res: Response) => {
6
+
7
+ sendResponse(res, {
8
+ status: 200,
9
+ success: true,
10
+ message: "Health test route is working!",
11
+ data: {
8
12
  timestamp: new Date().toISOString(),
9
13
  version: "1.0.0",
10
- });
11
- } catch (error) {
12
- next(error);
13
- }
14
- };
14
+ },
15
+ });
16
+ });
15
17
 
16
18
  export const healthController = {
17
19
  health,
@@ -2,11 +2,6 @@ import { Router } from "express";
2
2
  import { healthRoutes } from "../modules/health/health.route";
3
3
  const router = Router();
4
4
 
5
- // versioned API
6
- const v1 = Router();
7
-
8
- v1.use("/health", healthRoutes);
9
-
10
- router.use("/v1", v1);
5
+ router.use("/health", healthRoutes);
11
6
 
12
7
  export const apiRoutes = router;
@@ -1,14 +1,14 @@
1
- import app from "./app";
2
- import { env } from "./config/env";
1
+ import { app } from "./app";
2
+ import { envVars } from "./config/env";
3
3
 
4
- async function startServer() {
5
- const port = env.port;
6
- app.listen(port, () => {
7
- console.log(`Server is running on http://localhost:${port}`);
8
- });
9
- }
4
+ const bootstrap = async () => {
5
+ try {
6
+ app.listen(envVars.PORT, () => {
7
+ console.log(`Server is running on http://localhost:${envVars.PORT}`);
8
+ });
9
+ } catch (error) {
10
+ console.error("Failed to start server:", error);
11
+ }
12
+ };
10
13
 
11
- startServer().catch((err) => {
12
- console.error("Failed to start server:", err);
13
- process.exit(1);
14
- });
14
+ bootstrap();
@@ -0,0 +1,16 @@
1
+ class AppError extends Error {
2
+ public statusCode: number;
3
+
4
+ constructor(statusCode: number, message: string, stack = "") {
5
+ super(message);
6
+ this.statusCode = statusCode;
7
+
8
+ if (stack) {
9
+ this.stack = stack;
10
+ } else {
11
+ Error.captureStackTrace(this, this.constructor);
12
+ }
13
+ }
14
+ }
15
+
16
+ export { AppError };
@@ -1,18 +1,160 @@
1
- import { NextFunction, Request, Response } from "express";
2
- import { env } from "../../config/env";
1
+ import { ErrorRequestHandler } from "express";
2
+ import status from "http-status";
3
+ import { envVars } from "../../config/env";
4
+ import ApiError from "../errors/api-error";
5
+ import { AppError } from "../errors/app-error";
3
6
 
4
- export const errorHandler = (err: any, _req: Request, res: Response, _: NextFunction) => {
5
- const statusCode = err.status || 500;
6
- const errorMessage = err?.message || "Internal server error!";
7
+ type ErrorSource = {
8
+ path: string;
9
+ message: string;
10
+ };
7
11
 
8
- const payload: any = {
9
- success: false,
10
- message: errorMessage,
11
- };
12
+ type ErrorResponse = {
13
+ success: false;
14
+ message: string;
15
+ errorSources: ErrorSource[];
16
+ stack?: string;
17
+ };
18
+
19
+ const isRecord = (value: unknown): value is Record<string, unknown> => {
20
+ return typeof value === "object" && value !== null;
21
+ };
22
+
23
+ const toErrorSource = (value: unknown): ErrorSource | null => {
24
+ if (!isRecord(value)) return null;
25
+
26
+ const message = typeof value.message === "string" ? value.message : "Unknown error";
27
+ const path =
28
+ typeof value.path === "string"
29
+ ? value.path
30
+ : typeof value.field === "string"
31
+ ? value.field
32
+ : "";
33
+
34
+ return { path, message };
35
+ };
36
+
37
+ const getStatusCode = (value: unknown): number | null => {
38
+ if (!isRecord(value)) return null;
39
+
40
+ const statusCodeCandidate =
41
+ typeof value.statusCode === "number"
42
+ ? value.statusCode
43
+ : typeof value.status === "number"
44
+ ? value.status
45
+ : null;
12
46
 
13
- if (!env.isProduction) {
14
- payload.errors = err?.stack || err;
47
+ if (
48
+ typeof statusCodeCandidate === "number" &&
49
+ Number.isInteger(statusCodeCandidate) &&
50
+ statusCodeCandidate >= 400 &&
51
+ statusCodeCandidate <= 599
52
+ ) {
53
+ return statusCodeCandidate;
15
54
  }
16
55
 
17
- res.status(statusCode).json(payload);
56
+ return null;
18
57
  };
58
+
59
+ const isJsonParseError = (value: unknown): value is SyntaxError => {
60
+ if (!(value instanceof SyntaxError)) return false;
61
+ if (!isRecord(value)) return false;
62
+
63
+ return "body" in value;
64
+ };
65
+
66
+ const isZodLikeError = (
67
+ value: unknown,
68
+ ): value is {
69
+ issues: Array<{ path: Array<string | number>; message: string }>;
70
+ stack?: string;
71
+ } => {
72
+ if (!isRecord(value)) return false;
73
+ if (!Array.isArray(value.issues)) return false;
74
+
75
+ return value.issues.every((issue) => {
76
+ if (!isRecord(issue)) return false;
77
+ if (!Array.isArray(issue.path)) return false;
78
+ if (typeof issue.message !== "string") return false;
79
+
80
+ return issue.path.every(
81
+ (segment) => typeof segment === "string" || typeof segment === "number",
82
+ );
83
+ });
84
+ };
85
+
86
+ const errorHandler: ErrorRequestHandler = (err, _req, res) => {
87
+ const isDevelopment = envVars.NODE_ENV === "development";
88
+
89
+ if (isDevelopment) {
90
+ console.error("Global Error Handler:", err);
91
+ }
92
+
93
+ let errorSources: ErrorSource[] = [];
94
+ let statusCode: number = status.INTERNAL_SERVER_ERROR;
95
+ let message = "Internal Server Error";
96
+ let stack: string | undefined;
97
+
98
+ if (isZodLikeError(err)) {
99
+ statusCode = status.BAD_REQUEST;
100
+ message = "Validation Error";
101
+ errorSources = err.issues.map((issue) => ({
102
+ path: issue.path.join("."),
103
+ message: issue.message,
104
+ }));
105
+ stack = err.stack;
106
+ } else if (err instanceof AppError || err instanceof ApiError) {
107
+ statusCode = err.statusCode;
108
+ message = err.message;
109
+ stack = err.stack;
110
+ errorSources = [{ path: "", message: err.message }];
111
+ } else if (isJsonParseError(err)) {
112
+ statusCode = status.BAD_REQUEST;
113
+ message = "Invalid JSON payload";
114
+ stack = err.stack;
115
+ errorSources = [{ path: "body", message }];
116
+ } else if (err instanceof Error) {
117
+ statusCode = getStatusCode(err) ?? status.INTERNAL_SERVER_ERROR;
118
+ message = err.message || message;
119
+ stack = err.stack;
120
+ errorSources = [{ path: "", message }];
121
+ } else if (isRecord(err)) {
122
+ statusCode = getStatusCode(err) ?? status.INTERNAL_SERVER_ERROR;
123
+
124
+ if (typeof err.message === "string" && err.message.length > 0) {
125
+ message = err.message;
126
+ }
127
+
128
+ const sources: ErrorSource[] = [];
129
+ if (Array.isArray(err.errorSources)) {
130
+ err.errorSources.forEach((source) => {
131
+ const normalized = toErrorSource(source);
132
+ if (normalized) {
133
+ sources.push(normalized);
134
+ }
135
+ });
136
+ }
137
+
138
+ if (Array.isArray(err.errors)) {
139
+ err.errors.forEach((source) => {
140
+ const normalized = toErrorSource(source);
141
+ if (normalized) {
142
+ sources.push(normalized);
143
+ }
144
+ });
145
+ }
146
+
147
+ errorSources = sources.length > 0 ? sources : [{ path: "", message: String(message) }];
148
+ }
149
+
150
+ const errorResponse: ErrorResponse = {
151
+ success: false,
152
+ message,
153
+ errorSources,
154
+ stack: isDevelopment ? stack : undefined,
155
+ };
156
+
157
+ res.status(statusCode).json(errorResponse);
158
+ };
159
+
160
+ export const globalErrorHandler = errorHandler;
@@ -1,7 +1,8 @@
1
1
  import { Request, Response } from "express";
2
+ import status from "http-status";
2
3
 
3
4
  export function notFound(req: Request, res: Response) {
4
- res.status(404).json({
5
+ res.status(status.NOT_FOUND).json({
5
6
  message: `Can't find ${req.originalUrl} on this server!`,
6
7
  path: req.originalUrl,
7
8
  date: Date(),
@@ -0,0 +1,11 @@
1
+ import { NextFunction, Request, RequestHandler, Response } from "express";
2
+
3
+ export const catchAsync = (fn: RequestHandler) => {
4
+ return async (req: Request, res: Response, next: NextFunction) => {
5
+ try {
6
+ await fn(req, res, next);
7
+ } catch (error: unknown) {
8
+ next(error);
9
+ }
10
+ };
11
+ };
@@ -1,4 +1,9 @@
1
- export function getPagination(query: any) {
1
+ type PaginationQuery = {
2
+ page?: string | number;
3
+ limit?: string | number;
4
+ };
5
+
6
+ export function getPagination(query: PaginationQuery) {
2
7
  const page = Math.max(1, parseInt(String(query.page || "1"), 10) || 1);
3
8
  const limit = Math.min(100, parseInt(String(query.limit || "10"), 10) || 10);
4
9
  const skip = (page - 1) * limit;
@@ -0,0 +1,25 @@
1
+ import { Response } from "express";
2
+
3
+ interface IResponseData<T> {
4
+ status: number;
5
+ success: boolean;
6
+ message: string;
7
+ data?: T;
8
+ meta?: {
9
+ page: number;
10
+ limit: number;
11
+ total: number;
12
+ totalPages: number;
13
+ };
14
+ }
15
+
16
+ export const sendResponse = <T>(res: Response, responseData: IResponseData<T>) => {
17
+ const { status, success, message, data, meta } = responseData;
18
+
19
+ res.status(status).json({
20
+ success,
21
+ message,
22
+ data,
23
+ meta,
24
+ });
25
+ };
@@ -1,8 +1,19 @@
1
- export const env = {
2
- app: {
3
- url: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
4
- },
5
-
6
- isDev: process.env.NODE_ENV === "development",
7
- isProd: process.env.NODE_ENV === "production",
8
- } as const;
1
+ interface EnvVars {
2
+ APP_URL: string;
3
+ }
4
+
5
+ const loadEnvVars = (): EnvVars => {
6
+ const requiredEnvVars = [""];
7
+
8
+ requiredEnvVars.forEach((varName) => {
9
+ if (!process.env[varName]) {
10
+ throw new Error(`Environment variable ${varName} is required but not defined.`);
11
+ }
12
+ });
13
+
14
+ return {
15
+ APP_URL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
16
+ };
17
+ };
18
+
19
+ export const envVars = loadEnvVars();
@@ -1,33 +0,0 @@
1
- import nodemailer from "nodemailer";
2
-
3
- // Create email transporter
4
- const transporter = nodemailer.createTransport({
5
- host: process.env.EMAIL_HOST,
6
- port: parseInt(process.env.EMAIL_PORT || "587"),
7
- secure: process.env.EMAIL_PORT === "465",
8
- auth: {
9
- user: process.env.EMAIL_USER,
10
- pass: process.env.EMAIL_PASS,
11
- },
12
- });
13
-
14
- // Send email function
15
- export const sendEmail = async ({ to, subject, text, html }: {
16
- to: string;
17
- subject: string;
18
- text?: string;
19
- html?: string;
20
- }) => {
21
- try {
22
- await transporter.sendMail({
23
- from: process.env.EMAIL_FROM,
24
- to,
25
- subject,
26
- text,
27
- html,
28
- });
29
- } catch (error) {
30
- console.error("Email sending failed:", error);
31
- throw error;
32
- }
33
- };
@@ -1,89 +0,0 @@
1
- export const getVerificationEmailTemplate = (user: { name?: string; email: string }, url: string) => {
2
- const html = `
3
- <!DOCTYPE html>
4
- <html lang="en">
5
- <head>
6
- <meta charset="UTF-8">
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
- <title>Verify Your Email</title>
9
- <style>
10
- body { font-family: Arial, sans-serif; line-height: 1.6; color: #000; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #fff; }
11
- .header { padding: 20px; text-align: center; border-bottom: 1px solid #000; }
12
- .content { padding: 20px; }
13
- .button { display: inline-block; background-color: #fff; color: #000; padding: 10px 20px; text-decoration: none; border: 1px solid #000; border-radius: 5px; margin: 20px 0; }
14
- .footer { font-size: 12px; color: #000; text-align: center; margin-top: 20px; border-top: 1px solid #000; padding-top: 20px; }
15
- </style>
16
- </head>
17
- <body>
18
- <div class="header">
19
- <h1>Verify Your Email Address</h1>
20
- </div>
21
- <div class="content">
22
- <p>Hi ${user.name || user.email},</p>
23
- <p>Thank you for signing up. Please verify your email address to complete your registration.</p>
24
- <a href="${url}" class="button">Verify Email</a>
25
- <p>If the button doesn't work, copy and paste this link: <a href="${url}">${url}</a></p>
26
- <p>This link expires in 24 hours.</p>
27
- </div>
28
- <div class="footer">
29
- <p>If you didn't create an account, ignore this email.</p>
30
- </div>
31
- </body>
32
- </html>
33
- `;
34
-
35
- const text = `Hi ${user.name || user.email},
36
-
37
- Thank you for signing up. Please verify your email address by clicking this link: ${url}
38
-
39
- This link expires in 24 hours.
40
-
41
- If you didn't create an account, ignore this email.`;
42
-
43
- return { html, text };
44
- };
45
-
46
- export const getPasswordResetEmailTemplate = (user: { name?: string; email: string }, url: string) => {
47
- const html = `
48
- <!DOCTYPE html>
49
- <html lang="en">
50
- <head>
51
- <meta charset="UTF-8">
52
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
53
- <title>Reset Your Password</title>
54
- <style>
55
- body { font-family: Arial, sans-serif; line-height: 1.6; color: #000; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #fff; }
56
- .header { padding: 20px; text-align: center; border-bottom: 1px solid #000; }
57
- .content { padding: 20px; }
58
- .button { display: inline-block; background-color: #fff; color: #000; padding: 10px 20px; text-decoration: none; border: 1px solid #000; border-radius: 5px; margin: 20px 0; }
59
- .footer { font-size: 12px; color: #000; text-align: center; margin-top: 20px; border-top: 1px solid #000; padding-top: 20px; }
60
- </style>
61
- </head>
62
- <body>
63
- <div class="header">
64
- <h1>Reset Your Password</h1>
65
- </div>
66
- <div class="content">
67
- <p>Hi ${user.name || user.email},</p>
68
- <p>You requested a password reset. Click the link below to reset your password.</p>
69
- <a href="${url}" class="button">Reset Password</a>
70
- <p>If the button doesn't work, copy and paste this link: <a href="${url}">${url}</a></p>
71
- <p>This link expires in 1 hour.</p>
72
- </div>
73
- <div class="footer">
74
- <p>If you didn't request this, ignore this email.</p>
75
- </div>
76
- </body>
77
- </html>
78
- `;
79
-
80
- const text = `Hi ${user.name || user.email},
81
-
82
- You requested a password reset. Click this link to reset your password: ${url}
83
-
84
- This link expires in 1 hour.
85
-
86
- If you didn't request this, ignore this email.`;
87
-
88
- return { html, text };
89
- };