feat: simplify Coffee management by removing unnecessary fields and enforcing fixed billing defaults

This commit is contained in:
seaznCode 2025-11-20 17:38:04 +01:00
parent 20f69c272c
commit c62d775e66
4 changed files with 25 additions and 68 deletions

View File

@ -23,14 +23,12 @@ exports.list = async (req, res) => {
exports.create = async (req, res) => { exports.create = async (req, res) => {
try { try {
const { title, description, quantity, price } = req.body; const { title, description, price } = req.body;
const currency = req.body.currency || 'EUR'; const currency = req.body.currency || 'EUR';
const tax_rate = req.body.tax_rate !== undefined ? Number(req.body.tax_rate) : null;
const is_featured = req.body.is_featured === 'true' || req.body.is_featured === true ? true : false; const is_featured = req.body.is_featured === 'true' || req.body.is_featured === true ? true : false;
const billing_interval = req.body.billing_interval || null; // 'day'|'week'|'month'|'year' // Fixed billing defaults
const interval_count = req.body.interval_count !== undefined ? Number(req.body.interval_count) : null; // supports 6 months const billing_interval = 'month';
const sku = req.body.sku || null; const interval_count = 1;
const slug = req.body.slug || null;
const state = req.body.state === 'false' || req.body.state === false ? false : true; // default available const state = req.body.state === 'false' || req.body.state === false ? false : true; // default available
// If file uploaded, push to Exoscale and set object_storage_id // If file uploaded, push to Exoscale and set object_storage_id
@ -62,15 +60,11 @@ exports.create = async (req, res) => {
const created = await CoffeeService.create({ const created = await CoffeeService.create({
title, title,
description, description,
quantity: Number(quantity),
price: Number(price), price: Number(price),
currency, currency,
tax_rate,
is_featured, is_featured,
billing_interval, billing_interval,
interval_count, interval_count,
sku,
slug,
object_storage_id, object_storage_id,
original_filename, original_filename,
state, state,
@ -105,14 +99,9 @@ exports.create = async (req, res) => {
exports.update = async (req, res) => { exports.update = async (req, res) => {
try { try {
const id = parseInt(req.params.id, 10); const id = parseInt(req.params.id, 10);
const { title, description, quantity, price } = req.body; const { title, description, price } = req.body;
const currency = req.body.currency; const currency = req.body.currency;
const tax_rate = req.body.tax_rate !== undefined ? Number(req.body.tax_rate) : undefined;
const is_featured = req.body.is_featured === undefined ? undefined : (req.body.is_featured === 'true' || req.body.is_featured === true); const is_featured = req.body.is_featured === undefined ? undefined : (req.body.is_featured === 'true' || req.body.is_featured === true);
const billing_interval = req.body.billing_interval;
const interval_count = req.body.interval_count !== undefined ? Number(req.body.interval_count) : undefined;
const sku = req.body.sku;
const slug = req.body.slug;
const state = req.body.state === undefined ? undefined : (req.body.state === 'false' || req.body.state === false ? false : true); const state = req.body.state === undefined ? undefined : (req.body.state === 'false' || req.body.state === false ? false : true);
let object_storage_id; let object_storage_id;
@ -141,15 +130,9 @@ exports.update = async (req, res) => {
const updated = await CoffeeService.update(id, { const updated = await CoffeeService.update(id, {
title: title ?? current.title, title: title ?? current.title,
description: description ?? current.description, description: description ?? current.description,
quantity: quantity !== undefined ? Number(quantity) : current.quantity,
price: price !== undefined ? Number(price) : current.price, price: price !== undefined ? Number(price) : current.price,
currency: currency !== undefined ? currency : current.currency, currency: currency !== undefined ? currency : current.currency,
tax_rate: tax_rate !== undefined ? tax_rate : current.tax_rate,
is_featured: is_featured !== undefined ? is_featured : !!current.is_featured, is_featured: is_featured !== undefined ? is_featured : !!current.is_featured,
billing_interval: billing_interval !== undefined ? billing_interval : current.billing_interval,
interval_count: interval_count !== undefined ? interval_count : current.interval_count,
sku: sku !== undefined ? sku : current.sku,
slug: slug !== undefined ? slug : current.slug,
object_storage_id: object_storage_id !== undefined ? object_storage_id : current.object_storage_id, object_storage_id: object_storage_id !== undefined ? object_storage_id : current.object_storage_id,
original_filename: original_filename !== undefined ? original_filename : current.original_filename, original_filename: original_filename !== undefined ? original_filename : current.original_filename,
state: state !== undefined ? state : !!current.state, state: state !== undefined ? state : !!current.state,

View File

@ -540,7 +540,7 @@ async function createDatabase() {
`); `);
console.log('✅ Company stamps table created/verified'); console.log('✅ Company stamps table created/verified');
// --- Coffee / Subscriptions Table --- // --- Coffee / Subscriptions Table (simplified) ---
await connection.query(` await connection.query(`
CREATE TABLE IF NOT EXISTS coffee_table ( CREATE TABLE IF NOT EXISTS coffee_table (
id BIGINT AUTO_INCREMENT PRIMARY KEY, id BIGINT AUTO_INCREMENT PRIMARY KEY,
@ -548,21 +548,17 @@ async function createDatabase() {
description TEXT NOT NULL, description TEXT NOT NULL,
price DECIMAL(10,2) NOT NULL DEFAULT 0.00, price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
currency CHAR(3) NOT NULL DEFAULT 'EUR', currency CHAR(3) NOT NULL DEFAULT 'EUR',
tax_rate DECIMAL(5,2) NULL,
is_featured BOOLEAN NOT NULL DEFAULT FALSE, is_featured BOOLEAN NOT NULL DEFAULT FALSE,
billing_interval ENUM('day','week','month','year') NULL, billing_interval ENUM('day','week','month','year') NULL,
interval_count INT UNSIGNED NULL, interval_count INT UNSIGNED NULL,
sku VARCHAR(100) NULL,
slug VARCHAR(200) NULL,
object_storage_id VARCHAR(255) NULL, object_storage_id VARCHAR(255) NULL,
original_filename VARCHAR(255) NULL, original_filename VARCHAR(255) NULL,
state BOOLEAN NOT NULL DEFAULT TRUE, -- available=true, unavailable=false state BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
UNIQUE KEY uq_slug (slug)
); );
`); `);
console.log('✅ Coffee table created/verified'); console.log('✅ Coffee table (simplified) created/verified');
// --- Matrix: Global 5-ary tree config and relations --- // --- Matrix: Global 5-ary tree config and relations ---
await connection.query(` await connection.query(`

View File

@ -17,23 +17,19 @@ class CoffeeRepository {
async create(data, conn) { async create(data, conn) {
const cx = conn || db; const cx = conn || db;
const sql = `INSERT INTO coffee_table ( const sql = `INSERT INTO coffee_table (
title, description, quantity, price, currency, tax_rate, is_featured, title, description, price, currency, is_featured,
billing_interval, interval_count, sku, slug, billing_interval, interval_count,
object_storage_id, original_filename, object_storage_id, original_filename,
state, created_at, updated_at state, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`; ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`;
const params = [ const params = [
data.title, data.title,
data.description, data.description,
data.quantity,
data.price, data.price,
data.currency, data.currency,
data.tax_rate,
data.is_featured, data.is_featured,
data.billing_interval, data.billing_interval,
data.interval_count, data.interval_count,
data.sku,
data.slug,
data.object_storage_id, data.object_storage_id,
data.original_filename, data.original_filename,
data.state data.state
@ -46,23 +42,19 @@ class CoffeeRepository {
async update(id, data, conn) { async update(id, data, conn) {
const cx = conn || db; const cx = conn || db;
const sql = `UPDATE coffee_table const sql = `UPDATE coffee_table
SET title = ?, description = ?, quantity = ?, price = ?, currency = ?, tax_rate = ?, is_featured = ?, SET title = ?, description = ?, price = ?, currency = ?, is_featured = ?,
billing_interval = ?, interval_count = ?, sku = ?, slug = ?, billing_interval = ?, interval_count = ?,
object_storage_id = ?, original_filename = ?, object_storage_id = ?, original_filename = ?,
state = ?, updated_at = NOW() state = ?, updated_at = NOW()
WHERE id = ?`; WHERE id = ?`;
const params = [ const params = [
data.title, data.title,
data.description, data.description,
data.quantity,
data.price, data.price,
data.currency, data.currency,
data.tax_rate,
data.is_featured, data.is_featured,
data.billing_interval, data.billing_interval,
data.interval_count, data.interval_count,
data.sku,
data.slug,
data.object_storage_id, data.object_storage_id,
data.original_filename, data.original_filename,
data.state, data.state,

View File

@ -6,34 +6,14 @@ function validate(data) {
const errors = []; const errors = [];
if (!data.title || String(data.title).trim() === '') errors.push('title'); if (!data.title || String(data.title).trim() === '') errors.push('title');
if (!data.description || String(data.description).trim() === '') errors.push('description'); if (!data.description || String(data.description).trim() === '') errors.push('description');
const q = Number(data.quantity);
if (!Number.isFinite(q) || q < 0) errors.push('quantity');
const price = Number(data.price); const price = Number(data.price);
if (!Number.isFinite(price) || price < 0) errors.push('price'); if (!Number.isFinite(price) || price < 0) errors.push('price');
// state is boolean (available=true/unavailable=false)
if (typeof data.state !== 'boolean') errors.push('state'); if (typeof data.state !== 'boolean') errors.push('state');
// currency optional; default EUR if missing
if (data.currency && String(data.currency).length > 3) errors.push('currency'); if (data.currency && String(data.currency).length > 3) errors.push('currency');
// tax_rate optional must be >= 0 if provided
if (data.tax_rate !== undefined && data.tax_rate !== null) {
const tr = Number(data.tax_rate);
if (!Number.isFinite(tr) || tr < 0) errors.push('tax_rate');
}
// is_featured boolean
if (typeof data.is_featured !== 'boolean') errors.push('is_featured'); if (typeof data.is_featured !== 'boolean') errors.push('is_featured');
// Enforce fixed billing defaults (month/1) if provided differently
// billing_interval/interval_count validation if (data.billing_interval && data.billing_interval !== 'month') errors.push('billing_interval');
if (data.billing_interval !== undefined || data.interval_count !== undefined) { if (data.interval_count && Number(data.interval_count) !== 1) errors.push('interval_count');
const allowed = ['day','week','month','year'];
if (data.billing_interval && !allowed.includes(String(data.billing_interval))) errors.push('billing_interval');
if (data.interval_count !== undefined) {
const ic = Number(data.interval_count);
if (!Number.isFinite(ic) || ic <= 0) errors.push('interval_count');
}
}
return errors; return errors;
} }
@ -47,6 +27,9 @@ class CoffeeService {
} }
async create(data) { async create(data) {
// Force billing defaults
data.billing_interval = 'month';
data.interval_count = 1;
const errors = validate(data); const errors = validate(data);
if (errors.length) { if (errors.length) {
logger.warn('[CoffeeService.create] validation_failed', { errors }); logger.warn('[CoffeeService.create] validation_failed', { errors });
@ -65,6 +48,9 @@ class CoffeeService {
} }
async update(id, data) { async update(id, data) {
// Keep billing values fixed regardless of incoming payload
data.billing_interval = 'month';
data.interval_count = 1;
const errors = validate(data); const errors = validate(data);
if (errors.length) { if (errors.length) {
logger.warn('[CoffeeService.update] validation_failed', { id, errors }); logger.warn('[CoffeeService.update] validation_failed', { id, errors });