@@ -26,6 +26,9 @@ type AddonService interface {
2626 Delete (ctx context.Context , id int ) error
2727 UpdateStatus (ctx context.Context , id int , status AddonStatus ) error
2828
29+ // SeedInstalledAddons upserts all built-in addons at startup.
30+ SeedInstalledAddons (ctx context.Context ) error
31+
2932 // Per-campaign controls (campaign owner).
3033 ListForCampaign (ctx context.Context , campaignID string ) ([]CampaignAddon , error )
3134 EnableForCampaign (ctx context.Context , campaignID string , addonID int , userID string ) error
@@ -117,29 +120,91 @@ var validStatuses = map[AddonStatus]bool{
117120 StatusDeprecated : true ,
118121}
119122
120- // installedAddons lists addon slugs that have real backing code in the
121- // codebase. Only installed addons can be activated by admins or enabled
122- // by campaign owners. Update this set as new addons are built.
123- var installedAddons = map [string ]bool {
124- "sync-api" : true ,
125- "notes" : true ,
126- "attributes" : true ,
127- "calendar" : true ,
128- "maps" : true ,
129- "sessions" : true ,
130- "timeline" : true ,
131- "media-gallery" : true ,
132- "npcs" : true ,
133- "dnd5e" : true ,
134- "pathfinder2e" : true ,
135- "drawsteel" : true ,
123+ // addonDef describes a built-in addon that ships with the codebase.
124+ // Used for automatic registration at startup — no migration needed.
125+ type addonDef struct {
126+ Slug string
127+ Name string
128+ Description string
129+ Version string
130+ Category AddonCategory
131+ Status AddonStatus
132+ Icon string
133+ Author string
134+ }
135+
136+ // builtinAddons is the canonical registry of all addons that ship with
137+ // Chronicle. Adding a new addon here is all that's needed — the startup
138+ // seeder will upsert it into the database automatically. No migration required.
139+ var builtinAddons = []addonDef {
140+ // Game systems (content packs).
141+ {Slug : "dnd5e" , Name : "D&D 5th Edition" , Description : "Reference data, stat blocks, and tooltips for Dungeons & Dragons 5th Edition" , Version : "0.1.0" , Category : CategorySystem , Status : StatusActive , Icon : "fa-dragon" , Author : "Chronicle" },
142+ {Slug : "pathfinder2e" , Name : "Pathfinder 2nd Edition" , Description : "Reference data and tooltips for Pathfinder 2nd Edition" , Version : "0.1.0" , Category : CategorySystem , Status : StatusActive , Icon : "fa-shield-halved" , Author : "Chronicle" },
143+ {Slug : "drawsteel" , Name : "Draw Steel" , Description : "Reference data for the Draw Steel RPG system" , Version : "0.1.0" , Category : CategorySystem , Status : StatusActive , Icon : "fa-swords" , Author : "Chronicle" },
144+
145+ // Plugins (feature apps).
146+ {Slug : "calendar" , Name : "Calendar" , Description : "Custom fantasy calendar with configurable months, weekdays, moons, seasons, and events. Link events to entities for timeline tracking." , Version : "0.1.0" , Category : CategoryPlugin , Status : StatusActive , Icon : "fa-calendar-days" , Author : "Chronicle" },
147+ {Slug : "maps" , Name : "Interactive Maps" , Description : "Leaflet.js map viewer with entity pins and layer support" , Version : "0.1.0" , Category : CategoryPlugin , Status : StatusActive , Icon : "fa-map" , Author : "Chronicle" },
148+ {Slug : "media-gallery" , Name : "Media Gallery" , Description : "Campaign media management — upload, browse, and organize images." , Version : "0.1.0" , Category : CategoryPlugin , Status : StatusActive , Icon : "fa-images" , Author : "Chronicle" },
149+ {Slug : "timeline" , Name : "Timeline" , Description : "Interactive visual timelines with zoom levels, entity grouping, and calendar integration." , Version : "0.1.0" , Category : CategoryPlugin , Status : StatusActive , Icon : "fa-timeline" , Author : "Chronicle" },
150+ {Slug : "sessions" , Name : "Sessions" , Description : "Track game sessions with scheduling, linked entities, and RSVP." , Version : "0.1.0" , Category : CategoryPlugin , Status : StatusActive , Icon : "fa-calendar-check" , Author : "Chronicle" },
151+ {Slug : "npcs" , Name : "NPC Gallery" , Description : "Browse and reveal character entities as NPCs for your players." , Version : "1.0.0" , Category : CategoryPlugin , Status : StatusActive , Icon : "fa-users" , Author : "Chronicle" },
152+
153+ // Integrations.
154+ {Slug : "sync-api" , Name : "Sync API" , Description : "Secure REST API for external tool integration (Foundry VTT, Roll20, etc.)" , Version : "0.1.0" , Category : CategoryIntegration , Status : StatusActive , Icon : "fa-arrows-rotate" , Author : "Chronicle" },
155+
156+ // Widgets.
157+ {Slug : "notes" , Name : "Notes" , Description : "Floating notebook panel for personal and shared campaign notes. Includes checklists, color coding, version history, and edit locking." , Version : "0.1.0" , Category : CategoryWidget , Status : StatusActive , Icon : "fa-book" , Author : "Chronicle" },
158+ {Slug : "attributes" , Name : "Attributes" , Description : "Custom attribute fields on entity pages (e.g. race, alignment, HP). When disabled, attribute panels are hidden." , Version : "0.1.0" , Category : CategoryWidget , Status : StatusActive , Icon : "fa-sliders" , Author : "Chronicle" },
159+
160+ // Planned (no backing code yet).
161+ {Slug : "player-notes" , Name : "Player Notes" , Description : "Collaborative note-taking block for entity pages." , Version : "0.1.0" , Category : CategoryWidget , Status : StatusPlanned , Icon : "fa-sticky-note" , Author : "Chronicle" },
162+ {Slug : "family-tree" , Name : "Family Tree" , Description : "Visual family/org tree diagram from entity relations" , Version : "0.1.0" , Category : CategoryWidget , Status : StatusPlanned , Icon : "fa-sitemap" , Author : "Chronicle" },
163+ {Slug : "dice-roller" , Name : "Dice Roller" , Description : "In-app dice rolling with formula support and history" , Version : "0.1.0" , Category : CategoryWidget , Status : StatusPlanned , Icon : "fa-dice-d20" , Author : "Chronicle" },
164+ }
165+
166+ // installedAddons is derived from builtinAddons for quick lookup.
167+ var installedAddons map [string ]bool
168+
169+ func init () {
170+ installedAddons = make (map [string ]bool , len (builtinAddons ))
171+ for _ , a := range builtinAddons {
172+ if a .Status == StatusActive {
173+ installedAddons [a .Slug ] = true
174+ }
175+ }
136176}
137177
138178// IsInstalled reports whether an addon slug has backing code in the codebase.
139179func IsInstalled (slug string ) bool {
140180 return installedAddons [slug ]
141181}
142182
183+ // SeedInstalledAddons upserts all built-in addons into the database.
184+ // Called once at startup so new addons are registered automatically
185+ // without requiring SQL migrations.
186+ func (s * addonService ) SeedInstalledAddons (ctx context.Context ) error {
187+ for _ , def := range builtinAddons {
188+ desc := def .Description
189+ author := def .Author
190+ addon := & Addon {
191+ Slug : def .Slug ,
192+ Name : def .Name ,
193+ Description : & desc ,
194+ Version : def .Version ,
195+ Category : def .Category ,
196+ Status : def .Status ,
197+ Icon : def .Icon ,
198+ Author : & author ,
199+ }
200+ if err := s .repo .Upsert (ctx , addon ); err != nil {
201+ return fmt .Errorf ("seeding addon %s: %w" , def .Slug , err )
202+ }
203+ }
204+ slog .Info ("built-in addons registered" , slog .Int ("count" , len (builtinAddons )))
205+ return nil
206+ }
207+
143208// Create registers a new addon in the global registry.
144209func (s * addonService ) Create (ctx context.Context , input CreateAddonInput ) (* Addon , error ) {
145210 slug := strings .TrimSpace (input .Slug )
0 commit comments