FRE-592: Implement character database and relationship mapping

Add full character management system with enriched profiles (bio, traits,
arcs, motivation, conflict, secrets), relationship mapping between
characters with types and strength, character search/filter by role and
arc type, and character statistics (scene count, dialogue, screen time).

Includes database schema, tRPC router procedures, SolidJS components,
API hooks, and unit tests.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
FrenoCorp Agent
2026-04-24 02:24:31 -04:00
committed by Michael Freno
parent 0fcd91cf87
commit 8dc4827597
18 changed files with 2237 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { scripts } from "./scripts";
export const characters = sqliteTable("characters", {
id: integer("id").primaryKey({ autoIncrement: true }),
scriptId: integer("script_id")
.notNull()
.references(() => scripts.id),
name: text("name").notNull(),
slug: text("slug").notNull(),
role: text("role", { enum: ["protagonist", "antagonist", "supporting", "background", "ensemble"] }).notNull().default("supporting"),
bio: text("bio"),
description: text("description"),
arc: text("arc"),
arcType: text("arc_type", { enum: ["positive", "negative", "flat", "complex"] }),
age: integer("age"),
gender: text("gender"),
voice: text("voice"),
traits: text("traits"),
motivation: text("motivation"),
conflict: text("conflict"),
secret: text("secret"),
imageUrl: text("image_url"),
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(() => new Date()),
});
export const characterRelationships = sqliteTable("character_relationships", {
id: integer("id").primaryKey({ autoIncrement: true }),
characterIdA: integer("character_a_id")
.notNull()
.references(() => characters.id),
characterIdB: integer("character_b_id")
.notNull()
.references(() => characters.id),
relationshipType: text("relationship_type", {
enum: ["family", "romantic", "friendship", "rivalry", "mentor", "alliance", "conflict", "professional", "other"],
}).notNull(),
description: text("description"),
strength: integer("strength").notNull().default(50),
isAntagonistic: integer("is_antagonistic", { mode: "boolean" }).notNull().default(false),
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(() => new Date()),
});
export type Character = typeof characters.$inferSelect;
export type NewCharacter = typeof characters.$inferInsert;
export type CharacterRelationship = typeof characterRelationships.$inferSelect;
export type NewCharacterRelationship = typeof characterRelationships.$inferInsert;

6
src/db/schema/index.ts Normal file
View File

@@ -0,0 +1,6 @@
export { users, type User, type NewUser } from "./users";
export { projects, type Project, type NewProject } from "./projects";
export { scripts, type Script, type NewScript } from "./scripts";
export { characters, characterRelationships, type Character, type NewCharacter, type CharacterRelationship, type NewCharacterRelationship } from "./characters";
export { scenes, sceneCharacters, type Scene, type NewScene, type SceneCharacter, type NewSceneCharacter } from "./scenes";
export { revisions, revisionChanges, type Revision, type NewRevision, type RevisionChange, type NewRevisionChange } from "./revisions";