Files
jerapah-flow/packages/server/http-pages-store.js
T
nsrb d666dc001d feat(server): implement HTTP auth and page management functionality
- Introduced new HTTP authentication and page management APIs, allowing for the creation, retrieval, updating, and deletion of HTTP auth profiles and pages.
- Added validation for auth and page fields to ensure proper configuration and error handling.
- Implemented a mechanism for resolving auth credentials from various sources, including inline definitions, KV store, and secrets.
- Enhanced workflow validation to include checks for HTTP triggers, ensuring proper auth and response configurations.
- Updated the web interface to include new routes for managing HTTP auth profiles and pages, improving user experience and accessibility.
2026-08-15 06:41:26 +07:00

151 lines
3.5 KiB
JavaScript

import { randomUUID } from "node:crypto";
import { db } from "./db.js";
const MAX_NAME_LENGTH = 128;
const NAME_RE = /^[A-Za-z0-9._-]+$/;
const ALLOWED_MIME = new Set(["html", "json"]);
function nowIso() {
return new Date().toISOString();
}
/**
* @param {unknown} name
* @returns {string}
*/
export function assertPageName(name) {
if (typeof name !== "string" || !NAME_RE.test(name)) {
const err = new Error("invalid page name");
err.statusCode = 400;
throw err;
}
if (name.length > MAX_NAME_LENGTH) {
const err = new Error(`page name must be at most ${MAX_NAME_LENGTH} characters`);
err.statusCode = 400;
throw err;
}
return name;
}
/**
* @param {unknown} mime
* @returns {"html" | "json"}
*/
export function assertMime(mime) {
const m = String(mime ?? "");
if (!ALLOWED_MIME.has(m)) {
const err = new Error('mime must be "html" or "json"');
err.statusCode = 400;
throw err;
}
return /** @type {"html" | "json"} */ (m);
}
/**
* @param {unknown} status
* @returns {number}
*/
export function assertHttpStatus(status, fallback = 200) {
if (status == null || status === "") return fallback;
const n = Number(status);
if (!Number.isInteger(n) || n < 100 || n > 599) {
const err = new Error("status must be an HTTP status code (100-599)");
err.statusCode = 400;
throw err;
}
return n;
}
function publicPage(row) {
return {
id: row.id,
name: row.name,
content: row.content,
mime: row.mime,
status: row.status,
created_at: row.created_at,
updated_at: row.updated_at,
};
}
export async function listHttpPages() {
const rows = await db("http_pages")
.select("*")
.orderBy("name", "asc");
return rows.map(publicPage);
}
/**
* @param {string} name
*/
export async function getHttpPageByName(name) {
const row = await db("http_pages").where({ name: assertPageName(name) }).first();
return row ? publicPage(row) : null;
}
/**
* @param {string} id
*/
export async function getHttpPageById(id) {
const row = await db("http_pages").where({ id }).first();
return row ? publicPage(row) : null;
}
/**
* @param {{ name: string, content: string, mime: string, status?: number }} opts
*/
export async function upsertHttpPage({ name, content, mime, status }) {
const pageName = assertPageName(name);
const pageMime = assertMime(mime);
const pageStatus = assertHttpStatus(status, 200);
if (typeof content !== "string") {
const err = new Error("content must be a string");
err.statusCode = 400;
throw err;
}
const now = nowIso();
const existing = await db("http_pages").where({ name: pageName }).first();
if (existing) {
await db("http_pages")
.where({ id: existing.id })
.update({
content,
mime: pageMime,
status: pageStatus,
updated_at: now,
});
return getHttpPageById(existing.id);
}
const id = randomUUID();
await db("http_pages").insert({
id,
name: pageName,
content,
mime: pageMime,
status: pageStatus,
created_at: now,
updated_at: now,
});
return getHttpPageById(id);
}
/**
* @param {string} id
* @returns {Promise<boolean>}
*/
export async function deleteHttpPage(id) {
const n = await db("http_pages").where({ id }).del();
return n > 0;
}
/**
* Content-Type for a page mime.
* @param {"html" | "json" | string} mime
*/
export function contentTypeForMime(mime) {
if (mime === "html") return "text/html; charset=utf-8";
return "application/json; charset=utf-8";
}