Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions api/src/repositories/BaseRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Base Repository Class
*
* Provides generic CRUD operations for all repositories.
* Child repositories can extend this class and add custom methods.
*/

import { DatabaseConnection } from '../db/sqlite';
import { handleDatabaseError, NotFoundError } from '../utils/errors';
import { buildInsertSQL, buildUpdateSQL, objectToCamelCase } from '../utils/sql';

/**
* Configuration interface for repository initialization
*/
export interface RepositoryConfig<T> {
tableName: string;
idField: string; // Database column name (snake_case), e.g., 'product_id'
entityName: string; // Human-readable name for error messages, e.g., 'Product'
}

/**
* Base repository class with generic CRUD operations
*/
export abstract class BaseRepository<T extends { [key: string]: any }> {
protected db: DatabaseConnection;
protected config: RepositoryConfig<T>;

constructor(db: DatabaseConnection, config: RepositoryConfig<T>) {
this.db = db;
this.config = config;
}

/**
* Get all entities
*/
async findAll(): Promise<T[]> {
try {
const rows = await this.db.all<any>(
`SELECT * FROM ${this.config.tableName} ORDER BY ${this.config.idField}`
);
return rows.map((row) => objectToCamelCase(row) as T);
} catch (error) {
handleDatabaseError(error);
}
}

/**
* Get entity by ID
*/
async findById(id: number): Promise<T | null> {
try {
const row = await this.db.get<any>(
`SELECT * FROM ${this.config.tableName} WHERE ${this.config.idField} = ?`,
[id]
);
return row ? (objectToCamelCase(row) as T) : null;
} catch (error) {
handleDatabaseError(error);
}
}

/**
* Create a new entity
*/
async create(entity: any): Promise<T> {
try {
const { sql, values } = buildInsertSQL(this.config.tableName, entity);
const result = await this.db.run(sql, values);

const createdEntity = await this.findById(result.lastID!);
if (!createdEntity) {
throw new Error(`Failed to retrieve created ${this.config.entityName}`);
}

return createdEntity;
} catch (error) {
handleDatabaseError(error);
}
}

/**
* Update entity by ID
*/
async update(id: number, entity: any): Promise<T> {
try {
const { sql, values } = buildUpdateSQL(
this.config.tableName,
entity,
`${this.config.idField} = ?`
);
const result = await this.db.run(sql, [...values, id]);

if (result.changes === 0) {
throw new NotFoundError(this.config.entityName, id);
}

const updatedEntity = await this.findById(id);
if (!updatedEntity) {
throw new Error(`Failed to retrieve updated ${this.config.entityName}`);
}

return updatedEntity;
} catch (error) {
handleDatabaseError(error, this.config.entityName, id);
}
}

/**
* Delete entity by ID
*/
async delete(id: number): Promise<void> {
try {
const result = await this.db.run(
`DELETE FROM ${this.config.tableName} WHERE ${this.config.idField} = ?`,
[id]
);

if (result.changes === 0) {
throw new NotFoundError(this.config.entityName, id);
}
} catch (error) {
handleDatabaseError(error, this.config.entityName, id);
}
}

/**
* Check if entity exists
*/
async exists(id: number): Promise<boolean> {
try {
const result = await this.db.get<{ count: number }>(
`SELECT COUNT(*) as count FROM ${this.config.tableName} WHERE ${this.config.idField} = ?`,
[id]
);
return (result?.count || 0) > 0;
} catch (error) {
handleDatabaseError(error);
}
}
}
111 changes: 9 additions & 102 deletions api/src/repositories/branchesRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,110 +4,17 @@

import { getDatabase, DatabaseConnection } from '../db/sqlite';
import { Branch } from '../models/branch';
import { handleDatabaseError, NotFoundError } from '../utils/errors';
import { buildInsertSQL, buildUpdateSQL, objectToCamelCase } from '../utils/sql';

