feat(server): implement ship REST endpoints with Zod validation
Adds GET/POST/PATCH/DELETE /api/ships with constraint validation (current <= max), enum checks for maneuver_class, and cascade delete for weapons. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
117
server/src/services/ship-service.ts
Normal file
117
server/src/services/ship-service.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import db from '../db/connection.js';
|
||||
import type { CreateShipInput, UpdateShipInput } from '../validation/ship-schema.js';
|
||||
|
||||
export interface Ship {
|
||||
id: string;
|
||||
name: string;
|
||||
hull_current: number;
|
||||
hull_max: number;
|
||||
armor_current: number;
|
||||
armor_max: number;
|
||||
ac: number;
|
||||
con_save: number | null;
|
||||
speed: number | null;
|
||||
maneuver_class: string | null;
|
||||
size_category: string | null;
|
||||
notes: string | null;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
export interface Weapon {
|
||||
id: string;
|
||||
ship_id: string;
|
||||
name: string;
|
||||
type: string | null;
|
||||
attack_mod: number | null;
|
||||
damage: string | null;
|
||||
range: string | null;
|
||||
ammo_current: number | null;
|
||||
ammo_max: number | null;
|
||||
status: string | null;
|
||||
notes: string | null;
|
||||
sort_order: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
export function listShips(): Pick<Ship, 'id' | 'name' | 'updated_at'>[] {
|
||||
return db
|
||||
.prepare('SELECT id, name, updated_at FROM ships ORDER BY updated_at DESC')
|
||||
.all() as Pick<Ship, 'id' | 'name' | 'updated_at'>[];
|
||||
}
|
||||
|
||||
export function getShip(id: string): { ship: Ship; weapons: Weapon[] } | null {
|
||||
const ship = db.prepare('SELECT * FROM ships WHERE id = ?').get(id) as Ship | undefined;
|
||||
if (!ship) return null;
|
||||
|
||||
const weapons = db
|
||||
.prepare('SELECT * FROM weapons WHERE ship_id = ? ORDER BY sort_order ASC, name ASC')
|
||||
.all(id) as Weapon[];
|
||||
|
||||
return { ship, weapons };
|
||||
}
|
||||
|
||||
export function createShip(input: CreateShipInput): Ship {
|
||||
const id = uuid();
|
||||
const now = Date.now();
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO ships (id, name, hull_current, hull_max, armor_current, armor_max, ac, con_save, speed, maneuver_class, size_category, notes, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
input.name,
|
||||
input.hull_current,
|
||||
input.hull_max,
|
||||
input.armor_current,
|
||||
input.armor_max,
|
||||
input.ac,
|
||||
input.con_save,
|
||||
input.speed,
|
||||
input.maneuver_class,
|
||||
input.size_category,
|
||||
input.notes,
|
||||
now,
|
||||
);
|
||||
|
||||
return db.prepare('SELECT * FROM ships WHERE id = ?').get(id) as Ship;
|
||||
}
|
||||
|
||||
export function updateShip(id: string, patch: UpdateShipInput): Ship | null {
|
||||
const existing = db.prepare('SELECT * FROM ships WHERE id = ?').get(id) as Ship | undefined;
|
||||
if (!existing) return null;
|
||||
|
||||
const fields = Object.keys(patch).filter(
|
||||
(k) => (patch as Record<string, unknown>)[k] !== undefined,
|
||||
);
|
||||
if (fields.length === 0) return existing;
|
||||
|
||||
// Validate current <= max against existing values when only one side is patched
|
||||
const newHullCurrent = patch.hull_current ?? existing.hull_current;
|
||||
const newHullMax = patch.hull_max ?? existing.hull_max;
|
||||
const newArmorCurrent = patch.armor_current ?? existing.armor_current;
|
||||
const newArmorMax = patch.armor_max ?? existing.armor_max;
|
||||
|
||||
if (newHullCurrent > newHullMax) {
|
||||
throw new Error('hull_current cannot exceed hull_max');
|
||||
}
|
||||
if (newArmorCurrent > newArmorMax) {
|
||||
throw new Error('armor_current cannot exceed armor_max');
|
||||
}
|
||||
|
||||
const sets = fields.map((f) => `${f} = ?`);
|
||||
sets.push('updated_at = ?');
|
||||
|
||||
const values = fields.map((f) => (patch as Record<string, unknown>)[f]);
|
||||
values.push(Date.now());
|
||||
values.push(id);
|
||||
|
||||
db.prepare(`UPDATE ships SET ${sets.join(', ')} WHERE id = ?`).run(...values);
|
||||
|
||||
return db.prepare('SELECT * FROM ships WHERE id = ?').get(id) as Ship;
|
||||
}
|
||||
|
||||
export function deleteShip(id: string): boolean {
|
||||
const result = db.prepare('DELETE FROM ships WHERE id = ?').run(id);
|
||||
return result.changes > 0;
|
||||
}
|
||||
Reference in New Issue
Block a user