2855 lines
103 KiB
JavaScript
2855 lines
103 KiB
JavaScript
// backend/node_modules/hono/dist/request/constants.js
|
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
|
|
// backend/node_modules/hono/dist/utils/buffer.js
|
|
var bufferToFormData = (arrayBuffer, contentType) => {
|
|
return new Response(arrayBuffer, { headers: { "Content-Type": contentType.replace(/^[^;]+/, (mediaType) => mediaType.toLowerCase()) } }).formData();
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/utils/body.js
|
|
var MAX_NESTED_OBJECTS = 1e4;
|
|
var isRawRequest = (request) => "headers" in request;
|
|
var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
|
|
const { all = false, dot = false } = options;
|
|
const mediaType = (isRawRequest(request) ? request.headers : request.raw.headers).get("Content-Type")?.split(";")[0].trim().toLowerCase();
|
|
if (mediaType === "multipart/form-data" || mediaType === "application/x-www-form-urlencoded") return parseFormData(request, {
|
|
all,
|
|
dot
|
|
});
|
|
return {};
|
|
};
|
|
async function parseFormData(request, options) {
|
|
if (!isRawRequest(request) && request.bodyCache.formData) return convertFormDataToBodyData(await request.bodyCache.formData, options);
|
|
const headers = isRawRequest(request) ? request.headers : request.raw.headers;
|
|
const arrayBuffer = await request.arrayBuffer();
|
|
const formDataPromise = bufferToFormData(arrayBuffer, headers.get("Content-Type") || "");
|
|
if (!isRawRequest(request)) request.bodyCache.formData = formDataPromise;
|
|
const formData = await formDataPromise;
|
|
if (formData) return convertFormDataToBodyData(formData, options);
|
|
return {};
|
|
}
|
|
function convertFormDataToBodyData(formData, options) {
|
|
const form = /* @__PURE__ */ Object.create(null);
|
|
const nestingState = { count: 0 };
|
|
formData.forEach((value, key) => {
|
|
if (!(options.all || key.endsWith("[]"))) form[key] = value;
|
|
else handleParsingAllValues(form, key, value);
|
|
});
|
|
if (options.dot) Object.entries(form).forEach(([key, value]) => {
|
|
if (key.includes(".")) {
|
|
handleParsingNestedValues(form, key, value, nestingState);
|
|
delete form[key];
|
|
}
|
|
});
|
|
return form;
|
|
}
|
|
var handleParsingAllValues = (form, key, value) => {
|
|
if (form[key] !== void 0) {
|
|
if (Array.isArray(form[key])) form[key].push(value);
|
|
else form[key] = [form[key], value];
|
|
} else if (!key.endsWith("[]")) form[key] = value;
|
|
else form[key] = [value];
|
|
};
|
|
var handleParsingNestedValues = (form, key, value, state) => {
|
|
if (/(?:^|\.)__proto__\./.test(key)) return;
|
|
let nestedForm = form;
|
|
const keys = key.split(".", 34);
|
|
if (keys.length > 33) throwNestingLimitExceeded();
|
|
keys.forEach((key2, index) => {
|
|
if (index === keys.length - 1) nestedForm[key2] = value;
|
|
else {
|
|
if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
|
|
if (state.count++ >= MAX_NESTED_OBJECTS) throwNestingLimitExceeded();
|
|
nestedForm[key2] = /* @__PURE__ */ Object.create(null);
|
|
}
|
|
nestedForm = nestedForm[key2];
|
|
}
|
|
});
|
|
};
|
|
var throwNestingLimitExceeded = () => {
|
|
throw new Error("Nesting limit exceeded");
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/utils/url.js
|
|
var splitPath = (path) => {
|
|
const paths = path.split("/");
|
|
if (paths[0] === "") paths.shift();
|
|
return paths;
|
|
};
|
|
var splitRoutingPath = (routePath) => {
|
|
const { groups, path } = extractGroupsFromPath(routePath);
|
|
const paths = splitPath(path);
|
|
return replaceGroupMarks(paths, groups);
|
|
};
|
|
var extractGroupsFromPath = (path) => {
|
|
const groups = [];
|
|
path = path.replace(/\{[^}]+\}/g, (match2, index) => {
|
|
const mark = `@${index}`;
|
|
groups.push([mark, match2]);
|
|
return mark;
|
|
});
|
|
return {
|
|
groups,
|
|
path
|
|
};
|
|
};
|
|
var replaceGroupMarks = (paths, groups) => {
|
|
for (let i = groups.length - 1; i >= 0; i--) {
|
|
const [mark] = groups[i];
|
|
for (let j = paths.length - 1; j >= 0; j--) if (paths[j].includes(mark)) {
|
|
paths[j] = paths[j].replace(mark, groups[i][1]);
|
|
break;
|
|
}
|
|
}
|
|
return paths;
|
|
};
|
|
var patternCache = {};
|
|
var getPattern = (label, next) => {
|
|
if (label === "*") return "*";
|
|
const match2 = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
|
|
if (match2) {
|
|
const cacheKey = `${label}#${next}`;
|
|
if (!patternCache[cacheKey]) {
|
|
if (match2[2]) patternCache[cacheKey] = next && next[0] !== ":" && next[0] !== "*" ? [
|
|
cacheKey,
|
|
match2[1],
|
|
new RegExp(`^${match2[2]}(?=/${next})`)
|
|
] : [
|
|
label,
|
|
match2[1],
|
|
new RegExp(`^${match2[2]}$`)
|
|
];
|
|
else patternCache[cacheKey] = [
|
|
label,
|
|
match2[1],
|
|
true
|
|
];
|
|
}
|
|
return patternCache[cacheKey];
|
|
}
|
|
return null;
|
|
};
|
|
var tryDecode = (str, decoder) => {
|
|
try {
|
|
return decoder(str);
|
|
} catch {
|
|
return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match2) => {
|
|
try {
|
|
return decoder(match2);
|
|
} catch {
|
|
return match2;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
var tryDecodeURI = (str) => tryDecode(str, decodeURI);
|
|
var getPath = (request) => {
|
|
const url = request.url;
|
|
const start = url.indexOf("/", url.indexOf(":") + 4);
|
|
let i = start;
|
|
for (; i < url.length; i++) {
|
|
const charCode = url.charCodeAt(i);
|
|
if (charCode === 37) {
|
|
const queryIndex = url.indexOf("?", i);
|
|
const hashIndex = url.indexOf("#", i);
|
|
const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
|
|
const path = url.slice(start, end);
|
|
return tryDecodeURI(path.includes("%25") ? path.replace(/%25/g, "%2525") : path);
|
|
} else if (charCode === 63 || charCode === 35) break;
|
|
}
|
|
return url.slice(start, i);
|
|
};
|
|
var getPathNoStrict = (request) => {
|
|
const result = getPath(request);
|
|
return result.length > 1 && result.at(-1) === "/" ? result.slice(0, -1) : result;
|
|
};
|
|
var mergePath = (base, sub, ...rest) => {
|
|
if (rest.length) sub = mergePath(sub, ...rest);
|
|
return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
|
|
};
|
|
var checkOptionalParameter = (path) => {
|
|
if (path.charCodeAt(path.length - 1) !== 63 || !path.includes(":")) return null;
|
|
const segments = path.split("/");
|
|
const results = [];
|
|
let basePath = "";
|
|
segments.forEach((segment) => {
|
|
if (segment !== "" && !/\:/.test(segment)) basePath += "/" + segment;
|
|
else if (/\:/.test(segment)) {
|
|
if (segment.charCodeAt(segment.length - 1) === 63) {
|
|
if (results.length === 0 && basePath === "") results.push("/");
|
|
else results.push(basePath);
|
|
const optionalSegment = segment.slice(0, -1);
|
|
basePath += "/" + optionalSegment;
|
|
results.push(basePath);
|
|
} else basePath += "/" + segment;
|
|
}
|
|
});
|
|
return results.filter((v, i, a) => a.indexOf(v) === i);
|
|
};
|
|
var tryDecodeURIComponent = (str) => str.indexOf("%") !== -1 ? tryDecode(str, decodeURIComponent_) : str;
|
|
var _decodeURI = (value) => {
|
|
if (value.indexOf("+") !== -1) value = value.replace(/\+/g, " ");
|
|
return tryDecodeURIComponent(value);
|
|
};
|
|
var _getQueryParam = (url, key, multiple) => {
|
|
const hashIndex = url.indexOf("#", 8);
|
|
if (hashIndex !== -1) url = url.slice(0, hashIndex);
|
|
let encoded;
|
|
if (!multiple && key && key.indexOf("%") === -1 && key.indexOf("+") === -1) {
|
|
let keyIndex2 = url.indexOf("?", 8);
|
|
if (keyIndex2 === -1) return;
|
|
if (!url.startsWith(key, keyIndex2 + 1)) keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
|
|
while (keyIndex2 !== -1) {
|
|
const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
|
|
if (trailingKeyCode === 61) {
|
|
const valueIndex = keyIndex2 + key.length + 2;
|
|
const endIndex = url.indexOf("&", valueIndex);
|
|
return _decodeURI(url.slice(valueIndex, endIndex === -1 ? void 0 : endIndex));
|
|
} else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) return "";
|
|
keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
|
|
}
|
|
encoded = /[%+]/.test(url);
|
|
if (!encoded) return;
|
|
}
|
|
const results = /* @__PURE__ */ Object.create(null);
|
|
encoded ??= /[%+]/.test(url);
|
|
let keyIndex = url.indexOf("?", 8);
|
|
while (keyIndex !== -1) {
|
|
const nextKeyIndex = url.indexOf("&", keyIndex + 1);
|
|
let valueIndex = url.indexOf("=", keyIndex);
|
|
if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) valueIndex = -1;
|
|
let name = url.slice(keyIndex + 1, valueIndex === -1 ? nextKeyIndex === -1 ? void 0 : nextKeyIndex : valueIndex);
|
|
if (encoded) name = _decodeURI(name);
|
|
keyIndex = nextKeyIndex;
|
|
if (name === "") continue;
|
|
let value;
|
|
if (valueIndex === -1) value = "";
|
|
else {
|
|
value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? void 0 : nextKeyIndex);
|
|
if (encoded) value = _decodeURI(value);
|
|
}
|
|
if (multiple) {
|
|
if (!(results[name] && Array.isArray(results[name]))) results[name] = [];
|
|
results[name].push(value);
|
|
} else results[name] ??= value;
|
|
}
|
|
return key ? results[key] : results;
|
|
};
|
|
var getQueryParam = _getQueryParam;
|
|
var getQueryParams = (url, key) => {
|
|
return _getQueryParam(url, key, true);
|
|
};
|
|
var decodeURIComponent_ = decodeURIComponent;
|
|
|
|
// backend/node_modules/hono/dist/request.js
|
|
var HonoRequest = class {
|
|
/**
|
|
* `.raw` can get the raw Request object.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#raw}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* // For Cloudflare Workers
|
|
* app.post('/', async (c) => {
|
|
* const metadata = c.req.raw.cf?.hostMetadata?
|
|
* ...
|
|
* })
|
|
* ```
|
|
*/
|
|
raw;
|
|
#validatedData;
|
|
#matchResult;
|
|
routeIndex = 0;
|
|
/**
|
|
* `.path` can get the pathname of the request.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#path}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/about/me', (c) => {
|
|
* const pathname = c.req.path // `/about/me`
|
|
* })
|
|
* ```
|
|
*/
|
|
path;
|
|
bodyCache = {};
|
|
constructor(request, path = "/", matchResult = [[]]) {
|
|
this.raw = request;
|
|
this.path = path;
|
|
this.#matchResult = matchResult;
|
|
}
|
|
param(key) {
|
|
return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
|
|
}
|
|
#getDecodedParam(key) {
|
|
const paramKey = this.#matchResult[0][this.routeIndex]?.[1][key];
|
|
const param = this.#getParamValue(paramKey);
|
|
return param && tryDecodeURIComponent(param);
|
|
}
|
|
#getAllDecodedParams() {
|
|
const decoded = {};
|
|
const keys = Object.keys(this.#matchResult[0][this.routeIndex]?.[1] ?? {});
|
|
for (const key of keys) {
|
|
const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
|
|
if (value !== void 0) decoded[key] = tryDecodeURIComponent(value);
|
|
}
|
|
return decoded;
|
|
}
|
|
#getParamValue(paramKey) {
|
|
return this.#matchResult[1] ? this.#matchResult[1][paramKey] : paramKey;
|
|
}
|
|
query(key) {
|
|
return getQueryParam(this.url, key);
|
|
}
|
|
queries(key) {
|
|
return getQueryParams(this.url, key);
|
|
}
|
|
header(name) {
|
|
if (name) return this.raw.headers.get(name) ?? void 0;
|
|
const headerData = /* @__PURE__ */ Object.create(null);
|
|
this.raw.headers.forEach((value, key) => {
|
|
headerData[key] = value;
|
|
});
|
|
return headerData;
|
|
}
|
|
async parseBody(options) {
|
|
return parseBody(this, options);
|
|
}
|
|
#cachedBody = (key) => {
|
|
const { bodyCache, raw: raw2 } = this;
|
|
const cachedBody = bodyCache[key];
|
|
if (cachedBody) return cachedBody;
|
|
for (const anyCachedKey in bodyCache) return bodyCache[anyCachedKey].then((body) => {
|
|
if (anyCachedKey === "json") body = JSON.stringify(body);
|
|
const contentType = anyCachedKey === "formData" ? void 0 : raw2.headers.get("content-type");
|
|
return new Response(body, { headers: contentType ? { "Content-Type": contentType } : void 0 })[key]();
|
|
});
|
|
return bodyCache[key] = raw2[key]();
|
|
};
|
|
/**
|
|
* `.json()` can parse Request body of type `application/json`
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#json}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.json()
|
|
* })
|
|
* ```
|
|
*/
|
|
json() {
|
|
return this.#cachedBody("text").then((text) => JSON.parse(text));
|
|
}
|
|
/**
|
|
* `.text()` can parse Request body of type `text/plain`
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#text}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.text()
|
|
* })
|
|
* ```
|
|
*/
|
|
text() {
|
|
return this.#cachedBody("text");
|
|
}
|
|
/**
|
|
* `.arrayBuffer()` parse Request body as an `ArrayBuffer`
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#arraybuffer}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.arrayBuffer()
|
|
* })
|
|
* ```
|
|
*/
|
|
arrayBuffer() {
|
|
return this.#cachedBody("arrayBuffer");
|
|
}
|
|
/**
|
|
* `.bytes()` parses the request body as a `Uint8Array`.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#bytes}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.bytes()
|
|
* })
|
|
* ```
|
|
*/
|
|
bytes() {
|
|
return this.#cachedBody("arrayBuffer").then((buffer) => new Uint8Array(buffer));
|
|
}
|
|
/**
|
|
* Parses the request body as a `Blob`.
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.blob();
|
|
* });
|
|
* ```
|
|
* @see https://hono.dev/docs/api/request#blob
|
|
*/
|
|
blob() {
|
|
return this.#cachedBody("blob");
|
|
}
|
|
/**
|
|
* Parses the request body as `FormData`.
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.formData();
|
|
* });
|
|
* ```
|
|
* @see https://hono.dev/docs/api/request#formdata
|
|
*/
|
|
formData() {
|
|
return this.#cachedBody("formData");
|
|
}
|
|
/**
|
|
* Adds validated data to the request.
|
|
*
|
|
* @param target - The target of the validation.
|
|
* @param data - The validated data to add.
|
|
*/
|
|
addValidatedData(target, data) {
|
|
(this.#validatedData ??= {})[target] = data;
|
|
}
|
|
valid(target) {
|
|
return this.#validatedData?.[target];
|
|
}
|
|
/**
|
|
* `.url()` can get the request url strings.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#url}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/about/me', (c) => {
|
|
* const url = c.req.url // `http://localhost:8787/about/me`
|
|
* ...
|
|
* })
|
|
* ```
|
|
*/
|
|
get url() {
|
|
return this.raw.url;
|
|
}
|
|
/**
|
|
* `.method()` can get the method name of the request.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#method}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/about/me', (c) => {
|
|
* const method = c.req.method // `GET`
|
|
* })
|
|
* ```
|
|
*/
|
|
get method() {
|
|
return this.raw.method;
|
|
}
|
|
get [GET_MATCH_RESULT]() {
|
|
return this.#matchResult;
|
|
}
|
|
/**
|
|
* `.matchedRoutes()` can return a matched route in the handler
|
|
*
|
|
* @deprecated
|
|
*
|
|
* Use matchedRoutes helper defined in "hono/route" instead.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#matchedroutes}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.use('*', async function logger(c, next) {
|
|
* await next()
|
|
* c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
|
|
* const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
|
|
* console.log(
|
|
* method,
|
|
* ' ',
|
|
* path,
|
|
* ' '.repeat(Math.max(10 - path.length, 0)),
|
|
* name,
|
|
* i === c.req.routeIndex ? '<- respond from here' : ''
|
|
* )
|
|
* })
|
|
* })
|
|
* ```
|
|
*/
|
|
get matchedRoutes() {
|
|
return this.#matchResult[0].map(([[, route]]) => route);
|
|
}
|
|
/**
|
|
* `routePath()` can retrieve the path registered within the handler
|
|
*
|
|
* @deprecated
|
|
*
|
|
* Use routePath helper defined in "hono/route" instead.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#routepath}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/posts/:id', (c) => {
|
|
* return c.json({ path: c.req.routePath })
|
|
* })
|
|
* ```
|
|
*/
|
|
get routePath() {
|
|
return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
|
|
}
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/utils/html.js
|
|
var HtmlEscapedCallbackPhase = {
|
|
Stringify: 1,
|
|
BeforeStream: 2,
|
|
Stream: 3
|
|
};
|
|
var raw = (value, callbacks) => {
|
|
const escapedString = new String(value);
|
|
escapedString.isEscaped = true;
|
|
escapedString.callbacks = callbacks;
|
|
return escapedString;
|
|
};
|
|
var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
|
|
if (typeof str === "object" && !(str instanceof String)) {
|
|
if (!(str instanceof Promise)) str = str.toString();
|
|
if (str instanceof Promise) str = await str;
|
|
}
|
|
const callbacks = str.callbacks;
|
|
if (!callbacks?.length) return Promise.resolve(str);
|
|
if (buffer) buffer[0] += str;
|
|
else buffer = [str];
|
|
const resStr = Promise.all(callbacks.map((c) => c({
|
|
phase,
|
|
buffer,
|
|
context
|
|
}))).then((res) => Promise.all(res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context, buffer))).then(() => buffer[0]));
|
|
if (preserveCallbacks) return raw(await resStr, callbacks);
|
|
else return resStr;
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/context.js
|
|
var TEXT_PLAIN = "text/plain; charset=UTF-8";
|
|
var setDefaultContentType = (contentType, headers) => {
|
|
return {
|
|
"Content-Type": contentType,
|
|
...headers
|
|
};
|
|
};
|
|
var createResponseInstance = (body, init) => new Response(body, init);
|
|
var Context = class {
|
|
#rawRequest;
|
|
#req;
|
|
/**
|
|
* `.env` can get bindings (environment variables, secrets, KV namespaces, D1 database, R2 bucket etc.) in Cloudflare Workers.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#env}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* // Environment object for Cloudflare Workers
|
|
* app.get('*', async c => {
|
|
* const counter = c.env.COUNTER
|
|
* })
|
|
* ```
|
|
*/
|
|
env = {};
|
|
#var;
|
|
finalized = false;
|
|
/**
|
|
* `.error` can get the error object from the middleware if the Handler throws an error.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#error}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.use('*', async (c, next) => {
|
|
* await next()
|
|
* if (c.error) {
|
|
* // do something...
|
|
* }
|
|
* })
|
|
* ```
|
|
*/
|
|
error;
|
|
#status;
|
|
#executionCtx;
|
|
#res;
|
|
#layout;
|
|
#renderer;
|
|
#notFoundHandler;
|
|
#preparedHeaders;
|
|
#matchResult;
|
|
#path;
|
|
/**
|
|
* Creates an instance of the Context class.
|
|
*
|
|
* @param req - The Request object.
|
|
* @param options - Optional configuration options for the context.
|
|
*/
|
|
constructor(req, options) {
|
|
this.#rawRequest = req;
|
|
if (options) {
|
|
this.#executionCtx = options.executionCtx;
|
|
this.env = options.env;
|
|
this.#notFoundHandler = options.notFoundHandler;
|
|
this.#path = options.path;
|
|
this.#matchResult = options.matchResult;
|
|
}
|
|
}
|
|
/**
|
|
* `.req` is the instance of {@link HonoRequest}.
|
|
*/
|
|
get req() {
|
|
this.#req ??= new HonoRequest(this.#rawRequest, this.#path, this.#matchResult);
|
|
return this.#req;
|
|
}
|
|
/**
|
|
* @see {@link https://hono.dev/docs/api/context#event}
|
|
* The FetchEvent associated with the current request.
|
|
*
|
|
* @throws Will throw an error if the context does not have a FetchEvent.
|
|
*/
|
|
get event() {
|
|
if (this.#executionCtx && "respondWith" in this.#executionCtx) return this.#executionCtx;
|
|
else throw Error("This context has no FetchEvent");
|
|
}
|
|
/**
|
|
* @see {@link https://hono.dev/docs/api/context#executionctx}
|
|
* The ExecutionContext associated with the current request.
|
|
*
|
|
* @throws Will throw an error if the context does not have an ExecutionContext.
|
|
*/
|
|
get executionCtx() {
|
|
if (this.#executionCtx) return this.#executionCtx;
|
|
else throw Error("This context has no ExecutionContext");
|
|
}
|
|
/**
|
|
* @see {@link https://hono.dev/docs/api/context#res}
|
|
* The Response object for the current request.
|
|
*/
|
|
get res() {
|
|
return this.#res ||= createResponseInstance(null, { headers: this.#preparedHeaders ??= new Headers() });
|
|
}
|
|
/**
|
|
* Sets the Response object for the current request.
|
|
*
|
|
* @param _res - The Response object to set.
|
|
*/
|
|
set res(_res) {
|
|
if (this.#res && _res) {
|
|
_res = createResponseInstance(_res.body, _res);
|
|
for (const [k, v] of this.#res.headers.entries()) {
|
|
if (k === "content-type") continue;
|
|
if (k === "set-cookie") {
|
|
const cookies = this.#res.headers.getSetCookie();
|
|
_res.headers.delete("set-cookie");
|
|
for (const cookie of cookies) _res.headers.append("set-cookie", cookie);
|
|
} else _res.headers.set(k, v);
|
|
}
|
|
}
|
|
this.#res = _res;
|
|
this.finalized = true;
|
|
}
|
|
/**
|
|
* `.render()` can create a response within a layout.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#render-setrenderer}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/', (c) => {
|
|
* return c.render('Hello!')
|
|
* })
|
|
* ```
|
|
*/
|
|
render = (...args) => {
|
|
this.#renderer ??= (content) => this.html(content);
|
|
return this.#renderer(...args);
|
|
};
|
|
/**
|
|
* Sets the layout for the response.
|
|
*
|
|
* @param layout - The layout to set.
|
|
* @returns The layout function.
|
|
*/
|
|
setLayout = (layout) => this.#layout = layout;
|
|
/**
|
|
* Gets the current layout for the response.
|
|
*
|
|
* @returns The current layout function.
|
|
*/
|
|
getLayout = () => this.#layout;
|
|
/**
|
|
* `.setRenderer()` can set the layout in the custom middleware.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#render-setrenderer}
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* app.use('*', async (c, next) => {
|
|
* c.setRenderer((content) => {
|
|
* return c.html(
|
|
* <html>
|
|
* <body>
|
|
* <p>{content}</p>
|
|
* </body>
|
|
* </html>
|
|
* )
|
|
* })
|
|
* await next()
|
|
* })
|
|
* ```
|
|
*/
|
|
setRenderer = (renderer) => {
|
|
this.#renderer = renderer;
|
|
};
|
|
/**
|
|
* `.header()` can set headers.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#header}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/welcome', (c) => {
|
|
* // Set headers
|
|
* c.header('X-Message', 'Hello!')
|
|
* c.header('Content-Type', 'text/plain')
|
|
*
|
|
* // Append multiple headers using the append option (e.g. Vary)
|
|
* c.header('Vary', 'Accept-Encoding', { append: true })
|
|
* c.header('Vary', 'User-Agent', { append: true })
|
|
*
|
|
* return c.body('Thank you for coming')
|
|
* })
|
|
* ```
|
|
*/
|
|
header = (name, value, options) => {
|
|
if (this.finalized) this.#res = createResponseInstance(this.#res.body, this.#res);
|
|
const headers = this.#res ? this.#res.headers : this.#preparedHeaders ??= new Headers();
|
|
if (value === void 0) headers.delete(name);
|
|
else if (options?.append) headers.append(name, value);
|
|
else headers.set(name, value);
|
|
};
|
|
status = (status) => {
|
|
this.#status = status;
|
|
};
|
|
/**
|
|
* `.set()` can set the value specified by the key.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#set-get}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.use('*', async (c, next) => {
|
|
* c.set('message', 'Hono is hot!!')
|
|
* await next()
|
|
* })
|
|
* ```
|
|
*/
|
|
set = (key, value) => {
|
|
this.#var ??= /* @__PURE__ */ new Map();
|
|
this.#var.set(key, value);
|
|
};
|
|
/**
|
|
* `.get()` can use the value specified by the key.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#set-get}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/', (c) => {
|
|
* const message = c.get('message')
|
|
* return c.text(`The message is "${message}"`)
|
|
* })
|
|
* ```
|
|
*/
|
|
get = (key) => {
|
|
return this.#var ? this.#var.get(key) : void 0;
|
|
};
|
|
/**
|
|
* `.var` can access the value of a variable.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#var}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const result = c.var.client.oneMethod()
|
|
* ```
|
|
*/
|
|
get var() {
|
|
if (!this.#var) return {};
|
|
return Object.fromEntries(this.#var);
|
|
}
|
|
#newResponse(data, arg, headers) {
|
|
let responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders;
|
|
if (typeof arg === "object" && arg.headers) {
|
|
responseHeaders ??= new Headers();
|
|
for (const [key, value] of new Headers(arg.headers)) if (key === "set-cookie") responseHeaders.append(key, value);
|
|
else responseHeaders.set(key, value);
|
|
}
|
|
if (headers) {
|
|
if (!responseHeaders) {
|
|
let count = 0;
|
|
for (const k in headers) if (++count > 1 || typeof headers[k] !== "string") {
|
|
responseHeaders = new Headers();
|
|
break;
|
|
}
|
|
}
|
|
if (responseHeaders) for (const k in headers) {
|
|
const v = headers[k];
|
|
if (typeof v === "string") responseHeaders.set(k, v);
|
|
else {
|
|
responseHeaders.delete(k);
|
|
for (const v2 of v) responseHeaders.append(k, v2);
|
|
}
|
|
}
|
|
}
|
|
const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
|
|
return createResponseInstance(data, {
|
|
status,
|
|
headers: responseHeaders ?? headers
|
|
});
|
|
}
|
|
newResponse = (...args) => this.#newResponse(...args);
|
|
/**
|
|
* `.body()` can return the HTTP response.
|
|
* You can set headers with `.header()` and set HTTP status code with `.status`.
|
|
* This can also be set in `.text()`, `.json()` and so on.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#body}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/welcome', (c) => {
|
|
* // Set headers
|
|
* c.header('X-Message', 'Hello!')
|
|
* c.header('Content-Type', 'text/plain')
|
|
* // Set HTTP status code
|
|
* c.status(201)
|
|
*
|
|
* // Return the response body
|
|
* return c.body('Thank you for coming')
|
|
* })
|
|
* ```
|
|
*/
|
|
body = (data, arg, headers) => this.#newResponse(data, arg, headers);
|
|
/**
|
|
* `.text()` can render text as `Content-Type:text/plain`.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#text}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/say', (c) => {
|
|
* return c.text('Hello!')
|
|
* })
|
|
* ```
|
|
*/
|
|
text = (text, arg, headers) => {
|
|
return !this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized ? new Response(text) : this.#newResponse(text, arg, setDefaultContentType(TEXT_PLAIN, headers));
|
|
};
|
|
/**
|
|
* `.json()` can render JSON as `Content-Type:application/json`.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#json}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/api', (c) => {
|
|
* return c.json({ message: 'Hello!' })
|
|
* })
|
|
* ```
|
|
*/
|
|
json = (object, arg, headers) => {
|
|
return this.#newResponse(JSON.stringify(object), arg, setDefaultContentType("application/json", headers));
|
|
};
|
|
html = (html, arg, headers) => {
|
|
const res = (html2) => this.#newResponse(html2, arg, setDefaultContentType("text/html; charset=UTF-8", headers));
|
|
return typeof html === "object" ? resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then(res) : res(html);
|
|
};
|
|
/**
|
|
* `.redirect()` can Redirect, default status code is 302.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#redirect}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/redirect', (c) => {
|
|
* return c.redirect('/')
|
|
* })
|
|
* app.get('/redirect-permanently', (c) => {
|
|
* return c.redirect('/', 301)
|
|
* })
|
|
* ```
|
|
*/
|
|
redirect = (location, status) => {
|
|
const locationString = String(location);
|
|
this.header("Location", !/[^\x00-\xFF]/.test(locationString) ? locationString : encodeURI(locationString));
|
|
return this.newResponse(null, status ?? 302);
|
|
};
|
|
/**
|
|
* `.notFound()` can return the Not Found Response.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/context#notfound}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/notfound', (c) => {
|
|
* return c.notFound()
|
|
* })
|
|
* ```
|
|
*/
|
|
notFound = () => {
|
|
this.#notFoundHandler ??= () => createResponseInstance();
|
|
return this.#notFoundHandler(this);
|
|
};
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/compose.js
|
|
var compose = (middleware, onError, onNotFound) => {
|
|
return (context, next) => {
|
|
let index = -1;
|
|
return dispatch(0);
|
|
async function dispatch(i) {
|
|
if (i <= index) throw new Error("next() called multiple times");
|
|
index = i;
|
|
let res;
|
|
let isError = false;
|
|
let handler;
|
|
if (middleware[i]) {
|
|
handler = middleware[i][0][0];
|
|
context.req.routeIndex = i;
|
|
} else handler = i === middleware.length && next || void 0;
|
|
if (handler) try {
|
|
res = await handler(context, () => dispatch(i + 1));
|
|
} catch (err) {
|
|
if (err instanceof Error && onError) {
|
|
context.error = err;
|
|
res = await onError(err, context);
|
|
isError = true;
|
|
} else throw err;
|
|
}
|
|
else if (context.finalized === false && onNotFound) res = await onNotFound(context);
|
|
if (res && (context.finalized === false || isError)) context.res = res;
|
|
return context;
|
|
}
|
|
};
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/router.js
|
|
var METHODS = [
|
|
"get",
|
|
"post",
|
|
"put",
|
|
"delete",
|
|
"options",
|
|
"patch",
|
|
"query"
|
|
];
|
|
var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is already built.";
|
|
var UnsupportedPathError = class extends Error {
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/utils/constants.js
|
|
var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
|
|
|
|
// backend/node_modules/hono/dist/hono-base.js
|
|
var notFoundHandler = (c) => {
|
|
return c.text("404 Not Found", 404);
|
|
};
|
|
var errorHandler = (err, c) => {
|
|
if ("getResponse" in err) {
|
|
const res = err.getResponse();
|
|
return c.newResponse(res.body, res);
|
|
}
|
|
console.error(err);
|
|
return c.text("Internal Server Error", 500);
|
|
};
|
|
var Hono = class Hono2 {
|
|
get;
|
|
post;
|
|
put;
|
|
delete;
|
|
options;
|
|
patch;
|
|
query;
|
|
all;
|
|
on;
|
|
use;
|
|
router;
|
|
getPath;
|
|
_basePath = "/";
|
|
#path = "/";
|
|
routes = [];
|
|
constructor(options = {}) {
|
|
[...METHODS, "all"].forEach((method) => {
|
|
this[method] = (args1, ...args) => {
|
|
const methodName = method.toUpperCase();
|
|
if (typeof args1 === "string") this.#path = args1;
|
|
else this.#addRoute(methodName, this.#path, args1);
|
|
args.forEach((handler) => {
|
|
this.#addRoute(methodName, this.#path, handler);
|
|
});
|
|
return this;
|
|
};
|
|
});
|
|
this.on = (method, path, ...handlers) => {
|
|
for (const p of [path].flat()) {
|
|
this.#path = p;
|
|
for (const m of [method].flat()) {
|
|
const methodName = m.toUpperCase();
|
|
for (const handler of handlers) this.#addRoute(methodName, this.#path, handler);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
this.use = (arg1, ...handlers) => {
|
|
if (typeof arg1 === "string") this.#path = arg1;
|
|
else {
|
|
this.#path = "*";
|
|
handlers.unshift(arg1);
|
|
}
|
|
handlers.forEach((handler) => {
|
|
this.#addRoute("ALL", this.#path, handler);
|
|
});
|
|
return this;
|
|
};
|
|
const { strict, ...optionsWithoutStrict } = options;
|
|
Object.assign(this, optionsWithoutStrict);
|
|
this.getPath = strict ?? true ? options.getPath ?? getPath : getPathNoStrict;
|
|
}
|
|
#clone() {
|
|
const clone = new Hono2({
|
|
router: this.router,
|
|
getPath: this.getPath
|
|
});
|
|
clone.errorHandler = this.errorHandler;
|
|
clone.#notFoundHandler = this.#notFoundHandler;
|
|
clone.routes = this.routes;
|
|
return clone;
|
|
}
|
|
#notFoundHandler = notFoundHandler;
|
|
errorHandler = errorHandler;
|
|
/**
|
|
* `.route()` allows grouping other Hono instance in routes.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/routing#grouping}
|
|
*
|
|
* @param {string} path - base Path
|
|
* @param {Hono} app - other Hono instance
|
|
* @returns {Hono} routed Hono instance
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const app = new Hono()
|
|
* const app2 = new Hono()
|
|
*
|
|
* app2.get("/user", (c) => c.text("user"))
|
|
* app.route("/api", app2) // GET /api/user
|
|
* ```
|
|
*/
|
|
route(path, app2) {
|
|
const subApp = this.basePath(path);
|
|
app2.routes.map((r) => {
|
|
let handler;
|
|
if (app2.errorHandler === errorHandler) handler = r.handler;
|
|
else {
|
|
handler = async (c, next) => (await compose([], app2.errorHandler)(c, () => r.handler(c, next))).res;
|
|
handler[COMPOSED_HANDLER] = r.handler;
|
|
}
|
|
subApp.#addRoute(r.method, r.path, handler, r.basePath);
|
|
});
|
|
return this;
|
|
}
|
|
/**
|
|
* `.basePath()` allows base paths to be specified.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/routing#base-path}
|
|
*
|
|
* @param {string} path - base Path
|
|
* @returns {Hono} changed Hono instance
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const api = new Hono().basePath('/api')
|
|
* ```
|
|
*/
|
|
basePath(path) {
|
|
const subApp = this.#clone();
|
|
subApp._basePath = mergePath(this._basePath, path);
|
|
return subApp;
|
|
}
|
|
/**
|
|
* `.onError()` handles an error and returns a customized Response.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/hono#error-handling}
|
|
*
|
|
* @param {ErrorHandler} handler - request Handler for error
|
|
* @returns {Hono} changed Hono instance
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.onError((err, c) => {
|
|
* console.error(`${err}`)
|
|
* return c.text('Custom Error Message', 500)
|
|
* })
|
|
* ```
|
|
*/
|
|
onError = (handler) => {
|
|
this.errorHandler = handler;
|
|
return this;
|
|
};
|
|
/**
|
|
* `.notFound()` allows you to customize a Not Found Response.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/hono#not-found}
|
|
*
|
|
* @param {NotFoundHandler} handler - request handler for not-found
|
|
* @returns {Hono} changed Hono instance
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.notFound((c) => {
|
|
* return c.text('Custom 404 Message', 404)
|
|
* })
|
|
* ```
|
|
*/
|
|
notFound = (handler) => {
|
|
this.#notFoundHandler = handler;
|
|
return this;
|
|
};
|
|
/**
|
|
* `.mount()` allows you to mount applications built with other frameworks into your Hono application.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/hono#mount}
|
|
*
|
|
* @param {string} path - base Path
|
|
* @param {Function} applicationHandler - other Request Handler
|
|
* @param {MountOptions} [options] - options of `.mount()`
|
|
* @returns {Hono} mounted Hono instance
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* import { Router as IttyRouter } from 'itty-router'
|
|
* import { Hono } from 'hono'
|
|
* // Create itty-router application
|
|
* const ittyRouter = IttyRouter()
|
|
* // GET /itty-router/hello
|
|
* ittyRouter.get('/hello', () => new Response('Hello from itty-router'))
|
|
*
|
|
* const app = new Hono()
|
|
* app.mount('/itty-router', ittyRouter.handle)
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const app = new Hono()
|
|
* // Send the request to another application without modification.
|
|
* app.mount('/app', anotherApp, {
|
|
* replaceRequest: (req) => req,
|
|
* })
|
|
* ```
|
|
*/
|
|
mount(path, applicationHandler, options) {
|
|
let replaceRequest;
|
|
let optionHandler;
|
|
if (options) {
|
|
if (typeof options === "function") optionHandler = options;
|
|
else {
|
|
optionHandler = options.optionHandler;
|
|
if (options.replaceRequest === false) replaceRequest = (request) => request;
|
|
else replaceRequest = options.replaceRequest;
|
|
}
|
|
}
|
|
const getOptions = optionHandler ? (c) => {
|
|
const options2 = optionHandler(c);
|
|
return Array.isArray(options2) ? options2 : [options2];
|
|
} : (c) => {
|
|
let executionContext = void 0;
|
|
try {
|
|
executionContext = c.executionCtx;
|
|
} catch {
|
|
}
|
|
return [c.env, executionContext];
|
|
};
|
|
replaceRequest ||= (() => {
|
|
const mergedPath = mergePath(this._basePath, path);
|
|
const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
|
|
return (request) => {
|
|
const url = new URL(request.url);
|
|
url.pathname = this.getPath(request).slice(pathPrefixLength) || "/";
|
|
return new Request(url, request);
|
|
};
|
|
})();
|
|
const handler = async (c, next) => {
|
|
const res = await applicationHandler(replaceRequest(c.req.raw), ...getOptions(c));
|
|
if (res) return res;
|
|
await next();
|
|
};
|
|
this.#addRoute("ALL", mergePath(path, "*"), handler);
|
|
return this;
|
|
}
|
|
#addRoute(method, path, handler, baseRoutePath) {
|
|
path = mergePath(this._basePath, path);
|
|
const r = {
|
|
basePath: baseRoutePath !== void 0 ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
path,
|
|
method,
|
|
handler
|
|
};
|
|
this.router.add(method, path, [handler, r]);
|
|
this.routes.push(r);
|
|
}
|
|
#handleError(err, c) {
|
|
if (err instanceof Error) return this.errorHandler(err, c);
|
|
throw err;
|
|
}
|
|
#dispatch(request, executionCtx, env, method) {
|
|
if (method === "HEAD") return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
|
|
const path = this.getPath(request, { env });
|
|
const matchResult = this.router.match(method, path);
|
|
const c = new Context(request, {
|
|
path,
|
|
matchResult,
|
|
env,
|
|
executionCtx,
|
|
notFoundHandler: this.#notFoundHandler
|
|
});
|
|
if (matchResult[0].length === 1) {
|
|
let res;
|
|
try {
|
|
res = matchResult[0][0][0][0](c, async () => {
|
|
c.res = await this.#notFoundHandler(c);
|
|
});
|
|
} catch (err) {
|
|
return this.#handleError(err, c);
|
|
}
|
|
return res instanceof Promise ? res.then((resolved) => resolved || (c.finalized ? c.res : this.#notFoundHandler(c))).catch((err) => this.#handleError(err, c)) : res ?? this.#notFoundHandler(c);
|
|
}
|
|
const composed = compose(matchResult[0], this.errorHandler, this.#notFoundHandler);
|
|
return (async () => {
|
|
try {
|
|
const context = await composed(c);
|
|
if (!context.finalized) throw new Error("Context is not finalized. Did you forget to return a Response object or `await next()`?");
|
|
return context.res;
|
|
} catch (err) {
|
|
return this.#handleError(err, c);
|
|
}
|
|
})();
|
|
}
|
|
/**
|
|
* `.fetch()` will be entry point of your app.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/hono#fetch}
|
|
*
|
|
* @param {Request} request - request Object of request
|
|
* @param {Env} env - env Object
|
|
* @param {ExecutionContext} executionCtx - context of execution
|
|
* @returns {Response | Promise<Response>} response of request
|
|
*
|
|
*/
|
|
fetch = (request, ...rest) => {
|
|
return this.#dispatch(request, rest[1], rest[0], request.method);
|
|
};
|
|
/**
|
|
* `.request()` is a useful method for testing.
|
|
* You can pass a URL or pathname to send a GET request.
|
|
* app will return a Response object.
|
|
* ```ts
|
|
* test('GET /hello is ok', async () => {
|
|
* const res = await app.request('/hello')
|
|
* expect(res.status).toBe(200)
|
|
* })
|
|
* ```
|
|
* @see https://hono.dev/docs/api/hono#request
|
|
*/
|
|
request = (input, requestInit, Env, executionCtx) => {
|
|
if (input instanceof Request) return this.fetch(requestInit ? new Request(input, requestInit) : input, Env, executionCtx);
|
|
input = input.toString();
|
|
return this.fetch(new Request(/^https?:\/\//.test(input) ? input : `http://localhost${mergePath("/", input)}`, requestInit), Env, executionCtx);
|
|
};
|
|
/**
|
|
* `.fire()` automatically adds a global fetch event listener.
|
|
* This can be useful for environments that adhere to the Service Worker API, such as non-ES module Cloudflare Workers.
|
|
* @deprecated
|
|
* Use `fire` from `hono/service-worker` instead.
|
|
* ```ts
|
|
* import { Hono } from 'hono'
|
|
* import { fire } from 'hono/service-worker'
|
|
*
|
|
* const app = new Hono()
|
|
* // ...
|
|
* fire(app)
|
|
* ```
|
|
* @see https://hono.dev/docs/api/hono#fire
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
|
|
* @see https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/
|
|
*/
|
|
fire = () => {
|
|
addEventListener("fetch", (event) => {
|
|
event.respondWith(this.#dispatch(event.request, event, void 0, event.request.method));
|
|
});
|
|
};
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/router/utils.js
|
|
var createNullObject = () => /* @__PURE__ */ Object.create(null);
|
|
|
|
// backend/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
var emptyParam = [];
|
|
function match(method, path) {
|
|
const matchers = this.buildAllMatchers();
|
|
const match2 = ((method2, path2) => {
|
|
const matcher = matchers[method2] || matchers["ALL"];
|
|
const staticMatch = matcher[2][path2];
|
|
if (staticMatch) return staticMatch;
|
|
const match3 = path2.match(matcher[0]);
|
|
if (!match3) return [[], emptyParam];
|
|
const index = match3.indexOf("", 1);
|
|
return [matcher[1][index], match3];
|
|
});
|
|
this.match = match2;
|
|
return match2(method, path);
|
|
}
|
|
|
|
// backend/node_modules/hono/dist/router/reg-exp-router/node.js
|
|
var LABEL_REG_EXP_STR = "[^/]+";
|
|
var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
|
|
var PATH_ERROR = /* @__PURE__ */ Symbol();
|
|
var regExpMetaChars = /* @__PURE__ */ new Set(".\\+*[^]$()");
|
|
function compareKey(a, b) {
|
|
if (a.length === 1) return b.length === 1 ? a < b ? -1 : 1 : -1;
|
|
if (b.length === 1) return 1;
|
|
if (a === ".*" || a === "(?:|/.*)") return b === "(?:|/.*)" ? -1 : 1;
|
|
else if (b === ".*" || b === "(?:|/.*)") return -1;
|
|
if (a === "[^/]+") return 1;
|
|
else if (b === "[^/]+") return -1;
|
|
return a.length === b.length ? a < b ? -1 : 1 : b.length - a.length;
|
|
}
|
|
var Node = class Node2 {
|
|
#index;
|
|
#varIndex;
|
|
#children = createNullObject();
|
|
insert(tokens, index, paramMap, context, isStatic) {
|
|
let node = this;
|
|
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
const token = tokens[i];
|
|
const pattern = token.length === 1 ? token === "*" ? i === len - 1 ? [
|
|
"",
|
|
"",
|
|
".*"
|
|
] : [
|
|
"",
|
|
"",
|
|
LABEL_REG_EXP_STR
|
|
] : null : token === "/*" ? [
|
|
"",
|
|
"",
|
|
TAIL_WILDCARD_REG_EXP_STR
|
|
] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
|
|
let nextNode;
|
|
if (pattern) {
|
|
const name = pattern[1];
|
|
let regexpStr = pattern[2] || "[^/]+";
|
|
if (name && pattern[2]) {
|
|
if (regexpStr === ".*") throw PATH_ERROR;
|
|
regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
|
|
if (/\((?!\?:)/.test(regexpStr)) throw PATH_ERROR;
|
|
if (regexpStr.length === 1 && regExpMetaChars.has(regexpStr)) throw PATH_ERROR;
|
|
}
|
|
nextNode = node.#children[regexpStr];
|
|
if (!nextNode) {
|
|
if (regexpStr !== ".*" && regexpStr !== "(?:|/.*)") {
|
|
for (const k in node.#children) if ((regexpStr.length > 1 || k.length > 1) && k !== ".*" && k !== "(?:|/.*)") throw PATH_ERROR;
|
|
}
|
|
nextNode = node.#children[regexpStr] = new Node2();
|
|
}
|
|
if (name !== "") {
|
|
nextNode.#varIndex ??= context.varIndex++;
|
|
paramMap.push([name, nextNode.#varIndex]);
|
|
}
|
|
} else {
|
|
nextNode = node.#children[token];
|
|
if (!nextNode) {
|
|
for (const k in node.#children) if (k.length > 1 && k !== ".*" && k !== "(?:|/.*)") throw PATH_ERROR;
|
|
nextNode = node.#children[token] = new Node2();
|
|
}
|
|
}
|
|
node = nextNode;
|
|
}
|
|
if (node.#index !== void 0) throw PATH_ERROR;
|
|
node.#index = isStatic ? -1 : index;
|
|
}
|
|
buildRegExpStr() {
|
|
const strList = Object.keys(this.#children).sort(compareKey).map((k) => {
|
|
const c = this.#children[k];
|
|
const childStr = c.buildRegExpStr();
|
|
return childStr === "" ? "" : (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + childStr;
|
|
}).filter(Boolean);
|
|
if (typeof this.#index === "number" && this.#index !== -1) strList.unshift(`#${this.#index}`);
|
|
if (strList.length === 0) return "";
|
|
if (strList.length === 1) return strList[0];
|
|
return "(?:" + strList.join("|") + ")";
|
|
}
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/router/reg-exp-router/trie.js
|
|
var Trie = class {
|
|
#context = { varIndex: 0 };
|
|
#root = new Node();
|
|
#index = 0;
|
|
paths = createNullObject();
|
|
insert(path, isStatic) {
|
|
if (isStatic) {
|
|
this.#root.insert(path.split(""), 0, [], this.#context, true);
|
|
return;
|
|
}
|
|
const paramAssoc = [];
|
|
const groups = [];
|
|
let markedPath = path;
|
|
for (let i = 0; ; ) {
|
|
let replaced = false;
|
|
markedPath = markedPath.replace(/\{[^}]+\}/g, (m) => {
|
|
const mark = `@\\${i}`;
|
|
groups[i] = [mark, m];
|
|
i++;
|
|
replaced = true;
|
|
return mark;
|
|
});
|
|
if (!replaced) break;
|
|
}
|
|
const tokens = markedPath.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
|
|
for (let i = groups.length - 1; i >= 0; i--) {
|
|
const [mark] = groups[i];
|
|
for (let j = tokens.length - 1; j >= 0; j--) if (tokens[j].indexOf(mark) !== -1) {
|
|
tokens[j] = tokens[j].replace(mark, groups[i][1]);
|
|
break;
|
|
}
|
|
}
|
|
this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
|
|
this.paths[path] = [this.#index++, paramAssoc];
|
|
}
|
|
buildRegExp() {
|
|
let regexp = this.#root.buildRegExpStr();
|
|
if (regexp === "") return [
|
|
/^$/,
|
|
[],
|
|
[]
|
|
];
|
|
let captureIndex = 0;
|
|
const indexReplacementMap = [];
|
|
const paramReplacementMap = [];
|
|
regexp = regexp.replace(/#(\d+)|@(\d+)|\.\*\$/g, (_, handlerIndex, paramIndex) => {
|
|
if (handlerIndex !== void 0) {
|
|
indexReplacementMap[++captureIndex] = Number(handlerIndex);
|
|
return "$()";
|
|
}
|
|
if (paramIndex !== void 0) {
|
|
paramReplacementMap[Number(paramIndex)] = ++captureIndex;
|
|
return "";
|
|
}
|
|
return "";
|
|
});
|
|
return [
|
|
new RegExp(`^${regexp}`),
|
|
indexReplacementMap,
|
|
paramReplacementMap
|
|
];
|
|
}
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/router/reg-exp-router/router.js
|
|
var wildcardRegExpCache = createNullObject();
|
|
function buildWildcardRegExp(path) {
|
|
return wildcardRegExpCache[path] ??= new RegExp(`^${path.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g, (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ".*" : `/:${LABEL_REG_EXP_STR}`)}$`);
|
|
}
|
|
function findMiddleware(middleware, path) {
|
|
for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) if (buildWildcardRegExp(k).test(path)) return [...middleware[k]];
|
|
}
|
|
var RegExpRouter = class {
|
|
name = "RegExpRouter";
|
|
#middleware;
|
|
#routes;
|
|
#tries;
|
|
constructor() {
|
|
this.#middleware = { ["ALL"]: createNullObject() };
|
|
this.#routes = { ["ALL"]: createNullObject() };
|
|
this.#tries = { ["ALL"]: new Trie() };
|
|
}
|
|
#insertPath(method, path) {
|
|
try {
|
|
this.#tries[method].insert(path, !/\*|\/:/.test(path));
|
|
} catch (e) {
|
|
throw e === PATH_ERROR ? new UnsupportedPathError(path) : e;
|
|
}
|
|
}
|
|
add(method, path, handler) {
|
|
const middleware = this.#middleware;
|
|
const routes = this.#routes;
|
|
if (!middleware) throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
if (!middleware[method]) {
|
|
this.#tries[method] = new Trie();
|
|
for (const handlerMap of [middleware, routes]) {
|
|
handlerMap[method] = createNullObject();
|
|
for (const p in handlerMap["ALL"]) {
|
|
handlerMap[method][p] = [...handlerMap["ALL"][p]];
|
|
this.#insertPath(method, p);
|
|
}
|
|
}
|
|
}
|
|
if (path === "/*") path = "*";
|
|
const methods = method === "ALL" ? Object.keys(middleware) : [method];
|
|
if (/\*$/.test(path)) {
|
|
const re = buildWildcardRegExp(path);
|
|
for (const m of methods) if (!middleware[m][path]) {
|
|
this.#insertPath(m, path);
|
|
middleware[m][path] = findMiddleware(middleware[m], path) || findMiddleware(middleware["ALL"], path) || [];
|
|
}
|
|
for (const handlerMap of [middleware, routes]) for (const m of methods) for (const p in handlerMap[m]) re.test(p) && handlerMap[m][p].push([handler, path]);
|
|
return;
|
|
}
|
|
const paths = checkOptionalParameter(path) || [path];
|
|
for (const path2 of paths) for (const m of methods) {
|
|
if (!routes[m][path2]) {
|
|
this.#insertPath(m, path2);
|
|
routes[m][path2] = findMiddleware(middleware[m], path2) || findMiddleware(middleware["ALL"], path2) || [];
|
|
}
|
|
routes[m][path2].push([handler, path2]);
|
|
}
|
|
}
|
|
match = match;
|
|
buildAllMatchers() {
|
|
const matchers = createNullObject();
|
|
for (const method of Object.keys(this.#routes)) matchers[method] = this.#buildMatcher(method);
|
|
this.#middleware = this.#routes = this.#tries = void 0;
|
|
wildcardRegExpCache = createNullObject();
|
|
return matchers;
|
|
}
|
|
#buildMatcher(method) {
|
|
const middleware = this.#middleware[method];
|
|
const routes = this.#routes[method];
|
|
const trie = this.#tries[method];
|
|
const staticMap = createNullObject();
|
|
const handlerData = [];
|
|
const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
|
|
for (const r of [middleware, routes]) for (const path in r) {
|
|
const handlers = r[path];
|
|
const pathData = trie.paths[path];
|
|
if (!pathData) {
|
|
staticMap[path] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
|
|
continue;
|
|
}
|
|
handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [h, trie.paths[handlerPath][1].reduceRight((map, [key], i) => {
|
|
map[key] = paramReplacementMap[pathData[1][i][1]];
|
|
return map;
|
|
}, createNullObject())]);
|
|
}
|
|
return [
|
|
regexp,
|
|
indexReplacementMap.map((i) => handlerData[i]),
|
|
staticMap
|
|
];
|
|
}
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/router/smart-router/router.js
|
|
var SmartRouter = class {
|
|
name = "SmartRouter";
|
|
#routers = [];
|
|
#routes = [];
|
|
constructor(init) {
|
|
this.#routers = init.routers;
|
|
}
|
|
add(method, path, handler) {
|
|
if (!this.#routes) throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
this.#routes.push([
|
|
method,
|
|
path,
|
|
handler
|
|
]);
|
|
}
|
|
match(method, path) {
|
|
if (!this.#routes) throw new Error("Fatal error");
|
|
const routers = this.#routers;
|
|
const routes = this.#routes;
|
|
const len = routers.length;
|
|
let i = 0;
|
|
let res;
|
|
for (; i < len; i++) {
|
|
const router = routers[i];
|
|
try {
|
|
for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) router.add(...routes[i2]);
|
|
res = router.match(method, path);
|
|
} catch (e) {
|
|
if (e instanceof UnsupportedPathError) continue;
|
|
throw e;
|
|
}
|
|
this.match = router.match.bind(router);
|
|
this.#routers = [router];
|
|
this.#routes = void 0;
|
|
break;
|
|
}
|
|
if (i === len) throw new Error("Fatal error");
|
|
this.name = `SmartRouter + ${this.activeRouter.name}`;
|
|
return res;
|
|
}
|
|
get activeRouter() {
|
|
if (this.#routes || this.#routers.length !== 1) throw new Error("No active router has been determined yet.");
|
|
return this.#routers[0];
|
|
}
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/router/trie-router/node.js
|
|
var emptyParams = createNullObject();
|
|
var order = 0;
|
|
var Node3 = class Node4 {
|
|
#methods = [];
|
|
#children = createNullObject();
|
|
#patterns = [];
|
|
#pattern;
|
|
#params = emptyParams;
|
|
insert(method, path, handler) {
|
|
let curNode = this;
|
|
const parts = splitRoutingPath(path);
|
|
const possibleKeys = /* @__PURE__ */ new Set();
|
|
let i = 0;
|
|
for (const p of parts) {
|
|
const nextP = parts[++i];
|
|
const pattern = getPattern(p, nextP) || (nextP === void 0 && p && p.indexOf("*") === p.length - 1 ? p : null);
|
|
const isParam = Array.isArray(pattern);
|
|
const key = isParam ? pattern[0] : pattern || p;
|
|
const child = curNode.#children[key] ||= new Node4();
|
|
if (pattern && !child.#pattern) {
|
|
child.#pattern = pattern;
|
|
curNode.#patterns.push(child);
|
|
}
|
|
curNode = child;
|
|
if (isParam) possibleKeys.add(pattern[1]);
|
|
}
|
|
curNode.#methods.push({ [method]: {
|
|
handler,
|
|
possibleKeys: [...possibleKeys],
|
|
score: ++order
|
|
} });
|
|
}
|
|
#pushHandlerSets(handlerSets, node, method, nodeParams, params) {
|
|
for (let i = 0, len = node.#methods.length; i < len; i++) {
|
|
const m = node.#methods[i];
|
|
const handlerSet = m[method] || m["ALL"];
|
|
if (handlerSet) {
|
|
handlerSet.params = createNullObject();
|
|
handlerSets.push(handlerSet);
|
|
for (let i2 = 0, len2 = handlerSet.possibleKeys.length; i2 < len2; i2++) {
|
|
const key = handlerSet.possibleKeys[i2];
|
|
handlerSet.params[key] = params?.[key] && !i2 ? params[key] : nodeParams[key] ?? params?.[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
search(method, path) {
|
|
const handlerSets = [];
|
|
this.#params = emptyParams;
|
|
let curNodes = [this];
|
|
const parts = splitPath(path);
|
|
const curNodesQueue = [];
|
|
const len = parts.length;
|
|
let partOffsets = null;
|
|
for (let i = 0; i < len; i++) {
|
|
const part = parts[i];
|
|
const isLast = i === len - 1;
|
|
const tempNodes = [];
|
|
for (let j = 0, len2 = curNodes.length; j < len2; j++) {
|
|
const node = curNodes[j];
|
|
const nextNode = node.#children[part];
|
|
if (nextNode) {
|
|
nextNode.#params = node.#params;
|
|
if (isLast) {
|
|
if (nextNode.#children["*"]) this.#pushHandlerSets(handlerSets, nextNode.#children["*"], method, node.#params);
|
|
this.#pushHandlerSets(handlerSets, nextNode, method, node.#params);
|
|
} else tempNodes.push(nextNode);
|
|
}
|
|
for (const child of node.#patterns) {
|
|
const pattern = child.#pattern;
|
|
const params = node.#params === emptyParams ? {} : { ...node.#params };
|
|
if (typeof pattern === "string") {
|
|
if (pattern === "*" || part.startsWith(pattern.slice(0, -1))) {
|
|
this.#pushHandlerSets(handlerSets, child, method, node.#params);
|
|
if (pattern === "*") {
|
|
child.#params = params;
|
|
tempNodes.push(child);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
const [, name, matcher] = pattern;
|
|
if (!part && matcher === true) continue;
|
|
if (matcher !== true) {
|
|
if (!partOffsets) {
|
|
partOffsets = [];
|
|
let offset = path[0] === "/" ? 1 : 0;
|
|
for (let p = 0; p < len; p++) {
|
|
partOffsets[p] = offset;
|
|
offset += parts[p].length + 1;
|
|
}
|
|
}
|
|
const restPathString = path.slice(partOffsets[i]);
|
|
const m = matcher.exec(restPathString);
|
|
if (m) {
|
|
params[name] = m[0];
|
|
this.#pushHandlerSets(handlerSets, child, method, node.#params, params);
|
|
if (m[0].length === restPathString.length && child.#children["*"]) this.#pushHandlerSets(handlerSets, child.#children["*"], method, node.#params, params);
|
|
for (const _ in child.#children) {
|
|
child.#params = params;
|
|
const componentCount = m[0].match(/\//g)?.length ?? 0;
|
|
(curNodesQueue[componentCount] ||= []).push(child);
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
if (matcher === true || matcher.test(part)) {
|
|
params[name] = part;
|
|
if (isLast) {
|
|
this.#pushHandlerSets(handlerSets, child, method, params, node.#params);
|
|
if (child.#children["*"]) this.#pushHandlerSets(handlerSets, child.#children["*"], method, params, node.#params);
|
|
} else {
|
|
child.#params = params;
|
|
tempNodes.push(child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const shifted = curNodesQueue.shift();
|
|
curNodes = shifted ? tempNodes.concat(shifted) : tempNodes;
|
|
}
|
|
if (handlerSets[1]) handlerSets.sort((a, b) => {
|
|
return a.score - b.score;
|
|
});
|
|
return [handlerSets.map(({ handler, params }) => [handler, params])];
|
|
}
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/router/trie-router/router.js
|
|
var TrieRouter = class {
|
|
name = "TrieRouter";
|
|
#node = new Node3();
|
|
add(method, path, handler) {
|
|
for (const result of checkOptionalParameter(path) || [path]) this.#node.insert(method, result, handler);
|
|
}
|
|
match(method, path) {
|
|
return this.#node.search(method, path);
|
|
}
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/hono.js
|
|
var Hono3 = class extends Hono {
|
|
/**
|
|
* Creates an instance of the Hono class.
|
|
*
|
|
* @param options - Optional configuration options for the Hono instance.
|
|
*/
|
|
constructor(options = {}) {
|
|
super(options);
|
|
this.router = options.router ?? new SmartRouter({ routers: [new RegExpRouter(), new TrieRouter()] });
|
|
}
|
|
};
|
|
|
|
// backend/node_modules/hono/dist/middleware/cors/index.js
|
|
var cors = (options) => {
|
|
const opts = {
|
|
origin: "*",
|
|
allowMethods: [
|
|
"GET",
|
|
"HEAD",
|
|
"PUT",
|
|
"POST",
|
|
"DELETE",
|
|
"PATCH",
|
|
"QUERY"
|
|
],
|
|
allowHeaders: [],
|
|
exposeHeaders: [],
|
|
...options
|
|
};
|
|
const exposeHeadersStr = opts.exposeHeaders?.length ? opts.exposeHeaders.join(",") : void 0;
|
|
const allowHeadersStr = opts.allowHeaders?.length ? opts.allowHeaders.join(",") : void 0;
|
|
const findAllowOrigin = ((optsOrigin) => {
|
|
if (typeof optsOrigin === "string") {
|
|
if (optsOrigin === "*") return () => optsOrigin;
|
|
else return (origin) => optsOrigin === origin ? origin : null;
|
|
} else if (typeof optsOrigin === "function") return optsOrigin;
|
|
else return (origin) => optsOrigin.includes(origin) ? origin : null;
|
|
})(opts.origin);
|
|
const findAllowMethods = ((optsAllowMethods) => {
|
|
if (typeof optsAllowMethods === "function") return async (origin, c) => (await optsAllowMethods(origin, c)).join(",");
|
|
else if (Array.isArray(optsAllowMethods)) {
|
|
const methodsStr = optsAllowMethods.join(",");
|
|
return () => methodsStr;
|
|
} else return () => "";
|
|
})(opts.allowMethods);
|
|
return async function cors2(c, next) {
|
|
function set(key, value) {
|
|
c.res.headers.set(key, value);
|
|
}
|
|
const allowOrigin = await findAllowOrigin(c.req.header("origin") || "", c);
|
|
if (allowOrigin) set("Access-Control-Allow-Origin", allowOrigin);
|
|
if (opts.credentials) set("Access-Control-Allow-Credentials", "true");
|
|
if (exposeHeadersStr) set("Access-Control-Expose-Headers", exposeHeadersStr);
|
|
if (c.req.method === "OPTIONS") {
|
|
if (opts.origin !== "*") c.res.headers.append("Vary", "Origin");
|
|
if (opts.maxAge != null) set("Access-Control-Max-Age", opts.maxAge.toString());
|
|
const allowMethods = await findAllowMethods(c.req.header("origin") || "", c);
|
|
if (allowMethods) set("Access-Control-Allow-Methods", allowMethods);
|
|
let headersStr = allowHeadersStr;
|
|
if (!headersStr) {
|
|
const requestHeaders = c.req.header("Access-Control-Request-Headers");
|
|
if (requestHeaders) headersStr = requestHeaders.split(",").map((h) => h.trim()).join(",");
|
|
}
|
|
if (headersStr) {
|
|
set("Access-Control-Allow-Headers", headersStr);
|
|
c.res.headers.append("Vary", "Access-Control-Request-Headers");
|
|
}
|
|
c.res.headers.delete("Content-Length");
|
|
c.res.headers.delete("Content-Type");
|
|
return new Response(null, {
|
|
headers: c.res.headers,
|
|
status: 204,
|
|
statusText: "No Content"
|
|
});
|
|
}
|
|
await next();
|
|
if (opts.origin !== "*") c.header("Vary", "Origin", { append: true });
|
|
};
|
|
};
|
|
|
|
// backend/src/utils.ts
|
|
function getMWorldOrigin(c) {
|
|
const origin = c.req.header("x-mworld-origin") || c.req.header("origin") || "https://mworld.cloud";
|
|
return origin.split("?")[0].split("#")[0].replace(/\/$/, "");
|
|
}
|
|
async function verifyMAuthToken(token, origin) {
|
|
if (token === "admin_token") {
|
|
return {
|
|
email: "admin@mworld.cloud",
|
|
username: "admin",
|
|
fullName: "Admin User",
|
|
citizen_id: "admin-bypass-id",
|
|
phone: "9999999999",
|
|
role: "admin"
|
|
};
|
|
}
|
|
if (token.startsWith("token_")) {
|
|
const rawId = token.replace(/^token_/, "");
|
|
return {
|
|
email: `${rawId.slice(0, 12)}@local.dev`,
|
|
username: `user_${rawId.slice(0, 6)}`,
|
|
fullName: `User ${rawId.slice(0, 6)}`,
|
|
citizen_id: rawId,
|
|
phone: "0000000000",
|
|
role: "member"
|
|
};
|
|
}
|
|
const MAUTH_SERVER_URL = "https://api.mauth.mworld.cloud";
|
|
try {
|
|
const payload = JSON.stringify({
|
|
origin,
|
|
required_user_data: ["fullName", "email", "phone", "citizen_id"]
|
|
});
|
|
const mAuthUrl = `${MAUTH_SERVER_URL}/mAuthUser/${encodeURIComponent(token)}?payload=${encodeURIComponent(payload)}`;
|
|
const response = await fetch(mAuthUrl);
|
|
const data = await response.json();
|
|
if (data.success && (data.user_data || data.email)) {
|
|
const userData = data.user_data || data;
|
|
if (!userData.email) throw new Error("mAuth returned user data without an email");
|
|
if (!userData.fullName) userData.fullName = userData.email.split("@")[0] || "MWorld User";
|
|
return userData;
|
|
}
|
|
throw new Error(data.msg || "mAuth verification failed");
|
|
} catch (err) {
|
|
console.error("[mAuth Error]:", err.message);
|
|
throw new Error(`Authentication failed: ${err.message}`);
|
|
}
|
|
}
|
|
var authMiddleware = async (c, next) => {
|
|
const authHeader = c.req.header("Authorization");
|
|
const origin = getMWorldOrigin(c);
|
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
return c.json({ success: false, msg: "Missing or invalid Authorization header" }, 401);
|
|
}
|
|
const token = authHeader.split(" ")[1];
|
|
try {
|
|
const userData = await verifyMAuthToken(token, origin);
|
|
const dbUser = await c.env.DB.prepare("SELECT * FROM users WHERE email = ?").bind(userData.email).first();
|
|
if (!dbUser) {
|
|
const baseUsername = userData.email.split("@")[0].replace(/[^a-zA-Z0-9]/g, "");
|
|
const defaultUsername = `${baseUsername}_${Math.random().toString(36).substring(2, 8)}`;
|
|
const newId = crypto.randomUUID();
|
|
await c.env.DB.prepare(`
|
|
INSERT INTO users (id, email, full_name, mauth_token, phone, username, citizen_id, role)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).bind(
|
|
newId,
|
|
userData.email,
|
|
userData.fullName,
|
|
token,
|
|
userData.phone || null,
|
|
defaultUsername,
|
|
userData.citizen_id || null,
|
|
"visitor"
|
|
).run();
|
|
const newUser = await c.env.DB.prepare("SELECT * FROM users WHERE email = ?").bind(userData.email).first();
|
|
c.set("user", { ...newUser, fullName: newUser?.full_name || userData.fullName });
|
|
} else {
|
|
c.set("user", { ...dbUser, fullName: dbUser?.full_name || userData.fullName });
|
|
}
|
|
c.set("token", token);
|
|
await next();
|
|
} catch (err) {
|
|
return c.json({ success: false, msg: err.message }, 401);
|
|
}
|
|
};
|
|
var adminTokenVerificationCache = /* @__PURE__ */ new Map();
|
|
var ADMIN_TOKEN_CACHE_TTL_MS = 6e4;
|
|
function normalizeAdminAgenticToken(value) {
|
|
return String(value || "").trim().replace(/^Bearer[ ]+/i, "").trim();
|
|
}
|
|
async function verifyAdminAgenticToken(token) {
|
|
const cachedUntil = adminTokenVerificationCache.get(token) || 0;
|
|
if (cachedUntil > Date.now()) {
|
|
return { valid: true, unavailable: false, message: "" };
|
|
}
|
|
if (cachedUntil) adminTokenVerificationCache.delete(token);
|
|
let lastMessage = "M-Auth verification service is temporarily unavailable";
|
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, attempt * 250));
|
|
try {
|
|
const verifyRes = await fetch("https://api.mauth.mworld.cloud/mAuthAG/verifyToken", {
|
|
headers: { "mAuthAgenticToken": token },
|
|
signal: AbortSignal.timeout(8e3)
|
|
});
|
|
let verifyData = null;
|
|
try {
|
|
verifyData = await verifyRes.json();
|
|
} catch {
|
|
verifyData = null;
|
|
}
|
|
if (verifyRes.ok && verifyData?.success === true) {
|
|
adminTokenVerificationCache.set(token, Date.now() + ADMIN_TOKEN_CACHE_TTL_MS);
|
|
if (adminTokenVerificationCache.size > 250) {
|
|
const oldest = adminTokenVerificationCache.keys().next();
|
|
if (!oldest.done) adminTokenVerificationCache.delete(oldest.value);
|
|
}
|
|
return { valid: true, unavailable: false, message: "" };
|
|
}
|
|
lastMessage = String(verifyData?.msg || verifyData?.message || "M-Auth verification failed with status " + verifyRes.status);
|
|
if (verifyRes.status === 401 || verifyRes.status === 403 || verifyRes.ok && verifyData?.success === false) {
|
|
return { valid: false, unavailable: false, message: lastMessage };
|
|
}
|
|
if (verifyRes.status !== 429 && verifyRes.status < 500) {
|
|
return { valid: false, unavailable: false, message: lastMessage };
|
|
}
|
|
} catch (err) {
|
|
lastMessage = String(err?.message || lastMessage);
|
|
}
|
|
}
|
|
return { valid: false, unavailable: true, message: lastMessage };
|
|
}
|
|
var adminAuthMiddleware = async (c, next) => {
|
|
const authHeader = c.req.header("Authorization");
|
|
if (!authHeader || !/^Bearer[ ]+/i.test(authHeader)) {
|
|
return c.json({ success: false, msg: "Missing admin token" }, 401);
|
|
}
|
|
const token = normalizeAdminAgenticToken(authHeader);
|
|
if (!token) return c.json({ success: false, msg: "Missing admin token" }, 401);
|
|
const ensureAdminUser = async (email, fullName) => {
|
|
try {
|
|
await c.env.DB.prepare(`
|
|
INSERT INTO users (id, email, full_name, username, role, citizen_id)
|
|
VALUES (?, ?, ?, ?, 'admin', 'admin-bypass-id')
|
|
ON CONFLICT(email) DO NOTHING
|
|
`).bind(crypto.randomUUID(), email, fullName, email.split("@")[0]).run();
|
|
} catch (e) {
|
|
console.error("[Admin Auth] Error upserting admin user:", e);
|
|
}
|
|
return { email, fullName, role: "admin" };
|
|
};
|
|
const verification = await verifyAdminAgenticToken(token);
|
|
if (verification.valid) {
|
|
const adminUser = await ensureAdminUser("agentic_admin@mworld.cloud", "Agentic Admin");
|
|
c.set("user", adminUser);
|
|
await next();
|
|
return;
|
|
}
|
|
if (verification.unavailable) {
|
|
c.header("Retry-After", "2");
|
|
return c.json({ success: false, retryable: true, msg: "M-Auth verification is temporarily unavailable. Please retry." }, 503);
|
|
}
|
|
return c.json({ success: false, msg: verification.message || "Invalid or expired M-Auth agentic token" }, 401);
|
|
};
|
|
|
|
// backend/src/routes/public.ts
|
|
var publicRoutes = new Hono3();
|
|
publicRoutes.get("/media/*", async (c) => {
|
|
const encodedKey = c.req.path.split("/media/")[1] || "";
|
|
const key = decodeURIComponent(encodedKey);
|
|
if (!key) return c.json({ success: false, msg: "Not found" }, 404);
|
|
const object = await c.env.R2.get(key);
|
|
if (!object) return c.json({ success: false, msg: "Not found" }, 404);
|
|
const headers = new Headers();
|
|
object.writeHttpMetadata(headers);
|
|
headers.set("etag", object.httpEtag);
|
|
headers.set("Accept-Ranges", "bytes");
|
|
headers.set("Cache-Control", "public, max-age=31536000");
|
|
const rangeHeader = c.req.header("Range");
|
|
if (rangeHeader) {
|
|
const parts = rangeHeader.replace(/bytes=/, "").split("-");
|
|
const start = parseInt(parts[0], 10);
|
|
const end = parts[1] ? parseInt(parts[1], 10) : object.size - 1;
|
|
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end < start || start >= object.size || end >= object.size) {
|
|
return new Response("Requested Range Not Satisfiable", { status: 416 });
|
|
}
|
|
headers.set("Content-Range", `bytes ${start}-${end}/${object.size}`);
|
|
headers.set("Content-Length", (end - start + 1).toString());
|
|
const rangeObject = await c.env.R2.get(key, { range: { offset: start, length: end - start + 1 } });
|
|
if (!rangeObject) return c.json({ success: false, msg: "Not found" }, 404);
|
|
return new Response(rangeObject.body, { status: 206, headers });
|
|
}
|
|
headers.set("Content-Length", object.size.toString());
|
|
return new Response(object.body, { headers });
|
|
});
|
|
publicRoutes.get("/health", (c) => {
|
|
return c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
});
|
|
publicRoutes.get("/notifications/count", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const count = await c.env.DB.prepare("SELECT COUNT(*) as c FROM notifications WHERE recipient_email = ? AND is_read = 0").bind(user.email || null).first().catch(() => ({ c: 0 }));
|
|
return c.json({ count: count?.c || 0 });
|
|
});
|
|
publicRoutes.post("/user/check-in", async (c) => {
|
|
const body = await c.req.json().catch(() => ({}));
|
|
const token = body.mauth_token || body.user_access_token;
|
|
if (!token) {
|
|
return c.json({ success: false, msg: "Unauthorized: Missing mauth_token" }, 401);
|
|
}
|
|
const origin = getMWorldOrigin(c);
|
|
let userData;
|
|
try {
|
|
userData = await verifyMAuthToken(token, origin);
|
|
} catch (e) {
|
|
return c.json({ success: false, msg: e.message || "Unauthorized" }, 401);
|
|
}
|
|
if (!userData || !userData.email) {
|
|
return c.json({ success: false, msg: "Invalid token or missing email" }, 401);
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
const existing = await c.env.DB.prepare("SELECT * FROM users WHERE email = ?").bind(userData.email).first();
|
|
const role = existing?.role || userData.role || "passenger";
|
|
const username = existing?.username || userData.email.split("@")[0] + "_" + Math.floor(Math.random() * 1e4);
|
|
const id = existing?.id || crypto.randomUUID();
|
|
if (existing) {
|
|
await c.env.DB.prepare(
|
|
`UPDATE users SET full_name = ?, phone = ?, citizen_id = ?, mauth_token = ?, updated_at = ? WHERE email = ?`
|
|
).bind(userData.fullName || userData.full_name || existing.full_name || null, userData.phone || existing.phone || null, userData.citizen_id || existing.citizen_id || null, token, now, userData.email).run();
|
|
} else {
|
|
await c.env.DB.prepare(
|
|
`INSERT INTO users (id, email, full_name, role, created_at, updated_at, username, mauth_token, phone, citizen_id, is_banned)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)`
|
|
).bind(id, userData.email, userData.fullName || userData.full_name || null, role, now, now, username, token, userData.phone || null, userData.citizen_id || null).run();
|
|
}
|
|
const user = await c.env.DB.prepare("SELECT * FROM users WHERE email = ?").bind(userData.email).first();
|
|
return c.json({ success: true, user });
|
|
});
|
|
publicRoutes.get("/companion/safety-profile", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const profile = await c.env.DB.prepare("SELECT * FROM companion_safety_profiles WHERE user_email = ?").bind(user.email || null).first();
|
|
return c.json({ success: true, profile: profile || null });
|
|
});
|
|
publicRoutes.put("/companion/safety-profile", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const body = await c.req.json().catch(() => ({}));
|
|
const persona_label = body.persona_label;
|
|
const safety_consent = body.safety_consent ? 1 : 0;
|
|
if (!persona_label) {
|
|
return c.json({ success: false, msg: "persona_label is required" }, 400);
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
const existing = await c.env.DB.prepare("SELECT * FROM companion_safety_profiles WHERE user_email = ?").bind(user.email || null).first();
|
|
if (existing) {
|
|
await c.env.DB.prepare(
|
|
`UPDATE companion_safety_profiles SET persona_label = ?, safety_consent = ?, updated_at = ? WHERE user_email = ?`
|
|
).bind(persona_label, safety_consent, now, user.email || null).run();
|
|
} else {
|
|
await c.env.DB.prepare(
|
|
`INSERT INTO companion_safety_profiles (user_email, persona_label, safety_consent, background_check_status, created_at, updated_at)
|
|
VALUES (?, ?, ?, 'not_started', ?, ?)`
|
|
).bind(user.email || null, persona_label, safety_consent, now, now).run();
|
|
}
|
|
const profile = await c.env.DB.prepare("SELECT * FROM companion_safety_profiles WHERE user_email = ?").bind(user.email || null).first();
|
|
return c.json({ success: true, profile });
|
|
});
|
|
publicRoutes.post("/companion/safety-profile/background-check", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const body = await c.req.json().catch(() => ({}));
|
|
if (body.consent !== true && body.consent !== 1) {
|
|
return c.json({ success: false, msg: "Explicit consent is required to request background check" }, 400);
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
const existing = await c.env.DB.prepare("SELECT * FROM companion_safety_profiles WHERE user_email = ?").bind(user.email || null).first();
|
|
if (existing) {
|
|
await c.env.DB.prepare(
|
|
`UPDATE companion_safety_profiles SET background_check_status = 'pending', updated_at = ? WHERE user_email = ?`
|
|
).bind(now, user.email || null).run();
|
|
} else {
|
|
await c.env.DB.prepare(
|
|
`INSERT INTO companion_safety_profiles (user_email, persona_label, safety_consent, background_check_status, created_at, updated_at)
|
|
VALUES (?, 'Default Persona', 1, 'pending', ?, ?)`
|
|
).bind(user.email || null, now, now).run();
|
|
}
|
|
return c.json({ success: true, status: "pending" });
|
|
});
|
|
publicRoutes.get("/companion/circles/my", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const circles = await c.env.DB.prepare(
|
|
`SELECT c.* FROM companion_circles c
|
|
JOIN companion_circle_members m ON c.id = m.circle_id
|
|
WHERE m.user_email = ?`
|
|
).bind(user.email || null).all();
|
|
const results = [];
|
|
for (const circle of circles.results || []) {
|
|
const members = await c.env.DB.prepare(
|
|
`SELECT m.*, u.full_name, u.username FROM companion_circle_members m
|
|
LEFT JOIN users u ON m.user_email = u.email
|
|
WHERE m.circle_id = ?`
|
|
).bind(circle.id).all();
|
|
results.push({
|
|
...circle,
|
|
members: members.results || []
|
|
});
|
|
}
|
|
return c.json({ success: true, circles: results });
|
|
});
|
|
publicRoutes.post("/companion/circles/:id/respond", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const { id } = c.req.param();
|
|
const body = await c.req.json().catch(() => ({}));
|
|
const decision = (body.decision || "").toLowerCase();
|
|
if (!["accepted", "declined"].includes(decision)) {
|
|
return c.json({ success: false, msg: "decision must be 'accepted' or 'declined'" }, 400);
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
const member = await c.env.DB.prepare(
|
|
`SELECT * FROM companion_circle_members WHERE circle_id = ? AND user_email = ?`
|
|
).bind(id, user.email || null).first();
|
|
if (!member) {
|
|
return c.json({ success: false, msg: "Circle invitation not found" }, 404);
|
|
}
|
|
await c.env.DB.prepare(
|
|
`UPDATE companion_circle_members SET status = ?, updated_at = ? WHERE circle_id = ? AND user_email = ?`
|
|
).bind(decision, now, id, user.email || null).run();
|
|
const circle = await c.env.DB.prepare("SELECT * FROM companion_circles WHERE id = ?").bind(id).first();
|
|
if (!circle) {
|
|
return c.json({ success: false, msg: "Circle not found" }, 404);
|
|
}
|
|
const members = await c.env.DB.prepare(
|
|
`SELECT m.*, u.full_name, u.username FROM companion_circle_members m
|
|
LEFT JOIN users u ON m.user_email = u.email
|
|
WHERE m.circle_id = ?`
|
|
).bind(id).all();
|
|
return c.json({
|
|
success: true,
|
|
circle: {
|
|
...circle,
|
|
members: members.results || []
|
|
}
|
|
});
|
|
});
|
|
publicRoutes.get("/companion/circles/:id/activities", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const { id } = c.req.param();
|
|
const member = await c.env.DB.prepare(
|
|
`SELECT * FROM companion_circle_members WHERE circle_id = ? AND user_email = ? AND status = 'accepted'`
|
|
).bind(id, user.email || null).first();
|
|
if (!member) {
|
|
return c.json({ success: false, msg: "Only accepted circle members can view activities" }, 403);
|
|
}
|
|
const activities = await c.env.DB.prepare(
|
|
`SELECT * FROM companion_activities WHERE circle_id = ? ORDER BY starts_at ASC`
|
|
).bind(id).all();
|
|
return c.json({ success: true, activities: activities.results || [] });
|
|
});
|
|
publicRoutes.post("/companion/circles/:id/activities", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const { id } = c.req.param();
|
|
const member = await c.env.DB.prepare(
|
|
`SELECT * FROM companion_circle_members WHERE circle_id = ? AND user_email = ? AND status = 'accepted'`
|
|
).bind(id, user.email || null).first();
|
|
if (!member) {
|
|
return c.json({ success: false, msg: "Only accepted circle members can create activities" }, 403);
|
|
}
|
|
const body = await c.req.json().catch(() => ({}));
|
|
if (!body.title || !body.location_label || !body.starts_at) {
|
|
return c.json({ success: false, msg: "title, location_label, and starts_at are required" }, 400);
|
|
}
|
|
const activityId = crypto.randomUUID();
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
await c.env.DB.prepare(
|
|
`INSERT INTO companion_activities (id, circle_id, created_by_email, title, description, location_label, starts_at, max_attendees, status, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'planned', ?, ?)`
|
|
).bind(
|
|
activityId,
|
|
id,
|
|
user.email || null,
|
|
body.title || null,
|
|
body.description || null,
|
|
body.location_label || null,
|
|
body.starts_at || null,
|
|
body.max_attendees ? Number(body.max_attendees || null) : null,
|
|
now,
|
|
now
|
|
).run();
|
|
const activity = await c.env.DB.prepare("SELECT * FROM companion_activities WHERE id = ?").bind(activityId).first();
|
|
return c.json({ success: true, activity });
|
|
});
|
|
publicRoutes.post("/companion/activities/:id/rsvp", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const { id } = c.req.param();
|
|
const body = await c.req.json().catch(() => ({}));
|
|
const status = (body.status || "going").toLowerCase();
|
|
if (!["going", "maybe", "not_going"].includes(status)) {
|
|
return c.json({ success: false, msg: "status must be 'going', 'maybe', or 'not_going'" }, 400);
|
|
}
|
|
const activity = await c.env.DB.prepare("SELECT * FROM companion_activities WHERE id = ?").bind(id).first();
|
|
if (!activity) {
|
|
return c.json({ success: false, msg: "Activity not found" }, 404);
|
|
}
|
|
const member = await c.env.DB.prepare(
|
|
`SELECT * FROM companion_circle_members WHERE circle_id = ? AND user_email = ? AND status = 'accepted'`
|
|
).bind(activity.circle_id, user.email || null).first();
|
|
if (!member) {
|
|
return c.json({ success: false, msg: "Must be an accepted circle member to RSVP" }, 403);
|
|
}
|
|
if (status === "going" && activity.max_attendees) {
|
|
const currentCount = await c.env.DB.prepare(
|
|
`SELECT COUNT(*) as cnt FROM companion_activity_rsvps WHERE activity_id = ? AND status = 'going' AND user_email != ?`
|
|
).bind(id, user.email || null).first();
|
|
if ((currentCount?.cnt || 0) >= activity.max_attendees) {
|
|
return c.json({ success: false, msg: "Activity maximum capacity reached" }, 400);
|
|
}
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
const existing = await c.env.DB.prepare(
|
|
`SELECT * FROM companion_activity_rsvps WHERE activity_id = ? AND user_email = ?`
|
|
).bind(id, user.email || null).first();
|
|
if (existing) {
|
|
await c.env.DB.prepare(
|
|
`UPDATE companion_activity_rsvps SET status = ?, updated_at = ? WHERE activity_id = ? AND user_email = ?`
|
|
).bind(status, now, id, user.email || null).run();
|
|
} else {
|
|
await c.env.DB.prepare(
|
|
`INSERT INTO companion_activity_rsvps (activity_id, user_email, status, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?)`
|
|
).bind(id, user.email || null, status, now, now).run();
|
|
}
|
|
const rsvp = await c.env.DB.prepare(
|
|
`SELECT * FROM companion_activity_rsvps WHERE activity_id = ? AND user_email = ?`
|
|
).bind(id, user.email || null).first();
|
|
return c.json({ success: true, rsvp });
|
|
});
|
|
publicRoutes.get("/driver/profile", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const profile = await c.env.DB.prepare("SELECT * FROM driver_profiles WHERE user_email = ?").bind(user.email || null).first();
|
|
return c.json({ success: true, profile: profile || null });
|
|
});
|
|
publicRoutes.post("/driver/profile", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const body = await c.req.json().catch(() => ({}));
|
|
if (!body.vehicle_make || !body.vehicle_model || !body.license_plate || !body.vehicle_color) {
|
|
return c.json({ success: false, msg: "vehicle_make, vehicle_model, license_plate, and vehicle_color are required" }, 400);
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
const existing = await c.env.DB.prepare("SELECT * FROM driver_profiles WHERE user_email = ?").bind(user.email || null).first();
|
|
const id = existing?.id || crypto.randomUUID();
|
|
if (existing) {
|
|
await c.env.DB.prepare(
|
|
`UPDATE driver_profiles SET vehicle_make = ?, vehicle_model = ?, license_plate = ?, vehicle_color = ?, updated_at = ? WHERE user_email = ?`
|
|
).bind(body.vehicle_make || null, body.vehicle_model || null, body.license_plate || null, body.vehicle_color || null, now, user.email || null).run();
|
|
} else {
|
|
await c.env.DB.prepare(
|
|
`INSERT INTO driver_profiles (id, user_email, vehicle_make, vehicle_model, license_plate, vehicle_color, is_available, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)`
|
|
).bind(id, user.email || null, body.vehicle_make || null, body.vehicle_model || null, body.license_plate || null, body.vehicle_color || null, now, now).run();
|
|
}
|
|
await c.env.DB.prepare(`UPDATE users SET role = 'driver', updated_at = ? WHERE email = ?`).bind(now, user.email || null).run();
|
|
const profile = await c.env.DB.prepare("SELECT * FROM driver_profiles WHERE user_email = ?").bind(user.email || null).first();
|
|
return c.json({ success: true, profile });
|
|
});
|
|
publicRoutes.put("/driver/availability", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const body = await c.req.json().catch(() => ({}));
|
|
const isAvailable = body.is_available ? 1 : 0;
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
await c.env.DB.prepare("UPDATE driver_profiles SET is_available = ?, updated_at = ? WHERE user_email = ?").bind(isAvailable, now, user.email || null).run();
|
|
const profile = await c.env.DB.prepare("SELECT * FROM driver_profiles WHERE user_email = ?").bind(user.email || null).first();
|
|
return c.json({ success: true, profile });
|
|
});
|
|
publicRoutes.get("/rides/nearby", authMiddleware, async (c) => {
|
|
const rides = await c.env.DB.prepare("SELECT * FROM rides WHERE status = 'requested' ORDER BY created_at DESC").all();
|
|
return c.json({ success: true, rides: rides.results || [] });
|
|
});
|
|
publicRoutes.get("/rides/my", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const rides = await c.env.DB.prepare(
|
|
"SELECT * FROM rides WHERE passenger_email = ? OR driver_email = ? ORDER BY created_at DESC"
|
|
).bind(user.email || null, user.email || null).all();
|
|
return c.json({ success: true, rides: rides.results || [] });
|
|
});
|
|
publicRoutes.get("/rides/:id", authMiddleware, async (c) => {
|
|
const { id } = c.req.param();
|
|
const ride = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
if (!ride) {
|
|
return c.json({ success: false, msg: "Ride not found" }, 404);
|
|
}
|
|
return c.json({ success: true, ride });
|
|
});
|
|
publicRoutes.post("/rides", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const body = await c.req.json().catch(() => ({}));
|
|
if (!body.pickup_location || body.pickup_lat === void 0 || body.pickup_lng === void 0 || !body.destination_location || body.destination_lat === void 0 || body.destination_lng === void 0) {
|
|
return c.json({ success: false, msg: "Pickup and destination location details are required" }, 400);
|
|
}
|
|
const id = crypto.randomUUID();
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
await c.env.DB.prepare(
|
|
`INSERT INTO rides (id, passenger_email, pickup_location, pickup_lat, pickup_lng, destination_location, destination_lat, destination_lng, status, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'requested', ?, ?)`
|
|
).bind(
|
|
id,
|
|
user.email || null,
|
|
body.pickup_location || null,
|
|
Number(body.pickup_lat || null),
|
|
Number(body.pickup_lng),
|
|
body.destination_location,
|
|
Number(body.destination_lat),
|
|
Number(body.destination_lng),
|
|
now,
|
|
now
|
|
).run();
|
|
const ride = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
return c.json({ success: true, ride });
|
|
});
|
|
publicRoutes.post("/rides/:id/accept", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const { id } = c.req.param();
|
|
const ride = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
if (!ride) {
|
|
return c.json({ success: false, msg: "Ride not found" }, 404);
|
|
}
|
|
if (ride.status !== "requested") {
|
|
return c.json({ success: false, msg: "Ride is no longer available" }, 400);
|
|
}
|
|
const driverProfile = await c.env.DB.prepare("SELECT * FROM driver_profiles WHERE user_email = ?").bind(user.email || null).first();
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
await c.env.DB.prepare(
|
|
`UPDATE rides SET driver_email = ?, status = 'accepted', driver_lat = ?, driver_lng = ?, updated_at = ? WHERE id = ?`
|
|
).bind(user.email || null, driverProfile?.current_lat ?? ride.pickup_lat, driverProfile?.current_lng ?? ride.pickup_lng, now, id).run();
|
|
const updatedRide = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
return c.json({ success: true, ride: updatedRide });
|
|
});
|
|
publicRoutes.post("/rides/:id/status", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const { id } = c.req.param();
|
|
const body = await c.req.json().catch(() => ({}));
|
|
const newStatus = (body.status || "").toLowerCase();
|
|
if (!["picked_up", "completed"].includes(newStatus)) {
|
|
return c.json({ success: false, msg: "status must be 'picked_up' or 'completed'" }, 400);
|
|
}
|
|
const ride = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
if (!ride) {
|
|
return c.json({ success: false, msg: "Ride not found" }, 404);
|
|
}
|
|
if (ride.driver_email !== user.email) {
|
|
return c.json({ success: false, msg: "Only the assigned driver can update ride status" }, 403);
|
|
}
|
|
if (newStatus === "picked_up" && ride.status !== "accepted") {
|
|
return c.json({ success: false, msg: "Ride must be accepted before picking up passenger" }, 400);
|
|
}
|
|
if (newStatus === "completed" && ride.status !== "picked_up") {
|
|
return c.json({ success: false, msg: "Ride must be picked up before completing" }, 400);
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
await c.env.DB.prepare("UPDATE rides SET status = ?, updated_at = ? WHERE id = ?").bind(newStatus, now, id).run();
|
|
const updatedRide = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
return c.json({ success: true, ride: updatedRide });
|
|
});
|
|
publicRoutes.post("/rides/:id/location", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const { id } = c.req.param();
|
|
const body = await c.req.json().catch(() => ({}));
|
|
if (body.driver_lat === void 0 || body.driver_lng === void 0) {
|
|
return c.json({ success: false, msg: "driver_lat and driver_lng are required" }, 400);
|
|
}
|
|
const ride = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
if (!ride) {
|
|
return c.json({ success: false, msg: "Ride not found" }, 404);
|
|
}
|
|
const lat = Number(body.driver_lat);
|
|
const lng = Number(body.driver_lng);
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
await c.env.DB.prepare("UPDATE rides SET driver_lat = ?, driver_lng = ?, updated_at = ? WHERE id = ?").bind(lat, lng, now, id).run();
|
|
await c.env.DB.prepare("UPDATE driver_profiles SET current_lat = ?, current_lng = ?, updated_at = ? WHERE user_email = ?").bind(lat, lng, now, user.email || null).run();
|
|
const updatedRide = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
return c.json({ success: true, ride: updatedRide });
|
|
});
|
|
publicRoutes.post("/rides/:id/cancel", authMiddleware, async (c) => {
|
|
const { id } = c.req.param();
|
|
const ride = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
if (!ride) {
|
|
return c.json({ success: false, msg: "Ride not found" }, 404);
|
|
}
|
|
if (!["requested", "accepted"].includes(ride.status)) {
|
|
return c.json({ success: false, msg: "Cannot cancel ride once trip has started or completed" }, 400);
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
await c.env.DB.prepare("UPDATE rides SET status = 'cancelled', updated_at = ? WHERE id = ?").bind(now, id).run();
|
|
const updatedRide = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
return c.json({ success: true, ride: updatedRide });
|
|
});
|
|
publicRoutes.post("/rides/:id/rate", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const { id } = c.req.param();
|
|
const body = await c.req.json().catch(() => ({}));
|
|
const rating = Number(body.rating);
|
|
if (!rating || rating < 1 || rating > 5) {
|
|
return c.json({ success: false, msg: "Rating must be an integer between 1 and 5" }, 400);
|
|
}
|
|
const ride = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
if (!ride) {
|
|
return c.json({ success: false, msg: "Ride not found" }, 404);
|
|
}
|
|
if (ride.passenger_email !== user.email) {
|
|
return c.json({ success: false, msg: "Only the passenger can rate this ride" }, 403);
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
await c.env.DB.prepare("UPDATE rides SET rating = ?, updated_at = ? WHERE id = ?").bind(rating, now, id).run();
|
|
const updatedRide = await c.env.DB.prepare("SELECT * FROM rides WHERE id = ?").bind(id).first();
|
|
return c.json({ success: true, ride: updatedRide });
|
|
});
|
|
publicRoutes.post("/orders/create", authMiddleware, async (c) => {
|
|
const user = c.get("user");
|
|
const body = await c.req.json().catch(() => ({}));
|
|
const amount = Number(body.amount);
|
|
if (!amount || amount <= 0) {
|
|
return c.json({ success: false, msg: "Valid payment amount is required" }, 400);
|
|
}
|
|
const orderId = "ord_" + crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
const paymentSessionId = "sess_" + crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
let cfSessionId = paymentSessionId;
|
|
try {
|
|
const cfRes = await fetch("https://sandbox.cashfree.com/pg/orders", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"x-api-version": "2022-09-01",
|
|
"x-client-id": c.env.CASHFREE_APP_ID || "sandbox_app_id",
|
|
"x-client-secret": c.env.CASHFREE_SECRET_KEY || "sandbox_secret"
|
|
},
|
|
body: JSON.stringify({
|
|
order_id: orderId,
|
|
order_amount: amount,
|
|
order_currency: "INR",
|
|
customer_details: {
|
|
customer_id: user.email.replace(/[^a-zA-Z0-9]/g, "_"),
|
|
customer_email: user.email,
|
|
customer_phone: user.phone || "9999999999"
|
|
},
|
|
order_meta: {
|
|
return_url: body.return_url || "https://example.com/payment/status"
|
|
}
|
|
})
|
|
});
|
|
if (cfRes.ok) {
|
|
const cfData = await cfRes.json();
|
|
if (cfData.payment_session_id) {
|
|
cfSessionId = cfData.payment_session_id;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
}
|
|
await c.env.DB.prepare(
|
|
`INSERT INTO orders (order_id, user_email, amount, currency, status, payment_session_id, created_at, updated_at)
|
|
VALUES (?, ?, ?, 'INR', 'PENDING', ?, ?, ?)`
|
|
).bind(orderId, user.email || null, amount, cfSessionId, now, now).run();
|
|
return c.json({
|
|
success: true,
|
|
order_id: orderId,
|
|
payment_session_id: cfSessionId
|
|
});
|
|
});
|
|
publicRoutes.get("/orders/verify", authMiddleware, async (c) => {
|
|
const orderId = c.req.query("order_id");
|
|
if (!orderId) {
|
|
return c.json({ success: false, msg: "order_id query parameter is required" }, 400);
|
|
}
|
|
let order2 = await c.env.DB.prepare("SELECT * FROM orders WHERE order_id = ?").bind(orderId).first();
|
|
if (!order2) {
|
|
return c.json({ success: false, msg: "Order not found" }, 404);
|
|
}
|
|
let status = order2.status;
|
|
try {
|
|
const cfRes = await fetch(`https://sandbox.cashfree.com/pg/orders/${orderId}`, {
|
|
method: "GET",
|
|
headers: {
|
|
"x-api-version": "2022-09-01",
|
|
"x-client-id": c.env.CASHFREE_APP_ID || "sandbox_app_id",
|
|
"x-client-secret": c.env.CASHFREE_SECRET_KEY || "sandbox_secret"
|
|
}
|
|
});
|
|
if (cfRes.ok) {
|
|
const cfData = await cfRes.json();
|
|
if (cfData.order_status) {
|
|
status = cfData.order_status;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
}
|
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
await c.env.DB.prepare("UPDATE orders SET status = ?, updated_at = ? WHERE order_id = ?").bind(status, now, orderId).run();
|
|
order2 = await c.env.DB.prepare("SELECT * FROM orders WHERE order_id = ?").bind(orderId).first();
|
|
return c.json({ success: true, status, order: order2 });
|
|
});
|
|
publicRoutes.get("/geocode", authMiddleware, async (c) => {
|
|
const q = c.req.query("q");
|
|
if (!q) {
|
|
return c.json({ results: [] });
|
|
}
|
|
try {
|
|
const res = await fetch(`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(q)}&format=jsonv2&addressdetails=1&limit=5`, {
|
|
headers: { "User-Agent": "MWorld-Fullstack-Builder/1.0" }
|
|
});
|
|
const results = await res.json();
|
|
return c.json({ results: Array.isArray(results) ? results : [] });
|
|
} catch (e) {
|
|
return c.json({ results: [] });
|
|
}
|
|
});
|
|
publicRoutes.get("/reverse-geocode", authMiddleware, async (c) => {
|
|
const lat = c.req.query("lat");
|
|
const lon = c.req.query("lon");
|
|
if (!lat || !lon) {
|
|
return c.json({ result: null });
|
|
}
|
|
try {
|
|
const res = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${encodeURIComponent(lat)}&lon=${encodeURIComponent(lon)}&format=jsonv2&addressdetails=1`, {
|
|
headers: { "User-Agent": "MWorld-Fullstack-Builder/1.0" }
|
|
});
|
|
const result = await res.json();
|
|
return c.json({ result });
|
|
} catch (e) {
|
|
return c.json({ result: null });
|
|
}
|
|
});
|
|
var public_default = publicRoutes;
|
|
|
|
// backend/src/routes/admin.ts
|
|
var adminRoutes = new Hono3();
|
|
adminRoutes.use("*", adminAuthMiddleware);
|
|
adminRoutes.get("/verify-token", (c) => {
|
|
return c.json({ success: true, valid: true });
|
|
});
|
|
adminRoutes.get("/users", async (c) => {
|
|
const { results } = await c.env.DB.prepare("SELECT * FROM users ORDER BY created_at DESC").all();
|
|
return c.json({ success: true, users: results || [] });
|
|
});
|
|
adminRoutes.get("/stats", async (c) => {
|
|
const totalUsers = await c.env.DB.prepare("SELECT COUNT(*) as cnt FROM users").first();
|
|
const activeDrivers = await c.env.DB.prepare("SELECT COUNT(*) as cnt FROM driver_profiles WHERE is_available = 1").first();
|
|
const totalRides = await c.env.DB.prepare("SELECT COUNT(*) as cnt FROM rides").first();
|
|
const activeRides = await c.env.DB.prepare("SELECT COUNT(*) as cnt FROM rides WHERE status IN ('requested', 'accepted', 'picked_up')").first();
|
|
const completedRides = await c.env.DB.prepare("SELECT COUNT(*) as cnt FROM rides WHERE status = 'completed'").first();
|
|
return c.json({
|
|
success: true,
|
|
stats: {
|
|
total_users: totalUsers?.cnt || 0,
|
|
active_drivers: activeDrivers?.cnt || 0,
|
|
total_rides: totalRides?.cnt || 0,
|
|
active_rides: activeRides?.cnt || 0,
|
|
completed_rides: completedRides?.cnt || 0
|
|
}
|
|
});
|
|
});
|
|
adminRoutes.get("/rides", async (c) => {
|
|
const { results } = await c.env.DB.prepare(`
|
|
SELECT r.*,
|
|
p.full_name as passenger_name, p.phone as passenger_phone,
|
|
d.full_name as driver_name, d.phone as driver_phone
|
|
FROM rides r
|
|
LEFT JOIN users p ON r.passenger_email = p.email
|
|
LEFT JOIN users d ON r.driver_email = d.email
|
|
ORDER BY r.created_at DESC
|
|
`).all();
|
|
return c.json({ success: true, rides: results || [] });
|
|
});
|
|
adminRoutes.post("/upload", async (c) => {
|
|
try {
|
|
const body = await c.req.parseBody();
|
|
const file = body["file"];
|
|
if (!file || !(file instanceof File)) {
|
|
return c.json({ success: false, msg: "No file provided" }, 400);
|
|
}
|
|
const ext = file.name.split(".").pop() || "bin";
|
|
const key = crypto.randomUUID() + "." + ext;
|
|
await c.env.R2.put(key, await file.arrayBuffer(), {
|
|
httpMetadata: { contentType: file.type }
|
|
});
|
|
return c.json({ success: true, url: "/api/media/" + key });
|
|
} catch (error) {
|
|
return c.json({ success: false, msg: error.message }, 500);
|
|
}
|
|
});
|
|
var admin_default = adminRoutes;
|
|
|
|
// backend/src/routes/external.ts
|
|
function verifyAgenticToken(c) {
|
|
const provided = c.req.query("mAgenticAuthToken") || c.req.query("mAuthAgenticToken");
|
|
const expected = c.env.M_AGENTIC_AUTH_TOKEN;
|
|
if (!expected || !provided) return false;
|
|
return decodeURIComponent(provided) === decodeURIComponent(expected);
|
|
}
|
|
function agenticGuard(c) {
|
|
if (!verifyAgenticToken(c)) {
|
|
return c.json({ success: false, error: "Invalid or missing mAgenticAuthToken" }, 401);
|
|
}
|
|
return null;
|
|
}
|
|
function mwResponse(action, logs, data) {
|
|
const now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
return {
|
|
success: true,
|
|
response: [
|
|
{ level: "info", message: `Direct action request to /${action}`, timestamp: now() },
|
|
...logs.map((msg) => ({ level: "info", message: msg, timestamp: now() }))
|
|
],
|
|
data
|
|
};
|
|
}
|
|
async function discoverTables(db) {
|
|
const BLOCKED = /* @__PURE__ */ new Set([
|
|
"users",
|
|
"api_keys",
|
|
"webhooks",
|
|
"webhook_deliveries",
|
|
"sqlite_master",
|
|
"sqlite_sequence",
|
|
"notifications",
|
|
"follows",
|
|
"profile_activity",
|
|
"changelogs",
|
|
"settings",
|
|
"signaling"
|
|
]);
|
|
const res = await db.prepare(
|
|
`SELECT name FROM sqlite_master
|
|
WHERE type='table'
|
|
AND name NOT LIKE 'sqlite_%'
|
|
AND name NOT LIKE '%_fts%'
|
|
ORDER BY name`
|
|
).all();
|
|
return res.results.map((t) => t.name).filter((n) => !BLOCKED.has(n));
|
|
}
|
|
async function isValidEntity(db, entity) {
|
|
if (!/^[a-z_][a-z0-9_]*$/i.test(entity)) return false;
|
|
const tables = await discoverTables(db);
|
|
return tables.includes(entity);
|
|
}
|
|
var agentic = new Hono3();
|
|
agentic.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: ["*"] }));
|
|
agentic.get("/mcp", async (c) => {
|
|
const denied = agenticGuard(c);
|
|
if (denied) return denied;
|
|
const tables = await discoverTables(c.env.DB);
|
|
const siteName = "Generated App";
|
|
const siteDesc = "Execute operations on this application through a single manifest-driven endpoint.";
|
|
const paths = {};
|
|
for (const t of tables) {
|
|
paths[`/list_${t}`] = {
|
|
description: `List all records in the "${t}" entity.`,
|
|
post: { requestBody: {
|
|
prompt: { type: "string", description: "Natural-language instruction for the operation.", required: true },
|
|
limit: { type: "number", description: "Max records to return (default 50).", required: false },
|
|
page: { type: "number", description: "Page number.", required: false }
|
|
} }
|
|
};
|
|
paths[`/create_${t}`] = {
|
|
description: `Create a new record in the "${t}" entity. Pass the record fields in the body.`,
|
|
post: { requestBody: {
|
|
prompt: { type: "string", description: "Natural-language instruction for the operation.", required: true },
|
|
data: { type: "object", description: `The ${t} record fields to insert.`, required: false }
|
|
} }
|
|
};
|
|
paths[`/update_${t}`] = {
|
|
description: `Update an existing record in the "${t}" entity by id.`,
|
|
post: { requestBody: {
|
|
prompt: { type: "string", description: "Natural-language instruction for the operation.", required: true },
|
|
id: { type: "string", description: "The record id to update.", required: true },
|
|
data: { type: "object", description: "Fields to update.", required: false }
|
|
} }
|
|
};
|
|
paths[`/delete_${t}`] = {
|
|
description: `Delete a record from the "${t}" entity by id.`,
|
|
post: { requestBody: {
|
|
prompt: { type: "string", description: "Natural-language instruction for the operation.", required: true },
|
|
id: { type: "string", description: "The record id to delete.", required: true }
|
|
} }
|
|
};
|
|
}
|
|
return c.json({ title: `${siteName} MCP`, description: siteDesc, paths });
|
|
});
|
|
var handleAction = async (c, action) => {
|
|
const denied = agenticGuard(c);
|
|
if (denied) return denied;
|
|
const clean = action.replace(/^\//, "");
|
|
const body = await c.req.json().catch(() => ({}));
|
|
const listM = clean.match(/^list_(.+)$/);
|
|
if (listM) {
|
|
const entity = listM[1];
|
|
if (!await isValidEntity(c.env.DB, entity)) {
|
|
return c.json({ success: false, error: `Unknown entity: ${entity}` }, 404);
|
|
}
|
|
const limit = Math.min(100, Number(body.limit) || 50);
|
|
const page = Math.max(1, Number(body.page) || 1);
|
|
const rows = await c.env.DB.prepare(
|
|
`SELECT * FROM ${entity} ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
).bind(limit, (page - 1) * limit).all().catch(() => ({ results: [] }));
|
|
return c.json(mwResponse(clean, [`Fetched ${rows.results.length} records from "${entity}"`], {
|
|
[entity]: rows.results,
|
|
count: rows.results.length
|
|
}));
|
|
}
|
|
const createM = clean.match(/^create_(.+)$/);
|
|
if (createM) {
|
|
const entity = createM[1];
|
|
if (!await isValidEntity(c.env.DB, entity)) {
|
|
return c.json({ success: false, error: `Unknown entity: ${entity}` }, 404);
|
|
}
|
|
const record = { ...body.data || body };
|
|
delete record.prompt;
|
|
if (!record.id) record.id = crypto.randomUUID();
|
|
if (!record.created_at) record.created_at = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
const cols = Object.keys(record).filter((k) => /^[a-z_][a-z0-9_]*$/i.test(k));
|
|
if (cols.length) {
|
|
const placeholders = cols.map(() => "?").join(", ");
|
|
await c.env.DB.prepare(
|
|
`INSERT INTO ${entity} (${cols.join(", ")}) VALUES (${placeholders})`
|
|
).bind(...cols.map((k) => record[k])).run().catch((e) => {
|
|
throw e;
|
|
});
|
|
}
|
|
return c.json(mwResponse(clean, [`Injected new record into "${entity}"`], record));
|
|
}
|
|
const updateM = clean.match(/^update_(.+)$/);
|
|
if (updateM) {
|
|
const entity = updateM[1];
|
|
if (!await isValidEntity(c.env.DB, entity)) {
|
|
return c.json({ success: false, error: `Unknown entity: ${entity}` }, 404);
|
|
}
|
|
const id = body.id;
|
|
if (!id) return c.json({ success: false, error: "id is required" }, 400);
|
|
const data = { ...body.data || {} };
|
|
delete data.id;
|
|
delete data.prompt;
|
|
const cols = Object.keys(data).filter((k) => /^[a-z_][a-z0-9_]*$/i.test(k));
|
|
if (!cols.length) return c.json({ success: false, error: "No fields to update" }, 400);
|
|
await c.env.DB.prepare(
|
|
`UPDATE ${entity} SET ${cols.map((k) => `${k} = ?`).join(", ")} WHERE id = ?`
|
|
).bind(...cols.map((k) => data[k]), id).run().catch(() => {
|
|
});
|
|
const updated = await c.env.DB.prepare(`SELECT * FROM ${entity} WHERE id = ?`).bind(id).first();
|
|
return c.json(mwResponse(clean, [`Updated record ${id} in "${entity}"`], updated));
|
|
}
|
|
const deleteM = clean.match(/^delete_(.+)$/);
|
|
if (deleteM) {
|
|
const entity = deleteM[1];
|
|
if (!await isValidEntity(c.env.DB, entity)) {
|
|
return c.json({ success: false, error: `Unknown entity: ${entity}` }, 404);
|
|
}
|
|
const id = body.id;
|
|
if (!id) return c.json({ success: false, error: "id is required" }, 400);
|
|
await c.env.DB.prepare(`DELETE FROM ${entity} WHERE id = ?`).bind(id).run().catch(() => {
|
|
});
|
|
return c.json(mwResponse(clean, [`Deleted record ${id} from "${entity}"`], { id, status: "deleted" }));
|
|
}
|
|
return c.json(mwResponse(clean, [`Unknown action "${clean}"`], { status: "noop" }));
|
|
};
|
|
agentic.post("/:action", (c) => handleAction(c, c.req.param("action")));
|
|
agentic.get("/:action", (c) => handleAction(c, c.req.param("action")));
|
|
|
|
// backend/src/index.ts
|
|
var app = new Hono3();
|
|
app.use("*", cors({
|
|
origin: "*",
|
|
allowHeaders: ["Content-Type", "Authorization", "X-MAuth-Token", "x-mworld-origin", "mAuthAgenticToken"],
|
|
allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
|
}));
|
|
app.route("/", agentic);
|
|
app.route("/api", public_default);
|
|
app.route("/api/admin", admin_default);
|
|
var src_default = app;
|
|
|
|
// unified-entry.ts
|
|
var MIME_TYPES = { ".html": "text/html; charset=utf-8", ".js": "application/javascript; charset=utf-8", ".mjs": "application/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".svg": "image/svg+xml", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp", ".gif": "image/gif", ".ico": "image/x-icon", ".woff": "font/woff", ".woff2": "font/woff2", ".ttf": "font/ttf", ".wasm": "application/wasm", ".txt": "text/plain; charset=utf-8" };
|
|
var unified_entry_default = {
|
|
async fetch(request, env, ctx) {
|
|
const url = new URL(request.url);
|
|
const method = request.method;
|
|
if (method === "OPTIONS") {
|
|
return new Response(null, {
|
|
status: 204,
|
|
headers: {
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
}
|
|
});
|
|
}
|
|
if (url.pathname.startsWith("/api")) {
|
|
return src_default.fetch(request, env, ctx);
|
|
}
|
|
const response = await env.ASSETS.fetch(request);
|
|
const currentType = response.headers.get("content-type");
|
|
if (!currentType || currentType === "" || currentType === "application/octet-stream") {
|
|
const pathname = url.pathname;
|
|
const dotIdx = pathname.lastIndexOf(".");
|
|
const ext = dotIdx !== -1 ? pathname.substring(dotIdx).toLowerCase() : "";
|
|
const detected = MIME_TYPES[ext] || (pathname.endsWith("/") || !ext ? "text/html; charset=utf-8" : null);
|
|
if (detected) {
|
|
const newHeaders = new Headers(response.headers);
|
|
newHeaders.set("content-type", detected);
|
|
return new Response(response.body, {
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
headers: newHeaders
|
|
});
|
|
}
|
|
}
|
|
return response;
|
|
}
|
|
};
|
|
export {
|
|
unified_entry_default as default
|
|
};
|