mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
refactor: extract business logic from routes into reusable service modules
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
import { db, canAccessTrip } from '../db/database';
|
||||
|
||||
const BAG_COLORS = ['#6366f1', '#ec4899', '#f97316', '#10b981', '#06b6d4', '#8b5cf6', '#ef4444', '#f59e0b'];
|
||||
|
||||
export function verifyTripAccess(tripId: string | number, userId: number) {
|
||||
return canAccessTrip(tripId, userId);
|
||||
}
|
||||
|
||||
// ── Items ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export function listItems(tripId: string | number) {
|
||||
return db.prepare(
|
||||
'SELECT * FROM packing_items WHERE trip_id = ? ORDER BY sort_order ASC, created_at ASC'
|
||||
).all(tripId);
|
||||
}
|
||||
|
||||
export function createItem(tripId: string | number, data: { name: string; category?: string; checked?: boolean }) {
|
||||
const maxOrder = db.prepare('SELECT MAX(sort_order) as max FROM packing_items WHERE trip_id = ?').get(tripId) as { max: number | null };
|
||||
const sortOrder = (maxOrder.max !== null ? maxOrder.max : -1) + 1;
|
||||
|
||||
const result = db.prepare(
|
||||
'INSERT INTO packing_items (trip_id, name, checked, category, sort_order) VALUES (?, ?, ?, ?, ?)'
|
||||
).run(tripId, data.name, data.checked ? 1 : 0, data.category || 'Allgemein', sortOrder);
|
||||
|
||||
return db.prepare('SELECT * FROM packing_items WHERE id = ?').get(result.lastInsertRowid);
|
||||
}
|
||||
|
||||
export function updateItem(
|
||||
tripId: string | number,
|
||||
id: string | number,
|
||||
data: { name?: string; checked?: number; category?: string; weight_grams?: number | null; bag_id?: number | null },
|
||||
bodyKeys: string[]
|
||||
) {
|
||||
const item = db.prepare('SELECT * FROM packing_items WHERE id = ? AND trip_id = ?').get(id, tripId);
|
||||
if (!item) return null;
|
||||
|
||||
db.prepare(`
|
||||
UPDATE packing_items SET
|
||||
name = COALESCE(?, name),
|
||||
checked = CASE WHEN ? IS NOT NULL THEN ? ELSE checked END,
|
||||
category = COALESCE(?, category),
|
||||
weight_grams = CASE WHEN ? THEN ? ELSE weight_grams END,
|
||||
bag_id = CASE WHEN ? THEN ? ELSE bag_id END
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
data.name || null,
|
||||
data.checked !== undefined ? 1 : null,
|
||||
data.checked ? 1 : 0,
|
||||
data.category || null,
|
||||
bodyKeys.includes('weight_grams') ? 1 : 0,
|
||||
data.weight_grams ?? null,
|
||||
bodyKeys.includes('bag_id') ? 1 : 0,
|
||||
data.bag_id ?? null,
|
||||
id
|
||||
);
|
||||
|
||||
return db.prepare('SELECT * FROM packing_items WHERE id = ?').get(id);
|
||||
}
|
||||
|
||||
export function deleteItem(tripId: string | number, id: string | number) {
|
||||
const item = db.prepare('SELECT id FROM packing_items WHERE id = ? AND trip_id = ?').get(id, tripId);
|
||||
if (!item) return false;
|
||||
|
||||
db.prepare('DELETE FROM packing_items WHERE id = ?').run(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Bulk Import ────────────────────────────────────────────────────────────
|
||||
|
||||
interface ImportItem {
|
||||
name?: string;
|
||||
checked?: boolean;
|
||||
category?: string;
|
||||
weight_grams?: string | number;
|
||||
bag?: string;
|
||||
}
|
||||
|
||||
export function bulkImport(tripId: string | number, items: ImportItem[]) {
|
||||
const maxOrder = db.prepare('SELECT MAX(sort_order) as max FROM packing_items WHERE trip_id = ?').get(tripId) as { max: number | null };
|
||||
let sortOrder = (maxOrder.max !== null ? maxOrder.max : -1) + 1;
|
||||
|
||||
const stmt = db.prepare('INSERT INTO packing_items (trip_id, name, checked, category, weight_grams, bag_id, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?)');
|
||||
const created: any[] = [];
|
||||
|
||||
const insertAll = db.transaction(() => {
|
||||
for (const item of items) {
|
||||
if (!item.name?.trim()) continue;
|
||||
const checked = item.checked ? 1 : 0;
|
||||
const weight = item.weight_grams ? parseInt(String(item.weight_grams)) || null : null;
|
||||
|
||||
// Resolve bag by name if provided
|
||||
let bagId = null;
|
||||
if (item.bag?.trim()) {
|
||||
const bagName = item.bag.trim();
|
||||
const existing = db.prepare('SELECT id FROM packing_bags WHERE trip_id = ? AND name = ?').get(tripId, bagName) as { id: number } | undefined;
|
||||
if (existing) {
|
||||
bagId = existing.id;
|
||||
} else {
|
||||
const bagCount = (db.prepare('SELECT COUNT(*) as c FROM packing_bags WHERE trip_id = ?').get(tripId) as { c: number }).c;
|
||||
const newBag = db.prepare('INSERT INTO packing_bags (trip_id, name, color) VALUES (?, ?, ?)').run(tripId, bagName, BAG_COLORS[bagCount % BAG_COLORS.length]);
|
||||
bagId = newBag.lastInsertRowid;
|
||||
}
|
||||
}
|
||||
|
||||
const result = stmt.run(tripId, item.name.trim(), checked, item.category?.trim() || 'Other', weight, bagId, sortOrder++);
|
||||
created.push(db.prepare('SELECT * FROM packing_items WHERE id = ?').get(result.lastInsertRowid));
|
||||
}
|
||||
});
|
||||
|
||||
insertAll();
|
||||
return created;
|
||||
}
|
||||
|
||||
// ── Bags ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export function listBags(tripId: string | number) {
|
||||
return db.prepare('SELECT * FROM packing_bags WHERE trip_id = ? ORDER BY sort_order, id').all(tripId);
|
||||
}
|
||||
|
||||
export function createBag(tripId: string | number, data: { name: string; color?: string }) {
|
||||
const maxOrder = db.prepare('SELECT MAX(sort_order) as max FROM packing_bags WHERE trip_id = ?').get(tripId) as { max: number | null };
|
||||
const result = db.prepare('INSERT INTO packing_bags (trip_id, name, color, sort_order) VALUES (?, ?, ?, ?)').run(
|
||||
tripId, data.name.trim(), data.color || '#6366f1', (maxOrder.max ?? -1) + 1
|
||||
);
|
||||
return db.prepare('SELECT * FROM packing_bags WHERE id = ?').get(result.lastInsertRowid);
|
||||
}
|
||||
|
||||
export function updateBag(
|
||||
tripId: string | number,
|
||||
bagId: string | number,
|
||||
data: { name?: string; color?: string; weight_limit_grams?: number | null }
|
||||
) {
|
||||
const bag = db.prepare('SELECT * FROM packing_bags WHERE id = ? AND trip_id = ?').get(bagId, tripId);
|
||||
if (!bag) return null;
|
||||
|
||||
db.prepare('UPDATE packing_bags SET name = COALESCE(?, name), color = COALESCE(?, color), weight_limit_grams = ? WHERE id = ?').run(
|
||||
data.name?.trim() || null, data.color || null, data.weight_limit_grams ?? null, bagId
|
||||
);
|
||||
return db.prepare('SELECT * FROM packing_bags WHERE id = ?').get(bagId);
|
||||
}
|
||||
|
||||
export function deleteBag(tripId: string | number, bagId: string | number) {
|
||||
const bag = db.prepare('SELECT * FROM packing_bags WHERE id = ? AND trip_id = ?').get(bagId, tripId);
|
||||
if (!bag) return false;
|
||||
|
||||
db.prepare('DELETE FROM packing_bags WHERE id = ?').run(bagId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Apply Template ─────────────────────────────────────────────────────────
|
||||
|
||||
export function applyTemplate(tripId: string | number, templateId: string | number) {
|
||||
const templateItems = db.prepare(`
|
||||
SELECT ti.name, tc.name as category
|
||||
FROM packing_template_items ti
|
||||
JOIN packing_template_categories tc ON ti.category_id = tc.id
|
||||
WHERE tc.template_id = ?
|
||||
ORDER BY tc.sort_order, ti.sort_order
|
||||
`).all(templateId) as { name: string; category: string }[];
|
||||
|
||||
if (templateItems.length === 0) return null;
|
||||
|
||||
const maxOrder = db.prepare('SELECT MAX(sort_order) as max FROM packing_items WHERE trip_id = ?').get(tripId) as { max: number | null };
|
||||
let sortOrder = (maxOrder.max !== null ? maxOrder.max : -1) + 1;
|
||||
|
||||
const insert = db.prepare('INSERT INTO packing_items (trip_id, name, checked, category, sort_order) VALUES (?, ?, 0, ?, ?)');
|
||||
const added: any[] = [];
|
||||
for (const ti of templateItems) {
|
||||
const result = insert.run(tripId, ti.name, ti.category, sortOrder++);
|
||||
const item = db.prepare('SELECT * FROM packing_items WHERE id = ?').get(result.lastInsertRowid);
|
||||
added.push(item);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
// ── Category Assignees ─────────────────────────────────────────────────────
|
||||
|
||||
export function getCategoryAssignees(tripId: string | number) {
|
||||
const rows = db.prepare(`
|
||||
SELECT pca.category_name, pca.user_id, u.username, u.avatar
|
||||
FROM packing_category_assignees pca
|
||||
JOIN users u ON pca.user_id = u.id
|
||||
WHERE pca.trip_id = ?
|
||||
`).all(tripId);
|
||||
|
||||
// Group by category
|
||||
const assignees: Record<string, { user_id: number; username: string; avatar: string | null }[]> = {};
|
||||
for (const row of rows as any[]) {
|
||||
if (!assignees[row.category_name]) assignees[row.category_name] = [];
|
||||
assignees[row.category_name].push({ user_id: row.user_id, username: row.username, avatar: row.avatar });
|
||||
}
|
||||
|
||||
return assignees;
|
||||
}
|
||||
|
||||
export function updateCategoryAssignees(tripId: string | number, categoryName: string, userIds: number[] | undefined) {
|
||||
db.prepare('DELETE FROM packing_category_assignees WHERE trip_id = ? AND category_name = ?').run(tripId, categoryName);
|
||||
|
||||
if (Array.isArray(userIds) && userIds.length > 0) {
|
||||
const insert = db.prepare('INSERT OR IGNORE INTO packing_category_assignees (trip_id, category_name, user_id) VALUES (?, ?, ?)');
|
||||
for (const uid of userIds) insert.run(tripId, categoryName, uid);
|
||||
}
|
||||
|
||||
return db.prepare(`
|
||||
SELECT pca.user_id, u.username, u.avatar
|
||||
FROM packing_category_assignees pca
|
||||
JOIN users u ON pca.user_id = u.id
|
||||
WHERE pca.trip_id = ? AND pca.category_name = ?
|
||||
`).all(tripId, categoryName);
|
||||
}
|
||||
|
||||
// ── Reorder ────────────────────────────────────────────────────────────────
|
||||
|
||||
export function reorderItems(tripId: string | number, orderedIds: number[]) {
|
||||
const update = db.prepare('UPDATE packing_items SET sort_order = ? WHERE id = ? AND trip_id = ?');
|
||||
const updateMany = db.transaction((ids: number[]) => {
|
||||
ids.forEach((id, index) => {
|
||||
update.run(index, id, tripId);
|
||||
});
|
||||
});
|
||||
updateMany(orderedIds);
|
||||
}
|
||||
Reference in New Issue
Block a user