CentralBackend/repositories/subscriptions/CoffeeRepository.js
DeathKaioken baf53e36c1 Zipfelzwerg
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 23:47:25 +02:00

326 lines
9.8 KiB
JavaScript

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();