First Commit

This commit is contained in:
2026-09-12 13:56:36 +01:00
commit 4d75af0418
426 changed files with 36452 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
export default async function bufferFromBase64(
base_64: string,
): Promise<{ buffer: Buffer; ext: string }> {
let mimeType = "";
let data = base_64;
const dataUrlMatch = base_64.match(
/^data:([^;]+);base64,([\s\S]+)$/i,
);
if (dataUrlMatch?.[1] && dataUrlMatch[2]) {
mimeType = dataUrlMatch[1].trim().toLowerCase();
data = dataUrlMatch[2];
}
const buffer = Buffer.from(data, "base64");
let ext = "bin";
if (mimeType.includes("image/jpeg") || mimeType.includes("image/jpg"))
ext = "jpeg";
else if (mimeType.includes("image/png")) ext = "png";
else if (mimeType.includes("image/webp")) ext = "webp";
else if (mimeType.includes("image/gif")) ext = "gif";
else if (mimeType.includes("image/svg")) ext = "svg";
else if (mimeType.includes("application/pdf")) ext = "pdf";
else if (
mimeType.includes(
"application/vnd.openxmlformats-officedocument.wordprocessingml",
)
)
ext = "docx";
else if (
mimeType.includes(
"application/vnd.openxmlformats-officedocument.spreadsheetml",
)
)
ext = "xlsx";
else if (mimeType.includes("text/csv")) ext = "csv";
else if (mimeType.includes("text/markdown")) ext = "md";
else if (mimeType.includes("text/plain")) ext = "txt";
else if (mimeType.includes("/")) {
ext = mimeType.split("/").pop()?.split("+")[0] || "bin";
}
return { buffer, ext };
}
+34
View File
@@ -0,0 +1,34 @@
export default async function bufferFromUrl(
url: string,
): Promise<{ buffer: Buffer; ext: string }> {
const res = await fetch(url);
if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`);
const contentType = res.headers.get("content-type") ?? "";
const arrayBuffer = await res.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
// Derive ext from content-type first, fall back to URL path
let ext = "txt";
if (contentType.includes("application/pdf")) ext = "pdf";
else if (
contentType.includes(
"application/vnd.openxmlformats-officedocument.wordprocessingml",
)
)
ext = "docx";
else if (
contentType.includes(
"application/vnd.openxmlformats-officedocument.spreadsheetml",
)
)
ext = "xlsx";
else if (contentType.includes("text/csv")) ext = "csv";
else if (contentType.includes("text/markdown")) ext = "md";
else {
const urlPath = new URL(url).pathname;
ext = urlPath.split(".").pop()?.toLowerCase() ?? "txt";
}
return { buffer, ext };
}
+86
View File
@@ -0,0 +1,86 @@
/**
* Extracts a specific cookie value from a Request's Cookie header.
* Returns undefined if the cookie is not found.
*/
export function getCookie(request: Request, name: string): string | undefined {
const header = request.headers.get("Cookie");
if (!header) return undefined;
const cookies = header.split(";");
for (const cookie of cookies) {
const [key, ...rest] = cookie.split("=");
if (!key) continue;
if (key.trim() === name) {
return rest.join("=").trim();
}
}
return undefined;
}
export type CookieOptions = {
/** Cookie name */
name: string;
/** Cookie value */
value: string;
/** Prevents client-side JS access (default: true) */
httpOnly?: boolean;
/** Only send over HTTPS (default: true) */
secure?: boolean;
/** CSRF protection strategy (default: "Lax") */
sameSite?: "Strict" | "Lax" | "None";
/** URL path scope (default: "/") */
path?: string;
/** Lifetime in seconds. Omit for a session cookie. */
maxAge?: number;
/** Restrict to a specific domain */
domain?: string;
};
/**
* Appends one or more Set-Cookie headers to a Response.
* Uses `append` so multiple cookies don't overwrite each other.
*/
export function setCookies(response: Response, cookies: CookieOptions[]): void {
for (const cookie of cookies) {
const {
name,
value,
httpOnly = true,
secure = true,
sameSite = "Lax",
path = "/",
maxAge,
domain,
} = cookie;
let header = `${name}=${value}; Path=${path}; SameSite=${sameSite}`;
if (httpOnly) header += "; HttpOnly";
if (secure) header += "; Secure";
if (maxAge !== undefined) header += `; Max-Age=${maxAge}`;
if (domain) header += `; Domain=${domain}`;
response.headers.append("Set-Cookie", header);
}
}
/**
* Appends a Set-Cookie header that expires the named cookie immediately.
* Sets Max-Age=0 and an empty value to instruct the browser to remove it.
* Path and domain must match the original cookie for deletion to work.
*/
export function deleteCookies(
response: Response,
cookies: Pick<CookieOptions, "name" | "path" | "domain" | "httpOnly">[],
): void {
for (const { name, path = "/", domain, httpOnly } of cookies) {
let header = `${name}=; Path=${path}; Max-Age=0`;
if (httpOnly) header += "; HttpOnly";
if (domain) header += `; Domain=${domain}`;
response.headers.append("Set-Cookie", header);
}
}
+53
View File
@@ -0,0 +1,53 @@
const key = process.env.ENCRYPTION_KEY;
const salt = process.env.ENCRYPTION_SALT;
async function getKey() {
const material = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(key),
"PBKDF2",
false,
["deriveKey"],
);
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: new TextEncoder().encode(salt),
iterations: 100000,
hash: "SHA-256",
},
material,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"],
);
}
export async function encrypt(text: string): Promise<string> {
const derivedKey = await getKey();
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
derivedKey,
new TextEncoder().encode(text),
);
const combined = new Uint8Array(
iv.length + new Uint8Array(ciphertext).length,
);
combined.set(iv);
combined.set(new Uint8Array(ciphertext), iv.length);
return btoa(String.fromCharCode(...combined));
}
export async function decrypt(encoded: string): Promise<string> {
const derivedKey = await getKey();
const combined = Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
const iv = combined.slice(0, 12);
const ciphertext = combined.slice(12);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
derivedKey,
ciphertext,
);
return new TextDecoder().decode(decrypted);
}
+38
View File
@@ -0,0 +1,38 @@
/**
* # EJSON parse string
*/
function parse(
string: string | null | number,
reviver?: (this: any, key: string, value: any) => any
): { [s: string]: any } | { [s: string]: any }[] | undefined {
if (!string) return undefined;
if (typeof string == "object") return string;
if (typeof string !== "string") return undefined;
try {
return JSON.parse(string, reviver);
} catch (error) {
return undefined;
}
}
/**
* # EJSON stringify object
*/
function stringify(
value: any,
replacer?: ((this: any, key: string, value: any) => any) | null,
space?: string | number
): string | undefined {
try {
return JSON.stringify(value, replacer || undefined, space);
} catch (error) {
return undefined;
}
}
const EJSON = {
parse,
stringify,
};
export default EJSON;
+41
View File
@@ -0,0 +1,41 @@
import path from "path";
import type { User } from "../types";
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
type Params = {
user?: BUN_SQLITE_WGUI_USERS | User;
};
export default function grabDirNames(params?: Params) {
const DIRNAME = __dirname;
const ROOT_DIR = DIRNAME.includes(`/.bunext`)
? process.cwd()
: path.resolve(__dirname, "../..");
const DATA_DIR = process.env.DATA_DIR || path.join(ROOT_DIR, ".data");
const MEDIA_RELATIVE_DIR = "media";
const MEDIA_DIR = path.join(DATA_DIR, MEDIA_RELATIVE_DIR);
const USER_MEDIA_PUBLIC_DIR = params?.user?.id
? path.join(MEDIA_DIR, String(params.user.id), "public")
: undefined;
const USER_MEDIA_PUBLIC_RELATIVE_DIR = params?.user?.id
? path.join(MEDIA_RELATIVE_DIR, String(params.user.id), "public")
: undefined;
const USER_MEDIA_PRIVATE_DIR = params?.user?.id
? path.join(MEDIA_DIR, String(params.user.id), "private")
: undefined;
const USER_MEDIA_PRIVATE_RELATIVE_DIR = params?.user?.id
? path.join(MEDIA_RELATIVE_DIR, String(params.user.id), "private")
: undefined;
return {
ROOT_DIR,
MEDIA_DIR,
USER_MEDIA_PUBLIC_DIR,
USER_MEDIA_PRIVATE_DIR,
DATA_DIR,
USER_MEDIA_PUBLIC_RELATIVE_DIR,
USER_MEDIA_PRIVATE_RELATIVE_DIR,
};
}
@@ -0,0 +1,20 @@
import type { ApiReqParams } from "../types";
import type { BUN_SQLITE_WGUI_USERS_JOIN } from "../types/sql-joins";
export default function searchableFieldToQueryField({
field,
}: {
field: keyof BUN_SQLITE_WGUI_USERS_JOIN;
}): keyof ApiReqParams {
switch (field) {
case "email":
return "search_term_email";
case "last_name":
return "search_term_last_name";
case "user_types":
return "search_term_user_type";
case "first_name":
default:
return "search_term_first_name";
}
}
+3
View File
@@ -0,0 +1,3 @@
export default function uniqueStringArr(arr: string[]): string[] {
return [...new Set(arr)];
}