diff --git a/app/bundle/daton_units.ts b/app/bundle/daton_units.ts index e46aa13..15554b3 100644 --- a/app/bundle/daton_units.ts +++ b/app/bundle/daton_units.ts @@ -5,3 +5,4 @@ export { default as MiddlewareUnit } from '../../lib/src/unit/Middlewares.ts' export { default as RoutesUnit } from '../../lib/src/unit/Routes.ts' export { default as HttpKernelUnit } from '../../lib/src/unit/HttpKernel.ts' export { default as ModelsUnit } from '../../orm/src/ModelsUnit.ts' +export { default as HttpServerUnit } from '../../lib/src/unit/HttpServer.ts' diff --git a/app/models/http/Session.model.ts b/app/models/http/Session.model.ts index 246a34b..76286c2 100644 --- a/app/models/http/Session.model.ts +++ b/app/models/http/Session.model.ts @@ -4,13 +4,13 @@ import {SessionModel} from '../../../lib/src/module.ts' export default class Session extends SessionModel { protected static table = 'sessions' - protected static key = 'session_id' + protected static key = 'session_key' protected static readonly CREATED_AT = 'start_time' protected static readonly UPDATED_AT = null // No updated at - @Field(Type.int) - protected session_id!: number + @Field(Type.varchar) + protected session_key!: string @Field(Type.int) protected user_id?: number diff --git a/app/units.ts b/app/units.ts index 587794a..d8017b9 100644 --- a/app/units.ts +++ b/app/units.ts @@ -1,5 +1,5 @@ import { - ConfigUnit, DatabaseUnit, ControllerUnit, MiddlewareUnit, RoutesUnit, HttpKernelUnit, ModelsUnit + ConfigUnit, DatabaseUnit, ControllerUnit, MiddlewareUnit, RoutesUnit, HttpKernelUnit, ModelsUnit, HttpServerUnit } from './bundle/daton_units.ts' export default [ @@ -10,4 +10,5 @@ export default [ MiddlewareUnit, ControllerUnit, RoutesUnit, + HttpServerUnit, ] diff --git a/lib/src/http/Request.ts b/lib/src/http/Request.ts index b2d19ca..e30f025 100644 --- a/lib/src/http/Request.ts +++ b/lib/src/http/Request.ts @@ -4,6 +4,7 @@ import { Response } from './Response.ts' import { HTTPResponse } from './type/HTTPResponse.ts' import Utility from '../service/utility/Utility.ts' import { Injectable } from '../../../di/src/decorator/Injection.ts' +import SessionInterface from './session/SessionInterface.ts' @Injectable() export class Request implements HTTPRequest { @@ -11,6 +12,7 @@ export class Request implements HTTPRequest { private readonly _deno_req: ServerRequest private _body: any private _query: { [key: string]: any } = {} + private _session!: SessionInterface public readonly url: string public readonly method: string @@ -30,6 +32,14 @@ export class Request implements HTTPRequest { return this.response.cookies } + get session(): SessionInterface { + return this._session + } + + set session(session: SessionInterface) { + this._session = session + } + constructor( protected utility: Utility, from: ServerRequest diff --git a/lib/src/http/kernel/module/InjectSession.ts b/lib/src/http/kernel/module/InjectSession.ts new file mode 100644 index 0000000..f460eff --- /dev/null +++ b/lib/src/http/kernel/module/InjectSession.ts @@ -0,0 +1,59 @@ +import Module from '../Module.ts' +import Kernel from '../Kernel.ts' +import {Request} from '../../Request.ts' +import SetSessionCookie from './SetSessionCookie.ts' +import SessionManager from '../../session/SessionManager.ts' +import {Logging} from '../../../service/logging/Logging.ts' +import {Injectable} from '../../../../../di/src/decorator/Injection.ts' + +@Injectable() +export default class InjectSession extends Module { + public static register(kernel: Kernel) { + kernel.register(this).after(SetSessionCookie) + } + + constructor( + protected readonly sessions: SessionManager, + protected readonly logger: Logging, + ) { + super() + } + + public async apply(request: Request): Promise { + if ( request.session ) return request + + let key: string | undefined + try { + const result = await request.cookies.get('daton.session') + key = result?.value + } catch (e) { + this.logger.error('Invalid Daton session cookie. The session will not be injected.') + + try { + this.logger.debug(`Cookie: ${await request.cookies.get_raw('daton.session')}`) + } catch (e2) {} + + this.logger.debug(e) + return request + } + + if ( !key ) { + this.logger.warn(`No session key was found. Is the SetSessionCookie module registered?`) + return request + } + + const has_existing = await this.sessions.has_session(key) + if ( has_existing ) { + request.session = await this.sessions.get_session(key) + return request + } + + const new_session = await this.sessions.get_session() + this.logger.verbose(`Populating new session: ${key}`) + new_session.set_key(key) + await new_session.persist() + request.session = new_session + + return request + } +} diff --git a/lib/src/http/kernel/module/PersistSession.ts b/lib/src/http/kernel/module/PersistSession.ts new file mode 100644 index 0000000..0ee4517 --- /dev/null +++ b/lib/src/http/kernel/module/PersistSession.ts @@ -0,0 +1,14 @@ +import Module from '../Module.ts' +import Kernel from '../Kernel.ts' +import {Request} from '../../Request.ts' + +export default class PersistSession extends Module { + public static register(kernel: Kernel) { + kernel.register(this).last() + } + + public async apply(request: Request): Promise { + await request.session.persist() + return request + } +} diff --git a/lib/src/http/kernel/module/SetSessionCookie.ts b/lib/src/http/kernel/module/SetSessionCookie.ts index 646cbd7..8543f85 100644 --- a/lib/src/http/kernel/module/SetSessionCookie.ts +++ b/lib/src/http/kernel/module/SetSessionCookie.ts @@ -13,7 +13,7 @@ export default class SetSessionCookie extends Module { } constructor( - protected utility: Utility, + protected readonly utility: Utility, ) { super() } diff --git a/lib/src/http/session/ModelSessionManager.ts b/lib/src/http/session/ModelSessionManager.ts index fe881a6..98cb1b6 100644 --- a/lib/src/http/session/ModelSessionManager.ts +++ b/lib/src/http/session/ModelSessionManager.ts @@ -34,9 +34,10 @@ export default class ModelSessionManager extends SessionManager { public async has_session(key: string): Promise { const ModelClass: typeof Model = this.ModelClass as typeof Model - return ModelClass.select(ModelClass.qualified_key_name()) + const query = ModelClass.select(ModelClass.qualified_key_name()) .where(ModelClass.qualified_key_name(), '=', key) - .exists() + + return await query.exists() } public async purge(key?: string): Promise { diff --git a/lib/src/http/session/SessionModel.ts b/lib/src/http/session/SessionModel.ts index 55a4702..04aa1ae 100644 --- a/lib/src/http/session/SessionModel.ts +++ b/lib/src/http/session/SessionModel.ts @@ -4,6 +4,7 @@ import {Field} from '../../../../orm/src/model/Field.ts' import {Type} from '../../../../orm/src/db/types.ts' export default class SessionModel extends Model implements SessionInterface { + protected static populate_key_on_insert: boolean = true @Field(Type.json) protected data?: string @@ -14,7 +15,7 @@ export default class SessionModel extends Model implements Session public set_key(key: string) { // @ts-ignore - this[this.key_name()] = parseInt(key) + this[this.key_name()] = key } public async persist(): Promise { @@ -42,6 +43,5 @@ export default class SessionModel extends Model implements Session public async init_session(): Promise { this.data = JSON.stringify({}) - await this.save() } } diff --git a/lib/src/http/type/HTTPRequest.ts b/lib/src/http/type/HTTPRequest.ts index 3081a96..9bc0fc8 100644 --- a/lib/src/http/type/HTTPRequest.ts +++ b/lib/src/http/type/HTTPRequest.ts @@ -1,5 +1,6 @@ import { ServerRequest } from '../../external/http.ts' -import {HTTPResponse} from "./HTTPResponse.ts"; +import {HTTPResponse} from './HTTPResponse.ts' +import SessionInterface from '../session/SessionInterface.ts' export interface HTTPProtocol { string: string, @@ -27,4 +28,6 @@ export interface HTTPRequest { query: any hostname: string | undefined secure: boolean + + session: SessionInterface, } diff --git a/lib/src/unit/HttpKernel.ts b/lib/src/unit/HttpKernel.ts index 635e352..89019b9 100644 --- a/lib/src/unit/HttpKernel.ts +++ b/lib/src/unit/HttpKernel.ts @@ -15,6 +15,8 @@ import {StaticClass} from '../../../di/src/type/StaticClass.ts' import ModelSessionFactory from '../http/session/ModelSessionFactory.ts' import ModelSessionManagerFactory from '../http/session/ModelSessionManagerFactory.ts' import SessionInterface from '../http/session/SessionInterface.ts' +import InjectSession from '../http/kernel/module/InjectSession.ts' +import PersistSession from '../http/kernel/module/PersistSession.ts' @Unit() export default class HttpKernel extends LifecycleUnit { @@ -29,21 +31,23 @@ export default class HttpKernel extends LifecycleUnit { } public async up() { + this.determine_session_provider() + PrepareRequest.register(this.kernel) SetSessionCookie.register(this.kernel) + InjectSession.register(this.kernel) + PersistSession.register(this.kernel) if ( this.config.get('server.powered_by.enable') ) { SetDatonHeaders.register(this.kernel) } - - this.determine_session_provider() } protected determine_session_provider() { const driver = this.config.get('server.session.driver') if ( driver === 'memory' ) { - this.logger.verbose('Adding the memory session production factories to the container...') + this.logger.info('Adding the memory session production factories to the container...') this.injector.register_factory(new MemorySessionFactory()) this.injector.register_factory(new MemorySessionManagerFactory()) } else if ( driver === 'database' ) { diff --git a/lib/src/unit/HttpServer.ts b/lib/src/unit/HttpServer.ts new file mode 100644 index 0000000..3adefde --- /dev/null +++ b/lib/src/unit/HttpServer.ts @@ -0,0 +1,32 @@ +import LifecycleUnit from '../lifecycle/Unit.ts' +import {Unit} from '../lifecycle/decorators.ts' +import Kernel from '../http/kernel/Kernel.ts' +import {Logging} from '../service/logging/Logging.ts' +import {serve} from '../external/http.ts' +import {Request} from '../http/Request.ts' + +@Unit() +export default class HttpServer extends LifecycleUnit { + protected _server: any // TODO replace with more specific type + + constructor( + protected readonly kernel: Kernel, + protected readonly logger: Logging, + ) { + super() + } + + public async up() { + this._server = serve({ port: 8000 }) + + this.logger.success(`HTTP/S server listening on port 8000!`) + + for await ( const native_request of this._server ) { + let req: Request = this.make(Request, native_request) + req = await this.kernel.handle(req) + + req.response.body = req.session.get_key() + req.response.send() + } + } +} diff --git a/orm/src/db/PostgresConnection.ts b/orm/src/db/PostgresConnection.ts index 059588e..9911fe0 100644 --- a/orm/src/db/PostgresConnection.ts +++ b/orm/src/db/PostgresConnection.ts @@ -16,6 +16,7 @@ export default class PostgresConnection extends Connection { public async query(query: string) { if ( !this._client ) throw new ConnectionNotReadyError(this.name) + logger.verbose(`Executing query: \n${query}`) const result = await this._client.query(query) let base_i = 0 diff --git a/orm/src/model/Model.ts b/orm/src/model/Model.ts index ef9ff66..d2d5cfa 100644 --- a/orm/src/model/Model.ts +++ b/orm/src/model/Model.ts @@ -38,6 +38,11 @@ export abstract class Model> extends Builder { */ protected static key: string + /** + * If false (default), the primary key will be excluded from INSERTs. + */ + protected static populate_key_on_insert: boolean = false + /** * Optionally, the timestamp field set on creation. * @type string @@ -642,7 +647,11 @@ export abstract class Model> extends Builder { * @return FieldValueObject */ protected _build_insert_field_object(): FieldValueObject { - const fields = this.field_defs().whereNot('model_key', '=', this.key_name()) + let fields = this.field_defs() + + if ( !(this.constructor as typeof Model).populate_key_on_insert ) + fields = fields.whereNot('model_key', '=', this.key_name()) + const values = {} fields.each(field_def => { // @ts-ignore