@sqrzro/server 2.0.0-bz.29 → 2.0.0-bz.30

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 (88) hide show
  1. package/dist/auth/index.cjs +59 -0
  2. package/dist/auth/index.cjs.map +1 -0
  3. package/dist/auth/index.d.cts +100 -0
  4. package/dist/auth/index.d.ts +100 -6
  5. package/dist/auth/index.js +21 -6
  6. package/dist/auth/index.js.map +1 -0
  7. package/dist/cache/index.cjs +10 -0
  8. package/dist/cache/index.cjs.map +1 -0
  9. package/dist/cache/index.d.cts +4 -0
  10. package/dist/cache/index.d.ts +4 -1
  11. package/dist/cache/index.js +7 -1
  12. package/dist/cache/index.js.map +1 -0
  13. package/dist/database/schema.cjs +16 -0
  14. package/dist/database/schema.cjs.map +1 -0
  15. package/dist/database/schema.d.cts +288 -0
  16. package/dist/database/schema.d.ts +38 -34
  17. package/dist/database/schema.js +7 -42
  18. package/dist/database/schema.js.map +1 -0
  19. package/dist/forms/index.cjs +22 -0
  20. package/dist/forms/index.cjs.map +1 -0
  21. package/dist/forms/index.d.cts +50 -0
  22. package/dist/forms/index.d.ts +50 -3
  23. package/dist/forms/index.js +8 -3
  24. package/dist/forms/index.js.map +1 -0
  25. package/dist/lists/index.cjs +9 -0
  26. package/dist/lists/index.cjs.map +1 -0
  27. package/dist/lists/{ListService.d.ts → index.d.cts} +5 -3
  28. package/dist/lists/index.d.ts +18 -1
  29. package/dist/lists/index.js +7 -1
  30. package/dist/lists/index.js.map +1 -0
  31. package/dist/mail/index.cjs +9 -0
  32. package/dist/mail/index.cjs.map +1 -0
  33. package/dist/mail/index.d.cts +13 -0
  34. package/dist/mail/index.d.ts +13 -1
  35. package/dist/mail/index.js +5 -1
  36. package/dist/mail/index.js.map +1 -0
  37. package/dist/middleware.cjs +9 -0
  38. package/dist/middleware.cjs.map +1 -0
  39. package/dist/middleware.d.cts +5 -0
  40. package/dist/middleware.d.ts +5 -3
  41. package/dist/middleware.js +6 -45
  42. package/dist/middleware.js.map +1 -0
  43. package/dist/url/index.cjs +12 -0
  44. package/dist/url/index.cjs.map +1 -0
  45. package/dist/url/{URLService.d.ts → index.d.cts} +6 -4
  46. package/dist/url/index.d.ts +33 -1
  47. package/dist/url/index.js +7 -1
  48. package/dist/url/index.js.map +1 -0
  49. package/jest.config.js +7 -0
  50. package/package.json +56 -29
  51. package/dist/auth/AuthService.d.ts +0 -14
  52. package/dist/auth/AuthService.js +0 -135
  53. package/dist/auth/ClientService.d.ts +0 -11
  54. package/dist/auth/ClientService.js +0 -47
  55. package/dist/auth/LoginRequest.d.ts +0 -4
  56. package/dist/auth/LoginRequest.js +0 -8
  57. package/dist/auth/MFARequest.d.ts +0 -4
  58. package/dist/auth/MFARequest.js +0 -9
  59. package/dist/auth/MFAService.d.ts +0 -6
  60. package/dist/auth/MFAService.js +0 -105
  61. package/dist/auth/PasswordRequest.d.ts +0 -4
  62. package/dist/auth/PasswordRequest.js +0 -12
  63. package/dist/auth/PasswordResetRequest.d.ts +0 -4
  64. package/dist/auth/PasswordResetRequest.js +0 -13
  65. package/dist/auth/PasswordService.d.ts +0 -8
  66. package/dist/auth/PasswordService.js +0 -54
  67. package/dist/auth/SessionService.d.ts +0 -42
  68. package/dist/auth/SessionService.js +0 -127
  69. package/dist/auth/interfaces.d.ts +0 -20
  70. package/dist/auth/interfaces.js +0 -1
  71. package/dist/cache/CacheService.d.ts +0 -2
  72. package/dist/cache/CacheService.js +0 -27
  73. package/dist/database/DatabaseService.d.ts +0 -7
  74. package/dist/database/DatabaseService.js +0 -12
  75. package/dist/forms/FormService.d.ts +0 -18
  76. package/dist/forms/FormService.js +0 -98
  77. package/dist/forms/ImageService.d.ts +0 -7
  78. package/dist/forms/ImageService.js +0 -19
  79. package/dist/forms/ValidationError.d.ts +0 -4
  80. package/dist/forms/ValidationError.js +0 -7
  81. package/dist/forms/ValidationService.d.ts +0 -20
  82. package/dist/forms/ValidationService.js +0 -59
  83. package/dist/forms/lang.d.ts +0 -2
  84. package/dist/forms/lang.js +0 -115
  85. package/dist/lists/ListService.js +0 -28
  86. package/dist/mail/MailService.d.ts +0 -12
  87. package/dist/mail/MailService.js +0 -55
  88. package/dist/url/URLService.js +0 -61
