diff --git a/package.json b/package.json index 2c7da4b..14e263c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "bun run --watch src/index.ts --dev", "lint": "eslint", "lint:fix": "bun lint --fix", - "cleanup": "rm -rf logs node_modules bun.lockdb" + "cleanup": "rm -rf logs node_modules bun.lockdb", + "clearTable": "bun run src/helpers/commands/clearTable.ts" }, "devDependencies": { "@eslint/js": "^9.22.0", @@ -32,7 +33,7 @@ }, "dependencies": { "ejs": "^3.1.10", - "exiftool-vendored": "^29.1.0", + "exiftool-vendored": "^29.2.0", "fast-jwt": "^5.0.5", "fluent-ffmpeg": "^2.1.3", "image-thumbnail": "^1.0.17", diff --git a/public/css/auth/login.css b/public/css/auth/login.css new file mode 100644 index 0000000..4de920e --- /dev/null +++ b/public/css/auth/login.css @@ -0,0 +1,27 @@ +.container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + height: 100vh; +} + +.content { + border: 1px solid var(--border); + background-color: var(--background-secondary); + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem; + + width: clamp(200px, 50%, 300px); +} + +.content form { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} diff --git a/public/css/global.css b/public/css/global.css index 55545de..f550c18 100644 --- a/public/css/global.css +++ b/public/css/global.css @@ -1,5 +1,9 @@ [data-theme="dark"] { --background: rgb(31, 30, 30); + --background-secondary: rgb(45, 45, 45); + --border: rgb(31, 30, 30); + --text: rgb(255, 255, 255); + --text-secondary: rgb(255, 255, 255); } body { diff --git a/src/helpers/char.ts b/src/helpers/char.ts index 21aa15c..084386b 100644 --- a/src/helpers/char.ts +++ b/src/helpers/char.ts @@ -205,3 +205,23 @@ export function supportsExif(mimeType: string, extension: string): boolean { export function supportsThumbnail(mimeType: string): boolean { return /^(image\/(?!svg+xml)|video\/)/i.test(mimeType); } + +// Commands +export function parseArgs(): Record { + const args: string[] = process.argv.slice(2); + const parsedArgs: Record = {}; + + for (let i: number = 0; i < args.length; i++) { + if (args[i].startsWith("--")) { + const key: string = args[i].slice(2); + const value: string | boolean = + args[i + 1] && !args[i + 1].startsWith("--") + ? args[i + 1] + : true; + parsedArgs[key] = value; + if (value !== true) i++; + } + } + + return parsedArgs; +} diff --git a/src/helpers/commands/clearTable.ts b/src/helpers/commands/clearTable.ts new file mode 100644 index 0000000..cdb11a1 --- /dev/null +++ b/src/helpers/commands/clearTable.ts @@ -0,0 +1,40 @@ +import { parseArgs } from "@helpers/char"; +import { type ReservedSQL, sql } from "bun"; + +(async (): Promise => { + try { + const args: Record = parseArgs(); + const table: string | undefined = args.table as string | undefined; + const cascade: boolean = args.cascade === true; + + if (!table) { + throw new Error("Missing required argument: --table "); + } + + const reservation: ReservedSQL = await sql.reserve(); + + try { + await reservation`TRUNCATE TABLE ${sql(table)} ${cascade ? sql`CASCADE` : sql``};`; + console.log( + `Table ${table} has been cleared${cascade ? " with CASCADE" : ""}.`, + ); + } catch (error) { + if ( + error instanceof Error && + error.message.includes("foreign key constraint") + ) { + console.error( + `Could not clear table "${table}" due to foreign key constraints.\n` + + "Try using --cascade if you want to remove dependent records.", + ); + } else { + console.error("Could not clear table:", error); + } + } finally { + reservation.release(); + } + } catch (error) { + console.error("Unexpected error:", error); + process.exit(1); + } +})(); diff --git a/src/routes/api/settings/set.ts b/src/routes/api/settings/set.ts index 5176416..61f1279 100644 --- a/src/routes/api/settings/set.ts +++ b/src/routes/api/settings/set.ts @@ -48,12 +48,7 @@ async function handler( ); } - if ( - typeof key !== "string" || - (typeof value !== "string" && - typeof value !== "boolean" && - typeof value !== "number") - ) { + if (!["string", "boolean", "number"].includes(typeof value)) { return Response.json( { success: false, diff --git a/src/routes/auth/login.ts b/src/routes/auth/login.ts new file mode 100644 index 0000000..b7b4e68 --- /dev/null +++ b/src/routes/auth/login.ts @@ -0,0 +1,20 @@ +import { getSetting } from "@config/sql/settings"; +import { renderEjsTemplate } from "@helpers/ejs"; + +const routeDef: RouteDef = { + method: "GET", + accepts: "*/*", + returns: "text/html", +}; + +async function handler(): Promise { + const ejsTemplateData: EjsTemplateData = { + title: "Hello, World!", + instance_name: + (await getSetting("instance_name")) || "Unnamed Instance", + }; + + return await renderEjsTemplate("auth/login", ejsTemplateData); +} + +export { handler, routeDef }; diff --git a/src/routes/index.ts b/src/routes/index.ts index 9204e19..711c0f2 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -6,7 +6,11 @@ const routeDef: RouteDef = { returns: "text/html", }; -async function handler(): Promise { +async function handler(request: ExtendedRequest): Promise { + if (!request.session) { + return Response.redirect("/auth/login"); + } + const ejsTemplateData: EjsTemplateData = { title: "Hello, World!", }; diff --git a/src/routes/user/avatar/[user].ts b/src/routes/user/avatar/[user].ts index 029c435..9c8be7b 100644 --- a/src/routes/user/avatar/[user].ts +++ b/src/routes/user/avatar/[user].ts @@ -3,7 +3,7 @@ import { isValidUsername } from "@config/sql/users"; import { type BunFile, type ReservedSQL, sql } from "bun"; import { resolve } from "path"; -import { isUUID, nameWithoutExtension } from "@/helpers/char"; +import { getBaseUrl, isUUID, nameWithoutExtension } from "@/helpers/char"; import { logger } from "@/helpers/logger"; const routeDef: RouteDef = { @@ -70,7 +70,13 @@ async function handler(request: ExtendedRequest): Promise { if (json === "true" || json === "1") { return Response.json( - { success: true, code: 200, data: avatar }, + { + success: true, code: 200, + avatar: { + ...avatar, + url: `${getBaseUrl(request)}/user/avatar/${user.id}`, + } + }, { status: 200 }, ); } diff --git a/src/views/auth/login.ejs b/src/views/auth/login.ejs new file mode 100644 index 0000000..b6f4f15 --- /dev/null +++ b/src/views/auth/login.ejs @@ -0,0 +1,28 @@ + + + + <%- include("../global", { styles: ["auth/login"], scripts: [] }) %> + + +
+
+

<%= instance_name %>

+
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +