Compare commits
6 Commits
cf04ecc78a
...
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'
|
// This is a sample file provided by diachron. You are encouraged to modify it.
|
||||||
import { formatError } from './diachron/errors';
|
|
||||||
|
|
||||||
process.on('uncaughtException', (err) => {
|
|
||||||
console.error(formatError(err));
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason) => {
|
|
||||||
console.error(formatError(reason));
|
|
||||||
});
|
|
||||||
|
|
||||||
import { core } from "./diachron/core";
|
import { core } from "./diachron/core";
|
||||||
|
|
||||||
@@ -24,8 +17,4 @@ core.logging.log({ source: "logging", text: ["1"] });
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.start()
|
||||||
|
|
||||||
app.listen(cli.listen.port, cli.listen.host, () => {
|
|
||||||
console.log(`Listening on ${cli.listen.host}:${cli.listen.port}`);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import{contentTypes} from './content-types'
|
import{contentTypes} from './content-types'
|
||||||
import{httpCodes}from'./http-codes'
|
import{httpCodes}from'./http-codes'
|
||||||
import express, {
|
import express, {
|
||||||
|
type Express,
|
||||||
type NextFunction,
|
type NextFunction,
|
||||||
type Request as ExpressRequest,
|
type Request as ExpressRequest,
|
||||||
type Response as ExpressResponse,
|
type Response as ExpressResponse,
|
||||||
@@ -10,94 +11,31 @@ import express, {
|
|||||||
import { formatError, formatErrorHtml } from "./errors";
|
import { formatError, formatErrorHtml } from "./errors";
|
||||||
import {isRedirect, InternalHandler, AuthenticationRequired,
|
import {isRedirect, InternalHandler, AuthenticationRequired,
|
||||||
AuthorizationDenied, Call,type Method, type ProcessedRoute,methodParser, type Result, type Route,massageMethod } from "./types";
|
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 { 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[]) => {
|
process.on('uncaughtException', (err) => {
|
||||||
// const pattern /*: URLPattern */ = new URLPattern({ pathname: route.path });
|
console.error(formatError(err));
|
||||||
const matcher = match<Record<string, string>>(route.path);
|
process.exit(1);
|
||||||
const methodList = route.methods;
|
});
|
||||||
|
|
||||||
const handler: InternalHandler = async (
|
process.on('unhandledRejection', (reason) => {
|
||||||
expressRequest: ExpressRequest,
|
console.error(formatError(reason));
|
||||||
): 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
type MakeAppArgs={routes:Route[],
|
type MakeAppArgs={routes:Route[],
|
||||||
processTitle?: string,
|
processTitle?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DiachronApp extends Express {
|
||||||
|
start: () => void
|
||||||
|
}
|
||||||
|
|
||||||
const makeApp = ({routes, processTitle}: MakeAppArgs) => {
|
const makeApp = ({routes, processTitle}: MakeAppArgs) => {
|
||||||
if (process.title) {
|
if (process.title) {
|
||||||
process.title = `diachron:${cli.listen.port}`;
|
process.title = `diachron:${cli.listen.port}`;
|
||||||
@@ -138,8 +76,17 @@ async function handler(
|
|||||||
return retval;
|
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())
|
app.use(express.json())
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,16 @@ const routes: Record<string, Route> = {
|
|||||||
hello: {
|
hello: {
|
||||||
path: "/hello",
|
path: "/hello",
|
||||||
methods: ["GET"],
|
methods: ["GET"],
|
||||||
handler: async (_call: Call): Promise<Result> => {
|
handler: async (call: Call): Promise<Result> => {
|
||||||
const now = DateTime.now();
|
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);
|
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 assert from "node:assert/strict";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
import type { Request as ExpressRequest } from "express";
|
import type { Request as ExpressRequest } from "express";
|
||||||
import { Session } from "./auth/types";
|
import { Session } from "./diachron/auth/types";
|
||||||
import { contentTypes } from "./content-types";
|
import { contentTypes } from "./diachron/content-types";
|
||||||
import { multiHandler } from "./handlers";
|
import { multiHandler } from "./handlers";
|
||||||
import { httpCodes } from "./http-codes";
|
import { httpCodes } from "./diachron/http-codes";
|
||||||
import type { Call } from "./types";
|
import type { Call } from "./diachron/types";
|
||||||
import { anonymousUser } from "./user";
|
import { anonymousUser } from "./diachron/user";
|
||||||
|
|
||||||
// Helper to create a minimal mock Call
|
// Helper to create a minimal mock Call
|
||||||
function createMockCall(overrides: Partial<Call> = {}): Call {
|
function createMockCall(overrides: Partial<Call> = {}): Call {
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { contentTypes } from "./content-types";
|
// This is a sample file provided by diachron. You are encouraged to modify it.
|
||||||
import { core } from "./core";
|
|
||||||
import { httpCodes } from "./http-codes";
|
import { contentTypes } from "./diachron/content-types";
|
||||||
import type { Call, Handler, Result } from "./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 multiHandler: Handler = async (call: Call): Promise<Result> => {
|
||||||
const code = httpCodes.success.OK;
|
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" />
|
/// <reference lib="dom" />
|
||||||
|
|
||||||
import nunjucks from "nunjucks";
|
import nunjucks from "nunjucks";
|
||||||
@@ -6,7 +8,7 @@ import { authRoutes } from "./diachron/auth/routes";
|
|||||||
import { routes as basicRoutes } from "./diachron/basic/routes";
|
import { routes as basicRoutes } from "./diachron/basic/routes";
|
||||||
import { contentTypes } from "./diachron/content-types";
|
import { contentTypes } from "./diachron/content-types";
|
||||||
import { core } from "./diachron/core";
|
import { core } from "./diachron/core";
|
||||||
import { multiHandler } from "./diachron/handlers";
|
import { multiHandler } from "./handlers";
|
||||||
import { httpCodes } from "./diachron/http-codes";
|
import { httpCodes } from "./diachron/http-codes";
|
||||||
import type { Call, Result, Route } from "./diachron/types";
|
import type { Call, Result, Route } from "./diachron/types";
|
||||||
|
|
||||||
@@ -27,6 +29,9 @@ const routes: Route[] = [
|
|||||||
...authRoutes,
|
...authRoutes,
|
||||||
basicRoutes.home,
|
basicRoutes.home,
|
||||||
basicRoutes.hello,
|
basicRoutes.hello,
|
||||||
|
{...basicRoutes.hello,
|
||||||
|
path: "/yo-whats-up"
|
||||||
|
},
|
||||||
basicRoutes.login,
|
basicRoutes.login,
|
||||||
basicRoutes.logout,
|
basicRoutes.logout,
|
||||||
{
|
{
|
||||||
@@ -35,7 +40,7 @@ const routes: Route[] = [
|
|||||||
handler: async (_call: Call): Promise<Result> => {
|
handler: async (_call: Call): Promise<Result> => {
|
||||||
console.log("starting slow request");
|
console.log("starting slow request");
|
||||||
|
|
||||||
await core.misc.sleep(2);
|
await core.misc.sleep(5000);
|
||||||
|
|
||||||
console.log("finishing slow request");
|
console.log("finishing slow request");
|
||||||
const retval = okText("that was slow");
|
const retval = okText("that was slow");
|
||||||
@@ -72,7 +77,6 @@ const routes: Route[] = [
|
|||||||
`;
|
`;
|
||||||
const result = nunjucks.renderString(template, { rrr });
|
const result = nunjucks.renderString(template, { rrr });
|
||||||
|
|
||||||
const _listing = lr(routes).join(", ");
|
|
||||||
return {
|
return {
|
||||||
code,
|
code,
|
||||||
result,
|
result,
|
||||||
|
|||||||
24
file-list
24
file-list
@@ -1,16 +1,36 @@
|
|||||||
# please keep this file sorted alphabetically
|
# 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/diachron
|
||||||
|
backend/generated
|
||||||
|
backend/group.ts
|
||||||
|
backend/handlers.spec.ts
|
||||||
|
backend/handlers.ts
|
||||||
backend/package.json
|
backend/package.json
|
||||||
backend/pnpm-workspace.yaml
|
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
|
cmd
|
||||||
file-list
|
|
||||||
develop
|
develop
|
||||||
diachron
|
diachron
|
||||||
|
file-list
|
||||||
logger
|
logger
|
||||||
master
|
master
|
||||||
mgmt
|
mgmt
|
||||||
sync.sh
|
sync.sh
|
||||||
templates
|
templates
|
||||||
|
update-cached-repository.sh
|
||||||
upgrade.sh
|
upgrade.sh
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<head></head>
|
<head></head>
|
||||||
<body>
|
<body>
|
||||||
<p>
|
<p>
|
||||||
Hello.
|
{{ greeting | default("hello")}}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The current time is {{ now }}.
|
The current time is {{ now }}.
|
||||||
|
|||||||
Reference in New Issue
Block a user