Skip to content

Commit 5adc2ff

Browse files
author
mirkobrombin
committed
feat: implement BasePlugin for shared logging functionality across plugins
plus delete useless comments
1 parent 1b0758a commit 5adc2ff

File tree

6 files changed

+175
-150
lines changed

6 files changed

+175
-150
lines changed

internal/plugin/plugin.go

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package plugin
22

33
import (
4+
"fmt"
45
"net/http"
56
"sync"
7+
"time"
68

79
"github.com/mirkobrombin/goup/internal/config"
810
"github.com/mirkobrombin/goup/internal/server/middleware"
@@ -33,8 +35,14 @@ type PluginManager struct {
3335
plugins []Plugin
3436
}
3537

36-
// DefaultPluginManager is the default instance used by the application.
37-
var DefaultPluginManager *PluginManager
38+
var (
39+
// DefaultPluginManager is the default instance used by the application.
40+
DefaultPluginManager *PluginManager
41+
42+
progressBarRunning bool
43+
progressBarStopChan chan bool
44+
progressBarLock sync.Mutex
45+
)
3846

3947
// NewPluginManager creates a new PluginManager instance.
4048
func NewPluginManager() *PluginManager {
@@ -137,3 +145,42 @@ func PluginMiddleware(pm *PluginManager) middleware.MiddlewareFunc {
137145
})
138146
}
139147
}
148+
149+
// ShowProgressBar displays a basic spinner on stdout for a long-running task.
150+
func ShowProgressBar(taskName string) {
151+
progressBarLock.Lock()
152+
defer progressBarLock.Unlock()
153+
if progressBarRunning {
154+
return
155+
}
156+
progressBarRunning = true
157+
progressBarStopChan = make(chan bool)
158+
159+
go func() {
160+
chars := []rune{'|', '/', '-', '\\'}
161+
idx := 0
162+
for {
163+
select {
164+
case <-progressBarStopChan:
165+
return
166+
default:
167+
fmt.Printf("\r%s %c", taskName, chars[idx])
168+
idx = (idx + 1) % len(chars)
169+
time.Sleep(150 * time.Millisecond)
170+
}
171+
}
172+
}()
173+
}
174+
175+
// HideProgressBar stops the spinner and clears the line.
176+
func HideProgressBar() {
177+
progressBarLock.Lock()
178+
defer progressBarLock.Unlock()
179+
if !progressBarRunning {
180+
return
181+
}
182+
progressBarStopChan <- true
183+
close(progressBarStopChan)
184+
progressBarRunning = false
185+
fmt.Printf("\r\033[K")
186+
}

internal/plugin/plugin_base.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package plugin
2+
3+
import (
4+
"github.com/mirkobrombin/goup/internal/config"
5+
"github.com/mirkobrombin/goup/internal/logger"
6+
log "github.com/sirupsen/logrus"
7+
)
8+
9+
// BasePlugin stores two loggers and a domain name.
10+
// - DomainLogger: logs to console + domain file
11+
// - PluginLogger: logs only to the plugin-specific file
12+
type BasePlugin struct {
13+
DomainLogger *log.Logger
14+
PluginLogger *log.Logger
15+
Domain string
16+
}
17+
18+
// SetupLoggers is typically called in OnInitForSite, so each plugin can
19+
// use DomainLogger and PluginLogger without rewriting the same logic.
20+
func (bp *BasePlugin) SetupLoggers(conf config.SiteConfig, pluginName string, domainLogger *log.Logger) error {
21+
pluginLogger, err := logger.NewPluginLogger(conf.Domain, pluginName)
22+
if err != nil {
23+
domainLogger.Errorf("Failed to create plugin logger for domain %s: %v", conf.Domain, err)
24+
return err
25+
}
26+
27+
bp.DomainLogger = domainLogger
28+
bp.PluginLogger = pluginLogger
29+
bp.Domain = conf.Domain
30+
31+
return nil
32+
}

