rename some functions, change name to username ( username table), fix exports, add validations folder
All checks were successful
Code quality checks / biome (push) Successful in 9s
All checks were successful
Code quality checks / biome (push) Successful in 9s
This commit is contained in:
parent
61db491848
commit
f93cef442a
15 changed files with 280 additions and 72 deletions
|
@ -1,17 +1,17 @@
|
||||||
import { logger } from "@creations.works/logger";
|
import { logger } from "@creations.works/logger";
|
||||||
|
|
||||||
export const environment: Environment = {
|
const environment: Environment = {
|
||||||
port: Number.parseInt(process.env.PORT || "", 10),
|
port: Number.parseInt(process.env.PORT || "", 10),
|
||||||
host: process.env.HOST || "0.0.0.0",
|
host: process.env.HOST || "0.0.0.0",
|
||||||
development:
|
development:
|
||||||
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
|
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const redisTtl: number = process.env.REDIS_TTL
|
const redisTtl: number = process.env.REDIS_TTL
|
||||||
? Number.parseInt(process.env.REDIS_TTL, 10)
|
? Number.parseInt(process.env.REDIS_TTL, 10)
|
||||||
: 60 * 60 * 1; // 1 hour
|
: 60 * 60 * 1; // 1 hour
|
||||||
|
|
||||||
export const cassandra: CassandraConfig = {
|
const cassandra: CassandraConfig = {
|
||||||
host: process.env.CASSANDRA_HOST || "localhost",
|
host: process.env.CASSANDRA_HOST || "localhost",
|
||||||
port: Number.parseInt(process.env.CASSANDRA_PORT || "9042", 10),
|
port: Number.parseInt(process.env.CASSANDRA_PORT || "9042", 10),
|
||||||
keyspace: process.env.CASSANDRA_KEYSPACE || "void_db",
|
keyspace: process.env.CASSANDRA_KEYSPACE || "void_db",
|
||||||
|
@ -24,7 +24,7 @@ export const cassandra: CassandraConfig = {
|
||||||
authEnabled: process.env.CASSANDRA_AUTH_ENABLED === "false",
|
authEnabled: process.env.CASSANDRA_AUTH_ENABLED === "false",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function verifyRequiredVariables(): void {
|
function verifyRequiredVariables(): void {
|
||||||
const requiredVariables = [
|
const requiredVariables = [
|
||||||
"HOST",
|
"HOST",
|
||||||
"PORT",
|
"PORT",
|
||||||
|
@ -51,3 +51,5 @@ export function verifyRequiredVariables(): void {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { environment, cassandra, redisTtl, verifyRequiredVariables };
|
||||||
|
|
|
@ -6,13 +6,13 @@ import {
|
||||||
verifyRequiredVariables,
|
verifyRequiredVariables,
|
||||||
} from "@config/environment";
|
} from "@config/environment";
|
||||||
import { logger } from "@creations.works/logger";
|
import { logger } from "@creations.works/logger";
|
||||||
import { CassandraService } from "@lib/cassandra";
|
import { cassandra } from "@lib/cassandra";
|
||||||
|
|
||||||
async function setup(): Promise<void> {
|
async function setup(): Promise<void> {
|
||||||
verifyRequiredVariables();
|
verifyRequiredVariables();
|
||||||
await CassandraService.connect({ withKeyspace: false });
|
await cassandra.connect({ withKeyspace: false });
|
||||||
|
|
||||||
const client = CassandraService.getClient();
|
const client = cassandra.getClient();
|
||||||
const keyspace = cassandraConfig.keyspace;
|
const keyspace = cassandraConfig.keyspace;
|
||||||
|
|
||||||
if (!keyspace) {
|
if (!keyspace) {
|
||||||
|
@ -60,7 +60,7 @@ setup()
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
CassandraService.shutdown().catch((error: Error) => {
|
cassandra.shutdown().catch((error: Error) => {
|
||||||
logger.error(["Error shutting down Cassandra client:", error as Error]);
|
logger.error(["Error shutting down Cassandra client:", error as Error]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { CassandraService } from "@lib/cassandra";
|
import { cassandra } from "@lib/cassandra";
|
||||||
|
|
||||||
async function createTable() {
|
async function createTable() {
|
||||||
await CassandraService.getClient().execute(`
|
await cassandra.getClient().execute(`
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
name TEXT,
|
username TEXT,
|
||||||
display_name TEXT,
|
display_name TEXT,
|
||||||
email TEXT,
|
email TEXT,
|
||||||
password TEXT,
|
password TEXT,
|
||||||
|
@ -14,6 +14,14 @@ async function createTable() {
|
||||||
updated_at TIMESTAMP
|
updated_at TIMESTAMP
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
await cassandra.getClient().execute(`
|
||||||
|
CREATE INDEX IF NOT EXISTS users_username_idx ON users (username);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await cassandra.getClient().execute(`
|
||||||
|
CREATE INDEX IF NOT EXISTS users_email_idx ON users (email);
|
||||||
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createTable };
|
export { createTable };
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
"lint": "bunx biome check",
|
"lint": "bunx biome check",
|
||||||
"lint:fix": "bunx biome check --fix",
|
"lint:fix": "bunx biome check --fix",
|
||||||
"cleanup": "rm -rf logs node_modules bun.lockdb",
|
"cleanup": "rm -rf logs node_modules bun.lockdb",
|
||||||
|
|
||||||
"setup": "bun run config/setup/index.ts"
|
"setup": "bun run config/setup/index.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -22,6 +21,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@creations.works/logger": "^1.0.3",
|
"@creations.works/logger": "^1.0.3",
|
||||||
"cassandra-driver": "^4.8.0"
|
"cassandra-driver": "^4.8.0",
|
||||||
|
"pika-id": "^1.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { serverHandler } from "@/server";
|
import { serverHandler } from "@/server";
|
||||||
import { verifyRequiredVariables } from "@config/environment";
|
import { verifyRequiredVariables } from "@config/environment";
|
||||||
import { logger } from "@creations.works/logger";
|
import { logger } from "@creations.works/logger";
|
||||||
import { CassandraService } from "@lib/cassandra";
|
import { cassandra } from "@lib/cassandra";
|
||||||
import { redis } from "bun";
|
import { redis } from "bun";
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
|
@ -16,7 +16,7 @@ async function main(): Promise<void> {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await CassandraService.connect();
|
await cassandra.connect();
|
||||||
|
|
||||||
serverHandler.initialize();
|
serverHandler.initialize();
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,4 +49,4 @@ class CassandraService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { CassandraService };
|
export { CassandraService as cassandra };
|
||||||
|
|
|
@ -9,10 +9,10 @@ const statusMessages: Record<number, string> = {
|
||||||
500: "Internal Server Error",
|
500: "Internal Server Error",
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function returnGenericJsonResponse(
|
function jsonResponse(
|
||||||
statusCode: number,
|
statusCode: number,
|
||||||
options: GenericJsonResponseOptions = {},
|
options: GenericJsonResponseOptions = {},
|
||||||
): Promise<Response> {
|
): Response {
|
||||||
const { headers, message, error, ...extra } = options;
|
const { headers, message, error, ...extra } = options;
|
||||||
|
|
||||||
if (statusCode === 204) {
|
if (statusCode === 204) {
|
||||||
|
@ -53,8 +53,9 @@ export async function returnGenericJsonResponse(
|
||||||
return Response.json(orderedResponse, {
|
return Response.json(orderedResponse, {
|
||||||
status: statusCode,
|
status: statusCode,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
|
||||||
...headers,
|
...headers,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { jsonResponse };
|
||||||
|
|
61
src/lib/pika.ts
Normal file
61
src/lib/pika.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import Pika from "pika-id";
|
||||||
|
|
||||||
|
const pika = new Pika([
|
||||||
|
"user",
|
||||||
|
{
|
||||||
|
prefix: "user",
|
||||||
|
description: "User ID",
|
||||||
|
},
|
||||||
|
"guild",
|
||||||
|
{
|
||||||
|
prefix: "guild",
|
||||||
|
description: "Guild ID",
|
||||||
|
},
|
||||||
|
"message",
|
||||||
|
{
|
||||||
|
prefix: "msg",
|
||||||
|
description: "Message ID",
|
||||||
|
},
|
||||||
|
"channel",
|
||||||
|
{
|
||||||
|
prefix: "ch",
|
||||||
|
description: "Channel ID",
|
||||||
|
},
|
||||||
|
"role",
|
||||||
|
{
|
||||||
|
prefix: "role",
|
||||||
|
description: "Role ID",
|
||||||
|
},
|
||||||
|
"emoji",
|
||||||
|
{
|
||||||
|
prefix: "emoji",
|
||||||
|
description: "Emoji ID",
|
||||||
|
},
|
||||||
|
"webhook",
|
||||||
|
{
|
||||||
|
prefix: "wh",
|
||||||
|
description: "Webhook ID",
|
||||||
|
},
|
||||||
|
"application",
|
||||||
|
{
|
||||||
|
prefix: "app",
|
||||||
|
description: "Application ID",
|
||||||
|
},
|
||||||
|
"invite",
|
||||||
|
{
|
||||||
|
prefix: "inv",
|
||||||
|
description: "Invite ID",
|
||||||
|
},
|
||||||
|
"sticker",
|
||||||
|
{
|
||||||
|
prefix: "sticker",
|
||||||
|
description: "Sticker ID",
|
||||||
|
},
|
||||||
|
"session",
|
||||||
|
{
|
||||||
|
prefix: "sess",
|
||||||
|
description: "Session ID",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export { pika };
|
19
src/lib/validators/email.ts
Normal file
19
src/lib/validators/email.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const emailRestrictions: { regex: RegExp } = {
|
||||||
|
regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
||||||
|
};
|
||||||
|
|
||||||
|
function isValidEmail(rawEmail: string): validationResult {
|
||||||
|
const email = rawEmail.trim();
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
return { valid: false, error: "Email is required" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emailRestrictions.regex.test(email)) {
|
||||||
|
return { valid: false, error: "Invalid email address" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
export { emailRestrictions, isValidEmail };
|
3
src/lib/validators/index.ts
Normal file
3
src/lib/validators/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "@lib/validators/name";
|
||||||
|
export * from "@lib/validators/password";
|
||||||
|
export * from "@lib/validators/email";
|
30
src/lib/validators/name.ts
Normal file
30
src/lib/validators/name.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// ? should support non english characters but won't mess up the url
|
||||||
|
const nameRestrictions: genericValidation = {
|
||||||
|
length: { min: 3, max: 20 },
|
||||||
|
regex: /^[\p{L}\p{N}._-]+$/u,
|
||||||
|
};
|
||||||
|
|
||||||
|
function isValidUsername(rawUsername: string): validationResult {
|
||||||
|
const username = rawUsername.trim().normalize("NFC");
|
||||||
|
|
||||||
|
if (!username) return { valid: false, error: "Username is required" };
|
||||||
|
|
||||||
|
if (username.length < nameRestrictions.length.min)
|
||||||
|
return { valid: false, error: "Username is too short" };
|
||||||
|
|
||||||
|
if (username.length > nameRestrictions.length.max)
|
||||||
|
return { valid: false, error: "Username is too long" };
|
||||||
|
|
||||||
|
if (!nameRestrictions.regex.test(username))
|
||||||
|
return { valid: false, error: "Username contains invalid characters" };
|
||||||
|
|
||||||
|
if (/^[._-]|[._-]$/.test(username))
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
error: "Username can't start or end with special characters",
|
||||||
|
};
|
||||||
|
|
||||||
|
return { valid: true, username };
|
||||||
|
}
|
||||||
|
|
||||||
|
export { nameRestrictions, isValidUsername };
|
36
src/lib/validators/password.ts
Normal file
36
src/lib/validators/password.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
const passwordRestrictions: genericValidation = {
|
||||||
|
length: { min: 12, max: 64 },
|
||||||
|
regex: /^(?=.*\p{Ll})(?=.*\p{Lu})(?=.*\d)(?=.*[^\w\s]).{12,64}$/u,
|
||||||
|
};
|
||||||
|
|
||||||
|
function isValidPassword(rawPassword: string): validationResult {
|
||||||
|
if (!rawPassword) {
|
||||||
|
return { valid: false, error: "Password is required" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawPassword.length < passwordRestrictions.length.min) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
error: `Password must be at least ${passwordRestrictions.length.min} characters`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawPassword.length > passwordRestrictions.length.max) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
error: `Password must be at most ${passwordRestrictions.length.max} characters`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordRestrictions.regex.test(rawPassword)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
error:
|
||||||
|
"Password must contain at least one uppercase, one lowercase, one digit, and one special character",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
export { passwordRestrictions, isValidPassword };
|
|
@ -1,33 +1,94 @@
|
||||||
import { returnGenericJsonResponse } from "@lib/http";
|
import { cassandra } from "@lib/cassandra";
|
||||||
|
import { jsonResponse } from "@lib/http";
|
||||||
|
import { pika } from "@lib/pika";
|
||||||
|
import {
|
||||||
|
isValidEmail,
|
||||||
|
isValidPassword,
|
||||||
|
isValidUsername,
|
||||||
|
} from "@lib/validators";
|
||||||
|
|
||||||
const routeDef: RouteDef = {
|
const routeDef: RouteDef = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
accepts: "application/json",
|
accepts: "application/json",
|
||||||
returns: "application/json",
|
returns: "application/json;charset=utf-8",
|
||||||
needsBody: "json",
|
needsBody: "json",
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handler(
|
async function handler(
|
||||||
request: ExtendedRequest,
|
_request: ExtendedRequest,
|
||||||
requestBody: unknown,
|
requestBody: unknown,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const { username, password, email } = requestBody as {
|
const { username, password, email } = requestBody as {
|
||||||
username: string;
|
username?: string;
|
||||||
password: string;
|
password?: string;
|
||||||
email: string;
|
email?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!username || !password || !email) {
|
const fields = { username, password, email };
|
||||||
return returnGenericJsonResponse(400, {
|
|
||||||
message: "Missing required fields: username, password, email",
|
const validators = {
|
||||||
|
username: isValidUsername,
|
||||||
|
password: isValidPassword,
|
||||||
|
email: isValidEmail,
|
||||||
|
};
|
||||||
|
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
for (const [key, validate] of Object.entries(validators)) {
|
||||||
|
const value = fields[key as keyof typeof fields];
|
||||||
|
if (typeof value !== "string" || value.trim() === "") {
|
||||||
|
errors.push(`${key} is required`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = validate(value);
|
||||||
|
if (!result.valid) errors.push(`${key}: ${result.error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return jsonResponse(400, {
|
||||||
|
message: "Validation failed",
|
||||||
|
error: errors.join("; "),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnGenericJsonResponse(200, {
|
const usernameResult = await cassandra
|
||||||
|
.getClient()
|
||||||
|
.execute("SELECT id FROM users WHERE username = ?", [username], {
|
||||||
|
prepare: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emailResult = await cassandra
|
||||||
|
.getClient()
|
||||||
|
.execute("SELECT id FROM users WHERE email = ?", [email], {
|
||||||
|
prepare: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (usernameResult.rowLength > 0 || emailResult.rowLength > 0) {
|
||||||
|
const errorMessages: string[] = [];
|
||||||
|
|
||||||
|
if (usernameResult.rowLength > 0) {
|
||||||
|
errorMessages.push("Username has already been taken");
|
||||||
|
}
|
||||||
|
if (emailResult.rowLength > 0) {
|
||||||
|
errorMessages.push("Email has already been taken");
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonResponse(400, {
|
||||||
|
message: "Validation failed",
|
||||||
|
error: errorMessages.join("; "),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = pika.gen("user");
|
||||||
|
|
||||||
|
return jsonResponse(200, {
|
||||||
message: "User registered successfully",
|
message: "User registered successfully",
|
||||||
username: username,
|
data: {
|
||||||
password,
|
username,
|
||||||
email,
|
email,
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { environment } from "@config/environment";
|
import { environment } from "@config/environment";
|
||||||
import { logger } from "@creations.works/logger";
|
import { logger } from "@creations.works/logger";
|
||||||
|
import { jsonResponse } from "@lib/http";
|
||||||
import {
|
import {
|
||||||
type BunFile,
|
type BunFile,
|
||||||
FileSystemRouter,
|
FileSystemRouter,
|
||||||
|
@ -138,18 +139,13 @@ class ServerHandler {
|
||||||
(!Array.isArray(routeModule.routeDef.method) &&
|
(!Array.isArray(routeModule.routeDef.method) &&
|
||||||
routeModule.routeDef.method !== request.method)
|
routeModule.routeDef.method !== request.method)
|
||||||
) {
|
) {
|
||||||
response = Response.json(
|
response = jsonResponse(405, {
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
code: 405,
|
|
||||||
error: `Method ${request.method} Not Allowed, expected ${
|
error: `Method ${request.method} Not Allowed, expected ${
|
||||||
Array.isArray(routeModule.routeDef.method)
|
Array.isArray(routeModule.routeDef.method)
|
||||||
? routeModule.routeDef.method.join(", ")
|
? routeModule.routeDef.method.join(", ")
|
||||||
: routeModule.routeDef.method
|
: routeModule.routeDef.method
|
||||||
}`,
|
}`,
|
||||||
},
|
});
|
||||||
{ status: 405 },
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
const expectedContentType: string | string[] | null =
|
const expectedContentType: string | string[] | null =
|
||||||
routeModule.routeDef.accepts;
|
routeModule.routeDef.accepts;
|
||||||
|
@ -167,18 +163,13 @@ class ServerHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!matchesAccepts) {
|
if (!matchesAccepts) {
|
||||||
response = Response.json(
|
response = jsonResponse(406, {
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
code: 406,
|
|
||||||
error: `Content-Type ${actualContentType} Not Acceptable, expected ${
|
error: `Content-Type ${actualContentType} Not Acceptable, expected ${
|
||||||
Array.isArray(expectedContentType)
|
Array.isArray(expectedContentType)
|
||||||
? expectedContentType.join(", ")
|
? expectedContentType.join(", ")
|
||||||
: expectedContentType
|
: expectedContentType
|
||||||
}`,
|
}`,
|
||||||
},
|
});
|
||||||
{ status: 406 },
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
extendedRequest.params = params;
|
extendedRequest.params = params;
|
||||||
extendedRequest.query = query;
|
extendedRequest.query = query;
|
||||||
|
@ -200,24 +191,10 @@ class ServerHandler {
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error([`Error handling route ${request.url}:`, error as Error]);
|
logger.error([`Error handling route ${request.url}:`, error as Error]);
|
||||||
|
|
||||||
response = Response.json(
|
response = jsonResponse(500);
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
code: 500,
|
|
||||||
error: "Internal Server Error",
|
|
||||||
},
|
|
||||||
{ status: 500 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response = Response.json(
|
response = jsonResponse(404);
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
code: 404,
|
|
||||||
error: "Not Found",
|
|
||||||
},
|
|
||||||
{ status: 404 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = request.headers;
|
const headers = request.headers;
|
||||||
|
|
10
types/validation.d.ts
vendored
Normal file
10
types/validation.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
type genericValidation = {
|
||||||
|
length: { min: number; max: number };
|
||||||
|
regex: RegExp;
|
||||||
|
};
|
||||||
|
|
||||||
|
type validationResult = {
|
||||||
|
valid: boolean;
|
||||||
|
error?: string;
|
||||||
|
username?: string;
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue