startx 1.0.5 → 1.0.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 (48) hide show
  1. package/apps/core-server/Dockerfile +9 -3
  2. package/apps/core-server/src/config/custom-type.ts +1 -1
  3. package/apps/core-server/src/middlewares/auth-middleware.ts +74 -30
  4. package/apps/queue-worker/Dockerfile +16 -9
  5. package/apps/queue-worker/package.json +5 -1
  6. package/apps/queue-worker/src/bullmq/board.ts +28 -0
  7. package/apps/queue-worker/src/index.ts +2 -0
  8. package/apps/startx-cli/dist/index.mjs +3 -3
  9. package/apps/startx-cli/src/configs/scripts.ts +44 -4
  10. package/apps/web-client/Dockerfile +40 -0
  11. package/apps/web-client/nginx.conf +20 -0
  12. package/apps/web-client/package.json +1 -1
  13. package/apps/web-client/src/app.css +0 -1
  14. package/assets/avatars/54b19ada-d53e-4ee9-8882-9dfed1bf1396.jpg +0 -0
  15. package/assets/avatars/ad3bf027-e85b-4cad-ab5f-80a25e37f4cb.jpg +0 -0
  16. package/assets/avatars/e67eb556-f125-4e24-95ad-8aff21b9926a.jpg +0 -0
  17. package/package.json +8 -2
  18. package/packages/@db/drizzle/drizzle.config.ts +1 -1
  19. package/packages/@db/drizzle/src/index.ts +4 -15
  20. package/packages/@repo/env/src/default-env.ts +1 -0
  21. package/packages/@repo/lib/src/cookie-module/cookie-module.ts +94 -38
  22. package/packages/@repo/lib/src/extra/index.ts +1 -0
  23. package/packages/@repo/lib/src/extra/token-module.ts +50 -21
  24. package/packages/@repo/lib/src/mail-module/nodemailer.ts +36 -23
  25. package/packages/@repo/lib/src/session-module/i-session.ts +132 -59
  26. package/packages/@repo/lib/src/session-module/index.ts +8 -2
  27. package/packages/@repo/lib/src/session-module/redis-session.ts +53 -23
  28. package/packages/@repo/lib/src/validation-module/index.ts +50 -78
  29. package/packages/@repo/lib/tsconfig.json +2 -1
  30. package/packages/@repo/model/eslint.config.ts +4 -0
  31. package/packages/@repo/model/package.json +41 -0
  32. package/packages/@repo/model/src/index.ts +0 -0
  33. package/packages/@repo/model/tsconfig.json +7 -0
  34. package/packages/@repo/model/vitest.config.ts +3 -0
  35. package/packages/common/src/time.ts +95 -22
  36. package/packages/queue/src/adapter/bullmq-adapter.ts +138 -47
  37. package/packages/queue/src/index.ts +3 -0
  38. package/packages/queue/src/queue-interface.ts +12 -5
  39. package/packages/queue/src/registry.ts +2 -2
  40. package/packages/queue/tsconfig.json +1 -1
  41. package/packages/ui/src/api/use-api/react-query/types.ts +3 -3
  42. package/packages/ui/src/api/use-api/react-query/use-api.ts +10 -11
  43. package/pnpm-workspace.yaml +4 -2
  44. package/turbo.json +21 -1
  45. package/packages/@repo/lib/src/bucket-module/file-storage.ts +0 -50
  46. package/packages/@repo/lib/src/bucket-module/index.ts +0 -3
  47. package/packages/@repo/lib/src/bucket-module/s3-storage.ts +0 -120
  48. package/packages/@repo/lib/src/bucket-module/utils.ts +0 -10
@@ -1,12 +1,18 @@
1
+ import { Time } from "@repo/common/time";
1
2
  import type { SessionUser } from "@repo/common/types/users";
2
3
  import { defineEnv } from "@repo/env";
4
+ import crypto from "node:crypto";
3
5
  import { z } from "zod";
4
6
  import { TokenModule } from "../extra/token-module.js";
