Skip to content

Commit 6176fc4

Browse files
compscidrclaude
andcommitted
Move plugin settings to separate plugin_settings table
Plugin settings were stored in the main settings table with namespaced keys (analytics.enabled, analytics.tracking_id), which caused them to appear in the global settings form. - Add PluginSetting model with composite PK (plugin_name, key) - Auto-migrate plugin_settings table in Registry.Init() - Add UpdateSetting method on Registry - Add PATCH /api/v1/plugin-settings endpoint for saving plugin settings - Update JS to use the new endpoint for both toggle and form save - Remove blog.Setting dependency from plugin package Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1c90ffd commit 6176fc4

6 files changed

Lines changed: 75 additions & 22 deletions

File tree

admin/admin.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,45 @@ func (a *Admin) getPluginSettings(c *gin.Context) interface{} {
6767
return nil
6868
}
6969

70+
// UpdatePluginSettings saves plugin settings via the plugin registry.
71+
func (a *Admin) UpdatePluginSettings(c *gin.Context) {
72+
if !a.auth.IsAdmin(c) {
73+
c.JSON(http.StatusUnauthorized, "Not Authorized")
74+
return
75+
}
76+
77+
var settings []struct {
78+
Key string `json:"key"`
79+
Value string `json:"value"`
80+
}
81+
if err := c.BindJSON(&settings); err != nil {
82+
c.JSON(http.StatusBadRequest, "Malformed request")
83+
return
84+
}
85+
86+
reg, exists := c.Get("plugin_registry")
87+
if !exists {
88+
c.JSON(http.StatusInternalServerError, "Plugin registry not available")
89+
return
90+
}
91+
r, ok := reg.(*gplugin.Registry)
92+
if !ok {
93+
c.JSON(http.StatusInternalServerError, "Plugin registry not available")
94+
return
95+
}
96+
97+
for _, s := range settings {
98+
// Key format is "pluginname.settingkey"
99+
parts := strings.SplitN(s.Key, ".", 2)
100+
if len(parts) != 2 {
101+
continue
102+
}
103+
r.UpdateSetting(parts[0], parts[1], s.Value)
104+
}
105+
106+
c.JSON(http.StatusAccepted, settings)
107+
}
108+
70109
func (a *Admin) UpdateDb(db *gorm.DB) {
71110
a.db = &db
72111
}

