mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
1bddb3c588
Replace all db.prepare() calls in mcp/index.ts, mcp/resources.ts, and mcp/tools.ts with calls to the service layer. Add missing service functions: - authService: isDemoUser, verifyMcpToken, verifyJwtToken - adminService: isAddonEnabled - atlasService: listVisitedCountries - tripService: getTripSummary, listTrips with null archived param Also fix getAssignmentWithPlace and formatAssignmentWithPlace to expose place_id, assignment_time, and assignment_end_time at the top level, and fix updateDay to correctly handle null title for clearing. Add comprehensive unit and integration test suite for the MCP layer (821 tests all passing).
97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
import { db } from '../db/database';
|
|
import { AssignmentRow, Tag, Participant } from '../types';
|
|
|
|
interface TagRow extends Tag {
|
|
place_id: number;
|
|
}
|
|
|
|
interface ParticipantRow {
|
|
assignment_id: number;
|
|
user_id: number;
|
|
username: string;
|
|
avatar: string | null;
|
|
}
|
|
|
|
/** Batch-load tags for multiple places in a single query, indexed by place ID. */
|
|
function loadTagsByPlaceIds(placeIds: number[], { compact }: { compact?: boolean } = {}): Record<number, Partial<Tag>[]> {
|
|
const tagsByPlaceId: Record<number, Partial<Tag>[]> = {};
|
|
if (placeIds.length > 0) {
|
|
const placeholders = placeIds.map(() => '?').join(',');
|
|
const allTags = db.prepare(`
|
|
SELECT t.*, pt.place_id FROM tags t
|
|
JOIN place_tags pt ON t.id = pt.tag_id
|
|
WHERE pt.place_id IN (${placeholders})
|
|
`).all(...placeIds) as TagRow[];
|
|
|
|
for (const tag of allTags) {
|
|
const pid = tag.place_id;
|
|
if (!tagsByPlaceId[pid]) tagsByPlaceId[pid] = [];
|
|
if (compact) {
|
|
tagsByPlaceId[pid].push({ id: tag.id, name: tag.name, color: tag.color, created_at: tag.created_at });
|
|
} else {
|
|
const { place_id, ...rest } = tag;
|
|
tagsByPlaceId[pid].push(rest);
|
|
}
|
|
}
|
|
}
|
|
return tagsByPlaceId;
|
|
}
|
|
|
|
/** Batch-load participants for multiple day-assignments in a single query, indexed by assignment ID. */
|
|
function loadParticipantsByAssignmentIds(assignmentIds: number[]): Record<number, Participant[]> {
|
|
const participantsByAssignment: Record<number, Participant[]> = {};
|
|
if (assignmentIds.length > 0) {
|
|
const allParticipants = db.prepare(`SELECT ap.assignment_id, ap.user_id, u.username, u.avatar FROM assignment_participants ap JOIN users u ON ap.user_id = u.id WHERE ap.assignment_id IN (${assignmentIds.map(() => '?').join(',')})`)
|
|
.all(...assignmentIds) as ParticipantRow[];
|
|
for (const p of allParticipants) {
|
|
if (!participantsByAssignment[p.assignment_id]) participantsByAssignment[p.assignment_id] = [];
|
|
participantsByAssignment[p.assignment_id].push({ user_id: p.user_id, username: p.username, avatar: p.avatar });
|
|
}
|
|
}
|
|
return participantsByAssignment;
|
|
}
|
|
|
|
/** Reshape a flat assignment+place DB row into the nested API response shape with embedded place, tags, and participants. */
|
|
function formatAssignmentWithPlace(a: AssignmentRow, tags: Partial<Tag>[], participants: Participant[]) {
|
|
return {
|
|
id: a.id,
|
|
day_id: a.day_id,
|
|
place_id: a.place_id,
|
|
order_index: a.order_index,
|
|
notes: a.notes,
|
|
assignment_time: a.assignment_time ?? null,
|
|
assignment_end_time: a.assignment_end_time ?? null,
|
|
participants: participants || [],
|
|
created_at: a.created_at,
|
|
place: {
|
|
id: a.place_id,
|
|
name: a.place_name,
|
|
description: a.place_description,
|
|
lat: a.lat,
|
|
lng: a.lng,
|
|
address: a.address,
|
|
category_id: a.category_id,
|
|
price: a.price,
|
|
currency: a.place_currency,
|
|
place_time: a.place_time,
|
|
end_time: a.end_time,
|
|
duration_minutes: a.duration_minutes,
|
|
notes: a.place_notes,
|
|
image_url: a.image_url,
|
|
transport_mode: a.transport_mode,
|
|
google_place_id: a.google_place_id,
|
|
website: a.website,
|
|
phone: a.phone,
|
|
category: a.category_id ? {
|
|
id: a.category_id,
|
|
name: a.category_name,
|
|
color: a.category_color,
|
|
icon: a.category_icon,
|
|
} : null,
|
|
tags: tags || [],
|
|
}
|
|
};
|
|
}
|
|
|
|
export { loadTagsByPlaceIds, loadParticipantsByAssignmentIds, formatAssignmentWithPlace };
|