Expose auth repos in context; create routes commands
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2021-06-17 19:35:31 -05:00
parent 9796a7277e
commit 36b451c32b
14 changed files with 300 additions and 4 deletions

View File

@@ -24,6 +24,10 @@ export class Authentication extends Unit {
this.middleware.registerNamespace('@auth', this.getMiddlewareResolver())
}
/**
* Create the canonical namespace resolver for auth middleware.
* @protected
*/
protected getMiddlewareResolver(): CanonicalResolver<StaticClass<Middleware, Instantiable<Middleware>>> {
return (key: string) => {
return ({

View File

@@ -19,7 +19,7 @@ export abstract class SecurityContext {
constructor(
/** The repository from which to draw users. */
protected readonly repository: AuthenticatableRepository,
public readonly repository: AuthenticatableRepository,
/** The name of this context. */
public readonly name: string,

View File

@@ -14,7 +14,7 @@ export class SessionSecurityContext extends SecurityContext {
constructor(
/** The repository from which to draw users. */
protected readonly repository: AuthenticatableRepository,
public readonly repository: AuthenticatableRepository,
) {
super(repository, 'session')
}

View File

@@ -6,7 +6,7 @@ import {AuthenticatableRepository} from '../types'
import {SessionSecurityContext} from '../contexts/SessionSecurityContext'
import {SecurityContext} from '../SecurityContext'
import {ORMUserRepository} from '../orm/ORMUserRepository'
import {AuthConfig} from '../config'
import {AuthConfig, AuthenticatableRepositories} from '../config'
/**
* Injects a SessionSecurityContext into the request and attempts to
@@ -29,6 +29,7 @@ export class SessionAuthMiddleware extends Middleware {
*/
protected getRepository(): AuthenticatableRepository {
const config: AuthConfig | undefined = this.config.get('auth')
return this.make<AuthenticatableRepository>(config?.repositories?.session ?? ORMUserRepository)
const repo: typeof AuthenticatableRepository = AuthenticatableRepositories[config?.repositories?.session ?? 'orm']
return this.make<AuthenticatableRepository>(repo ?? ORMUserRepository)
}
}

View File

@@ -0,0 +1,71 @@
import {Directive, OptionDefinition} from '../Directive'
import {Inject, Injectable} from '../../di'
import {Routing} from '../../service/Routing'
import Table = require('cli-table')
import {RouteHandler} from '../../http/routing/Route'
@Injectable()
export class RouteDirective extends Directive {
@Inject()
protected readonly routing!: Routing
getDescription(): string {
return 'Get information about a specific route'
}
getKeywords(): string | string[] {
return ['route']
}
getOptions(): OptionDefinition[] {
return [
'{route} | the path of the route',
'--method -m {value} | the HTTP method of the route',
]
}
async handle(): Promise<void> {
const method: string | undefined = this.option('method')
?.toLowerCase()
?.trim()
const route: string = this.option('route')
.toLowerCase()
.trim()
this.routing.getCompiled()
.filter(match => match.getRoute().trim() === route && (!method || match.getMethod() === method))
.tap(matches => {
if ( !matches.length ) {
this.error('No matching routes found. (Use `./ex routes` to list)')
process.exitCode = 1
}
})
.each(match => {
const pre = match.getMiddlewares()
.where('stage', '=', 'pre')
.map<[string, string]>(ware => [ware.stage, this.handlerToString(ware.handler)])
const post = match.getMiddlewares()
.where('stage', '=', 'post')
.map<[string, string]>(ware => [ware.stage, this.handlerToString(ware.handler)])
const maxLen = match.getMiddlewares().max(ware => this.handlerToString(ware.handler).length)
const table = new Table({
head: ['Stage', 'Handler'],
colWidths: [10, Math.max(maxLen, match.getDisplayableHandler().length) + 2],
})
table.push(...pre.toArray())
table.push(['handler', match.getDisplayableHandler()])
table.push(...post.toArray())
this.info(`\nRoute: ${match}\n\n${table}`)
})
}
protected handlerToString(handler: RouteHandler): string {
return typeof handler === 'string' ? handler : '(anonymous function)'
}
}

View File

@@ -0,0 +1,33 @@
import {Directive} from '../Directive'
import {Inject, Injectable} from '../../di'
import {Routing} from '../../service/Routing'
import Table = require('cli-table')
@Injectable()
export class RoutesDirective extends Directive {
@Inject()
protected readonly routing!: Routing
getDescription(): string {
return 'List routes registered in the application'
}
getKeywords(): string | string[] {
return ['routes']
}
async handle(): Promise<void> {
const maxRouteLength = this.routing.getCompiled().max(route => String(route).length)
const maxHandlerLength = this.routing.getCompiled().max(route => route.getDisplayableHandler().length)
const rows = this.routing.getCompiled().map<[string, string]>(route => [String(route), route.getDisplayableHandler()])
const table = new Table({
head: ['Route', 'Handler'],
colWidths: [maxRouteLength + 2, maxHandlerLength + 2],
})
table.push(...rows.toArray())
this.info('\n' + table)
}
}

View File

@@ -34,6 +34,7 @@ export class ShellDirective extends Directive {
async handle(): Promise<void> {
const state: any = {
app: this.app(),
lib: await import('../../index'),
make: (target: DependencyKey, ...parameters: any[]) => this.make(target, ...parameters),
}

View File

@@ -7,6 +7,8 @@ import {Directive} from '../Directive'
import {ShellDirective} from '../directive/ShellDirective'
import {TemplateDirective} from '../directive/TemplateDirective'
import {RunDirective} from '../directive/RunDirective'
import {RoutesDirective} from '../directive/RoutesDirective'
import {RouteDirective} from '../directive/RouteDirective'
/**
* Unit that takes the place of the final unit in the application that handles
@@ -42,6 +44,8 @@ export class CommandLineApplication extends Unit {
this.cli.registerDirective(ShellDirective)
this.cli.registerDirective(TemplateDirective)
this.cli.registerDirective(RunDirective)
this.cli.registerDirective(RoutesDirective)
this.cli.registerDirective(RouteDirective)
const argv = process.argv.slice(2)
const match = this.cli.getDirectives()

View File

@@ -224,6 +224,34 @@ export class Route extends AppClass {
super()
}
/**
* Get the string-form of the route.
*/
public getRoute(): string {
return this.route
}
/**
* Get the string-form method of the route.
*/
public getMethod(): HTTPMethod | HTTPMethod[] {
return this.method
}
/**
* Get collection of applied middlewares.
*/
public getMiddlewares(): Collection<{ stage: 'pre' | 'post', handler: RouteHandler }> {
return this.middlewares.clone()
}
/**
* Get the string-form of the route handler.
*/
public getDisplayableHandler(): string {
return typeof this.handler === 'string' ? this.handler : '(anonymous function)'
}
/**
* Returns true if this route matches the given HTTP verb and request path.
* @param method

14
src/orm/schema/Schema.ts Normal file
View File

@@ -0,0 +1,14 @@
import {Connection} from '../connection/Connection'
import {Awaitable} from '../../util'
export abstract class Schema {
constructor(
protected readonly connection: Connection,
) { }
public abstract hasTable(name: string): Awaitable<boolean>
public abstract hasColumn(table: string, name: string): Awaitable<boolean>
public abstract hasColumns(table: string, name: string[]): Awaitable<boolean>
}

View File

@@ -0,0 +1,109 @@
import {Pipe} from '../../util'
export abstract class SchemaBuilderBase {
protected shouldDrop: 'yes'|'no'|'exists' = 'no'
protected shouldRenameTo?: string
constructor(
protected readonly name: string,
) { }
public drop(): this {
this.shouldDrop = 'yes'
return this
}
public dropIfExists(): this {
this.shouldDrop = 'exists'
return this
}
public rename(to: string): this {
this.shouldRenameTo = to
return this
}
pipe(): Pipe<this> {
return Pipe.wrap<this>(this)
}
}
export class ColumnBuilder extends SchemaBuilderBase {
}
export class IndexBuilder extends SchemaBuilderBase {
protected fields: Set<string> = new Set<string>()
protected removedFields: Set<string> = new Set<string>()
protected shouldBeUnique = false
protected shouldBePrimary = false
protected field(name: string): this {
this.fields.add(name)
return this
}
protected removeField(name: string): this {
this.removedFields.add(name)
this.fields.delete(name)
return this
}
primary(): this {
this.shouldBePrimary = true
return this
}
unique(): this {
this.shouldBeUnique = true
return this
}
}
export class TableBuilder extends SchemaBuilderBase {
protected columns: {[key: string]: ColumnBuilder} = {}
protected indexes: {[key: string]: IndexBuilder} = {}
public dropColumn(name: string): this {
this.column(name).drop()
return this
}
public renameColumn(from: string, to: string): this {
this.column(from).rename(to)
return this
}
public dropIndex(name: string): this {
this.index(name).drop()
return this
}
public renameIndex(from: string, to: string): this {
this.index(from).rename(to)
return this
}
public column(name: string) {
if ( !this.columns[name] ) {
this.columns[name] = new ColumnBuilder(name)
}
return this.columns[name]
}
public index(name: string) {
if ( !this.indexes[name] ) {
this.indexes[name] = new IndexBuilder(name)
}
return this.indexes[name]
}
}

View File

@@ -56,4 +56,11 @@ export class Routing extends Unit {
public get path(): UniversalPath {
return this.app().appPath('http', 'routes')
}
/**
* Get the collection of compiled routes.
*/
public getCompiled(): Collection<Route> {
return this.compiledRoutes
}
}