export class BranchesRepository {
private db: DatabaseConnection;
import { handleDatabaseError } from '../utils/errors';
import { objectToCamelCase } from '../utils/sql';
import { BaseRepository } from './BaseRepository';

export class BranchesRepository extends BaseRepository<Branch> {
constructor(db: DatabaseConnection) {
this.db = db;
}

/**
* Get all branches
*/
async findAll(): Promise<Branch[]> {
try {
const rows = await this.db.all<any>('SELECT * FROM branches ORDER BY branch_id');
return rows.map((row) => objectToCamelCase(row) as Branch);
} catch (error) {
handleDatabaseError(error);
}
}

/**
* Get branch by ID
*/
async findById(id: number): Promise<Branch | null> {
try {
const row = await this.db.get<any>('SELECT * FROM branches WHERE branch_id = ?', [id]);
return row ? (objectToCamelCase(row) as Branch) : null;
} catch (error) {
handleDatabaseError(error);
}
}

/**
* Create a new branch
*/
async create(branch: Omit<Branch, 'branchId'>): Promise<Branch> {
try {
const { sql, values } = buildInsertSQL('branches', branch);
const result = await this.db.run(sql, values);

const createdBranch = await this.findById(result.lastID!);
if (!createdBranch) {
throw new Error('Failed to retrieve created branch');
}

return createdBranch;
} catch (error) {
handleDatabaseError(error);
}
}

/**
* Update branch by ID
*/
async update(id: number, branch: Partial<Omit<Branch, 'branchId'>>): Promise<Branch> {
try {
const { sql, values } = buildUpdateSQL('branches', branch, 'branch_id = ?');
const result = await this.db.run(sql, [...values, id]);

if (result.changes === 0) {
throw new NotFoundError('Branch', id);
}

const updatedBranch = await this.findById(id);
if (!updatedBranch) {
throw new Error('Failed to retrieve updated branch');
}

return updatedBranch;
} catch (error) {
handleDatabaseError(error, 'Branch', id);
}
}

/**
* Delete branch by ID
*/
async delete(id: number): Promise<void> {
try {
const result = await this.db.run('DELETE FROM branches WHERE branch_id = ?', [id]);

if (result.changes === 0) {
throw new NotFoundError('Branch', id);
}
} catch (error) {
handleDatabaseError(error, 'Branch', id);
}
}

/**
* Check if branch exists
*/
async exists(id: number): Promise<boolean> {
try {
const result = await this.db.get<{ count: number }>(
'SELECT COUNT(*) as count FROM branches WHERE branch_id = ?',
[id],
);
return (result?.count || 0) > 0;
} catch (error) {
handleDatabaseError(error);
}
super(db, {
tableName: 'branches',
idField: 'branch_id',
entityName: 'Branch',
});
}

/**
Expand Down
111 changes: 9 additions & 102 deletions api/src/repositories/deliveriesRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,110 +4,17 @@

import { getDatabase, DatabaseConnection } from '../db/sqlite';
import { Delivery } from '../models/delivery';
import { handleDatabaseError, NotFoundError } from '../utils/errors';
import { buildInsertSQL, buildUpdateSQL, objectToCamelCase } from '../utils/sql';

export class DeliveriesRepository {
private db: DatabaseConnection;
import { handleDatabaseError } from '../utils/errors';
import { objectToCamelCase } from '../utils/sql';
import { BaseRepository } from './BaseRepository';

export class DeliveriesRepository extends BaseRepository<Delivery> {
constructor(db: DatabaseConnection) {
this.db = db;
}

/**
* Get all deliveries
*/
async findAll(): Promise<Delivery[]> {
try {
const rows = await this.db.all<any>('SELECT * FROM deliveries ORDER BY delivery_id');
return rows.map((row) => objectToCamelCase(row) as Delivery);
} catch (error) {
handleDatabaseError(error);
}
}

/**
* Get delivery by ID
*/
async findById(id: number): Promise<Delivery | null> {
try {
const row = await this.db.get<any>('SELECT * FROM deliveries WHERE delivery_id = ?', [id]);
return row ? (objectToCamelCase(row) as Delivery) : null;
} catch (error) {
handleDatabaseError(error);
}
}

/**
* Create a new delivery
*/
async create(delivery: Omit<Delivery, 'deliveryId'>): Promise<Delivery> {
try {
const { sql, values } = buildInsertSQL('deliveries', delivery);
const result = await this.db.run(sql, values);

const createdDelivery = await this.findById(result.lastID!);
if (!createdDelivery) {
throw new Error('Failed to retrieve created delivery');
}

return createdDelivery;
} catch (error) {
handleDatabaseError(error);
}
}

/**
* Update delivery by ID
*/
async update(id: number, delivery: Partial<Omit<Delivery, 'deliveryId'>>): Promise<Delivery> {
try {
const { sql, values } = buildUpdateSQL('deliveries', delivery, 'delivery_id = ?');
const result = await this.db.run(sql, [...values, id]);

if (result.changes === 0) {
throw new NotFoundError('Delivery', id);
}

const updatedDelivery = await this.findById(id);
if (!updatedDelivery) {
throw new Error('Failed to retrieve updated delivery');
}

return updatedDelivery;
} catch (error) {
handleDatabaseError(error, 'Delivery', id);
}
}

/**
* Delete delivery by ID
*/
async delete(id: number): Promise<void> {
try {
const result = await this.db.run('DELETE FROM deliveries WHERE delivery_id = ?', [id]);

if (result.changes === 0) {
throw new NotFoundError('Delivery', id);
}
} catch (error) {
handleDatabaseError(error, 'Delivery', id);
}
}

/**
* Check if delivery exists
*/
async exists(id: number): Promise<boolean> {
try {
const result = await this.db.get<{ count: number }>(
'SELECT COUNT(*) as count FROM deliveries WHERE delivery_id = ?',
[id],
);
return (result?.count || 0) > 0;
} catch (error) {
handleDatabaseError(error);
}
super(db, {
tableName: 'deliveries',
idField: 'delivery_id',
entityName: 'Delivery',
});
}

/**
Expand Down
Loading