start of the 50th remake of a file host doomed to never be finished

This commit is contained in:
creations 2025-03-02 17:36:45 -05:00
commit 46c05ca3a9
Signed by: creations
GPG key ID: 8F553AA4320FC711
33 changed files with 2155 additions and 0 deletions

27
config/environment.ts Normal file
View file

@ -0,0 +1,27 @@
export const environment: Environment = {
port: parseInt(process.env.PORT || "8080", 10),
host: process.env.HOST || "0.0.0.0",
development:
process.env.NODE_ENV === "development" ||
process.argv.includes("--dev"),
};
export const redisConfig: {
host: string;
port: number;
username?: string | undefined;
password?: string | undefined;
} = {
host: process.env.REDIS_HOST || "localhost",
port: parseInt(process.env.REDIS_PORT || "6379", 10),
username: process.env.REDIS_USERNAME || undefined,
password: process.env.REDIS_PASSWORD || undefined,
};
export const jwt: {
secret: string;
expiresIn: string;
} = {
secret: process.env.JWT_SECRET || "",
expiresIn: process.env.JWT_EXPIRES || "1d",
};

177
config/sql/settings.ts Normal file
View file

@ -0,0 +1,177 @@
import { logger } from "@helpers/logger";
import { type ReservedSQL, sql } from "bun";
const defaultSettings: { key: string; value: string }[] = [
{ key: "default_role", value: "user" },
{ key: "default_timezone", value: "UTC" },
{ key: "server_timezone", value: "UTC" },
{ key: "enable_registration", value: "false" },
{ key: "enable_invitations", value: "true" },
{ key: "allow_user_invites", value: "false" },
{ key: "require_email_verification", value: "false" },
];
export async function createTable(reservation?: ReservedSQL): Promise<void> {
let selfReservation: boolean = false;
if (!reservation) {
reservation = await sql.reserve();
selfReservation = true;
}
try {
await reservation`
CREATE TABLE IF NOT EXISTS settings (
"key" VARCHAR(64) PRIMARY KEY NOT NULL UNIQUE,
"value" TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);`;
for (const setting of defaultSettings) {
await reservation`
INSERT INTO settings ("key", "value")
VALUES (${setting.key}, ${setting.value})
ON CONFLICT ("key")
DO NOTHING;`;
}
} catch (error) {
logger.error(["Could not create the settings table:", error as Error]);
throw error;
} finally {
if (selfReservation) {
reservation.release();
}
}
}
export async function drop(
cascade: boolean,
reservation?: ReservedSQL,
): Promise<void> {
let selfReservation: boolean = false;
if (!reservation) {
reservation = await sql.reserve();
selfReservation = true;
}
try {
await reservation`DROP TABLE IF EXISTS settings ${cascade ? "CASCADE" : ""};`;
} catch (error) {
logger.error(["Could not drop the settings table:", error as Error]);
throw error;
} finally {
if (selfReservation) {
reservation.release();
}
}
}
// * Validation functions
export async function getSetting(
key: string,
reservation?: ReservedSQL,
): Promise<string | null> {
let selfReservation: boolean = false;
if (!reservation) {
reservation = await sql.reserve();
selfReservation = true;
}
try {
const result: { value: string }[] =
await reservation`SELECT value FROM settings WHERE "key" = ${key};`;
if (result.length === 0) {
return null;
}
return result[0].value;
} catch (error) {
logger.error(["Could not get the setting:", error as Error]);
throw error;
} finally {
if (selfReservation) {
reservation.release();
}
}
}
export async function setSetting(
key: string,
value: string,
reservation?: ReservedSQL,
): Promise<void> {
let selfReservation: boolean = false;
if (!reservation) {
reservation = await sql.reserve();
selfReservation = true;
}
try {
await reservation`
INSERT INTO settings ("key", "value")
VALUES (${key}, ${value})
ON CONFLICT ("key")
DO UPDATE SET "value" = ${value};`;
} catch (error) {
logger.error(["Could not set the setting:", error as Error]);
throw error;
} finally {
if (selfReservation) {
reservation.release();
}
}
}
export async function deleteSetting(
key: string,
reservation?: ReservedSQL,
): Promise<void> {
let selfReservation: boolean = false;
if (!reservation) {
reservation = await sql.reserve();
selfReservation = true;
}
try {
await reservation`DELETE FROM settings WHERE "key" = ${key};`;
} catch (error) {
logger.error(["Could not delete the setting:", error as Error]);
throw error;
} finally {
if (selfReservation) {
reservation.release();
}
}
}
export async function getAllSettings(
reservation?: ReservedSQL,
): Promise<{ key: string; value: string }[]> {
let selfReservation: boolean = false;
if (!reservation) {
reservation = await sql.reserve();
selfReservation = true;
}
try {
const result: { key: string; value: string }[] =
await reservation`SELECT "key", "value" FROM settings;`;
return result;
} catch (error) {
logger.error(["Could not get all settings:", error as Error]);
throw error;
} finally {
if (selfReservation) {
reservation.release();
}
}
}

