@@ -5,10 +5,13 @@ package admin
55
66import (
77 "context"
8+ "encoding/json"
89 "fmt"
910 "log/slog"
1011 "net/http"
12+ "os"
1113 "strconv"
14+ "strings"
1215
1316 "github.com/labstack/echo/v4"
1417
@@ -43,6 +46,7 @@ type Handler struct {
4346 securityService SecurityService
4447 hygieneScanner DataHygieneScanner
4548 databaseExplorer DatabaseExplorer
49+ baseURL string
4650}
4751
4852// StoragePageData holds all data needed for the combined storage management page.
@@ -103,6 +107,11 @@ func (h *Handler) SetDatabaseExplorer(explorer DatabaseExplorer) {
103107 h .databaseExplorer = explorer
104108}
105109
110+ // SetBaseURL sets the public-facing base URL for the Foundry module admin page.
111+ func (h * Handler ) SetBaseURL (url string ) {
112+ h .baseURL = url
113+ }
114+
106115// --- Data Hygiene ---
107116
108117// DataHygiene renders the data hygiene dashboard (GET /admin/data-hygiene).
@@ -748,3 +757,89 @@ type SecurityPageData struct {
748757 Sessions []auth.SessionInfo
749758 CSRFToken string
750759}
760+
761+ // --- Foundry VTT Module Management ---
762+
763+ // FoundryModuleData holds data for the admin Foundry module page.
764+ type FoundryModuleData struct {
765+ Version string
766+ InstallURL string
767+ CSRFToken string
768+ }
769+
770+ // FoundryModule renders the Foundry VTT module management page (GET /admin/foundry).
771+ func (h * Handler ) FoundryModule (c echo.Context ) error {
772+ version := readFoundryModuleVersion ()
773+ baseURL := strings .TrimRight (h .baseURL , "/" )
774+ data := FoundryModuleData {
775+ Version : version ,
776+ InstallURL : baseURL + "/foundry-module/module.json" ,
777+ CSRFToken : middleware .GetCSRFToken (c ),
778+ }
779+ return middleware .Render (c , http .StatusOK , AdminFoundryModulePage (data ))
780+ }
781+
782+ // UpdateFoundryModuleVersion updates the version in foundry-module/module.json
783+ // (PUT /admin/foundry/version).
784+ func (h * Handler ) UpdateFoundryModuleVersion (c echo.Context ) error {
785+ var req struct {
786+ Version string `json:"version" form:"version"`
787+ }
788+ if err := c .Bind (& req ); err != nil || req .Version == "" {
789+ return apperror .NewBadRequest ("version is required" )
790+ }
791+
792+ // Read current module.json.
793+ data , err := os .ReadFile ("foundry-module/module.json" )
794+ if err != nil {
795+ return apperror .NewInternal (fmt .Errorf ("read module.json: %w" , err ))
796+ }
797+
798+ var manifest map [string ]any
799+ if err := json .Unmarshal (data , & manifest ); err != nil {
800+ return apperror .NewInternal (fmt .Errorf ("parse module.json: %w" , err ))
801+ }
802+
803+ manifest ["version" ] = req .Version
804+
805+ // Update the download URL to use the new version tag.
806+ baseURL := strings .TrimRight (h .baseURL , "/" )
807+ manifest ["download" ] = baseURL + "/foundry-module/chronicle-sync.zip"
808+ manifest ["manifest" ] = baseURL + "/foundry-module/module.json"
809+
810+ out , err := json .MarshalIndent (manifest , "" , " " )
811+ if err != nil {
812+ return apperror .NewInternal (fmt .Errorf ("marshal module.json: %w" , err ))
813+ }
814+ out = append (out , '\n' )
815+
816+ if err := os .WriteFile ("foundry-module/module.json" , out , 0644 ); err != nil {
817+ return apperror .NewInternal (fmt .Errorf ("write module.json: %w" , err ))
818+ }
819+
820+ slog .Info ("foundry module version updated" ,
821+ slog .String ("version" , req .Version ),
822+ slog .String ("by" , auth .GetUserID (c )),
823+ )
824+
825+ if middleware .IsHTMX (c ) {
826+ c .Response ().Header ().Set ("HX-Redirect" , "/admin/foundry" )
827+ return c .NoContent (http .StatusNoContent )
828+ }
829+ return c .Redirect (http .StatusSeeOther , "/admin/foundry" )
830+ }
831+
832+ // readFoundryModuleVersion reads the version from foundry-module/module.json.
833+ func readFoundryModuleVersion () string {
834+ data , err := os .ReadFile ("foundry-module/module.json" )
835+ if err != nil {
836+ return "unknown"
837+ }
838+ var manifest struct {
839+ Version string `json:"version"`
840+ }
841+ if err := json .Unmarshal (data , & manifest ); err != nil {
842+ return "unknown"
843+ }
844+ return manifest .Version
845+ }
0 commit comments