Features: - HP Control component with damage/heal/direct modes (mobile-optimized) - Conditions system with PF2e condition database - Equipment database with 5,482 items from PF2e (weapons, armor, equipment) - AddItemModal with search, category filters, and pagination - Bulk tracking with encumbered/overburdened status display - Item management (add, remove, toggle equipped) Backend: - Equipment module with search/filter endpoints - Prisma migration for equipment detail fields - Equipment seed script importing from JSON data files - Extended Equipment model (damage, hands, AC, etc.) Frontend: - New components: HpControl, AddConditionModal, AddItemModal - Improved character sheet with tabbed interface - API methods for equipment search and item management Documentation: - CLAUDE.md with project philosophy and architecture decisions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
255 lines
7.6 KiB
TypeScript
255 lines
7.6 KiB
TypeScript
import 'dotenv/config';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { PrismaClient } from '../src/generated/prisma/client.js';
|
|
import { PrismaPg } from '@prisma/adapter-pg';
|
|
|
|
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
|
const prisma = new PrismaClient({ adapter });
|
|
|
|
interface WeaponJson {
|
|
name: string;
|
|
trait: string;
|
|
item_category: string;
|
|
item_subcategory: string;
|
|
bulk: string;
|
|
url: string;
|
|
summary: string;
|
|
hands?: string;
|
|
damage?: string;
|
|
range?: string;
|
|
weapon_category?: string;
|
|
}
|
|
|
|
interface ArmorJson {
|
|
name: string;
|
|
trait: string;
|
|
item_category: string;
|
|
item_subcategory: string;
|
|
bulk: string;
|
|
url: string;
|
|
summary: string;
|
|
ac?: string;
|
|
dex_cap?: string;
|
|
}
|
|
|
|
interface EquipmentJson {
|
|
name: string;
|
|
trait: string;
|
|
item_category: string;
|
|
item_subcategory: string;
|
|
bulk: string;
|
|
url: string;
|
|
summary: string;
|
|
activation?: string;
|
|
}
|
|
|
|
function parseTraits(traitString: string): string[] {
|
|
if (!traitString || traitString.trim() === '') return [];
|
|
return traitString.split(',').map(t => t.trim()).filter(t => t.length > 0);
|
|
}
|
|
|
|
function parseDamage(damageStr: string): { damage: string | null; damageType: string | null } {
|
|
if (!damageStr || damageStr.trim() === '') return { damage: null, damageType: null };
|
|
|
|
// Parse strings like "1d8 S", "1d6 P", "2d6 B"
|
|
const match = damageStr.match(/^(.+?)\s+([SPB])$/i);
|
|
if (match) {
|
|
return { damage: match[1].trim(), damageType: match[2].toUpperCase() };
|
|
}
|
|
return { damage: damageStr, damageType: null };
|
|
}
|
|
|
|
function parseNumber(str: string | undefined): number | null {
|
|
if (!str || str.trim() === '') return null;
|
|
const num = parseInt(str, 10);
|
|
return isNaN(num) ? null : num;
|
|
}
|
|
|
|
async function seedWeapons() {
|
|
const dataPath = path.join(__dirname, 'data', 'weapons.json');
|
|
const data: WeaponJson[] = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
|
|
|
console.log(`⚔️ Importing ${data.length} weapons...`);
|
|
|
|
let created = 0;
|
|
let updated = 0;
|
|
let errors = 0;
|
|
|
|
for (const item of data) {
|
|
try {
|
|
const { damage, damageType } = parseDamage(item.damage || '');
|
|
|
|
// Check if exists first
|
|
const existing = await prisma.equipment.findUnique({ where: { name: item.name } });
|
|
|
|
if (existing) {
|
|
// Update with weapon-specific fields
|
|
await prisma.equipment.update({
|
|
where: { name: item.name },
|
|
data: {
|
|
hands: item.hands || existing.hands,
|
|
damage: damage || existing.damage,
|
|
damageType: damageType || existing.damageType,
|
|
range: item.range || existing.range,
|
|
weaponCategory: item.weapon_category || existing.weaponCategory,
|
|
// Don't overwrite traits/summary if already set
|
|
traits: existing.traits.length > 0 ? existing.traits : parseTraits(item.trait),
|
|
summary: existing.summary || item.summary || null,
|
|
},
|
|
});
|
|
updated++;
|
|
} else {
|
|
await prisma.equipment.create({
|
|
data: {
|
|
name: item.name,
|
|
traits: parseTraits(item.trait),
|
|
itemCategory: item.item_category || 'Weapons',
|
|
itemSubcategory: item.item_subcategory || null,
|
|
bulk: item.bulk || null,
|
|
url: item.url || null,
|
|
summary: item.summary || null,
|
|
hands: item.hands || null,
|
|
damage,
|
|
damageType,
|
|
range: item.range || null,
|
|
weaponCategory: item.weapon_category || null,
|
|
},
|
|
});
|
|
created++;
|
|
}
|
|
} catch (error: any) {
|
|
if (errors === 0) {
|
|
// Print full error for first failure only
|
|
console.log(` ⚠️ First error for "${item.name}":`);
|
|
console.log(error.message);
|
|
}
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
console.log(` ✅ Created: ${created}, Updated: ${updated}, Errors: ${errors}`);
|
|
}
|
|
|
|
async function seedArmor() {
|
|
const dataPath = path.join(__dirname, 'data', 'armor.json');
|
|
const data: ArmorJson[] = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
|
|
|
console.log(`🛡️ Importing ${data.length} armor items...`);
|
|
|
|
let created = 0;
|
|
let updated = 0;
|
|
let errors = 0;
|
|
|
|
for (const item of data) {
|
|
try {
|
|
const existing = await prisma.equipment.findUnique({ where: { name: item.name } });
|
|
|
|
if (existing) {
|
|
// Update with armor-specific fields
|
|
await prisma.equipment.update({
|
|
where: { name: item.name },
|
|
data: {
|
|
ac: parseNumber(item.ac) ?? existing.ac,
|
|
dexCap: parseNumber(item.dex_cap) ?? existing.dexCap,
|
|
traits: existing.traits.length > 0 ? existing.traits : parseTraits(item.trait),
|
|
summary: existing.summary || item.summary || null,
|
|
},
|
|
});
|
|
updated++;
|
|
} else {
|
|
await prisma.equipment.create({
|
|
data: {
|
|
name: item.name,
|
|
traits: parseTraits(item.trait),
|
|
itemCategory: item.item_category || 'Armor',
|
|
itemSubcategory: item.item_subcategory || null,
|
|
bulk: item.bulk || null,
|
|
url: item.url || null,
|
|
summary: item.summary || null,
|
|
ac: parseNumber(item.ac),
|
|
dexCap: parseNumber(item.dex_cap),
|
|
},
|
|
});
|
|
created++;
|
|
}
|
|
} catch (error: any) {
|
|
if (errors < 3) {
|
|
console.log(` ⚠️ Error for "${item.name}": ${error.message?.slice(0, 100)}`);
|
|
}
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
console.log(` ✅ Created: ${created}, Updated: ${updated}, Errors: ${errors}`);
|
|
}
|
|
|
|
async function seedEquipment() {
|
|
const dataPath = path.join(__dirname, 'data', 'equipment.json');
|
|
const data: EquipmentJson[] = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
|
|
|
console.log(`📦 Importing ${data.length} equipment items...`);
|
|
|
|
let created = 0;
|
|
let updated = 0;
|
|
let errors = 0;
|
|
|
|
for (const item of data) {
|
|
try {
|
|
await prisma.equipment.upsert({
|
|
where: { name: item.name },
|
|
update: {
|
|
traits: parseTraits(item.trait),
|
|
itemCategory: item.item_category || 'Equipment',
|
|
itemSubcategory: item.item_subcategory || null,
|
|
bulk: item.bulk || null,
|
|
url: item.url || null,
|
|
summary: item.summary || null,
|
|
activation: item.activation || null,
|
|
},
|
|
create: {
|
|
name: item.name,
|
|
traits: parseTraits(item.trait),
|
|
itemCategory: item.item_category || 'Equipment',
|
|
itemSubcategory: item.item_subcategory || null,
|
|
bulk: item.bulk || null,
|
|
url: item.url || null,
|
|
summary: item.summary || null,
|
|
activation: item.activation || null,
|
|
},
|
|
});
|
|
created++;
|
|
} catch (error) {
|
|
// Item with same name already exists - count as update attempt
|
|
updated++;
|
|
}
|
|
}
|
|
|
|
console.log(` ✅ Created: ${created}, Duplicates: ${updated}, Errors: ${errors}`);
|
|
}
|
|
|
|
async function main() {
|
|
console.log('🗃️ Seeding Pathfinder 2e Equipment Database...\n');
|
|
|
|
const startTime = Date.now();
|
|
|
|
// WICHTIG: Equipment zuerst, dann Waffen/Rüstung um spezifische Felder zu ergänzen
|
|
await seedEquipment();
|
|
await seedWeapons(); // Ergänzt damage, hands, weapon_category etc.
|
|
await seedArmor(); // Ergänzt ac, dex_cap etc.
|
|
|
|
const totalCount = await prisma.equipment.count();
|
|
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
|
|
console.log(`\n✅ Equipment database seeded successfully!`);
|
|
console.log(` Total items in database: ${totalCount}`);
|
|
console.log(` Duration: ${duration}s`);
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error('Equipment seeding failed:', e);
|
|
process.exit(1);
|
|
})
|
|
.finally(() => prisma.$disconnect());
|