Make model primaryKey dynamic (allows for composite primary keys)
This commit is contained in:
parent
b85fbe6c21
commit
0970ff3116
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wms-core",
|
||||
"version": "0.4.35",
|
||||
"version": "0.5.0",
|
||||
"description": "Node web framework",
|
||||
"repository": "git@gitlab.com:ArisuOngaku/wms-core.git",
|
||||
"author": "Alice Gaudon <alice@gaudon.pro>",
|
||||
|
@ -9,7 +9,7 @@ export default abstract class AuthGuard<P extends AuthProof> {
|
||||
|
||||
public async getUserForSession(session: Express.Session): Promise<User | null> {
|
||||
if (!await this.isAuthenticated(session)) return null;
|
||||
return await User.getById<User>(session.auth_id);
|
||||
return await User.getById<User>(`${session.auth_id}`);
|
||||
}
|
||||
|
||||
public async authenticateOrRegister(session: Express.Session, proof: P, registerCallback?: (connection: Connection, userID: number) => Promise<(() => Promise<void>)[]>): Promise<void> {
|
||||
|
@ -85,7 +85,7 @@ export default abstract class MagicLinkController extends Controller {
|
||||
|
||||
let success = true;
|
||||
let err;
|
||||
const magicLink = await MagicLink.getById<MagicLink>(id);
|
||||
const magicLink = await MagicLink.getById<MagicLink>(`${id}`);
|
||||
if (!magicLink) {
|
||||
res.status(404);
|
||||
err = `Couldn't find this magic link. Perhaps it has already expired.`;
|
||||
|
@ -6,7 +6,7 @@ import {Request} from "express";
|
||||
import Pagination from "../Pagination";
|
||||
|
||||
export default abstract class Model {
|
||||
public static async getById<T extends Model>(id: number): Promise<T | null> {
|
||||
public static async getById<T extends Model>(id: string): Promise<T | null> {
|
||||
const cachedModel = ModelCache.get(this.table, id);
|
||||
if (cachedModel?.constructor === this) {
|
||||
return <T>cachedModel;
|
||||
@ -48,7 +48,7 @@ export default abstract class Model {
|
||||
const models: T[] = [];
|
||||
const factory = this.getFactory<T>();
|
||||
for (const result of results.results) {
|
||||
const cachedModel = ModelCache.get(this.table, result.id);
|
||||
const cachedModel = ModelCache.get(this.table, this.getPrimaryKey(result));
|
||||
if (cachedModel && cachedModel.constructor === this) {
|
||||
cachedModel.updateWithData(result);
|
||||
models.push(<T>cachedModel);
|
||||
@ -61,6 +61,14 @@ export default abstract class Model {
|
||||
return models;
|
||||
}
|
||||
|
||||
protected static getPrimaryKey(modelData: any): string {
|
||||
return this.getPrimaryKeyFields().map(f => `${modelData[f]}`).join(',');
|
||||
}
|
||||
|
||||
protected static getPrimaryKeyFields(): string[] {
|
||||
return ['id'];
|
||||
}
|
||||
|
||||
public static async loadRelation<T extends Model>(models: T[], relation: string, model: Function, localField: string) {
|
||||
const loadMap: { [p: number]: (model: T) => void } = {};
|
||||
const ids = models.map(m => {
|
||||
@ -85,15 +93,27 @@ export default abstract class Model {
|
||||
protected readonly properties: ModelProperty<any>[] = [];
|
||||
private readonly relations: { [p: string]: (Model | null) } = {};
|
||||
public id?: number;
|
||||
private readonly automaticIdProperty: boolean;
|
||||
|
||||
[key: string]: any;
|
||||
|
||||
public constructor(data: any) {
|
||||
this.defineProperty<number>('id', new Validator());
|
||||
public constructor(data: any, automaticIdProperty: boolean = true) {
|
||||
this.automaticIdProperty = automaticIdProperty;
|
||||
if (automaticIdProperty) {
|
||||
this.defineProperty<number>('id', new Validator());
|
||||
}
|
||||
this.defineProperties();
|
||||
this.updateWithData(data);
|
||||
}
|
||||
|
||||
public getPrimaryKey(): string {
|
||||
return (<any>this.constructor).getPrimaryKey(this);
|
||||
}
|
||||
|
||||
public getPrimaryKeyFields(): string {
|
||||
return (<any>this.constructor).getPrimaryKeyFields();
|
||||
}
|
||||
|
||||
protected abstract defineProperties(): void;
|
||||
|
||||
protected defineProperty<T>(name: string, validator?: Validator<T> | RegExp) {
|
||||
@ -112,7 +132,7 @@ export default abstract class Model {
|
||||
}
|
||||
|
||||
private updateWithData(data: any) {
|
||||
this.id = data['id'];
|
||||
if (this.automaticIdProperty) this.id = data['id'];
|
||||
|
||||
for (const prop of this.properties) {
|
||||
if (data[prop.name] !== undefined) {
|
||||
@ -141,7 +161,7 @@ export default abstract class Model {
|
||||
|
||||
const callback = async () => {
|
||||
if (needs_full_update) {
|
||||
this.updateWithData((await (<Model><unknown>this.constructor).select().where('id', this.id!).first().execute()).results[0]);
|
||||
this.updateWithData((await (<any>this.constructor).select().where('id', this.id!).first().execute()).results[0]);
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
@ -169,16 +189,19 @@ export default abstract class Model {
|
||||
const values = [];
|
||||
|
||||
if (exists) {
|
||||
const data: any = {};
|
||||
for (const prop of this.properties) {
|
||||
if (prop.value !== undefined) {
|
||||
props.push(prop.name + '=?');
|
||||
values.push(prop.value);
|
||||
data[prop.name] = prop.value;
|
||||
} else {
|
||||
needs_full_update = true;
|
||||
}
|
||||
}
|
||||
values.push(this.id);
|
||||
await query(`UPDATE ${this.table} SET ${props.join(',')} WHERE id=?`, values, connection);
|
||||
let query = Query.update(this.table, data);
|
||||
for (const indexField of this.getPrimaryKeyFields()) {
|
||||
query = query.where(indexField, this[indexField]);
|
||||
}
|
||||
await query.execute(connection);
|
||||
} else {
|
||||
const props_holders = [];
|
||||
for (const prop of this.properties) {
|
||||
@ -192,7 +215,7 @@ export default abstract class Model {
|
||||
}
|
||||
const result = await query(`INSERT INTO ${this.table} (${props.join(', ')}) VALUES(${props_holders.join(', ')})`, values, connection);
|
||||
|
||||
this.id = result.other.insertId;
|
||||
if (this.automaticIdProperty) this.id = result.other.insertId;
|
||||
}
|
||||
|
||||
return needs_full_update;
|
||||
@ -213,20 +236,25 @@ export default abstract class Model {
|
||||
public async exists(): Promise<boolean> {
|
||||
if (!this.id) return false;
|
||||
|
||||
const result = await query(`SELECT 1 FROM ${this.table} WHERE id=? LIMIT 1`, [
|
||||
this.id,
|
||||
]);
|
||||
let query = Query.select(this.table, '1');
|
||||
for (const indexField of this.getPrimaryKeyFields()) {
|
||||
query = query.where(indexField, this[indexField]);
|
||||
}
|
||||
query = query.first();
|
||||
const result = await query.execute();
|
||||
return result.results.length > 0;
|
||||
}
|
||||
|
||||
public async delete(): Promise<void> {
|
||||
if (!(await this.exists())) throw new Error('This model instance doesn\'t exist in DB.');
|
||||
|
||||
await query(`DELETE FROM ${this.table} WHERE id=?`, [
|
||||
this.id,
|
||||
]);
|
||||
let query = Query.delete(this.table);
|
||||
for (const indexField of this.getPrimaryKeyFields()) {
|
||||
query = query.where(indexField, this[indexField]);
|
||||
}
|
||||
await query.execute();
|
||||
ModelCache.forget(this);
|
||||
this.id = undefined;
|
||||
if (this.automaticIdProperty) this.id = undefined;
|
||||
}
|
||||
|
||||
public async validate(onlyFormat: boolean = false, connection?: Connection): Promise<void[]> {
|
||||
@ -273,38 +301,40 @@ class ModelProperty<T> {
|
||||
export class ModelCache {
|
||||
private static readonly caches: {
|
||||
[key: string]: {
|
||||
[key: number]: Model
|
||||
[key: string]: Model
|
||||
}
|
||||
} = {};
|
||||
|
||||
public static cache(instance: Model) {
|
||||
if (instance.id === undefined) throw new Error('Cannot cache an instance with an undefined id.');
|
||||
const primaryKey = instance.getPrimaryKey();
|
||||
if (primaryKey === undefined) throw new Error('Cannot cache an instance with an undefined primaryKey.');
|
||||
|
||||
let tableCache = this.caches[instance.table];
|
||||
if (!tableCache) tableCache = this.caches[instance.table] = {};
|
||||
|
||||
if (!tableCache[instance.id]) tableCache[instance.id] = instance;
|
||||
if (!tableCache[primaryKey]) tableCache[primaryKey] = instance;
|
||||
}
|
||||
|
||||
public static forget(instance: Model) {
|
||||
if (instance.id === undefined) throw new Error('Cannot forget an instance with an undefined id.');
|
||||
const primaryKey = instance.getPrimaryKey();
|
||||
if (primaryKey === undefined) throw new Error('Cannot forget an instance with an undefined primaryKey.');
|
||||
|
||||
let tableCache = this.caches[instance.table];
|
||||
if (!tableCache) return;
|
||||
|
||||
if (tableCache[instance.id]) delete tableCache[instance.id];
|
||||
if (tableCache[primaryKey]) delete tableCache[primaryKey];
|
||||
}
|
||||
|
||||
public static all(table: string): {
|
||||
[key: number]: Model
|
||||
[key: string]: Model
|
||||
} | undefined {
|
||||
return this.caches[table];
|
||||
}
|
||||
|
||||
public static get(table: string, id: number): Model | undefined {
|
||||
public static get(table: string, primaryKey: string): Model | undefined {
|
||||
const tableCache = this.all(table);
|
||||
if (!tableCache) return undefined;
|
||||
return tableCache[id];
|
||||
return tableCache[primaryKey];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ describe('Model', () => {
|
||||
expect(instance.date?.getTime()).toBeCloseTo(date.getTime(), -4);
|
||||
expect(instance.date_default).toBeDefined();
|
||||
|
||||
instance = await FakeDummyModel.getById(1);
|
||||
instance = await FakeDummyModel.getById('1');
|
||||
expect(instance).toBeDefined();
|
||||
expect(instance!.id).toBe(1);
|
||||
expect(instance!.name).toBe('name1');
|
||||
|
Loading…
Reference in New Issue
Block a user