diff --git a/src/app/dao/BaseDAO.ts b/src/app/dao/BaseDAO.ts new file mode 100644 index 0000000..20dffe2 --- /dev/null +++ b/src/app/dao/BaseDAO.ts @@ -0,0 +1,87 @@ +import { inject } from '@angular/core'; +import { Sqlite } from '../services/sqlite'; +import { QueryResult } from '../types/sqlite.type'; + +export abstract class BaseDAO { + 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) { + let sql = `SELECT * FROM ${this.tableName} `; + const params: any[] = []; + const whereSql = this.whereClauseGenerator(values, params); + sql += whereSql; + sql += ';'; + const queryResults: QueryResult = await this.sqlite.executeQuery(sql, params); + return queryResults.rows.map(this.fromDB); + } + + async findAll() { + const queryResults: QueryResult = await this.sqlite.executeQuery( + `SELECT * FROM ${this.tableName.toLowerCase()}` + ); + return queryResults.rows.map(this.fromDB); + } + + async update(values: T, where: Partial) { + 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, 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; +} diff --git a/src/app/dao/ChainDAO.ts b/src/app/dao/ChainDAO.ts new file mode 100644 index 0000000..2cf0406 --- /dev/null +++ b/src/app/dao/ChainDAO.ts @@ -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 { + 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); + } +} diff --git a/src/app/dao/ComposedDAO.ts b/src/app/dao/ComposedDAO.ts new file mode 100644 index 0000000..9531ed0 --- /dev/null +++ b/src/app/dao/ComposedDAO.ts @@ -0,0 +1,28 @@ +import { QueryResult } from '../types/sqlite.type'; +import { BaseDAO } from './BaseDAO'; + +export abstract class ComposedDAO extends BaseDAO { + 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 = await this.sqlite.executeQuery(this.JOIN_SQL); + return result.rows.map(this.fromDB); + } + + override async findBy(values: Partial): Promise { + 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 = await this.sqlite.executeQuery(sql, params); + return result.rows.map(this.fromDB); + } +} diff --git a/src/app/dao/EstablishmentDAO.ts b/src/app/dao/EstablishmentDAO.ts new file mode 100644 index 0000000..2ccbd10 --- /dev/null +++ b/src/app/dao/EstablishmentDAO.ts @@ -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 & + Omit & { id: number; chain_id: number }; diff --git a/src/app/dao/ProductDAO.ts b/src/app/dao/ProductDAO.ts new file mode 100644 index 0000000..e82ef44 --- /dev/null +++ b/src/app/dao/ProductDAO.ts @@ -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 { + 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); + } +} diff --git a/src/app/dao/ProductEstablishmentDAO.ts b/src/app/dao/ProductEstablishmentDAO.ts new file mode 100644 index 0000000..93a7e33 --- /dev/null +++ b/src/app/dao/ProductEstablishmentDAO.ts @@ -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; +}; diff --git a/src/app/dao/PurchaseDAO.ts b/src/app/dao/PurchaseDAO.ts new file mode 100644 index 0000000..c342d9e --- /dev/null +++ b/src/app/dao/PurchaseDAO.ts @@ -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 { + 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; +};