mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
refactor(trip): Naver List Import as Addon
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 (?, ?, ?, ?, ?, ?, ?)');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -113,6 +113,7 @@ const DEFAULT_ADDONS = [
|
||||
{ id: 'vacay', name: 'Vacay', description: 'Vacation day planner', type: 'global', icon: 'CalendarDays',enabled: 1, sort_order: 10 },
|
||||
{ id: 'atlas', name: 'Atlas', description: 'Visited countries map', type: 'global', icon: 'Globe', enabled: 1, sort_order: 11 },
|
||||
{ id: 'mcp', name: 'MCP', description: '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, live chat', type: 'trip', icon: 'Users', enabled: 1, sort_order: 6 },
|
||||
];
|
||||
|
||||
|
||||
@@ -521,11 +521,28 @@ describe('Naver list import', () => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('POST /import/naver-list returns 403 when addon is disabled', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
|
||||
testDb.prepare("UPDATE addons SET enabled = 0 WHERE id = 'naver_list_import'").run();
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/api/trips/${trip.id}/places/import/naver-list`)
|
||||
.set('Cookie', authCookie(user.id))
|
||||
.send({ url: 'https://naver.me/GYDpx3Wv' });
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(res.body.error).toContain('addon is disabled');
|
||||
});
|
||||
|
||||
it('POST /import/naver-list resolves shortlink, paginates, and creates places', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const folderId = 'a04c3f7a8dd24d42a8eb52d710a700cc';
|
||||
|
||||
testDb.prepare("UPDATE addons SET enabled = 1 WHERE id = 'naver_list_import'").run();
|
||||
|
||||
const fetchMock = vi.fn()
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
@@ -576,6 +593,8 @@ describe('Naver list import', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
|
||||
testDb.prepare("UPDATE addons SET enabled = 1 WHERE id = 'naver_list_import'").run();
|
||||
|
||||
const res = await request(app)
|
||||
.post(`/api/trips/${trip.id}/places/import/naver-list`)
|
||||
.set('Cookie', authCookie(user.id))
|
||||
|
||||
Reference in New Issue
Block a user