- commit
- 1e436da
- parent
- 44c4fb4
- author
- Eric Bower
- date
- 2024-02-17 03:18:04 +0000 UTC
refactor: replace zap with slog (#75) chore: remove lists project
+1,
-1
1@@ -41,7 +41,7 @@ jobs:
2 needs: test
3 strategy:
4 matrix:
5- APP: [lists, prose, pastes, imgs, pgs, feeds]
6+ APP: [prose, pastes, imgs, pgs, feeds]
7 steps:
8 - name: Checkout repo
9 uses: actions/checkout@v3
+1,
-1
1@@ -22,7 +22,7 @@ jobs:
2 pgit \
3 --out ./public \
4 --label pico \
5- --desc "pico services - prose.sh, lists.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh" \
6+ --desc "pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh" \
7 --clone-url "https://github.com/picosh/pico.git" \
8 --home-url "https://git.erock.io" \
9 --revs main
+3,
-3
1@@ -14,7 +14,7 @@ FROM builder-deps as builder-web
2
3 COPY . .
4
5-ARG APP=lists
6+ARG APP=prose
7 ARG TARGETOS
8 ARG TARGETARCH
9
10@@ -33,7 +33,7 @@ FROM scratch as release-web
11
12 WORKDIR /app
13
14-ARG APP=lists
15+ARG APP=prose
16
17 COPY --from=builder-web /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
18 COPY --from=builder-web /go/bin/${APP}-web ./web
19@@ -46,7 +46,7 @@ FROM scratch as release-ssh
20
21 WORKDIR /app
22
23-ARG APP=lists
24+ARG APP=prose
25
26 COPY --from=builder-ssh /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
27 COPY --from=builder-ssh /go/bin/${APP}-ssh ./ssh
M
Makefile
+3,
-3
1@@ -44,7 +44,7 @@ bp-%: bp-setup
2 $(DOCKER_BUILDX_BUILD) --build-arg "APP=$*" -t "ghcr.io/picosh/pico/$*-web:$(DOCKER_TAG)" --target release-web .
3 .PHONY: bp-%
4
5-bp-all: bp-prose bp-lists bp-pastes bp-imgs bp-feeds bp-pgs bp-auth bp-bouncer
6+bp-all: bp-prose bp-pastes bp-imgs bp-feeds bp-pgs bp-auth bp-bouncer
7 .PHONY: bp-all
8
9 bp-podman-%:
10@@ -54,7 +54,7 @@ bp-podman-%:
11 $(DOCKER_CMD) push "ghcr.io/picosh/pico/$*-web:$(DOCKER_TAG)"
12 .PHONY: bp-%
13
14-bp-podman-all: bp-podman-prose bp-podman-lists bp-podman-pastes bp-podman-imgs bp-podman-feeds bp-podman-pgs
15+bp-podman-all: bp-podman-prose bp-podman-pastes bp-podman-imgs bp-podman-feeds bp-podman-pgs
16 .PHONY: all
17
18 build-auth:
19@@ -66,7 +66,7 @@ build-%:
20 go build -o "build/$*-ssh" "./cmd/$*/ssh"
21 .PHONY: build-%
22
23-build: build-prose build-lists build-pastes build-imgs build-feeds build-pgs build-auth
24+build: build-prose build-pastes build-imgs build-feeds build-pgs build-auth
25 .PHONY: build
26
27 store-clean:
+41,
-20
1@@ -5,6 +5,7 @@ import (
2 "encoding/json"
3 "fmt"
4 "html/template"
5+ "log/slog"
6 "net/http"
7 "net/url"
8 "strings"
9@@ -12,13 +13,12 @@ import (
10 "github.com/picosh/pico/db"
11 "github.com/picosh/pico/db/postgres"
12 "github.com/picosh/pico/shared"
13- "go.uber.org/zap"
14 )
15
16 type Client struct {
17 Cfg *AuthCfg
18 Dbpool db.DB
19- Logger *zap.SugaredLogger
20+ Logger *slog.Logger
21 }
22
23 func (client *Client) hasPrivilegedAccess(apiToken string) bool {
24@@ -73,7 +73,7 @@ func wellKnownHandler(w http.ResponseWriter, r *http.Request) {
25 w.WriteHeader(http.StatusOK)
26 err := json.NewEncoder(w).Encode(p)
27 if err != nil {
28- client.Logger.Error(err)
29+ client.Logger.Error(err.Error())
30 http.Error(w, err.Error(), http.StatusInternalServerError)
31 }
32 }
33@@ -86,11 +86,11 @@ type oauth2Introspection struct {
34 func introspectHandler(w http.ResponseWriter, r *http.Request) {
35 client := getClient(r)
36 token := r.FormValue("token")
37- client.Logger.Infof("introspect token (%s)", token)
38+ client.Logger.Info("introspect token", "token", token)
39
40 user, err := client.Dbpool.FindUserForToken(token)
41 if err != nil {
42- client.Logger.Error(err)
43+ client.Logger.Error(err.Error())
44 http.Error(w, err.Error(), http.StatusUnauthorized)
45 return
46 }
47@@ -103,7 +103,7 @@ func introspectHandler(w http.ResponseWriter, r *http.Request) {
48 w.WriteHeader(http.StatusOK)
49 err = json.NewEncoder(w).Encode(p)
50 if err != nil {
51- client.Logger.Error(err)
52+ client.Logger.Error(err.Error())
53 http.Error(w, err.Error(), http.StatusInternalServerError)
54 }
55 }
56@@ -116,7 +116,13 @@ func authorizeHandler(w http.ResponseWriter, r *http.Request) {
57 redirectURI := r.URL.Query().Get("redirect_uri")
58 scope := r.URL.Query().Get("scope")
59
60- client.Logger.Infof("authorize handler (%s, %s, %s, %s)", responseType, clientID, redirectURI, scope)
61+ client.Logger.Info(
62+ "authorize handler",
63+ "responseType", responseType,
64+ "clientID", clientID,
65+ "redirectURI", redirectURI,
66+ "scope", scope,
67+ )
68
69 ts, err := template.ParseFiles(
70 "auth/html/redirect.page.tmpl",
71@@ -126,7 +132,7 @@ func authorizeHandler(w http.ResponseWriter, r *http.Request) {
72 )
73
74 if err != nil {
75- client.Logger.Error(err)
76+ client.Logger.Error(err.Error())
77 http.Error(w, err.Error(), http.StatusUnauthorized)
78 return
79 }
80@@ -139,7 +145,7 @@ func authorizeHandler(w http.ResponseWriter, r *http.Request) {
81 })
82
83 if err != nil {
84- client.Logger.Error(err)
85+ client.Logger.Error(err.Error())
86 http.Error(w, err.Error(), http.StatusUnauthorized)
87 return
88 }
89@@ -152,7 +158,11 @@ func redirectHandler(w http.ResponseWriter, r *http.Request) {
90 redirectURI := r.FormValue("redirect_uri")
91 responseType := r.FormValue("response_type")
92
93- client.Logger.Infof("redirect handler (%s, %s, %s)", token, redirectURI, responseType)
94+ client.Logger.Info("redirect handler",
95+ "token", token,
96+ "redirectURI", redirectURI,
97+ "responseType", responseType,
98+ )
99
100 if token == "" || redirectURI == "" || responseType != "code" {
101 http.Error(w, "bad request", http.StatusBadRequest)
102@@ -184,11 +194,16 @@ func tokenHandler(w http.ResponseWriter, r *http.Request) {
103 redirectURI := r.FormValue("redirect_uri")
104 grantType := r.FormValue("grant_type")
105
106- client.Logger.Infof("handle token (%s, %s, %s)", token, redirectURI, grantType)
107+ client.Logger.Info(
108+ "handle token",
109+ "token", token,
110+ "redirectURI", redirectURI,
111+ "grantType", grantType,
112+ )
113
114 _, err := client.Dbpool.FindUserForToken(token)
115 if err != nil {
116- client.Logger.Error(err)
117+ client.Logger.Error(err.Error())
118 http.Error(w, err.Error(), http.StatusUnauthorized)
119 return
120 }
121@@ -200,7 +215,7 @@ func tokenHandler(w http.ResponseWriter, r *http.Request) {
122 w.WriteHeader(http.StatusOK)
123 err = json.NewEncoder(w).Encode(p)
124 if err != nil {
125- client.Logger.Error(err)
126+ client.Logger.Error(err.Error())
127 http.Error(w, err.Error(), http.StatusInternalServerError)
128 }
129 }
130@@ -218,7 +233,7 @@ func keyHandler(w http.ResponseWriter, r *http.Request) {
131
132 err := json.NewDecoder(r.Body).Decode(&data)
133 if err != nil {
134- client.Logger.Error(err)
135+ client.Logger.Error(err.Error())
136 http.Error(w, err.Error(), http.StatusBadRequest)
137 return
138 }
139@@ -226,15 +241,21 @@ func keyHandler(w http.ResponseWriter, r *http.Request) {
140 space := r.URL.Query().Get("space")
141 if space == "" {
142 spaceErr := fmt.Errorf("Must provide `space` query parameter")
143- client.Logger.Error(spaceErr)
144+ client.Logger.Error(spaceErr.Error())
145 http.Error(w, spaceErr.Error(), http.StatusUnprocessableEntity)
146 }
147
148- client.Logger.Infof("handle key (%s, %s, %s, %s)", data.RemoteAddress, data.Username, space, data.PublicKey)
149+ client.Logger.Info(
150+ "handle key",
151+ "remoteAddress", data.RemoteAddress,
152+ "user", data.Username,
153+ "space", space,
154+ "publicKey", data.PublicKey,
155+ )
156
157 user, err := client.Dbpool.FindUserForKey(data.Username, data.PublicKey)
158 if err != nil {
159- client.Logger.Error(err)
160+ client.Logger.Error(err.Error())
161 http.Error(w, err.Error(), http.StatusUnauthorized)
162 return
163 }
164@@ -253,7 +274,7 @@ func keyHandler(w http.ResponseWriter, r *http.Request) {
165 w.WriteHeader(http.StatusOK)
166 err = json.NewEncoder(w).Encode(user)
167 if err != nil {
168- client.Logger.Error(err)
169+ client.Logger.Error(err.Error())
170 http.Error(w, err.Error(), http.StatusInternalServerError)
171 }
172 }
173@@ -342,6 +363,6 @@ func StartApiServer() {
174 router := http.HandlerFunc(handler(routes, client))
175
176 portStr := fmt.Sprintf(":%s", cfg.Port)
177- client.Logger.Infof("Starting server on port %s", cfg.Port)
178- client.Logger.Fatal(http.ListenAndServe(portStr, router))
179+ client.Logger.Info("starting server on port", "port", cfg.Port)
180+ client.Logger.Error(http.ListenAndServe(portStr, router).Error())
181 }
+0,
-7
1@@ -1,7 +0,0 @@
2-package main
3-
4-import "github.com/picosh/pico/lists"
5-
6-func main() {
7- lists.StartSshServer()
8-}
+0,
-7
1@@ -1,7 +0,0 @@
2-package main
3-
4-import "github.com/picosh/pico/lists"
5-
6-func main() {
7- lists.StartApiServer()
8-}
+5,
-15
1@@ -1,7 +1,7 @@
2 package main
3
4 import (
5- "log"
6+ "log/slog"
7 "os"
8 "strings"
9
10@@ -11,18 +11,8 @@ import (
11 "github.com/picosh/pico/shared"
12 "github.com/picosh/pico/shared/storage"
13 "github.com/picosh/pico/wish/cms/config"
14- "go.uber.org/zap"
15 )
16
17-func createLogger() *zap.SugaredLogger {
18- logger, err := zap.NewProduction()
19- if err != nil {
20- log.Fatal(err)
21- }
22-
23- return logger.Sugar()
24-}
25-
26 func bail(err error) {
27 if err != nil {
28 panic(err)
29@@ -43,7 +33,7 @@ func main() {
30 if writeEnv == "1" {
31 write = true
32 }
33- logger := createLogger()
34+ logger := slog.Default()
35
36 picoCfg := config.NewConfigCms()
37 picoCfg.Logger = logger
38@@ -104,7 +94,7 @@ func main() {
39 }
40 }
41 if !found {
42- logger.Infof("marking (bucket: %s) (%s) for removal", bucketName, bucketProject.Name())
43+ logger.Info("marking for removal", "bucket", bucketName, "project", bucketProject.Name())
44 rmProjects = append(rmProjects, RmProject{
45 name: bucketProject.Name(),
46 user: user,
47@@ -130,9 +120,9 @@ func main() {
48 bail(err)
49 }
50
51- logger.Infof("(%d) Store projects marked for deletion", len(rmProjects))
52+ logger.Info("store projects marked for deletion", "length", len(rmProjects))
53 for _, project := range rmProjects {
54- logger.Infof("(user: %s) (project: %s)", project.user.Name, project.name)
55+ logger.Info("removing project", "user", project.user.Name, "project", project.name)
56 }
57 if !write {
58 logger.Info("WARNING: changes not committed, need env var WRITE=1")
+8,
-18
1@@ -3,7 +3,7 @@ package main
2 import (
3 "context"
4 "database/sql"
5- "log"
6+ "log/slog"
7 "os"
8 "time"
9
10@@ -11,18 +11,8 @@ import (
11 "github.com/picosh/pico/db/postgres"
12 "github.com/picosh/pico/shared"
13 "github.com/picosh/pico/wish/cms/config"
14- "go.uber.org/zap"
15 )
16
17-func createLogger() *zap.SugaredLogger {
18- logger, err := zap.NewProduction()
19- if err != nil {
20- log.Fatal(err)
21- }
22-
23- return logger.Sugar()
24-}
25-
26 func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
27 var posts []*db.Post
28 rs, err := dbpool.Query(`SELECT
29@@ -67,7 +57,7 @@ func updateDates(tx *sql.Tx, postID string, date *time.Time) error {
30 }
31
32 func main() {
33- logger := createLogger()
34+ logger := slog.Default()
35
36 picoCfg := config.NewConfigCms()
37 picoCfg.Logger = logger
38@@ -79,7 +69,7 @@ func main() {
39 if err != nil {
40 panic(err)
41 }
42- logger.Infof("found (%d) posts", len(posts))
43+ logger.Info("found posts", "len", len(posts))
44
45 ctx := context.Background()
46 tx, err := picoDb.Db.BeginTx(ctx, nil)
47@@ -98,14 +88,14 @@ func main() {
48 if post.Space == "prose" {
49 parsed, err := shared.ParseText(post.Text)
50 if err != nil {
51- logger.Error(err)
52+ logger.Error(err.Error())
53 continue
54 }
55
56 if parsed.PublishAt != nil && !parsed.PublishAt.IsZero() {
57 err = updateDates(tx, post.ID, parsed.MetaData.PublishAt)
58 if err != nil {
59- logger.Error(err)
60+ logger.Error(err.Error())
61 continue
62 }
63
64@@ -116,14 +106,14 @@ func main() {
65 } else if post.Space == "lists" {
66 parsed := shared.ListParseText(post.Text)
67 if err != nil {
68- logger.Error(err)
69+ logger.Error(err.Error())
70 continue
71 }
72
73 if parsed.PublishAt != nil && !parsed.PublishAt.IsZero() {
74 err = updateDates(tx, post.ID, parsed.PublishAt)
75 if err != nil {
76- logger.Error(err)
77+ logger.Error(err.Error())
78 continue
79 }
80 if !parsed.PublishAt.Equal(*post.PublishAt) {
81@@ -137,5 +127,5 @@ func main() {
82 if err != nil {
83 panic(err)
84 }
85- logger.Infof("(%d) dates fixed!", len(datesFixed))
86+ logger.Info("dates fixed!", "len", len(datesFixed))
87 }
+2,
-12
1@@ -2,23 +2,13 @@ package main
2
3 import (
4 "encoding/binary"
5- "log"
6+ "log/slog"
7 "os"
8
9 "github.com/picosh/pico/db/postgres"
10 "github.com/picosh/pico/wish/cms/config"
11- "go.uber.org/zap"
12 )
13
14-func createLogger() *zap.SugaredLogger {
15- logger, err := zap.NewProduction()
16- if err != nil {
17- log.Fatal(err)
18- }
19-
20- return logger.Sugar()
21-}
22-
23 func bail(err error) {
24 if err != nil {
25 panic(err)
26@@ -26,7 +16,7 @@ func bail(err error) {
27 }
28
29 func main() {
30- logger := createLogger()
31+ logger := slog.Default()
32
33 picoCfg := config.NewConfigCms()
34 picoCfg.Logger = logger
+4,
-14
1@@ -4,24 +4,14 @@ import (
2 "context"
3 "database/sql"
4 "fmt"
5- "log"
6+ "log/slog"
7 "os"
8
9 "github.com/picosh/pico/db"
10 "github.com/picosh/pico/db/postgres"
11 "github.com/picosh/pico/wish/cms/config"
12- "go.uber.org/zap"
13 )
14
15-func createLogger() *zap.SugaredLogger {
16- logger, err := zap.NewProduction()
17- if err != nil {
18- log.Fatal(err)
19- }
20-
21- return logger.Sugar()
22-}
23-
24 func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
25 var posts []*db.Post
26 rs, err := dbpool.Query(`SELECT
27@@ -109,7 +99,7 @@ type ConflictData struct {
28 }
29
30 func main() {
31- logger := createLogger()
32+ logger := slog.Default()
33
34 listsCfg := config.NewConfigCms()
35 listsCfg.Logger = logger
36@@ -221,7 +211,7 @@ func main() {
37 }
38 }
39
40- logger.Infof("Adding records with no conflicts (%d)", len(noconflicts))
41+ logger.Info("adding records with no conflicts", "len", len(noconflicts))
42 for _, data := range noconflicts {
43 err = insertUser(tx, data.User)
44 if err != nil {
45@@ -236,7 +226,7 @@ func main() {
46 }
47 }
48
49- logger.Infof("Adding records with conflicts (%d)", len(conflicts))
50+ logger.Info("adding records with conflicts", "len", len(conflicts))
51 for _, data := range conflicts {
52 data.User.Name = fmt.Sprintf("%stmp", data.User.Name)
53 err = insertUser(tx, data.User)
+3,
-13
1@@ -1,26 +1,16 @@
2 package main
3
4 import (
5- "log"
6+ "log/slog"
7 "os"
8
9 "github.com/picosh/pico/db/postgres"
10 "github.com/picosh/pico/shared"
11 "github.com/picosh/pico/wish/cms/config"
12- "go.uber.org/zap"
13 )
14
15-func createLogger() *zap.SugaredLogger {
16- logger, err := zap.NewProduction()
17- if err != nil {
18- log.Fatal(err)
19- }
20-
21- return logger.Sugar()
22-}
23-
24 func main() {
25- logger := createLogger()
26+ logger := slog.Default()
27 picoCfg := config.NewConfigCms()
28 picoCfg.Logger = logger
29 picoCfg.DbURL = os.Getenv("DATABASE_URL")
30@@ -49,5 +39,5 @@ func main() {
31 }
32 }
33
34- logger.Infof("empty (%d), diff (%d)", empty, diff)
35+ logger.Info("empty, diff", "empty", empty, "diff", diff)
36 }
1@@ -2,25 +2,15 @@ package main
2
3 import (
4 "database/sql"
5- "log"
6+ "log/slog"
7 "os"
8
9 "github.com/picosh/pico/db"
10 "github.com/picosh/pico/db/postgres"
11 "github.com/picosh/pico/shared"
12 "github.com/picosh/pico/wish/cms/config"
13- "go.uber.org/zap"
14 )
15
16-func createLogger() *zap.SugaredLogger {
17- logger, err := zap.NewProduction()
18- if err != nil {
19- log.Fatal(err)
20- }
21-
22- return logger.Sugar()
23-}
24-
25 func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
26 var posts []*db.Post
27 rs, err := dbpool.Query(`SELECT
28@@ -60,7 +50,7 @@ func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
29 }
30
31 func main() {
32- logger := createLogger()
33+ logger := slog.Default()
34
35 picoCfg := config.NewConfigCms()
36 picoCfg.Logger = logger
+11,
-11
1@@ -5,6 +5,7 @@ import (
2 "database/sql"
3 "errors"
4 "fmt"
5+ "log/slog"
6 "math"
7 "strings"
8 "time"
9@@ -14,7 +15,6 @@ import (
10 _ "github.com/lib/pq"
11 "github.com/picosh/pico/db"
12 "github.com/picosh/pico/shared"
13- "go.uber.org/zap"
14 )
15
16 var PAGER_SIZE = 15
17@@ -271,7 +271,7 @@ const (
18 )
19
20 type PsqlDB struct {
21- Logger *zap.SugaredLogger
22+ Logger *slog.Logger
23 Db *sql.DB
24 }
25
26@@ -347,16 +347,16 @@ func CreatePostWithTagsFromRow(r RowScanner) (*db.Post, error) {
27 return post, nil
28 }
29
30-func NewDB(databaseUrl string, logger *zap.SugaredLogger) *PsqlDB {
31+func NewDB(databaseUrl string, logger *slog.Logger) *PsqlDB {
32 var err error
33 d := &PsqlDB{
34 Logger: logger,
35 }
36- d.Logger.Infof("Connecting to postgres: %s", databaseUrl)
37+ d.Logger.Info("Connecting to postgres", "databaseUrl", databaseUrl)
38
39 db, err := sql.Open("postgres", databaseUrl)
40 if err != nil {
41- d.Logger.Fatal(err)
42+ d.Logger.Error(err.Error())
43 }
44 d.Db = db
45 return d
46@@ -507,7 +507,7 @@ func (me *PsqlDB) FindPostsBeforeDate(date *time.Time, space string) ([]*db.Post
47 }
48
49 func (me *PsqlDB) FindUserForKey(username string, key string) (*db.User, error) {
50- me.Logger.Infof("Attempting to find user with only public key (%s)", key)
51+ me.Logger.Info("attempting to find user with only public key", "key", key)
52 pk, err := me.FindPublicKeyForKey(key)
53 if err == nil {
54 user, err := me.FindUser(pk.UserID)
55@@ -519,10 +519,10 @@ func (me *PsqlDB) FindUserForKey(username string, key string) (*db.User, error)
56 }
57
58 if errors.Is(err, &db.ErrMultiplePublicKeys{}) {
59- me.Logger.Infof("Detected multiple users with same public key, using ssh username (%s) to find correct one", username)
60+ me.Logger.Info("detected multiple users with same public key", "user", username)
61 user, err := me.FindUserForNameAndKey(username, key)
62 if err != nil {
63- me.Logger.Infof("Could not find user by username (%s) and public key (%s)", username, key)
64+ me.Logger.Info("could not find user by username and public key", "user", username, "key", key)
65 // this is a little hacky but if we cannot find a user by name and public key
66 // then we return the multiple keys detected error so the user knows to specify their
67 // when logging in
68@@ -997,9 +997,9 @@ func (me *PsqlDB) insertAliasesForPost(tx *sql.Tx, aliases []string, postID stri
69 ids := make([]string, 0)
70 for _, alias := range aliases {
71 if slices.Contains(denyList, alias) {
72- me.Logger.Infof(
73- "(%s) is in the deny list for aliases because it conflicts with a static route, skipping",
74- alias,
75+ me.Logger.Info(
76+ "name is in the deny list for aliases because it conflicts with a static route, skipping",
77+ "alias", alias,
78 )
79 continue
80 }
+8,
-6
1@@ -53,7 +53,7 @@ func StartApiServer() {
2 cache := gocache.New(2*time.Minute, 5*time.Minute)
3
4 if err != nil {
5- logger.Fatal(err)
6+ logger.Error(err.Error())
7 }
8
9 // cron daily digest
10@@ -72,10 +72,12 @@ func StartApiServer() {
11 router := http.HandlerFunc(handler)
12
13 portStr := fmt.Sprintf(":%s", cfg.Port)
14- logger.Infof("Starting server on port %s", cfg.Port)
15- logger.Infof("Subdomains enabled: %t", cfg.SubdomainsEnabled)
16- logger.Infof("Domain: %s", cfg.Domain)
17- logger.Infof("Email: %s", cfg.Email)
18+ logger.Info(
19+ "Starting server on port",
20+ "port", cfg.Port,
21+ "domain", cfg.Domain,
22+ "email", cfg.Email,
23+ )
24
25- logger.Fatal(http.ListenAndServe(portStr, router))
26+ logger.Error(http.ListenAndServe(portStr, router).Error())
27 }
+18,
-18
1@@ -133,14 +133,14 @@ func (f *Fetcher) Validate(lastDigest *time.Time, parsed *shared.ListParsedText)
2 }
3
4 func (f *Fetcher) RunPost(user *db.User, post *db.Post) error {
5- f.cfg.Logger.Infof("(%s) running feed post (%s)", user.Name, post.Filename)
6+ f.cfg.Logger.Info("running feed post", "user", user.Name, "filename", post.Filename)
7
8 parsed := shared.ListParseText(post.Text)
9
10- f.cfg.Logger.Infof("(%s) Last digest at (%s)", user.Name, post.Data.LastDigest)
11+ f.cfg.Logger.Info("last digest at", "user", user.Name, "lastDigest", post.Data.LastDigest)
12 err := f.Validate(post.Data.LastDigest, parsed)
13 if err != nil {
14- f.cfg.Logger.Infof("(%s) %s", user.Name, err.Error())
15+ f.cfg.Logger.Info(err.Error(), "user", user.Name)
16 return nil
17 }
18
19@@ -184,13 +184,13 @@ func (f *Fetcher) RunUser(user *db.User) error {
20 }
21
22 if len(posts.Data) > 0 {
23- f.cfg.Logger.Infof("(%s) found (%d) feed posts", user.Name, len(posts.Data))
24+ f.cfg.Logger.Info("found feed posts", "user", user.Name, "len", len(posts.Data))
25 }
26
27 for _, post := range posts.Data {
28 err = f.RunPost(user, post)
29 if err != nil {
30- f.cfg.Logger.Infof("(%s) %s", user.Name, err.Error())
31+ f.cfg.Logger.Info(err.Error(), "user", user)
32 }
33 }
34
35@@ -232,7 +232,7 @@ func (f *Fetcher) ParseURL(fp *gofeed.Parser, url string) (*gofeed.Feed, error)
36 }
37
38 func (f *Fetcher) Fetch(fp *gofeed.Parser, url string, username string, feedItems []*db.FeedItem) (*Feed, error) {
39- f.cfg.Logger.Infof("(%s) %s fetching feed", username, url)
40+ f.cfg.Logger.Info("fetching feed", "user", username, "url", url)
41
42 feed, err := f.ParseURL(fp, url)
43 if err != nil {
44@@ -328,9 +328,9 @@ func (f *Fetcher) FetchAll(urls []string, inlineContent bool, postID string, use
45 feedTmpl, err := f.Fetch(fp, url, username, feedItems)
46 if err != nil {
47 if errors.Is(err, ErrNoRecentArticles) {
48- f.cfg.Logger.Info(err)
49+ f.cfg.Logger.Info(err.Error())
50 } else {
51- f.cfg.Logger.Error(err)
52+ f.cfg.Logger.Error(err.Error())
53 }
54 continue
55 }
56@@ -391,7 +391,7 @@ func (f *Fetcher) SendEmail(username, email string, subject string, msg *MsgBody
57 message := mail.NewSingleEmail(from, subject, to, msg.Text, msg.Html)
58 client := sendgrid.NewSendClient(f.cfg.SendgridKey)
59
60- f.cfg.Logger.Infof("(%s) sending email digest", username)
61+ f.cfg.Logger.Info("sending email digest", "user", username)
62 response, err := client.Send(message)
63 if err != nil {
64 return err
65@@ -400,15 +400,15 @@ func (f *Fetcher) SendEmail(username, email string, subject string, msg *MsgBody
66 // f.cfg.Logger.Infof("(%s) email digest response: %v", username, response)
67
68 if len(response.Headers["X-Message-Id"]) > 0 {
69- f.cfg.Logger.Infof(
70- "(%s) successfully sent email digest (x-message-id: %s)",
71- email,
72- response.Headers["X-Message-Id"][0],
73+ f.cfg.Logger.Info(
74+ "successfully sent email digest",
75+ "email", email,
76+ "x-message-id", response.Headers["X-Message-Id"][0],
77 )
78 } else {
79- f.cfg.Logger.Errorf(
80- "(%s) could not find x-message-id, which means sending an email failed",
81- email,
82+ f.cfg.Logger.Error(
83+ "could not find x-message-id, which means sending an email failed",
84+ "email", email,
85 )
86 }
87
88@@ -424,7 +424,7 @@ func (f *Fetcher) Run() error {
89 for _, user := range users {
90 err := f.RunUser(user)
91 if err != nil {
92- f.cfg.Logger.Error(err)
93+ f.cfg.Logger.Error(err.Error())
94 continue
95 }
96 }
97@@ -438,7 +438,7 @@ func (f *Fetcher) Loop() {
98
99 err := f.Run()
100 if err != nil {
101- f.cfg.Logger.Error(err)
102+ f.cfg.Logger.Error(err.Error())
103 }
104
105 f.cfg.Logger.Info("digest emailer finished, waiting 10 mins")
+7,
-5
1@@ -81,7 +81,8 @@ func StartSshServer() {
2 }
3
4 if err != nil {
5- logger.Fatal(err)
6+ logger.Error(err.Error())
7+ return
8 }
9
10 fileMap := map[string]filehandlers.ReadWriteHandler{
11@@ -100,15 +101,16 @@ func StartSshServer() {
12 ),
13 )
14 if err != nil {
15- logger.Fatal(err)
16+ logger.Error(err.Error())
17+ return
18 }
19
20 done := make(chan os.Signal, 1)
21 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
22- logger.Infof("Starting SSH server on %s:%s", host, port)
23+ logger.Info("Starting SSH server", "host", host, "port", port)
24 go func() {
25 if err = s.ListenAndServe(); err != nil {
26- logger.Fatal(err)
27+ logger.Error(err.Error())
28 }
29 }()
30
31@@ -117,6 +119,6 @@ func StartSshServer() {
32 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
33 defer func() { cancel() }()
34 if err := s.Shutdown(ctx); err != nil {
35- logger.Fatal(err)
36+ logger.Error(err.Error())
37 }
38 }
+15,
-12
1@@ -5,6 +5,7 @@ import (
2 "encoding/binary"
3 "fmt"
4 "io"
5+ "log/slog"
6 "os"
7 "path/filepath"
8 "strings"
9@@ -17,7 +18,6 @@ import (
10 "github.com/picosh/pico/shared/storage"
11 "github.com/picosh/pico/wish/cms/util"
12 "github.com/picosh/send/send/utils"
13- "go.uber.org/zap"
14 )
15
16 type ctxBucketKey struct{}
17@@ -81,7 +81,7 @@ func NewUploadAssetHandler(dbpool db.DB, cfg *shared.ConfigSite, storage storage
18 }
19 }
20
21-func (h *UploadAssetHandler) GetLogger() *zap.SugaredLogger {
22+func (h *UploadAssetHandler) GetLogger() *slog.Logger {
23 return h.Cfg.Logger
24 }
25
26@@ -207,9 +207,9 @@ func (h *UploadAssetHandler) Validate(s ssh.Session) error {
27 return err
28 }
29 s.Context().SetValue(ctxStorageSizeKey{}, totalStorageSize)
30- h.Cfg.Logger.Infof("(%s) bucket size is current (%d bytes)", user.Name, totalStorageSize)
31+ h.Cfg.Logger.Info("bucket size is current (%d bytes)", "user", user.Name, "size", fmt.Sprintf("%d bytes", totalStorageSize))
32
33- h.Cfg.Logger.Infof("(%s) attempting to upload files to (%s)", user.Name, h.Cfg.Space)
34+ h.Cfg.Logger.Info("attempting to upload files", "user", user.Name, "space", h.Cfg.Space)
35
36 return nil
37 }
38@@ -217,7 +217,7 @@ func (h *UploadAssetHandler) Validate(s ssh.Session) error {
39 func (h *UploadAssetHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error) {
40 user, err := futil.GetUser(s)
41 if err != nil {
42- h.Cfg.Logger.Error(err)
43+ h.Cfg.Logger.Error(err.Error())
44 return "", err
45 }
46
47@@ -232,7 +232,7 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *utils.FileEntry) (strin
48
49 bucket, err := getBucket(s)
50 if err != nil {
51- h.Cfg.Logger.Error(err)
52+ h.Cfg.Logger.Error(err.Error())
53 return "", err
54 }
55
56@@ -245,18 +245,18 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *utils.FileEntry) (strin
57 if err == nil {
58 err = h.DBPool.UpdateProject(user.ID, projectName)
59 if err != nil {
60- h.Cfg.Logger.Error(err)
61+ h.Cfg.Logger.Error(err.Error())
62 return "", err
63 }
64 } else {
65 _, err = h.DBPool.InsertProject(user.ID, projectName, projectName)
66 if err != nil {
67- h.Cfg.Logger.Error(err)
68+ h.Cfg.Logger.Error(err.Error())
69 return "", err
70 }
71 project, err = h.DBPool.FindProjectByName(user.ID, projectName)
72 if err != nil {
73- h.Cfg.Logger.Error(err)
74+ h.Cfg.Logger.Error(err.Error())
75 return "", err
76 }
77 }
78@@ -285,7 +285,7 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *utils.FileEntry) (strin
79 }
80 err = h.writeAsset(data)
81 if err != nil {
82- h.Cfg.Logger.Error(err)
83+ h.Cfg.Logger.Error(err.Error())
84 return "", err
85 }
86 nextStorageSize := incrementStorageSize(s, deltaFileSize)
87@@ -380,10 +380,13 @@ func (h *UploadAssetHandler) writeAsset(data *FileData) error {
88 } else {
89 reader := bytes.NewReader(data.Text)
90
91- h.Cfg.Logger.Infof(
92- "(%s) uploading to (bucket: %s) (%s)",
93+ h.Cfg.Logger.Info(
94+ "uploading file to bucket",
95+ "user",
96 data.User.Name,
97+ "bucket",
98 data.Bucket.Name,
99+ "filename",
100 assetFilename,
101 )
102
+10,
-10
1@@ -49,14 +49,14 @@ func NewUploadImgHandler(dbpool db.DB, cfg *shared.ConfigSite, storage storage.O
2 func (h *UploadImgHandler) removePost(data *PostMetaData) error {
3 // skip empty files from being added to db
4 if data.Post == nil {
5- h.Cfg.Logger.Infof("(%s) is empty, skipping record", data.Filename)
6+ h.Cfg.Logger.Info("file is empty, skipping record", "filename", data.Filename)
7 return nil
8 }
9
10- h.Cfg.Logger.Infof("(%s) is empty, removing record (%s)", data.Filename, data.Cur.ID)
11+ h.Cfg.Logger.Info("file is empty, removing record", "filename", data.Filename, "recordId", data.Cur.ID)
12 err := h.DBPool.RemovePosts([]string{data.Cur.ID})
13 if err != nil {
14- h.Cfg.Logger.Errorf("error for %s: %v", data.Filename, err)
15+ h.Cfg.Logger.Error(err.Error(), "filename", data.Filename)
16 return fmt.Errorf("error for %s: %v", data.Filename, err)
17 }
18
19@@ -105,7 +105,7 @@ func (h *UploadImgHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileI
20 func (h *UploadImgHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error) {
21 user, err := util.GetUser(s)
22 if err != nil {
23- h.Cfg.Logger.Error(err)
24+ h.Cfg.Logger.Error(err.Error())
25 return "", err
26 }
27
28@@ -125,13 +125,13 @@ func (h *UploadImgHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
29 noExifBytes, err := exifremove.Remove(text)
30 if err == nil {
31 if len(noExifBytes) == 0 {
32- h.Cfg.Logger.Infof("(%s) silently failed to strip exif data", filename)
33+ h.Cfg.Logger.Info("file silently failed to strip exif data", "filename", filename)
34 } else {
35 text = noExifBytes
36- h.Cfg.Logger.Infof("(%s) stripped exif data", filename)
37+ h.Cfg.Logger.Info("stripped exif data", "filename", filename)
38 }
39 } else {
40- h.Cfg.Logger.Error(err)
41+ h.Cfg.Logger.Error(err.Error())
42 }
43 }
44
45@@ -156,7 +156,7 @@ func (h *UploadImgHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
46 Space,
47 )
48 if err != nil {
49- h.Cfg.Logger.Infof("(%s) unable to find image (%s), continuing", nextPost.Filename, err)
50+ h.Cfg.Logger.Info("unable to find image, continuing", "filename", nextPost.Filename, "err", err.Error())
51 }
52
53 featureFlag, err := util.GetFeatureFlag(s)
54@@ -178,13 +178,13 @@ func (h *UploadImgHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
55
56 err = h.writeImg(s, &metadata)
57 if err != nil {
58- h.Cfg.Logger.Error(err)
59+ h.Cfg.Logger.Error(err.Error())
60 return "", err
61 }
62
63 totalFileSize, err := h.DBPool.FindTotalSizeForUser(user.ID)
64 if err != nil {
65- h.Cfg.Logger.Error(err)
66+ h.Cfg.Logger.Error(err.Error())
67 return "", err
68 }
69
+18,
-14
1@@ -87,11 +87,15 @@ func (h *UploadImgHandler) writeImg(s ssh.Session, data *PostMetaData) error {
2
3 err = h.metaImg(data)
4 if err != nil {
5- h.Cfg.Logger.Info(err)
6+ h.Cfg.Logger.Info(err.Error())
7 return err
8 }
9
10 modTime := time.Unix(data.Mtime, 0)
11+ logger := h.Cfg.Logger.With(
12+ "user", data.Username,
13+ "filename", data.Filename,
14+ )
15
16 if len(data.OrigText) == 0 {
17 err = h.removePost(data)
18@@ -108,7 +112,7 @@ func (h *UploadImgHandler) writeImg(s ssh.Session, data *PostMetaData) error {
19 return err
20 }
21 } else if data.Cur == nil {
22- h.Cfg.Logger.Infof("(%s) not found, adding record", data.Filename)
23+ logger.Info("file not found, adding record")
24 insertPost := db.Post{
25 UserID: user.ID,
26 Space: Space,
27@@ -128,28 +132,28 @@ func (h *UploadImgHandler) writeImg(s ssh.Session, data *PostMetaData) error {
28 }
29 _, err := h.DBPool.InsertPost(&insertPost)
30 if err != nil {
31- h.Cfg.Logger.Errorf("error for %s: %v", data.Filename, err)
32+ logger.Error(err.Error())
33 return fmt.Errorf("error for %s: %v", data.Filename, err)
34 }
35
36 if len(data.Tags) > 0 {
37- h.Cfg.Logger.Infof(
38- "Found (%s) post tags, replacing with old tags",
39- strings.Join(data.Tags, ","),
40+ logger.Info(
41+ "found post tags, replacing with old tags",
42+ "tags", strings.Join(data.Tags, ","),
43 )
44 err = h.DBPool.ReplaceTagsForPost(data.Tags, data.Post.ID)
45 if err != nil {
46- h.Cfg.Logger.Errorf("error for %s: %v", data.Filename, err)
47+ logger.Error(err.Error())
48 return fmt.Errorf("error for %s: %v", data.Filename, err)
49 }
50 }
51 } else {
52 if data.Shasum == data.Cur.Shasum && modTime.Equal(*data.Cur.UpdatedAt) {
53- h.Cfg.Logger.Infof("(%s) found, but image is identical, skipping", data.Filename)
54+ logger.Info("image found, but image is identical, skipping")
55 return nil
56 }
57
58- h.Cfg.Logger.Infof("(%s) found, updating record", data.Filename)
59+ logger.Info("file found, updating record")
60
61 updatePost := db.Post{
62 ID: data.Cur.ID,
63@@ -167,17 +171,17 @@ func (h *UploadImgHandler) writeImg(s ssh.Session, data *PostMetaData) error {
64 }
65 _, err = h.DBPool.UpdatePost(&updatePost)
66 if err != nil {
67- h.Cfg.Logger.Errorf("error for %s: %v", data.Filename, err)
68+ logger.Error(err.Error())
69 return fmt.Errorf("error for %s: %v", data.Filename, err)
70 }
71
72- h.Cfg.Logger.Infof(
73- "Found (%s) post tags, replacing with old tags",
74- strings.Join(data.Tags, ","),
75+ logger.Info(
76+ "found post tags, replacing with old tags",
77+ "tags", strings.Join(data.Tags, ","),
78 )
79 err = h.DBPool.ReplaceTagsForPost(data.Tags, data.Cur.ID)
80 if err != nil {
81- h.Cfg.Logger.Errorf("error for %s: %v", data.Filename, err)
82+ logger.Error(err.Error())
83 return fmt.Errorf("error for %s: %v", data.Filename, err)
84 }
85 }
+32,
-28
1@@ -78,12 +78,16 @@ func (h *ScpUploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
2 logger := h.Cfg.Logger
3 user, err := util.GetUser(s)
4 if err != nil {
5- logger.Error(err)
6+ logger.Error(err.Error())
7 return "", err
8 }
9
10 userID := user.ID
11 filename := filepath.Base(entry.Filepath)
12+ logger = logger.With(
13+ "user", user.Name,
14+ "filename", filename,
15+ )
16
17 var origText []byte
18 if b, err := io.ReadAll(entry.Reader); err == nil {
19@@ -120,14 +124,13 @@ func (h *ScpUploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
20
21 valid, err := h.Hooks.FileValidate(s, &metadata)
22 if !valid {
23- logger.Error(err)
24+ logger.Error(err.Error())
25 return "", err
26 }
27
28 post, err := h.DBPool.FindPostWithFilename(metadata.Filename, metadata.User.ID, h.Cfg.Space)
29 if err != nil {
30- logger.Infof("unable to load post (%s), continuing", filename)
31- logger.Info(err)
32+ logger.Info("unable to load post, continuing", "err", err.Error())
33 }
34
35 if post != nil {
36@@ -137,7 +140,7 @@ func (h *ScpUploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
37
38 err = h.Hooks.FileMeta(s, &metadata)
39 if err != nil {
40- logger.Error(err)
41+ logger.Error(err.Error())
42 return "", err
43 }
44
45@@ -147,18 +150,18 @@ func (h *ScpUploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
46 if len(origText) == 0 {
47 // skip empty files from being added to db
48 if post == nil {
49- logger.Infof("(%s) is empty, skipping record", filename)
50+ logger.Info("file is empty, skipping record")
51 return "", nil
52 }
53
54 err := h.DBPool.RemovePosts([]string{post.ID})
55- logger.Infof("(%s) is empty, removing record", filename)
56+ logger.Info("file is empty, removing record")
57 if err != nil {
58- logger.Errorf("error for %s: %v", filename, err)
59+ logger.Error(err.Error())
60 return "", fmt.Errorf("error for %s: %v", filename, err)
61 }
62 } else if post == nil {
63- logger.Infof("(%s) not found, adding record", filename)
64+ logger.Info("file not found, adding record")
65 insertPost := db.Post{
66 UserID: userID,
67 Space: h.Cfg.Space,
68@@ -179,41 +182,42 @@ func (h *ScpUploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
69 }
70 post, err = h.DBPool.InsertPost(&insertPost)
71 if err != nil {
72- logger.Errorf("error for %s: %v", filename, err)
73+ logger.Error(err.Error())
74 return "", fmt.Errorf("error for %s: %v", filename, err)
75 }
76
77 if len(metadata.Aliases) > 0 {
78- logger.Infof(
79- "Found (%s) post aliases, replacing with old aliases",
80+ logger.Info(
81+ "found post aliases, replacing with old aliases",
82+ "aliases",
83 strings.Join(metadata.Aliases, ","),
84 )
85 err = h.DBPool.ReplaceAliasesForPost(metadata.Aliases, post.ID)
86 if err != nil {
87- logger.Errorf("error for %s: %v", filename, err)
88+ logger.Error(err.Error())
89 return "", fmt.Errorf("error for %s: %v", filename, err)
90 }
91 }
92
93 if len(metadata.Tags) > 0 {
94- logger.Infof(
95- "Found (%s) post tags, replacing with old tags",
96- strings.Join(metadata.Tags, ","),
97+ logger.Info(
98+ "found post tags, replacing with old tags",
99+ "tags", strings.Join(metadata.Tags, ","),
100 )
101 err = h.DBPool.ReplaceTagsForPost(metadata.Tags, post.ID)
102 if err != nil {
103- logger.Errorf("error for %s: %v", filename, err)
104+ logger.Error(err.Error())
105 return "", fmt.Errorf("error for %s: %v", filename, err)
106 }
107 }
108 } else {
109 if metadata.Text == post.Text && modTime.Equal(*post.UpdatedAt) {
110- logger.Infof("(%s) found, but text is identical, skipping", filename)
111+ logger.Info("file found, but text is identical, skipping")
112 curl := shared.NewCreateURL(h.Cfg)
113 return h.Cfg.FullPostURL(curl, user.Name, metadata.Slug), nil
114 }
115
116- logger.Infof("(%s) found, updating record", filename)
117+ logger.Info("file found, updating record")
118
119 updatePost := db.Post{
120 ID: post.ID,
121@@ -232,27 +236,27 @@ func (h *ScpUploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
122 }
123 _, err = h.DBPool.UpdatePost(&updatePost)
124 if err != nil {
125- logger.Errorf("error for %s: %v", filename, err)
126+ logger.Error(err.Error())
127 return "", fmt.Errorf("error for %s: %v", filename, err)
128 }
129
130- logger.Infof(
131- "Found (%s) post tags, replacing with old tags",
132- strings.Join(metadata.Tags, ","),
133+ logger.Info(
134+ "found post tags, replacing with old tags",
135+ "tags", strings.Join(metadata.Tags, ","),
136 )
137 err = h.DBPool.ReplaceTagsForPost(metadata.Tags, post.ID)
138 if err != nil {
139- logger.Errorf("error for %s: %v", filename, err)
140+ logger.Error(err.Error())
141 return "", fmt.Errorf("error for %s: %v", filename, err)
142 }
143
144- logger.Infof(
145- "Found (%s) post aliases, replacing with old aliases",
146- strings.Join(metadata.Aliases, ","),
147+ logger.Info(
148+ "found post aliases, replacing with old aliases",
149+ "aliases", strings.Join(metadata.Aliases, ","),
150 )
151 err = h.DBPool.ReplaceAliasesForPost(metadata.Aliases, post.ID)
152 if err != nil {
153- logger.Errorf("error for %s: %v", filename, err)
154+ logger.Error(err.Error())
155 return "", fmt.Errorf("error for %s: %v", filename, err)
156 }
157 }
+3,
-3
1@@ -2,6 +2,7 @@ package filehandlers
2
3 import (
4 "fmt"
5+ "log/slog"
6 "os"
7 "path/filepath"
8
9@@ -10,7 +11,6 @@ import (
10 "github.com/picosh/pico/filehandlers/util"
11 "github.com/picosh/pico/shared"
12 "github.com/picosh/send/send/utils"
13- "go.uber.org/zap"
14 )
15
16 type ReadWriteHandler interface {
17@@ -125,7 +125,7 @@ func (r *FileHandlerRouter) List(s ssh.Session, fpath string, isDir bool, recurs
18 return fileList, nil
19 }
20
21-func (r *FileHandlerRouter) GetLogger() *zap.SugaredLogger {
22+func (r *FileHandlerRouter) GetLogger() *slog.Logger {
23 return r.Cfg.Logger
24 }
25
26@@ -163,6 +163,6 @@ func (r *FileHandlerRouter) Validate(s ssh.Session) error {
27 util.SetUser(s, user)
28 util.SetFeatureFlag(s, ff)
29
30- r.Cfg.Logger.Infof("(%s) attempting to upload files to (%s)", user.Name, r.Cfg.Space)
31+ r.Cfg.Logger.Info("attempting to upload files", "user", user.Name, "space", r.Cfg.Space)
32 return nil
33 }
M
go.mod
+1,
-3
1@@ -21,13 +21,12 @@ require (
2 github.com/muesli/reflow v0.3.0
3 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577
4 github.com/patrickmn/go-cache v2.1.0+incompatible
5- github.com/picosh/send v0.0.0-20231126163457-97725d2b2be1
6+ github.com/picosh/send v0.0.0-20240217010313-c282075fbdf8
7 github.com/sendgrid/sendgrid-go v3.13.0+incompatible
8 github.com/yuin/goldmark v1.6.0
9 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
10 github.com/yuin/goldmark-meta v1.1.0
11 go.abhg.dev/goldmark/anchor v0.1.1
12- go.uber.org/zap v1.26.0
13 golang.org/x/crypto v0.17.0
14 gopkg.in/yaml.v2 v2.4.0
15 )
16@@ -106,7 +105,6 @@ require (
17 github.com/tklauser/go-sysconf v0.3.12 // indirect
18 github.com/tklauser/numcpus v0.6.1 // indirect
19 github.com/yusufpapurcu/wmi v1.2.3 // indirect
20- go.uber.org/multierr v1.11.0 // indirect
21 golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
22 golang.org/x/net v0.18.0 // indirect
23 golang.org/x/sync v0.5.0 // indirect
M
go.sum
+2,
-8
1@@ -182,8 +182,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
2 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
3 github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
4 github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
5-github.com/picosh/send v0.0.0-20231126163457-97725d2b2be1 h1:CsRnWhDufv7yiLgbClZGvWk/iFRB/8Uzo8jypLfY8J8=
6-github.com/picosh/send v0.0.0-20231126163457-97725d2b2be1/go.mod h1:3N0Z4Z8367ikx3v14CVU8HCwNXTwz+Brt+GzYE9i8wU=
7+github.com/picosh/send v0.0.0-20240217010313-c282075fbdf8 h1:yYsjCSE+SRMDVj7efPu4I048jevI42ZvaD0GQiRiRBI=
8+github.com/picosh/send v0.0.0-20240217010313-c282075fbdf8/go.mod h1:1JCq0NVOdTDenQ0/Kd8e4rP80lu06UHJJ+6dQxhcpew=
9 github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
10 github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
11 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
12@@ -254,12 +254,6 @@ github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFi
13 github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
14 go.abhg.dev/goldmark/anchor v0.1.1 h1:NUH3hAzhfeymRqZKOkSoFReZlEAmfXBZlbXEzpD2Qgc=
15 go.abhg.dev/goldmark/anchor v0.1.1/go.mod h1:zYKiaHXTdugwVJRZqInVdmNGQRM3ZRJ6AGBC7xP7its=
16-go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
17-go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
18-go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
19-go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
20-go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
21-go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
22 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
23 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
24 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+19,
-17
1@@ -42,7 +42,7 @@ func ImgsListHandler(w http.ResponseWriter, r *http.Request) {
2
3 user, err := dbpool.FindUserForName(username)
4 if err != nil {
5- logger.Infof("blog not found: %s", username)
6+ logger.Info("blog not found", "username", username)
7 http.Error(w, "blog not found", http.StatusNotFound)
8 return
9 }
10@@ -53,7 +53,7 @@ func ImgsListHandler(w http.ResponseWriter, r *http.Request) {
11 posts = p.Data
12
13 if err != nil {
14- logger.Error(err)
15+ logger.Error(err.Error())
16 http.Error(w, "could not fetch posts for blog", http.StatusInternalServerError)
17 return
18 }
19@@ -63,7 +63,7 @@ func ImgsListHandler(w http.ResponseWriter, r *http.Request) {
20 })
21
22 if err != nil {
23- logger.Error(err)
24+ logger.Error(err.Error())
25 http.Error(w, err.Error(), http.StatusInternalServerError)
26 return
27 }
28@@ -85,7 +85,7 @@ func ImgsListHandler(w http.ResponseWriter, r *http.Request) {
29
30 err = ts.Execute(w, data)
31 if err != nil {
32- logger.Error(err)
33+ logger.Error(err.Error())
34 http.Error(w, err.Error(), http.StatusInternalServerError)
35 }
36 }
37@@ -97,14 +97,14 @@ func ImgsRssHandler(w http.ResponseWriter, r *http.Request) {
38
39 pager, err := dbpool.FindAllPosts(&db.Pager{Num: 25, Page: 0}, Space)
40 if err != nil {
41- logger.Error(err)
42+ logger.Error(err.Error())
43 http.Error(w, err.Error(), http.StatusInternalServerError)
44 return
45 }
46
47 ts, err := template.ParseFiles(cfg.StaticPath("html/rss.page.tmpl"))
48 if err != nil {
49- logger.Error(err)
50+ logger.Error(err.Error())
51 http.Error(w, err.Error(), http.StatusInternalServerError)
52 return
53 }
54@@ -155,14 +155,14 @@ func ImgsRssHandler(w http.ResponseWriter, r *http.Request) {
55
56 rss, err := feed.ToAtom()
57 if err != nil {
58- logger.Fatal(err)
59+ logger.Error(err.Error())
60 http.Error(w, "Could not generate atom rss feed", http.StatusInternalServerError)
61 }
62
63 w.Header().Add("Content-Type", "application/atom+xml")
64 _, err = w.Write([]byte(rss))
65 if err != nil {
66- logger.Error(err)
67+ logger.Error(err.Error())
68 }
69 }
70
71@@ -175,7 +175,7 @@ func ImgRequest(w http.ResponseWriter, r *http.Request) {
72
73 user, err := dbpool.FindUserForName(username)
74 if err != nil {
75- logger.Infof("rss feed not found: %s", username)
76+ logger.Info("rss feed not found", "user", username)
77 http.Error(w, "rss feed not found", http.StatusNotFound)
78 return
79 }
80@@ -193,7 +193,7 @@ func ImgRequest(w http.ResponseWriter, r *http.Request) {
81 opts, err := storage.UriToImgProcessOpts(imgOpts)
82 if err != nil {
83 errMsg := fmt.Sprintf("error processing img options: %s", err.Error())
84- logger.Infof(errMsg)
85+ logger.Info(errMsg)
86 http.Error(w, errMsg, http.StatusUnprocessableEntity)
87 return
88 }
89@@ -224,7 +224,7 @@ func ImgRequest(w http.ResponseWriter, r *http.Request) {
90 post, err := FindImgPost(r, user, slug)
91 if err != nil {
92 errMsg := fmt.Sprintf("image not found %s/%s", user.Name, slug)
93- logger.Infof(errMsg)
94+ logger.Info(errMsg)
95 http.Error(w, errMsg, http.StatusNotFound)
96 return
97 }
98@@ -313,7 +313,7 @@ func StartApiServer() {
99 cache := gocache.New(2*time.Minute, 5*time.Minute)
100
101 if err != nil {
102- logger.Fatal(err)
103+ logger.Error(err.Error())
104 }
105
106 staticRoutes := []shared.Route{}
107@@ -328,10 +328,12 @@ func StartApiServer() {
108 router := http.HandlerFunc(handler)
109
110 portStr := fmt.Sprintf(":%s", cfg.Port)
111- logger.Infof("Starting server on port %s", cfg.Port)
112- logger.Infof("Subdomains enabled: %t", cfg.SubdomainsEnabled)
113- logger.Infof("Domain: %s", cfg.Domain)
114- logger.Infof("Email: %s", cfg.Email)
115+ logger.Info(
116+ "Starting server on port",
117+ "port", cfg.Port,
118+ "domain", cfg.Domain,
119+ "email", cfg.Email,
120+ )
121
122- logger.Fatal(http.ListenAndServe(portStr, router))
123+ logger.Error(http.ListenAndServe(portStr, router).Error())
124 }
+0,
-743
1@@ -1,743 +0,0 @@
2-package lists
3-
4-import (
5- "bytes"
6- "fmt"
7- "html/template"
8- "net/http"
9- "net/url"
10- "sort"
11- "strconv"
12- "time"
13-
14- "slices"
15-
16- "github.com/gorilla/feeds"
17- gocache "github.com/patrickmn/go-cache"
18- "github.com/picosh/pico/db"
19- "github.com/picosh/pico/db/postgres"
20- "github.com/picosh/pico/shared"
21- "github.com/picosh/pico/shared/storage"
22-)
23-
24-type PostItemData struct {
25- URL template.URL
26- BlogURL template.URL
27- Username string
28- Title string
29- Description string
30- PublishAtISO string
31- PublishAt string
32- UpdatedAtISO string
33- UpdatedTimeAgo string
34- Padding string
35-}
36-
37-type BlogPageData struct {
38- Site shared.SitePageData
39- PageTitle string
40- URL template.URL
41- RSSURL template.URL
42- Username string
43- Readme *ReadmeTxt
44- Header *HeaderTxt
45- Posts []PostItemData
46- HasFilter bool
47-}
48-
49-type ReadPageData struct {
50- Site shared.SitePageData
51- NextPage string
52- PrevPage string
53- Posts []PostItemData
54- Tags []string
55- HasFilter bool
56-}
57-
58-type PostPageData struct {
59- Site shared.SitePageData
60- PageTitle string
61- URL template.URL
62- BlogURL template.URL
63- Title string
64- Description string
65- Username string
66- BlogName string
67- ListType string
68- Items []*shared.ListItem
69- PublishAtISO string
70- PublishAt string
71- Tags []string
72-}
73-
74-type TransparencyPageData struct {
75- Site shared.SitePageData
76- Analytics *db.Analytics
77-}
78-
79-type HeaderTxt struct {
80- Title string
81- Bio string
82- Nav []*shared.ListItem
83- Layout string
84- HasItems bool
85-}
86-
87-type ReadmeTxt struct {
88- HasItems bool
89- ListType string
90- Items []*shared.ListItem
91-}
92-
93-func getPostsForUser(r *http.Request, user *db.User, tag string, num int) ([]*db.Post, error) {
94- dbpool := shared.GetDB(r)
95- cfg := shared.GetCfg(r)
96- var err error
97-
98- pager := &db.Pager{Num: num, Page: 0}
99- var p *db.Paginate[*db.Post]
100- if tag == "" {
101- p, err = dbpool.FindPostsForUser(pager, user.ID, cfg.Space)
102- } else {
103- p, err = dbpool.FindUserPostsByTag(pager, tag, user.ID, cfg.Space)
104- }
105- posts := p.Data
106-
107- if err != nil {
108- return posts, err
109- }
110-
111- sort.Slice(posts, func(i, j int) bool {
112- return posts[i].UpdatedAt.After(*posts[j].UpdatedAt)
113- })
114-
115- return posts, nil
116-}
117-
118-func isRequestTrackable(r *http.Request) bool {
119- return true
120-}
121-
122-func blogHandler(w http.ResponseWriter, r *http.Request) {
123- username := shared.GetUsernameFromRequest(r)
124- dbpool := shared.GetDB(r)
125- logger := shared.GetLogger(r)
126- cfg := shared.GetCfg(r)
127-
128- user, err := dbpool.FindUserForName(username)
129- if err != nil {
130- logger.Infof("blog not found: %s", username)
131- http.Error(w, "blog not found", http.StatusNotFound)
132- return
133- }
134-
135- tag := r.URL.Query().Get("tag")
136- posts, err := getPostsForUser(r, user, tag, 1000)
137- if err != nil {
138- logger.Error(err)
139- http.Error(w, "could not fetch posts for blog", http.StatusInternalServerError)
140- return
141- }
142-
143- curl := shared.CreateURLFromRequest(cfg, r)
144-
145- ts, err := shared.RenderTemplate(cfg, []string{
146- cfg.StaticPath("html/blog-default.partial.tmpl"),
147- cfg.StaticPath("html/blog-aside.partial.tmpl"),
148- cfg.StaticPath("html/blog.page.tmpl"),
149- cfg.StaticPath("html/list.partial.tmpl"),
150- })
151-
152- if err != nil {
153- logger.Error(err)
154- http.Error(w, err.Error(), http.StatusInternalServerError)
155- return
156- }
157-
158- headerTxt := &HeaderTxt{
159- Title: GetBlogName(username),
160- Bio: "",
161- }
162- header, err := dbpool.FindPostWithFilename("_header.txt", user.ID, cfg.Space)
163- if err == nil {
164- parsedText := shared.ListParseText(header.Text)
165- if parsedText.Title != "" {
166- headerTxt.Title = parsedText.Title
167- }
168-
169- if parsedText.Description != "" {
170- headerTxt.Bio = parsedText.Description
171- }
172-
173- if parsedText.Layout != "" {
174- headerTxt.Layout = parsedText.Layout
175- }
176-
177- headerTxt.Nav = parsedText.Items
178- if len(headerTxt.Nav) > 0 {
179- headerTxt.HasItems = true
180- }
181- }
182-
183- readmeTxt := &ReadmeTxt{}
184- readme, err := dbpool.FindPostWithFilename("_readme.txt", user.ID, cfg.Space)
185- if err == nil {
186- parsedText := shared.ListParseText(readme.Text)
187- readmeTxt.Items = parsedText.Items
188- readmeTxt.ListType = parsedText.ListType
189- if len(readmeTxt.Items) > 0 {
190- readmeTxt.HasItems = true
191- }
192- }
193-
194- postCollection := make([]PostItemData, 0, len(posts))
195- for _, post := range posts {
196- p := PostItemData{
197- URL: template.URL(cfg.FullPostURL(curl, post.Username, post.Slug)),
198- BlogURL: template.URL(cfg.FullBlogURL(curl, post.Username)),
199- Title: shared.FilenameToTitle(post.Filename, post.Title),
200- PublishAt: post.PublishAt.Format("02 Jan, 2006"),
201- PublishAtISO: post.PublishAt.Format(time.RFC3339),
202- UpdatedTimeAgo: shared.TimeAgo(post.UpdatedAt),
203- UpdatedAtISO: post.UpdatedAt.Format(time.RFC3339),
204- }
205- postCollection = append(postCollection, p)
206- }
207-
208- data := BlogPageData{
209- Site: *cfg.GetSiteData(),
210- PageTitle: headerTxt.Title,
211- URL: template.URL(cfg.FullBlogURL(curl, username)),
212- RSSURL: template.URL(cfg.RssBlogURL(curl, username, tag)),
213- Readme: readmeTxt,
214- Header: headerTxt,
215- Username: username,
216- Posts: postCollection,
217- HasFilter: tag != "",
218- }
219-
220- err = ts.Execute(w, data)
221- if err != nil {
222- logger.Error(err)
223- http.Error(w, err.Error(), http.StatusInternalServerError)
224- }
225-}
226-
227-func GetPostTitle(post *db.Post) string {
228- if post.Description == "" {
229- return post.Title
230- }
231-
232- return fmt.Sprintf("%s: %s", post.Title, post.Description)
233-}
234-
235-func GetBlogName(username string) string {
236- return fmt.Sprintf("%s's lists", username)
237-}
238-
239-func postRawHandler(w http.ResponseWriter, r *http.Request) {
240- username := shared.GetUsernameFromRequest(r)
241- subdomain := shared.GetSubdomain(r)
242- cfg := shared.GetCfg(r)
243-
244- var slug string
245- if !cfg.IsSubdomains() || subdomain == "" {
246- slug, _ = url.PathUnescape(shared.GetField(r, 1))
247- } else {
248- slug, _ = url.PathUnescape(shared.GetField(r, 0))
249- }
250-
251- dbpool := shared.GetDB(r)
252- logger := shared.GetLogger(r)
253-
254- user, err := dbpool.FindUserForName(username)
255- if err != nil {
256- logger.Infof("blog not found: %s", username)
257- http.Error(w, "blog not found", http.StatusNotFound)
258- return
259- }
260-
261- post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
262- if err != nil {
263- logger.Infof("post not found")
264- http.Error(w, "post not found", http.StatusNotFound)
265- return
266- }
267-
268- w.Header().Add("Content-Type", "text/plain")
269-
270- _, err = w.Write([]byte(post.Text))
271- if err != nil {
272- logger.Error(err)
273- http.Error(w, "server error", 500)
274- }
275-}
276-
277-func postHandler(w http.ResponseWriter, r *http.Request) {
278- username := shared.GetUsernameFromRequest(r)
279- subdomain := shared.GetSubdomain(r)
280- cfg := shared.GetCfg(r)
281-
282- var slug string
283- if !cfg.IsSubdomains() || subdomain == "" {
284- slug, _ = url.PathUnescape(shared.GetField(r, 1))
285- } else {
286- slug, _ = url.PathUnescape(shared.GetField(r, 0))
287- }
288-
289- dbpool := shared.GetDB(r)
290- logger := shared.GetLogger(r)
291-
292- user, err := dbpool.FindUserForName(username)
293- if err != nil {
294- logger.Infof("blog not found: %s", username)
295- http.Error(w, "blog not found", http.StatusNotFound)
296- return
297- }
298-
299- header, _ := dbpool.FindPostWithFilename("_header.txt", user.ID, cfg.Space)
300- blogName := GetBlogName(username)
301- if header != nil {
302- headerParsed := shared.ListParseText(header.Text)
303- if headerParsed.Title != "" {
304- blogName = headerParsed.Title
305- }
306- }
307-
308- var data PostPageData
309- post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
310- if err == nil {
311- parsedText := shared.ListParseText(post.Text)
312-
313- // we need the blog name from the readme unfortunately
314- readme, err := dbpool.FindPostWithFilename("_readme.txt", user.ID, cfg.Space)
315- if err == nil {
316- readmeParsed := shared.ListParseText(readme.Text)
317- if readmeParsed.Title != "" {
318- blogName = readmeParsed.Title
319- }
320- }
321-
322- // validate and fire off analytic event
323- if isRequestTrackable(r) {
324- _, err := dbpool.AddViewCount(post.ID)
325- if err != nil {
326- logger.Error(err)
327- }
328- }
329-
330- data = PostPageData{
331- Site: *cfg.GetSiteData(),
332- PageTitle: GetPostTitle(post),
333- URL: template.URL(cfg.PostURL(post.Username, post.Slug)),
334- BlogURL: template.URL(cfg.BlogURL(username)),
335- Description: post.Description,
336- ListType: parsedText.ListType,
337- Title: shared.FilenameToTitle(post.Filename, post.Title),
338- PublishAt: post.PublishAt.Format("02 Jan, 2006"),
339- PublishAtISO: post.PublishAt.Format(time.RFC3339),
340- Username: username,
341- BlogName: blogName,
342- Items: parsedText.Items,
343- Tags: parsedText.Tags,
344- }
345- } else {
346- logger.Infof("post not found %s/%s", username, slug)
347- data = PostPageData{
348- Site: *cfg.GetSiteData(),
349- PageTitle: "Post not found",
350- Description: "Post not found",
351- Title: "Post not found",
352- ListType: "none",
353- BlogURL: template.URL(cfg.BlogURL(username)),
354- PublishAt: time.Now().Format("02 Jan, 2006"),
355- PublishAtISO: time.Now().Format(time.RFC3339),
356- Username: username,
357- BlogName: blogName,
358- Items: []*shared.ListItem{
359- {
360- Value: "oops! we can't seem to find this post.",
361- IsText: true,
362- },
363- },
364- }
365- }
366-
367- ts, err := shared.RenderTemplate(cfg, []string{
368- cfg.StaticPath("html/post.page.tmpl"),
369- cfg.StaticPath("html/list.partial.tmpl"),
370- })
371-
372- if err != nil {
373- http.Error(w, err.Error(), http.StatusInternalServerError)
374- }
375-
376- err = ts.Execute(w, data)
377- if err != nil {
378- logger.Error(err)
379- http.Error(w, err.Error(), http.StatusInternalServerError)
380- }
381-}
382-
383-func readHandler(w http.ResponseWriter, r *http.Request) {
384- dbpool := shared.GetDB(r)
385- logger := shared.GetLogger(r)
386- cfg := shared.GetCfg(r)
387-
388- page, _ := strconv.Atoi(r.URL.Query().Get("page"))
389- tag := r.URL.Query().Get("tag")
390- var pager *db.Paginate[*db.Post]
391- var err error
392- if tag == "" {
393- pager, err = dbpool.FindAllUpdatedPosts(&db.Pager{Num: 30, Page: page}, cfg.Space)
394- } else {
395- pager, err = dbpool.FindPostsByTag(&db.Pager{Num: 30, Page: page}, tag, cfg.Space)
396- }
397-
398- if err != nil {
399- logger.Error(err)
400- http.Error(w, err.Error(), http.StatusInternalServerError)
401- return
402- }
403-
404- ts, err := shared.RenderTemplate(cfg, []string{
405- cfg.StaticPath("html/read.page.tmpl"),
406- })
407-
408- if err != nil {
409- http.Error(w, err.Error(), http.StatusInternalServerError)
410- }
411-
412- nextPage := ""
413- if page < pager.Total-1 {
414- nextPage = fmt.Sprintf("/read?page=%d", page+1)
415- if tag != "" {
416- nextPage = fmt.Sprintf("%s&tag=%s", nextPage, tag)
417- }
418- }
419-
420- prevPage := ""
421- if page > 0 {
422- prevPage = fmt.Sprintf("/read?page=%d", page-1)
423- if tag != "" {
424- prevPage = fmt.Sprintf("%s&tag=%s", prevPage, tag)
425- }
426- }
427-
428- tags, err := dbpool.FindPopularTags(cfg.Space)
429- if err != nil {
430- logger.Error(err)
431- }
432-
433- data := ReadPageData{
434- Site: *cfg.GetSiteData(),
435- NextPage: nextPage,
436- PrevPage: prevPage,
437- Tags: tags,
438- HasFilter: tag != "",
439- }
440- for _, post := range pager.Data {
441- item := PostItemData{
442- URL: template.URL(cfg.PostURL(post.Username, post.Slug)),
443- BlogURL: template.URL(cfg.BlogURL(post.Username)),
444- Title: shared.FilenameToTitle(post.Filename, post.Title),
445- Description: post.Description,
446- Username: post.Username,
447- PublishAt: post.PublishAt.Format("02 Jan, 2006"),
448- PublishAtISO: post.PublishAt.Format(time.RFC3339),
449- UpdatedTimeAgo: shared.TimeAgo(post.UpdatedAt),
450- UpdatedAtISO: post.UpdatedAt.Format(time.RFC3339),
451- }
452- data.Posts = append(data.Posts, item)
453- }
454-
455- err = ts.Execute(w, data)
456- if err != nil {
457- logger.Error(err)
458- http.Error(w, err.Error(), http.StatusInternalServerError)
459- }
460-}
461-
462-func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
463- username := shared.GetUsernameFromRequest(r)
464- dbpool := shared.GetDB(r)
465- logger := shared.GetLogger(r)
466- cfg := shared.GetCfg(r)
467-
468- user, err := dbpool.FindUserForName(username)
469- if err != nil {
470- logger.Infof("rss feed not found: %s", username)
471- http.Error(w, "rss feed not found", http.StatusNotFound)
472- return
473- }
474-
475- tag := r.URL.Query().Get("tag")
476- posts, err := getPostsForUser(r, user, tag, 10)
477- if err != nil {
478- logger.Error(err)
479- http.Error(w, "could not fetch posts for blog", http.StatusInternalServerError)
480- return
481- }
482-
483- ts, err := template.New("rss.page.tmpl").Funcs(shared.FuncMap).ParseFiles(
484- cfg.StaticPath("html/rss.page.tmpl"),
485- cfg.StaticPath("html/list.partial.tmpl"),
486- )
487- if err != nil {
488- logger.Error(err)
489- http.Error(w, err.Error(), http.StatusInternalServerError)
490- return
491- }
492-
493- headerTxt := &HeaderTxt{
494- Title: GetBlogName(username),
495- }
496- header, err := dbpool.FindPostWithFilename("_header.txt", user.ID, cfg.Space)
497- if err == nil {
498- parsedText := shared.ListParseText(header.Text)
499- if parsedText.Title != "" {
500- headerTxt.Title = parsedText.Title
501- }
502-
503- if parsedText.Description != "" {
504- headerTxt.Bio = parsedText.Description
505- }
506- }
507-
508- feed := &feeds.Feed{
509- Title: headerTxt.Title,
510- Link: &feeds.Link{Href: cfg.BlogURL(username)},
511- Description: headerTxt.Bio,
512- Author: &feeds.Author{Name: username},
513- Created: time.Now(),
514- }
515-
516- var feedItems []*feeds.Item
517- for _, post := range posts {
518- if slices.Contains(cfg.HiddenPosts, post.Filename) {
519- continue
520- }
521- parsed := shared.ListParseText(post.Text)
522- var tpl bytes.Buffer
523- data := &PostPageData{
524- ListType: parsed.ListType,
525- Items: parsed.Items,
526- }
527- if err := ts.Execute(&tpl, data); err != nil {
528- logger.Error(err)
529- continue
530- }
531-
532- item := &feeds.Item{
533- Id: cfg.PostURL(post.Username, post.Slug),
534- Title: shared.FilenameToTitle(post.Filename, post.Title),
535- Link: &feeds.Link{Href: cfg.PostURL(post.Username, post.Slug)},
536- Content: tpl.String(),
537- Created: *post.PublishAt,
538- Updated: *post.UpdatedAt,
539- Description: post.Description,
540- }
541-
542- if post.Description != "" {
543- item.Description = post.Description
544- }
545-
546- feedItems = append(feedItems, item)
547- }
548- feed.Items = feedItems
549-
550- rss, err := feed.ToAtom()
551- if err != nil {
552- logger.Error(err)
553- http.Error(w, "Could not generate atom rss feed", http.StatusInternalServerError)
554- }
555-
556- w.Header().Add("Content-Type", "application/atom+xml")
557- _, err = w.Write([]byte(rss))
558- if err != nil {
559- logger.Error(err)
560- }
561-}
562-
563-func rssHandler(w http.ResponseWriter, r *http.Request) {
564- dbpool := shared.GetDB(r)
565- logger := shared.GetLogger(r)
566- cfg := shared.GetCfg(r)
567-
568- pager, err := dbpool.FindAllPosts(&db.Pager{Num: 25, Page: 0}, cfg.Space)
569- if err != nil {
570- logger.Error(err)
571- http.Error(w, err.Error(), http.StatusInternalServerError)
572- return
573- }
574-
575- ts, err := template.New("rss.page.tmpl").Funcs(shared.FuncMap).ParseFiles(
576- cfg.StaticPath("html/rss.page.tmpl"),
577- cfg.StaticPath("html/list.partial.tmpl"),
578- )
579- if err != nil {
580- logger.Error(err)
581- http.Error(w, err.Error(), http.StatusInternalServerError)
582- return
583- }
584-
585- feed := &feeds.Feed{
586- Title: fmt.Sprintf("%s discovery feed", cfg.Domain),
587- Link: &feeds.Link{Href: cfg.ReadURL()},
588- Description: fmt.Sprintf("%s latest posts", cfg.Domain),
589- Author: &feeds.Author{Name: cfg.Domain},
590- Created: time.Now(),
591- }
592-
593- var feedItems []*feeds.Item
594- for _, post := range pager.Data {
595- parsed := shared.ListParseText(post.Text)
596- var tpl bytes.Buffer
597- data := &PostPageData{
598- ListType: parsed.ListType,
599- Items: parsed.Items,
600- }
601- if err := ts.Execute(&tpl, data); err != nil {
602- logger.Error(err)
603- continue
604- }
605-
606- item := &feeds.Item{
607- Id: cfg.PostURL(post.Username, post.Slug),
608- Title: post.Title,
609- Link: &feeds.Link{Href: cfg.PostURL(post.Username, post.Slug)},
610- Content: tpl.String(),
611- Created: *post.PublishAt,
612- Updated: *post.UpdatedAt,
613- Description: post.Description,
614- Author: &feeds.Author{Name: post.Username},
615- }
616-
617- if post.Description != "" {
618- item.Description = post.Description
619- }
620-
621- feedItems = append(feedItems, item)
622- }
623- feed.Items = feedItems
624-
625- rss, err := feed.ToAtom()
626- if err != nil {
627- logger.Error(err)
628- http.Error(w, "Could not generate atom rss feed", http.StatusInternalServerError)
629- }
630-
631- w.Header().Add("Content-Type", "application/atom+xml")
632- _, err = w.Write([]byte(rss))
633- if err != nil {
634- logger.Error(err)
635- }
636-}
637-
638-func createStaticRoutes() []shared.Route {
639- return []shared.Route{
640- shared.NewRoute("GET", "/main.css", shared.ServeFile("main.css", "text/css")),
641- shared.NewRoute("GET", "/lists.css", shared.ServeFile("lists.css", "text/css")),
642- shared.NewRoute("GET", "/card.png", shared.ServeFile("card.png", "image/png")),
643- shared.NewRoute("GET", "/favicon-16x16.png", shared.ServeFile("favicon-16x16.png", "image/png")),
644- shared.NewRoute("GET", "/favicon-32x32.png", shared.ServeFile("favicon-32x32.png", "image/png")),
645- shared.NewRoute("GET", "/apple-touch-icon.png", shared.ServeFile("apple-touch-icon.png", "image/png")),
646- shared.NewRoute("GET", "/favicon.ico", shared.ServeFile("favicon.ico", "image/x-icon")),
647- shared.NewRoute("GET", "/robots.txt", shared.ServeFile("robots.txt", "text/plain")),
648- }
649-}
650-
651-func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
652- routes := []shared.Route{
653- shared.NewRoute("GET", "/", readHandler),
654- shared.NewRoute("GET", "/read", readHandler),
655- shared.NewRoute("GET", "/check", shared.CheckHandler),
656- }
657-
658- routes = append(
659- routes,
660- staticRoutes...,
661- )
662-
663- routes = append(
664- routes,
665- shared.NewRoute("GET", "/rss", rssHandler),
666- shared.NewRoute("GET", "/rss.xml", rssHandler),
667- shared.NewRoute("GET", "/atom.xml", rssHandler),
668- shared.NewRoute("GET", "/feed.xml", rssHandler),
669-
670- shared.NewRoute("GET", "/([^/]+)", blogHandler),
671- shared.NewRoute("GET", "/([^/]+)/rss", rssBlogHandler),
672- shared.NewRoute("GET", "/([^/]+)/rss.xml", rssBlogHandler),
673- shared.NewRoute("GET", "/([^/]+)/atom.xml", rssBlogHandler),
674- shared.NewRoute("GET", "/([^/]+)/feed.xml", rssBlogHandler),
675- shared.NewRoute("GET", "/([^/]+)/([^/]+)", postHandler),
676- shared.NewRoute("GET", "/raw/([^/]+)/([^/]+)", postRawHandler),
677- )
678-
679- return routes
680-}
681-
682-func createSubdomainRoutes(staticRoutes []shared.Route) []shared.Route {
683- routes := []shared.Route{
684- shared.NewRoute("GET", "/", blogHandler),
685- shared.NewRoute("GET", "/rss", rssBlogHandler),
686- shared.NewRoute("GET", "/rss.xml", rssBlogHandler),
687- shared.NewRoute("GET", "/atom.xml", rssBlogHandler),
688- shared.NewRoute("GET", "/feed.xml", rssBlogHandler),
689- }
690-
691- routes = append(
692- routes,
693- staticRoutes...,
694- )
695-
696- routes = append(
697- routes,
698- shared.NewRoute("GET", "/([^/]+)", postHandler),
699- shared.NewRoute("GET", "/raw/([^/]+)", postRawHandler),
700- )
701-
702- return routes
703-}
704-
705-func StartApiServer() {
706- cfg := NewConfigSite()
707- db := postgres.NewDB(cfg.DbURL, cfg.Logger)
708- defer db.Close()
709- logger := cfg.Logger
710-
711- var st storage.ObjectStorage
712- var err error
713- if cfg.MinioURL == "" {
714- st, err = storage.NewStorageFS(cfg.StorageDir)
715- } else {
716- st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
717- }
718-
719- cache := gocache.New(2*time.Minute, 5*time.Minute)
720-
721- if err != nil {
722- logger.Fatal(err)
723- }
724-
725- staticRoutes := createStaticRoutes()
726-
727- if cfg.Debug {
728- staticRoutes = shared.CreatePProfRoutes(staticRoutes)
729- }
730-
731- mainRoutes := createMainRoutes(staticRoutes)
732- subdomainRoutes := createSubdomainRoutes(staticRoutes)
733-
734- handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger, cache)
735- router := http.HandlerFunc(handler)
736-
737- portStr := fmt.Sprintf(":%s", cfg.Port)
738- logger.Infof("Starting server on port %s", cfg.Port)
739- logger.Infof("Subdomains enabled: %t", cfg.SubdomainsEnabled)
740- logger.Infof("Domain: %s", cfg.Domain)
741- logger.Infof("Email: %s", cfg.Email)
742-
743- logger.Fatal(http.ListenAndServe(portStr, router))
744-}
+0,
-51
1@@ -1,51 +0,0 @@
2-package lists
3-
4-import (
5- "github.com/picosh/pico/shared"
6- "github.com/picosh/pico/wish/cms/config"
7-)
8-
9-func NewConfigSite() *shared.ConfigSite {
10- debug := shared.GetEnv("LISTS_DEBUG", "0")
11- domain := shared.GetEnv("LISTS_DOMAIN", "lists.sh")
12- email := shared.GetEnv("LISTS_EMAIL", "support@lists.sh")
13- subdomains := shared.GetEnv("LISTS_SUBDOMAINS", "0")
14- customdomains := shared.GetEnv("LISTS_CUSTOMDOMAINS", "0")
15- port := shared.GetEnv("LISTS_WEB_PORT", "3000")
16- protocol := shared.GetEnv("LISTS_PROTOCOL", "https")
17- allowRegister := shared.GetEnv("LISTS_ALLOW_REGISTER", "1")
18- storageDir := shared.GetEnv("IMGS_STORAGE_DIR", ".storage")
19- minioURL := shared.GetEnv("MINIO_URL", "")
20- minioUser := shared.GetEnv("MINIO_ROOT_USER", "")
21- minioPass := shared.GetEnv("MINIO_ROOT_PASSWORD", "")
22- dbURL := shared.GetEnv("DATABASE_URL", "")
23- useImgProxy := shared.GetEnv("USE_IMGPROXY", "1")
24-
25- intro := "To get started, enter a username.\n"
26- intro += "To learn next steps go to our docs at https://pico.sh/lists\n"
27-
28- return &shared.ConfigSite{
29- Debug: debug == "1",
30- SubdomainsEnabled: subdomains == "1",
31- CustomdomainsEnabled: customdomains == "1",
32- UseImgProxy: useImgProxy == "1",
33- ConfigCms: config.ConfigCms{
34- Domain: domain,
35- Email: email,
36- Port: port,
37- Protocol: protocol,
38- DbURL: dbURL,
39- StorageDir: storageDir,
40- MinioURL: minioURL,
41- MinioUser: minioUser,
42- MinioPass: minioPass,
43- Description: "A microblog for your lists",
44- IntroText: intro,
45- Space: "lists",
46- AllowedExt: []string{".txt"},
47- HiddenPosts: []string{"_header.txt", "_readme.txt"},
48- Logger: shared.CreateLogger(debug == "1"),
49- AllowRegister: allowRegister == "1",
50- },
51- }
52-}
+0,
-25
1@@ -1,25 +0,0 @@
2-{{define "base"}}
3-<!doctype html>
4-<html lang="en">
5- <head>
6- <meta charset='utf-8'>
7- <meta name="viewport" content="width=device-width, initial-scale=1" />
8- <title>{{template "title" .}}</title>
9-
10- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
11-
12- <meta name="keywords" content="blog, blogging, write, writing, lists" />
13- {{template "meta" .}}
14-
15- <link rel="stylesheet" href="https://pico.sh/smol.css" />
16- <link rel="stylesheet" href="/main.css" />
17- </head>
18- <body {{template "attrs" .}}>
19- <div class="box mb-2 text-center">
20- This service will shut down on <strong>2024-07-20</strong>.
21- <a href="https://blog.pico.sh/lists-shutdown-notice">Read our post about it.</a>
22- </div>
23- {{template "body" .}}
24- </body>
25-</html>
26-{{end}}
+0,
-67
1@@ -1,67 +0,0 @@
2-{{define "blog-aside"}}
3-<main class="flex">
4- <section class="flex-1 mr">
5- <div>
6- <h1 class="text-2xl font-bold">{{.Header.Title}}</h1>
7- {{if .Header.Bio}}<span>{{.Header.Bio}}</span>{{end}}
8- </div>
9-
10- <div id="readme">
11- {{if .Readme.HasItems}}
12- <section>
13- <article>
14- {{template "list" .Readme}}
15- </article>
16- <hr />
17- </section>
18- {{end}}
19-
20- <ul>
21- {{range .Header.Nav}}
22- {{if .IsURL}}
23- <li><a href="{{.URL}}" class="text-lg">{{.Value}}</a></li>
24- {{end}}
25- {{end}}
26- <li><a href="{{.RSSURL}}" class="text-lg">rss</a></li>
27- </ul>
28- <hr />
29- </div>
30-
31- {{if .HasFilter}}
32- <a href={{.URL}}>clear filters</a>
33- {{end}}
34-
35- <div class="posts">
36- {{range .Posts}}
37- <article class="my-2">
38- <div class="flex items-center">
39- <time datetime="{{.PublishAtISO}}" class="text-sm post-date">{{.PublishAt}}</time>
40- <span class="text-md flex-1"><a href="{{.URL}}">{{.Title}}</a></span>
41- </div>
42- </article>
43- {{end}}
44- </div>
45- </section>
46-
47- <aside>
48- {{if .Readme.HasItems}}
49- <section>
50- <article>
51- {{template "list" .Readme}}
52- </article>
53- </section>
54- {{end}}
55-
56- <nav>
57- <ul>
58- {{range .Header.Nav}}
59- {{if .IsURL}}
60- <li><a href="{{.URL}}" class="text-md">{{.Value}}</a></li>
61- {{end}}
62- {{end}}
63- <li><a href="{{.RSSURL}}" class="text-md">rss</a></li>
64- </ul>
65- </nav>
66- </aside>
67-</main>
68-{{end}}
+0,
-39
1@@ -1,39 +0,0 @@
2-{{define "blog-default"}}
3-<header class="text-center">
4- <h1 class="text-2xl font-bold">{{.Header.Title}}</h1>
5- {{if .Header.Bio}}<p class="text-lg">{{.Header.Bio}}</p>{{end}}
6- <nav>
7- {{range .Header.Nav}}
8- {{if .IsURL}}
9- <a href="{{.URL}}" class="text-md">{{.Value}}</a> |
10- {{end}}
11- {{end}}
12- <a href="{{.RSSURL}}" class="text-md">rss</a>
13- </nav>
14- <hr />
15-</header>
16-<main>
17- {{if .Readme.HasItems}}
18- <section>
19- <article>
20- {{template "list" .Readme}}
21- </article>
22- <hr />
23- </section>
24- {{end}}
25-
26- <section class="posts">
27- {{if .HasFilter}}
28- <a href="{{.URL}}">clear filters</a>
29- {{end}}
30- {{range .Posts}}
31- <article class="my-2">
32- <div class="flex items-center">
33- <time datetime="{{.PublishAtISO}}" class="text-sm post-date">{{.PublishAt}}</time>
34- <span class="text-md flex-1"><a href="{{.URL}}">{{.Title}}</a></span>
35- </div>
36- </article>
37- {{end}}
38- </section>
39-</main>
40-{{end}}
+0,
-39
1@@ -1,39 +0,0 @@
2-{{template "base" .}}
3-
4-{{define "title"}}{{.PageTitle}}{{end}}
5-
6-{{define "meta"}}
7-<meta name="description" content="{{if .Header.Bio}}{{.Header.Bio}}{{else}}{{.Header.Title}}{{end}}" />
8-
9-<meta property="og:type" content="website">
10-<meta property="og:site_name" content="{{.Site.Domain}}">
11-<meta property="og:url" content="{{.URL}}">
12-<meta property="og:title" content="{{.Header.Title}}">
13-{{if .Header.Bio}}<meta property="og:description" content="{{.Header.Bio}}">{{end}}
14-<meta property="og:image:width" content="300" />
15-<meta property="og:image:height" content="300" />
16-<meta itemprop="image" content="https://{{.Site.Domain}}/card.png" />
17-<meta property="og:image" content="https://{{.Site.Domain}}/card.png" />
18-
19-<meta property="twitter:card" content="summary">
20-<meta property="twitter:url" content="{{.URL}}">
21-<meta property="twitter:title" content="{{.Header.Title}}">
22-{{if .Header.Bio}}<meta property="twitter:description" content="{{.Header.Bio}}">{{end}}
23-<meta name="twitter:image" content="https://{{.Site.Domain}}/card.png" />
24-<meta name="twitter:image:src" content="https://{{.Site.Domain}}/card.png" />
25-
26-<link rel="alternate" href="{{.RSSURL}}" type="application/rss+xml" title="RSS feed for {{.Header.Title}}" />
27-{{end}}
28-
29-{{define "attrs"}}id="blog" class="layout-{{.Header.Layout}}"{{end}}
30-
31-{{define "body"}}
32-
33-{{if eq .Header.Layout "aside"}}
34- {{template "blog-aside" .}}
35-{{else}}
36- {{template "blog-default" .}}
37-{{end}}
38-
39-{{template "footer" .}}
40-{{end}}
1@@ -1,6 +0,0 @@
2-{{define "footer"}}
3-<footer>
4- <hr />
5- published with <a href={{.Site.HomeURL}}>{{.Site.Domain}}</a>
6-</footer>
7-{{end}}
+0,
-51
1@@ -1,51 +0,0 @@
2-{{define "list"}}
3-{{$indent := 0}}
4-{{$mod := 0}}
5-<ul style="list-style-type: {{.ListType}};">
6- {{range .Items}}
7- {{if lt $indent .Indent}}
8- <ul style="list-style-type: {{$.ListType}};">
9- {{else if gt $indent .Indent}}
10-
11- {{$mod = minus $indent .Indent}}
12- {{range $y := intRange 1 $mod}}
13- </li></ul>
14- {{end}}
15-
16- {{else}}
17- </li>
18- {{end}}
19- {{$indent = .Indent}}
20-
21- {{if .IsText}}
22- {{if .Value}}
23- <li>{{.Value}}
24- {{end}}
25- {{end}}
26-
27- {{if .IsURL}}
28- <li><a href="{{.URL}}">{{.Value}}</a>
29- {{end}}
30-
31- {{if .IsImg}}
32- <li><img src="{{.URL}}" alt="{{.Value}}" />
33- {{end}}
34-
35- {{if .IsBlock}}
36- <li><blockquote>{{.Value}}</blockquote>
37- {{end}}
38-
39- {{if .IsHeaderOne}}
40- </ul><h2 class="text-xl font-bold">{{.Value}}</h2><ul style="list-style-type: {{$.ListType}};">
41- {{end}}
42-
43- {{if .IsHeaderTwo}}
44- </ul><h3 class="text-lg font-bold">{{.Value}}</h3><ul style="list-style-type: {{$.ListType}};">
45- {{end}}
46-
47- {{if .IsPre}}
48- <li><pre>{{.Value}}</pre>
49- {{end}}
50- {{end}}
51-</ul>
52-{{end}}
1@@ -1,9 +0,0 @@
2-{{define "marketing-footer"}}
3-<footer>
4- <hr />
5- <p class="font-italic">Built and maintained by <a href="https://pico.sh">pico.sh</a>.</p>
6- <div>
7- <a href="/rss">rss</a>
8- </div>
9-</footer>
10-{{end}}
+0,
-48
1@@ -1,48 +0,0 @@
2-{{template "base" .}}
3-
4-{{define "title"}}{{.PageTitle}}{{end}}
5-
6-{{define "meta"}}
7-<meta name="description" content="{{.Description}}" />
8-
9-<meta property="og:type" content="website">
10-<meta property="og:site_name" content="{{.Site.Domain}}">
11-<meta property="og:url" content="{{.URL}}">
12-<meta property="og:title" content="{{.Title}}">
13-{{if .Description}}<meta property="og:description" content="{{.Description}}">{{end}}
14-<meta property="og:image:width" content="300" />
15-<meta property="og:image:height" content="300" />
16-<meta itemprop="image" content="https://{{.Site.Domain}}/card.png" />
17-<meta property="og:image" content="https://{{.Site.Domain}}/card.png" />
18-
19-<meta property="twitter:card" content="summary">
20-<meta property="twitter:url" content="{{.URL}}">
21-<meta property="twitter:title" content="{{.Title}}">
22-{{if .Description}}<meta property="twitter:description" content="{{.Description}}">{{end}}
23-<meta name="twitter:image" content="https://{{.Site.Domain}}/card.png" />
24-<meta name="twitter:image:src" content="https://{{.Site.Domain}}/card.png" />
25-{{end}}
26-
27-{{define "attrs"}}{{end}}
28-
29-{{define "body"}}
30-<header>
31- <h1 class="text-2xl font-bold">{{.Title}}</h1>
32- <p class="font-bold m-0">
33- <time datetime="{{.PublishAtISO}}">{{.PublishAt}}</time>
34- <span> on </span>
35- <a href="{{.BlogURL}}">{{.BlogName}}</a></p>
36- {{if .Description}}<div class="my font-italic">{{.Description}}</div>{{end}}
37- <div class="tags">
38- {{range .Tags}}
39- <a class="tag" href="{{$.BlogURL}}?tag={{.}}">#{{.}}</a>
40- {{end}}
41- </div>
42-</header>
43-<main>
44- <article>
45- {{template "list" .}}
46- </article>
47-</main>
48-{{template "footer" .}}
49-{{end}}
+0,
-51
1@@ -1,51 +0,0 @@
2-{{template "base" .}}
3-
4-{{define "title"}}discover lists -- {{.Site.Domain}}{{end}}
5-
6-{{define "meta"}}
7-<meta name="description" content="discover interesting lists" />
8-{{end}}
9-
10-{{define "attrs"}}{{end}}
11-
12-{{define "body"}}
13-<header class="text-center">
14- <h1 class="text-2xl font-bold">lists.sh</h1>
15- <p class="text-lg">A microblog for lists</p>
16- <div>
17- <a href="https://pico.sh/getting-started" class="btn-link mt inline-block">GET STARTED</a>
18- </div>
19- <hr />
20-</header>
21-<main>
22- <div class="flex items-center">
23- <div class="font-italic text-sm post-date">popular tags</div>
24- <div class="flex-1">
25- {{range .Tags}}
26- <span class="text-md"><a href="/read?tag={{.}}">#{{.}}</a></span>
27- {{end}}
28- </div>
29- </div>
30- {{if .HasFilter}}<a href="/read">clear filter</a>{{end}}
31-
32- <div class="mb">
33- {{if .PrevPage}}<a href="{{.PrevPage}}">prev</a>{{else}}<span class="text-grey">prev</span>{{end}}
34- {{if .NextPage}}<a href="{{.NextPage}}">next</a>{{else}}<span class="text-grey">next</span>{{end}}
35- </div>
36-
37- {{range .Posts}}
38- <article class="my-2">
39- <div class="flex items-center">
40- <time datetime="{{.UpdatedAtISO}}" class="text-sm post-date">{{.UpdatedTimeAgo}}</time>
41- <div class="flex-1">
42- <span class="text-md"><a href="{{.URL}}">{{.Title}}</a></span>
43- <address class="text-sm inline">
44- <a href="{{.BlogURL}}" class="link-grey">({{.Username}})</a>
45- </address>
46- </div>
47- </div>
48- </article>
49- {{end}}
50-</main>
51-{{template "marketing-footer" .}}
52-{{end}}
+0,
-1
1@@ -1 +0,0 @@
2-{{template "list" .}}
+0,
-0
+0,
-0
+0,
-0
+0,
-0
+0,
-37
1@@ -1,37 +0,0 @@
2-body {
3- max-width: 720px;
4-}
5-
6-.post-date {
7- width: 130px;
8-}
9-
10-.layout-aside {
11- max-width: 50rem;
12-}
13-
14-.layout-aside aside {
15- width: 200px;
16-}
17-
18-.layout-aside img {
19- border-radius: 5px;
20-}
21-
22-#readme {
23- display: none;
24-}
25-
26-@media only screen and (max-width: 600px) {
27- .layout-aside main {
28- flex-direction: column;
29- }
30-
31- aside {
32- display: none;
33- }
34-
35- #readme {
36- display: block;
37- }
38-}
+0,
-2
1@@ -1,2 +0,0 @@
2-User-agent: *
3-Allow: /
+0,
-61
1@@ -1,61 +0,0 @@
2-package lists
3-
4-import (
5- "fmt"
6- "strings"
7-
8- "slices"
9-
10- "github.com/charmbracelet/ssh"
11- "github.com/picosh/pico/db"
12- "github.com/picosh/pico/filehandlers"
13- "github.com/picosh/pico/shared"
14-)
15-
16-type ListHooks struct {
17- Cfg *shared.ConfigSite
18- Db db.DB
19-}
20-
21-func (p *ListHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData) (bool, error) {
22- if !shared.IsTextFile(string(data.Text)) {
23- err := fmt.Errorf(
24- "WARNING: (%s) invalid file must be plain text (utf-8), skipping",
25- data.Filename,
26- )
27- return false, err
28- }
29-
30- if !shared.IsExtAllowed(data.Filename, p.Cfg.AllowedExt) {
31- extStr := strings.Join(p.Cfg.AllowedExt, ",")
32- err := fmt.Errorf(
33- "WARNING: (%s) invalid file, format must be (%s), skipping",
34- data.Filename,
35- extStr,
36- )
37- return false, err
38- }
39-
40- return true, nil
41-}
42-
43-func (p *ListHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
44- parsedText := shared.ListParseText(string(data.Text))
45-
46- if parsedText.Title == "" {
47- data.Title = shared.ToUpper(data.Slug)
48- } else {
49- data.Title = parsedText.Title
50- }
51-
52- data.Description = parsedText.Description
53- data.Tags = parsedText.Tags
54-
55- if parsedText.PublishAt != nil && !parsedText.PublishAt.IsZero() {
56- data.PublishAt = parsedText.PublishAt
57- }
58-
59- data.Hidden = slices.Contains(p.Cfg.HiddenPosts, data.Filename)
60-
61- return nil
62-}
+0,
-122
1@@ -1,122 +0,0 @@
2-package lists
3-
4-import (
5- "context"
6- "fmt"
7- "os"
8- "os/signal"
9- "syscall"
10- "time"
11-
12- "github.com/charmbracelet/promwish"
13- "github.com/charmbracelet/ssh"
14- "github.com/charmbracelet/wish"
15- bm "github.com/charmbracelet/wish/bubbletea"
16- lm "github.com/charmbracelet/wish/logging"
17- "github.com/picosh/pico/db/postgres"
18- "github.com/picosh/pico/filehandlers"
19- "github.com/picosh/pico/shared"
20- "github.com/picosh/pico/shared/storage"
21- "github.com/picosh/pico/wish/cms"
22- "github.com/picosh/send/list"
23- "github.com/picosh/send/pipe"
24- "github.com/picosh/send/proxy"
25- "github.com/picosh/send/send/auth"
26- wishrsync "github.com/picosh/send/send/rsync"
27- "github.com/picosh/send/send/scp"
28- "github.com/picosh/send/send/sftp"
29-)
30-
31-type SSHServer struct{}
32-
33-func (me *SSHServer) authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
34- return true
35-}
36-
37-func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
38- return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
39- return []wish.Middleware{
40- pipe.Middleware(handler, ".txt"),
41- list.Middleware(handler),
42- scp.Middleware(handler),
43- wishrsync.Middleware(handler),
44- auth.Middleware(handler),
45- bm.Middleware(cms.Middleware(&handler.Cfg.ConfigCms, handler.Cfg)),
46- lm.Middleware(),
47- }
48- }
49-}
50-
51-func withProxy(handler *filehandlers.FileHandlerRouter, otherMiddleware ...wish.Middleware) ssh.Option {
52- return func(server *ssh.Server) error {
53- err := sftp.SSHOption(handler)(server)
54- if err != nil {
55- return err
56- }
57-
58- return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
59- }
60-}
61-
62-func StartSshServer() {
63- host := shared.GetEnv("LISTS_HOST", "0.0.0.0")
64- port := shared.GetEnv("LISTS_SSH_PORT", "2222")
65- promPort := shared.GetEnv("LISTS_PROM_PORT", "9222")
66- cfg := NewConfigSite()
67- logger := cfg.Logger
68- dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
69- defer dbh.Close()
70-
71- hooks := &ListHooks{
72- Cfg: cfg,
73- Db: dbh,
74- }
75-
76- var st storage.ObjectStorage
77- var err error
78- if cfg.MinioURL == "" {
79- st, err = storage.NewStorageFS(cfg.StorageDir)
80- } else {
81- st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
82- }
83-
84- if err != nil {
85- logger.Fatal(err)
86- }
87-
88- fileMap := map[string]filehandlers.ReadWriteHandler{
89- "fallback": filehandlers.NewScpPostHandler(dbh, cfg, hooks, st),
90- }
91- handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
92-
93- sshServer := &SSHServer{}
94- s, err := wish.NewServer(
95- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
96- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
97- wish.WithPublicKeyAuth(sshServer.authHandler),
98- withProxy(
99- handler,
100- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "lists-ssh"),
101- ),
102- )
103- if err != nil {
104- logger.Fatal(err)
105- }
106-
107- done := make(chan os.Signal, 1)
108- signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
109- logger.Infof("Starting SSH server on %s:%s", host, port)
110- go func() {
111- if err = s.ListenAndServe(); err != nil {
112- logger.Fatal(err)
113- }
114- }()
115-
116- <-done
117- logger.Info("Stopping SSH server")
118- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
119- defer func() { cancel() }()
120- if err := s.Shutdown(ctx); err != nil {
121- logger.Fatal(err)
122- }
123-}
+22,
-19
1@@ -83,7 +83,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
2
3 user, err := dbpool.FindUserForName(username)
4 if err != nil {
5- logger.Infof("blog not found: %s", username)
6+ logger.Info("blog not found", "user", username)
7 http.Error(w, "blog not found", http.StatusNotFound)
8 return
9 }
10@@ -91,7 +91,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
11 posts := pager.Data
12
13 if err != nil {
14- logger.Error(err)
15+ logger.Error(err.Error())
16 http.Error(w, "could not fetch posts for blog", http.StatusInternalServerError)
17 return
18 }
19@@ -101,7 +101,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
20 })
21
22 if err != nil {
23- logger.Error(err)
24+ logger.Error(err.Error())
25 http.Error(w, err.Error(), http.StatusInternalServerError)
26 return
27 }
28@@ -138,7 +138,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
29
30 err = ts.Execute(w, data)
31 if err != nil {
32- logger.Error(err)
33+ logger.Error(err.Error())
34 http.Error(w, err.Error(), http.StatusInternalServerError)
35 }
36 }
37@@ -172,7 +172,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
38
39 user, err := dbpool.FindUserForName(username)
40 if err != nil {
41- logger.Infof("blog not found: %s", username)
42+ logger.Info("blog not found", "user", username)
43 http.Error(w, "blog not found", http.StatusNotFound)
44 return
45 }
46@@ -184,7 +184,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
47 if err == nil {
48 parsedText, err := ParseText(post.Filename, post.Text)
49 if err != nil {
50- logger.Error(err)
51+ logger.Error(err.Error())
52 }
53 expiresAt := "never"
54 if post.ExpiresAt != nil {
55@@ -207,7 +207,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
56 ExpiresAt: expiresAt,
57 }
58 } else {
59- logger.Infof("post not found %s/%s", username, slug)
60+ logger.Info("post not found", "user", username, "slug", slug)
61 data = PostPageData{
62 Site: *cfg.GetSiteData(),
63 PageTitle: "Paste not found",
64@@ -233,7 +233,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
65
66 err = ts.Execute(w, data)
67 if err != nil {
68- logger.Error(err)
69+ logger.Error(err.Error())
70 http.Error(w, err.Error(), http.StatusInternalServerError)
71 }
72 }
73@@ -255,14 +255,14 @@ func postHandlerRaw(w http.ResponseWriter, r *http.Request) {
74
75 user, err := dbpool.FindUserForName(username)
76 if err != nil {
77- logger.Infof("blog not found: %s", username)
78+ logger.Info("blog not found", "user", username)
79 http.Error(w, "blog not found", http.StatusNotFound)
80 return
81 }
82
83 post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
84 if err != nil {
85- logger.Infof("post not found %s/%s", username, slug)
86+ logger.Info("post not found", "user", username, "slug", slug)
87 http.Error(w, "post not found", http.StatusNotFound)
88 return
89 }
90@@ -270,7 +270,7 @@ func postHandlerRaw(w http.ResponseWriter, r *http.Request) {
91 w.Header().Set("Content-Type", "text/plain")
92 _, err = w.Write([]byte(post.Text))
93 if err != nil {
94- logger.Error(err)
95+ logger.Error(err.Error())
96 }
97 }
98
99@@ -281,14 +281,14 @@ func serveFile(file string, contentType string) http.HandlerFunc {
100
101 contents, err := os.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
102 if err != nil {
103- logger.Error(err)
104+ logger.Error(err.Error())
105 http.Error(w, "file not found", 404)
106 }
107 w.Header().Add("Content-Type", contentType)
108
109 _, err = w.Write(contents)
110 if err != nil {
111- logger.Error(err)
112+ logger.Error(err.Error())
113 http.Error(w, "server error", 500)
114 }
115 }
116@@ -367,7 +367,8 @@ func StartApiServer() {
117 cache := gocache.New(2*time.Minute, 5*time.Minute)
118
119 if err != nil {
120- logger.Fatal(err)
121+ logger.Error(err.Error())
122+ return
123 }
124
125 go CronDeleteExpiredPosts(cfg, db)
126@@ -385,10 +386,12 @@ func StartApiServer() {
127 router := http.HandlerFunc(handler)
128
129 portStr := fmt.Sprintf(":%s", cfg.Port)
130- logger.Infof("Starting server on port %s", cfg.Port)
131- logger.Infof("Subdomains enabled: %t", cfg.SubdomainsEnabled)
132- logger.Infof("Domain: %s", cfg.Domain)
133- logger.Infof("Email: %s", cfg.Email)
134+ logger.Info(
135+ "Starting server on port",
136+ "port", cfg.Port,
137+ "domain", cfg.Domain,
138+ "email", cfg.Email,
139+ )
140
141- logger.Fatal(http.ListenAndServe(portStr, router))
142+ logger.Error(http.ListenAndServe(portStr, router).Error())
143 }
+3,
-3
1@@ -8,7 +8,7 @@ import (
2 )
3
4 func deleteExpiredPosts(cfg *shared.ConfigSite, dbpool db.DB) error {
5- cfg.Logger.Infof("checking for expired posts")
6+ cfg.Logger.Info("checking for expired posts")
7 posts, err := dbpool.FindExpiredPosts(cfg.Space)
8 if err != nil {
9 return err
10@@ -19,7 +19,7 @@ func deleteExpiredPosts(cfg *shared.ConfigSite, dbpool db.DB) error {
11 postIds = append(postIds, post.ID)
12 }
13
14- cfg.Logger.Infof("deleting (%d) expired posts", len(postIds))
15+ cfg.Logger.Info("deleting expired posts", "len", len(postIds))
16 err = dbpool.RemovePosts(postIds)
17 if err != nil {
18 return err
19@@ -32,7 +32,7 @@ func CronDeleteExpiredPosts(cfg *shared.ConfigSite, dbpool db.DB) {
20 for {
21 err := deleteExpiredPosts(cfg, dbpool)
22 if err != nil {
23- cfg.Logger.Error(err)
24+ cfg.Logger.Error(err.Error())
25 }
26 time.Sleep(1 * time.Minute)
27 }
+7,
-5
1@@ -80,7 +80,8 @@ func StartSshServer() {
2 }
3
4 if err != nil {
5- logger.Fatal(err)
6+ logger.Error(err.Error())
7+ return
8 }
9
10 fileMap := map[string]filehandlers.ReadWriteHandler{
11@@ -99,15 +100,16 @@ func StartSshServer() {
12 ),
13 )
14 if err != nil {
15- logger.Fatal(err)
16+ logger.Error(err.Error())
17+ return
18 }
19
20 done := make(chan os.Signal, 1)
21 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
22- logger.Infof("Starting SSH server on %s:%s", host, port)
23+ logger.Info("Starting SSH server", "host", host, "port", port)
24 go func() {
25 if err = s.ListenAndServe(); err != nil {
26- logger.Fatal(err)
27+ logger.Error(err.Error())
28 }
29 }()
30
31@@ -116,6 +118,6 @@ func StartSshServer() {
32 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
33 defer func() { cancel() }()
34 if err := s.Shutdown(ctx); err != nil {
35- logger.Fatal(err)
36+ logger.Error(err.Error())
37 }
38 }
+33,
-27
1@@ -4,6 +4,7 @@ import (
2 "fmt"
3 "html/template"
4 "io"
5+ "log/slog"
6 "net/http"
7 "net/url"
8 "path/filepath"
9@@ -20,7 +21,6 @@ import (
10 "github.com/picosh/pico/shared"
11 "github.com/picosh/pico/shared/storage"
12 "github.com/picosh/send/send/utils"
13- "go.uber.org/zap"
14 )
15
16 type AssetHandler struct {
17@@ -31,7 +31,7 @@ type AssetHandler struct {
18 Cfg *shared.ConfigSite
19 Dbpool db.DB
20 Storage storage.ObjectStorage
21- Logger *zap.SugaredLogger
22+ Logger *slog.Logger
23 Cache *gocache.Cache
24 UserID string
25 Bucket storage.Bucket
26@@ -48,24 +48,28 @@ func checkHandler(w http.ResponseWriter, r *http.Request) {
27 appDomain := strings.Split(cfg.ConfigCms.Domain, ":")[0]
28
29 if !strings.Contains(hostDomain, appDomain) {
30- subdomain := shared.GetCustomDomain(logger, hostDomain, cfg.Space)
31+ subdomain := shared.GetCustomDomain(hostDomain, cfg.Space)
32 props, err := getProjectFromSubdomain(subdomain)
33 if err != nil {
34- logger.Error(err)
35+ logger.Error(err.Error())
36 w.WriteHeader(http.StatusNotFound)
37 return
38 }
39
40 u, err := dbpool.FindUserForName(props.Username)
41 if err != nil {
42- logger.Error(err)
43+ logger.Error(err.Error())
44 w.WriteHeader(http.StatusNotFound)
45 return
46 }
47
48+ logger = logger.With(
49+ "user", u.Name,
50+ "project", props.ProjectName,
51+ )
52 p, err := dbpool.FindProjectByName(u.ID, props.ProjectName)
53 if err != nil {
54- logger.Error(err)
55+ logger.Error(err.Error())
56 w.WriteHeader(http.StatusNotFound)
57 return
58 }
59@@ -91,7 +95,7 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
60
61 pager, err := dbpool.FindAllProjects(&db.Pager{Num: 50, Page: 0})
62 if err != nil {
63- logger.Error(err)
64+ logger.Error(err.Error())
65 http.Error(w, err.Error(), http.StatusInternalServerError)
66 return
67 }
68@@ -128,14 +132,14 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
69
70 rss, err := feed.ToAtom()
71 if err != nil {
72- logger.Fatal(err)
73+ logger.Error(err.Error())
74 http.Error(w, "Could not generate atom rss feed", http.StatusInternalServerError)
75 }
76
77 w.Header().Add("Content-Type", "application/atom+xml")
78 _, err = w.Write([]byte(rss))
79 if err != nil {
80- logger.Error(err)
81+ logger.Error(err.Error())
82 }
83 }
84
85@@ -226,14 +230,14 @@ func (h *AssetHandler) handle(w http.ResponseWriter) {
86 buf := new(strings.Builder)
87 _, err := io.Copy(buf, redirectFp)
88 if err != nil {
89- h.Logger.Error(err)
90+ h.Logger.Error(err.Error())
91 http.Error(w, "cannot read _redirect file", http.StatusInternalServerError)
92 return
93 }
94
95 redirects, err = parseRedirectText(buf.String())
96 if err != nil {
97- h.Logger.Error(err)
98+ h.Logger.Error(err.Error())
99 }
100 }
101
102@@ -266,10 +270,10 @@ func (h *AssetHandler) handle(w http.ResponseWriter) {
103 }
104
105 if assetFilepath == "" {
106- h.Logger.Infof(
107- "asset not found in bucket: bucket:[%s], routes:[%s]",
108- h.Bucket.Name,
109- strings.Join(attempts, ", "),
110+ h.Logger.Info(
111+ "asset not found in bucket",
112+ "bucket", h.Bucket.Name,
113+ "routes", strings.Join(attempts, ", "),
114 )
115 http.Error(w, "404 not found", http.StatusNotFound)
116 return
117@@ -285,7 +289,7 @@ func (h *AssetHandler) handle(w http.ResponseWriter) {
118 _, err = io.Copy(w, contents)
119
120 if err != nil {
121- h.Logger.Error(err)
122+ h.Logger.Error(err.Error())
123 }
124 }
125
126@@ -318,14 +322,14 @@ func ServeAsset(fname string, opts *storage.ImgProcessOpts, fromImgs bool, w htt
127
128 props, err := getProjectFromSubdomain(subdomain)
129 if err != nil {
130- logger.Info(err)
131+ logger.Info(err.Error(), "subdomain", subdomain, "filename", fname)
132 http.Error(w, err.Error(), http.StatusNotFound)
133 return
134 }
135
136 user, err := dbpool.FindUserForName(props.Username)
137 if err != nil {
138- logger.Infof("user not found: %s", props.Username)
139+ logger.Info("user not found", "user", props.Username)
140 http.Error(w, "user not found", http.StatusNotFound)
141 return
142 }
143@@ -347,7 +351,7 @@ func ServeAsset(fname string, opts *storage.ImgProcessOpts, fromImgs bool, w htt
144 }
145
146 if err != nil {
147- logger.Infof("bucket not found for %s", props.Username)
148+ logger.Info("bucket not found", "user", props.Username)
149 http.Error(w, "bucket not found", http.StatusNotFound)
150 return
151 }
152@@ -377,7 +381,7 @@ func ImgAssetRequest(w http.ResponseWriter, r *http.Request) {
153 opts, err := storage.UriToImgProcessOpts(imgOpts)
154 if err != nil {
155 errMsg := fmt.Sprintf("error processing img options: %s", err.Error())
156- logger.Infof(errMsg)
157+ logger.Info(errMsg)
158 http.Error(w, errMsg, http.StatusUnprocessableEntity)
159 }
160
161@@ -410,7 +414,8 @@ func StartApiServer() {
162 cache := gocache.New(2*time.Minute, 5*time.Minute)
163
164 if err != nil {
165- logger.Fatal(err)
166+ logger.Error(err.Error())
167+ return
168 }
169
170 mainRoutes := []shared.Route{
171@@ -436,10 +441,11 @@ func StartApiServer() {
172 router := http.HandlerFunc(handler)
173
174 portStr := fmt.Sprintf(":%s", cfg.Port)
175- logger.Infof("Starting server on port %s", cfg.Port)
176- logger.Infof("Subdomains enabled: %t", cfg.SubdomainsEnabled)
177- logger.Infof("Domain: %s", cfg.Domain)
178- logger.Infof("Email: %s", cfg.Email)
179-
180- logger.Fatal(http.ListenAndServe(portStr, router))
181+ logger.Info(
182+ "Starting server on port",
183+ "port", cfg.Port,
184+ "domain", cfg.Domain,
185+ "email", cfg.Email,
186+ )
187+ logger.Error(http.ListenAndServe(portStr, router).Error())
188 }
+19,
-19
1@@ -4,13 +4,13 @@ import (
2 "errors"
3 "fmt"
4 "io"
5+ "log/slog"
6 "os"
7 "path/filepath"
8
9 "github.com/picosh/pico/db"
10 "github.com/picosh/pico/shared"
11 "github.com/picosh/pico/shared/storage"
12- "go.uber.org/zap"
13 )
14
15 func getHelpText(userName, projectName string) string {
16@@ -34,7 +34,7 @@ func getHelpText(userName, projectName string) string {
17 }
18
19 type CmdSessionLogger struct {
20- Log *zap.SugaredLogger
21+ Log *slog.Logger
22 }
23
24 func (c *CmdSessionLogger) Write(out []byte) (int, error) {
25@@ -65,7 +65,7 @@ type CmdSession interface {
26 type Cmd struct {
27 User *db.User
28 Session CmdSession
29- Log *zap.SugaredLogger
30+ Log *slog.Logger
31 Store storage.ObjectStorage
32 Dbpool db.DB
33 Write bool
34@@ -85,7 +85,7 @@ func (c *Cmd) bail(err error) {
35 if err == nil {
36 return
37 }
38- c.Log.Error(err)
39+ c.Log.Error(err.Error())
40 c.error(err)
41 }
42
43@@ -116,11 +116,11 @@ func (c *Cmd) RmProjectAssets(projectName string) error {
44
45 for _, file := range fileList {
46 intent := fmt.Sprintf("deleted (%s)", file.Name())
47- c.Log.Infof(
48- "(%s) attempting to delete (bucket: %s) (%s)",
49- c.User.Name,
50- bucket.Name,
51- file.Name(),
52+ c.Log.Info(
53+ "attempting to delete file",
54+ "user", c.User.Name,
55+ "bucket", bucket.Name,
56+ "filename", file.Name(),
57 )
58 if c.Write {
59 err = c.Store.DeleteFile(
60@@ -204,7 +204,7 @@ func (c *Cmd) ls() error {
61 }
62
63 func (c *Cmd) unlink(projectName string) error {
64- c.Log.Infof("user (%s) running `unlink` command with (%s)", c.User.Name, projectName)
65+ c.Log.Info("user running `unlink` command", "user", c.User.Name, "project", projectName)
66 project, err := c.Dbpool.FindProjectByName(c.User.ID, projectName)
67 if err != nil {
68 return errors.Join(err, fmt.Errorf("project (%s) does not exit", projectName))
69@@ -220,7 +220,7 @@ func (c *Cmd) unlink(projectName string) error {
70 }
71
72 func (c *Cmd) link(projectName, linkTo string) error {
73- c.Log.Infof("user (%s) running `link` command with (%s) (%s)", c.User.Name, projectName, linkTo)
74+ c.Log.Info("user running `link` command", "user", c.User.Name, "project", projectName, "link", linkTo)
75
76 projectDir := linkTo
77 _, err := c.Dbpool.FindProjectByName(c.User.ID, linkTo)
78@@ -233,13 +233,13 @@ func (c *Cmd) link(projectName, linkTo string) error {
79 projectID := ""
80 if err == nil {
81 projectID = project.ID
82- c.Log.Infof("user (%s) already has project (%s), updating", c.User.Name, projectName)
83+ c.Log.Info("user already has project, updating", "user", c.User.Name, "project", projectName)
84 err = c.Dbpool.LinkToProject(c.User.ID, project.ID, projectDir, c.Write)
85 if err != nil {
86 return err
87 }
88 } else {
89- c.Log.Infof("user (%s) has no project record (%s), creating", c.User.Name, projectName)
90+ c.Log.Info("user has no project record, creating", "user", c.User.Name, "project", projectName)
91 if !c.Write {
92 out := fmt.Sprintf("(%s) cannot create a new project without `--write` permission, aborting", projectName)
93 c.output(out)
94@@ -252,7 +252,7 @@ func (c *Cmd) link(projectName, linkTo string) error {
95 projectID = id
96 }
97
98- c.Log.Infof("user (%s) linking (%s) to (%s)", c.User.Name, projectName, projectDir)
99+ c.Log.Info("user linking", "user", c.User.Name, "project", projectName, "projectDir", projectDir)
100 err = c.Dbpool.LinkToProject(c.User.ID, projectID, projectDir, c.Write)
101 if err != nil {
102 return err
103@@ -297,7 +297,7 @@ func (c *Cmd) depends(projectName string) error {
104 // delete all the projects and associated assets matching prefix
105 // but keep the latest N records.
106 func (c *Cmd) prune(prefix string, keepNumLatest int) error {
107- c.Log.Infof("user (%s) running `clean` command for (%s)", c.User.Name, prefix)
108+ c.Log.Info("user running `clean` command", "user", c.User.Name, "prefix", prefix)
109 c.output(fmt.Sprintf("searching for projects that match prefix (%s) and are not linked to other projects", prefix))
110
111 if prefix == "" || prefix == "*" {
112@@ -357,7 +357,7 @@ func (c *Cmd) prune(prefix string, keepNumLatest int) error {
113 c.output(out)
114
115 if c.Write {
116- c.Log.Infof("(%s) removing", project.Name)
117+ c.Log.Info("removing project", "project", project.Name)
118 err = c.Dbpool.RemoveProject(project.ID)
119 if err != nil {
120 return err
121@@ -375,10 +375,10 @@ func (c *Cmd) prune(prefix string, keepNumLatest int) error {
122 }
123
124 func (c *Cmd) rm(projectName string) error {
125- c.Log.Infof("user (%s) running `rm` command for (%s)", c.User.Name, projectName)
126+ c.Log.Info("user running `rm` command", "user", c.User.Name, "project", projectName)
127 project, err := c.Dbpool.FindProjectByName(c.User.ID, projectName)
128 if err == nil {
129- c.Log.Infof("found project (%s) (%s), checking dependencies", projectName, project.ID)
130+ c.Log.Info("found project, checking dependencies", "project", projectName, "projectID", project.ID)
131
132 links, err := c.Dbpool.FindProjectLinks(c.User.ID, projectName)
133 if err != nil {
134@@ -393,7 +393,7 @@ func (c *Cmd) rm(projectName string) error {
135 out := fmt.Sprintf("(%s) removing", project.Name)
136 c.output(out)
137 if c.Write {
138- c.Log.Infof("(%s) removing", project.Name)
139+ c.Log.Info("removing project", "project", project.Name)
140 err = c.Dbpool.RemoveProject(project.ID)
141 if err != nil {
142 return err
+4,
-4
1@@ -93,7 +93,7 @@ func CmsMiddleware(cfg *config.ConfigCms, urls config.ConfigURL) bm.Handler {
2 }
3 key, err := util.KeyText(s)
4 if err != nil {
5- logger.Error(err)
6+ logger.Error(err.Error())
7 }
8
9 sshUser := s.User()
10@@ -108,7 +108,7 @@ func CmsMiddleware(cfg *config.ConfigCms, urls config.ConfigURL) bm.Handler {
11 }
12
13 if err != nil {
14- logger.Fatal(err)
15+ logger.Error(err.Error())
16 }
17
18 m := model{
19@@ -171,14 +171,14 @@ func (m model) findUser() (*db.User, error) {
20 var user *db.User
21
22 if m.sshUser == "new" {
23- logger.Infof("User requesting to register account")
24+ logger.Info("user requesting to register account", "user", user.Name)
25 return nil, nil
26 }
27
28 user, err := m.dbpool.FindUserForKey(m.sshUser, m.publicKey)
29
30 if err != nil {
31- logger.Error(err)
32+ logger.Error(err.Error())
33 // we only want to throw an error for specific cases
34 if errors.Is(err, &db.ErrMultiplePublicKeys{}) {
35 return nil, err
+7,
-5
1@@ -76,7 +76,8 @@ func StartSshServer() {
2 }
3
4 if err != nil {
5- logger.Fatal(err)
6+ logger.Error(err.Error())
7+ return
8 }
9
10 handler := uploadassets.NewUploadAssetHandler(
11@@ -97,15 +98,16 @@ func StartSshServer() {
12 ),
13 )
14 if err != nil {
15- logger.Fatal(err)
16+ logger.Error(err.Error())
17+ return
18 }
19
20 done := make(chan os.Signal, 1)
21 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
22- logger.Infof("Starting SSH server on %s:%s", host, port)
23+ logger.Info("Starting SSH server on", "host", host, "port", port)
24 go func() {
25 if err = s.ListenAndServe(); err != nil {
26- logger.Fatal(err)
27+ logger.Error(err.Error())
28 }
29 }()
30
31@@ -114,6 +116,6 @@ func StartSshServer() {
32 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
33 defer func() { cancel() }()
34 if err := s.Shutdown(ctx); err != nil {
35- logger.Fatal(err)
36+ logger.Error(err.Error())
37 }
38 }
+1,
-1
1@@ -83,7 +83,7 @@ func WishMiddleware(handler *uploadassets.UploadAssetHandler) wish.Middleware {
2 }
3 }
4
5- log.Infof("pgs middleware detected command: %s", args)
6+ log.Info("pgs middleware detected command", "args", args)
7 projectName := strings.TrimSpace(args[1])
8
9 if projectName == "--write" {
+44,
-42
1@@ -131,13 +131,13 @@ func blogStyleHandler(w http.ResponseWriter, r *http.Request) {
2
3 user, err := dbpool.FindUserForName(username)
4 if err != nil {
5- logger.Infof("blog not found: %s", username)
6+ logger.Info("blog not found", "user", username)
7 http.Error(w, "blog not found", http.StatusNotFound)
8 return
9 }
10 styles, err := dbpool.FindPostWithFilename("_styles.css", user.ID, cfg.Space)
11 if err != nil {
12- logger.Infof("css not found for: %s", username)
13+ logger.Info("css not found", "user", username)
14 http.Error(w, "css not found", http.StatusNotFound)
15 return
16 }
17@@ -146,7 +146,7 @@ func blogStyleHandler(w http.ResponseWriter, r *http.Request) {
18
19 _, err = w.Write([]byte(styles.Text))
20 if err != nil {
21- logger.Error(err)
22+ logger.Error(err.Error())
23 http.Error(w, "server error", 500)
24 }
25 }
26@@ -159,7 +159,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
27
28 user, err := dbpool.FindUserForName(username)
29 if err != nil {
30- logger.Infof("blog not found: %s", username)
31+ logger.Info("blog not found", "user", username)
32 http.Error(w, "blog not found", http.StatusNotFound)
33 return
34 }
35@@ -176,7 +176,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
36 posts = p.Data
37
38 if err != nil {
39- logger.Error(err)
40+ logger.Error(err.Error())
41 http.Error(w, "could not fetch posts for blog", http.StatusInternalServerError)
42 return
43 }
44@@ -190,7 +190,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
45 curl := shared.CreateURLFromRequest(cfg, r)
46
47 if err != nil {
48- logger.Error(err)
49+ logger.Error(err.Error())
50 http.Error(w, err.Error(), http.StatusInternalServerError)
51 return
52 }
53@@ -207,7 +207,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
54 if err == nil {
55 parsedText, err := shared.ParseText(readme.Text)
56 if err != nil {
57- logger.Error(err)
58+ logger.Error(err.Error())
59 }
60 headerTxt.Bio = parsedText.Description
61 headerTxt.Layout = parsedText.Layout
62@@ -277,7 +277,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
63
64 err = ts.Execute(w, data)
65 if err != nil {
66- logger.Error(err)
67+ logger.Error(err.Error())
68 http.Error(w, err.Error(), http.StatusInternalServerError)
69 }
70 }
71@@ -300,14 +300,14 @@ func postRawHandler(w http.ResponseWriter, r *http.Request) {
72
73 user, err := dbpool.FindUserForName(username)
74 if err != nil {
75- logger.Infof("blog not found: %s", username)
76+ logger.Info("blog not found", "user", username)
77 http.Error(w, "blog not found", http.StatusNotFound)
78 return
79 }
80
81 post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
82 if err != nil {
83- logger.Infof("post not found")
84+ logger.Info("post not found")
85 http.Error(w, "post not found", http.StatusNotFound)
86 return
87 }
88@@ -316,7 +316,7 @@ func postRawHandler(w http.ResponseWriter, r *http.Request) {
89
90 _, err = w.Write([]byte(post.Text))
91 if err != nil {
92- logger.Error(err)
93+ logger.Error(err.Error())
94 http.Error(w, "server error", 500)
95 }
96 }
97@@ -339,7 +339,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
98
99 user, err := dbpool.FindUserForName(username)
100 if err != nil {
101- logger.Infof("blog not found: %s", username)
102+ logger.Info("blog not found", "user", username)
103 http.Error(w, "blog not found", http.StatusNotFound)
104 return
105 }
106@@ -356,7 +356,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
107 if err == nil {
108 parsedText, err := shared.ParseText(post.Text)
109 if err != nil {
110- logger.Error(err)
111+ logger.Error(err.Error())
112 }
113
114 // we need the blog name from the readme unfortunately
115@@ -364,7 +364,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
116 if err == nil {
117 readmeParsed, err := shared.ParseText(readme.Text)
118 if err != nil {
119- logger.Error(err)
120+ logger.Error(err.Error())
121 }
122 if readmeParsed.MetaData.Title != "" {
123 blogName = readmeParsed.MetaData.Title
124@@ -394,7 +394,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
125 if err == nil {
126 footerParsed, err := shared.ParseText(footer.Text)
127 if err != nil {
128- logger.Error(err)
129+ logger.Error(err.Error())
130 }
131 footerHTML = template.HTML(footerParsed.Html)
132 }
133@@ -403,7 +403,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
134 if isRequestTrackable(r) {
135 _, err := dbpool.AddViewCount(post.ID)
136 if err != nil {
137- logger.Error(err)
138+ logger.Error(err.Error())
139 }
140 }
141
142@@ -456,7 +456,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
143 Contents: "Oops! we can't seem to find this post.",
144 Unlisted: true,
145 }
146- logger.Infof("post not found %s/%s", username, slug)
147+ logger.Info("post not found", "user", username, "slug", slug)
148 }
149
150 ts, err := shared.RenderTemplate(cfg, []string{
151@@ -469,7 +469,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
152
153 err = ts.Execute(w, data)
154 if err != nil {
155- logger.Error(err)
156+ logger.Error(err.Error())
157 http.Error(w, err.Error(), http.StatusInternalServerError)
158 }
159 }
160@@ -490,7 +490,7 @@ func readHandler(w http.ResponseWriter, r *http.Request) {
161 }
162
163 if err != nil {
164- logger.Error(err)
165+ logger.Error(err.Error())
166 http.Error(w, err.Error(), http.StatusInternalServerError)
167 return
168 }
169@@ -521,7 +521,7 @@ func readHandler(w http.ResponseWriter, r *http.Request) {
170
171 tags, err := dbpool.FindPopularTags(cfg.Space)
172 if err != nil {
173- logger.Error(err)
174+ logger.Error(err.Error())
175 }
176
177 data := ReadPageData{
178@@ -551,7 +551,7 @@ func readHandler(w http.ResponseWriter, r *http.Request) {
179
180 err = ts.Execute(w, data)
181 if err != nil {
182- logger.Error(err)
183+ logger.Error(err.Error())
184 http.Error(w, err.Error(), http.StatusInternalServerError)
185 }
186 }
187@@ -564,7 +564,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
188
189 user, err := dbpool.FindUserForName(username)
190 if err != nil {
191- logger.Infof("rss feed not found: %s", username)
192+ logger.Info("rss feed not found", "user", username)
193 http.Error(w, "rss feed not found", http.StatusNotFound)
194 return
195 }
196@@ -581,14 +581,14 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
197 posts = p.Data
198
199 if err != nil {
200- logger.Error(err)
201+ logger.Error(err.Error())
202 http.Error(w, err.Error(), http.StatusInternalServerError)
203 return
204 }
205
206 ts, err := template.ParseFiles(cfg.StaticPath("html/rss.page.tmpl"))
207 if err != nil {
208- logger.Error(err)
209+ logger.Error(err.Error())
210 http.Error(w, err.Error(), http.StatusInternalServerError)
211 return
212 }
213@@ -601,7 +601,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
214 if err == nil {
215 parsedText, err := shared.ParseText(readme.Text)
216 if err != nil {
217- logger.Error(err)
218+ logger.Error(err.Error())
219 }
220 if parsedText.Title != "" {
221 headerTxt.Title = parsedText.Title
222@@ -631,7 +631,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
223 }
224 parsed, err := shared.ParseText(post.Text)
225 if err != nil {
226- logger.Error(err)
227+ logger.Error(err.Error())
228 }
229
230 footer, err := dbpool.FindPostWithFilename("_footer.md", user.ID, cfg.Space)
231@@ -639,7 +639,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
232 if err == nil {
233 footerParsed, err := shared.ParseText(footer.Text)
234 if err != nil {
235- logger.Error(err)
236+ logger.Error(err.Error())
237 }
238 footerHTML = footerParsed.Html
239 }
240@@ -673,14 +673,14 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
241
242 rss, err := feed.ToAtom()
243 if err != nil {
244- logger.Fatal(err)
245+ logger.Error(err.Error())
246 http.Error(w, "Could not generate atom rss feed", http.StatusInternalServerError)
247 }
248
249 w.Header().Add("Content-Type", "application/atom+xml")
250 _, err = w.Write([]byte(rss))
251 if err != nil {
252- logger.Error(err)
253+ logger.Error(err.Error())
254 }
255 }
256
257@@ -691,14 +691,14 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
258
259 pager, err := dbpool.FindAllPosts(&db.Pager{Num: 25, Page: 0}, cfg.Space)
260 if err != nil {
261- logger.Error(err)
262+ logger.Error(err.Error())
263 http.Error(w, err.Error(), http.StatusInternalServerError)
264 return
265 }
266
267 ts, err := template.ParseFiles(cfg.StaticPath("html/rss.page.tmpl"))
268 if err != nil {
269- logger.Error(err)
270+ logger.Error(err.Error())
271 http.Error(w, err.Error(), http.StatusInternalServerError)
272 return
273 }
274@@ -717,7 +717,7 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
275 for _, post := range pager.Data {
276 parsed, err := shared.ParseText(post.Text)
277 if err != nil {
278- logger.Error(err)
279+ logger.Error(err.Error())
280 }
281
282 var tpl bytes.Buffer
283@@ -754,14 +754,14 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
284
285 rss, err := feed.ToAtom()
286 if err != nil {
287- logger.Fatal(err)
288+ logger.Error(err.Error())
289 http.Error(w, "Could not generate atom rss feed", http.StatusInternalServerError)
290 }
291
292 w.Header().Add("Content-Type", "application/atom+xml")
293 _, err = w.Write([]byte(rss))
294 if err != nil {
295- logger.Error(err)
296+ logger.Error(err.Error())
297 }
298 }
299
300@@ -772,14 +772,14 @@ func serveFile(file string, contentType string) http.HandlerFunc {
301
302 contents, err := os.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
303 if err != nil {
304- logger.Error(err)
305+ logger.Error(err.Error())
306 http.Error(w, "file not found", 404)
307 }
308 w.Header().Add("Content-Type", contentType)
309
310 _, err = w.Write(contents)
311 if err != nil {
312- logger.Error(err)
313+ logger.Error(err.Error())
314 http.Error(w, "server error", 500)
315 }
316 }
317@@ -882,7 +882,7 @@ func StartApiServer() {
318 cache := gocache.New(2*time.Minute, 5*time.Minute)
319
320 if err != nil {
321- logger.Fatal(err)
322+ logger.Error(err.Error())
323 }
324
325 staticRoutes := createStaticRoutes()
326@@ -898,10 +898,12 @@ func StartApiServer() {
327 router := http.HandlerFunc(handler)
328
329 portStr := fmt.Sprintf(":%s", cfg.Port)
330- logger.Infof("Starting server on port %s", cfg.Port)
331- logger.Infof("Subdomains enabled: %t", cfg.SubdomainsEnabled)
332- logger.Infof("Domain: %s", cfg.Domain)
333- logger.Infof("Email: %s", cfg.Email)
334+ logger.Info(
335+ "Starting server on port",
336+ "port", cfg.Port,
337+ "domain", cfg.Domain,
338+ "email", cfg.Email,
339+ )
340
341- logger.Fatal(http.ListenAndServe(portStr, router))
342+ logger.Error(http.ListenAndServe(portStr, router).Error())
343 }
+7,
-5
1@@ -81,7 +81,8 @@ func StartSshServer() {
2 }
3
4 if err != nil {
5- logger.Fatal(err)
6+ logger.Error(err.Error())
7+ return
8 }
9
10 fileMap := map[string]filehandlers.ReadWriteHandler{
11@@ -103,15 +104,16 @@ func StartSshServer() {
12 ),
13 )
14 if err != nil {
15- logger.Fatal(err)
16+ logger.Error(err.Error())
17+ return
18 }
19
20 done := make(chan os.Signal, 1)
21 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
22- logger.Infof("Starting SSH server on %s:%s", host, port)
23+ logger.Info("Starting SSH server", "host", host, "port", port)
24 go func() {
25 if err = s.ListenAndServe(); err != nil {
26- logger.Fatal(err)
27+ logger.Error(err.Error())
28 }
29 }()
30
31@@ -120,6 +122,6 @@ func StartSshServer() {
32 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
33 defer func() { cancel() }()
34 if err := s.Shutdown(ctx); err != nil {
35- logger.Fatal(err)
36+ logger.Error(err.Error())
37 }
38 }
1@@ -11,14 +11,13 @@ import (
2 func CheckHandler(w http.ResponseWriter, r *http.Request) {
3 dbpool := GetDB(r)
4 cfg := GetCfg(r)
5- logger := GetLogger(r)
6
7 if cfg.IsCustomdomains() {
8 hostDomain := r.URL.Query().Get("domain")
9 appDomain := strings.Split(cfg.ConfigCms.Domain, ":")[0]
10
11 if !strings.Contains(hostDomain, appDomain) {
12- subdomain := GetCustomDomain(logger, hostDomain, cfg.Space)
13+ subdomain := GetCustomDomain(hostDomain, cfg.Space)
14 if subdomain != "" {
15 u, err := dbpool.FindUserForName(subdomain)
16 if u != nil && err == nil {
17@@ -49,7 +48,7 @@ func ServeFile(file string, contentType string) http.HandlerFunc {
18
19 contents, err := os.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
20 if err != nil {
21- logger.Error(err)
22+ logger.Error(err.Error())
23 http.Error(w, "file not found", 404)
24 }
25
26@@ -57,7 +56,7 @@ func ServeFile(file string, contentType string) http.HandlerFunc {
27
28 _, err = w.Write(contents)
29 if err != nil {
30- logger.Error(err)
31+ logger.Error(err.Error())
32 }
33 }
34 }
35@@ -104,7 +103,7 @@ func CreatePageHandler(fname string) http.HandlerFunc {
36 ts, err := RenderTemplate(cfg, []string{cfg.StaticPath(fname)})
37
38 if err != nil {
39- logger.Error(err)
40+ logger.Error(err.Error())
41 http.Error(w, err.Error(), http.StatusInternalServerError)
42 return
43 }
44@@ -114,7 +113,7 @@ func CreatePageHandler(fname string) http.HandlerFunc {
45 }
46 err = ts.Execute(w, data)
47 if err != nil {
48- logger.Error(err)
49+ logger.Error(err.Error())
50 http.Error(w, err.Error(), http.StatusInternalServerError)
51 }
52 }
1@@ -3,14 +3,14 @@ package shared
2 import (
3 "fmt"
4 "html/template"
5- "log"
6+ "log/slog"
7 "net/http"
8 "net/url"
9+ "os"
10 "path"
11 "strings"
12
13 "github.com/picosh/pico/wish/cms/config"
14- "go.uber.org/zap"
15 )
16
17 type SitePageData struct {
18@@ -251,21 +251,11 @@ func (c *ConfigSite) AssetURL(username, projectName, fpath string) string {
19 )
20 }
21
22-func CreateLogger(debug bool) *zap.SugaredLogger {
23- var (
24- err error
25- logger *zap.Logger
26- )
27-
28- if debug {
29- logger, err = zap.NewDevelopment()
30- } else {
31- logger, err = zap.NewProduction()
32+func CreateLogger(debug bool) *slog.Logger {
33+ opts := &slog.HandlerOptions{
34+ AddSource: true,
35 }
36-
37- if err != nil {
38- log.Fatal(err)
39- }
40-
41- return logger.Sugar()
42+ return slog.New(
43+ slog.NewJSONHandler(os.Stdout, opts),
44+ )
45 }
1@@ -3,6 +3,7 @@ package shared
2 import (
3 "context"
4 "fmt"
5+ "log/slog"
6 "net"
7 "net/http"
8 "net/http/pprof"
9@@ -12,7 +13,6 @@ import (
10 "github.com/patrickmn/go-cache"
11 "github.com/picosh/pico/db"
12 "github.com/picosh/pico/shared/storage"
13- "go.uber.org/zap"
14 )
15
16 type Route struct {
17@@ -46,7 +46,7 @@ func CreatePProfRoutes(routes []Route) []Route {
18
19 type ServeFn func(http.ResponseWriter, *http.Request)
20
21-func CreateServe(routes []Route, subdomainRoutes []Route, cfg *ConfigSite, dbpool db.DB, st storage.ObjectStorage, logger *zap.SugaredLogger, cache *cache.Cache) ServeFn {
22+func CreateServe(routes []Route, subdomainRoutes []Route, cfg *ConfigSite, dbpool db.DB, st storage.ObjectStorage, logger *slog.Logger, cache *cache.Cache) ServeFn {
23 return func(w http.ResponseWriter, r *http.Request) {
24 var allow []string
25 var subdomain string
26@@ -63,7 +63,7 @@ func CreateServe(routes []Route, subdomainRoutes []Route, cfg *ConfigSite, dbpoo
27 curRoutes = subdomainRoutes
28 }
29 } else {
30- subdomain = GetCustomDomain(logger, hostDomain, cfg.Space)
31+ subdomain = GetCustomDomain(hostDomain, cfg.Space)
32 if subdomain != "" {
33 curRoutes = subdomainRoutes
34 }
35@@ -110,8 +110,8 @@ func GetCfg(r *http.Request) *ConfigSite {
36 return r.Context().Value(ctxCfg{}).(*ConfigSite)
37 }
38
39-func GetLogger(r *http.Request) *zap.SugaredLogger {
40- return r.Context().Value(ctxLoggerKey{}).(*zap.SugaredLogger)
41+func GetLogger(r *http.Request) *slog.Logger {
42+ return r.Context().Value(ctxLoggerKey{}).(*slog.Logger)
43 }
44
45 func GetCache(r *http.Request) *cache.Cache {
46@@ -138,11 +138,10 @@ func GetSubdomain(r *http.Request) string {
47 return r.Context().Value(ctxSubdomainKey{}).(string)
48 }
49
50-func GetCustomDomain(logger *zap.SugaredLogger, host string, space string) string {
51+func GetCustomDomain(host string, space string) string {
52 txt := fmt.Sprintf("_%s.%s", space, host)
53 records, err := net.LookupTXT(txt)
54 if err != nil {
55- logger.Info(err)
56 return ""
57 }
58
+4,
-4
1@@ -99,7 +99,7 @@ func Middleware(cfg *config.ConfigCms, urls config.ConfigURL) bm.Handler {
2 }
3 key, err := util.KeyText(s)
4 if err != nil {
5- logger.Error(err)
6+ logger.Error(err.Error())
7 }
8
9 sshUser := s.User()
10@@ -114,7 +114,7 @@ func Middleware(cfg *config.ConfigCms, urls config.ConfigURL) bm.Handler {
11 }
12
13 if err != nil {
14- logger.Fatal(err)
15+ logger.Error(err.Error())
16 }
17
18 m := model{
19@@ -178,14 +178,14 @@ func (m model) findUser() (*db.User, error) {
20 var user *db.User
21
22 if m.sshUser == "new" {
23- logger.Infof("User requesting to register account")
24+ logger.Info("user requesting to register account", "user", user.Name)
25 return nil, nil
26 }
27
28 user, err := m.dbpool.FindUserForKey(m.sshUser, m.publicKey)
29
30 if err != nil {
31- logger.Error(err)
32+ logger.Error(err.Error())
33 // we only want to throw an error for specific cases
34 if errors.Is(err, &db.ErrMultiplePublicKeys{}) {
35 return nil, err
+2,
-4
1@@ -1,8 +1,6 @@
2 package config
3
4-import (
5- "go.uber.org/zap"
6-)
7+import "log/slog"
8
9 type ConfigURL interface {
10 BlogURL(username string) string
11@@ -24,7 +22,7 @@ type ConfigCms struct {
12 Space string
13 AllowedExt []string
14 HiddenPosts []string
15- Logger *zap.SugaredLogger
16+ Logger *slog.Logger
17 AllowRegister bool
18 MaxSize uint64
19 MaxAssetSize int64
+1,
-1
1@@ -411,7 +411,7 @@ func unlinkKey(m Model) tea.Cmd {
2 func deleteAccount(m Model) tea.Cmd {
3 return func() tea.Msg {
4 id := m.keys[m.getSelectedIndex()].UserID
5- m.cfg.Logger.Infof("User (%s) requested account deletion (%s)", m.user.Name, id)
6+ m.cfg.Logger.Info("user requested account deletion", "user", m.user.Name, "id", id)
7 err := m.dbpool.RemoveUsers([]string{id})
8 if err != nil {
9 return errMsg{err}
+2,
-3
1@@ -2,6 +2,7 @@ package posts
2
3 import (
4 "errors"
5+ "log/slog"
6
7 pager "github.com/charmbracelet/bubbles/paginator"
8 "github.com/charmbracelet/bubbles/spinner"
9@@ -11,8 +12,6 @@ import (
10 "github.com/picosh/pico/shared/storage"
11 "github.com/picosh/pico/wish/cms/config"
12 "github.com/picosh/pico/wish/cms/ui/common"
13-
14- "go.uber.org/zap"
15 )
16
17 const keysPerPage = 1
18@@ -62,7 +61,7 @@ type Model struct {
19 Exit bool
20 Quit bool
21 spinner spinner.Model
22- logger *zap.SugaredLogger
23+ logger *slog.Logger
24 }
25
26 // getSelectedIndex returns the index of the cursor in relation to the total