const db = require('../../database/database'); const { logger } = require('../../middleware/logger'); class CoffeeRepository { async _syncSortOrders(coffeeId, conn) { const cx = conn || db; const [rows] = await cx.query( `SELECT id FROM coffee_table_images WHERE coffee_id = ? ORDER BY sort_order ASC, id ASC`, [coffeeId] ); for (let i = 0; i < (rows || []).length; i += 1) { await cx.query('UPDATE coffee_table_images SET sort_order = ? WHERE id = ?', [i, rows[i].id]); } } async ensureLegacyPictureRow(coffeeId, conn) { const cx = conn || db; const [coffeeRows] = await cx.query( `SELECT object_storage_id, original_filename FROM coffee_table WHERE id = ? LIMIT 1`, [coffeeId] ); const coffee = coffeeRows?.[0]; if (!coffee || !coffee.object_storage_id) return; const [countRows] = await cx.query( `SELECT COUNT(*) AS c FROM coffee_table_images WHERE coffee_id = ?`, [coffeeId] ); const count = Number(countRows?.[0]?.c || 0); if (count > 0) return; await cx.query( `INSERT INTO coffee_table_images (coffee_id, object_storage_id, original_filename, sort_order) VALUES (?, ?, ?, 0)`, [coffeeId, coffee.object_storage_id, coffee.original_filename || null] ); } async listPicturesByCoffeeId(coffeeId, conn) { const cx = conn || db; const [rows] = await cx.query( `SELECT id, coffee_id, object_storage_id, original_filename, sort_order, created_at FROM coffee_table_images WHERE coffee_id = ? ORDER BY sort_order ASC, id ASC`, [coffeeId] ); return rows || []; } async deleteAllPicturesByCoffeeId(coffeeId, conn) { const cx = conn || db; const toDelete = await this.listPicturesByCoffeeId(coffeeId, cx); if (!toDelete.length) return []; await cx.query('DELETE FROM coffee_table_images WHERE coffee_id = ?', [coffeeId]); return toDelete; } async deletePicturesByIds(coffeeId, pictureIds, conn) { const cx = conn || db; const ids = Array.isArray(pictureIds) ? pictureIds.map((x) => Number(x)).filter((x) => Number.isFinite(x) && x > 0) : []; if (!ids.length) return []; const placeholders = ids.map(() => '?').join(','); const [rows] = await cx.query( `SELECT id, coffee_id, object_storage_id, original_filename, sort_order, created_at FROM coffee_table_images WHERE coffee_id = ? AND id IN (${placeholders})`, [coffeeId, ...ids] ); if (!(rows || []).length) return []; const deletePlaceholders = rows.map(() => '?').join(','); await cx.query( `DELETE FROM coffee_table_images WHERE coffee_id = ? AND id IN (${deletePlaceholders})`, [coffeeId, ...rows.map((r) => r.id)] ); return rows; } async addPictures(coffeeId, images, conn) { const cx = conn || db; const existing = await this.listPicturesByCoffeeId(coffeeId, cx); let nextSort = existing.length; for (const img of (images || [])) { await cx.query( `INSERT INTO coffee_table_images (coffee_id, object_storage_id, original_filename, sort_order) VALUES (?, ?, ?, ?)`, [ coffeeId, img.object_storage_id, img.original_filename || null, nextSort, ] ); nextSort += 1; } } async syncPrimaryPictureFromGallery(coffeeId, conn) { const cx = conn || db; const [rows] = await cx.query( `SELECT object_storage_id, original_filename FROM coffee_table_images WHERE coffee_id = ? ORDER BY sort_order ASC, id ASC LIMIT 1`, [coffeeId] ); const first = rows?.[0]; await cx.query( `UPDATE coffee_table SET object_storage_id = ?, original_filename = ?, updated_at = NOW() WHERE id = ?`, [first?.object_storage_id || null, first?.original_filename || null, coffeeId] ); } async _attachPictures(rows, conn) { const cx = conn || db; if (!Array.isArray(rows) || !rows.length) return rows || []; const ids = rows.map((r) => Number(r.id)).filter((id) => Number.isFinite(id)); if (!ids.length) return rows; const placeholders = ids.map(() => '?').join(','); const [pictureRows] = await cx.query( `SELECT id, coffee_id, object_storage_id, original_filename, sort_order, created_at FROM coffee_table_images WHERE coffee_id IN (${placeholders}) ORDER BY coffee_id ASC, sort_order ASC, id ASC`, ids ); const grouped = new Map(); for (const p of (pictureRows || [])) { const key = Number(p.coffee_id); if (!grouped.has(key)) grouped.set(key, []); grouped.get(key).push({ id: Number(p.id), coffee_id: Number(p.coffee_id), object_storage_id: p.object_storage_id, original_filename: p.original_filename, sort_order: Number(p.sort_order || 0), created_at: p.created_at, }); } for (const row of rows) { const coffeeId = Number(row.id); const fromTable = grouped.get(coffeeId) || []; if (fromTable.length) { row.pictures = fromTable; } else if (row.object_storage_id) { row.pictures = [{ id: null, coffee_id: coffeeId, object_storage_id: row.object_storage_id, original_filename: row.original_filename || null, sort_order: 0, created_at: row.created_at || null, }]; } else { row.pictures = []; } } return rows; } async listAll(conn) { const cx = conn || db; const [rows] = await cx.query('SELECT * FROM coffee_table ORDER BY id DESC'); return this._attachPictures(rows || [], cx); } async getById(id, conn) { const cx = conn || db; const [rows] = await cx.query('SELECT * FROM coffee_table WHERE id = ? LIMIT 1', [id]); if (!rows || !rows[0]) return null; const hydrated = await this._attachPictures([rows[0]], cx); return hydrated[0] || null; } async create(data, conn) { const cx = conn || db; const sql = `INSERT INTO coffee_table ( title, description, price, currency, is_featured, billing_interval, interval_count, object_storage_id, original_filename, state, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`; const params = [ data.title, data.description, data.price, data.currency, data.is_featured, data.billing_interval, data.interval_count, data.object_storage_id, data.original_filename, data.state ]; const [result] = await cx.query(sql, params); const createdId = result.insertId; const pictures = Array.isArray(data.images) ? [...data.images] : []; if (!pictures.length && data.object_storage_id) { pictures.push({ object_storage_id: data.object_storage_id, original_filename: data.original_filename || null, sort_order: 0, }); } for (const picture of pictures) { await cx.query( `INSERT INTO coffee_table_images (coffee_id, object_storage_id, original_filename, sort_order) VALUES (?, ?, ?, ?)`, [ createdId, picture.object_storage_id, picture.original_filename || null, Number.isFinite(Number(picture.sort_order)) ? Number(picture.sort_order) : 0, ] ); } logger.info('[CoffeeRepository.create] insert', { id: result.insertId }); return this.getById(createdId, cx); } async update(id, data, conn) { const cx = conn || db; const sql = `UPDATE coffee_table 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.price, data.currency, data.is_featured, data.billing_interval, data.interval_count, data.object_storage_id, data.original_filename, data.state, id ]; const [result] = await cx.query(sql, params); logger.info('[CoffeeRepository.update] update', { id, affected: result.affectedRows }); return result.affectedRows > 0; } async setState(id, state, conn) { const cx = conn || db; const [result] = await cx.query('UPDATE coffee_table SET state = ?, updated_at = NOW() WHERE id = ?', [state, id]); return result.affectedRows > 0; } async delete(id, conn) { const cx = conn || db; const [result] = await cx.query('DELETE FROM coffee_table WHERE id = ?', [id]); return result.affectedRows > 0; } async listActive(conn) { const cx = conn || db; const [rows] = await cx.query('SELECT * FROM coffee_table WHERE state = TRUE ORDER BY id DESC'); return this._attachPictures(rows || [], cx); } async editPictures(coffeeId, { replaceAll = false, removePictureIds = [], images = [] } = {}, conn) { const cx = conn || db; await this.ensureLegacyPictureRow(coffeeId, cx); let deleted = []; if (replaceAll) { deleted = await this.deleteAllPicturesByCoffeeId(coffeeId, cx); } else if (Array.isArray(removePictureIds) && removePictureIds.length) { deleted = await this.deletePicturesByIds(coffeeId, removePictureIds, cx); } if (Array.isArray(images) && images.length) { await this.addPictures(coffeeId, images, cx); } await this._syncSortOrders(coffeeId, cx); await this.syncPrimaryPictureFromGallery(coffeeId, cx); const updated = await this.getById(coffeeId, cx); return { updated, deleted, }; } } module.exports = new CoffeeRepository();