goblog.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ func main() {
387387
router.POST("/test_db", testDB)
388388
router.POST("/api/v1/upload", goblog._admin.UploadFile)
389389
router.PATCH("/api/v1/settings", goblog._admin.UpdateSettings)
390+
router.PATCH("/api/v1/plugin-settings", goblog._admin.UpdatePluginSettings)
390391
//if we use true here - it will override the home route and just show files
391392
router.Use(static.Serve("/", static.LocalFile("www", false)))
392393
if err != nil {

plugin/registry.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package plugin
22

33
import (
4-
"goblog/blog"
54
"log"
65
"sync"
76
"time"
@@ -64,15 +63,16 @@ func (r *Registry) Init() error {
6463
if r.db == nil {
6564
return nil
6665
}
66+
// Create the plugin_settings table if it doesn't exist
67+
r.db.AutoMigrate(&PluginSetting{})
6768
for _, p := range r.plugins {
6869
for _, s := range p.Settings() {
69-
fullKey := p.Name() + "." + s.Key
70-
setting := blog.Setting{
71-
Key: fullKey,
72-
Type: s.Type,
73-
Value: s.DefaultValue,
70+
setting := PluginSetting{
71+
PluginName: p.Name(),
72+
Key: s.Key,
73+
Value: s.DefaultValue,
7474
}
75-
r.db.Where("key = ?", fullKey).FirstOrCreate(&setting)
75+
r.db.Where("plugin_name = ? AND key = ?", p.Name(), s.Key).FirstOrCreate(&setting)
7676
}
7777
if err := p.OnInit(r.db); err != nil {
7878
log.Printf("Plugin %s init error: %v", p.Name(), err)
@@ -112,15 +112,13 @@ func (r *Registry) Stop() {
112112
close(r.stopCh)
113113
}
114114

115-
// getPluginSettings returns a plugin's settings as a map with the
116-
// namespace prefix stripped (e.g. "analytics.tracking_id" -> "tracking_id").
115+
// getPluginSettings returns a plugin's settings as a simple key→value map.
117116
func (r *Registry) getPluginSettings(pluginName string) map[string]string {
118-
var settings []blog.Setting
119-
prefix := pluginName + "."
120-
r.db.Where("key LIKE ?", prefix+"%").Find(&settings)
117+
var settings []PluginSetting
118+
r.db.Where("plugin_name = ?", pluginName).Find(&settings)
121119
result := make(map[string]string)
122120
for _, s := range settings {
123-
result[s.Key[len(prefix):]] = s.Value
121+
result[s.Key] = s.Value
124122
}
125123
return result
126124
}
@@ -178,6 +176,13 @@ func (r *Registry) GetAllSettings() []PluginSettingsGroup {
178176
return groups
179177
}
180178

179+
// UpdateSetting saves a single plugin setting.
180+
func (r *Registry) UpdateSetting(pluginName, key, value string) {
181+
r.db.Where("plugin_name = ? AND key = ?", pluginName, key).
182+
Assign(PluginSetting{Value: value}).
183+
FirstOrCreate(&PluginSetting{PluginName: pluginName, Key: key, Value: value})
184+
}
185+
181186
// Middleware returns a Gin middleware that stores the registry on the context.
182187
func Middleware(registry *Registry) gin.HandlerFunc {
183188
return func(c *gin.Context) {

plugin/registry_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package plugin_test
22

33
import (
4-
"goblog/blog"
54
"goblog/plugin"
65
"net/http"
76
"net/http/httptest"
@@ -43,7 +42,7 @@ func (p *testPlugin) TemplateData(ctx *plugin.HookContext) gin.H {
4342

4443
func TestRegistryBasics(t *testing.T) {
4544
db, _ := gorm.Open(sqlite.Open(":memory:"))
46-
db.AutoMigrate(&blog.Setting{})
45+
db.AutoMigrate(&plugin.PluginSetting{})
4746

4847
reg := plugin.NewRegistry(db)
4948
tp := &testPlugin{}
@@ -59,7 +58,7 @@ func TestRegistryBasics(t *testing.T) {
5958

6059
func TestRegistryInit(t *testing.T) {
6160
db, _ := gorm.Open(sqlite.Open(":memory:"))
62-
db.AutoMigrate(&blog.Setting{})
61+
db.AutoMigrate(&plugin.PluginSetting{})
6362

6463
reg := plugin.NewRegistry(db)
6564
reg.Register(&testPlugin{})
@@ -68,16 +67,16 @@ func TestRegistryInit(t *testing.T) {
6867
}
6968

7069
// Check setting was seeded
71-
var setting blog.Setting
72-
db.Where("key = ?", "test.api_key").First(&setting)
70+
var setting plugin.PluginSetting
71+
db.Where("plugin_name = ? AND key = ?", "test", "api_key").First(&setting)
7372
if setting.Value != "default123" {
7473
t.Fatalf("expected default value 'default123', got %q", setting.Value)
7574
}
7675
}
7776

7877
func TestRegistryInjectTemplateData(t *testing.T) {
7978
db, _ := gorm.Open(sqlite.Open(":memory:"))
80-
db.AutoMigrate(&blog.Setting{})
79+
db.AutoMigrate(&plugin.PluginSetting{})
8180

8281
reg := plugin.NewRegistry(db)
8382
reg.Register(&testPlugin{})
@@ -121,7 +120,7 @@ func TestRegistryInjectTemplateData(t *testing.T) {
121120

122121
func TestGetAllSettings(t *testing.T) {
123122
db, _ := gorm.Open(sqlite.Open(":memory:"))
124-
db.AutoMigrate(&blog.Setting{})
123+
db.AutoMigrate(&plugin.PluginSetting{})
125124

126125
reg := plugin.NewRegistry(db)
127126
reg.Register(&testPlugin{})

plugin/setting.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package plugin
2+
3+
// PluginSetting stores a plugin's configuration in its own table,
4+
// separate from the blog's core Setting table.
5+
type PluginSetting struct {
6+
PluginName string `gorm:"primaryKey" json:"plugin_name"`
7+
Key string `gorm:"primaryKey" json:"key"`
8+
Value string `json:"value"`
9+
}

www/js/admin-script.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ function togglePluginEnabled(checkbox) {
238238
// Save the enabled setting immediately
239239
var settings = [{"key": pluginName + ".enabled", "value": enabled, "type": "text"}];
240240
$.ajax({
241-
url: "/api/v1/settings",
241+
url: "/api/v1/plugin-settings",
242242
type: "patch",
243243
dataType: "json",
244244
contentType: "application/json",
@@ -275,7 +275,7 @@ function updatePluginSettings(btn) {
275275
});
276276

277277
$.ajax({
278-
url: "/api/v1/settings",
278+
url: "/api/v1/plugin-settings",
279279
type: "patch",
280280
dataType: "json",
281281
contentType: "application/json",

0 commit comments

Comments
 (0)