- commit
- da7212a
- parent
- e65a196
- author
- Eric Bower
- date
- 2022-07-30 02:01:45 +0000 UTC
refactor: abstract scp upload handler
16 files changed,
+229,
-334
+10,
-5
1@@ -8,6 +8,7 @@ import (
2 "syscall"
3 "time"
4
5+ "git.sr.ht/~erock/pico/filehandlers"
6 "git.sr.ht/~erock/pico/lists"
7 "git.sr.ht/~erock/pico/shared"
8 "git.sr.ht/~erock/pico/wish/cms"
9@@ -27,7 +28,7 @@ func (me *SSHServer) authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
10 return true
11 }
12
13-func createRouter(handler *lists.DbHandler) proxy.Router {
14+func createRouter(handler *filehandlers.ScpUploadHandler) proxy.Router {
15 return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
16 cmd := s.Command()
17 mdw := []wish.Middleware{}
18@@ -45,7 +46,7 @@ func createRouter(handler *lists.DbHandler) proxy.Router {
19 }
20 }
21
22-func withProxy(handler *lists.DbHandler) ssh.Option {
23+func withProxy(handler *filehandlers.ScpUploadHandler) ssh.Option {
24 return func(server *ssh.Server) error {
25 err := sftp.SSHOption(handler)(server)
26 if err != nil {
27@@ -57,13 +58,17 @@ func withProxy(handler *lists.DbHandler) ssh.Option {
28 }
29
30 func main() {
31- host := shared.GetEnv("PROSE_HOST", "0.0.0.0")
32- port := shared.GetEnv("PROSE_SSH_PORT", "2222")
33+ host := shared.GetEnv("LISTS_HOST", "0.0.0.0")
34+ port := shared.GetEnv("LISTS_SSH_PORT", "2222")
35 cfg := lists.NewConfigSite()
36 logger := cfg.Logger
37 dbh := postgres.NewDB(&cfg.ConfigCms)
38 defer dbh.Close()
39- handler := lists.NewDbHandler(dbh, cfg)
40+
41+ fileHandler := lists.ListsHandler{
42+ Cfg: cfg,
43+ }
44+ handler := filehandlers.NewScpPostHandler(dbh, cfg, &fileHandler)
45
46 sshServer := &SSHServer{}
47 s, err := wish.NewServer(
+9,
-5
1@@ -8,6 +8,7 @@ import (
2 "syscall"
3 "time"
4
5+ "git.sr.ht/~erock/pico/filehandlers"
6 "git.sr.ht/~erock/pico/pastes"
7 "git.sr.ht/~erock/pico/shared"
8 "git.sr.ht/~erock/pico/wish/cms"
9@@ -27,7 +28,7 @@ func (me *SSHServer) authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
10 return true
11 }
12
13-func createRouter(handler *pastes.DbHandler) proxy.Router {
14+func createRouter(handler *filehandlers.ScpUploadHandler) proxy.Router {
15 return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
16 cmd := s.Command()
17 mdw := []wish.Middleware{}
18@@ -45,7 +46,7 @@ func createRouter(handler *pastes.DbHandler) proxy.Router {
19 }
20 }
21
22-func withProxy(handler *pastes.DbHandler) ssh.Option {
23+func withProxy(handler *filehandlers.ScpUploadHandler) ssh.Option {
24 return func(server *ssh.Server) error {
25 err := sftp.SSHOption(handler)(server)
26 if err != nil {
27@@ -57,13 +58,16 @@ func withProxy(handler *pastes.DbHandler) ssh.Option {
28 }
29
30 func main() {
31- host := shared.GetEnv("PROSE_HOST", "0.0.0.0")
32- port := shared.GetEnv("PROSE_SSH_PORT", "2222")
33+ host := shared.GetEnv("PASTES_HOST", "0.0.0.0")
34+ port := shared.GetEnv("PASTES_SSH_PORT", "2222")
35 cfg := pastes.NewConfigSite()
36 logger := cfg.Logger
37 dbh := postgres.NewDB(&cfg.ConfigCms)
38 defer dbh.Close()
39- handler := pastes.NewDbHandler(dbh, cfg)
40+ fileHandler := pastes.PastesHandler{
41+ Cfg: cfg,
42+ }
43+ handler := filehandlers.NewScpPostHandler(dbh, cfg, &fileHandler)
44
45 sshServer := &SSHServer{}
46 s, err := wish.NewServer(
+7,
-3
1@@ -8,6 +8,7 @@ import (
2 "syscall"
3 "time"
4
5+ "git.sr.ht/~erock/pico/filehandlers"
6 "git.sr.ht/~erock/pico/prose"
7 "git.sr.ht/~erock/pico/shared"
8 "git.sr.ht/~erock/pico/wish/cms"
9@@ -27,7 +28,7 @@ func (me *SSHServer) authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
10 return true
11 }
12
13-func createRouter(handler *prose.DbHandler) proxy.Router {
14+func createRouter(handler *filehandlers.ScpUploadHandler) proxy.Router {
15 return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
16 cmd := s.Command()
17 mdw := []wish.Middleware{}
18@@ -45,7 +46,7 @@ func createRouter(handler *prose.DbHandler) proxy.Router {
19 }
20 }
21
22-func withProxy(handler *prose.DbHandler) ssh.Option {
23+func withProxy(handler *filehandlers.ScpUploadHandler) ssh.Option {
24 return func(server *ssh.Server) error {
25 err := sftp.SSHOption(handler)(server)
26 if err != nil {
27@@ -63,7 +64,10 @@ func main() {
28 logger := cfg.Logger
29 dbh := postgres.NewDB(&cfg.ConfigCms)
30 defer dbh.Close()
31- handler := prose.NewDbHandler(dbh, cfg)
32+ fileHandler := &prose.ProseHandler{
33+ Cfg: cfg,
34+ }
35+ handler := filehandlers.NewScpPostHandler(dbh, cfg, fileHandler)
36
37 sshServer := &SSHServer{}
38 s, err := wish.NewServer(
R pastes/db_handler.go =>
filehandlers/post_handler.go
+62,
-35
1@@ -1,9 +1,8 @@
2-package pastes
3+package filehandlers
4
5 import (
6 "fmt"
7 "io"
8- "math"
9 "time"
10
11 "git.sr.ht/~erock/pico/shared"
12@@ -13,37 +12,36 @@ import (
13 "github.com/gliderlabs/ssh"
14 )
15
16-// IsTextFile reports whether the file has a known extension indicating
17-// a text file, or if a significant chunk of the specified file looks like
18-// correct UTF-8; that is, if it is likely that the file contains human-
19-// readable text.
20-func IsTextFile(text string, filename string) bool {
21- num := math.Min(float64(len(text)), 1024)
22- return shared.IsText(text[0:int(num)])
23+type PostMetaData struct {
24+ Text string
25+ Title string
26+ Description string
27+ PublishAt *time.Time
28+ Hidden bool
29+ Filename string
30 }
31
32-type Opener struct {
33- entry *utils.FileEntry
34+type ScpFileHooks interface {
35+ FileValidate(text string, filename string) (bool, error)
36+ FileMeta(text string, data *PostMetaData) error
37 }
38
39-func (o *Opener) Open(name string) (io.Reader, error) {
40- return o.entry.Reader, nil
41-}
42-
43-type DbHandler struct {
44+type ScpUploadHandler struct {
45 User *db.User
46 DBPool db.DB
47 Cfg *shared.ConfigSite
48+ Hooks ScpFileHooks
49 }
50
51-func NewDbHandler(dbpool db.DB, cfg *shared.ConfigSite) *DbHandler {
52- return &DbHandler{
53+func NewScpPostHandler(dbpool db.DB, cfg *shared.ConfigSite, hooks ScpFileHooks) *ScpUploadHandler {
54+ return &ScpUploadHandler{
55 DBPool: dbpool,
56 Cfg: cfg,
57+ Hooks: hooks,
58 }
59 }
60
61-func (h *DbHandler) Validate(s ssh.Session) error {
62+func (h *ScpUploadHandler) Validate(s ssh.Session) error {
63 var err error
64 key, err := util.KeyText(s)
65 if err != nil {
66@@ -63,16 +61,10 @@ func (h *DbHandler) Validate(s ssh.Session) error {
67 return nil
68 }
69
70-func (h *DbHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error) {
71+func (h *ScpUploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error) {
72 logger := h.Cfg.Logger
73 userID := h.User.ID
74 filename := entry.Name
75- title := filename
76- var err error
77- post, err := h.DBPool.FindPostWithFilename(filename, userID, h.Cfg.Space)
78- if err != nil {
79- logger.Debug("unable to load post, continuing:", err)
80- }
81
82 user, err := h.DBPool.FindUser(userID)
83 if err != nil {
84@@ -84,9 +76,31 @@ func (h *DbHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error)
85 text = string(b)
86 }
87
88- if !IsTextFile(text, entry.Filepath) {
89- logger.Errorf("WARNING: (%s) invalid file, the contents must be plain text, skipping", entry.Name)
90- return "", fmt.Errorf("WARNING: (%s) invalid file, the contents must be plain text, skipping", entry.Name)
91+ valid, err := h.Hooks.FileValidate(text, entry.Filepath)
92+ if !valid {
93+ return "", err
94+ }
95+
96+ post, err := h.DBPool.FindPostWithFilename(filename, userID, h.Cfg.Space)
97+ if err != nil {
98+ logger.Debugf("unable to load post (%s), continuing", filename)
99+ logger.Debug(err)
100+ }
101+
102+ now := time.Now()
103+ metadata := PostMetaData{
104+ Filename: filename,
105+ Title: shared.SanitizeFileExt(filename),
106+ PublishAt: &now,
107+ }
108+ if post != nil {
109+ metadata.PublishAt = post.PublishAt
110+ }
111+
112+ err = h.Hooks.FileMeta(text, &metadata)
113+ if err != nil {
114+ logger.Error(err)
115+ return "", err
116 }
117
118 // if the file is empty we remove it from our database
119@@ -104,27 +118,40 @@ func (h *DbHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error)
120 return "", fmt.Errorf("error for %s: %v", filename, err)
121 }
122 } else if post == nil {
123- publishAt := time.Now()
124 logger.Infof("(%s) not found, adding record", filename)
125- _, err = h.DBPool.InsertPost(userID, filename, title, text, "", &publishAt, false, h.Cfg.Space)
126+ _, err = h.DBPool.InsertPost(
127+ userID,
128+ filename,
129+ metadata.Title,
130+ text,
131+ metadata.Description,
132+ metadata.PublishAt,
133+ metadata.Hidden,
134+ h.Cfg.Space,
135+ )
136 if err != nil {
137 logger.Errorf("error for %s: %v", filename, err)
138 return "", fmt.Errorf("error for %s: %v", filename, err)
139 }
140 } else {
141- publishAt := post.PublishAt
142 if text == post.Text {
143 logger.Infof("(%s) found, but text is identical, skipping", filename)
144- return h.Cfg.PostURL(user.Name, filename), nil
145+ return h.Cfg.FullPostURL(user.Name, filename, h.Cfg.IsSubdomains(), true), nil
146 }
147
148 logger.Infof("(%s) found, updating record", filename)
149- _, err = h.DBPool.UpdatePost(post.ID, title, text, "", publishAt)
150+ _, err = h.DBPool.UpdatePost(
151+ post.ID,
152+ metadata.Title,
153+ text,
154+ metadata.Description,
155+ metadata.PublishAt,
156+ )
157 if err != nil {
158 logger.Errorf("error for %s: %v", filename, err)
159 return "", fmt.Errorf("error for %s: %v", filename, err)
160 }
161 }
162
163- return h.Cfg.PostURL(user.Name, filename), nil
164+ return h.Cfg.FullPostURL(user.Name, filename, h.Cfg.IsSubdomains(), true), nil
165 }
+1,
-1
1@@ -505,7 +505,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
2
3 var feedItems []*feeds.Item
4 for _, post := range posts {
5- if slices.Contains(HiddenPosts, post.Filename) {
6+ if slices.Contains(cfg.HiddenPosts, post.Filename) {
7 continue
8 }
9
+2,
-0
1@@ -36,6 +36,8 @@ func NewConfigSite() *shared.ConfigSite {
2 Description: "A microblog for your lists.",
3 IntroText: intro,
4 Space: "lists",
5+ AllowedExt: []string{".txt"},
6+ HiddenPosts: []string{"_header.txt", "_readme.txt"},
7 Logger: shared.CreateLogger(),
8 },
9 }
+0,
-135
1@@ -1,135 +0,0 @@
2-package lists
3-
4-import (
5- "fmt"
6- "io"
7- "time"
8-
9- "git.sr.ht/~erock/pico/lists/pkg"
10- "git.sr.ht/~erock/pico/shared"
11- "git.sr.ht/~erock/pico/wish/cms/db"
12- "git.sr.ht/~erock/pico/wish/cms/util"
13- sendutils "git.sr.ht/~erock/pico/wish/send/utils"
14- "github.com/gliderlabs/ssh"
15- "golang.org/x/exp/slices"
16-)
17-
18-var HiddenPosts = []string{"_readme", "_header"}
19-var allowedExtensions = []string{".txt"}
20-
21-type Opener struct {
22- entry *sendutils.FileEntry
23-}
24-
25-func (o *Opener) Open(name string) (io.Reader, error) {
26- return o.entry.Reader, nil
27-}
28-
29-type DbHandler struct {
30- User *db.User
31- DBPool db.DB
32- Cfg *shared.ConfigSite
33-}
34-
35-func NewDbHandler(dbpool db.DB, cfg *shared.ConfigSite) *DbHandler {
36- return &DbHandler{
37- DBPool: dbpool,
38- Cfg: cfg,
39- }
40-}
41-
42-func (h *DbHandler) Validate(s ssh.Session) error {
43- var err error
44- key, err := util.KeyText(s)
45- if err != nil {
46- return fmt.Errorf("key not found")
47- }
48-
49- user, err := h.DBPool.FindUserForKey(s.User(), key)
50- if err != nil {
51- return err
52- }
53-
54- if user.Name == "" {
55- return fmt.Errorf("must have username set")
56- }
57-
58- h.User = user
59- return nil
60-}
61-
62-func (h *DbHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (string, error) {
63- logger := h.Cfg.Logger
64- userID := h.User.ID
65- filename := shared.SanitizeFileExt(entry.Name)
66- title := filename
67-
68- post, err := h.DBPool.FindPostWithFilename(filename, userID, h.Cfg.Space)
69- if err != nil {
70- logger.Debug("unable to load post, continuing:", err)
71- }
72-
73- user, err := h.DBPool.FindUser(userID)
74- if err != nil {
75- return "", fmt.Errorf("error for %s: %v", filename, err)
76- }
77-
78- var text string
79- if b, err := io.ReadAll(entry.Reader); err == nil {
80- text = string(b)
81- }
82-
83- if !shared.IsTextFile(text, entry.Filepath, allowedExtensions) {
84- return "", fmt.Errorf("WARNING: (%s) invalid file, format must be '.txt' and the contents must be plain text, skipping", entry.Name)
85- }
86-
87- parsedText := pkg.ParseText(text)
88- if parsedText.MetaData.Title != "" {
89- title = parsedText.MetaData.Title
90- }
91- description := parsedText.MetaData.Description
92-
93- // if the file is empty we remove it from our database
94- if len(text) == 0 {
95- // skip empty files from being added to db
96- if post == nil {
97- logger.Infof("(%s) is empty, skipping record", filename)
98- return "", nil
99- }
100-
101- err := h.DBPool.RemovePosts([]string{post.ID})
102- logger.Infof("(%s) is empty, removing record", filename)
103- if err != nil {
104- return "", fmt.Errorf("error for %s: %v", filename, err)
105- }
106- } else if post == nil {
107- publishAt := time.Now()
108- if parsedText.MetaData.PublishAt != nil {
109- publishAt = *parsedText.MetaData.PublishAt
110- }
111- hidden := slices.Contains(HiddenPosts, filename)
112-
113- logger.Infof("(%s) not found, adding record", filename)
114- _, err = h.DBPool.InsertPost(userID, filename, title, text, description, &publishAt, hidden, h.Cfg.Space)
115- if err != nil {
116- return "", fmt.Errorf("error for %s: %v", filename, err)
117- }
118- } else {
119- publishAt := post.PublishAt
120- if parsedText.MetaData.PublishAt != nil {
121- publishAt = parsedText.MetaData.PublishAt
122- }
123- if text == post.Text {
124- logger.Infof("(%s) found, but text is identical, skipping", filename)
125- return h.Cfg.PostURL(user.Name, filename), nil
126- }
127-
128- logger.Infof("(%s) found, updating record", filename)
129- _, err = h.DBPool.UpdatePost(post.ID, title, text, description, publishAt)
130- if err != nil {
131- return "", fmt.Errorf("error for %s: %v", filename, err)
132- }
133- }
134-
135- return h.Cfg.PostURL(user.Name, filename), nil
136-}
+1,
-1
1@@ -403,7 +403,7 @@ func rssBlogHandler(ctx context.Context, w gemini.ResponseWriter, r *gemini.Requ
2
3 var feedItems []*feeds.Item
4 for _, post := range posts {
5- if slices.Contains(lists.HiddenPosts, post.Filename) {
6+ if slices.Contains(cfg.HiddenPosts, post.Filename) {
7 continue
8 }
9 parsed := pkg.ParseText(post.Text)
+47,
-0
1@@ -0,0 +1,47 @@
2+package lists
3+
4+import (
5+ "fmt"
6+ "strings"
7+
8+ "git.sr.ht/~erock/pico/filehandlers"
9+ "git.sr.ht/~erock/pico/lists/pkg"
10+ "git.sr.ht/~erock/pico/shared"
11+ "golang.org/x/exp/slices"
12+)
13+
14+type ListsHandler struct {
15+ Cfg *shared.ConfigSite
16+}
17+
18+func (p *ListsHandler) FileValidate(text string, filename string) (bool, error) {
19+ if !shared.IsTextFile(text, filename, p.Cfg.AllowedExt) {
20+ extStr := strings.Join(p.Cfg.AllowedExt, ",")
21+ err := fmt.Errorf(
22+ "WARNING: (%s) invalid file, format must be (%s) and the contents must be plain text, skipping",
23+ filename,
24+ extStr,
25+ )
26+ return false, err
27+ }
28+
29+ return true, nil
30+}
31+
32+func (p *ListsHandler) FileMeta(text string, data *filehandlers.PostMetaData) error {
33+ parsedText := pkg.ParseText(text)
34+
35+ if parsedText.MetaData.Title != "" {
36+ data.Title = parsedText.MetaData.Title
37+ }
38+
39+ data.Description = parsedText.MetaData.Description
40+
41+ if parsedText.MetaData.PublishAt != nil && !parsedText.MetaData.PublishAt.IsZero() {
42+ data.PublishAt = parsedText.MetaData.PublishAt
43+ }
44+
45+ data.Hidden = slices.Contains(p.Cfg.HiddenPosts, data.Filename)
46+
47+ return nil
48+}
+28,
-0
1@@ -0,0 +1,28 @@
2+package pastes
3+
4+import (
5+ "fmt"
6+
7+ "git.sr.ht/~erock/pico/filehandlers"
8+ "git.sr.ht/~erock/pico/shared"
9+)
10+
11+type PastesHandler struct {
12+ Cfg *shared.ConfigSite
13+}
14+
15+func (p *PastesHandler) FileValidate(text string, filename string) (bool, error) {
16+ if !shared.IsTextFile(text, filename, p.Cfg.AllowedExt) {
17+ err := fmt.Errorf(
18+ "WARNING: (%s) invalid file, the contents must be plain text, skipping",
19+ filename,
20+ )
21+ return false, err
22+ }
23+
24+ return true, nil
25+}
26+
27+func (p *PastesHandler) FileMeta(text string, data *filehandlers.PostMetaData) error {
28+ return nil
29+}
+1,
-1
1@@ -612,7 +612,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
2
3 var feedItems []*feeds.Item
4 for _, post := range posts {
5- if slices.Contains(hiddenPosts, post.Filename) {
6+ if slices.Contains(cfg.HiddenPosts, post.Filename) {
7 continue
8 }
9 parsed, err := ParseText(post.Text)
+2,
-0
1@@ -43,6 +43,8 @@ func NewConfigSite() *shared.ConfigSite {
2 Description: "a blog platform for hackers.",
3 IntroText: intro,
4 Space: "prose",
5+ AllowedExt: []string{".md", ".css"},
6+ HiddenPosts: []string{"_readme.md", "_styles.css"},
7 Logger: shared.CreateLogger(),
8 },
9 }
+0,
-145
1@@ -1,145 +0,0 @@
2-package prose
3-
4-import (
5- "fmt"
6- "io"
7- "strings"
8- "time"
9-
10- "git.sr.ht/~erock/pico/shared"
11- "git.sr.ht/~erock/pico/wish/cms/db"
12- "git.sr.ht/~erock/pico/wish/cms/util"
13- "git.sr.ht/~erock/pico/wish/send/utils"
14- "github.com/gliderlabs/ssh"
15- "golang.org/x/exp/slices"
16-)
17-
18-var hiddenPosts = []string{"_readme.md", "_styles.css"}
19-var allowedExtensions = []string{".md", ".css"}
20-
21-type Opener struct {
22- entry *utils.FileEntry
23-}
24-
25-func (o *Opener) Open(name string) (io.Reader, error) {
26- return o.entry.Reader, nil
27-}
28-
29-type DbHandler struct {
30- User *db.User
31- DBPool db.DB
32- Cfg *shared.ConfigSite
33-}
34-
35-func NewDbHandler(dbpool db.DB, cfg *shared.ConfigSite) *DbHandler {
36- return &DbHandler{
37- DBPool: dbpool,
38- Cfg: cfg,
39- }
40-}
41-
42-func (h *DbHandler) Validate(s ssh.Session) error {
43- var err error
44- key, err := util.KeyText(s)
45- if err != nil {
46- return fmt.Errorf("key not found")
47- }
48-
49- user, err := h.DBPool.FindUserForKey(s.User(), key)
50- if err != nil {
51- return err
52- }
53-
54- if user.Name == "" {
55- return fmt.Errorf("must have username set")
56- }
57-
58- h.User = user
59- return nil
60-}
61-
62-func (h *DbHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error) {
63- logger := h.Cfg.Logger
64- userID := h.User.ID
65- filename := shared.SanitizeFileExt(entry.Name)
66- title := filename
67- var err error
68- post, err := h.DBPool.FindPostWithFilename(filename, userID, h.Cfg.Space)
69- if err != nil {
70- logger.Debug("unable to load post, continuing:", err)
71- }
72-
73- user, err := h.DBPool.FindUser(userID)
74- if err != nil {
75- return "", fmt.Errorf("error for %s: %v", filename, err)
76- }
77-
78- var text string
79- if b, err := io.ReadAll(entry.Reader); err == nil {
80- text = string(b)
81- }
82-
83- if !shared.IsTextFile(text, entry.Filepath, allowedExtensions) {
84- extStr := strings.Join(allowedExtensions, ",")
85- logger.Errorf("WARNING: (%s) invalid file, format must be (%s) and the contents must be plain text, skipping", entry.Name, extStr)
86- return "", fmt.Errorf("WARNING: (%s) invalid file, format must be (%s) and the contents must be plain text, skipping", entry.Name, extStr)
87- }
88-
89- parsedText, err := ParseText(text)
90- if err != nil {
91- logger.Errorf("error for %s: %v", filename, err)
92- return "", fmt.Errorf("error for %s: %v", filename, err)
93- }
94-
95- if parsedText.MetaData.Title != "" {
96- title = parsedText.MetaData.Title
97- }
98- description := parsedText.MetaData.Description
99-
100- // if the file is empty we remove it from our database
101- if len(text) == 0 {
102- // skip empty files from being added to db
103- if post == nil {
104- logger.Infof("(%s) is empty, skipping record", filename)
105- return "", nil
106- }
107-
108- err := h.DBPool.RemovePosts([]string{post.ID})
109- logger.Infof("(%s) is empty, removing record", filename)
110- if err != nil {
111- logger.Errorf("error for %s: %v", filename, err)
112- return "", fmt.Errorf("error for %s: %v", filename, err)
113- }
114- } else if post == nil {
115- publishAt := time.Now()
116- if parsedText.MetaData.PublishAt != nil && !parsedText.MetaData.PublishAt.IsZero() {
117- publishAt = *parsedText.MetaData.PublishAt
118- }
119- hidden := slices.Contains(hiddenPosts, entry.Name)
120-
121- logger.Infof("(%s) not found, adding record", filename)
122- _, err = h.DBPool.InsertPost(userID, filename, title, text, description, &publishAt, hidden, h.Cfg.Space)
123- if err != nil {
124- logger.Errorf("error for %s: %v", filename, err)
125- return "", fmt.Errorf("error for %s: %v", filename, err)
126- }
127- } else {
128- publishAt := post.PublishAt
129- if parsedText.MetaData.PublishAt != nil {
130- publishAt = parsedText.MetaData.PublishAt
131- }
132- if text == post.Text {
133- logger.Infof("(%s) found, but text is identical, skipping", filename)
134- return h.Cfg.FullPostURL(user.Name, filename, h.Cfg.IsSubdomains(), true), nil
135- }
136-
137- logger.Infof("(%s) found, updating record", filename)
138- _, err = h.DBPool.UpdatePost(post.ID, title, text, description, publishAt)
139- if err != nil {
140- logger.Errorf("error for %s: %v", filename, err)
141- return "", fmt.Errorf("error for %s: %v", filename, err)
142- }
143- }
144-
145- return h.Cfg.FullPostURL(user.Name, filename, h.Cfg.IsSubdomains(), true), nil
146-}
+52,
-0
1@@ -0,0 +1,52 @@
2+package prose
3+
4+import (
5+ "fmt"
6+ "strings"
7+
8+ "git.sr.ht/~erock/pico/filehandlers"
9+ "git.sr.ht/~erock/pico/shared"
10+ "golang.org/x/exp/slices"
11+)
12+
13+// var hiddenPosts = []string{"_readme.md", "_styles.css"}
14+// var allowedExtensions = []string{".md", ".css"}
15+
16+type ProseHandler struct {
17+ Cfg *shared.ConfigSite
18+}
19+
20+func (p *ProseHandler) FileValidate(text string, filename string) (bool, error) {
21+ if !shared.IsTextFile(text, filename, p.Cfg.AllowedExt) {
22+ extStr := strings.Join(p.Cfg.AllowedExt, ",")
23+ err := fmt.Errorf(
24+ "WARNING: (%s) invalid file, format must be (%s) and the contents must be plain text, skipping",
25+ filename,
26+ extStr,
27+ )
28+ return false, err
29+ }
30+
31+ return true, nil
32+}
33+
34+func (p *ProseHandler) FileMeta(text string, data *filehandlers.PostMetaData) error {
35+ parsedText, err := ParseText(text)
36+ if err != nil {
37+ return err
38+ }
39+
40+ if parsedText.Title != "" {
41+ data.Title = parsedText.Title
42+ }
43+
44+ data.Description = parsedText.Description
45+
46+ if parsedText.PublishAt != nil && !parsedText.PublishAt.IsZero() {
47+ data.PublishAt = parsedText.MetaData.PublishAt
48+ }
49+
50+ data.Hidden = slices.Contains(p.Cfg.HiddenPosts, data.Filename)
51+
52+ return nil
53+}
1@@ -75,9 +75,11 @@ func IsText(s string) bool {
2 // correct UTF-8; that is, if it is likely that the file contains human-
3 // readable text.
4 func IsTextFile(text string, filename string, allowedExtensions []string) bool {
5- ext := pathpkg.Ext(filename)
6- if !slices.Contains(allowedExtensions, ext) {
7- return false
8+ if len(allowedExtensions) > 0 {
9+ ext := pathpkg.Ext(filename)
10+ if !slices.Contains(allowedExtensions, ext) {
11+ return false
12+ }
13 }
14
15 num := math.Min(float64(len(text)), 1024)
+2,
-0
1@@ -18,6 +18,8 @@ type ConfigCms struct {
2 Description string
3 IntroText string
4 Space string
5+ AllowedExt []string
6+ HiddenPosts []string
7 Logger *zap.SugaredLogger
8 }
9