globalThis.process ??= {};
globalThis.process.env ??= {};
import { Tt as sql } from "./d1_DTqC4o_t.mjs";
import { q as authorizeContentAction } from "./crypto_CY2zBtG3.mjs";
import { A as resolveContentTypes, D as resolveContentRoute, O as resolveContentStatuses, i as compileContentRoutes, k as resolveContentTemplate } from "./dist_TEPdmzZw.mjs";
import { n as getFullDb } from "./db_DZJ1iBSP.mjs";
import { i as hashPassword, l as verifyPassword } from "./engine-custom_BJVTPCgT.mjs";
//#region src/lib/content-model-routes.ts
var MAX_PATH_DEPTH = 128;
var PROOF_VERSION = "v1";
var CONTENT_PROOF_COOKIE = "mod_content_proof";
function rowsOf(value) {
	if (Array.isArray(value)) return value;
	const rows = value?.rows;
	return Array.isArray(rows) ? rows : [];
}
async function execute(db, query) {
	return rowsOf(await db.execute(query));
}
function date(value, field) {
	const out = value instanceof Date ? new Date(value.getTime()) : new Date(String(value));
	if (Number.isNaN(out.getTime())) throw new Error(`content route integrity: ${field} is invalid`);
	return out;
}
function nullableDate(value, field) {
	return value === null || value === void 0 ? null : date(value, field);
}
function bool(value) {
	return value === true || value === 1 || value === "1" || value === "true";
}
function media(value) {
	if (value === null || value === void 0) return null;
	let parsed = value;
	if (typeof value === "string") try {
		parsed = JSON.parse(value);
	} catch {
		return null;
	}
	if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || typeof parsed.id !== "string") return null;
	const row = parsed;
	return Object.freeze({
		id: row.id,
		...typeof row.kind === "string" ? { kind: row.kind } : {}
	});
}
async function terms(db, entryId) {
	return (await execute(db, sql`SELECT t.id,t.taxonomy,t.slug,t.name,t.parent_id AS "parentId",t.depth FROM content_entry_terms et INNER JOIN content_terms t ON t.id=et.term_id WHERE et.entry_id=${entryId} ORDER BY t.taxonomy,t.depth,t.slug,t.id`)).map((row) => Object.freeze({
		id: String(row.id),
		taxonomy: String(row.taxonomy),
		slug: String(row.slug),
		name: String(row.name),
		parentId: row.parentId === null ? null : String(row.parentId),
		depth: Number(row.depth)
	}));
}
async function entryFromRow(db, row) {
	return Object.freeze({
		id: String(row.id),
		slug: String(row.slug),
		type: String(row.type),
		title: String(row.title),
		body: String(row.body),
		status: String(row.status),
		visibility: row.visibility,
		publishedAt: nullableDate(row.publishedAt, "publishedAt"),
		author: String(row.author),
		terms: await terms(db, String(row.id)),
		createdAt: date(row.createdAt, "createdAt"),
		updatedAt: date(row.updatedAt, "updatedAt"),
		parentId: row.parentId === null ? null : String(row.parentId),
		menuOrder: Number(row.menuOrder),
		templateKey: row.templateKey === null ? null : String(row.templateKey),
		excerpt: String(row.excerpt ?? ""),
		featuredMedia: media(row.featuredMedia),
		commentStatus: row.commentStatus,
		pingStatus: row.pingStatus,
		passwordProtected: bool(row.passwordProtected),
		sticky: bool(row.sticky),
		format: row.format === null ? null : String(row.format),
		deletedAt: nullableDate(row.deletedAt, "deletedAt"),
		lastEditedBy: String(row.lastEditedBy),
		typeDefinitionRevision: Number(row.typeDefinitionRevision),
		statusDefinitionRevision: Number(row.statusDefinitionRevision)
	});
}
var ENTRY_SELECT = sql`SELECT id,slug,type,title,body,status,visibility,published_at AS "publishedAt",author,created_at AS "createdAt",updated_at AS "updatedAt",parent_id AS "parentId",menu_order AS "menuOrder",template_key AS "templateKey",excerpt,featured_media AS "featuredMedia",comment_status AS "commentStatus",ping_status AS "pingStatus",EXISTS(SELECT 1 FROM content_password_credentials c WHERE c.entry_id=content_entries.id) AS "passwordProtected",sticky,format,deleted_at AS "deletedAt",last_edited_by AS "lastEditedBy",type_definition_revision AS "typeDefinitionRevision",status_definition_revision AS "statusDefinitionRevision" FROM content_entries`;
async function rowById(db, id) {
	return (await execute(db, sql`${ENTRY_SELECT} WHERE id=${id} LIMIT 1`))[0];
}
async function leafRows(db, typeKey, slug) {
	return execute(db, sql`${ENTRY_SELECT} WHERE type=${typeKey} AND slug=${slug} AND deleted_at IS NULL ORDER BY id LIMIT 256`);
}
function templatesFor(types) {
	const descriptors = types.map((type) => Object.freeze({
		key: type.defaultTemplateKey,
		label: type.labels.singularName,
		typeKeys: Object.freeze([type.key]),
		active: true,
		version: String(type.revision)
	}));
	return Object.freeze({
		version: descriptors.map((item) => `${item.typeKeys[0]}:${item.key}:${item.version}`).sort().join("|") || "none",
		resolve: (typeKey, key) => descriptors.find((item) => item.key === key && item.typeKeys.includes(typeKey)),
		list: (typeKey) => Object.freeze(descriptors.filter((item) => item.typeKeys.includes(typeKey)))
	});
}
function publicPrincipal(types, member = false) {
	return Object.freeze({
		id: "anonymous",
		member,
		capabilities: new Set(types.map((type) => type.capabilities.read).filter((value) => typeof value === "string" && value.length > 0))
	});
}
function routeDefinitionReader(db) {
	return Object.freeze({ execute: async (query) => execute(db, query) });
}
function typeStatus(entry, types, statuses) {
	const type = types.find((item) => item.key === entry.type), status = statuses.find((item) => item.key === entry.status);
	if (!type || !status || entry.typeDefinitionRevision !== type.revision || entry.statusDefinitionRevision !== status.revision) return null;
	return {
		type,
		status
	};
}
async function canonicalPath(db, entry) {
	const path = [entry.slug], seen = /* @__PURE__ */ new Set([entry.id]);
	let parentId = entry.parentId;
	while (parentId !== null) {
		if (path.length >= MAX_PATH_DEPTH || seen.has(parentId)) return null;
		seen.add(parentId);
		const row = await rowById(db, parentId);
		if (!row || row.type !== entry.type || row.deletedAt !== null) return null;
		path.unshift(String(row.slug));
		parentId = row.parentId === null ? null : String(row.parentId);
	}
	return Object.freeze(path);
}
function bytes(value) {
	return new TextEncoder().encode(value);
}
function buffer(value) {
	return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
}
function base64url(value) {
	let binary = "";
	for (const byte of value) binary += String.fromCharCode(byte);
	return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
async function proofSignature(secret, entryId, credentialVersion) {
	const key = await crypto.subtle.importKey("raw", buffer(bytes(secret)), {
		name: "HMAC",
		hash: "SHA-256"
	}, false, ["sign"]);
	return base64url(new Uint8Array(await crypto.subtle.sign("HMAC", key, buffer(bytes(`${PROOF_VERSION}\n${entryId}\n${credentialVersion}`)))));
}
async function issueContentProof(secret, entryId, credentialVersion) {
	return `${PROOF_VERSION}.${entryId}.${credentialVersion}.${await proofSignature(secret, entryId, credentialVersion)}`;
}
async function verifyContentProof(secret, entryId, credentialVersion, proof) {
	if (!proof) return false;
	const parts = proof.split(".");
	if (parts.length !== 4 || parts[0] !== PROOF_VERSION || parts[1] !== entryId || parts[2] !== credentialVersion) return false;
	const expected = await proofSignature(secret, entryId, credentialVersion);
	const left = bytes(parts[3]), right = bytes(expected);
	if (left.length !== right.length) return false;
	let diff = 0;
	for (let i = 0; i < left.length; i++) diff |= left[i] ^ right[i];
	return diff === 0;
}
function pepper(secret) {
	return {
		currentVersion: "v1",
		secrets: { v1: secret }
	};
}
function contentPasswordAdapter(env) {
	return Object.freeze({
		hash: async (password) => Object.freeze({
			credential: await hashPassword(password, pepper(env.AUTH_PEPPER)),
			version: crypto.randomUUID()
		}),
		verify: (password, credential) => verifyPassword(password, credential, pepper(env.AUTH_PEPPER)),
		issueProof: (entryId, credentialVersion) => issueContentProof(env.AUTH_SESSION_SECRET, entryId, credentialVersion),
		verifyProof: (entryId, credentialVersion, proof) => verifyContentProof(env.AUTH_SESSION_SECRET, entryId, credentialVersion, proof)
	});
}
async function protectedRead(db, entry, types, statuses, principal, proof, secret) {
	const resolved = typeStatus(entry, types, statuses);
	if (!resolved || !resolved.type.publiclyQueryable || !resolved.status.published || !resolved.status.publiclyQueryable) return null;
	if (entry.visibility === "private" || entry.visibility === "members" && !principal.member) return null;
	try {
		authorizeContentAction({
			principal,
			type: resolved.type,
			status: resolved.status,
			operation: "read",
			entry
		});
	} catch {
		return null;
	}
	if (!entry.passwordProtected) return Object.freeze({
		access: "granted",
		entry
	});
	const credential = (await execute(db, sql`SELECT credential_version AS "credentialVersion" FROM content_password_credentials WHERE entry_id=${entry.id} LIMIT 1`))[0];
	if (credential && await verifyContentProof(secret, entry.id, String(credential.credentialVersion), proof)) return Object.freeze({
		access: "granted",
		entry
	});
	const { body: _body, ...metadata } = entry;
	return Object.freeze({
		access: "passwordRequired",
		entry: Object.freeze(metadata)
	});
}
function pathLookup(db, types, statuses, principal, secret) {
	return Object.freeze({
		async resolve(typeKey, pathSegments, _principal, proof) {
			if (pathSegments.length < 1 || pathSegments.length > MAX_PATH_DEPTH) return null;
			const leaves = await leafRows(db, typeKey, pathSegments.at(-1));
			const matched = [];
			for (const row of leaves) {
				const entry = await entryFromRow(db, row), path = await canonicalPath(db, entry);
				if (path && path.length === pathSegments.length && path.every((segment, index) => segment === pathSegments[index])) matched.push(entry);
			}
			if (matched.length !== 1) return null;
			return protectedRead(db, matched[0], types, statuses, principal, proof, secret);
		},
		async canonicalPath(entryId, _principal) {
			const row = await rowById(db, entryId);
			if (!row) return null;
			return canonicalPath(db, await entryFromRow(db, row));
		}
	});
}
async function resolveContentModelPublicRouteFromDb(db, proofSecret, url, proof, sources = {}, member = false) {
	const reader = routeDefinitionReader(db), [types, statuses] = await Promise.all([resolveContentTypes(reader, { codeTypes: sources.codeTypes }), resolveContentStatuses(reader, { codeStatuses: sources.codeStatuses })]), compiled = compileContentRoutes(types), templates = templatesFor(types), principal = publicPrincipal(types, member), resolution = await resolveContentRoute(compiled, pathLookup(db, types, statuses, principal, proofSecret), url, principal, proof);
	return Object.freeze({
		resolution,
		types,
		statuses,
		templates,
		compiled
	});
}
async function resolveContentModelPublicRoute(env, url, proof, sources = {}, member = false) {
	return resolveContentModelPublicRouteFromDb(getFullDb(env).db, env.AUTH_SESSION_SECRET, url, proof, sources, member);
}
function resolveContentModelRouteTemplate(context) {
	const resolution = context.resolution;
	if (resolution.kind === "notFound" || resolution.kind === "redirect") return null;
	const type = context.types.find((item) => item.key === resolution.match.typeKey);
	if (!type) return null;
	if (resolution.kind === "single") {
		const entry = resolution.content.entry;
		return resolveContentTemplate(context.templates, type, resolution.match, entry);
	}
	return resolveContentTemplate(context.templates, type, resolution.match);
}
async function unlockContentEntry(env, entryId, password) {
	if (typeof password !== "string" || password.length < 1 || password.length > 4096) return null;
	const db = getFullDb(env).db, credential = (await execute(db, sql`SELECT credential_version AS "credentialVersion",credential FROM content_password_credentials WHERE entry_id=${entryId} LIMIT 1`))[0];
	if (!credential || !await contentPasswordAdapter(env).verify(password, String(credential.credential))) return null;
	return issueContentProof(env.AUTH_SESSION_SECRET, entryId, String(credential.credentialVersion));
}
async function canonicalContentModelEntryLocation(db, compiled, entry) {
	const path = await canonicalPath(db, entry);
	if (!path) return null;
	const location = compiled.canonical(entry.type, path);
	return `${location.pathname}${location.search ?? ""}`;
}
async function listContentModelArchiveEntries(env, typeKey, page, pageSize = 20, sources = {}) {
	const db = getFullDb(env).db, reader = routeDefinitionReader(db), [types, statuses] = await Promise.all([resolveContentTypes(reader, { codeTypes: sources.codeTypes }), resolveContentStatuses(reader, { codeStatuses: sources.codeStatuses })]), type = types.find((item) => item.key === typeKey);
	if (!type || !type.publiclyQueryable) return [];
	const publishedKeys = statuses.filter((status) => status.published && status.publiclyQueryable && status.active).map((status) => status.key);
	if (publishedKeys.length === 0) return [];
	const offset = (Math.max(1, page) - 1) * pageSize, rows = await execute(db, sql`${ENTRY_SELECT} WHERE type=${typeKey} AND visibility='public' AND deleted_at IS NULL AND status IN (${sql.join(publishedKeys.map((key) => sql`${key}`), sql`,`)}) ORDER BY published_at DESC NULLS LAST,created_at DESC LIMIT ${pageSize} OFFSET ${offset}`);
	const output = [];
	for (const row of rows) {
		const entry = await entryFromRow(db, row), resolved = typeStatus(entry, types, statuses);
		if (!resolved || !resolved.status.published || !resolved.status.publiclyQueryable) continue;
		output.push(entry);
	}
	return Object.freeze(output);
}
//#endregion
export { resolveContentModelRouteTemplate as a, resolveContentModelPublicRoute as i, canonicalContentModelEntryLocation as n, unlockContentEntry as o, listContentModelArchiveEntries as r, CONTENT_PROOF_COOKIE as t };
