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.
- package/apps/core-server/Dockerfile +9 -3
- package/apps/core-server/src/config/custom-type.ts +1 -1
- package/apps/core-server/src/middlewares/auth-middleware.ts +74 -30
- package/apps/queue-worker/Dockerfile +16 -9
- package/apps/queue-worker/package.json +5 -1
- package/apps/queue-worker/src/bullmq/board.ts +28 -0
- package/apps/queue-worker/src/index.ts +2 -0
- package/apps/startx-cli/dist/index.mjs +3 -3
- package/apps/startx-cli/src/configs/scripts.ts +44 -4
- package/apps/web-client/Dockerfile +40 -0
- package/apps/web-client/nginx.conf +20 -0
- package/apps/web-client/package.json +1 -1
- package/apps/web-client/src/app.css +0 -1
- package/assets/avatars/54b19ada-d53e-4ee9-8882-9dfed1bf1396.jpg +0 -0
- package/assets/avatars/ad3bf027-e85b-4cad-ab5f-80a25e37f4cb.jpg +0 -0
- package/assets/avatars/e67eb556-f125-4e24-95ad-8aff21b9926a.jpg +0 -0
- package/package.json +8 -2
- package/packages/@db/drizzle/drizzle.config.ts +1 -1
- package/packages/@db/drizzle/src/index.ts +4 -15
- package/packages/@repo/env/src/default-env.ts +1 -0
- package/packages/@repo/lib/src/cookie-module/cookie-module.ts +94 -38
- package/packages/@repo/lib/src/extra/index.ts +1 -0
- package/packages/@repo/lib/src/extra/token-module.ts +50 -21
- package/packages/@repo/lib/src/mail-module/nodemailer.ts +36 -23
- package/packages/@repo/lib/src/session-module/i-session.ts +132 -59
- package/packages/@repo/lib/src/session-module/index.ts +8 -2
- package/packages/@repo/lib/src/session-module/redis-session.ts +53 -23
- package/packages/@repo/lib/src/validation-module/index.ts +50 -78
- package/packages/@repo/lib/tsconfig.json +2 -1
- package/packages/@repo/model/eslint.config.ts +4 -0
- package/packages/@repo/model/package.json +41 -0
- package/packages/@repo/model/src/index.ts +0 -0
- package/packages/@repo/model/tsconfig.json +7 -0
- package/packages/@repo/model/vitest.config.ts +3 -0
- package/packages/common/src/time.ts +95 -22
- package/packages/queue/src/adapter/bullmq-adapter.ts +138 -47
- package/packages/queue/src/index.ts +3 -0
- package/packages/queue/src/queue-interface.ts +12 -5
- package/packages/queue/src/registry.ts +2 -2
- package/packages/queue/tsconfig.json +1 -1
- package/packages/ui/src/api/use-api/react-query/types.ts +3 -3
- package/packages/ui/src/api/use-api/react-query/use-api.ts +10 -11
- package/pnpm-workspace.yaml +4 -2
- package/turbo.json +21 -1
- package/packages/@repo/lib/src/bucket-module/file-storage.ts +0 -50
- package/packages/@repo/lib/src/bucket-module/index.ts +0 -3
- package/packages/@repo/lib/src/bucket-module/s3-storage.ts +0 -120
- 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
|
-
|
|
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:
|
|
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
|
|
19
|
-
|
|
41
|
+
protected abstract type: SessionType;
|
|
42
|
+
|
|
43
|
+
protected sessionKey(sessionId: string) {
|
|
44
|
+
return `session:${sessionId}`;
|
|
20
45
|
}
|
|
21
|
-
|
|
22
|
-
|
|
46
|
+
|
|
47
|
+
protected userSessionsKey(userId: string) {
|
|
48
|
+
return `user:sessions:${userId}`;
|
|
23
49
|
}
|
|
24
|
-
|
|
25
|
-
|
|
50
|
+
|
|
51
|
+
protected hashToken(token: string) {
|
|
52
|
+
return crypto.createHash("sha256").update(token).digest("hex");
|
|
26
53
|
}
|
|
27
54
|
|
|
28
|
-
protected abstract
|
|
29
|
-
|
|
30
|
-
protected abstract
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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:
|
|
45
|
-
email:
|
|
95
|
+
userID: user.id,
|
|
96
|
+
email: user.email,
|
|
97
|
+
sessionID: sessionId,
|
|
46
98
|
});
|
|
47
99
|
|
|
48
100
|
const refreshToken = TokenModule.signRefreshToken({
|
|
49
|
-
userID:
|
|
50
|
-
email:
|
|
101
|
+
userID: user.id,
|
|
102
|
+
email: user.email,
|
|
103
|
+
sessionID: sessionId,
|
|
104
|
+
jti: refreshJti,
|
|
51
105
|
});
|
|
52
106
|
|
|
53
|
-
const
|
|
54
|
-
|
|
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.
|
|
58
|
-
this.
|
|
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
|
|
120
|
+
return {
|
|
121
|
+
accessToken,
|
|
122
|
+
refreshToken,
|
|
123
|
+
};
|
|
64
124
|
}
|
|
65
125
|
|
|
66
|
-
public async
|
|
67
|
-
return await this.
|
|
126
|
+
public async validateSession(sessionId: string): Promise<SessionRecord | null> {
|
|
127
|
+
return await this.getSession(sessionId);
|
|
68
128
|
}
|
|
69
129
|
|
|
70
|
-
public async
|
|
71
|
-
const
|
|
72
|
-
if (!
|
|
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
|
|
75
|
-
const sessionData: SessionUser = { ...payload, accessToken };
|
|
134
|
+
const incomingHash = this.hashToken(refreshToken);
|
|
76
135
|
|
|
77
|
-
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
136
|
+
if (incomingHash !== session.refreshTokenHash) {
|
|
137
|
+
await this.endSession(sessionId);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
81
140
|
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
|
90
|
-
const session = await this.
|
|
91
|
-
if (!session) return
|
|
167
|
+
public async endSession(sessionId: string): Promise<void> {
|
|
168
|
+
const session = await this.getSession(sessionId);
|
|
169
|
+
if (!session) return;
|
|
92
170
|
|
|
93
|
-
await this.
|
|
94
|
-
return null;
|
|
171
|
+
await Promise.all([this.deleteSession(sessionId), this.removeUserSession(session.user.id, sessionId)]);
|
|
95
172
|
}
|
|
96
173
|
|
|
97
|
-
public async
|
|
98
|
-
const
|
|
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
|
-
|
|
103
|
-
|
|
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 {
|
|
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
|
|
7
|
-
private
|
|
8
|
+
private sessionStore: RedisStore<SessionRecord>;
|
|
9
|
+
private sessionIndexStore: RedisStore<SessionIndex>;
|
|
8
10
|
|
|
9
|
-
constructor() {
|
|
11
|
+
constructor(type: SessionType) {
|
|
10
12
|
super();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
this.type = type;
|
|
15
|
+
|
|
16
|
+
this.sessionStore = new RedisStore<SessionRecord>({
|
|
17
|
+
namespace: "auth-session",
|
|
13
18
|
});
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
|
|
20
|
+
this.sessionIndexStore = new RedisStore<SessionIndex>({
|
|
21
|
+
namespace: "auth-session-index",
|
|
16
22
|
});
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
protected
|
|
20
|
-
|
|
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
|
|
24
|
-
const
|
|
25
|
-
return
|
|
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
|
|
29
|
-
await this.
|
|
35
|
+
protected async deleteSession(sessionId: string): Promise<void> {
|
|
36
|
+
await this.sessionStore.del(sessionId);
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
protected async
|
|
33
|
-
|
|
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
|
|
37
|
-
const
|
|
38
|
-
return
|
|
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
|
|
42
|
-
await this.
|
|
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 <
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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<
|
|
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 <
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|
|
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 <
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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<
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
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 <
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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<
|
|
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
|
-
|
|
228
|
-
|
|
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) {
|
|
@@ -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
|