Skip to content

Commit 436b8fb

Browse files
authored
Merge pull request #137 from keyxmakerx/claude/phase-sprint-planning-MEZXp
feat: integrate NPC plugin into the addon framework for per-campaign …
2 parents 44f316a + a930646 commit 436b8fb

7 files changed

Lines changed: 23 additions & 5 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DELETE FROM addons WHERE slug = 'npcs';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Register the NPC Gallery as a toggleable addon so campaign owners can
2+
-- enable/disable it from the Plugin Hub, just like calendar, maps, etc.
3+
INSERT INTO addons (slug, name, description, version, category, status, icon, author)
4+
VALUES ('npcs', 'NPC Gallery', 'Browse and reveal character entities as NPCs for your players.', '1.0.0', 'plugin', 'active', 'fa-users', 'Chronicle');

internal/app/routes.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,7 +1016,7 @@ func (a *App) RegisterRoutes() {
10161016
npcSvc := npcs.NewNPCService(npcRepo, &npcEntityTypeFinderAdapter{svc: entityService})
10171017
npcHandler := npcs.NewHandler(npcSvc)
10181018
npcHandler.SetVisibilityToggler(&npcVisibilityTogglerAdapter{svc: entityService})
1019-
npcs.RegisterRoutes(e, npcHandler, campaignService, authService)
1019+
npcs.RegisterRoutes(e, npcHandler, campaignService, authService, addonService)
10201020

10211021
// Notes widget: personal floating note-taking panel (Google Keep-style).
10221022
noteRepo := notes.NewNoteRepository(a.DB)
@@ -1087,7 +1087,7 @@ func (a *App) RegisterRoutes() {
10871087
// NPC gallery block — embeds a compact NPC grid on entity pages/dashboards.
10881088
blockRegistry.Register(entities.BlockMeta{
10891089
Type: "npc_gallery", Label: "NPC Gallery", Icon: "fa-users",
1090-
Description: "Grid of revealed NPCs",
1090+
Description: "Grid of revealed NPCs", Addon: "npcs",
10911091
}, func(bctx entities.BlockRenderContext) templ.Component {
10921092
limit := entities.BlockConfigLimit(bctx.Block.Config, "limit", 8)
10931093
cards, err := npcHandler.GalleryBlock(context.Background(), bctx.CC.Campaign.ID, int(bctx.CC.MemberRole), "", limit)

internal/plugins/addons/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ var installedAddons = map[string]bool{
129129
"sessions": true,
130130
"timeline": true,
131131
"media-gallery": true,
132+
"npcs": true,
132133
"dnd5e": true,
133134
"pathfinder2e": true,
134135
"drawsteel": true,

internal/plugins/npcs/.ai.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ The NPC gallery provides a dedicated hub for revealed (non-private) character en
66
in a campaign. It gives players a visual directory of NPCs they've encountered, and
77
DMs a quick way to reveal/hide characters from player view.
88

9+
## Addon Gating
10+
11+
Registered as the `npcs` addon (category: `plugin`, status: `active`). Campaign owners
12+
enable/disable it from the Plugin Hub. All routes use `addons.RequireAddon(addonSvc, "npcs")`
13+
middleware. Sidebar link and `npc_gallery` layout block are gated behind `IsAddonEnabled(ctx, "npcs")`.
14+
915
## Architecture
1016

1117
This is a **view plugin** — it has no database tables. All data comes from the existing
@@ -69,7 +75,7 @@ type TagLister interface {
6975

7076
Type: `npc_gallery`
7177
Config: `{"limit": 8}` (default 8 cards)
72-
Always available (no addon requirement).
78+
Gated behind the `npcs` addon — hidden when addon is disabled for the campaign.
7379

7480
## Foundry VTT Sync
7581

internal/plugins/npcs/routes.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,29 @@ package npcs
55
import (
66
"github.com/labstack/echo/v4"
77

8+
"github.com/keyxmakerx/chronicle/internal/plugins/addons"
89
"github.com/keyxmakerx/chronicle/internal/plugins/auth"
910
"github.com/keyxmakerx/chronicle/internal/plugins/campaigns"
1011
)
1112

1213
// RegisterRoutes sets up NPC gallery routes on the Echo instance.
1314
// Public-capable routes use AllowPublicCampaignAccess so public campaigns
14-
// show NPCs to unauthenticated visitors.
15-
func RegisterRoutes(e *echo.Echo, h *Handler, campaignSvc campaigns.CampaignService, authSvc auth.AuthService) {
15+
// show NPCs to unauthenticated visitors. All routes are gated behind the
16+
// "npcs" addon — campaign owners can enable/disable via the Plugin Hub.
17+
func RegisterRoutes(e *echo.Echo, h *Handler, campaignSvc campaigns.CampaignService, authSvc auth.AuthService, addonSvc addons.AddonService) {
1618
// Authenticated routes: reveal toggle (Scribe+).
1719
cg := e.Group("/campaigns/:id",
1820
auth.RequireAuth(authSvc),
1921
campaigns.RequireCampaignAccess(campaignSvc),
22+
addons.RequireAddon(addonSvc, "npcs"),
2023
)
2124
cg.POST("/npcs/:eid/reveal", h.ToggleReveal, campaigns.RequireRole(campaigns.RoleScribe))
2225

2326
// Public-capable routes: gallery view (Player+).
2427
pub := e.Group("/campaigns/:id",
2528
auth.OptionalAuth(authSvc),
2629
campaigns.AllowPublicCampaignAccess(campaignSvc),
30+
addons.RequireAddon(addonSvc, "npcs"),
2731
)
2832
pub.GET("/npcs", h.Index, campaigns.RequireRole(campaigns.RolePlayer))
2933
pub.GET("/npcs/count", h.CountAPI, campaigns.RequireRole(campaigns.RolePlayer))

internal/templates/layouts/app.templ

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ templ Sidebar() {
246246
</span>
247247
<span class="flex-1">All Pages</span>
248248
</a>
249+
if IsAddonEnabled(ctx, "npcs") {
249250
<a
250251
href={ templ.SafeURL(fmt.Sprintf("/campaigns/%s/npcs", GetCampaignID(ctx))) }
251252
class={ sidebarNavLink,
@@ -257,6 +258,7 @@ templ Sidebar() {
257258
</span>
258259
<span class="flex-1">NPCs</span>
259260
</a>
261+
}
260262
<!-- Custom sidebar sections and links -->
261263
if len(GetCustomSections(ctx)) > 0 || len(GetCustomLinks(ctx)) > 0 {
262264
for _, link := range GetCustomLinks(ctx) {

0 commit comments

Comments
 (0)