forked from atums.world/atums.world
remove all drops from sql files, add triggers and functions for updated_at, added files and folder tables, update some types
This commit is contained in:
parent
6fdc82dd49
commit
94ba46cc2d
11 changed files with 295 additions and 101 deletions
85
config/sql/files.ts
Normal file
85
config/sql/files.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { logger } from "@helpers/logger";
|
||||||
|
import { type ReservedSQL, sql } from "bun";
|
||||||
|
|
||||||
|
export const order: number = 5;
|
||||||
|
|
||||||
|
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 files (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
owner UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
folder UUID DEFAULT NULL REFERENCES folders(id) ON DELETE SET NULL,
|
||||||
|
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
original_name VARCHAR(255),
|
||||||
|
mime_type VARCHAR(255) NOT NULL,
|
||||||
|
size BIGINT NOT NULL,
|
||||||
|
|
||||||
|
views INTEGER DEFAULT 0,
|
||||||
|
max_views INTEGER DEFAULT 1,
|
||||||
|
password TEXT DEFAULT NULL,
|
||||||
|
favorite BOOLEAN DEFAULT FALSE,
|
||||||
|
tags TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
thumbnail BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
expires_at TIMESTAMPTZ DEFAULT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const functionExists: { exists: boolean }[] = await reservation`
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_proc
|
||||||
|
JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
|
||||||
|
WHERE proname = 'update_files_updated_at' AND nspname = 'public'
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!functionExists[0].exists) {
|
||||||
|
await reservation`
|
||||||
|
CREATE FUNCTION update_files_updated_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerExists: { exists: boolean }[] = await reservation`
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_trigger
|
||||||
|
WHERE tgname = 'trigger_update_files_updated_at'
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!triggerExists[0].exists) {
|
||||||
|
await reservation`
|
||||||
|
CREATE TRIGGER trigger_update_files_updated_at
|
||||||
|
BEFORE UPDATE ON files
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_files_updated_at();
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error([
|
||||||
|
"Could not create the files table or trigger:",
|
||||||
|
error as Error,
|
||||||
|
]);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
if (selfReservation) {
|
||||||
|
reservation.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
config/sql/folders.ts
Normal file
75
config/sql/folders.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import { logger } from "@helpers/logger";
|
||||||
|
import { type ReservedSQL, sql } from "bun";
|
||||||
|
|
||||||
|
export const order: number = 4;
|
||||||
|
|
||||||
|
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 folders (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
owner UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
public BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
allow_uploads BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const functionExists: { exists: boolean }[] = await reservation`
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_proc
|
||||||
|
JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
|
||||||
|
WHERE proname = 'update_folders_updated_at' AND nspname = 'public'
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!functionExists[0].exists) {
|
||||||
|
await reservation`
|
||||||
|
CREATE FUNCTION update_folders_updated_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerExists: { exists: boolean }[] = await reservation`
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_trigger
|
||||||
|
WHERE tgname = 'trigger_update_folders_updated_at'
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!triggerExists[0].exists) {
|
||||||
|
await reservation`
|
||||||
|
CREATE TRIGGER trigger_update_folders_updated_at
|
||||||
|
BEFORE UPDATE ON folders
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_folders_updated_at();
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error([
|
||||||
|
"Could not create the folders table or trigger:",
|
||||||
|
error as Error,
|
||||||
|
]);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
if (selfReservation) {
|
||||||
|
reservation.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
import { logger } from "@helpers/logger";
|
import { logger } from "@helpers/logger";
|
||||||
import { type ReservedSQL, sql } from "bun";
|
import { type ReservedSQL, sql } from "bun";
|
||||||
|
|
||||||
|
export const order: number = 3;
|
||||||
|
|
||||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation: boolean = false;
|
||||||
|
|
||||||
|
@ -29,26 +31,3 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 invites ${cascade ? "CASCADE" : ""};`;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(["Could not drop the invites table:", error as Error]);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
if (selfReservation) {
|
|
||||||
reservation.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { logger } from "@helpers/logger";
|
import { logger } from "@helpers/logger";
|
||||||
import { type ReservedSQL, sql } from "bun";
|
import { type ReservedSQL, sql } from "bun";
|
||||||
|
|
||||||
|
export const order: number = 2;
|
||||||
|
|
||||||
const defaultSettings: Setting[] = [
|
const defaultSettings: Setting[] = [
|
||||||
{ key: "default_role", value: "user" },
|
{ key: "default_role", value: "user" },
|
||||||
{ key: "default_timezone", value: "UTC" },
|
{ key: "default_timezone", value: "UTC" },
|
||||||
|
@ -21,45 +23,62 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await reservation`
|
await reservation`
|
||||||
CREATE TABLE IF NOT EXISTS settings (
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
"key" VARCHAR(64) PRIMARY KEY NOT NULL UNIQUE,
|
"key" VARCHAR(64) PRIMARY KEY NOT NULL UNIQUE,
|
||||||
"value" TEXT NOT NULL,
|
"value" TEXT NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
||||||
);`;
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const functionExists: { exists: boolean }[] = await reservation`
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_proc
|
||||||
|
JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
|
||||||
|
WHERE proname = 'update_settings_updated_at' AND nspname = 'public'
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!functionExists[0].exists) {
|
||||||
|
await reservation`
|
||||||
|
CREATE FUNCTION update_settings_updated_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerExists: { exists: boolean }[] = await reservation`
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_trigger
|
||||||
|
WHERE tgname = 'trigger_update_settings_updated_at'
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!triggerExists[0].exists) {
|
||||||
|
await reservation`
|
||||||
|
CREATE TRIGGER trigger_update_settings_updated_at
|
||||||
|
BEFORE UPDATE ON settings
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_settings_updated_at();
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
for (const setting of defaultSettings) {
|
for (const setting of defaultSettings) {
|
||||||
await reservation`
|
await reservation`
|
||||||
INSERT INTO settings ("key", "value")
|
INSERT INTO settings ("key", "value")
|
||||||
VALUES (${setting.key}, ${setting.value})
|
VALUES (${setting.key}, ${setting.value})
|
||||||
ON CONFLICT ("key")
|
ON CONFLICT ("key") DO NOTHING;
|
||||||
DO NOTHING;`;
|
`;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(["Could not create the settings table:", error as Error]);
|
logger.error([
|
||||||
throw error;
|
"Could not create the settings table or trigger:",
|
||||||
} finally {
|
error as Error,
|
||||||
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;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
if (selfReservation) {
|
if (selfReservation) {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { logger } from "@helpers/logger";
|
import { logger } from "@helpers/logger";
|
||||||
import { type ReservedSQL, sql } from "bun";
|
import { type ReservedSQL, sql } from "bun";
|
||||||
|
|
||||||
|
export const order: number = 1;
|
||||||
|
|
||||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation: boolean = false;
|
||||||
|
|
||||||
|
@ -11,20 +13,20 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await reservation`
|
await reservation`
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
authorization_token UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
|
authorization_token UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
|
||||||
username VARCHAR(20) NOT NULL UNIQUE,
|
username VARCHAR(20) NOT NULL UNIQUE,
|
||||||
email VARCHAR(254) NOT NULL UNIQUE,
|
email VARCHAR(254) NOT NULL UNIQUE,
|
||||||
email_verified boolean NOT NULL DEFAULT false,
|
email_verified boolean NOT NULL DEFAULT false,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
avatar boolean NOT NULL DEFAULT false,
|
avatar boolean NOT NULL DEFAULT false,
|
||||||
roles TEXT[] NOT NULL DEFAULT ARRAY['user'],
|
roles TEXT[] NOT NULL DEFAULT ARRAY['user'],
|
||||||
timezone VARCHAR(64) DEFAULT NULL,
|
timezone VARCHAR(64) DEFAULT NULL,
|
||||||
invited_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
invited_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
last_seen TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
last_seen TIMESTAMPTZ DEFAULT NOW() NOT NULL
|
||||||
);`;
|
);`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(["Could not create the users table:", error as Error]);
|
logger.error(["Could not create the users table:", error as Error]);
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -35,29 +37,6 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// * Validation functions
|
||||||
|
|
||||||
// ? should support non english characters but wont mess up the url
|
// ? should support non english characters but wont mess up the url
|
||||||
|
|
24
src/index.ts
24
src/index.ts
|
@ -13,12 +13,26 @@ async function initializeDatabase(): Promise<void> {
|
||||||
const sqlDir: string = resolve("config", "sql");
|
const sqlDir: string = resolve("config", "sql");
|
||||||
const files: string[] = await readdir(sqlDir);
|
const files: string[] = await readdir(sqlDir);
|
||||||
|
|
||||||
const reservation: ReservedSQL = await sql.reserve();
|
const modules: Module[] = await Promise.all(
|
||||||
for (const file of files) {
|
files
|
||||||
if (file.endsWith(".ts")) {
|
.filter((file: string): boolean => file.endsWith(".ts"))
|
||||||
const { createTable } = await import(resolve(sqlDir, file));
|
.map(async (file: string): Promise<Module> => {
|
||||||
|
const module: Module["module"] = await import(
|
||||||
|
resolve(sqlDir, file)
|
||||||
|
);
|
||||||
|
return { file, module };
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
await createTable(reservation);
|
modules.sort(
|
||||||
|
(a: Module, b: Module): number =>
|
||||||
|
(a.module.order ?? 0) - (b.module.order ?? 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
const reservation: ReservedSQL = await sql.reserve();
|
||||||
|
for (const { module } of modules) {
|
||||||
|
if (module.createTable) {
|
||||||
|
await module.createTable(reservation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
0
src/routes/api/files/upload.ts
Normal file
0
src/routes/api/files/upload.ts
Normal file
4
types/bun.d.ts
vendored
4
types/bun.d.ts
vendored
|
@ -6,10 +6,10 @@ type Params = Record<string, string>;
|
||||||
declare global {
|
declare global {
|
||||||
type BunServer = Server;
|
type BunServer = Server;
|
||||||
|
|
||||||
type ExtendedRequest = Request & {
|
interface ExtendedRequest extends Request {
|
||||||
startPerf: number;
|
startPerf: number;
|
||||||
query: Query;
|
query: Query;
|
||||||
params: Params;
|
params: Params;
|
||||||
session: UserSession | ApiUserSession | null;
|
session: UserSession | ApiUserSession | null;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
2
types/char.d.ts
vendored
2
types/char.d.ts
vendored
|
@ -7,3 +7,5 @@ type DurationObject = {
|
||||||
minutes: number;
|
minutes: number;
|
||||||
seconds: number;
|
seconds: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UUID = `${string}-${string}-${string}-${string}-${string}`;
|
||||||
|
|
8
types/config.d.ts
vendored
8
types/config.d.ts
vendored
|
@ -13,3 +13,11 @@ type Setting = {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Module = {
|
||||||
|
file: string;
|
||||||
|
module: {
|
||||||
|
order?: number;
|
||||||
|
createTable?: (reservation?: ReservedSQL) => Promise<void>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
33
types/file.d.ts
vendored
Normal file
33
types/file.d.ts
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
type File = {
|
||||||
|
id: UUID;
|
||||||
|
owner: UUID;
|
||||||
|
folder?: UUID | null;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
original_name?: string | null;
|
||||||
|
mime_type: string;
|
||||||
|
size: number;
|
||||||
|
|
||||||
|
views: number;
|
||||||
|
max_views: number;
|
||||||
|
password?: string | null;
|
||||||
|
favorite: boolean;
|
||||||
|
tags: string[];
|
||||||
|
thumbnail: boolean;
|
||||||
|
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
expires_at?: Date | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Folder = {
|
||||||
|
id: UUID;
|
||||||
|
owner: UUID;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
public: boolean;
|
||||||
|
allow_uploads: boolean;
|
||||||
|
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue