159 lines
3.9 KiB
TypeScript
159 lines
3.9 KiB
TypeScript
import { db } from './db/index.js';
|
|
import { randomUUID } from 'crypto';
|
|
|
|
export type PlanType =
|
|
| 'destination'
|
|
| 'activity'
|
|
| 'transport'
|
|
| 'lodging'
|
|
| 'restaurant'
|
|
| 'tour'
|
|
| 'packing'
|
|
| 'todo';
|
|
|
|
export type PlanStatus = 'idea' | 'tentative' | 'confirmed';
|
|
|
|
export interface Plan {
|
|
id: string;
|
|
trip_id: string;
|
|
user_id: string;
|
|
type: PlanType;
|
|
status: PlanStatus;
|
|
parent_id: string | null;
|
|
title: string;
|
|
notes: string | null;
|
|
city_id: number | null;
|
|
city_name: string | null;
|
|
country: string | null;
|
|
country_code: string | null;
|
|
start_date: string | null;
|
|
end_date: string | null;
|
|
position: number;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface City {
|
|
id: number;
|
|
name: string;
|
|
country: string;
|
|
country_code: string;
|
|
population: number | null;
|
|
}
|
|
|
|
export interface CreateDestinationInput {
|
|
tripId: string;
|
|
userId: string;
|
|
cityId?: number;
|
|
cityName: string;
|
|
country: string;
|
|
countryCode: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
status?: PlanStatus;
|
|
notes?: string;
|
|
}
|
|
|
|
export function createDestination(input: CreateDestinationInput): Plan {
|
|
const id = randomUUID();
|
|
const maxPos = db.get<{ pos: number }>(
|
|
`SELECT COALESCE(MAX(position), -1) + 1 as pos FROM plans WHERE trip_id = ? AND user_id = ?`,
|
|
[input.tripId, input.userId]
|
|
);
|
|
const position = maxPos?.pos ?? 0;
|
|
|
|
db.run(
|
|
`INSERT INTO plans (id, trip_id, user_id, type, status, title, notes, city_id, city_name, country, country_code, start_date, end_date, position)
|
|
VALUES (?, ?, ?, 'destination', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
id,
|
|
input.tripId,
|
|
input.userId,
|
|
input.status ?? 'idea',
|
|
input.cityName,
|
|
input.notes ?? null,
|
|
input.cityId ?? null,
|
|
input.cityName,
|
|
input.country,
|
|
input.countryCode,
|
|
input.startDate ?? null,
|
|
input.endDate ?? null,
|
|
position
|
|
]
|
|
);
|
|
return db.get<Plan>('SELECT * FROM plans WHERE id = ?', [id])!;
|
|
}
|
|
|
|
export function getPlansForTrip(tripId: string, userId: string): Plan[] {
|
|
return db.all<Plan>(
|
|
`SELECT * FROM plans WHERE trip_id = ? AND user_id = ? ORDER BY position ASC, created_at ASC`,
|
|
[tripId, userId]
|
|
);
|
|
}
|
|
|
|
export function getPlanCountForTrip(tripId: string, userId: string): number {
|
|
const row = db.get<{ count: number }>(
|
|
`SELECT COUNT(*) as count FROM plans WHERE trip_id = ? AND user_id = ?`,
|
|
[tripId, userId]
|
|
);
|
|
return row?.count ?? 0;
|
|
}
|
|
|
|
export interface Country {
|
|
name: string;
|
|
country_code: string;
|
|
}
|
|
|
|
export function getCountries(query?: string): Country[] {
|
|
if (!query?.trim()) {
|
|
return db.all<Country>(
|
|
`SELECT DISTINCT country as name, country_code FROM cities ORDER BY country`
|
|
);
|
|
}
|
|
const pattern = `%${query.trim()}%`;
|
|
return db.all<Country>(
|
|
`SELECT DISTINCT country as name, country_code
|
|
FROM cities
|
|
WHERE country LIKE ? COLLATE NOCASE
|
|
ORDER BY country
|
|
LIMIT 50`,
|
|
[pattern]
|
|
);
|
|
}
|
|
|
|
export function searchCities(query: string, countryCode?: string): City[] {
|
|
if (!query.trim()) return [];
|
|
const pattern = `%${query.trim()}%`;
|
|
if (countryCode?.trim()) {
|
|
return db.all<City>(
|
|
`SELECT id, name, country, country_code, population
|
|
FROM cities
|
|
WHERE name LIKE ? COLLATE NOCASE AND country_code = ?
|
|
ORDER BY
|
|
CASE WHEN name LIKE ? COLLATE NOCASE THEN 0 ELSE 1 END,
|
|
population DESC NULLS LAST
|
|
LIMIT 10`,
|
|
[pattern, countryCode.trim().toUpperCase(), `${query.trim()}%`]
|
|
);
|
|
}
|
|
return db.all<City>(
|
|
`SELECT id, name, country, country_code, population
|
|
FROM cities
|
|
WHERE name LIKE ? COLLATE NOCASE
|
|
ORDER BY
|
|
CASE WHEN name LIKE ? COLLATE NOCASE THEN 0 ELSE 1 END,
|
|
population DESC NULLS LAST
|
|
LIMIT 10`,
|
|
[pattern, `${query.trim()}%`]
|
|
);
|
|
}
|
|
|
|
export function deletePlan(planId: string, userId: string): void {
|
|
// Verify the plan belongs to the user
|
|
const plan = db.get<Plan>('SELECT * FROM plans WHERE id = ? AND user_id = ?', [planId, userId]);
|
|
if (!plan) {
|
|
throw new Error('Plan not found or not authorized');
|
|
}
|
|
db.run('DELETE FROM plans WHERE id = ?', [planId]);
|
|
}
|