feat(persistance) : add DAO
This commit is contained in:
87
src/app/dao/BaseDAO.ts
Normal file
87
src/app/dao/BaseDAO.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { inject } from '@angular/core';
|
||||||
|
import { Sqlite } from '../services/sqlite';
|
||||||
|
import { QueryResult } from '../types/sqlite.type';
|
||||||
|
|
||||||
|
export abstract class BaseDAO<T extends Object, K extends Object, U> {
|
||||||
|
protected readonly sqlite = inject(Sqlite);
|
||||||
|
protected readonly tableName: string;
|
||||||
|
|
||||||
|
constructor(tableName: string) {
|
||||||
|
this.tableName = tableName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
async insert(entity: T) {
|
||||||
|
const params = [];
|
||||||
|
let sql = `INSERT INTO ${this.tableName} ( `;
|
||||||
|
let values = `VALUES ( `;
|
||||||
|
let dbModel = this.toDB(entity);
|
||||||
|
for (let key in dbModel) {
|
||||||
|
if (dbModel[key] !== undefined) {
|
||||||
|
sql += `${key}, `;
|
||||||
|
params.push(dbModel[key]);
|
||||||
|
values += '?, ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sql = sql.slice(0, -2);
|
||||||
|
sql += ' ) ';
|
||||||
|
values = values.slice(0, -2);
|
||||||
|
values += ' );';
|
||||||
|
sql = sql + values;
|
||||||
|
const result: { rows: never[] } = await this.sqlite.executeQuery(sql, params);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findBy(values: Partial<K>) {
|
||||||
|
let sql = `SELECT * FROM ${this.tableName} `;
|
||||||
|
const params: any[] = [];
|
||||||
|
const whereSql = this.whereClauseGenerator(values, params);
|
||||||
|
sql += whereSql;
|
||||||
|
sql += ';';
|
||||||
|
const queryResults: QueryResult<U> = await this.sqlite.executeQuery(sql, params);
|
||||||
|
return queryResults.rows.map(this.fromDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll() {
|
||||||
|
const queryResults: QueryResult<U> = await this.sqlite.executeQuery(
|
||||||
|
`SELECT * FROM ${this.tableName.toLowerCase()}`
|
||||||
|
);
|
||||||
|
return queryResults.rows.map(this.fromDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(values: T, where: Partial<K>) {
|
||||||
|
const params: any[] = [];
|
||||||
|
let sql = `UPDATE ${this.tableName} SET `;
|
||||||
|
let updates = '';
|
||||||
|
let dbModel = this.toDB(values);
|
||||||
|
for (let key in dbModel) {
|
||||||
|
if (dbModel[key] !== undefined) {
|
||||||
|
updates += `${key} = ?, `;
|
||||||
|
params.push(dbModel[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updates = updates.slice(0, -2);
|
||||||
|
updates += ' ';
|
||||||
|
sql += updates;
|
||||||
|
const whereSql = this.whereClauseGenerator(where, params);
|
||||||
|
sql += whereSql + ';';
|
||||||
|
console.log(sql);
|
||||||
|
const result: { rows: never[] } = await this.sqlite.executeQuery(sql, params);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected whereClauseGenerator(values: Partial<K>, params: any[], alias?: string) {
|
||||||
|
let sql = `WHERE `;
|
||||||
|
const objLength = Object.keys(values).length;
|
||||||
|
let i = 0;
|
||||||
|
for (let key in values) {
|
||||||
|
i += 1;
|
||||||
|
sql += `${alias ? alias + '.' : ''}${key} = ? `;
|
||||||
|
params.push(values[key]);
|
||||||
|
if (i < objLength) sql += `AND `;
|
||||||
|
}
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract toDB(model: T): K;
|
||||||
|
protected abstract fromDB(queryResult: U): T;
|
||||||
|
}
|
||||||
21
src/app/dao/ChainDAO.ts
Normal file
21
src/app/dao/ChainDAO.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Chain } from '../models/Chain';
|
||||||
|
import { BaseDAO } from './BaseDAO';
|
||||||
|
import { DBChain } from '../models/db/DBChain';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ChainDAO extends BaseDAO<Chain, DBChain, DBChain> {
|
||||||
|
constructor() {
|
||||||
|
super(Chain.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
override toDB(model: Chain): DBChain {
|
||||||
|
return new DBChain(model.name, model.image, model.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
override fromDB(qResult: DBChain): Chain {
|
||||||
|
return new Chain(qResult.name, qResult.image, qResult.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/app/dao/ComposedDAO.ts
Normal file
28
src/app/dao/ComposedDAO.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { QueryResult } from '../types/sqlite.type';
|
||||||
|
import { BaseDAO } from './BaseDAO';
|
||||||
|
|
||||||
|
export abstract class ComposedDAO<T extends Object, K extends Object, U> extends BaseDAO<T, K, U> {
|
||||||
|
protected readonly JOIN_SQL: string;
|
||||||
|
protected readonly tableAlias: string;
|
||||||
|
|
||||||
|
constructor(tableName: string, joinSql: string, tableAlias: string) {
|
||||||
|
super(tableName);
|
||||||
|
this.JOIN_SQL = joinSql;
|
||||||
|
this.tableAlias = tableAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async findAll() {
|
||||||
|
const result: QueryResult<U> = await this.sqlite.executeQuery(this.JOIN_SQL);
|
||||||
|
return result.rows.map(this.fromDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async findBy(values: Partial<K>): Promise<T[]> {
|
||||||
|
let sql = this.JOIN_SQL.slice(0, -1) + ' ';
|
||||||
|
const params: any[] = [];
|
||||||
|
const whereSql = this.whereClauseGenerator(values, params, this.tableAlias);
|
||||||
|
sql += whereSql + ';';
|
||||||
|
console.log(sql);
|
||||||
|
const result: QueryResult<U> = await this.sqlite.executeQuery(sql, params);
|
||||||
|
return result.rows.map(this.fromDB);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/app/dao/EstablishmentDAO.ts
Normal file
35
src/app/dao/EstablishmentDAO.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Establishment } from '../models/Establishment';
|
||||||
|
import { Chain } from '../models/Chain';
|
||||||
|
import { DBEstablishment } from '../models/db/DBEstablishment';
|
||||||
|
import { ComposedDAO } from './ComposedDAO';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class EstablishmentDAO extends ComposedDAO<
|
||||||
|
Establishment,
|
||||||
|
DBEstablishment,
|
||||||
|
EstablishmentQueryResult
|
||||||
|
> {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
Establishment.name,
|
||||||
|
`SELECT e.id, e.address, chain_id, c.name, c.image FROM establishment e JOIN chain c ON c.id = chain_id;`,
|
||||||
|
'e'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override toDB(model: Establishment): DBEstablishment {
|
||||||
|
if (!model.chain.id) throw new Error('Chain id is required');
|
||||||
|
return new DBEstablishment(model.address, model.chain.id, model.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override fromDB(qR: EstablishmentQueryResult): Establishment {
|
||||||
|
const chain = new Chain(qR.name, qR.image, qR.chain_id);
|
||||||
|
return new Establishment(chain, qR.address, qR.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EstablishmentQueryResult = Omit<Establishment, 'chain'> &
|
||||||
|
Omit<Chain, 'id'> & { id: number; chain_id: number };
|
||||||
21
src/app/dao/ProductDAO.ts
Normal file
21
src/app/dao/ProductDAO.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Product } from '../models/Product';
|
||||||
|
import { BaseDAO } from './BaseDAO';
|
||||||
|
import { DBProduct } from '../models/db/DBProduct';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ProductDAO extends BaseDAO<Product, DBProduct, DBProduct> {
|
||||||
|
constructor() {
|
||||||
|
super(Product.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override toDB(model: Product): DBProduct {
|
||||||
|
return new Product(model.barcode, model.name, model.image, model.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override fromDB(qR: DBProduct): Product {
|
||||||
|
return new DBProduct(qR.barcode, qR.name, qR.image, qR.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/app/dao/ProductEstablishmentDAO.ts
Normal file
55
src/app/dao/ProductEstablishmentDAO.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Establishment } from '../models/Establishment';
|
||||||
|
import { Product } from '../models/Product';
|
||||||
|
import { ProductEstablishment } from '../models/ProductEstablisment';
|
||||||
|
import { Chain } from '../models/Chain';
|
||||||
|
import { DBProductEstablishment } from '../models/db/DBProductEstablishment';
|
||||||
|
import { ComposedDAO } from './ComposedDAO';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ProductEstablishmentDAO extends ComposedDAO<
|
||||||
|
ProductEstablishment,
|
||||||
|
DBProductEstablishment,
|
||||||
|
ProductEstablishmentQueryResult
|
||||||
|
> {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
'product_establishment',
|
||||||
|
`
|
||||||
|
SELECT pe.id, pe.product_id, pe.establishment_id, p.barcode, p.image as product_image, p.name as product_name,
|
||||||
|
e.address, e.chain_id, c.image as chain_image, c.name as chain_name FROM product_establishment pe
|
||||||
|
JOIN product p ON p.id = pe.product_id
|
||||||
|
JOIN establishment e ON e.id = pe.establishment_id
|
||||||
|
JOIN chain c ON c.id = e.chain_id;`,
|
||||||
|
'pe'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override toDB(model: ProductEstablishment): DBProductEstablishment {
|
||||||
|
if (!model.product.id) throw new Error('Product id is required');
|
||||||
|
if (!model.establishment.id) throw new Error('Establishment id is required');
|
||||||
|
return new DBProductEstablishment(model.product.id, model.establishment.id, model.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override fromDB(qR: ProductEstablishmentQueryResult): ProductEstablishment {
|
||||||
|
const chain = new Chain(qR.chain_name, qR.chain_image, qR.chain_id);
|
||||||
|
const establishment = new Establishment(chain, qR.address, qR.establishment_id);
|
||||||
|
const product = new Product(qR.barcode, qR.product_name, qR.product_image, qR.product_id);
|
||||||
|
return new ProductEstablishment(product, establishment, qR.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductEstablishmentQueryResult = {
|
||||||
|
address: string;
|
||||||
|
barcode: string;
|
||||||
|
chain_id: number;
|
||||||
|
chain_image: string | null;
|
||||||
|
chain_name: string;
|
||||||
|
establishment_id: number;
|
||||||
|
id: number;
|
||||||
|
product_id: number;
|
||||||
|
product_image: string | null;
|
||||||
|
product_name: string;
|
||||||
|
};
|
||||||
63
src/app/dao/PurchaseDAO.ts
Normal file
63
src/app/dao/PurchaseDAO.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Purchase } from '../models/Purchase';
|
||||||
|
import { DBPurchase } from '../models/db/DBPurchase';
|
||||||
|
import { Establishment } from '../models/Establishment';
|
||||||
|
import { Chain } from '../models/Chain';
|
||||||
|
import { Product } from '../models/Product';
|
||||||
|
import { ComposedDAO } from './ComposedDAO';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class PurchaseDAO extends ComposedDAO<Purchase, DBPurchase, PurchaseQueryResult> {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
Purchase.name,
|
||||||
|
`SELECT p.id, p.price, p.quantity, p.date, p.establishment_id, p.product_id,
|
||||||
|
pr.barcode, pr.image as product_image, pr.name as product_name,
|
||||||
|
e.address, e.chain_id,
|
||||||
|
c.image as chain_image, c.name as chain_name
|
||||||
|
FROM purchase p
|
||||||
|
JOIN product pr ON pr.id = p.product_id
|
||||||
|
JOIN establishment e ON e.id = p.establishment_id
|
||||||
|
JOIN chain c ON c.id = e.chain_id;`,
|
||||||
|
'e'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override toDB(model: Purchase): DBPurchase {
|
||||||
|
if (!model.establishment.id) throw new Error('Esablishment id is required');
|
||||||
|
if (!model.product.id) throw new Error('Product id is required');
|
||||||
|
return new DBPurchase(
|
||||||
|
model.establishment.id,
|
||||||
|
model.product.id,
|
||||||
|
model.date,
|
||||||
|
model.price * 100,
|
||||||
|
model.quantity,
|
||||||
|
model.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override fromDB(qR: PurchaseQueryResult): Purchase {
|
||||||
|
const chain = new Chain(qR.chain_name, qR.chain_image, qR.chain_id);
|
||||||
|
const establishment = new Establishment(chain, qR.address, qR.establishment_id);
|
||||||
|
const product = new Product(qR.barcode, qR.product_name, qR.product_image, qR.product_id);
|
||||||
|
return new Purchase(establishment, product, qR.price / 100, qR.quantity, qR.date, qR.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseQueryResult = {
|
||||||
|
id: number;
|
||||||
|
price: number;
|
||||||
|
quantity: number;
|
||||||
|
date: number;
|
||||||
|
establishment_id: number;
|
||||||
|
product_id: number;
|
||||||
|
barcode: string;
|
||||||
|
product_image: string;
|
||||||
|
product_name: string;
|
||||||
|
address: string;
|
||||||
|
chain_id: number;
|
||||||
|
chain_image: string;
|
||||||
|
chain_name: string;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user