Commit c6e626e

Antonio Mika  ·  2026-10-10 15:51:00 -0400 EDT
parent 949a5c0
feat(features): honor standalone flags for every service
8 files changed,  +50, -84
+1, -1
......@@ -21,7 +21,7 @@ rsync, scp, or sftp. Hopefully you see the trend.
2121 - [pipe](https://pipe.pico.sh): Authenticated `*nix` pipes over `ssh`
2222 - [prose](https://pico.sh/prose): A blog platform using `ssh` for content
2323 management.
24-- [rss-to-email](https://pico.sh/feeds): An RSS email notification service using `ssh`.
24+- [feeds](https://pico.sh/feeds): An RSS email notification service using `ssh`.
2525 - [pastes](https://pico.sh/pastes): Upload code snippets using rsync, scp, and
2626 sftp.
2727
+1, -7
......@@ -160,13 +160,7 @@ func Middleware(dbpool db.DB, cfg *shared.ConfigSite) pssh.SSHServerMiddleware {
160160 logger.Info("run cmd", "filename", filename)
161161 fetcher := NewFetcher(dbpool, cfg)
162162
163- isPicoPlus := false
164- ff, _ := dbpool.FindFeature(user.ID, "plus")
165- if ff != nil {
166- if ff.IsValid() {
167- isPicoPlus = true
168- }
169- }
163+ isPicoPlus := db.HasPlusOrSpace(dbpool, user.ID, "feeds")
170164
171165 err = fetcher.RunPost(logger, user, isPicoPlus, post, true, time.Now().UTC())
172166 if err != nil {
+2, -9
......@@ -336,16 +336,9 @@ func (f *Fetcher) RunUser(user *db.User, now time.Time) error {
336336 if err != nil {
337337 return err
338338 }
339- isPicoPlus := false
340- ff, _ := f.db.FindFeature(user.ID, "plus")
341- if ff != nil {
342- if ff.IsValid() {
343- isPicoPlus = true
344- }
345- }
346-
339+ isPicoPlus := db.HasPlusOrSpace(f.db, user.ID, "feeds")
347340 if !isPicoPlus {
348- return fmt.Errorf("must be pico+ user to receive rss digests")
341+ return fmt.Errorf("must have pico+ or feeds access to receive rss digests")
349342 }
350343
351344 if len(posts.Data) > 0 {
+4, -0
......@@ -660,3 +660,7 @@ type DB interface {
660660
661661 Close() error
662662 }
663+
664+func HasPlusOrSpace(dbpool DB, userID, space string) bool {
665+ return dbpool.HasFeatureByUser(userID, "plus") || dbpool.HasFeatureByUser(userID, space)
666+}
+1, -1
......@@ -77,7 +77,7 @@ func (hc *ApiConfig) HasPrivilegedAccess(apiToken string) bool {
7777 }
7878
7979 func (hc *ApiConfig) HasPlusOrSpace(user *db.User, space string) bool {
80- return hc.Dbpool.HasFeatureByUser(user.ID, "plus") || hc.Dbpool.HasFeatureByUser(user.ID, space)
80+ return db.HasPlusOrSpace(hc.Dbpool, user.ID, space)
8181 }
8282
8383 func (hc *ApiConfig) CreateCtx(prevCtx context.Context, subdomain string) context.Context {
+35, -47
......@@ -153,14 +153,42 @@ func (m *FeaturesList) getFeaturesKv() []Kv {
153153 }
154154
155155 type ServicesList struct {
156- plusFf *db.FeatureFlag
157- pgsFf *db.FeatureFlag
158- proseFf *db.FeatureFlag
159- pastesFf *db.FeatureFlag
156+ shared *SharedModel
157+ data [][]string
158+}
159+
160+func NewServicesList(shrd *SharedModel) *ServicesList {
161+ return &ServicesList{shared: shrd}
162+}
163+
164+func (m *ServicesList) HandleEvent(ev vaxis.Event, phase vxfw.EventPhase) (vxfw.Command, error) {
165+ switch ev.(type) {
166+ case vxfw.Init:
167+ m.fetchServices()
168+ return vxfw.RedrawCmd{}, nil
169+ }
170+ return nil, nil
160171 }
161172
162-func NewServicesList(plusFf *db.FeatureFlag, pgsFf *db.FeatureFlag, proseFf *db.FeatureFlag, pastesFf *db.FeatureFlag) *ServicesList {
163- return &ServicesList{plusFf: plusFf, pgsFf: pgsFf, proseFf: proseFf, pastesFf: pastesFf}
173+func (m *ServicesList) fetchServices() {
174+ dbpool := m.shared.Dbpool
175+ userID := m.shared.User.ID
176+ status := func(space, missing string) string {
177+ if db.HasPlusOrSpace(dbpool, userID, space) {
178+ return "active"
179+ }
180+ return missing
181+ }
182+ m.data = [][]string{
183+ {"name", "status"},
184+ {"pipe", "active"},
185+ {"pastes", status("pastes", "invite/pico+")},
186+ {"prose", status("prose", "invite/pico+")},
187+ {"pages", status("pgs", "invite/pico+")},
188+ {"tuns", status("tuns", "pico+")},
189+ {"irc bouncer", status("bouncer", "pico+")},
190+ {"feeds", status("feeds", "pico+")},
191+ }
164192 }
165193
166194 func (m *ServicesList) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
......@@ -179,48 +207,8 @@ func (m *ServicesList) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
179207 }
180208
181209 func (m *ServicesList) getServiceKv() []Kv {
182- hasPlus := m.plusFf != nil && m.plusFf.IsValid()
183- hasPgs := m.pgsFf != nil && m.pgsFf.IsValid()
184- hasProse := m.proseFf != nil && m.proseFf.IsValid()
185- hasPastes := m.pastesFf != nil && m.pastesFf.IsValid()
186- pagesStatus := "invite/pico+"
187- if hasPlus || hasPgs {
188- pagesStatus = "active"
189- }
190- proseStatus := "invite/pico+"
191- if hasPlus || hasProse {
192- proseStatus = "active"
193- }
194- pastesStatus := "invite/pico+"
195- if hasPlus || hasPastes {
196- pastesStatus = "active"
197- }
198- data := [][]string{
199- {"name", "status"},
200- {"pipe", "active"},
201- {"pastes", pastesStatus},
202- {"prose", proseStatus},
203- {"pages", pagesStatus},
204- }
205-
206- if hasPlus {
207- data = append(
208- data,
209- []string{"tuns", "active"},
210- []string{"irc bouncer", "active"},
211- []string{"rss-to-email", "active"},
212- )
213- } else {
214- data = append(
215- data,
216- []string{"tuns", "pico+"},
217- []string{"irc bouncer", "pico+"},
218- []string{"rss-to-email", "pico+"},
219- )
220- }
221-
222210 kv := []Kv{}
223- for idx, d := range data {
211+ for idx, d := range m.data {
224212 value := d[1]
225213 var style vaxis.Style
226214 if idx == 0 {
+6, -7
......@@ -28,6 +28,7 @@ type MenuPage struct {
2828
2929 list list.Dynamic
3030 features *FeaturesList
31+ services *ServicesList
3132 stats *db.UserStats
3233 }
3334
......@@ -50,6 +51,7 @@ func NewMenuPage(shrd *SharedModel) *MenuPage {
5051 m := &MenuPage{shared: shrd}
5152 m.list = list.Dynamic{Builder: getMenuWidget, DrawCursor: true}
5253 m.features = NewFeaturesList(shrd)
54+ m.services = NewServicesList(shrd)
5355 return m
5456 }
5557
......@@ -74,8 +76,10 @@ func (m *MenuPage) HandleEvent(ev vaxis.Event, phase vxfw.EventPhase) (vxfw.Comm
7476 case PageIn:
7577 _ = m.fetchUsageStats()
7678 cmd, _ := m.features.HandleEvent(vxfw.Init{}, phase)
79+ servicesCmd, _ := m.services.HandleEvent(vxfw.Init{}, phase)
7780 return vxfw.BatchCmd([]vxfw.Command{
7881 cmd,
82+ servicesCmd,
7983 vxfw.FocusWidgetCmd(&m.list),
8084 }), nil
8185 case vaxis.Key:
......@@ -105,12 +109,7 @@ func (m *MenuPage) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
105109 },
106110 })
107111
108- services, _ := NewServicesList(
109- m.shared.PlusFeatureFlag,
110- m.shared.PgsFeatureFlag,
111- m.shared.ProseFeatureFlag,
112- m.shared.PastesFeatureFlag,
113- ).Draw(ctx)
112+ services, _ := m.services.Draw(ctx)
114113 features, _ := m.features.Draw(ctx)
115114
116115 leftPane := NewGroupStack([]vxfw.Surface{
......@@ -129,7 +128,7 @@ func (m *MenuPage) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
129128 pages, _ := NewUsageInfo("pages", &m.stats.Pages).Draw(ctx)
130129 prose, _ := NewUsageInfo("prose", &m.stats.Prose).Draw(ctx)
131130 pastes, _ := NewUsageInfo("pastes", &m.stats.Pastes).Draw(ctx)
132- feeds, _ := NewUsageInfo("rss-to-email", &m.stats.Feeds).Draw(ctx)
131+ feeds, _ := NewUsageInfo("feeds", &m.stats.Feeds).Draw(ctx)
133132 right = append(right,
134133 pages,
135134 prose,
+0, -12
......@@ -31,9 +31,6 @@ type SharedModel struct {
3131 InvitedBy string
3232 PlusFeatureFlag *db.FeatureFlag
3333 BouncerFeatureFlag *db.FeatureFlag
34- PgsFeatureFlag *db.FeatureFlag
35- ProseFeatureFlag *db.FeatureFlag
36- PastesFeatureFlag *db.FeatureFlag
3734 Impersonator string
3835 App *vxfw.App
3936 }
......@@ -277,15 +274,6 @@ func initData(shrd *SharedModel) error {
277274 bff, _ := FindFeatureFlag(shrd, "bouncer")
278275 shrd.BouncerFeatureFlag = bff
279276
280- pff, _ := FindFeatureFlag(shrd, "pgs")
281- shrd.PgsFeatureFlag = pff
282-
283- prosef, _ := FindFeatureFlag(shrd, "prose")
284- shrd.ProseFeatureFlag = prosef
285-
286- pastesf, _ := FindFeatureFlag(shrd, "pastes")
287- shrd.PastesFeatureFlag = pastesf
288-
289277 if shrd.User != nil {
290278 invitedBy, _ := shrd.Dbpool.FindWhoInvitedUser(user.ID)
291279 shrd.InvitedBy = invitedBy