Compare commits
6 Commits
hydrators-
...
64b1b12379
| Author | SHA1 | Date | |
|---|---|---|---|
| 64b1b12379 | |||
| 07bd447fff | |||
| 9aad5fcb5f | |||
| c2e703d642 | |||
| 0b52b92819 | |||
| ad1b2de806 |
1
backend/.npmrc
Normal file
1
backend/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
shamefully-hoist=true
|
||||
@@ -1,14 +1,7 @@
|
||||
import{cli}from'./diachron/cli'
|
||||
import { formatError } from './diachron/errors';
|
||||
// This is a sample file provided by diachron. You are encouraged to modify it.
|
||||
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error(formatError(err));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.error(formatError(reason));
|
||||
});
|
||||
|
||||
import { core } from "./diachron/core";
|
||||
|
||||
@@ -24,8 +17,4 @@ core.logging.log({ source: "logging", text: ["1"] });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app.listen(cli.listen.port, cli.listen.host, () => {
|
||||
console.log(`Listening on ${cli.listen.host}:${cli.listen.port}`);
|
||||
});
|
||||
app.start()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import{contentTypes} from './content-types'
|
||||
import{httpCodes}from'./http-codes'
|
||||
import express, {
|
||||
type Express,
|
||||
type NextFunction,
|
||||
type Request as ExpressRequest,
|
||||
type Response as ExpressResponse,
|
||||
@@ -10,92 +11,29 @@ import express, {
|
||||
import { formatError, formatErrorHtml } from "./errors";
|
||||
import {isRedirect, InternalHandler, AuthenticationRequired,
|
||||
AuthorizationDenied, Call,type Method, type ProcessedRoute,methodParser, type Result, type Route,massageMethod } from "./types";
|
||||
import { runWithContext } from "./context";
|
||||
import { Session } from "./auth";import { request } from "./request";
|
||||
import { match } from "path-to-regexp";
|
||||
|
||||
|
||||
|
||||
import { cli } from "./cli";
|
||||
import{processRoutes}from'./routing'
|
||||
|
||||
type ProcessedRoutes= {[K in Method]: ProcessedRoute[] }
|
||||
const processRoutes=(routes:Route[]) :ProcessedRoutes => {
|
||||
const retval:ProcessedRoutes= {
|
||||
GET: [],
|
||||
POST: [],
|
||||
PUT: [],
|
||||
PATCH: [],
|
||||
DELETE: [],
|
||||
};
|
||||
|
||||
routes.forEach((route: Route, _idx: number, _allRoutes: Route[]) => {
|
||||
// const pattern /*: URLPattern */ = new URLPattern({ pathname: route.path });
|
||||
const matcher = match<Record<string, string>>(route.path);
|
||||
const methodList = route.methods;
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error(formatError(err));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const handler: InternalHandler = async (
|
||||
expressRequest: ExpressRequest,
|
||||
): Promise<Result> => {
|
||||
const method = massageMethod(expressRequest.method);
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.error(formatError(reason));
|
||||
});
|
||||
|
||||
console.log("method", method);
|
||||
|
||||
if (!methodList.includes(method)) {
|
||||
// XXX: Worth asserting this?
|
||||
}
|
||||
|
||||
console.log("request.originalUrl", expressRequest.originalUrl);
|
||||
|
||||
// Authenticate the request
|
||||
const auth = await request.auth.validateRequest(expressRequest);
|
||||
|
||||
const req: Call = {
|
||||
pattern: route.path,
|
||||
path: expressRequest.originalUrl,
|
||||
method,
|
||||
parameters: { one: 1, two: 2 },
|
||||
request: expressRequest,
|
||||
user: auth.user,
|
||||
session: new Session(auth.session, auth.user),
|
||||
};
|
||||
|
||||
try {
|
||||
const retval = await runWithContext({ user: auth.user }, () =>
|
||||
route.handler(req),
|
||||
);
|
||||
return retval;
|
||||
} catch (error) {
|
||||
// Handle authentication errors
|
||||
if (error instanceof AuthenticationRequired) {
|
||||
return {
|
||||
code: httpCodes.clientErrors.Unauthorized,
|
||||
contentType: contentTypes.application.json,
|
||||
result: JSON.stringify({
|
||||
error: "Authentication required",
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (error instanceof AuthorizationDenied) {
|
||||
return {
|
||||
code: httpCodes.clientErrors.Forbidden,
|
||||
contentType: contentTypes.application.json,
|
||||
result: JSON.stringify({ error: "Access denied" }),
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
for (const [_idx, method] of methodList.entries()) {
|
||||
const pr: ProcessedRoute = { matcher, method, handler };
|
||||
|
||||
retval[method].push(pr);
|
||||
}
|
||||
});
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
type MakeAppArgs={routes:Route[],
|
||||
processTitle?: string,
|
||||
processTitle?: string,
|
||||
}
|
||||
|
||||
export interface DiachronApp extends Express {
|
||||
start: () => void
|
||||
}
|
||||
|
||||
const makeApp = ({routes, processTitle}: MakeAppArgs) => {
|
||||
@@ -103,9 +41,9 @@ const makeApp = ({routes, processTitle}: MakeAppArgs) => {
|
||||
process.title = `diachron:${cli.listen.port}`;
|
||||
}
|
||||
|
||||
const processedRoutes = processRoutes(routes)
|
||||
const processedRoutes = processRoutes(routes)
|
||||
|
||||
async function handler(
|
||||
async function handler(
|
||||
req: ExpressRequest,
|
||||
_res: ExpressResponse,
|
||||
): Promise<Result> {
|
||||
@@ -138,8 +76,17 @@ async function handler(
|
||||
return retval;
|
||||
}
|
||||
|
||||
// I don't like going around tsc but this is simple enough that it's probably OK.
|
||||
const app = express() as DiachronApp
|
||||
app.start = function() {
|
||||
this.listen(cli.listen.port, cli.listen.host, () => {
|
||||
console.log(`Listening on ${cli.listen.host}:${cli.listen.port}`);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(express.json())
|
||||
|
||||
|
||||
@@ -10,9 +10,16 @@ const routes: Record<string, Route> = {
|
||||
hello: {
|
||||
path: "/hello",
|
||||
methods: ["GET"],
|
||||
handler: async (_call: Call): Promise<Result> => {
|
||||
handler: async (call: Call): Promise<Result> => {
|
||||
const now = DateTime.now();
|
||||
const c = await render("basic/hello", { now });
|
||||
const args: {now: DateTime,greeting?: string} = {now};
|
||||
|
||||
if (call.path !== '/hello') {
|
||||
const greeting = call. path.replaceAll('/','').replaceAll('-', ' ')
|
||||
args.greeting = greeting;
|
||||
}
|
||||
|
||||
const c = await render("basic/hello", args);
|
||||
|
||||
return html(c);
|
||||
},
|
||||
|
||||
93
backend/diachron/routing.ts
Normal file
93
backend/diachron/routing.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { contentTypes } from "./content-types";
|
||||
import { httpCodes } from "./http-codes";
|
||||
import express, {
|
||||
type NextFunction,
|
||||
type Request as ExpressRequest,
|
||||
type Response as ExpressResponse,
|
||||
} from "express";
|
||||
|
||||
import {isRedirect, InternalHandler, AuthenticationRequired,
|
||||
AuthorizationDenied, Call,type Method, type ProcessedRoute,methodParser, type Result, type Route,massageMethod } from "./types";
|
||||
import { runWithContext } from "./context";
|
||||
import { Session } from "./auth";import { request } from "./request";
|
||||
import { match } from "path-to-regexp";
|
||||
|
||||
type ProcessedRoutes= {[K in Method]: ProcessedRoute[] }
|
||||
const processRoutes=(routes:Route[]) :ProcessedRoutes => {
|
||||
const retval:ProcessedRoutes= {
|
||||
GET: [],
|
||||
POST: [],
|
||||
PUT: [],
|
||||
PATCH: [],
|
||||
DELETE: [],
|
||||
};
|
||||
|
||||
routes.forEach((route: Route, _idx: number, _allRoutes: Route[]) => {
|
||||
// const pattern /*: URLPattern */ = new URLPattern({ pathname: route.path });
|
||||
const matcher = match<Record<string, string>>(route.path);
|
||||
const methodList = route.methods;
|
||||
|
||||
const handler: InternalHandler = async (
|
||||
expressRequest: ExpressRequest,
|
||||
): Promise<Result> => {
|
||||
const method = massageMethod(expressRequest.method);
|
||||
|
||||
console.log("method", method);
|
||||
|
||||
if (!methodList.includes(method)) {
|
||||
// XXX: Worth asserting this?
|
||||
}
|
||||
|
||||
console.log("request.originalUrl", expressRequest.originalUrl);
|
||||
|
||||
// Authenticate the request
|
||||
const auth = await request.auth.validateRequest(expressRequest);
|
||||
|
||||
const req: Call = {
|
||||
pattern: route.path,
|
||||
path: expressRequest.originalUrl,
|
||||
method,
|
||||
parameters: { one: 1, two: 2 },
|
||||
request: expressRequest,
|
||||
user: auth.user,
|
||||
session: new Session(auth.session, auth.user),
|
||||
};
|
||||
|
||||
try {
|
||||
const retval = await runWithContext({ user: auth.user }, () =>
|
||||
route.handler(req),
|
||||
);
|
||||
return retval;
|
||||
} catch (error) {
|
||||
// Handle authentication errors
|
||||
if (error instanceof AuthenticationRequired) {
|
||||
return {
|
||||
code: httpCodes.clientErrors.Unauthorized,
|
||||
contentType: contentTypes.application.json,
|
||||
result: JSON.stringify({
|
||||
error: "Authentication required",
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (error instanceof AuthorizationDenied) {
|
||||
return {
|
||||
code: httpCodes.clientErrors.Forbidden,
|
||||
contentType: contentTypes.application.json,
|
||||
result: JSON.stringify({ error: "Access denied" }),
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
for (const [_idx, method] of methodList.entries()) {
|
||||
const pr: ProcessedRoute = { matcher, method, handler };
|
||||
|
||||
retval[method].push(pr);
|
||||
}
|
||||
});
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
export{processRoutes}
|
||||
@@ -4,12 +4,12 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import type { Request as ExpressRequest } from "express";
|
||||
import { Session } from "./auth/types";
|
||||
import { contentTypes } from "./content-types";
|
||||
import { Session } from "./diachron/auth/types";
|
||||
import { contentTypes } from "./diachron/content-types";
|
||||
import { multiHandler } from "./handlers";
|
||||
import { httpCodes } from "./http-codes";
|
||||
import type { Call } from "./types";
|
||||
import { anonymousUser } from "./user";
|
||||
import { httpCodes } from "./diachron/http-codes";
|
||||
import type { Call } from "./diachron/types";
|
||||
import { anonymousUser } from "./diachron/user";
|
||||
|
||||
// Helper to create a minimal mock Call
|
||||
function createMockCall(overrides: Partial<Call> = {}): Call {
|
||||
@@ -1,7 +1,9 @@
|
||||
import { contentTypes } from "./content-types";
|
||||
import { core } from "./core";
|
||||
import { httpCodes } from "./http-codes";
|
||||
import type { Call, Handler, Result } from "./types";
|
||||
// This is a sample file provided by diachron. You are encouraged to modify it.
|
||||
|
||||
import { contentTypes } from "./diachron/content-types";
|
||||
import { core } from "./diachron/core";
|
||||
import { httpCodes } from "./diachron/http-codes";
|
||||
import type { Call, Handler, Result } from "./diachron/types";
|
||||
|
||||
const multiHandler: Handler = async (call: Call): Promise<Result> => {
|
||||
const code = httpCodes.success.OK;
|
||||
@@ -1,3 +1,5 @@
|
||||
// This is a sample file provided by diachron. You are encouraged to modify it.
|
||||
|
||||
/// <reference lib="dom" />
|
||||
|
||||
import nunjucks from "nunjucks";
|
||||
@@ -6,7 +8,7 @@ import { authRoutes } from "./diachron/auth/routes";
|
||||
import { routes as basicRoutes } from "./diachron/basic/routes";
|
||||
import { contentTypes } from "./diachron/content-types";
|
||||
import { core } from "./diachron/core";
|
||||
import { multiHandler } from "./diachron/handlers";
|
||||
import { multiHandler } from "./handlers";
|
||||
import { httpCodes } from "./diachron/http-codes";
|
||||
import type { Call, Result, Route } from "./diachron/types";
|
||||
|
||||
@@ -27,6 +29,9 @@ const routes: Route[] = [
|
||||
...authRoutes,
|
||||
basicRoutes.home,
|
||||
basicRoutes.hello,
|
||||
{...basicRoutes.hello,
|
||||
path: "/yo-whats-up"
|
||||
},
|
||||
basicRoutes.login,
|
||||
basicRoutes.logout,
|
||||
{
|
||||
@@ -35,7 +40,7 @@ const routes: Route[] = [
|
||||
handler: async (_call: Call): Promise<Result> => {
|
||||
console.log("starting slow request");
|
||||
|
||||
await core.misc.sleep(2);
|
||||
await core.misc.sleep(5000);
|
||||
|
||||
console.log("finishing slow request");
|
||||
const retval = okText("that was slow");
|
||||
@@ -72,7 +77,6 @@ const routes: Route[] = [
|
||||
`;
|
||||
const result = nunjucks.renderString(template, { rrr });
|
||||
|
||||
const _listing = lr(routes).join(", ");
|
||||
return {
|
||||
code,
|
||||
result,
|
||||
|
||||
24
file-list
24
file-list
@@ -1,16 +1,36 @@
|
||||
# please keep this file sorted alphabetically
|
||||
|
||||
.gitignore
|
||||
.go-version
|
||||
backend/.gitignore
|
||||
backend/.npmrc
|
||||
backend/app.ts
|
||||
backend/build.sh
|
||||
backend/check-deps.ts
|
||||
backend/check.sh
|
||||
backend/diachron
|
||||
backend/generated
|
||||
backend/group.ts
|
||||
backend/handlers.spec.ts
|
||||
backend/handlers.ts
|
||||
backend/package.json
|
||||
backend/pnpm-workspace.yaml
|
||||
# express/framework
|
||||
backend/routes.ts
|
||||
backend/run.sh
|
||||
backend/show-config.sh
|
||||
backend/tsconfig.json
|
||||
backend/watch.sh
|
||||
bootstrap.sh
|
||||
cmd
|
||||
file-list
|
||||
develop
|
||||
diachron
|
||||
file-list
|
||||
logger
|
||||
master
|
||||
mgmt
|
||||
sync.sh
|
||||
templates
|
||||
update-cached-repository.sh
|
||||
upgrade.sh
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<head></head>
|
||||
<body>
|
||||
<p>
|
||||
Hello.
|
||||
{{ greeting | default("hello")}}
|
||||
</p>
|
||||
<p>
|
||||
The current time is {{ now }}.
|
||||
|
||||
Reference in New Issue
Block a user