@@ -1,4 +0,0 @@
1
- import Joi from 'joi';
2
- import type { LoginFormFields } from './interfaces';
3
- declare const LoginRequest: Joi.ObjectSchema<LoginFormFields>;
4
- export default LoginRequest;
@@ -1,8 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-magic-numbers */
2
- import Joi from 'joi';
3
- import { createSchema } from '../forms/ValidationService';
4
- const LoginRequest = createSchema({
5
- email: Joi.string().email({ minDomainSegments: 2, tlds: false }).required(),
6
- password: Joi.string().min(8).required(),
7
- });
8
- export default LoginRequest;
@@ -1,4 +0,0 @@
1
- import Joi from 'joi';
2
- import type { MFAFormFields } from './interfaces';
3
- declare const MFARequest: Joi.ObjectSchema<MFAFormFields>;
4
- export default MFARequest;
@@ -1,9 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-magic-numbers */
2
- import Joi from 'joi';
3
- import { createSchema } from '../forms/ValidationService';
4
- const MFARequest = createSchema({
5
- token: Joi.string()
6
- .pattern(/^[0-9]{6}$/u)
7
- .required(),
8
- });
9
- export default MFARequest;
@@ -1,6 +0,0 @@
1
- import type { Errorable } from '@sqrzro/interfaces';
2
- import type { MFAFormFields, UserObject } from './interfaces';
3
- export declare function checkMFAEnabled(): boolean;
4
- export declare function generateMFA(name: string, email?: string): Promise<string | null>;
5
- export declare function checkUserHasMFA(user: UserObject): Promise<boolean>;
6
- export declare function handleMFAForm(formData: MFAFormFields): Promise<Errorable<boolean>>;
@@ -1,105 +0,0 @@
1
- import { and, eq, isNotNull, isNull } from 'drizzle-orm';
2
- import qrcode from 'qrcode';
3
- import { authenticator } from 'otplib';
4
- import { db } from '../database/DatabaseService';
5
- import { authMFATable, authUserTable } from '../database/schema';
6
- import { submitForm } from '../forms/FormService';
7
- import MFARequest from './MFARequest';
8
- import { createUserSession, generateID, getSessionUser } from './SessionService';
9
- export function checkMFAEnabled() {
10
- return process.env.AUTH_MFA_ENABLED !== 'false';
11
- }
12
- export async function generateMFA(name, email) {
13
- if (!checkMFAEnabled() || !email) {
14
- return null;
15
- }
16
- const [user] = await db
17
- .select()
18
- .from(authUserTable)
19
- .where(eq(authUserTable.email, email))
20
- .limit(1);
21
- if (!user) {
22
- return null;
23
- }
24
- const secret = authenticator.generateSecret();
25
- const otpauth = authenticator.keyuri(email, name, secret);
26
- // Delete all the unverified MFA entries for this user
27
- await db
28
- .delete(authMFATable)
29
- .where(and(eq(authMFATable.userId, user.id), isNull(authMFATable.verifiedAt)));
30
- // Add the new MFA entry
31
- await db.insert(authMFATable).values({
32
- id: generateID(),
33
- name: 'Default',
34
- secret,
35
- userId: user.id,
36
- });
37
- return new Promise((resolve, reject) => {
38
- qrcode.toDataURL(otpauth, { rendererOpts: { quality: 1 }, margin: 0, scale: 2 }, (err, data) => {
39
- if (err) {
40
- reject(err);
41
- }
42
- resolve(data);
43
- });
44
- });
45
- }
46
- export async function checkUserHasMFA(user) {
47
- if (!checkMFAEnabled()) {
48
- return false;
49
- }
50
- const [mfa] = await db
51
- .select()
52
- .from(authMFATable)
53
- .where(and(eq(authMFATable.userId, user.id), isNotNull(authMFATable.verifiedAt)))
54
- .limit(1);
55
- return Boolean(mfa);
56
- }
57
- async function markAsVerified(userID) {
58
- if (!checkMFAEnabled()) {
59
- return;
60
- }
61
- await db
62
- .update(authMFATable)
63
- .set({ verifiedAt: new Date() })
64
- .where(eq(authMFATable.userId, userID));
65
- }
66
- async function validateUserToken(userID, token) {
67
- if (!checkMFAEnabled()) {
68
- return false;
69
- }
70
- const [mfa] = await db
71
- .select()
72
- .from(authMFATable)
73
- .where(eq(authMFATable.userId, userID))
74
- .limit(1);
75
- if (!mfa) {
76
- return false;
77
- }
78
- return authenticator.check(token, mfa.secret);
79
- }
80
- async function handleMFA(formData) {
81
- if (!checkMFAEnabled()) {
82
- return false;
83
- }
84
- const user = await getSessionUser();
85
- if (!user) {
86
- return false;
87
- }
88
- const isValid = await validateUserToken(user.id, formData.token);
89
- if (!isValid) {
90
- return false;
91
- }
92
- await markAsVerified(user.id);
93
- return createUserSession(user.id, 'AUTHED');
94
- }
95
- export async function handleMFAForm(formData) {
96
- if (!checkMFAEnabled()) {
97
- return [null, new Error('MFA is not enabled')];
98
- }
99
- const response = await submitForm({
100
- fn: handleMFA,
101
- formData,
102
- request: MFARequest,
103
- });
104
- return response;
105
- }
@@ -1,4 +0,0 @@
1
- import Joi from 'joi';
2
- import type { PasswordFormFields } from './interfaces';
3
- declare const PasswordRequest: Joi.ObjectSchema<PasswordFormFields>;
4
- export default PasswordRequest;
@@ -1,12 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-magic-numbers */
2
- import Joi from 'joi';
3
- import { createSchema } from '../forms/ValidationService';
4
- const PasswordRequest = createSchema({
5
- email: Joi.string().max(60).email({ minDomainSegments: 2, tlds: false }).required().messages({
6
- 'any.required': 'Please provide your email address, so we can send you a reset link',
7
- 'string.empty': 'Please provide your email address, so we can send you a reset link',
8
- 'string.email': 'Please make sure your email address is valid',
9
- 'string.max': 'Please make sure your email address is valid',
10
- }),
11
- });
12
- export default PasswordRequest;
@@ -1,4 +0,0 @@
1
- import Joi from 'joi';
2
- import type { PasswordResetFormFields } from './interfaces';
3
- declare const PasswordResetRequest: Joi.ObjectSchema<PasswordResetFormFields>;
4
- export default PasswordResetRequest;
@@ -1,13 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-magic-numbers */
2
- import Joi from 'joi';
3
- import { createSchema } from '../forms/ValidationService';
4
- const PasswordResetRequest = createSchema({
5
- token: Joi.string()
6
- .pattern(/[a-z0-9]{40}/u)
7
- .required(),
8
- password: Joi.string().required().messages({
9
- 'any.required': 'Please provide your new password',
10
- 'string.empty': 'Please provide your new password',
11
- }),
12
- });
13
- export default PasswordResetRequest;
@@ -1,8 +0,0 @@
1
- type PasswordRule = 'lower' | 'min' | 'number' | 'symbol' | 'upper';
2
- export type PasswordRuleObject = Partial<Record<PasswordRule, number>>;
3
- type PasswordValidityObject = Record<PasswordRule, boolean>;
4
- export declare function hashPassword(password: string): Promise<string>;
5
- export declare function verifyPassword(data?: string, encrypted?: string): Promise<boolean>;
6
- export declare function getPasswordComplexity(password: string, rules?: Partial<PasswordRuleObject>): Promise<Partial<PasswordValidityObject>>;
7
- export declare function checkPasswordComplexity(password: string, rules?: Partial<PasswordRuleObject>): Promise<Partial<boolean>>;
8
- export {};
@@ -1,54 +0,0 @@
1
- import bcrypt from 'bcryptjs';
2
- const PW_SALT_ROUNDS = 12;
3
- const PASSWORD_RULES = {
4
- min: 8,
5
- upper: 1,
6
- lower: 1,
7
- number: 1,
8
- symbol: 1,
9
- };
10
- function checkPasswordMin(password, value) {
11
- return password.length >= value;
12
- }
13
- function checkPasswordUpper(password, value) {
14
- return password.replace(/[^A-Z]/gu, '').length >= value;
15
- }
16
- function checkPasswordLower(password, value) {
17
- return password.replace(/[^a-z]/gu, '').length >= value;
18
- }
19
- function checkPasswordNumber(password, value) {
20
- return password.replace(/[^0-9]/gu, '').length >= value;
21
- }
22
- function checkPasswordSymbol(password, value) {
23
- return password.replace(/[^$]/gu, '').length >= value;
24
- }
25
- const PASSWORD_FUNCTIONS = {
26
- min: checkPasswordMin,
27
- upper: checkPasswordUpper,
28
- lower: checkPasswordLower,
29
- number: checkPasswordNumber,
30
- symbol: checkPasswordSymbol,
31
- };
32
- export async function hashPassword(password) {
33
- const hash = await bcrypt.hash(password, PW_SALT_ROUNDS);
34
- return hash;
35
- }
36
- export async function verifyPassword(data, encrypted) {
37
- if (!data || !encrypted) {
38
- return false;
39
- }
40
- const verified = await bcrypt.compare(data, encrypted);
41
- return verified;
42
- }
43
- export async function getPasswordComplexity(password, rules = PASSWORD_RULES) {
44
- const entries = Object.entries(rules);
45
- const validity = entries.reduce((acc, [rule, value]) => {
46
- acc[rule] = PASSWORD_FUNCTIONS[rule](password, value);
47
- return acc;
48
- }, {});
49
- return Promise.resolve(validity);
50
- }
51
- export async function checkPasswordComplexity(password, rules = PASSWORD_RULES) {
52
- const validity = await getPasswordComplexity(password, rules);
53
- return Promise.resolve(Object.values(validity).every(Boolean));
54
- }
@@ -1,42 +0,0 @@
1
- import { Lucia } from 'lucia';
2
- import { NextResponse } from 'next/server';
3
- import type { NextRequest } from 'next/server';
4
- import type { Scope } from '../database/schema';
5
- interface ScopeData {
6
- allowedRoute?: string;
7
- redirectOnAuth?: string;
8
- redirectOnUnauth?: string;
9
- }
10
- export type ScopeObject = Record<Scope, ScopeData>;
11
- interface DatabaseSessionAttributes {
12
- scope: Scope;
13
- }
14
- interface DatabaseUserAttributes {
15
- email: string;
16
- role: number;
17
- }
18
- declare module 'lucia' {
19
- interface Register {
20
- Lucia: typeof lucia;
21
- DatabaseSessionAttributes: DatabaseSessionAttributes;
22
- DatabaseUserAttributes: DatabaseUserAttributes;
23
- }
24
- }
25
- export declare const lucia: Lucia<DatabaseSessionAttributes, DatabaseUserAttributes>;
26
- export declare function generateID<T extends string = string>(prefix?: string, length?: number): T;
27
- export declare function invalidateSession(id: string): Promise<void>;
28
- export declare function invalidateUserSessions(id: string): Promise<void>;
29
- export declare function createUserSession(id: string, scope?: Scope): Promise<boolean>;
30
- export declare function getSessionID(): string | null;
31
- export declare function checkSessionExists(): boolean;
32
- export declare function getSessionUser(): Promise<{
33
- id: string;
34
- email: string;
35
- role: number;
36
- } | null>;
37
- export declare function checkRouteAllowed(pathname: string, route?: string): boolean;
38
- export declare function getScopes(): Promise<ScopeObject>;
39
- export declare function getScopeByID(id: Scope): Promise<ScopeData>;
40
- export declare function setScopes(customScopes?: Partial<ScopeObject>): Promise<void>;
41
- export declare function handleSession(request: NextRequest, customScopes?: Partial<ScopeObject>): Promise<NextResponse>;
42
- export {};
@@ -1,127 +0,0 @@
1
- import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
2
- import { Lucia, generateId } from 'lucia';
3
- import { cookies } from 'next/headers';
4
- import { NextResponse } from 'next/server';
5
- import { match } from 'path-to-regexp';
6
- import { getAllowedRoles } from './AuthService';
7
- import { db } from '../database/DatabaseService';
8
- import { authSessionTable, authUserTable } from '../database/schema';
9
- import { getFromCache, setToCache } from '../cache/CacheService';
10
- import { getOrigin } from '../url/URLService';
11
- import { getFromObject } from '@sqrzro/utility';
12
- const DEFAULT_REDIRECT = '/auth/login';
13
- const ID_LENGTH = 16;
14
- const DEFAULT_SCOPES = {
15
- ANON: {
16
- allowedRoute: '/auth/(login|password)',
17
- redirectOnUnauth: DEFAULT_REDIRECT,
18
- },
19
- MFA: {
20
- allowedRoute: '/auth/mfa',
21
- redirectOnUnauth: '/auth/mfa',
22
- },
23
- AUTHED: {
24
- allowedRoute: '*',
25
- redirectOnAuth: '/',
26
- },
27
- };
28
- const adapter = new DrizzlePostgreSQLAdapter(db, authSessionTable, authUserTable);
29
- export const lucia = new Lucia(adapter, {
30
- sessionCookie: {
31
- attributes: {
32
- secure: process.env.NODE_ENV === 'production',
33
- },
34
- name: process.env.AUTH_COOKIE_NAME || 'auth_session',
35
- },
36
- getSessionAttributes: (attributes) => ({
37
- scope: attributes.scope,
38
- }),
39
- getUserAttributes: (attributes) => ({
40
- email: attributes.email,
41
- role: attributes.role,
42
- }),
43
- });
44
- export function generateID(prefix = '', length = ID_LENGTH) {
45
- return `${prefix ? `${prefix}_` : ''}${generateId(length)}`;
46
- }
47
- export async function invalidateSession(id) {
48
- const cookie = lucia.createBlankSessionCookie();
49
- cookies().set(cookie.name, cookie.value, cookie.attributes);
50
- return lucia.invalidateSession(id);
51
- }
52
- export async function invalidateUserSessions(id) {
53
- return lucia.invalidateUserSessions(id);
54
- }
55
- export async function createUserSession(id, scope = 'ANON') {
56
- const session = await lucia.createSession(id, { scope });
57
- const sessionCookie = lucia.createSessionCookie(session.id);
58
- cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
59
- return true;
60
- }
61
- export function getSessionID() {
62
- return cookies().get(lucia.sessionCookieName)?.value ?? null;
63
- }
64
- export function checkSessionExists() {
65
- return Boolean(getSessionID());
66
- }
67
- export async function getSessionUser() {
68
- const sessionID = getSessionID();
69
- if (!sessionID) {
70
- return null;
71
- }
72
- const { user } = await lucia.validateSession(sessionID);
73
- if (!user?.role || !getAllowedRoles().includes(user.role)) {
74
- return null;
75
- }
76
- return getFromObject(user, ['id', 'email', 'role']);
77
- }
78
- export function checkRouteAllowed(pathname, route) {
79
- if (!route) {
80
- return false;
81
- }
82
- if (route === '*') {
83
- return true;
84
- }
85
- return match(route)(pathname) !== false;
86
- }
87
- export async function getScopes() {
88
- const scopes = await getFromCache(`${getOrigin()}:scopes`);
89
- return scopes ? JSON.parse(scopes) : DEFAULT_SCOPES;
90
- }
91
- export async function getScopeByID(id) {
92
- const scopes = await getScopes();
93
- return scopes[id];
94
- }
95
- export async function setScopes(customScopes) {
96
- const scopes = {
97
- ANON: {
98
- ...DEFAULT_SCOPES.ANON,
99
- ...customScopes?.ANON,
100
- },
101
- MFA: {
102
- ...DEFAULT_SCOPES.MFA,
103
- ...customScopes?.MFA,
104
- },
105
- AUTHED: {
106
- ...DEFAULT_SCOPES.AUTHED,
107
- ...customScopes?.AUTHED,
108
- },
109
- };
110
- return setToCache(`${getOrigin()}:scopes`, JSON.stringify(scopes));
111
- }
112
- async function validateSessionFromID(sessionID, pathname) {
113
- const scopes = await getScopes();
114
- const { session, user } = await lucia.validateSession(sessionID);
115
- const scope = scopes[session && getAllowedRoles().includes(user?.role) ? session.scope : 'ANON'];
116
- return checkRouteAllowed(pathname, scope.allowedRoute)
117
- ? null
118
- : scope.redirectOnUnauth || DEFAULT_REDIRECT;
119
- }
120
- export async function handleSession(request, customScopes) {
121
- const sessionID = request.nextUrl.searchParams.get('id') || '';
122
- const pathname = request.nextUrl.searchParams.get('pathname') || '/';
123
- await setScopes(customScopes);
124
- return NextResponse.json({
125
- redirect: await validateSessionFromID(sessionID, pathname),
126
- });
127
- }
@@ -1,20 +0,0 @@
1
- export interface LoginFormFields {
2
- email: string;
3
- password: string;
4
- }
5
- export interface MFAFormFields {
6
- token: string;
7
- }
8
- export interface PasswordFormFields {
9
- email: string;
10
- }
11
- export interface PasswordResetFormFields {
12
- password: string;
13
- token: string;
14
- }
15
- export interface UserObject {
16
- id: string;
17
- email: string;
18
- password?: string | null;
19
- role?: number;
20
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- export declare function getFromCache(key: string): Promise<string | null>;
2
- export declare function setToCache(key: string, value: string): Promise<void>;
@@ -1,27 +0,0 @@
1
- import { createClient } from 'redis';
2
- let client = null;
3
- function isLocalhost(url) {
4
- return url.includes('localhost') || url.includes('127.0.0.1');
5
- }
6
- async function getClient() {
7
- if (!process.env.REDIS_URL) {
8
- throw new Error('REDIS_URL is not defined. Access to the cache is not possible.');
9
- }
10
- if (client) {
11
- return client;
12
- }
13
- client = createClient({
14
- socket: {
15
- tls: !isLocalhost(process.env.REDIS_URL),
16
- },
17
- url: process.env.REDIS_URL,
18
- });
19
- await client.connect();
20
- return client;
21
- }
22
- export async function getFromCache(key) {
23
- return (await getClient()).get(key);
24
- }
25
- export async function setToCache(key, value) {
26
- await (await getClient()).set(key, value);
27
- }
@@ -1,7 +0,0 @@
1
- import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
2
- declare global {
3
- var szdb: ReturnType<typeof createSingleton> | undefined;
4
- }
5
- declare function createSingleton(): PostgresJsDatabase;
6
- export declare const db: PostgresJsDatabase;
7
- export {};
@@ -1,12 +0,0 @@
1
- import { drizzle } from 'drizzle-orm/postgres-js';
2
- import postgres from 'postgres';
3
- function createSingleton() {
4
- if (!process.env.DATABASE_URL) {
5
- throw new Error('DATABASE_URL is not defined');
6
- }
7
- return drizzle(postgres(process.env.DATABASE_URL, { prepare: false }));
8
- }
9
- export const db = globalThis.szdb ?? createSingleton();
10
- if (!process.env.VERCEL_ENV) {
11
- globalThis.szdb = db;
12
- }
@@ -1,18 +0,0 @@
1
- import type { SerializedErrorable } from '@sqrzro/interfaces';
2
- import type Joi from 'joi';
3
- import { NextResponse } from 'next/server';
4
- import ValidationError from './ValidationError';
5
- interface SubmitFormArgs<F extends object> {
6
- formData: F;
7
- onSuccess?: (model: F) => Promise<void> | void;
8
- onValidationError?: (error: ValidationError) => void;
9
- request?: Joi.ObjectSchema<F>;
10
- }
11
- interface SubmitFormArgsWithFn<F extends object, M> extends Omit<SubmitFormArgs<F>, 'onSuccess'> {
12
- fn: (data: F) => Promise<M | null>;
13
- onSuccess?: (model: M) => Promise<void> | void;
14
- }
15
- export declare function submitForm<F extends object>(args: SubmitFormArgs<F>): Promise<SerializedErrorable<F>>;
16
- export declare function submitForm<F extends object, M>(args: SubmitFormArgsWithFn<F, M>): Promise<SerializedErrorable<M>>;
17
- export declare function submitAPIRequest<F extends object, M>(args: SubmitFormArgsWithFn<F, M>): Promise<[M, null] | [null, NextResponse]>;
18
- export {};
@@ -1,98 +0,0 @@
1
- /* eslint-disable max-statements */
2
- import { NextResponse } from 'next/server';
3
- import ValidationError from './ValidationError';
4
- import { validateSchema } from './ValidationService';
5
- function serializeError(err) {
6
- return {
7
- cause: err.cause,
8
- message: err.message,
9
- name: err.name,
10
- stack: err.stack,
11
- };
12
- }
13
- function hasFn(args) {
14
- return Boolean(Object.prototype.hasOwnProperty.call(args, 'fn'));
15
- }
16
- export async function submitForm(args) {
17
- let data = { ...args.formData };
18
- if (args.request) {
19
- const [validated, validationError] = await validateSchema(args.formData, args.request);
20
- if (validationError !== null) {
21
- if (validationError instanceof ValidationError) {
22
- args.onValidationError?.(validationError);
23
- }
24
- return [null, serializeError(validationError)];
25
- }
26
- data = validated;
27
- }
28
- if (!hasFn(args)) {
29
- try {
30
- await args.onSuccess?.(data);
31
- }
32
- catch (err) {
33
- if (err instanceof Error) {
34
- return [null, serializeError(err)];
35
- }
36
- return [
37
- null,
38
- serializeError(new Error('The submitForm onSuccess function encountered an unknown error')),
39
- ];
40
- }
41
- return [data, null];
42
- }
43
- let model = null;
44
- try {
45
- model = await args.fn(data);
46
- }
47
- catch (err) {
48
- if (err instanceof ValidationError) {
49
- args.onValidationError?.(err);
50
- return [null, serializeError(err)];
51
- }
52
- if (err instanceof Error) {
53
- return [null, serializeError(err)];
54
- }
55
- return [
56
- null,
57
- serializeError(new Error('The function supplied to submitForm encountered an unknown error')),
58
- ];
59
- }
60
- if (!model) {
61
- return [
62
- null,
63
- serializeError(new Error('No model has been returned from the function supplied to submitForm')),
64
- ];
65
- }
66
- try {
67
- await args.onSuccess?.(model);
68
- }
69
- catch (err) {
70
- if (err instanceof Error) {
71
- return [null, serializeError(err)];
72
- }
73
- return [
74
- null,
75
- serializeError(new Error('The submitForm onSuccess function encountered an unknown error')),
76
- ];
77
- }
78
- return [model, null];
79
- }
80
- export async function submitAPIRequest(args) {
81
- const [response, error] = await submitForm(args);
82
- if (error !== null) {
83
- if (error.name === 'ValidationError') {
84
- try {
85
- const errors = JSON.parse(error.message);
86
- return [null, NextResponse.json(errors, { status: 422 })];
87
- }
88
- catch (err) {
89
- return [
90
- null,
91
- NextResponse.json({ message: 'An unknown error occured' }, { status: 500 }),
92
- ];
93
- }
94
- }
95
- return [null, NextResponse.json({ message: error.message }, { status: 500 })];
96
- }
97
- return [response, null];
98
- }
@@ -1,7 +0,0 @@
1
- import type { Errorable } from '@sqrzro/interfaces';
2
- interface ImageValidationConfig {
3
- maxSize?: number;
4
- types?: string[];
5
- }
6
- export declare function validateImage(image: File | null, config?: ImageValidationConfig): Promise<Errorable<File>>;
7
- export {};
@@ -1,19 +0,0 @@
1
- 'use server';
2
- const DEFAULT_TYPES = ['image/png', 'image/jpeg', 'image/jpg'];
3
- // 5MB
4
- const DEFAULT_MAX_SIZE = 5242880;
5
- export async function validateImage(image, config) {
6
- if (!image) {
7
- return Promise.resolve([null, new Error('IMAGE_UNDEFINED')]);
8
- }
9
- if (!(image instanceof File)) {
10
- return Promise.resolve([null, new Error('IMAGE_NOT_VALID')]);
11
- }
12
- if (!(config?.types || DEFAULT_TYPES).includes(image.type)) {
13
- return Promise.resolve([null, new Error('IMAGE_TYPE_NOT_VALID')]);
14
- }
15
- if (image.size > (config?.maxSize || DEFAULT_MAX_SIZE)) {
16
- return Promise.resolve([null, new Error('IMAGE_TOO_HEAVY')]);
17
- }
18
- return Promise.resolve([image, null]);
19
- }
@@ -1,4 +0,0 @@
1
- declare class ValidationError extends Error {
2
- constructor(messages: Record<string, string>);
3
- }
4
- export default ValidationError;
@@ -1,7 +0,0 @@
1
- class ValidationError extends Error {
2
- constructor(messages) {
3
- super(JSON.stringify(messages));
4
- this.name = 'ValidationError';
5
- }
6
- }
7
- export default ValidationError;