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 = {
async create(req, res) {
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;
if (!name) return res.status(400).json({ success: false, message: 'name is required' });
if (state && !['active', 'inactive'].includes(state)) {
return res.status(400).json({ success: false, message: 'Invalid state. Allowed: active, inactive' });
if (!pool_name) return res.status(400).json({ success: false, message: 'Pool name is required' });
if (!price || price < 0) return res.status(400).json({ success: false, message: 'Valid price is required' });
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 });
} catch (e) {
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)
async updateState(req, res) {
// Update pool active status
async updateActive(req, res) {
try {
const { id } = req.params || {};
const { state } = req.body || {};
const { is_active } = req.body || {};
const actorUserId = req.user && req.user.userId;
if (!id) return res.status(400).json({ success: false, message: 'id is required' });
if (!['active', 'inactive', 'archived'].includes(state)) {
return res.status(400).json({ success: false, message: 'Invalid state. Allowed: active, inactive, archived' });
if (typeof is_active !== 'boolean') {
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 });
} catch (e) {
if (e && e.status === 400) {
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' });
}
}

View File

@ -560,6 +560,27 @@ async function createDatabase() {
`);
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 ---
await connection.query(`
CREATE TABLE IF NOT EXISTS matrix_config (

View File

@ -1,9 +1,11 @@
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.name = name;
this.pool_name = pool_name;
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.updated_by = updated_by;
this.created_at = created_at;

View File

@ -5,14 +5,14 @@ class PoolRepository {
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 [res] = await conn.execute(
`INSERT INTO pools (name, description, state, created_by, updated_by)
VALUES (?, ?, ?, ?, NULL)`,
[name, description, state, created_by]
`INSERT INTO pools (pool_name, description, price, pool_type, is_active, created_by)
VALUES (?, ?, ?, ?, ?, ?)`,
[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() {
@ -20,7 +20,7 @@ class PoolRepository {
try {
console.debug('[PoolRepository.findAll] querying pools');
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
ORDER BY created_at DESC`
);
@ -36,30 +36,24 @@ class PoolRepository {
}
}
// NEW: update state with enforced transitions (active <-> inactive -> archived also supported)
async updateState(id, nextState, updated_by) {
// Update is_active flag (replaces old state transitions)
async updateActive(id, is_active, updated_by = null) {
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) {
const err = new Error('Pool not found');
err.status = 404;
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(
`UPDATE pools SET state = ?, updated_by = ?, updated_at = NOW() WHERE id = ?`,
[nextState, updated_by, id]
`UPDATE pools SET is_active = ?, updated_by = ?, updated_at = NOW() WHERE 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);
// Admin: set state for coffee product
router.patch('/admin/coffee/:id/state', authMiddleware, adminOnly, CoffeeController.setState);
// NEW: Admin pool state update
router.patch('/admin/pools/:id/state', authMiddleware, adminOnly, PoolController.updateState);
// NEW: Admin pool active status update
router.patch('/admin/pools/:id/active', authMiddleware, adminOnly, PoolController.updateActive);
// NEW: deactivate a matrix instance (admin-only)
router.patch('/admin/matrix/:id/deactivate', authMiddleware, adminOnly, MatrixController.deactivate);
// NEW: activate a matrix instance (admin-only)

View File

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