forked from atums.world/atums.world
add clearTable command, simple webpage start
This commit is contained in:
parent
f14daf041a
commit
9a93e6269d
10 changed files with 156 additions and 11 deletions
|
@ -7,7 +7,8 @@
|
||||||
"dev": "bun run --watch src/index.ts --dev",
|
"dev": "bun run --watch src/index.ts --dev",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"lint:fix": "bun lint --fix",
|
"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": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.22.0",
|
"@eslint/js": "^9.22.0",
|
||||||
|
@ -32,7 +33,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"exiftool-vendored": "^29.1.0",
|
"exiftool-vendored": "^29.2.0",
|
||||||
"fast-jwt": "^5.0.5",
|
"fast-jwt": "^5.0.5",
|
||||||
"fluent-ffmpeg": "^2.1.3",
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
"image-thumbnail": "^1.0.17",
|
"image-thumbnail": "^1.0.17",
|
||||||
|
|
27
public/css/auth/login.css
Normal file
27
public/css/auth/login.css
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
--background: rgb(31, 30, 30);
|
--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 {
|
body {
|
||||||
|
|
|
@ -205,3 +205,23 @@ export function supportsExif(mimeType: string, extension: string): boolean {
|
||||||
export function supportsThumbnail(mimeType: string): boolean {
|
export function supportsThumbnail(mimeType: string): boolean {
|
||||||
return /^(image\/(?!svg+xml)|video\/)/i.test(mimeType);
|
return /^(image\/(?!svg+xml)|video\/)/i.test(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
export function parseArgs(): Record<string, string | boolean> {
|
||||||
|
const args: string[] = process.argv.slice(2);
|
||||||
|
const parsedArgs: Record<string, string | boolean> = {};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
40
src/helpers/commands/clearTable.ts
Normal file
40
src/helpers/commands/clearTable.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { parseArgs } from "@helpers/char";
|
||||||
|
import { type ReservedSQL, sql } from "bun";
|
||||||
|
|
||||||
|
(async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const args: Record<string, string | boolean> = 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 <table_name>");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
})();
|
|
@ -48,12 +48,7 @@ async function handler(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!["string", "boolean", "number"].includes(typeof value)) {
|
||||||
typeof key !== "string" ||
|
|
||||||
(typeof value !== "string" &&
|
|
||||||
typeof value !== "boolean" &&
|
|
||||||
typeof value !== "number")
|
|
||||||
) {
|
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
|
|
20
src/routes/auth/login.ts
Normal file
20
src/routes/auth/login.ts
Normal file
|
@ -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<Response> {
|
||||||
|
const ejsTemplateData: EjsTemplateData = {
|
||||||
|
title: "Hello, World!",
|
||||||
|
instance_name:
|
||||||
|
(await getSetting("instance_name")) || "Unnamed Instance",
|
||||||
|
};
|
||||||
|
|
||||||
|
return await renderEjsTemplate("auth/login", ejsTemplateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { handler, routeDef };
|
|
@ -6,7 +6,11 @@ const routeDef: RouteDef = {
|
||||||
returns: "text/html",
|
returns: "text/html",
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handler(): Promise<Response> {
|
async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
|
if (!request.session) {
|
||||||
|
return Response.redirect("/auth/login");
|
||||||
|
}
|
||||||
|
|
||||||
const ejsTemplateData: EjsTemplateData = {
|
const ejsTemplateData: EjsTemplateData = {
|
||||||
title: "Hello, World!",
|
title: "Hello, World!",
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { isValidUsername } from "@config/sql/users";
|
||||||
import { type BunFile, type ReservedSQL, sql } from "bun";
|
import { type BunFile, type ReservedSQL, sql } from "bun";
|
||||||
import { resolve } from "path";
|
import { resolve } from "path";
|
||||||
|
|
||||||
import { isUUID, nameWithoutExtension } from "@/helpers/char";
|
import { getBaseUrl, isUUID, nameWithoutExtension } from "@/helpers/char";
|
||||||
import { logger } from "@/helpers/logger";
|
import { logger } from "@/helpers/logger";
|
||||||
|
|
||||||
const routeDef: RouteDef = {
|
const routeDef: RouteDef = {
|
||||||
|
@ -70,7 +70,13 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
|
|
||||||
if (json === "true" || json === "1") {
|
if (json === "true" || json === "1") {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{ success: true, code: 200, data: avatar },
|
{
|
||||||
|
success: true, code: 200,
|
||||||
|
avatar: {
|
||||||
|
...avatar,
|
||||||
|
url: `${getBaseUrl(request)}/user/avatar/${user.id}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
{ status: 200 },
|
{ status: 200 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
28
src/views/auth/login.ejs
Normal file
28
src/views/auth/login.ejs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<%- include("../global", { styles: ["auth/login"], scripts: [] }) %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1><%= instance_name %></h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<form id="login-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input type="email" name="email" id="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" id="password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Reference in a new issue