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