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) => {
try {
const { title, description, quantity, price } = req.body;
const { title, description, price } = req.body;
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 billing_interval = req.body.billing_interval || null; // 'day'|'week'|'month'|'year'
const interval_count = req.body.interval_count !== undefined ? Number(req.body.interval_count) : null; // supports 6 months
const sku = req.body.sku || null;
const slug = req.body.slug || null;
// Fixed billing defaults
const billing_interval = 'month';
const interval_count = 1;
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
@ -62,15 +60,11 @@ exports.create = async (req, res) => {
const created = await CoffeeService.create({
title,
description,
quantity: Number(quantity),
price: Number(price),
currency,
tax_rate,
is_featured,
billing_interval,
interval_count,
sku,
slug,
object_storage_id,
original_filename,
state,
@ -105,14 +99,9 @@ exports.create = async (req, res) => {
exports.update = async (req, res) => {
try {
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 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 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);
let object_storage_id;
@ -141,15 +130,9 @@ exports.update = async (req, res) => {
const updated = await CoffeeService.update(id, {
title: title ?? current.title,
description: description ?? current.description,
quantity: quantity !== undefined ? Number(quantity) : current.quantity,
price: price !== undefined ? Number(price) : current.price,
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,
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,
original_filename: original_filename !== undefined ? original_filename : current.original_filename,
state: state !== undefined ? state : !!current.state,

View File

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

View File

@ -17,28 +17,24 @@ class CoffeeRepository {
async create(data, conn) {
const cx = conn || db;
const sql = `INSERT INTO coffee_table (
title, description, quantity, price, currency, tax_rate, is_featured,
billing_interval, interval_count, sku, slug,
title, description, price, currency, is_featured,
billing_interval, interval_count,
object_storage_id, original_filename,
state, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`;
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`;
const params = [
data.title,
data.description,
data.quantity,
data.price,
data.currency,
data.tax_rate,
data.is_featured,
data.billing_interval,
data.interval_count,
data.sku,
data.slug,
data.object_storage_id,
data.original_filename,
data.state
];
const [result] = await cx.query(sql, params);
const [result] = await cx.query(sql, params);
logger.info('[CoffeeRepository.create] insert', { id: result.insertId });
return { id: result.insertId, ...data };
}
@ -46,29 +42,25 @@ class CoffeeRepository {
async update(id, data, conn) {
const cx = conn || db;
const sql = `UPDATE coffee_table
SET title = ?, description = ?, quantity = ?, price = ?, currency = ?, tax_rate = ?, is_featured = ?,
billing_interval = ?, interval_count = ?, sku = ?, slug = ?,
SET title = ?, description = ?, price = ?, currency = ?, is_featured = ?,
billing_interval = ?, interval_count = ?,
object_storage_id = ?, original_filename = ?,
state = ?, updated_at = NOW()
WHERE id = ?`;
const params = [
data.title,
data.description,
data.quantity,
data.price,
data.currency,
data.tax_rate,
data.is_featured,
data.billing_interval,
data.interval_count,
data.sku,
data.slug,
data.object_storage_id,
data.original_filename,
data.state,
id
];
const [result] = await cx.query(sql, params);
const [result] = await cx.query(sql, params);
logger.info('[CoffeeRepository.update] update', { id, affected: result.affectedRows });
return result.affectedRows > 0;
}

View File

@ -6,34 +6,14 @@ function validate(data) {
const errors = [];
if (!data.title || String(data.title).trim() === '') errors.push('title');
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);
if (!Number.isFinite(price) || price < 0) errors.push('price');
// state is boolean (available=true/unavailable=false)
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');
// 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');
// billing_interval/interval_count validation
if (data.billing_interval !== undefined || data.interval_count !== undefined) {
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');
}
}
// Enforce fixed billing defaults (month/1) if provided differently
if (data.billing_interval && data.billing_interval !== 'month') errors.push('billing_interval');
if (data.interval_count && Number(data.interval_count) !== 1) errors.push('interval_count');
return errors;
}
@ -47,6 +27,9 @@ class CoffeeService {
}
async create(data) {
// Force billing defaults
data.billing_interval = 'month';
data.interval_count = 1;
const errors = validate(data);
if (errors.length) {
logger.warn('[CoffeeService.create] validation_failed', { errors });
@ -65,6 +48,9 @@ class CoffeeService {
}
async update(id, data) {
// Keep billing values fixed regardless of incoming payload
data.billing_interval = 'month';
data.interval_count = 1;
const errors = validate(data);
if (errors.length) {
logger.warn('[CoffeeService.update] validation_failed', { id, errors });