feat(persistance) : add DAO

This commit is contained in:
2026-01-17 15:01:16 -03:00
parent 8b462f98f6
commit fd763cc162
7 changed files with 310 additions and 0 deletions

87
src/app/dao/BaseDAO.ts Normal file
View 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
View 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);
}
}

View 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);
}
}

View 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
View 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);
}
}

View 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;
};

View 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;
};