-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add basic auth to todos, migrate examples to Sync() lifecycle #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,37 +4,57 @@ import ( | |
| "log" | ||
| "net/http" | ||
| "os" | ||
| "sync" | ||
| "time" | ||
|
|
||
| "github.com/livetemplate/livetemplate" | ||
| e2etest "github.com/livetemplate/lvt/testing" | ||
| ) | ||
|
|
||
| // NotepadController is a stateless singleton — all state lives in NotepadState | ||
| // and is shared across tabs via WithSharedState. | ||
| type NotepadController struct{} | ||
| type NotepadController struct { | ||
| mu sync.RWMutex | ||
| notes map[string]NotepadState // userID -> latest state | ||
| } | ||
|
|
||
| // NotepadState is shared across all tabs of the same authenticated user. | ||
| // WithSharedState means any change auto-broadcasts to all other tabs. | ||
| type NotepadState struct { | ||
| Username string `json:"username"` | ||
| Content string `json:"content"` | ||
| SavedAt string `json:"saved_at"` | ||
| CharCount int `json:"char_count"` | ||
| } | ||
|
|
||
| // Mount initializes the notepad for a new session. | ||
| func (c *NotepadController) Mount(state NotepadState, ctx *livetemplate.Context) (NotepadState, error) { | ||
| state.Username = ctx.UserID() | ||
| c.mu.RLock() | ||
| if saved, ok := c.notes[ctx.UserID()]; ok { | ||
| state.Content = saved.Content | ||
| state.CharCount = saved.CharCount | ||
| state.SavedAt = saved.SavedAt | ||
| } | ||
| c.mu.RUnlock() | ||
| return state, nil | ||
| } | ||
|
|
||
| // Save persists the note (triggered by the Save button). | ||
| // WithSharedState auto-broadcasts to all other tabs of this user. | ||
| func (c *NotepadController) Save(state NotepadState, ctx *livetemplate.Context) (NotepadState, error) { | ||
| state.Content = ctx.GetString("content") | ||
| state.CharCount = len([]rune(state.Content)) | ||
| state.SavedAt = time.Now().Format("15:04:05") | ||
|
|
||
| c.mu.Lock() | ||
| c.notes[ctx.UserID()] = state | ||
| c.mu.Unlock() | ||
|
|
||
| return state, nil | ||
| } | ||
|
|
||
| func (c *NotepadController) Sync(state NotepadState, ctx *livetemplate.Context) (NotepadState, error) { | ||
| c.mu.RLock() | ||
| if saved, ok := c.notes[ctx.UserID()]; ok { | ||
| state.Content = saved.Content | ||
| state.CharCount = saved.CharCount | ||
| state.SavedAt = saved.SavedAt | ||
| } | ||
| c.mu.RUnlock() | ||
| return state, nil | ||
|
Comment on lines
+50
to
58
|
||
| } | ||
|
|
||
|
|
@@ -49,21 +69,16 @@ func main() { | |
| log.Fatalf("Invalid configuration: %v", err) | ||
| } | ||
|
|
||
| // BasicAuth: each user gets their own isolated session group. | ||
| // The library's ChallengeAuthenticator sends WWW-Authenticate header | ||
| // automatically, triggering the browser's login dialog. | ||
| auth := livetemplate.NewBasicAuthenticator(func(username, password string) (bool, error) { | ||
| // Demo: accept any username with password "demo" | ||
| return password == "demo", nil | ||
| }) | ||
|
|
||
| opts := envConfig.ToOptions() | ||
| opts = append(opts, | ||
| livetemplate.WithAuthenticator(auth), | ||
| livetemplate.WithSharedState(), // All tabs of the same user share state | ||
| ) | ||
| opts = append(opts, livetemplate.WithAuthenticator(auth)) | ||
|
|
||
| controller := &NotepadController{} | ||
| controller := &NotepadController{ | ||
| notes: make(map[string]NotepadState), | ||
| } | ||
| initialState := &NotepadState{} | ||
|
|
||
| tmpl := livetemplate.Must(livetemplate.New("notepad", opts...)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The controller stores per-user notes in an unbounded in-memory map (
notes map[string]NotepadState). For a long-running server or many distinct usernames, this will grow without limit and can lead to memory pressure. Consider adding a simple eviction policy/TTL, limiting accepted usernames in the demo, or documenting that this is intentionally in-memory/demo-only.