Named routes & basic login framework
This commit is contained in:
@@ -5,6 +5,7 @@ import {Logging} from '../../service/Logging'
|
||||
import {AppClass} from '../../lifecycle/AppClass'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
import {error} from '../response/ErrorResponseFactory'
|
||||
import {HTTPError} from '../HTTPError'
|
||||
|
||||
/**
|
||||
* Interface for fluently registering kernel modules into the kernel.
|
||||
@@ -105,7 +106,8 @@ export class HTTPKernel extends AppClass {
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.logging.error(e)
|
||||
await error(e).status(HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
const status = (e instanceof HTTPError && e.status) ? e.status : HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
await error(e).status(status)
|
||||
.write(request)
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,6 @@ export class ParseIncomingBodyHTTPModule extends HTTPKernelModule {
|
||||
})
|
||||
|
||||
busboy.on('finish', () => {
|
||||
this.logging.debug(`Parsed body input: ${JSON.stringify(request.parsedInput)}`)
|
||||
res()
|
||||
})
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export class PoweredByHeaderInjectionHTTPModule extends HTTPKernelModule {
|
||||
public async apply(request: Request): Promise<Request> {
|
||||
if ( !this.config.get('server.poweredBy.hide', false) ) {
|
||||
request.response.setHeader('X-Powered-By', this.config.get('server.poweredBy.header', 'Extollo'))
|
||||
request.response.setHeader('Server', this.config.get('server.poweredBy.header', 'Extollo'))
|
||||
}
|
||||
|
||||
return request
|
||||
|
||||
@@ -229,6 +229,9 @@ export class Response {
|
||||
this.setHeader('Content-Length', String(this.body?.length ?? 0))
|
||||
}
|
||||
|
||||
this.setHeader('Date', (new Date()).toUTCString())
|
||||
this.setHeader('Permissions-Policy', 'interest-cohort=()')
|
||||
|
||||
await this.write(this.body ?? '')
|
||||
this.end()
|
||||
|
||||
|
||||
31
src/http/response/RedirectResponseFactory.ts
Normal file
31
src/http/response/RedirectResponseFactory.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {HTTPStatus} from '../../util'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
|
||||
/**
|
||||
* Helper function to create a new RedirectResponseFactory to the given destination.
|
||||
* @param destination
|
||||
*/
|
||||
export function redirect(destination: string): RedirectResponseFactory {
|
||||
return new RedirectResponseFactory(destination)
|
||||
}
|
||||
|
||||
/**
|
||||
* Response factory that sends an HTTP redirect to the given destination.
|
||||
*/
|
||||
export class RedirectResponseFactory extends ResponseFactory {
|
||||
protected targetStatus: HTTPStatus = HTTPStatus.MOVED_TEMPORARILY
|
||||
|
||||
constructor(
|
||||
/** THe URL where the client should redirect to. */
|
||||
public readonly destination: string,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public async write(request: Request): Promise<Request> {
|
||||
request = await super.write(request)
|
||||
request.response.setHeader('Location', this.destination)
|
||||
return request
|
||||
}
|
||||
}
|
||||
40
src/http/response/RouteResponseFactory.ts
Normal file
40
src/http/response/RouteResponseFactory.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {HTTPStatus} from '../../util'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
import {Routing} from '../../service/Routing'
|
||||
|
||||
/**
|
||||
* Helper function to create a new RouteResponseFactory to the given destination.
|
||||
* @param nameOrPath
|
||||
*/
|
||||
export function route(nameOrPath: string): RouteResponseFactory {
|
||||
return new RouteResponseFactory(nameOrPath)
|
||||
}
|
||||
|
||||
/**
|
||||
* Response factory that sends an HTTP redirect to the given destination.
|
||||
*/
|
||||
export class RouteResponseFactory extends ResponseFactory {
|
||||
protected targetStatus: HTTPStatus = HTTPStatus.MOVED_TEMPORARILY
|
||||
|
||||
constructor(
|
||||
/** The alias or path of the route to redirect to. */
|
||||
public readonly nameOrPath: string,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public async write(request: Request): Promise<Request> {
|
||||
const routing = <Routing> request.make(Routing)
|
||||
request = await super.write(request)
|
||||
|
||||
try {
|
||||
const routePath = routing.getNamedPath(this.nameOrPath)
|
||||
request.response.setHeader('Location', routePath.toRemote)
|
||||
} catch (e: unknown) {
|
||||
request.response.setHeader('Location', routing.getAppUrl().concat(this.nameOrPath).toRemote)
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import {Request} from '../lifecycle/Request'
|
||||
* Helper function to create a new TemporaryRedirectResponseFactory to the given destination.
|
||||
* @param destination
|
||||
*/
|
||||
export function redirect(destination: string): TemporaryRedirectResponseFactory {
|
||||
export function temporary(destination: string): TemporaryRedirectResponseFactory {
|
||||
return new TemporaryRedirectResponseFactory(destination)
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,9 @@ export class Route extends AppClass {
|
||||
/** Pre-compiled route handler for the main route handler for this route. */
|
||||
protected compiledPostflight?: ResolvedRouteHandler[]
|
||||
|
||||
/** Programmatic aliases of this route. */
|
||||
public aliases: string[] = []
|
||||
|
||||
constructor(
|
||||
/** The HTTP method(s) that this route listens on. */
|
||||
protected method: HTTPMethod | HTTPMethod[],
|
||||
@@ -228,6 +231,15 @@ export class Route extends AppClass {
|
||||
super()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a programmatic name for this route.
|
||||
* @param name
|
||||
*/
|
||||
public alias(name: string): this {
|
||||
this.aliases.push(name)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string-form of the route.
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Collection, HTTPStatus, UniversalPath, universalPath} from '../../util'
|
||||
import {Application} from '../../lifecycle/Application'
|
||||
import {HTTPError} from '../HTTPError'
|
||||
import {view, ViewResponseFactory} from '../response/ViewResponseFactory'
|
||||
import {redirect} from '../response/TemporaryRedirectResponseFactory'
|
||||
import {redirect} from '../response/RedirectResponseFactory'
|
||||
import {file} from '../response/FileResponseFactory'
|
||||
import {RouteHandler} from '../routing/Route'
|
||||
|
||||
@@ -24,6 +24,9 @@ export interface StaticServerOptions {
|
||||
|
||||
/** If specified, files with these extensions will not be served. */
|
||||
excludedExtensions?: string[]
|
||||
|
||||
/** If a file with this name exists in a directory, it will be served. */
|
||||
indexFile?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,6 +159,13 @@ export function staticServer(options: StaticServerOptions = {}): RouteHandler {
|
||||
|
||||
// If the resolved path is a directory, send the directory listing response
|
||||
if ( await filePath.isDirectory() ) {
|
||||
if ( options.indexFile ) {
|
||||
const indexFile = filePath.concat(options.indexFile)
|
||||
if ( await indexFile.exists() ) {
|
||||
return file(indexFile)
|
||||
}
|
||||
}
|
||||
|
||||
if ( !options.directoryListing ) {
|
||||
throw new StaticServerHTTPError(HTTPStatus.NOT_FOUND, 'File not found', {
|
||||
basePath: basePath.toString(),
|
||||
|
||||
@@ -93,4 +93,12 @@ export class MemorySession extends Session {
|
||||
|
||||
this.data[key] = value
|
||||
}
|
||||
|
||||
public forget(key: string): void {
|
||||
if ( !this.data ) {
|
||||
throw new SessionNotLoadedError()
|
||||
}
|
||||
|
||||
delete this.data[key]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,7 @@ export abstract class Session {
|
||||
|
||||
/** Set a value in the session by key. */
|
||||
public abstract set(key: string, value: unknown): void
|
||||
|
||||
/** Remove a key from the session data. */
|
||||
public abstract forget(key: string): void
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user