174
config/sql/users.ts Normal file
View file

@ -0,0 +1,174 @@
import { logger } from "@helpers/logger";
import { type ReservedSQL, sql } from "bun";
export async function createTable(reservation?: ReservedSQL): Promise<void> {
let selfReservation: boolean = false;
if (!reservation) {
reservation = await sql.reserve();
selfReservation = true;
}
try {
await reservation`
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
authorization_token UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
username VARCHAR(20) NOT NULL UNIQUE,
email VARCHAR(254) NOT NULL UNIQUE,
email_verified boolean NOT NULL DEFAULT false,
password TEXT NOT NULL,
avatar boolean NOT NULL DEFAULT false,
roles TEXT[] NOT NULL DEFAULT ARRAY['user'],
timezone VARCHAR(64) DEFAULT NULL,
invited_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
last_seen TIMESTAMPTZ DEFAULT NOW() NOT NULL
);`;
} catch (error) {
logger.error(["Could not create the users table:", error as Error]);
throw error;
} finally {
if (selfReservation) {
reservation.release();
}
}
}
export async function drop(
cascade: boolean,
reservation?: ReservedSQL,
): Promise<void> {
let selfReservation: boolean = false;
if (!reservation) {
reservation = await sql.reserve();
selfReservation = true;
}
try {
await reservation`DROP TABLE IF EXISTS users ${cascade ? "CASCADE" : ""};`;
} catch (error) {
logger.error(["Could not drop the users table:", error as Error]);
throw error;
} finally {
if (selfReservation) {
reservation.release();
}
}
}
// * Validation functions
// ? should support non english characters but wont mess up the url
export const userNameRestrictions: {
length: { min: number; max: number };
regex: RegExp;
} = {
length: { min: 3, max: 20 },
regex: /^[\p{L}0-9._-]+$/u,
};
export const passwordRestrictions: {
length: { min: number; max: number };
regex: RegExp;
} = {
length: { min: 12, max: 64 },
regex: /^(?=.*\p{Ll})(?=.*\p{Lu})(?=.*\d)(?=.*[^\w\s]).{12,64}$/u,
};
export const emailRestrictions: { regex: RegExp } = {
regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
};
export const inviteRestrictions: { min: number; max: number; regex: RegExp } = {
min: 4,
max: 15,
regex: /^[a-zA-Z0-9]+$/,
};
export function isValidUsername(username: string): {
valid: boolean;
error?: string;
} {
if (username.length < userNameRestrictions.length.min) {
return { valid: false, error: "Username is too short" };
}
if (username.length > userNameRestrictions.length.max) {
return { valid: false, error: "Username is too long" };
}
if (!userNameRestrictions.regex.test(username)) {
return { valid: false, error: "Username contains invalid characters" };
}
return { valid: true };
}
export function isValidPassword(password: string): {
valid: boolean;
error?: string;
} {
if (password.length < passwordRestrictions.length.min) {
return {
valid: false,
error: `Password must be at least ${passwordRestrictions.length.min} characters long`,
};
}
if (password.length > passwordRestrictions.length.max) {
return {
valid: false,
error: `Password can't be longer than ${passwordRestrictions.length.max} characters`,
};
}
if (!passwordRestrictions.regex.test(password)) {
return {
valid: false,
error: "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character",
};
}
return { valid: true };
}
export function isValidEmail(email: string): {
valid: boolean;
error?: string;
} {
if (!emailRestrictions.regex.test(email)) {
return { valid: false, error: "Invalid email address" };
}
return { valid: true };
}
export function isValidInvite(invite: string): {
valid: boolean;
error?: string;
} {
if (invite.length < inviteRestrictions.min) {
return {
valid: false,
error: `Invite code must be at least ${inviteRestrictions.min} characters long`,
};
}
if (invite.length > inviteRestrictions.max) {
return {
valid: false,
error: `Invite code can't be longer than ${inviteRestrictions.max} characters`,
};
}
if (!inviteRestrictions.regex.test(invite)) {
return {
valid: false,
error: "Invite code contains invalid characters",
};
}
return { valid: true };
}