startx 0.0.1
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/.editorconfig +20 -0
- package/.prettierignore +24 -0
- package/.prettierrc.js +52 -0
- package/.vscode/settings.json +3 -0
- package/LICENSE +21 -0
- package/apps/core-server/.env.example +24 -0
- package/apps/core-server/Dockerfile +61 -0
- package/apps/core-server/eslint.config.mjs +47 -0
- package/apps/core-server/package.json +73 -0
- package/apps/core-server/src/config/custom-type.ts +54 -0
- package/apps/core-server/src/events/index.ts +37 -0
- package/apps/core-server/src/index.ts +19 -0
- package/apps/core-server/src/middlewares/auth-middleware.ts +50 -0
- package/apps/core-server/src/middlewares/cors-middleware.ts +6 -0
- package/apps/core-server/src/middlewares/error-middleware.ts +23 -0
- package/apps/core-server/src/middlewares/logger-middleware.ts +21 -0
- package/apps/core-server/src/middlewares/notfound-middleware.ts +14 -0
- package/apps/core-server/src/middlewares/serve-static.ts +24 -0
- package/apps/core-server/src/routes/files/router.ts +7 -0
- package/apps/core-server/src/routes/server.ts +36 -0
- package/apps/core-server/tsconfig.json +10 -0
- package/apps/core-server/tsdown.config.ts +14 -0
- package/biome.json +62 -0
- package/configs/eslint-config/package.json +60 -0
- package/configs/eslint-config/plugins.d.ts +1 -0
- package/configs/eslint-config/src/configs/base.ts +237 -0
- package/configs/eslint-config/src/configs/frontend.ts +62 -0
- package/configs/eslint-config/src/configs/node.ts +10 -0
- package/configs/eslint-config/src/plugin.ts +25 -0
- package/configs/eslint-config/src/rules/index.ts +30 -0
- package/configs/eslint-config/src/rules/no-argument-spread.test.ts +47 -0
- package/configs/eslint-config/src/rules/no-argument-spread.ts +96 -0
- package/configs/eslint-config/src/rules/no-dynamic-import-template.ts +32 -0
- package/configs/eslint-config/src/rules/no-internal-package-import.ts +40 -0
- package/configs/eslint-config/src/rules/no-interpolation-in-regular-string.ts +32 -0
- package/configs/eslint-config/src/rules/no-json-parse-json-stringify.test.ts +34 -0
- package/configs/eslint-config/src/rules/no-json-parse-json-stringify.ts +49 -0
- package/configs/eslint-config/src/rules/no-plain-errors.ts +50 -0
- package/configs/eslint-config/src/rules/no-skipped-tests.ts +61 -0
- package/configs/eslint-config/src/rules/no-top-level-relative-imports-in-backend-module.ts +27 -0
- package/configs/eslint-config/src/rules/no-type-unsafe-event-emitter.ts +33 -0
- package/configs/eslint-config/src/rules/no-uncaught-json-parse.test.ts +21 -0
- package/configs/eslint-config/src/rules/no-uncaught-json-parse.ts +45 -0
- package/configs/eslint-config/src/rules/no-untyped-config-class-field.ts +26 -0
- package/configs/eslint-config/src/rules/no-unused-param-catch-clause.ts +33 -0
- package/configs/eslint-config/src/rules/no-useless-catch-throw.test.ts +34 -0
- package/configs/eslint-config/src/rules/no-useless-catch-throw.ts +47 -0
- package/configs/eslint-config/src/utils/json.ts +21 -0
- package/configs/eslint-config/tsconfig.json +8 -0
- package/configs/eslint-config/tsdown.config.ts +11 -0
- package/configs/eslint-config/vitest.config.ts +3 -0
- package/configs/tsdown-config/package.json +14 -0
- package/configs/tsdown-config/src/config/tsdown.base.ts +13 -0
- package/configs/typescript-config/package.json +10 -0
- package/configs/typescript-config/tsconfig.common.json +32 -0
- package/configs/typescript-config/tsconfig.frontend.json +14 -0
- package/configs/typescript-config/tsconfig.node.json +9 -0
- package/configs/vitest-config/package.json +25 -0
- package/configs/vitest-config/src/base.ts +34 -0
- package/configs/vitest-config/src/frontend.ts +15 -0
- package/configs/vitest-config/src/node.ts +5 -0
- package/configs/vitest-config/tsconfig.json +7 -0
- package/package.json +47 -0
- package/packages/@repo/constants/eslint.config.mjs +21 -0
- package/packages/@repo/constants/package.json +19 -0
- package/packages/@repo/constants/src/api.ts +1 -0
- package/packages/@repo/constants/src/index.ts +8 -0
- package/packages/@repo/constants/src/time.ts +23 -0
- package/packages/@repo/constants/tsconfig.json +7 -0
- package/packages/@repo/db/eslint.config.mjs +21 -0
- package/packages/@repo/db/package.json +30 -0
- package/packages/@repo/db/src/functions.ts +122 -0
- package/packages/@repo/db/src/index.ts +20 -0
- package/packages/@repo/db/src/schema/common.ts +49 -0
- package/packages/@repo/db/src/schema/index.ts +1 -0
- package/packages/@repo/db/tsconfig.json +13 -0
- package/packages/@repo/lib/eslint.config.mjs +49 -0
- package/packages/@repo/lib/package.json +57 -0
- package/packages/@repo/lib/src/bucket-module/file-storage.ts +49 -0
- package/packages/@repo/lib/src/bucket-module/s3-storage.ts +114 -0
- package/packages/@repo/lib/src/bucket-module/utils.ts +11 -0
- package/packages/@repo/lib/src/command-module.ts +77 -0
- package/packages/@repo/lib/src/constants.ts +3 -0
- package/packages/@repo/lib/src/cookie-module.ts +42 -0
- package/packages/@repo/lib/src/custom-type.ts +54 -0
- package/packages/@repo/lib/src/env.ts +13 -0
- package/packages/@repo/lib/src/error-handlers-module/index.ts +11 -0
- package/packages/@repo/lib/src/file-system/index.ts +90 -0
- package/packages/@repo/lib/src/hashing-module.ts +9 -0
- package/packages/@repo/lib/src/index.ts +27 -0
- package/packages/@repo/lib/src/logger-module/log-config.ts +16 -0
- package/packages/@repo/lib/src/logger-module/logger.ts +78 -0
- package/packages/@repo/lib/src/logger-module/memory-profiler.ts +65 -0
- package/packages/@repo/lib/src/mail-module/api.ts +0 -0
- package/packages/@repo/lib/src/mail-module/mock.ts +8 -0
- package/packages/@repo/lib/src/mail-module/nodemailer.ts +45 -0
- package/packages/@repo/lib/src/notification-module/index.ts +172 -0
- package/packages/@repo/lib/src/notification-module/push-notification.ts +90 -0
- package/packages/@repo/lib/src/oauth2-client.ts +109 -0
- package/packages/@repo/lib/src/otp-module.ts +98 -0
- package/packages/@repo/lib/src/pagination-module.ts +49 -0
- package/packages/@repo/lib/src/token-module.ts +35 -0
- package/packages/@repo/lib/src/user-session.ts +117 -0
- package/packages/@repo/lib/src/utils.ts +42 -0
- package/packages/@repo/lib/src/validation-module.ts +187 -0
- package/packages/@repo/lib/tsconfig.json +7 -0
- package/packages/@repo/mail/package.json +29 -0
- package/packages/@repo/mail/src/emails/admin/OtpEmail.tsx +168 -0
- package/packages/@repo/mail/src/index.ts +13 -0
- package/packages/@repo/mail/tsconfig.build.json +14 -0
- package/packages/@repo/mail/tsconfig.json +13 -0
- package/packages/@repo/mail/tsdown.config.ts +9 -0
- package/packages/@repo/redis/eslint.config.mjs +8 -0
- package/packages/@repo/redis/package.json +31 -0
- package/packages/@repo/redis/src/index.ts +2 -0
- package/packages/@repo/redis/src/lib/redis-client.ts +23 -0
- package/packages/@repo/redis/src/lib/redis-module.ts +3 -0
- package/packages/@repo/redis/tsconfig.json +12 -0
- package/packages/ui/components.json +17 -0
- package/packages/ui/eslint.config.mjs +18 -0
- package/packages/ui/package.json +67 -0
- package/packages/ui/postcss.config.mjs +9 -0
- package/packages/ui/src/components/custom/form-wrapper.tsx +551 -0
- package/packages/ui/src/components/custom/grid-component.tsx +23 -0
- package/packages/ui/src/components/custom/hover-tool.tsx +38 -0
- package/packages/ui/src/components/custom/image-picker.tsx +109 -0
- package/packages/ui/src/components/custom/no-content.tsx +37 -0
- package/packages/ui/src/components/custom/page-container.tsx +24 -0
- package/packages/ui/src/components/custom/page-section.tsx +59 -0
- package/packages/ui/src/components/custom/simple-popover.tsx +29 -0
- package/packages/ui/src/components/custom/switch-component.tsx +20 -0
- package/packages/ui/src/components/custom/theme-provider.tsx +74 -0
- package/packages/ui/src/components/custom/typography.tsx +111 -0
- package/packages/ui/src/components/extensions/carousel.tsx +392 -0
- package/packages/ui/src/components/hooks/event/use-click.tsx +39 -0
- package/packages/ui/src/components/hooks/time/useDebounce.tsx +21 -0
- package/packages/ui/src/components/hooks/time/useInterval.tsx +35 -0
- package/packages/ui/src/components/hooks/time/useTimeout.tsx +19 -0
- package/packages/ui/src/components/hooks/time/useTimer.tsx +51 -0
- package/packages/ui/src/components/hooks/use-media-query.tsx +19 -0
- package/packages/ui/src/components/hooks/use-persistent-storage.tsx +52 -0
- package/packages/ui/src/components/hooks/use-update-effect.tsx +13 -0
- package/packages/ui/src/components/hooks/use-window-dimension.tsx +30 -0
- package/packages/ui/src/components/lib/utils.ts +242 -0
- package/packages/ui/src/components/lucide.tsx +3 -0
- package/packages/ui/src/components/sonner.tsx +1 -0
- package/packages/ui/src/components/ui/alert-dialog.tsx +116 -0
- package/packages/ui/src/components/ui/avatar.tsx +53 -0
- package/packages/ui/src/components/ui/badge.tsx +46 -0
- package/packages/ui/src/components/ui/breadcrumb.tsx +109 -0
- package/packages/ui/src/components/ui/button.tsx +96 -0
- package/packages/ui/src/components/ui/card.tsx +92 -0
- package/packages/ui/src/components/ui/carousel.tsx +243 -0
- package/packages/ui/src/components/ui/checkbox.tsx +32 -0
- package/packages/ui/src/components/ui/command.tsx +155 -0
- package/packages/ui/src/components/ui/dialog.tsx +127 -0
- package/packages/ui/src/components/ui/dropdown-menu.tsx +226 -0
- package/packages/ui/src/components/ui/form.tsx +165 -0
- package/packages/ui/src/components/ui/input-otp.tsx +76 -0
- package/packages/ui/src/components/ui/input.tsx +21 -0
- package/packages/ui/src/components/ui/label.tsx +24 -0
- package/packages/ui/src/components/ui/multiple-select.tsx +510 -0
- package/packages/ui/src/components/ui/popover.tsx +42 -0
- package/packages/ui/src/components/ui/select.tsx +170 -0
- package/packages/ui/src/components/ui/separator.tsx +28 -0
- package/packages/ui/src/components/ui/sheet.tsx +130 -0
- package/packages/ui/src/components/ui/skeleton.tsx +13 -0
- package/packages/ui/src/components/ui/spinner.tsx +16 -0
- package/packages/ui/src/components/ui/switch.tsx +28 -0
- package/packages/ui/src/components/ui/table.tsx +116 -0
- package/packages/ui/src/components/ui/tabs.tsx +54 -0
- package/packages/ui/src/components/ui/textarea.tsx +18 -0
- package/packages/ui/src/components/ui/timeline.tsx +118 -0
- package/packages/ui/src/components/ui/tooltip.tsx +30 -0
- package/packages/ui/src/components/util/n-formattor.ts +22 -0
- package/packages/ui/src/components/util/storage.ts +37 -0
- package/packages/ui/src/globals.css +87 -0
- package/packages/ui/tailwind.config.ts +94 -0
- package/packages/ui/tsconfig.json +12 -0
- package/pnpm-workspace.yaml +43 -0
- package/turbo.json +77 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TODO: connect to bucket
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
DeleteObjectCommand,
|
|
6
|
+
PutObjectCommand,
|
|
7
|
+
type PutObjectCommandInput,
|
|
8
|
+
S3Client,
|
|
9
|
+
} from "@aws-sdk/client-s3";
|
|
10
|
+
|
|
11
|
+
const s3Endpoint = process.env.S3_ENDPOINT;
|
|
12
|
+
|
|
13
|
+
const credentials = {
|
|
14
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
15
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Create an S3 service client object.
|
|
19
|
+
const s3Client = new S3Client({
|
|
20
|
+
credentials,
|
|
21
|
+
region: process.env.AWS_REGION,
|
|
22
|
+
endpoint: s3Endpoint,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const bucket = process.env.AWS_BUCKET;
|
|
26
|
+
|
|
27
|
+
export type UploadFile = {
|
|
28
|
+
name: string;
|
|
29
|
+
mimetype: string;
|
|
30
|
+
data: Buffer;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class S3Bucket {
|
|
34
|
+
static async uploadFile(file: UploadFile, path: string) {
|
|
35
|
+
let key = `${path}`;
|
|
36
|
+
if (!key.includes(".")) {
|
|
37
|
+
key += ".jpeg";
|
|
38
|
+
}
|
|
39
|
+
const params = {
|
|
40
|
+
Bucket: bucket,
|
|
41
|
+
Key: key,
|
|
42
|
+
Body: file.data,
|
|
43
|
+
ContentType: file.mimetype,
|
|
44
|
+
} as PutObjectCommandInput;
|
|
45
|
+
const command = new PutObjectCommand(params);
|
|
46
|
+
await s3Client.send(command);
|
|
47
|
+
const fileUrl = S3Bucket.getAwsUrl(key);
|
|
48
|
+
return fileUrl;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static getAwsUrl(path: string) {
|
|
52
|
+
return `https://${bucket}.s3.${process.env.AWS_REGION}.amazonaws.com/${path}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static getKeyFromUrl(url: string) {
|
|
56
|
+
const key = url.split(`https://${bucket}.s3.${process.env.AWS_REGION}.amazonaws.com/`)[1];
|
|
57
|
+
return key;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static async uploadFiles({ path, files }: { path: string; files: UploadFile[] }) {
|
|
61
|
+
try {
|
|
62
|
+
if (!path) throw new Error("Path must be provided.");
|
|
63
|
+
if (!files) throw new Error("File must be provided.");
|
|
64
|
+
// const buffers: UploadFile[] = [];
|
|
65
|
+
// for (const key in file) {
|
|
66
|
+
// const item = file[key];
|
|
67
|
+
// if (Array.isArray(item)) item.forEach((e) => buffers.push(e));
|
|
68
|
+
// else buffers.push(item!);
|
|
69
|
+
// }
|
|
70
|
+
|
|
71
|
+
// let upload = [] as { url: string; type: string; name: string }[];
|
|
72
|
+
const upload = await Promise.all(
|
|
73
|
+
files.map(async (file) => {
|
|
74
|
+
const key = file.name;
|
|
75
|
+
const params: PutObjectCommandInput = {
|
|
76
|
+
Bucket: bucket,
|
|
77
|
+
Key: `${path}/${key}`,
|
|
78
|
+
Body: file.data,
|
|
79
|
+
ContentType: file.mimetype,
|
|
80
|
+
};
|
|
81
|
+
const command = new PutObjectCommand(params);
|
|
82
|
+
await s3Client.send(command);
|
|
83
|
+
return {
|
|
84
|
+
url: S3Bucket.getAwsUrl(`${path}/${key}`),
|
|
85
|
+
type: file.mimetype,
|
|
86
|
+
name: file.name,
|
|
87
|
+
};
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
return upload;
|
|
91
|
+
// if (upload.length > 0) return upload;
|
|
92
|
+
// else throw new Error("Something went wrong.");
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.log(error);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ! don't use
|
|
100
|
+
static async deleteFile(path: string) {
|
|
101
|
+
try {
|
|
102
|
+
const params = {
|
|
103
|
+
Bucket: bucket,
|
|
104
|
+
Key: path,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const command = new DeleteObjectCommand(params);
|
|
108
|
+
await s3Client.send(command);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.log(error);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { S3Bucket } from "./s3-storage.js";
|
|
2
|
+
|
|
3
|
+
export const pathGenerator = {
|
|
4
|
+
asset: {
|
|
5
|
+
base: "image/assets",
|
|
6
|
+
supportImage: "image/assets/bemyguest_support.png",
|
|
7
|
+
},
|
|
8
|
+
profile: (userId: string) => `user/${userId}/profile`,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const defaultAvatarUrl = `${S3Bucket.getAwsUrl("image/assets/avatar_image.png")}`;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { logger } from "./logger-module/logger.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @description Custom command class that extends for creating custom commands
|
|
5
|
+
* @constructor
|
|
6
|
+
* @param name
|
|
7
|
+
* @param description
|
|
8
|
+
* @param fn
|
|
9
|
+
*/
|
|
10
|
+
export class Command {
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
fn: (flags: string[]) => void;
|
|
14
|
+
constructor({
|
|
15
|
+
description = "",
|
|
16
|
+
fn,
|
|
17
|
+
name,
|
|
18
|
+
}: {
|
|
19
|
+
name: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
fn: (flags: string[]) => void;
|
|
22
|
+
}) {
|
|
23
|
+
this.name = name;
|
|
24
|
+
this.description = description;
|
|
25
|
+
this.fn = fn;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @description Custom command executer class
|
|
31
|
+
*/
|
|
32
|
+
export class CommandExecuter {
|
|
33
|
+
/**
|
|
34
|
+
* @description Available commands
|
|
35
|
+
*/
|
|
36
|
+
static commands = new Map<string, Command>();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @description Register a new command
|
|
40
|
+
* @param command
|
|
41
|
+
*/
|
|
42
|
+
static register(command: Command) {
|
|
43
|
+
this.commands.set(command.name, command);
|
|
44
|
+
}
|
|
45
|
+
static registerAll(commands: Command[]) {
|
|
46
|
+
for (const command of commands) {
|
|
47
|
+
this.register(command);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @description execute the command from console
|
|
53
|
+
* @param string
|
|
54
|
+
*/
|
|
55
|
+
static async start() {
|
|
56
|
+
const command = process.argv[2];
|
|
57
|
+
const flags = process.argv.slice(3);
|
|
58
|
+
if (command === undefined) {
|
|
59
|
+
CommandExecuter.listCommands();
|
|
60
|
+
} else if (this.commands.has(command)) {
|
|
61
|
+
this.commands.get(command)?.fn(flags);
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
63
|
+
logger.info(`Command executed: ${command}`);
|
|
64
|
+
process.exit();
|
|
65
|
+
} else {
|
|
66
|
+
console.log(`Unknown command: ${command}`);
|
|
67
|
+
CommandExecuter.listCommands();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static listCommands() {
|
|
72
|
+
console.log("Available commands:");
|
|
73
|
+
for (const command of this.commands.values()) {
|
|
74
|
+
console.log(`- ${command.name}: ${command.description}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CookieOptions } from "express";
|
|
2
|
+
|
|
3
|
+
const constants = {
|
|
4
|
+
refreshToken: {
|
|
5
|
+
cookie: "",
|
|
6
|
+
cookieDomain: "",
|
|
7
|
+
secureCookie: false,
|
|
8
|
+
maxAge: 0,
|
|
9
|
+
},
|
|
10
|
+
initialize() {
|
|
11
|
+
const prodMaxAge = 1000 * 60 * 60 * 24 * 30;
|
|
12
|
+
const stagingMaxAge = 1000 * 60 * 60 * 24 * 365;
|
|
13
|
+
const prodEnv = process.env.NODE_ENV === "production";
|
|
14
|
+
const stagingEnv = process.env.NODE_ENV === "staging";
|
|
15
|
+
|
|
16
|
+
if (prodEnv) {
|
|
17
|
+
this.refreshToken.cookie = "refresh-token";
|
|
18
|
+
this.refreshToken.cookieDomain = process.env.COOKIE_DOMAIN;
|
|
19
|
+
this.refreshToken.secureCookie = true;
|
|
20
|
+
this.refreshToken.maxAge = prodMaxAge;
|
|
21
|
+
} else {
|
|
22
|
+
this.refreshToken.cookie = "refresh-token-staging";
|
|
23
|
+
this.refreshToken.cookieDomain = stagingEnv
|
|
24
|
+
? process.env.COOKIE_DOMAIN
|
|
25
|
+
: process.env.COOKIE_DOMAIN || "localhost";
|
|
26
|
+
this.refreshToken.secureCookie = stagingEnv ? true : false;
|
|
27
|
+
this.refreshToken.maxAge = stagingMaxAge;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
constants.initialize();
|
|
32
|
+
|
|
33
|
+
export function getCookieOptions(refreshToken: string): [string, string, CookieOptions] {
|
|
34
|
+
const cookieOptions: CookieOptions = {
|
|
35
|
+
domain: constants.refreshToken.cookieDomain,
|
|
36
|
+
maxAge: constants.refreshToken.maxAge,
|
|
37
|
+
sameSite: "lax",
|
|
38
|
+
httpOnly: true,
|
|
39
|
+
secure: constants.refreshToken.secureCookie,
|
|
40
|
+
};
|
|
41
|
+
return [constants.refreshToken.cookie, refreshToken, cookieOptions];
|
|
42
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
2
|
+
import type { SessionUser } from "./user-session.js";
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
namespace NodeJS {
|
|
6
|
+
export interface ProcessEnv {
|
|
7
|
+
// from .env
|
|
8
|
+
COOKIE_DOMAIN: string;
|
|
9
|
+
NODE_ENV: "development" | "staging" | "production" | "test";
|
|
10
|
+
DATABASE_URL: string;
|
|
11
|
+
DATABASE_AUTH_TOKEN: string;
|
|
12
|
+
ACCESS_TOKEN_SECRET: string;
|
|
13
|
+
REFRESH_TOKEN_SECRET: string;
|
|
14
|
+
OAUTH_STATE_TOKEN_SECRET: string;
|
|
15
|
+
SUPPORT_MAIL: string;
|
|
16
|
+
AWS_ACCESS_KEY_ID: string;
|
|
17
|
+
AWS_SECRET_ACCESS_KEY: string;
|
|
18
|
+
AWS_REGION: string;
|
|
19
|
+
AWS_BUCKET: string;
|
|
20
|
+
GOOGLE_CLIENT_ID: string;
|
|
21
|
+
GOOGLE_CLIENT_SECRET: string;
|
|
22
|
+
GOOGLE_REDIRECT_URI: string;
|
|
23
|
+
|
|
24
|
+
CLIENT_URL: string;
|
|
25
|
+
SERVER_URL: string;
|
|
26
|
+
REDIS_URI: string;
|
|
27
|
+
REDIS_PORT: string;
|
|
28
|
+
REDIS_USERNAME: string;
|
|
29
|
+
REDIS_PASSWORD: string;
|
|
30
|
+
FIREBASE_PROJECT_ID: string;
|
|
31
|
+
STRIPE_WEBHOOK_SECRET: string;
|
|
32
|
+
STRIPE_SECRET_KEY: string;
|
|
33
|
+
|
|
34
|
+
SMTP_HOST: string;
|
|
35
|
+
SMTP_PORT: string;
|
|
36
|
+
SMTP_USER: string;
|
|
37
|
+
SMTP_PASSWORD: string;
|
|
38
|
+
SENDER_MAIL: string;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
namespace Express {
|
|
43
|
+
export interface Request {
|
|
44
|
+
user: SessionUser;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare module "http" {
|
|
50
|
+
interface IncomingMessage {
|
|
51
|
+
user: SessionUser;
|
|
52
|
+
body: any;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as dotenv from "dotenv";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
import { __dirname } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
if (process.env.NODE_ENV === "test") {
|
|
7
|
+
console.log("loading test env", process.cwd(), __dirname());
|
|
8
|
+
dotenv.config({ path: path.join(__dirname(), ".env.test") });
|
|
9
|
+
} else {
|
|
10
|
+
dotenv.config();
|
|
11
|
+
dotenv.config({ path: path.join(__dirname(), ".env") });
|
|
12
|
+
}
|
|
13
|
+
export {dotenv};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Custom error class that extends the built-in Error class for error responses
|
|
3
|
+
*/
|
|
4
|
+
export class ErrorResponse extends Error {
|
|
5
|
+
constructor(public message: string, public statusCode: number) {
|
|
6
|
+
super(message); // Call the parent constructor with the message
|
|
7
|
+
|
|
8
|
+
// Set the prototype chain explicitly for older environments (optional)
|
|
9
|
+
Object.setPrototypeOf(this, ErrorResponse.prototype);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import fsExists from "fs.promises.exists";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
import { __dirname } from "../utils.js";
|
|
6
|
+
export class FStool {
|
|
7
|
+
root = __dirname();
|
|
8
|
+
async writeFile({ file, content }: { file: string; content: string }) {
|
|
9
|
+
await fs.writeFile(path.join(this.root, file), content);
|
|
10
|
+
}
|
|
11
|
+
async writeJSONFile({ file, content, dir }: { file: string; content: object; dir?: string }) {
|
|
12
|
+
file = `${file}.json`;
|
|
13
|
+
let destination = path.join(this.root, file);
|
|
14
|
+
if (dir) {
|
|
15
|
+
const dirPath = path.join(dir, file);
|
|
16
|
+
destination = path.join(this.root, dir, file);
|
|
17
|
+
await this.ensurePathExists({ dir: path.dirname(dirPath) });
|
|
18
|
+
}
|
|
19
|
+
await fs.writeFile(destination, JSON.stringify(content, null, 2));
|
|
20
|
+
}
|
|
21
|
+
async readFile({ file }: { file: string }) {
|
|
22
|
+
return await fs.readFile(path.join(this.root, file), "utf-8");
|
|
23
|
+
}
|
|
24
|
+
async readJSONFile({ file, dir }: { file: string; dir?: string }) {
|
|
25
|
+
try {
|
|
26
|
+
file = `${file}.json`;
|
|
27
|
+
return JSON.parse(await fs.readFile(path.join(this.root, dir || "", file), "utf-8")) as object;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error(err)
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async ensurePathExists({ dir }: { dir: string }) {
|
|
34
|
+
dir = path.join(this.root, dir);
|
|
35
|
+
if (!(await fsExists(dir))) {
|
|
36
|
+
await fs.mkdir(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async avoidOverriding({ file }: { file: string }) {
|
|
40
|
+
file = path.join(this.root, file);
|
|
41
|
+
if (await fsExists(file)) {
|
|
42
|
+
throw new Error(`File ${file} already exists`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async removeFile({ file }: { file: string }) {
|
|
46
|
+
file = path.join(this.root, file);
|
|
47
|
+
if (await fsExists(file)) {
|
|
48
|
+
await fs.unlink(file);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async removeDirectory({ dir, target }: { dir: string; target: string }) {
|
|
52
|
+
dir = path.join(this.root, dir, target);
|
|
53
|
+
if (await fsExists(dir)) {
|
|
54
|
+
await fs.rm(dir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async copyDirectory({ from, to, dir }: { from: string; to: string; dir: string }) {
|
|
59
|
+
from = path.join(this.root, dir, from);
|
|
60
|
+
to = path.join(this.root, dir, to);
|
|
61
|
+
if (await fsExists(from)) {
|
|
62
|
+
await fs.cp(from, to, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async listDirectories({ dir }: { dir: string }) {
|
|
67
|
+
try {
|
|
68
|
+
return await fs
|
|
69
|
+
.readdir(path.join(this.root, dir), { withFileTypes: true })
|
|
70
|
+
.then((files) => files.filter((file) => file.isDirectory()))
|
|
71
|
+
.then((files) => files.map((file) => file.name));
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.warn(error);
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async listFiles({ dir }: { dir: string }) {
|
|
78
|
+
try {
|
|
79
|
+
return await fs
|
|
80
|
+
.readdir(path.join(this.root, dir), { withFileTypes: true })
|
|
81
|
+
.then((files) => files.filter((file) => !file.isDirectory()))
|
|
82
|
+
.then((files) => files.map((file) => file.name));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.warn(error);
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const fsTool = new FStool();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// eslint-disable-next-line import-x/export
|
|
2
|
+
export type * from "./custom-type.js";
|
|
3
|
+
export * from "./utils.js";
|
|
4
|
+
export * from "./logger-module/logger.js";
|
|
5
|
+
export * from "./notification-module/push-notification.js";
|
|
6
|
+
export * from "./token-module.js";
|
|
7
|
+
export * from "./bucket-module/file-storage.js";
|
|
8
|
+
export * from "./bucket-module/s3-storage.js";
|
|
9
|
+
export * from "./bucket-module/utils.js";
|
|
10
|
+
// export * from "./compression-module/compression-module.js";
|
|
11
|
+
|
|
12
|
+
// export * from "./env.js";
|
|
13
|
+
export * from "./file-system/index.js";
|
|
14
|
+
export * from "./mail-module/mock.js";
|
|
15
|
+
export * from "./mail-module/nodemailer.js";
|
|
16
|
+
|
|
17
|
+
export * from "./cookie-module.js";
|
|
18
|
+
export * from "./hashing-module.js";
|
|
19
|
+
|
|
20
|
+
export * from "./oauth2-client.js";
|
|
21
|
+
export * from "./otp-module.js";
|
|
22
|
+
export * from "./pagination-module.js";
|
|
23
|
+
export * from "./error-handlers-module/index.js";
|
|
24
|
+
export * from "./validation-module.js";
|
|
25
|
+
export * from "./command-module.js";
|
|
26
|
+
export * from "./logger-module/memory-profiler.js";
|
|
27
|
+
export * from "./user-session.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type LogConfig = {
|
|
2
|
+
routeInfo: boolean;
|
|
3
|
+
requestBody: boolean;
|
|
4
|
+
validationErrors: boolean;
|
|
5
|
+
queryParams: boolean;
|
|
6
|
+
responseBody: boolean;
|
|
7
|
+
steps: boolean;
|
|
8
|
+
};
|
|
9
|
+
export const logConfig: LogConfig = {
|
|
10
|
+
routeInfo: false,
|
|
11
|
+
responseBody: false,
|
|
12
|
+
validationErrors: false,
|
|
13
|
+
requestBody: false,
|
|
14
|
+
queryParams: false,
|
|
15
|
+
steps: false,
|
|
16
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createLogger, format, transports } from "winston";
|
|
2
|
+
|
|
3
|
+
import { type LogConfig, logConfig } from "./log-config.js";
|
|
4
|
+
|
|
5
|
+
const { combine, timestamp, printf, errors, colorize } = format;
|
|
6
|
+
|
|
7
|
+
// Custom format to include error stack traces
|
|
8
|
+
const customFormat = printf(({ level, message, timestamp, stack }: any) => {
|
|
9
|
+
return `${timestamp} [${level}]: ${stack ?? message}`;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const logConfigFilter = format((info) => {
|
|
13
|
+
if (!info.logType || logConfig[info.logType as keyof LogConfig]) {
|
|
14
|
+
return info;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Create level-specific filters
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
21
|
+
const levelFilter = (level: string) =>
|
|
22
|
+
format((info) => {
|
|
23
|
+
if (info.level === level) {
|
|
24
|
+
return info;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
})();
|
|
28
|
+
|
|
29
|
+
// Configure the logger
|
|
30
|
+
export const logger = createLogger({
|
|
31
|
+
format: combine(
|
|
32
|
+
timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
|
33
|
+
errors({ stack: true }), // Capture stack traces
|
|
34
|
+
customFormat,
|
|
35
|
+
),
|
|
36
|
+
transports: [
|
|
37
|
+
// Console transport - shows all levels
|
|
38
|
+
new transports.Console({
|
|
39
|
+
format: combine(logConfigFilter(), colorize(), customFormat),
|
|
40
|
+
}),
|
|
41
|
+
|
|
42
|
+
// Standard log file for all levels
|
|
43
|
+
// new DailyRotateFile({
|
|
44
|
+
// filename: path.join(__dirname(), "logs", "combined-%DATE%.log"),
|
|
45
|
+
// datePattern: "DD-MM-YYYY",
|
|
46
|
+
// maxFiles: "30d",
|
|
47
|
+
// format: combine(logConfigFilter(), customFormat),
|
|
48
|
+
// json: true,
|
|
49
|
+
// }),
|
|
50
|
+
|
|
51
|
+
// // File transport for 'info' logs only
|
|
52
|
+
// new DailyRotateFile({
|
|
53
|
+
// filename: path.join(__dirname(), "logs", "info-%DATE%.log"),
|
|
54
|
+
// datePattern: "DD-MM-YYYY",
|
|
55
|
+
// maxFiles: "30d",
|
|
56
|
+
// format: combine(logConfigFilter(), levelFilter("info"), customFormat),
|
|
57
|
+
// json: true,
|
|
58
|
+
// }),
|
|
59
|
+
|
|
60
|
+
// // File transport for 'error' logs only
|
|
61
|
+
// new DailyRotateFile({
|
|
62
|
+
// filename: path.join(__dirname(), "logs", "error-%DATE%.log"),
|
|
63
|
+
// datePattern: "DD-MM-YYYY",
|
|
64
|
+
// maxFiles: "30d",
|
|
65
|
+
// format: combine(logConfigFilter(), levelFilter("error"), customFormat),
|
|
66
|
+
// json: true,
|
|
67
|
+
// }),
|
|
68
|
+
|
|
69
|
+
// // File transport for 'warn' logs only
|
|
70
|
+
// new DailyRotateFile({
|
|
71
|
+
// filename: path.join(__dirname(), "logs", "warn-%DATE%.log"),
|
|
72
|
+
// datePattern: "DD-MM-YYYY",
|
|
73
|
+
// maxFiles: "30d",
|
|
74
|
+
// format: combine(logConfigFilter(), levelFilter("warn"), customFormat),
|
|
75
|
+
// json: true,
|
|
76
|
+
// }),
|
|
77
|
+
],
|
|
78
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
export class MemoryProfiler {
|
|
3
|
+
private startUsage: number | null = null;
|
|
4
|
+
private lastUsage: number | null = null;
|
|
5
|
+
private totalDelta = 0;
|
|
6
|
+
private state: "off" | "on" = "on";
|
|
7
|
+
private startTime: number | null = null;
|
|
8
|
+
private lastTime: number | null = null;
|
|
9
|
+
|
|
10
|
+
setState(state: "on" | "off") {
|
|
11
|
+
this.state = state;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
start(): void {
|
|
15
|
+
const usage = process.memoryUsage().heapUsed;
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
this.startUsage = usage;
|
|
18
|
+
this.lastUsage = usage;
|
|
19
|
+
this.startTime = now;
|
|
20
|
+
this.lastTime = now;
|
|
21
|
+
this.totalDelta = 0;
|
|
22
|
+
|
|
23
|
+
if (this.state === "off") return;
|
|
24
|
+
console.log(`[Profiler] Started at ${this.formatMB(usage)}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
at(label: string = ""): void {
|
|
28
|
+
if (this.startUsage === null || this.startTime === null) {
|
|
29
|
+
throw new Error("Profiler not started. Call .start() first.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const usage = process.memoryUsage().heapUsed;
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
|
|
35
|
+
const deltaFromStart = usage - this.startUsage;
|
|
36
|
+
const deltaFromLast = usage - (this.lastUsage ?? usage);
|
|
37
|
+
|
|
38
|
+
const timeFromStart = now - this.startTime;
|
|
39
|
+
const timeFromLast = now - (this.lastTime ?? now);
|
|
40
|
+
|
|
41
|
+
this.totalDelta = deltaFromStart;
|
|
42
|
+
|
|
43
|
+
if (this.state === "on") {
|
|
44
|
+
console.log(
|
|
45
|
+
`[Profiler] ${label ? label + " - " : ""}` +
|
|
46
|
+
`since start: ${this.formatMB(deltaFromStart)} | ${this.formatMS(timeFromStart)}, ` +
|
|
47
|
+
`since last: ${this.formatMB(deltaFromLast)} | ${this.formatMS(timeFromLast)}, ` +
|
|
48
|
+
`current heap: ${this.formatMB(usage)}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.lastUsage = usage;
|
|
53
|
+
this.lastTime = now;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private formatMB(bytes: number): string {
|
|
57
|
+
return (bytes / 1024 / 1024).toFixed(2) + " MB";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private formatMS(ms: number): string {
|
|
61
|
+
if (ms < 1000) return `${ms} ms`;
|
|
62
|
+
const sec = (ms / 1000).toFixed(2);
|
|
63
|
+
return `${sec} s`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Transporter } from "nodemailer";
|
|
2
|
+
import * as nodemailer from "nodemailer";
|
|
3
|
+
|
|
4
|
+
import { logger } from "../logger-module/logger";
|
|
5
|
+
|
|
6
|
+
class SMTPMailService {
|
|
7
|
+
private static transporter: Transporter;
|
|
8
|
+
|
|
9
|
+
// Initialize the transporter only once
|
|
10
|
+
private static initializeTransporter() {
|
|
11
|
+
if (!this.transporter) {
|
|
12
|
+
this.transporter = nodemailer.createTransport({
|
|
13
|
+
host: process.env.SMTP_HOST,
|
|
14
|
+
port: Number.parseInt(process.env.SMTP_PORT) ?? 587,
|
|
15
|
+
secure: process.env.SMTP_MAIL_ENCRYPTION === "ssl",
|
|
16
|
+
auth: {
|
|
17
|
+
user: process.env.SMTP_USER,
|
|
18
|
+
pass: process.env.SMTP_PASSWORD,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static async sendMail(to: string, subject: string, text: string, html?: string): Promise<void> {
|
|
25
|
+
this.initializeTransporter();
|
|
26
|
+
|
|
27
|
+
const mailOptions = {
|
|
28
|
+
from: `Bemyguest <${process.env.SENDER_MAIL}>`,
|
|
29
|
+
to,
|
|
30
|
+
subject,
|
|
31
|
+
text,
|
|
32
|
+
html,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const info = (await this.transporter.sendMail(mailOptions)) as { messageId: string };
|
|
37
|
+
logger.info(`Email sent: ${info.messageId}`);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.error("Error sending email:", error);
|
|
40
|
+
throw new Error("Failed to send email");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { SMTPMailService };
|