5
- const env = defineEnv({
6
- SESSION_DURATION: z.number().default(60 * 60 * 6),
7
+
8
+ export const sessionEnv = defineEnv({
9
+ SESSION_DURATION: z.number().default(Time.days(30).milliseconds),
10
+ SESSION_TYPE: z.enum(["single", "multi"]).default("multi"),
11
+ MAX_CONCURRENT_SESSIONS: z.number().default(1),
7
12
  });
13
+
8
14
  export const constants = {
9
- sessionDuration: env.SESSION_DURATION,
15
+ sessionDuration: sessionEnv.SESSION_DURATION,
10
16
  };
11
17
 
12
18
  export type TokenPair = {
@@ -14,95 +20,162 @@ export type TokenPair = {
14
20
  refreshToken: string;
15
21
  };
16
22
 
23
+ export type SessionType =
24
+ | {
25
+ type: "single";
26
+ }
27
+ | {
28
+ type: "multi";
29
+ maxConcurrentSessions: number;
30
+ };
31
+
32
+ export type SessionRecord = {
33
+ sessionId: string;
34
+ user: Omit<SessionUser, "accessToken">;
35
+ refreshTokenHash: string;
36
+ createdAt: number;
37
+ lastSeenAt: number;
38
+ };
39
+
17
40
  export abstract class IUserSession {
18
- protected accessTokenKey(key: string) {
19
- return `access_token:${key}`;
41
+ protected abstract type: SessionType;
42
+
43
+ protected sessionKey(sessionId: string) {
44
+ return `session:${sessionId}`;
20
45
  }
21
- protected refreshTokenKey(key: string) {
22
- return `refresh_token:${key}`;
46
+
47
+ protected userSessionsKey(userId: string) {
48
+ return `user:sessions:${userId}`;
23
49
  }
24
- protected userTokensKey(userId: string) {
25
- return `session:${userId}`;
50
+
51
+ protected hashToken(token: string) {
52
+ return crypto.createHash("sha256").update(token).digest("hex");
26
53
  }
27
54
 
28
- protected abstract setSessionData(key: string, data: SessionUser, ttl: number): Promise<void>;
29
- protected abstract getSessionData(key: string): Promise<SessionUser | null>;
30
- protected abstract deleteSessionData(key: string): Promise<void>;
55
+ protected abstract setSession(sessionId: string, data: SessionRecord, ttl: number): Promise<void>;
56
+
57
+ protected abstract getSession(sessionId: string): Promise<SessionRecord | null>;
31
58
 
32
- protected abstract setTokenData(key: string, data: TokenPair, ttl: number): Promise<void>;
33
- protected abstract getTokenData(key: string): Promise<TokenPair | null>;
34
- protected abstract deleteTokenData(key: string): Promise<void>;
59
+ protected abstract deleteSession(sessionId: string): Promise<void>;
35
60
 
36
- public async getSessionUser(token: string): Promise<SessionUser | null> {
37
- return await this.getSessionData(this.accessTokenKey(token));
61
+ protected abstract addUserSession(userId: string, sessionId: string): Promise<void>;
62
+
63
+ protected abstract removeUserSession(userId: string, sessionId: string): Promise<void>;
64
+
65
+ protected abstract getUserSessions(userId: string): Promise<string[]>;
66
+
67
+ protected abstract clearUserSessions(userId: string): Promise<void>;
68
+
69
+ protected generateSessionId() {
70
+ return crypto.randomUUID();
38
71
  }
39
72
 
40
- public async startSession(payload: Omit<SessionUser, "accessToken">): Promise<TokenPair> {
41
- await this.endSession(payload.id);
73
+ protected generateRefreshJti() {
74
+ return crypto.randomUUID();
75
+ }
76
+
77
+ public async startSession(user: Omit<SessionUser, "accessToken">): Promise<TokenPair> {
78
+ if (this.type.type === "single") {
79
+ await this.endAllSessions(user.id);
80
+ }
81
+
82
+ if (this.type.type === "multi") {
83
+ const existing = await this.getUserSessions(user.id);
84
+
85
+ if (existing.length >= this.type.maxConcurrentSessions) {
86
+ const oldest = existing[0];
87
+ await this.endSession(oldest);
88
+ }
89
+ }
90
+
91
+ const sessionId = this.generateSessionId();
92
+ const refreshJti = this.generateRefreshJti();
42
93
 
43
94
  const accessToken = TokenModule.signAccessToken({
44
- userID: payload.id,
45
- email: payload.email,
95
+ userID: user.id,
96
+ email: user.email,
97
+ sessionID: sessionId,
46
98
  });
47
99
 
48
100
  const refreshToken = TokenModule.signRefreshToken({
49
- userID: payload.id,
50
- email: payload.email,
101
+ userID: user.id,
102
+ email: user.email,
103
+ sessionID: sessionId,
104
+ jti: refreshJti,
51
105
  });
52
106
 
53
- const sessionData: SessionUser = { ...payload, accessToken };
54
- const tokens: TokenPair = { accessToken, refreshToken };
107
+ const record: SessionRecord = {
108
+ sessionId,
109
+ user,
110
+ refreshTokenHash: this.hashToken(refreshToken),
111
+ createdAt: Date.now(),
112
+ lastSeenAt: Date.now(),
113
+ };
55
114
 
56
115
  await Promise.all([
57
- this.setSessionData(this.accessTokenKey(accessToken), sessionData, constants.sessionDuration),
58
- this.setSessionData(payload.id, sessionData, constants.sessionDuration),
59
- this.setTokenData(this.refreshTokenKey(refreshToken), tokens, constants.sessionDuration),
60
- this.setTokenData(this.userTokensKey(payload.id), tokens, constants.sessionDuration),
116
+ this.setSession(sessionId, record, constants.sessionDuration),
117
+ this.addUserSession(user.id, sessionId),
61
118
  ]);
62
119
 
63
- return tokens;
120
+ return {
121
+ accessToken,
122
+ refreshToken,
123
+ };
64
124
  }
65
125
 
66
- public async checkRefreshToken(refreshToken: string): Promise<TokenPair | null> {
67
- return await this.getTokenData(this.refreshTokenKey(refreshToken));
126
+ public async validateSession(sessionId: string): Promise<SessionRecord | null> {
127
+ return await this.getSession(sessionId);
68
128
  }
69
129
 
70
- public async updateAccessToken(payload: Omit<SessionUser, "accessToken">): Promise<string | null> {
71
- const tokens = await this.getTokens(payload.id);
72
- if (!tokens) return null;
130
+ public async rotateRefreshToken(sessionId: string, refreshToken: string): Promise<TokenPair | null> {
131
+ const session = await this.getSession(sessionId);
132
+ if (!session) return null;
73
133
 
74
- const accessToken = tokens.accessToken;
75
- const sessionData: SessionUser = { ...payload, accessToken };
134
+ const incomingHash = this.hashToken(refreshToken);
76
135
 
77
- await Promise.all([
78
- this.setSessionData(this.accessTokenKey(accessToken), sessionData, constants.sessionDuration),
79
- this.setSessionData(payload.id, sessionData, constants.sessionDuration),
80
- ]);
136
+ if (incomingHash !== session.refreshTokenHash) {
137
+ await this.endSession(sessionId);
138
+ return null;
139
+ }
81
140
 
82
- return accessToken;
83
- }
141
+ const newRefreshJti = this.generateRefreshJti();
142
+
143
+ const accessToken = TokenModule.signAccessToken({
144
+ userID: session.user.id,
145
+ email: session.user.email,
146
+ sessionID: sessionId,
147
+ });
84
148
 
85
- public async getTokens(userId: string): Promise<TokenPair | null> {
86
- return await this.getTokenData(this.userTokensKey(userId));
149
+ const newRefreshToken = TokenModule.signRefreshToken({
150
+ userID: session.user.id,
151
+ email: session.user.email,
152
+ sessionID: sessionId,
153
+ jti: newRefreshJti,
154
+ });
155
+
156
+ session.refreshTokenHash = this.hashToken(newRefreshToken);
157
+ session.lastSeenAt = Date.now();
158
+
159
+ await this.setSession(sessionId, session, constants.sessionDuration);
160
+
161
+ return {
162
+ accessToken,
163
+ refreshToken: newRefreshToken,
164
+ };
87
165
  }
88
166
 
89
- public async logout(accessToken: string): Promise<null> {
90
- const session = await this.getSessionUser(accessToken);
91
- if (!session) return null;
167
+ public async endSession(sessionId: string): Promise<void> {
168
+ const session = await this.getSession(sessionId);
169
+ if (!session) return;
92
170
 
93
- await this.endSession(session.id);
94
- return null;
171
+ await Promise.all([this.deleteSession(sessionId), this.removeUserSession(session.user.id, sessionId)]);
95
172
  }
96
173
 
97
- public async endSession(userId: string): Promise<void> {
98
- const tokens = await this.getTokens(userId);
99
- if (!tokens) return;
174
+ public async endAllSessions(userId: string): Promise<void> {
175
+ const sessions = await this.getUserSessions(userId);
100
176
 
101
- await Promise.all([
102
- this.deleteTokenData(this.refreshTokenKey(tokens.refreshToken)),
103
- this.deleteSessionData(this.accessTokenKey(tokens.accessToken)),
104
- this.deleteTokenData(this.userTokensKey(userId)),
105
- this.deleteSessionData(userId),
106
- ]);
177
+ await Promise.all(sessions.map(sid => this.deleteSession(sid)));
178
+
179
+ await this.clearUserSessions(userId);
107
180
  }
108
181
  }
@@ -1,10 +1,16 @@
1
+ import { sessionEnv, type SessionType } from "./i-session.js";
1
2
  import { RedisUserSession } from "./redis-session.js";
2
3
 
3
- export const userSession = (type: "redis" | "pg") => {
4
+ export const userSession = (type: "redis" | "pg", options: SessionType) => {
4
5
  switch (type) {
5
6
  case "redis":
6
- return new RedisUserSession();
7
+ return new RedisUserSession(options);
7
8
  default:
8
9
  throw new Error("Unknown session type");
9
10
  }
10
11
  };
12
+
13
+ export const defaultUserSession = userSession("redis", {
14
+ type: sessionEnv.SESSION_TYPE,
15
+ maxConcurrentSessions: sessionEnv.MAX_CONCURRENT_SESSIONS,
16
+ });
@@ -1,44 +1,74 @@
1
- import type { SessionUser } from "@repo/common/types/users";
2
1
  import { RedisStore } from "@repo/redis";
3
- import { IUserSession, type TokenPair } from "./i-session.js";
2
+ import type { SessionRecord, SessionType } from "./i-session.js";
3
+ import { constants, IUserSession } from "./i-session.js";
4
+
5
+ type SessionIndex = string[];
4
6
 
5
7
  export class RedisUserSession extends IUserSession {
6
- private userStore: RedisStore<SessionUser>;
7
- private tokenStore: RedisStore<TokenPair>;
8
+ private sessionStore: RedisStore<SessionRecord>;
9
+ private sessionIndexStore: RedisStore<SessionIndex>;
8
10
 
9
- constructor() {
11
+ constructor(type: SessionType) {
10
12
  super();
11
- this.userStore = new RedisStore<SessionUser>({
12
- namespace: "user-session",
13
+
14
+ this.type = type;
15
+
16
+ this.sessionStore = new RedisStore<SessionRecord>({
17
+ namespace: "auth-session",
13
18
  });
14
- this.tokenStore = new RedisStore<TokenPair>({
15
- namespace: "user-tokens",
19
+
20
+ this.sessionIndexStore = new RedisStore<SessionIndex>({
21
+ namespace: "auth-session-index",
16
22
  });
17
23
  }
18
24
 
19
- protected async setSessionData(key: string, data: SessionUser, ttl: number): Promise<void> {
20
- await this.userStore.set(key, data, ttl);
25
+ protected type: SessionType;
26
+ protected async setSession(sessionId: string, data: SessionRecord, ttl: number): Promise<void> {
27
+ await this.sessionStore.set(sessionId, data, ttl);
21
28
  }
22
29
 
23
- protected async getSessionData(key: string): Promise<SessionUser | null> {
24
- const data = await this.userStore.get(key);
25
- return data ?? null;
30
+ protected async getSession(sessionId: string): Promise<SessionRecord | null> {
31
+ const session = await this.sessionStore.get(sessionId);
32
+ return session ?? null;
26
33
  }
27
34
 
28
- protected async deleteSessionData(key: string): Promise<void> {
29
- await this.userStore.del(key);
35
+ protected async deleteSession(sessionId: string): Promise<void> {
36
+ await this.sessionStore.del(sessionId);
30
37
  }
31
38
 
32
- protected async setTokenData(key: string, data: TokenPair, ttl: number): Promise<void> {
33
- await this.tokenStore.set(key, data, ttl);
39
+ protected async addUserSession(userId: string, sessionId: string): Promise<void> {
40
+ const key = this.userSessionsKey(userId);
41
+
42
+ const sessions = (await this.sessionIndexStore.get(key)) ?? [];
43
+
44
+ if (!sessions.includes(sessionId)) {
45
+ sessions.push(sessionId);
46
+ }
47
+
48
+ await this.sessionIndexStore.set(key, sessions, constants.sessionDuration);
49
+ }
50
+
51
+ protected async removeUserSession(userId: string, sessionId: string): Promise<void> {
52
+ const key = this.userSessionsKey(userId);
53
+
54
+ const sessions = (await this.sessionIndexStore.get(key)) ?? [];
55
+
56
+ const filtered = sessions.filter(id => id !== sessionId);
57
+
58
+ if (filtered.length === 0) {
59
+ await this.sessionIndexStore.del(key);
60
+ return;
61
+ }
62
+
63
+ await this.sessionIndexStore.set(key, filtered, constants.sessionDuration);
34
64
  }
35
65
 
36
- protected async getTokenData(key: string): Promise<TokenPair | null> {
37
- const data = await this.tokenStore.get(key);
38
- return data ?? null;
66
+ protected async getUserSessions(userId: string): Promise<string[]> {
67
+ const key = this.userSessionsKey(userId);
68
+ return (await this.sessionIndexStore.get(key)) ?? [];
39
69
  }
40
70
 
41
- protected async deleteTokenData(key: string): Promise<void> {
42
- await this.tokenStore.del(key);
71
+ protected async clearUserSessions(userId: string): Promise<void> {
72
+ await this.sessionIndexStore.del(this.userSessionsKey(userId));
43
73
  }
44
74
  }
@@ -1,15 +1,11 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { logger } from "@repo/logger";
1
3
  import vine from "@vinejs/vine";
2
4
  import type { Infer, SchemaTypes } from "@vinejs/vine/types";
3
5
  import type { NextFunction, Request, Response } from "express";
4
6
 
5
7
  import { ErrorResponse } from "../error-handlers-module/index.js";
6
- import { logger } from "@repo/logger";
7
8
 
8
- type ExpressHandler<P = unknown, ResBody = unknown, ReqBody = unknown, Query = unknown> = (
9
- req: Request<P, ResBody, ReqBody, Query>,
10
- res: Response,
11
- next: NextFunction
12
- ) => unknown;
13
9
  export async function validateBody<T extends SchemaTypes>(
14
10
  schema: T,
15
11
  payload: unknown
@@ -30,17 +26,20 @@ export async function validateBody<T extends SchemaTypes>(
30
26
  return { error: ["Validation failed"] };
31
27
  }
32
28
  }
29
+
33
30
  export function bodyValidator<T extends SchemaTypes>(schema: T) {
34
- return function <F extends ExpressHandler<unknown, unknown, Infer<T>, unknown>>(
35
- _target: unknown,
36
- _propertyKey: string,
37
- descriptor: TypedPropertyDescriptor<F>
38
- ) {
31
+ return function <
32
+ F extends (
33
+ req: Request<any, any, Infer<T>, any, Record<string, any>>,
34
+ res: Response<any, Record<string, any>>,
35
+ next: NextFunction
36
+ ) => Promise<any> | void,
37
+ >(_target: unknown, _propertyKey: string, descriptor: TypedPropertyDescriptor<F>) {
39
38
  const originalMethod = descriptor.value!;
40
39
 
41
40
  descriptor.value = async function (
42
41
  this: unknown,
43
- req: Request<unknown, unknown, Infer<T>>,
42
+ req: Request<any, any, Infer<T>, any, Record<string, any>>,
44
43
  res: Response,
45
44
  next: NextFunction
46
45
  ) {
@@ -57,23 +56,26 @@ export function bodyValidator<T extends SchemaTypes>(schema: T) {
57
56
 
58
57
  req.body = data;
59
58
 
60
- return originalMethod.call(this, req, res, next);
61
- } as F;
59
+ return await originalMethod.call(this, req, res, next);
60
+ } as unknown as F;
62
61
 
63
62
  return descriptor;
64
63
  };
65
64
  }
65
+
66
66
  export function paramsValidator<T extends SchemaTypes>(schema: T) {
67
- return function <F extends ExpressHandler<Infer<T>, unknown, unknown, unknown>>(
68
- _target: unknown,
69
- _propertyKey: string,
70
- descriptor: TypedPropertyDescriptor<F>
71
- ) {
67
+ return function <
68
+ F extends (
69
+ req: Request<any, any, Infer<T>, any, Record<string, any>>,
70
+ res: Response<any, Record<string, any>>,
71
+ next: NextFunction
72
+ ) => Promise<any> | void,
73
+ >(_target: unknown, _propertyKey: string, descriptor: TypedPropertyDescriptor<F>) {
72
74
  const originalMethod = descriptor.value!;
73
75
 
74
76
  descriptor.value = async function (
75
77
  this: unknown,
76
- req: Request<Infer<T>>,
78
+ req: Request<Infer<T>, any, any, any, Record<string, any>>,
77
79
  res: Response,
78
80
  next: NextFunction
79
81
  ) {
@@ -84,26 +86,28 @@ export function paramsValidator<T extends SchemaTypes>(schema: T) {
84
86
  return res.status(422).json({ message: error.join("\n") });
85
87
  }
86
88
 
87
- req.params = data;
89
+ Object.assign(req.query, data);
88
90
 
89
- return originalMethod.call(this, req, res, next);
90
- } as F;
91
+ return await originalMethod.call(this, req, res, next);
92
+ } as unknown as F;
91
93
 
92
94
  return descriptor;
93
95
  };
94
96
  }
95
97
 
96
98
  export function queryValidator<T extends SchemaTypes>(schema: T) {
97
- return function <F extends ExpressHandler<unknown, unknown, unknown, Infer<T>>>(
98
- _target: unknown,
99
- _propertyKey: string,
100
- descriptor: TypedPropertyDescriptor<F>
101
- ) {
99
+ return function <
100
+ F extends (
101
+ req: Request<any, any, Infer<T>, any, Record<string, any>>,
102
+ res: Response<any, Record<string, any>>,
103
+ next: NextFunction
104
+ ) => Promise<any> | void,
105
+ >(_target: unknown, _propertyKey: string, descriptor: TypedPropertyDescriptor<F>) {
102
106
  const originalMethod = descriptor.value!;
103
107
 
104
108
  descriptor.value = async function (
105
109
  this: unknown,
106
- req: Request<unknown, unknown, unknown, Infer<T>>,
110
+ req: Request<any, any, any, Infer<T>, Record<string, any>>,
107
111
  res: Response,
108
112
  next: NextFunction
109
113
  ) {
@@ -114,56 +118,27 @@ export function queryValidator<T extends SchemaTypes>(schema: T) {
114
118
  return res.status(422).json({ message: error.join("\n") });
115
119
  }
116
120
 
117
- req.query = data;
118
-
119
- return originalMethod.call(this, req, res, next);
120
- } as F;
121
+ Object.assign(req.query, data);
122
+ return await originalMethod.call(this, req, res, next);
123
+ } as unknown as F;
121
124
 
122
125
  return descriptor;
123
126
  };
124
127
  }
125
- // export function authValidator({ optional = false }: { optional?: boolean } | undefined = {}) {
126
- // return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
127
- // const originalMethod = descriptor.value;
128
- // descriptor.value = async function (req: Request, res: Response, next: NextFunction) {
129
- // try {
130
- // const accessToken = req.headers["authorization"]?.split(" ")[1];
131
- // if (optional && (!accessToken || !TokenModule.verifyAccessToken(accessToken))) {
132
- // return originalMethod.apply(this, [req, res, next]);
133
- // }
134
- // if (!accessToken) {
135
- // res.status(401).json({ message: "access token missing" });
136
- // return;
137
- // }
138
- // const payload = TokenModule.verifyAccessToken(accessToken);
139
-
140
- // if (!payload) {
141
- // res.status(401).json({ message: "invalid access token" });
142
- // return;
143
- // }
144
- // req.user = {
145
- // id: payload.userID,
146
- // email: payload.email
147
- // };
148
- // return originalMethod.apply(this, [req, res, next]);
149
- // } catch (error) {
150
- // next(error);
151
- // }
152
- // };
153
- // };
154
- // }
155
128
 
156
129
  export function mediaBodyValidator<T extends SchemaTypes>(schema: T, optional = false) {
157
- return function <F extends ExpressHandler<unknown, unknown, Infer<T>, unknown>>(
158
- _target: unknown,
159
- _propertyKey: string,
160
- descriptor: TypedPropertyDescriptor<F>
161
- ) {
130
+ return function <
131
+ F extends (
132
+ req: Request<any, any, Infer<T>, any, Record<string, any>> & { files?: any },
133
+ res: Response<any, Record<string, any>>,
134
+ next: NextFunction
135
+ ) => Promise<any> | void,
136
+ >(_target: unknown, _propertyKey: string, descriptor: TypedPropertyDescriptor<F>) {
162
137
  const originalMethod = descriptor.value!;
163
138
 
164
139
  descriptor.value = async function (
165
140
  this: unknown,
166
- req: Request<unknown, unknown, Infer<T>>,
141
+ req: Request<any, any, Infer<T>, any, Record<string, any>> & { files?: any },
167
142
  res: Response,
168
143
  next: NextFunction
169
144
  ) {
@@ -184,9 +159,7 @@ export function mediaBodyValidator<T extends SchemaTypes>(schema: T, optional =
184
159
  }
185
160
  };
186
161
 
187
- const parsedData = Object.fromEntries(
188
- Object.entries(req.body).map(([k, v]) => [k, isJSON(v)])
189
- );
162
+ const parsedData = Object.fromEntries(Object.entries(req.body).map(([k, v]) => [k, isJSON(v)]));
190
163
 
191
164
  const { error, data } = await validateBody(schema, parsedData);
192
165
 
@@ -198,12 +171,13 @@ export function mediaBodyValidator<T extends SchemaTypes>(schema: T, optional =
198
171
  req.body = data;
199
172
  req.files = files;
200
173
 
201
- return originalMethod.call(this, req, res, next);
202
- } as F;
174
+ return await originalMethod.call(this, req, res, next);
175
+ } as unknown as F;
203
176
 
204
177
  return descriptor;
205
178
  };
206
179
  }
180
+
207
181
  export const validateId = vine.object({
208
182
  id: vine.string().uuid(),
209
183
  });
@@ -224,10 +198,8 @@ export const paginationValidator = vine.object({
224
198
  .parse(e => (!e ? "" : e))
225
199
  .optional(),
226
200
  });
227
- export async function validate<T extends SchemaTypes>(
228
- schema: T,
229
- payload: Infer<T>
230
- ): Promise<Infer<T>> {
201
+
202
+ export async function validate<T extends SchemaTypes>(schema: T, payload: Infer<T>): Promise<Infer<T>> {
231
203
  const result = await validateBody(schema, payload);
232
204
 
233
205
  if (result.error.length) {
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "extends": "typescript-config/tsconfig.node.json",
3
3
  "compilerOptions": {
4
- "jsx": "react"
4
+ "jsx": "react",
5
+ "lib": ["DOM", "ES2022"]
5
6
  },
6
7
  "include": ["src/**/*.ts"]
7
8
  }
@@ -0,0 +1,4 @@
1
+ import { baseConfig } from "eslint-config/base";
2
+ import { extend } from "eslint-config/extend";
3
+
4
+ export default extend(baseConfig);
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@repo/model",
3
+ "type": "module",
4
+ "scripts": {
5
+ "lint": "eslint .",
6
+ "lint:fix": "eslint . src/**/*.ts --fix",
7
+ "clean": "rimraf dist build .turbo",
8
+ "deep:clean": "rimraf node_modules dist build .turbo",
9
+ "typecheck": "tsc --noEmit",
10
+ "format": "prettier --write .",
11
+ "format:check": "prettier --check .",
12
+ "test": "vitest run"
13
+ },
14
+ "exports": {
15
+ ".": "./src/index.ts",
16
+ "./*": "./src/*/index.ts"
17
+ },
18
+ "dependencies": {
19
+ "@repo/lib": "workspace:^",
20
+ "@repo/logger": "workspace:^"
21
+ },
22
+ "devDependencies": {
23
+ "typescript-config": "workspace:^",
24
+ "eslint-config": "workspace:^",
25
+ "vitest-config": "workspace:^"
26
+ },
27
+ "startx": {
28
+ "iTags": [
29
+ "node"
30
+ ],
31
+ "gTags": [],
32
+ "tags": [],
33
+ "requiredDeps": [
34
+ "@repo/env",
35
+ "@repo/lib"
36
+ ],
37
+ "requiredDevDeps": [
38
+ "typescript-config"
39
+ ]
40
+ }
41
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "typescript-config/tsconfig.node.json",
3
+ "compilerOptions": {
4
+ // "baseUrl": "./src"
5
+ },
6
+ "include": ["src/**/*.ts"]
7
+ }
@@ -0,0 +1,3 @@
1
+ import vitestConfig from "vitest-config/node";
2
+
3
+ export default vitestConfig;