feat: enhance pool management with new properties and active status updates

This commit is contained in:
seaznCode 2025-12-04 18:26:39 +01:00
parent 8e450cd9c5
commit 4baafedc79
6 changed files with 70 additions and 52 deletions

View File

@ -3,13 +3,14 @@ const { createPool, listPools, updatePoolState } = require('../../services/pool/
module.exports = { module.exports = {
async create(req, res) { async create(req, res) {
try { try {
const { name, description, state } = req.body || {}; const { pool_name, description, price, pool_type, is_active } = req.body || {};
const actorUserId = req.user && req.user.userId; const actorUserId = req.user && req.user.userId;
if (!name) return res.status(400).json({ success: false, message: 'name is required' }); if (!pool_name) return res.status(400).json({ success: false, message: 'Pool name is required' });
if (state && !['active', 'inactive'].includes(state)) { if (!price || price < 0) return res.status(400).json({ success: false, message: 'Valid price is required' });
return res.status(400).json({ success: false, message: 'Invalid state. Allowed: active, inactive' }); if (pool_type && !['coffee', 'other'].includes(pool_type)) {
return res.status(400).json({ success: false, message: 'Invalid pool_type. Allowed: coffee, other' });
} }
const pool = await createPool({ name, description, state, actorUserId }); const pool = await createPool({ pool_name, description, price, pool_type, is_active, created_by: actorUserId });
return res.status(201).json({ success: true, data: pool }); return res.status(201).json({ success: true, data: pool });
} catch (e) { } catch (e) {
if (e && (e.code === 'ER_DUP_ENTRY' || e.errno === 1062)) { if (e && (e.code === 'ER_DUP_ENTRY' || e.errno === 1062)) {
@ -34,23 +35,23 @@ module.exports = {
} }
}, },
// NEW: optional state update handler (route can be added later) // Update pool active status
async updateState(req, res) { async updateActive(req, res) {
try { try {
const { id } = req.params || {}; const { id } = req.params || {};
const { state } = req.body || {}; const { is_active } = req.body || {};
const actorUserId = req.user && req.user.userId; const actorUserId = req.user && req.user.userId;
if (!id) return res.status(400).json({ success: false, message: 'id is required' }); if (!id) return res.status(400).json({ success: false, message: 'id is required' });
if (!['active', 'inactive', 'archived'].includes(state)) { if (typeof is_active !== 'boolean') {
return res.status(400).json({ success: false, message: 'Invalid state. Allowed: active, inactive, archived' }); return res.status(400).json({ success: false, message: 'is_active must be a boolean' });
} }
const updated = await updatePoolState(id, state, actorUserId); const updated = await updatePoolState(id, is_active, actorUserId);
return res.status(200).json({ success: true, data: updated }); return res.status(200).json({ success: true, data: updated });
} catch (e) { } catch (e) {
if (e && e.status === 400) { if (e && e.status === 400) {
return res.status(400).json({ success: false, message: e.message }); return res.status(400).json({ success: false, message: e.message });
} }
console.error('[PoolController.updateState]', e); console.error('[PoolController.updateActive]', e);
return res.status(500).json({ success: false, message: 'Internal server error' }); return res.status(500).json({ success: false, message: 'Internal server error' });
} }
} }

View File

@ -560,6 +560,27 @@ async function createDatabase() {
`); `);
console.log('✅ Coffee table (simplified) created/verified'); console.log('✅ Coffee table (simplified) created/verified');
// --- Pools Table ---
await connection.query(`
CREATE TABLE IF NOT EXISTS pools (
id INT AUTO_INCREMENT PRIMARY KEY,
pool_name VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
pool_type ENUM('coffee', 'other') NOT NULL DEFAULT 'other',
is_active BOOLEAN DEFAULT TRUE,
created_by INT NULL,
updated_by INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE,
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE,
INDEX idx_pool_type (pool_type),
INDEX idx_is_active (is_active)
);
`);
console.log('✅ Pools table created/verified');
// --- Matrix: Global 5-ary tree config and relations --- // --- Matrix: Global 5-ary tree config and relations ---
await connection.query(` await connection.query(`
CREATE TABLE IF NOT EXISTS matrix_config ( CREATE TABLE IF NOT EXISTS matrix_config (

View File

@ -1,9 +1,11 @@
class Pool { class Pool {
constructor({ id = null, name, description = null, state = 'active', created_by = null, updated_by = null, created_at = null, updated_at = null }) { constructor({ id = null, pool_name, description = null, price = 0.00, pool_type = 'other', is_active = true, created_by = null, updated_by = null, created_at = null, updated_at = null }) {
this.id = id; this.id = id;
this.name = name; this.pool_name = pool_name;
this.description = description; this.description = description;
this.state = state; this.price = price;
this.pool_type = pool_type;
this.is_active = is_active;
this.created_by = created_by; this.created_by = created_by;
this.updated_by = updated_by; this.updated_by = updated_by;
this.created_at = created_at; this.created_at = created_at;

View File

@ -5,14 +5,14 @@ class PoolRepository {
this.uow = uow; this.uow = uow;
} }
async create({ name, description = null, state = 'active', created_by }) { async create({ pool_name, description = null, price = 0.00, pool_type = 'other', is_active = true, created_by = null }) {
const conn = this.uow.connection; const conn = this.uow.connection;
const [res] = await conn.execute( const [res] = await conn.execute(
`INSERT INTO pools (name, description, state, created_by, updated_by) `INSERT INTO pools (pool_name, description, price, pool_type, is_active, created_by)
VALUES (?, ?, ?, ?, NULL)`, VALUES (?, ?, ?, ?, ?, ?)`,
[name, description, state, created_by] [pool_name, description, price, pool_type, is_active, created_by]
); );
return new Pool({ id: res.insertId, name, description, state, created_by, updated_by: null }); return new Pool({ id: res.insertId, pool_name, description, price, pool_type, is_active, created_by });
} }
async findAll() { async findAll() {
@ -20,7 +20,7 @@ class PoolRepository {
try { try {
console.debug('[PoolRepository.findAll] querying pools'); console.debug('[PoolRepository.findAll] querying pools');
const [rows] = await conn.execute( const [rows] = await conn.execute(
`SELECT id, name, description, state, created_at, updated_at `SELECT id, pool_name, description, price, pool_type, is_active, created_by, updated_by, created_at, updated_at
FROM pools FROM pools
ORDER BY created_at DESC` ORDER BY created_at DESC`
); );
@ -36,30 +36,24 @@ class PoolRepository {
} }
} }
// NEW: update state with enforced transitions (active <-> inactive -> archived also supported) // Update is_active flag (replaces old state transitions)
async updateState(id, nextState, updated_by) { async updateActive(id, is_active, updated_by = null) {
const conn = this.uow.connection; const conn = this.uow.connection;
const [rows] = await conn.execute(`SELECT id, state FROM pools WHERE id = ?`, [id]); const [rows] = await conn.execute(`SELECT id FROM pools WHERE id = ?`, [id]);
if (!rows || rows.length === 0) { if (!rows || rows.length === 0) {
const err = new Error('Pool not found'); const err = new Error('Pool not found');
err.status = 404; err.status = 404;
throw err; throw err;
} }
const current = rows[0].state;
const allowed = ['active', 'inactive', 'archived'];
if (!allowed.includes(current) || !allowed.includes(nextState)) {
const err = new Error('Invalid state transition');
err.status = 400;
throw err;
}
if (current === nextState) {
return new Pool({ id, state: current, updated_by }); // no-op
}
await conn.execute( await conn.execute(
`UPDATE pools SET state = ?, updated_by = ?, updated_at = NOW() WHERE id = ?`, `UPDATE pools SET is_active = ?, updated_by = ?, updated_at = NOW() WHERE id = ?`,
[nextState, updated_by, id] [is_active, updated_by, id]
); );
return new Pool({ id, state: nextState, updated_by }); const [updated] = await conn.execute(
`SELECT id, pool_name, description, price, pool_type, is_active, created_by, updated_by, created_at, updated_at FROM pools WHERE id = ?`,
[id]
);
return new Pool(updated[0]);
} }
} }

View File

@ -38,8 +38,8 @@ router.patch('/admin/update-user-profile/:id', authMiddleware, adminOnly, AdminU
router.patch('/admin/update-user-status/:id', authMiddleware, adminOnly, AdminUserController.updateUserStatus); router.patch('/admin/update-user-status/:id', authMiddleware, adminOnly, AdminUserController.updateUserStatus);
// Admin: set state for coffee product // Admin: set state for coffee product
router.patch('/admin/coffee/:id/state', authMiddleware, adminOnly, CoffeeController.setState); router.patch('/admin/coffee/:id/state', authMiddleware, adminOnly, CoffeeController.setState);
// NEW: Admin pool state update // NEW: Admin pool active status update
router.patch('/admin/pools/:id/state', authMiddleware, adminOnly, PoolController.updateState); router.patch('/admin/pools/:id/active', authMiddleware, adminOnly, PoolController.updateActive);
// NEW: deactivate a matrix instance (admin-only) // NEW: deactivate a matrix instance (admin-only)
router.patch('/admin/matrix/:id/deactivate', authMiddleware, adminOnly, MatrixController.deactivate); router.patch('/admin/matrix/:id/deactivate', authMiddleware, adminOnly, MatrixController.deactivate);
// NEW: activate a matrix instance (admin-only) // NEW: activate a matrix instance (admin-only)

View File

@ -1,22 +1,22 @@
const UnitOfWork = require('../../database/UnitOfWork'); const UnitOfWork = require('../../database/UnitOfWork');
const PoolRepository = require('../../repositories/pool/poolRepository'); const PoolRepository = require('../../repositories/pool/poolRepository');
function isValidState(state) { function isValidPoolType(pool_type) {
return state === undefined || state === null || state === 'active' || state === 'inactive' || state === 'archived'; return pool_type === 'coffee' || pool_type === 'other';
} }
async function createPool({ name, description = null, state = 'active', actorUserId }) { async function createPool({ pool_name, description = null, price = 0.00, pool_type = 'other', is_active = true, created_by = null }) {
if (!isValidState(state)) { if (!isValidPoolType(pool_type)) {
const err = new Error('Invalid state. Allowed: active, inactive, archived'); const err = new Error('Invalid pool_type. Allowed: coffee, other');
err.status = 400; err.status = 400;
throw err; throw err;
} }
const uow = new UnitOfWork(); const uow = new UnitOfWork();
try { try {
console.debug('[PoolService.createPool] start', { name, state }); console.debug('[PoolService.createPool] start', { pool_name, pool_type });
await uow.start(); await uow.start();
const repo = new PoolRepository(uow); const repo = new PoolRepository(uow);
const pool = await repo.create({ name, description, state, created_by: actorUserId }); const pool = await repo.create({ pool_name, description, price, pool_type, is_active, created_by });
await uow.commit(); await uow.commit();
console.debug('[PoolService.createPool] success', { id: pool.id }); console.debug('[PoolService.createPool] success', { id: pool.id });
return pool; return pool;
@ -45,20 +45,20 @@ async function listPools() {
} }
} }
async function updatePoolState(id, nextState, actorUserId) { async function updatePoolState(id, is_active, actorUserId) {
if (!isValidState(nextState) || !['active', 'inactive', 'archived'].includes(nextState)) { if (typeof is_active !== 'boolean') {
const err = new Error('Invalid state. Allowed: active, inactive, archived'); const err = new Error('is_active must be a boolean');
err.status = 400; err.status = 400;
throw err; throw err;
} }
const uow = new UnitOfWork(); const uow = new UnitOfWork();
try { try {
console.debug('[PoolService.updatePoolState] start', { id, nextState }); console.debug('[PoolService.updatePoolState] start', { id, is_active });
await uow.start(); await uow.start();
const repo = new PoolRepository(uow); const repo = new PoolRepository(uow);
const updated = await repo.updateState(id, nextState, actorUserId); const updated = await repo.updateActive(id, is_active, actorUserId);
await uow.commit(); await uow.commit();
console.debug('[PoolService.updatePoolState] success', { id, state: nextState }); console.debug('[PoolService.updatePoolState] success', { id, is_active });
return updated; return updated;
} catch (err) { } catch (err) {
console.error('[PoolService.updatePoolState] error', err); console.error('[PoolService.updatePoolState] error', err);