plugins/auth.go

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,16 @@ import (
99
"time"
1010

1111
"github.com/mirkobrombin/goup/internal/config"
12+
"github.com/mirkobrombin/goup/internal/plugin"
1213
log "github.com/sirupsen/logrus"
1314
)
1415

1516
// AuthPlugin provides HTTP Basic Authentication for protected paths.
1617
type AuthPlugin struct {
17-
// Keeps the parsed config for the plugin.
18-
conf AuthPluginConfig
19-
// State holds the active sessions.
18+
plugin.BasePlugin
19+
20+
conf AuthPluginConfig
2021
state *AuthPluginState
21-
// Logger instance for this plugin.
22-
logger *log.Logger
2322
}
2423

2524
// AuthPluginConfig represents the configuration for the AuthPlugin.
@@ -33,7 +32,6 @@ type AuthPluginConfig struct {
3332
SessionExpiration int `json:"session_expiration"`
3433
}
3534

36-
// session represents an authenticated session.
3735
type session struct {
3836
Username string
3937
Expiry time.Time
@@ -45,28 +43,24 @@ type AuthPluginState struct {
4543
mu sync.RWMutex
4644
}
4745

48-
// Name returns the name of the plugin.
4946
func (p *AuthPlugin) Name() string {
5047
return "AuthPlugin"
5148
}
5249

53-
// OnInit is called once during the global plugin initialization.
5450
func (p *AuthPlugin) OnInit() error {
55-
// No global setup needed here for now.
5651
return nil
5752
}
5853

59-
// OnInitForSite is called for each site configuration.
60-
func (p *AuthPlugin) OnInitForSite(conf config.SiteConfig, logger *log.Logger) error {
61-
p.logger = logger
54+
func (p *AuthPlugin) OnInitForSite(conf config.SiteConfig, domainLogger *log.Logger) error {
55+
if err := p.SetupLoggers(conf, p.Name(), domainLogger); err != nil {
56+
return err
57+
}
6258
p.state = &AuthPluginState{
6359
sessions: make(map[string]session),
6460
}
6561

66-
// Try to parse this plugin's config if present.
6762
pluginConfigRaw, ok := conf.PluginConfigs[p.Name()]
6863
if !ok {
69-
// If there's no AuthPlugin config, just do nothing.
7064
return nil
7165
}
7266

@@ -100,7 +94,7 @@ func (p *AuthPlugin) OnInitForSite(conf config.SiteConfig, logger *log.Logger) e
10094

10195
// Validate session expiration
10296
if authConfig.SessionExpiration > 86400 {
103-
return errors.New("session_expiration cannot exceed 86400 seconds (24 hours)")
97+
return errors.New("session_expiration cannot exceed 86400 seconds (24h)")
10498
}
10599
if authConfig.SessionExpiration < -1 {
106100
return errors.New("session_expiration cannot be less than -1")
@@ -110,28 +104,22 @@ func (p *AuthPlugin) OnInitForSite(conf config.SiteConfig, logger *log.Logger) e
110104

111105
// Initialization of the plugin state with optional session cleanup.
112106
if p.conf.SessionExpiration != -1 {
113-
go p.state.cleanupExpiredSessions(time.Minute, logger)
107+
go p.state.cleanupExpiredSessions(time.Minute, p.DomainLogger)
114108
}
115109

116-
logger.Infof("Initializing AuthPlugin for domain: %s with session_expiration: %d",
117-
conf.Domain, authConfig.SessionExpiration)
110+
p.DomainLogger.Infof("[AuthPlugin] Initialized for domain=%s with session_expiration=%d",
111+
conf.Domain, p.conf.SessionExpiration)
118112

119113
return nil
120114
}
121115

122-
// BeforeRequest is invoked before serving each request.
123-
func (p *AuthPlugin) BeforeRequest(r *http.Request) {
124-
// No specific pre-processing is needed here; logic is in HandleRequest.
125-
}
116+
func (p *AuthPlugin) BeforeRequest(r *http.Request) {}
126117

127-
// HandleRequest can fully handle the request, returning true if it does so.
128118
func (p *AuthPlugin) HandleRequest(w http.ResponseWriter, r *http.Request) bool {
129-
// If there's no plugin config, do nothing.
130119
if p.conf.Credentials == nil {
131120
return false
132121
}
133122

134-
// Check if the path is protected.
135123
protected := false
136124
for _, path := range p.conf.ProtectedPaths {
137125
if strings.HasPrefix(r.URL.Path, path) {
@@ -147,8 +135,8 @@ func (p *AuthPlugin) HandleRequest(w http.ResponseWriter, r *http.Request) bool
147135
// The path is protected, check session or credentials.
148136
ip := getClientIP(r)
149137
if sess, exists := p.state.getSession(ip); exists {
150-
p.logger.Infof("Session valid for IP: %s, user: %s", ip, sess.Username)
151-
return false // Let the next handler continue.
138+
p.DomainLogger.Infof("[AuthPlugin] Valid session for IP=%s user=%s", ip, sess.Username)
139+
return false
152140
}
153141

154142
// No valid session, check for Authorization header.
@@ -173,21 +161,15 @@ func (p *AuthPlugin) HandleRequest(w http.ResponseWriter, r *http.Request) bool
173161
}
174162

175163
// Create a new session
176-
p.state.createSession(ip, username, p.conf.SessionExpiration, p.logger)
177-
p.logger.Infof("Authenticated IP: %s, user: %s", ip, username)
164+
p.state.createSession(ip, username, p.conf.SessionExpiration, p.PluginLogger)
165+
p.PluginLogger.Infof("[AuthPlugin] Authenticated IP=%s user=%s", ip, username)
178166

179-
// Return false to continue normal flow.
180167
return false
181168
}
182169

183-
// AfterRequest is invoked after the request has been served or handled.
184-
func (p *AuthPlugin) AfterRequest(w http.ResponseWriter, r *http.Request) {
185-
// Nothing to do here in this plugin.
186-
}
170+
func (p *AuthPlugin) AfterRequest(w http.ResponseWriter, r *http.Request) {}
187171

188-
// OnExit is called when the server is shutting down.
189172
func (p *AuthPlugin) OnExit() error {
190-
// No cleanup needed, but you could stop session cleanup goroutines if needed.
191173
return nil
192174
}
193175

@@ -200,6 +182,7 @@ func getClientIP(r *http.Request) string {
200182
// X-Forwarded-For may contain multiple IPs, take the first one
201183
return strings.Split(ips, ",")[0]
202184
}
185+
203186
// Fallback to RemoteAddr
204187
ip := r.RemoteAddr
205188
if colonIndex := strings.LastIndex(ip, ":"); colonIndex != -1 {
@@ -263,9 +246,9 @@ func (s *AuthPluginState) createSession(ip, username string, expiration int, log
263246
Expiry: expiry,
264247
}
265248
if expiration != -1 {
266-
logger.Infof("Session created for IP: %s, user: %s, expires at: %v", ip, username, expiry)
249+
logger.Infof("[AuthPlugin] Created session IP=%s user=%s expires=%v", ip, username, expiry)
267250
} else {
268-
logger.Infof("Session created for IP: %s, user: %s, never expires", ip, username)
251+
logger.Infof("[AuthPlugin] Created session IP=%s user=%s never expires", ip, username)
269252
}
270253
}
271254

@@ -278,7 +261,7 @@ func (s *AuthPluginState) cleanupExpiredSessions(interval time.Duration, logger
278261
for ip, sess := range s.sessions {
279262
if !sess.Expiry.IsZero() && sess.Expiry.Before(time.Now()) {
280263
delete(s.sessions, ip)
281-
logger.Infof("Session expired and removed for IP: %s, user: %s", ip, sess.Username)
264+
logger.Infof("[AuthPlugin] Session expired removed IP=%s user=%s", ip, sess.Username)
282265
}
283266
}
284267
s.mu.Unlock()

plugins/custom_header.go

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,36 @@ import (
55
"strings"
66

77
"github.com/mirkobrombin/goup/internal/config"
8+
"github.com/mirkobrombin/goup/internal/plugin"
89
log "github.com/sirupsen/logrus"
910
)
1011

1112
// CustomHeaderPlugin adds a custom header to all HTTP responses.
1213
type CustomHeaderPlugin struct {
13-
// siteConfigs stores the configurations keyed by domain.
14+
plugin.BasePlugin
15+
1416
siteConfigs map[string]config.SiteConfig
15-
// logger is optional and can be shared among sites.
16-
logger *log.Logger
1717
}
1818

19-
// Name returns the name of the plugin.
2019
func (p *CustomHeaderPlugin) Name() string {
2120
return "CustomHeaderPlugin"
2221
}
2322

24-
// OnInit is called once during the global plugin initialization.
2523
func (p *CustomHeaderPlugin) OnInit() error {
2624
p.siteConfigs = make(map[string]config.SiteConfig)
2725
return nil
2826
}
2927

30-
// OnInitForSite initializes the plugin for a specific site.
31-
func (p *CustomHeaderPlugin) OnInitForSite(conf config.SiteConfig, logger *log.Logger) error {
32-
// Store the config, keyed by domain.
33-
p.siteConfigs[conf.Domain] = conf
34-
if p.logger == nil {
35-
p.logger = logger
28+
func (p *CustomHeaderPlugin) OnInitForSite(conf config.SiteConfig, domainLogger *log.Logger) error {
29+
if err := p.SetupLoggers(conf, p.Name(), domainLogger); err != nil {
30+
return err
3631
}
32+
p.siteConfigs[conf.Domain] = conf
3733
return nil
3834
}
3935

40-
// BeforeRequest is invoked before serving each request.
41-
func (p *CustomHeaderPlugin) BeforeRequest(r *http.Request) {
42-
// No operation here; logic is in HandleRequest.
43-
}
36+
func (p *CustomHeaderPlugin) BeforeRequest(r *http.Request) {}
4437

45-
// HandleRequest can fully handle the request, returning true if it does so.
4638
func (p *CustomHeaderPlugin) HandleRequest(w http.ResponseWriter, r *http.Request) bool {
4739
// Identify the domain and strip any port.
4840
host := r.Host
@@ -51,24 +43,20 @@ func (p *CustomHeaderPlugin) HandleRequest(w http.ResponseWriter, r *http.Reques
5143
}
5244

5345
// Check if there's a site config for this domain.
54-
if conf, ok := p.siteConfigs[host]; ok {
55-
for key, value := range conf.CustomHeaders {
56-
w.Header().Set(key, value)
57-
if p.logger != nil {
58-
p.logger.Infof("Added custom header %s: %s", key, value)
59-
}
60-
}
46+
conf, ok := p.siteConfigs[host]
47+
if !ok {
48+
return false
49+
}
50+
51+
for key, value := range conf.CustomHeaders {
52+
w.Header().Set(key, value)
53+
p.DomainLogger.Infof("[CustomHeaderPlugin] Added header=%s value=%s (domain=%s)", key, value, host)
6154
}
62-
// Returning false tells GoUP to continue with other handlers.
6355
return false
6456
}
6557

66-
// AfterRequest is invoked after the request has been served or handled.
67-
func (p *CustomHeaderPlugin) AfterRequest(w http.ResponseWriter, r *http.Request) {
68-
// No operation here.
69-
}
58+
func (p *CustomHeaderPlugin) AfterRequest(w http.ResponseWriter, r *http.Request) {}
7059

71-
// OnExit is called when the server is shutting down.
7260
func (p *CustomHeaderPlugin) OnExit() error {
7361
return nil
7462
}

0 commit comments

Comments
 (0)