- Start support for auto-generated routes using UniversalPath
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Start support for custom view engine props & functions - Start login template and namespace
This commit is contained in:
parent
faa8a31102
commit
cf6d14abca
@ -28,7 +28,7 @@ export class MountActivatedRouteHTTPModule extends HTTPKernelModule {
|
|||||||
const route = this.routing.match(request.method, request.path)
|
const route = this.routing.match(request.method, request.path)
|
||||||
if ( route ) {
|
if ( route ) {
|
||||||
this.logging.verbose(`Mounting activated route: ${request.path} -> ${route}`)
|
this.logging.verbose(`Mounting activated route: ${request.path} -> ${route}`)
|
||||||
const activated = new ActivatedRoute(route, request.path)
|
const activated = <ActivatedRoute> request.make(ActivatedRoute, route, request.path)
|
||||||
request.registerSingletonInstance<ActivatedRoute>(ActivatedRoute, activated)
|
request.registerSingletonInstance<ActivatedRoute>(ActivatedRoute, activated)
|
||||||
} else {
|
} else {
|
||||||
this.logging.debug(`No matching route found for: ${request.method} -> ${request.path}`)
|
this.logging.debug(`No matching route found for: ${request.method} -> ${request.path}`)
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import {ErrorWithContext} from '../../util'
|
import {ErrorWithContext} from '../../util'
|
||||||
import {ResolvedRouteHandler, Route} from './Route'
|
import {ResolvedRouteHandler, Route} from './Route'
|
||||||
|
import {Injectable} from '../../di'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing a resolved route that a request is mounted to.
|
* Class representing a resolved route that a request is mounted to.
|
||||||
*/
|
*/
|
||||||
|
@Injectable()
|
||||||
export class ActivatedRoute {
|
export class ActivatedRoute {
|
||||||
/**
|
/**
|
||||||
* The parsed params from the route definition.
|
* The parsed params from the route definition.
|
||||||
|
12
src/resources/views/auth/form.pug
Normal file
12
src/resources/views/auth/form.pug
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
extends ./theme
|
||||||
|
|
||||||
|
block content
|
||||||
|
h3.login-heading.mb-4
|
||||||
|
block heading
|
||||||
|
|
||||||
|
if errors
|
||||||
|
each error in errors
|
||||||
|
p.form-error-message #{error}
|
||||||
|
|
||||||
|
form(method='post' enctype='multipart/form-data')
|
||||||
|
block form
|
@ -1,3 +1,22 @@
|
|||||||
html
|
extends ./form
|
||||||
body
|
|
||||||
h1 Extollo Login Page
|
block head
|
||||||
|
title Login | #{config('app.name', 'Extollo')}
|
||||||
|
|
||||||
|
block heading
|
||||||
|
| Login to Continue
|
||||||
|
|
||||||
|
block form
|
||||||
|
.form-label-group
|
||||||
|
input#inputUsername.form-control(type='text' name='username' value=(form_data ? form_data.username : '') required placeholder='Username' autofocus)
|
||||||
|
label(for='inputUsername') Username
|
||||||
|
.form-label-group
|
||||||
|
input#inputPassword.form-control(type='password' name='password' required placeholder='Password')
|
||||||
|
label(for='inputPassword') Password
|
||||||
|
button.btn.btn-lg.btn-primary.btn-block.btn-login.text-uppercase.font-weight-bold.mb-2.form-submit-button(type='submit') Login
|
||||||
|
|
||||||
|
.text-center
|
||||||
|
span.small Need an account?
|
||||||
|
a(href='./register') Register here.
|
||||||
|
// .text-center
|
||||||
|
span.small(style="color: #999999;") Provider: #{provider_name}
|
||||||
|
22
src/resources/views/auth/theme.pug
Normal file
22
src/resources/views/auth/theme.pug
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
html
|
||||||
|
head
|
||||||
|
meta(name='viewport' content='width=device-width initial-scale=1')
|
||||||
|
|
||||||
|
block head
|
||||||
|
|
||||||
|
block styles
|
||||||
|
link(rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css')
|
||||||
|
link(rel='stylesheet' href=vendor('@extollo', 'auth/theme.css'))
|
||||||
|
body
|
||||||
|
.container-fluid
|
||||||
|
.row.no-gutter
|
||||||
|
.d-none.d-md-flex.col-md-6.col-lg-8.bg-image
|
||||||
|
.col-md-6.col-lg-4
|
||||||
|
.login.d-flex.align-items-center.py-5
|
||||||
|
.container
|
||||||
|
.row
|
||||||
|
.col-md-9.col-lg-8.mx-auto
|
||||||
|
block content
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
script(src='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css')
|
@ -1,5 +1,5 @@
|
|||||||
import {Singleton, Inject} from '../di'
|
import {Singleton, Inject} from '../di'
|
||||||
import {UniversalPath, Collection} from '../util'
|
import {UniversalPath, Collection, Pipe, universalPath} from '../util'
|
||||||
import {Unit} from '../lifecycle/Unit'
|
import {Unit} from '../lifecycle/Unit'
|
||||||
import {Logging} from './Logging'
|
import {Logging} from './Logging'
|
||||||
import {Route} from '../http/routing/Route'
|
import {Route} from '../http/routing/Route'
|
||||||
@ -7,6 +7,7 @@ import {HTTPMethod} from '../http/lifecycle/Request'
|
|||||||
import {ViewEngineFactory} from '../views/ViewEngineFactory'
|
import {ViewEngineFactory} from '../views/ViewEngineFactory'
|
||||||
import {ViewEngine} from '../views/ViewEngine'
|
import {ViewEngine} from '../views/ViewEngine'
|
||||||
import {lib} from '../lib'
|
import {lib} from '../lib'
|
||||||
|
import {Config} from './Config'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application unit that loads the various route files from `app/http/routes` and pre-compiles the route handlers.
|
* Application unit that loads the various route files from `app/http/routes` and pre-compiles the route handlers.
|
||||||
@ -16,6 +17,9 @@ export class Routing extends Unit {
|
|||||||
@Inject()
|
@Inject()
|
||||||
protected readonly logging!: Logging
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
protected readonly config!: Config
|
||||||
|
|
||||||
protected compiledRoutes: Collection<Route> = new Collection<Route>()
|
protected compiledRoutes: Collection<Route> = new Collection<Route>()
|
||||||
|
|
||||||
public async up(): Promise<void> {
|
public async up(): Promise<void> {
|
||||||
@ -68,4 +72,47 @@ export class Routing extends Unit {
|
|||||||
public getCompiled(): Collection<Route> {
|
public getCompiled(): Collection<Route> {
|
||||||
return this.compiledRoutes
|
return this.compiledRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getAssetPath(...parts: string[]): UniversalPath {
|
||||||
|
return this.getAssetBase().concat(...parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAssetBase(): UniversalPath {
|
||||||
|
return this.getAppUrl().concat(this.config.get('server.builtIns.assets.prefix', '/assets'))
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVendorPath(namespace: string, ...parts: string[]): UniversalPath {
|
||||||
|
return this.getVendorBase().concat(encodeURIComponent(namespace), ...parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVendorBase(): UniversalPath {
|
||||||
|
return this.getAppUrl().concat(this.config.get('server.builtIns.vendor.prefix', '/vendor'))
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAppUrl(): UniversalPath {
|
||||||
|
const rawHost = String(this.config.get('server.url', 'http://localhost')).toLowerCase()
|
||||||
|
const isSSL = rawHost.startsWith('https://')
|
||||||
|
const port = this.config.get('server.port', 8000)
|
||||||
|
|
||||||
|
return Pipe.wrap<string>(rawHost)
|
||||||
|
.unless(
|
||||||
|
host => host.startsWith('http://') || host.startsWith('https'),
|
||||||
|
host => `http://${host}`,
|
||||||
|
)
|
||||||
|
.when(
|
||||||
|
host => {
|
||||||
|
const hasPort = host.split(':').length > 2
|
||||||
|
const defaultRaw = !isSSL && port === 80
|
||||||
|
const defaultSSL = isSSL && port === 443
|
||||||
|
return !hasPort && !defaultRaw && !defaultSSL
|
||||||
|
},
|
||||||
|
host => {
|
||||||
|
const parts = host.split('/')
|
||||||
|
parts[2] += `:${port}`
|
||||||
|
return parts.join('/')
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.tap<UniversalPath>(host => universalPath(host))
|
||||||
|
.get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,11 @@ export type PipeOperator<T, T2> = (subject: T) => T2
|
|||||||
*/
|
*/
|
||||||
export type ReflexivePipeOperator<T> = (subject: T) => T
|
export type ReflexivePipeOperator<T> = (subject: T) => T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A condition or condition-resolving function for pipe methods.
|
||||||
|
*/
|
||||||
|
export type PipeCondition<T> = boolean | ((subject: T) => boolean)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for writing chained/conditional operations in a data-flow manner.
|
* A class for writing chained/conditional operations in a data-flow manner.
|
||||||
*
|
*
|
||||||
@ -79,8 +84,8 @@ export class Pipe<T> {
|
|||||||
* @param check
|
* @param check
|
||||||
* @param op
|
* @param op
|
||||||
*/
|
*/
|
||||||
when(check: boolean, op: ReflexivePipeOperator<T>): Pipe<T> {
|
when(check: PipeCondition<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||||
if ( check ) {
|
if ( (typeof check === 'function' && check(this.subject)) || check ) {
|
||||||
return Pipe.wrap(op(this.subject))
|
return Pipe.wrap(op(this.subject))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,8 +99,12 @@ export class Pipe<T> {
|
|||||||
* @param check
|
* @param check
|
||||||
* @param op
|
* @param op
|
||||||
*/
|
*/
|
||||||
unless(check: boolean, op: ReflexivePipeOperator<T>): Pipe<T> {
|
unless(check: PipeCondition<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||||
return this.when(!check, op)
|
if ( (typeof check === 'function' && check(this.subject)) || check ) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pipe.wrap(op(this.subject))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,7 +112,7 @@ export class Pipe<T> {
|
|||||||
* @param check
|
* @param check
|
||||||
* @param op
|
* @param op
|
||||||
*/
|
*/
|
||||||
whenNot(check: boolean, op: ReflexivePipeOperator<T>): Pipe<T> {
|
whenNot(check: PipeCondition<T>, op: ReflexivePipeOperator<T>): Pipe<T> {
|
||||||
return this.unless(check, op)
|
return this.unless(check, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,14 +17,20 @@ export class PugViewEngine extends ViewEngine {
|
|||||||
public renderByName(templateName: string, locals: { [p: string]: any }): string | Promise<string> {
|
public renderByName(templateName: string, locals: { [p: string]: any }): string | Promise<string> {
|
||||||
let compiled = this.compileCache[templateName]
|
let compiled = this.compileCache[templateName]
|
||||||
if ( compiled ) {
|
if ( compiled ) {
|
||||||
return compiled(locals)
|
return compiled({
|
||||||
|
...this.getGlobals(),
|
||||||
|
...locals,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = this.resolveName(templateName)
|
const filePath = this.resolveName(templateName)
|
||||||
compiled = pug.compileFile(filePath.toLocal, this.getOptions(templateName))
|
compiled = pug.compileFile(filePath.toLocal, this.getOptions(templateName))
|
||||||
|
|
||||||
this.compileCache[templateName] = compiled
|
this.compileCache[templateName] = compiled
|
||||||
return compiled(locals)
|
return compiled({
|
||||||
|
...this.getGlobals(),
|
||||||
|
...locals,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,4 +45,8 @@ export class PugViewEngine extends ViewEngine {
|
|||||||
globals: [],
|
globals: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFileExtension(): string {
|
||||||
|
return '.pug'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import {AppClass} from '../lifecycle/AppClass'
|
|||||||
import {Config} from '../service/Config'
|
import {Config} from '../service/Config'
|
||||||
import {Container} from '../di'
|
import {Container} from '../di'
|
||||||
import {ErrorWithContext, UniversalPath} from '../util'
|
import {ErrorWithContext, UniversalPath} from '../util'
|
||||||
|
import {Routing} from '../service/Routing'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for rendering views via different view engines.
|
* Abstract base class for rendering views via different view engines.
|
||||||
@ -9,6 +10,8 @@ import {ErrorWithContext, UniversalPath} from '../util'
|
|||||||
export abstract class ViewEngine extends AppClass {
|
export abstract class ViewEngine extends AppClass {
|
||||||
protected readonly config: Config
|
protected readonly config: Config
|
||||||
|
|
||||||
|
protected readonly routing: Routing
|
||||||
|
|
||||||
protected readonly debug: boolean
|
protected readonly debug: boolean
|
||||||
|
|
||||||
protected readonly namespaces: {[key: string]: UniversalPath} = {}
|
protected readonly namespaces: {[key: string]: UniversalPath} = {}
|
||||||
@ -16,6 +19,7 @@ export abstract class ViewEngine extends AppClass {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.config = Container.getContainer().make(Config)
|
this.config = Container.getContainer().make(Config)
|
||||||
|
this.routing = Container.getContainer().make(Routing)
|
||||||
this.debug = (this.config.get('server.mode', 'production') === 'development'
|
this.debug = (this.config.get('server.mode', 'production') === 'development'
|
||||||
|| this.config.get('server.debug', false))
|
|| this.config.get('server.debug', false))
|
||||||
}
|
}
|
||||||
@ -41,6 +45,30 @@ export abstract class ViewEngine extends AppClass {
|
|||||||
*/
|
*/
|
||||||
public abstract renderByName(templateName: string, locals: {[key: string]: any}): string | Promise<string>
|
public abstract renderByName(templateName: string, locals: {[key: string]: any}): string | Promise<string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file extension of template files of this engine.
|
||||||
|
* @example `.pug`
|
||||||
|
*/
|
||||||
|
public abstract getFileExtension(): string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the global variables that should be passed to every view rendered.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected getGlobals(): {[key: string]: any} {
|
||||||
|
return {
|
||||||
|
app: this.app(),
|
||||||
|
config: (key: string, fallback?: any) => this.config.get(key, fallback),
|
||||||
|
asset: (...parts: string[]) => this.routing.getAssetPath(...parts).toRemote,
|
||||||
|
vendor: (namespace: string, ...parts: string[]) => this.routing.getVendorPath(namespace, ...parts).toRemote,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a path as a root for rendering views prefixed with the given namespace.
|
||||||
|
* @param namespace
|
||||||
|
* @param basePath
|
||||||
|
*/
|
||||||
public registerNamespace(namespace: string, basePath: UniversalPath): this {
|
public registerNamespace(namespace: string, basePath: UniversalPath): this {
|
||||||
if ( namespace.startsWith('@') ) {
|
if ( namespace.startsWith('@') ) {
|
||||||
namespace = namespace.substr(1)
|
namespace = namespace.substr(1)
|
||||||
@ -50,6 +78,10 @@ export abstract class ViewEngine extends AppClass {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the name of a template, get a UniversalPath pointing to its file.
|
||||||
|
* @param templateName
|
||||||
|
*/
|
||||||
public resolveName(templateName: string): UniversalPath {
|
public resolveName(templateName: string): UniversalPath {
|
||||||
let path = this.path
|
let path = this.path
|
||||||
if ( templateName.startsWith('@') ) {
|
if ( templateName.startsWith('@') ) {
|
||||||
@ -66,13 +98,18 @@ export abstract class ViewEngine extends AppClass {
|
|||||||
templateName = parts.join(':')
|
templateName = parts.join(':')
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !templateName.endsWith('.pug') ) {
|
if ( !templateName.endsWith(this.getFileExtension()) ) {
|
||||||
templateName += '.pug'
|
templateName += this.getFileExtension()
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.concat(...templateName.split(':'))
|
return path.concat(...templateName.split(':'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the name of a template, get a UniversalPath to the root of the tree where
|
||||||
|
* that template resides.
|
||||||
|
* @param templateName
|
||||||
|
*/
|
||||||
public resolveBasePath(templateName: string): UniversalPath {
|
public resolveBasePath(templateName: string): UniversalPath {
|
||||||
let path = this.path
|
let path = this.path
|
||||||
if ( templateName.startsWith('@') ) {
|
if ( templateName.startsWith('@') ) {
|
||||||
|
Loading…
Reference in New Issue
Block a user