main pico / pkg / apps / prose / uploader.go
Antonio Mika  ·  2026-10-11
 1package prose
 2
 3import (
 4	"fmt"
 5	"log/slog"
 6
 7	"github.com/picosh/pico/pkg/db"
 8	"github.com/picosh/pico/pkg/pssh"
 9	sendutils "github.com/picosh/pico/pkg/send/utils"
10	"github.com/picosh/pico/pkg/storage"
11)
12
13type ctxFeatureFlagKey struct{}
14
15func getFeatureFlag(s *pssh.SSHServerConnSession) *db.FeatureFlag {
16	v := s.Context().Value(ctxFeatureFlagKey{})
17	if v == nil {
18		return nil
19	}
20	ff := s.Context().Value(ctxFeatureFlagKey{}).(*db.FeatureFlag)
21	return ff
22}
23
24func setFeatureFlag(s *pssh.SSHServerConnSession, ff *db.FeatureFlag) {
25	s.SetValue(ctxFeatureFlagKey{}, ff)
26}
27
28type UploadHandler struct {
29	*fileRouter
30	Cfg *Config
31	DB  DB
32}
33
34var _ sendutils.CopyFromClientHandler = &UploadHandler{}
35var _ sendutils.CopyFromClientHandler = (*UploadHandler)(nil)
36
37func NewUploadHandler(cfg *Config, dbpool DB, fileMap map[string]ReadWriteHandler) *UploadHandler {
38	router := newFileRouter(cfg.Space, fileMap)
39	return &UploadHandler{
40		fileRouter: router,
41		Cfg:        cfg,
42		DB:         dbpool,
43	}
44}
45
46// NewFileHandler routes prose's text files to posts and the rest to images.
47func NewFileHandler(cfg *Config, dbpool DB, st storage.StorageServe) *UploadHandler {
48	// One post handler serves every text extension, so a post can be
49	// renamed in place from one to another.
50	posts := &postUploader{Cfg: cfg, DB: dbpool}
51	return NewUploadHandler(cfg, dbpool, map[string]ReadWriteHandler{
52		".md":      posts,
53		".txt":     posts,
54		".css":     posts,
55		".lxt":     posts,
56		"fallback": NewUploadImgHandler(dbpool, cfg, st),
57	})
58}
59
60func (h *UploadHandler) GetLogger(s *pssh.SSHServerConnSession) *slog.Logger {
61	logger := pssh.GetLogger(s)
62	if logger == nil {
63		if h.Cfg != nil && h.Cfg.Logger != nil {
64			return h.Cfg.Logger
65		}
66		return slog.Default()
67	}
68	return logger
69}
70
71func (h *UploadHandler) Validate(s *pssh.SSHServerConnSession) error {
72	logger := h.GetLogger(s)
73	user := pssh.GetUser(s)
74
75	if user == nil {
76		err := fmt.Errorf("could not get user from ctx")
77		logger.Error("error getting user from ctx", "err", err)
78		return err
79	}
80
81	ff, err := db.FindAccess(h.DB, user.ID, "prose")
82	if err != nil {
83		return err
84	}
85	ff.SetLimits(h.Cfg.MaxSize, h.Cfg.MaxAssetSize, h.Cfg.MaxSpecialFileSize)
86	setFeatureFlag(s, ff)
87
88	return h.fileRouter.Validate(s)
89}