refactor(trip): Naver List Import as Addon

This commit is contained in:
Marco Sadowski
2026-04-10 15:15:04 +02:00
parent f82f00216b
commit 6a632137ed
18 changed files with 123 additions and 28 deletions
+50 -6
View File
@@ -867,23 +867,67 @@ function runMigrations(db: Database.Database): void {
// Migration: Budget category ordering
() => {
db.exec(`
CREATE TABLE IF NOT EXISTS budget_category_order (
trip_id INTEGER NOT NULL REFERENCES trips(id) ON DELETE CASCADE,
CREATE TABLE IF NOT EXISTS budget_category_order
(
trip_id
INTEGER
NOT
NULL
REFERENCES
trips
(
id
) ON DELETE CASCADE,
category TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (trip_id, category)
);
PRIMARY KEY
(
trip_id,
category
)
);
`);
// Seed existing categories with alphabetical order
const rows = db.prepare('SELECT DISTINCT trip_id, category FROM budget_items ORDER BY trip_id, category').all() as { trip_id: number; category: string }[];
const rows = db.prepare('SELECT DISTINCT trip_id, category FROM budget_items ORDER BY trip_id, category').all() as {
trip_id: number;
category: string
}[];
const ins = db.prepare('INSERT OR IGNORE INTO budget_category_order (trip_id, category, sort_order) VALUES (?, ?, ?)');
let lastTripId = -1;
let idx = 0;
for (const r of rows) {
if (r.trip_id !== lastTripId) { lastTripId = r.trip_id; idx = 0; }
if (r.trip_id !== lastTripId) {
lastTripId = r.trip_id;
idx = 0;
}
ins.run(r.trip_id, r.category, idx++);
}
},
// Migration: Naver list import addon (default off)
() => {
try {
db.prepare(`
INSERT INTO addons (id, name, description, type, icon, enabled, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
description = excluded.description,
type = excluded.type,
icon = excluded.icon,
sort_order = excluded.sort_order
`).run(
'naver_list_import',
'Naver List Import',
'Import places from shared Naver Maps lists',
'trip',
'Link2',
0,
13,
);
} catch (err: any) {
console.warn('[migrations] Non-fatal migration step failed:', err);
}
},
];
if (currentVersion < migrations.length) {
+1
View File
@@ -88,6 +88,7 @@ function seedAddons(db: Database.Database): void {
{ id: 'vacay', name: 'Vacay', description: 'Personal vacation day planner with calendar view', type: 'global', icon: 'CalendarDays', enabled: 1, sort_order: 10 },
{ id: 'atlas', name: 'Atlas', description: 'World map of your visited countries with travel stats', type: 'global', icon: 'Globe', enabled: 1, sort_order: 11 },
{ id: 'mcp', name: 'MCP', description: 'Model Context Protocol for AI assistant integration', type: 'integration', icon: 'Terminal', enabled: 0, sort_order: 12 },
{ id: 'naver_list_import', name: 'Naver List Import', description: 'Import places from shared Naver Maps lists', type: 'trip', icon: 'Link2', enabled: 0, sort_order: 13 },
{ id: 'collab', name: 'Collab', description: 'Notes, polls, and live chat for trip collaboration', type: 'trip', icon: 'Users', enabled: 1, sort_order: 6 },
];
const insertAddon = db.prepare('INSERT OR IGNORE INTO addons (id, name, description, type, icon, enabled, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?)');
+4
View File
@@ -5,6 +5,7 @@ import { requireTripAccess } from '../middleware/tripAccess';
import { broadcast } from '../websocket';
import { validateStringLengths } from '../middleware/validate';
import { checkPermission } from '../services/permissions';
import { isAddonEnabled } from '../services/adminService';
import { AuthRequest } from '../types';
import {
listPlaces,
@@ -105,6 +106,9 @@ router.post('/import/naver-list', authenticate, requireTripAccess, async (req: R
const authReq = req as AuthRequest;
if (!checkPermission('place_edit', authReq.user.role, authReq.trip!.user_id, authReq.user.id, authReq.trip!.user_id !== authReq.user.id))
return res.status(403).json({ error: 'No permission' });
if (!isAddonEnabled('naver_list_import')) {
return res.status(403).json({ error: 'Naver list import addon is disabled' });
}
const { tripId } = req.params;
const { url } = req.body;