- commit
- a2877c7
- parent
- c71945d
- author
- Eric Bower
- date
- 2024-04-08 20:37:28 +0000 UTC
chore: TUI only accessible via pico.sh BREAKING CHANGE: TUI only accessible via pico.sh
50 files changed,
+248,
-1304
+0,
-41
1@@ -13,7 +13,6 @@ MINIO_PROMETHEUS_AUTH_TYPE=public
2 MINIO_PROMETHEUS_URL=
3 MINIO_PROMETHEUS_JOB_ID=minio
4
5-USE_IMGPROXY=1
6 IMGPROXY_DOMAIN=imgproxy.dev.pico.sh
7 IMGPROXY_URL=http://imgproxy:8080
8 IMGPROXY_ALLOWED_SOURCES=s3://,local://
9@@ -27,27 +26,6 @@ AWS_SECRET_ACCESS_KEY=$MINIO_ROOT_PASSWORD
10 IMGPROXY_PROMETHEUS_BIND=:8081
11 IMGPROXY_PROMETHEUS_NAMESPACE=imgproxy
12
13-LISTS_CADDYFILE=./caddy/Caddyfile
14-LISTS_V4=
15-LISTS_V6=
16-LISTS_HTTP_V4=$LISTS_V4:80
17-LISTS_HTTP_V6=[$LISTS_V6]:80
18-LISTS_HTTPS_V4=$LISTS_V4:443
19-LISTS_HTTPS_V6=[$LISTS_V6]:443
20-LISTS_SSH_V4=$LISTS_V4:22
21-LISTS_SSH_V6=[$LISTS_V6]:22
22-LISTS_HOST=
23-LISTS_SSH_PORT=2222
24-LISTS_WEB_PORT=3000
25-LISTS_PROM_PORT=9222
26-LISTS_DOMAIN=lists.dev.pico.sh:3000
27-LISTS_EMAIL=hello@pico.sh
28-LISTS_SUBDOMAINS=1
29-LISTS_CUSTOMDOMAINS=1
30-LISTS_PROTOCOL=http
31-LISTS_ALLOW_REGISTER=1
32-LISTS_DEBUG=1
33-
34 PASTES_CADDYFILE=./caddy/Caddyfile
35 PASTES_V4=
36 PASTES_V6=
37@@ -62,11 +40,7 @@ PASTES_SSH_PORT=2222
38 PASTES_WEB_PORT=3000
39 PASTES_PROM_PORT=9222
40 PASTES_DOMAIN=pastes.dev.pico.sh:3001
41-PASTES_EMAIL=hello@pico.sh
42-PASTES_SUBDOMAINS=1
43-PASTES_CUSTOMDOMAINS=1
44 PASTES_PROTOCOL=http
45-PASTES_ALLOW_REGISTER=1
46 PASTES_DEBUG=1
47
48 PROSE_CADDYFILE=./caddy/Caddyfile
49@@ -83,11 +57,7 @@ PROSE_SSH_PORT=2222
50 PROSE_WEB_PORT=3000
51 PROSE_PROM_PORT=9222
52 PROSE_DOMAIN=prose.dev.pico.sh:3002
53-PROSE_EMAIL=hello@pico.sh
54-PROSE_SUBDOMAINS=1
55-PROSE_CUSTOMDOMAINS=1
56 PROSE_PROTOCOL=http
57-PROSE_ALLOW_REGISTER=1
58 PROSE_DEBUG=1
59
60 IMGS_CADDYFILE=./caddy/Caddyfile
61@@ -105,10 +75,7 @@ IMGS_WEB_PORT=3000
62 IMGS_PROM_PORT=9222
63 IMGS_DOMAIN=imgs.dev.pico.sh:3003
64 IMGS_EMAIL=hello@pico.sh
65-IMGS_SUBDOMAINS=1
66-IMGS_CUSTOMDOMAINS=1
67 IMGS_PROTOCOL=http
68-IMGS_ALLOW_REGISTER=1
69 IMGS_STORAGE_DIR=.storage
70 IMGS_DEBUG=1
71
72@@ -127,11 +94,7 @@ FEEDS_SSH_PORT=2222
73 FEEDS_WEB_PORT=3000
74 FEEDS_PROM_PORT=9222
75 FEEDS_DOMAIN=feeds.dev.pico.sh:3004
76-FEEDS_EMAIL=hello@pico.sh
77-FEEDS_SUBDOMAINS=1
78-FEEDS_CUSTOMDOMAINS=1
79 FEEDS_PROTOCOL=http
80-FEEDS_ALLOW_REGISTER=1
81 FEEDS_DEBUG=1
82
83 PGS_CADDYFILE=./caddy/Caddyfile
84@@ -148,11 +111,7 @@ PGS_SSH_PORT=2222
85 PGS_WEB_PORT=3000
86 PGS_PROM_PORT=9222
87 PGS_DOMAIN=pgs.dev.pico.sh:3005
88-PGS_EMAIL=hello@pico.sh
89-PGS_SUBDOMAINS=1
90-PGS_CUSTOMDOMAINS=1
91 PGS_PROTOCOL=http
92-PGS_ALLOW_REGISTER=1
93 PGS_STORAGE_DIR=.storage
94 PGS_DEBUG=1
95
1@@ -10,7 +10,6 @@ import (
2 "github.com/picosh/pico/pgs"
3 "github.com/picosh/pico/shared"
4 "github.com/picosh/pico/shared/storage"
5- "github.com/picosh/pico/wish/cms/config"
6 )
7
8 func bail(err error) {
9@@ -35,7 +34,7 @@ func main() {
10 }
11 logger := slog.Default()
12
13- picoCfg := config.NewConfigCms()
14+ picoCfg := shared.NewConfigSite()
15 picoCfg.Logger = logger
16 picoCfg.DbURL = os.Getenv("DATABASE_URL")
17 picoCfg.MinioURL = os.Getenv("MINIO_URL")
+1,
-2
1@@ -10,7 +10,6 @@ import (
2 "github.com/picosh/pico/db"
3 "github.com/picosh/pico/db/postgres"
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/cms/config"
6 )
7
8 func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
9@@ -59,7 +58,7 @@ func updateDates(tx *sql.Tx, postID string, date *time.Time) error {
10 func main() {
11 logger := slog.Default()
12
13- picoCfg := config.NewConfigCms()
14+ picoCfg := shared.NewConfigSite()
15 picoCfg.Logger = logger
16 picoCfg.DbURL = os.Getenv("DATABASE_URL")
17 picoDb := postgres.NewDB(picoCfg.DbURL, picoCfg.Logger)
+2,
-2
1@@ -6,7 +6,7 @@ import (
2 "os"
3
4 "github.com/picosh/pico/db/postgres"
5- "github.com/picosh/pico/wish/cms/config"
6+ "github.com/picosh/pico/shared"
7 )
8
9 func bail(err error) {
10@@ -18,7 +18,7 @@ func bail(err error) {
11 func main() {
12 logger := slog.Default()
13
14- picoCfg := config.NewConfigCms()
15+ picoCfg := shared.NewConfigSite()
16 picoCfg.Logger = logger
17 picoCfg.DbURL = os.Getenv("DATABASE_URL")
18 picoDb := postgres.NewDB(picoCfg.DbURL, picoCfg.Logger)
+4,
-4
1@@ -9,7 +9,7 @@ import (
2
3 "github.com/picosh/pico/db"
4 "github.com/picosh/pico/db/postgres"
5- "github.com/picosh/pico/wish/cms/config"
6+ "github.com/picosh/pico/shared"
7 )
8
9 func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
10@@ -101,17 +101,17 @@ type ConflictData struct {
11 func main() {
12 logger := slog.Default()
13
14- listsCfg := config.NewConfigCms()
15+ listsCfg := shared.NewConfigSite()
16 listsCfg.Logger = logger
17 listsCfg.DbURL = os.Getenv("LISTS_DB_URL")
18 listsDb := postgres.NewDB(listsCfg.DbURL, listsCfg.Logger)
19
20- proseCfg := config.NewConfigCms()
21+ proseCfg := shared.NewConfigSite()
22 proseCfg.DbURL = os.Getenv("PROSE_DB_URL")
23 proseCfg.Logger = logger
24 proseDb := postgres.NewDB(proseCfg.DbURL, proseCfg.Logger)
25
26- picoCfg := config.NewConfigCms()
27+ picoCfg := shared.NewConfigSite()
28 picoCfg.Logger = logger
29 picoCfg.DbURL = os.Getenv("PICO_DB_URL")
30 picoDb := postgres.NewDB(picoCfg.DbURL, picoCfg.Logger)
+1,
-2
1@@ -6,12 +6,11 @@ import (
2
3 "github.com/picosh/pico/db/postgres"
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/cms/config"
6 )
7
8 func main() {
9 logger := slog.Default()
10- picoCfg := config.NewConfigCms()
11+ picoCfg := shared.NewConfigSite()
12 picoCfg.Logger = logger
13 picoCfg.DbURL = os.Getenv("DATABASE_URL")
14 picoDb := postgres.NewDB(picoCfg.DbURL, picoCfg.Logger)
1@@ -8,7 +8,6 @@ import (
2 "github.com/picosh/pico/db"
3 "github.com/picosh/pico/db/postgres"
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/cms/config"
6 )
7
8 func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
9@@ -52,7 +51,7 @@ func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
10 func main() {
11 logger := slog.Default()
12
13- picoCfg := config.NewConfigCms()
14+ picoCfg := shared.NewConfigSite()
15 picoCfg.Logger = logger
16 picoCfg.DbURL = os.Getenv("DATABASE_URL")
17 picoDb := postgres.NewDB(picoCfg.DbURL, picoCfg.Logger)
+0,
-1
1@@ -77,7 +77,6 @@ func StartApiServer() {
2 "Starting server on port",
3 "port", cfg.Port,
4 "domain", cfg.Domain,
5- "email", cfg.Email,
6 )
7
8 logger.Error(http.ListenAndServe(portStr, router).Error())
+14,
-32
1@@ -2,52 +2,34 @@ package feeds
2
3 import (
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/cms/config"
6 )
7
8 func NewConfigSite() *shared.ConfigSite {
9 debug := shared.GetEnv("FEEDS_DEBUG", "0")
10 domain := shared.GetEnv("FEEDS_DOMAIN", "feeds.sh")
11- email := shared.GetEnv("FEEDS_EMAIL", "hello@feeds.sh")
12- subdomains := shared.GetEnv("FEEDS_SUBDOMAINS", "0")
13- customdomains := shared.GetEnv("FEEDS_CUSTOMDOMAINS", "0")
14 port := shared.GetEnv("FEEDS_WEB_PORT", "3000")
15 protocol := shared.GetEnv("FEEDS_PROTOCOL", "https")
16- allowRegister := shared.GetEnv("FEEDS_ALLOW_REGISTER", "1")
17 storageDir := shared.GetEnv("IMGS_STORAGE_DIR", ".storage")
18 minioURL := shared.GetEnv("MINIO_URL", "")
19 minioUser := shared.GetEnv("MINIO_ROOT_USER", "")
20 minioPass := shared.GetEnv("MINIO_ROOT_PASSWORD", "")
21 dbURL := shared.GetEnv("DATABASE_URL", "")
22 sendgridKey := shared.GetEnv("SENDGRID_API_KEY", "")
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/feeds\n"
27
28 return &shared.ConfigSite{
29- Debug: debug == "1",
30- SubdomainsEnabled: subdomains == "1",
31- CustomdomainsEnabled: customdomains == "1",
32- UseImgProxy: useImgProxy == "1",
33- SendgridKey: sendgridKey,
34- ConfigCms: config.ConfigCms{
35- Domain: domain,
36- Email: email,
37- Port: port,
38- Protocol: protocol,
39- DbURL: dbURL,
40- StorageDir: storageDir,
41- MinioURL: minioURL,
42- MinioUser: minioUser,
43- MinioPass: minioPass,
44- Description: "An rss-to-email digest service for hackers",
45- IntroText: intro,
46- Space: "feeds",
47- AllowedExt: []string{".txt"},
48- HiddenPosts: []string{"_header.txt", "_readme.txt"},
49- Logger: shared.CreateLogger(debug == "1"),
50- AllowRegister: allowRegister == "1",
51- },
52+ Debug: debug == "1",
53+ SendgridKey: sendgridKey,
54+ Domain: domain,
55+ Port: port,
56+ Protocol: protocol,
57+ DbURL: dbURL,
58+ StorageDir: storageDir,
59+ MinioURL: minioURL,
60+ MinioUser: minioUser,
61+ MinioPass: minioPass,
62+ Space: "feeds",
63+ AllowedExt: []string{".txt"},
64+ HiddenPosts: []string{"_header.txt", "_readme.txt"},
65+ Logger: shared.CreateLogger(debug == "1"),
66 }
67 }
+1,
-1
1@@ -383,7 +383,7 @@ func (f *Fetcher) SendEmail(username, email string, subject string, msg *MsgBody
2 return fmt.Errorf("(%s) does not have an email associated with their feed post", username)
3 }
4
5- from := mail.NewEmail("team pico", f.cfg.Email)
6+ from := mail.NewEmail("team pico", shared.DefaultEmail)
7 to := mail.NewEmail(username, email)
8
9 // f.cfg.Logger.Infof("message body (%s)", plainTextContent)
+4,
-4
1@@ -1,20 +1,20 @@
2 {{template "base" .}}
3
4-{{define "title"}}{{.Site.Domain}} -- an rss email notification service{{end}}
5+{{define "title"}}{{.Site.Domain}} -- An rss email notification service{{end}}
6
7 {{define "meta"}}
8-<meta name="description" content="an rss email notification service" />
9+<meta name="description" content="An rss email notification service" />
10
11 <meta property="og:type" content="website">
12 <meta property="og:site_name" content="{{.Site.Domain}}">
13 <meta property="og:url" content="https://{{.Site.Domain}}">
14 <meta property="og:title" content="{{.Site.Domain}}">
15-<meta property="og:description" content="an rss email notification service">
16+<meta property="og:description" content="An rss email notification service">
17
18 <meta name="twitter:card" content="summary" />
19 <meta property="twitter:url" content="https://{{.Site.Domain}}">
20 <meta property="twitter:title" content="{{.Site.Domain}}">
21-<meta property="twitter:description" content="an rss email notification service">
22+<meta property="twitter:description" content="An rss email notification service">
23 <meta name="twitter:image" content="https://{{.Site.Domain}}/card.png" />
24 <meta name="twitter:image:src" content="https://{{.Site.Domain}}/card.png" />
25
+1,
-3
1@@ -11,13 +11,11 @@ import (
2 "github.com/charmbracelet/promwish"
3 "github.com/charmbracelet/ssh"
4 "github.com/charmbracelet/wish"
5- bm "github.com/charmbracelet/wish/bubbletea"
6 "github.com/picosh/pico/db/postgres"
7 "github.com/picosh/pico/filehandlers"
8 "github.com/picosh/pico/shared"
9 "github.com/picosh/pico/shared/storage"
10 wsh "github.com/picosh/pico/wish"
11- "github.com/picosh/pico/wish/cms"
12 "github.com/picosh/send/list"
13 "github.com/picosh/send/pipe"
14 "github.com/picosh/send/proxy"
15@@ -41,7 +39,7 @@ func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
16 scp.Middleware(handler),
17 wishrsync.Middleware(handler),
18 auth.Middleware(handler),
19- wsh.PtyMdw(bm.Middleware(cms.Middleware(&handler.Cfg.ConfigCms, handler.Cfg))),
20+ wsh.PtyMdw(wsh.DeprecatedNotice()),
21 wsh.LogMiddleware(handler.GetLogger()),
22 }
23 }
+0,
-1
1@@ -335,7 +335,6 @@ func StartApiServer() {
2 "Starting server on port",
3 "port", cfg.Port,
4 "domain", cfg.Domain,
5- "email", cfg.Email,
6 )
7
8 logger.Error(http.ListenAndServe(portStr, router).Error())
+11,
-30
1@@ -2,50 +2,31 @@ package imgs
2
3 import (
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/cms/config"
6 )
7
8 func NewConfigSite() *shared.ConfigSite {
9 debug := shared.GetEnv("IMGS_DEBUG", "0")
10 domain := shared.GetEnv("IMGS_DOMAIN", "prose.sh")
11- email := shared.GetEnv("IMGS_EMAIL", "hello@prose.sh")
12- subdomains := shared.GetEnv("IMGS_SUBDOMAINS", "0")
13- customdomains := shared.GetEnv("IMGS_CUSTOMDOMAINS", "0")
14 port := shared.GetEnv("IMGS_WEB_PORT", "3000")
15 protocol := shared.GetEnv("IMGS_PROTOCOL", "https")
16- allowRegister := shared.GetEnv("IMGS_ALLOW_REGISTER", "1")
17 storageDir := shared.GetEnv("IMGS_STORAGE_DIR", ".storage")
18 minioURL := shared.GetEnv("MINIO_URL", "")
19 minioUser := shared.GetEnv("MINIO_ROOT_USER", "")
20 minioPass := shared.GetEnv("MINIO_ROOT_PASSWORD", "")
21 dbURL := shared.GetEnv("DATABASE_URL", "")
22- useImgProxy := shared.GetEnv("USE_IMGPROXY", "1")
23-
24- intro := "To get started, enter a username.\n"
25- intro += "To learn next steps go to our docs at https://pico.sh/imgs\n"
26
27 cfg := shared.ConfigSite{
28- Debug: debug == "1",
29- SubdomainsEnabled: subdomains == "1",
30- CustomdomainsEnabled: customdomains == "1",
31- UseImgProxy: useImgProxy == "1",
32- ConfigCms: config.ConfigCms{
33- Domain: domain,
34- Email: email,
35- Port: port,
36- Protocol: protocol,
37- DbURL: dbURL,
38- StorageDir: storageDir,
39- MinioURL: minioURL,
40- MinioUser: minioUser,
41- MinioPass: minioPass,
42- Description: "An image hosting service for hackers.",
43- IntroText: intro,
44- Space: "imgs",
45- AllowedExt: []string{".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico"},
46- Logger: shared.CreateLogger(debug == "1"),
47- AllowRegister: allowRegister == "1",
48- },
49+ Debug: debug == "1",
50+ Domain: domain,
51+ Port: port,
52+ Protocol: protocol,
53+ DbURL: dbURL,
54+ StorageDir: storageDir,
55+ MinioURL: minioURL,
56+ MinioUser: minioUser,
57+ MinioPass: minioPass,
58+ Space: "imgs",
59+ Logger: shared.CreateLogger(debug == "1"),
60 }
61
62 return &cfg
+0,
-1
1@@ -392,7 +392,6 @@ func StartApiServer() {
2 "Starting server on port",
3 "port", cfg.Port,
4 "domain", cfg.Domain,
5- "email", cfg.Email,
6 )
7
8 logger.Error(http.ListenAndServe(portStr, router).Error())
+11,
-29
1@@ -2,48 +2,30 @@ package pastes
2
3 import (
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/cms/config"
6 )
7
8 func NewConfigSite() *shared.ConfigSite {
9 debug := shared.GetEnv("PASTES_DEBUG", "0")
10 domain := shared.GetEnv("PASTES_DOMAIN", "pastes.sh")
11- email := shared.GetEnv("PASTES_EMAIL", "hello@pastes.sh")
12- subdomains := shared.GetEnv("PASTES_SUBDOMAINS", "0")
13- customdomains := shared.GetEnv("PASTES_CUSTOMDOMAINS", "0")
14 port := shared.GetEnv("PASTES_WEB_PORT", "3000")
15 dbURL := shared.GetEnv("DATABASE_URL", "")
16 protocol := shared.GetEnv("PASTES_PROTOCOL", "https")
17- allowRegister := shared.GetEnv("PASTES_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- useImgProxy := shared.GetEnv("USE_IMGPROXY", "1")
23-
24- intro := "To get started, enter a username.\n"
25- intro += "To learn next steps go to our docs at https://pico.sh/pastes\n"
26
27 return &shared.ConfigSite{
28- Debug: debug == "1",
29- SubdomainsEnabled: subdomains == "1",
30- CustomdomainsEnabled: customdomains == "1",
31- UseImgProxy: useImgProxy == "1",
32- ConfigCms: config.ConfigCms{
33- Domain: domain,
34- Port: port,
35- Protocol: protocol,
36- Email: email,
37- DbURL: dbURL,
38- StorageDir: storageDir,
39- MinioURL: minioURL,
40- MinioUser: minioUser,
41- MinioPass: minioPass,
42- Description: "A pastebin for hackers.",
43- IntroText: intro,
44- Space: "pastes",
45- Logger: shared.CreateLogger(debug == "1"),
46- AllowRegister: allowRegister == "1",
47- },
48+ Debug: debug == "1",
49+ Domain: domain,
50+ Port: port,
51+ Protocol: protocol,
52+ DbURL: dbURL,
53+ StorageDir: storageDir,
54+ MinioURL: minioURL,
55+ MinioUser: minioUser,
56+ MinioPass: minioPass,
57+ Space: "pastes",
58+ Logger: shared.CreateLogger(debug == "1"),
59 }
60 }
+1,
-3
1@@ -11,13 +11,11 @@ import (
2 "github.com/charmbracelet/promwish"
3 "github.com/charmbracelet/ssh"
4 "github.com/charmbracelet/wish"
5- bm "github.com/charmbracelet/wish/bubbletea"
6 "github.com/picosh/pico/db/postgres"
7 "github.com/picosh/pico/filehandlers"
8 "github.com/picosh/pico/shared"
9 "github.com/picosh/pico/shared/storage"
10 wsh "github.com/picosh/pico/wish"
11- "github.com/picosh/pico/wish/cms"
12 "github.com/picosh/send/list"
13 "github.com/picosh/send/pipe"
14 "github.com/picosh/send/proxy"
15@@ -41,7 +39,7 @@ func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
16 scp.Middleware(handler),
17 wishrsync.Middleware(handler),
18 auth.Middleware(handler),
19- wsh.PtyMdw(bm.Middleware(cms.Middleware(&handler.Cfg.ConfigCms, handler.Cfg))),
20+ wsh.PtyMdw(wsh.DeprecatedNotice()),
21 wsh.LogMiddleware(handler.GetLogger()),
22 }
23 }
+1,
-2
1@@ -45,7 +45,7 @@ func checkHandler(w http.ResponseWriter, r *http.Request) {
2
3 if cfg.IsCustomdomains() {
4 hostDomain := r.URL.Query().Get("domain")
5- appDomain := strings.Split(cfg.ConfigCms.Domain, ":")[0]
6+ appDomain := strings.Split(cfg.Domain, ":")[0]
7
8 if !strings.Contains(hostDomain, appDomain) {
9 subdomain := shared.GetCustomDomain(hostDomain, cfg.Space)
10@@ -514,7 +514,6 @@ func StartApiServer() {
11 "Starting server on port",
12 "port", cfg.Port,
13 "domain", cfg.Domain,
14- "email", cfg.Email,
15 )
16 err = http.ListenAndServe(portStr, router)
17 logger.Error(
+1,
-1
1@@ -14,7 +14,7 @@ import (
2 "github.com/picosh/pico/db"
3 "github.com/picosh/pico/shared"
4 "github.com/picosh/pico/shared/storage"
5- "github.com/picosh/pico/wish/cms/ui/common"
6+ "github.com/picosh/pico/tui/common"
7 )
8
9 func styleRows(styles common.Styles) func(row, col int) lipgloss.Style {
+14,
-32
1@@ -2,7 +2,6 @@ package pgs
2
3 import (
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/cms/config"
6 )
7
8 var maxSize = uint64(25 * shared.MB)
9@@ -11,50 +10,33 @@ var maxAssetSize = int64(5 * shared.MB)
10 func NewConfigSite() *shared.ConfigSite {
11 debug := shared.GetEnv("PGS_DEBUG", "0")
12 domain := shared.GetEnv("PGS_DOMAIN", "pgs.sh")
13- email := shared.GetEnv("PGS_EMAIL", "hello@pico.sh")
14- subdomains := shared.GetEnv("PGS_SUBDOMAINS", "0")
15- customdomains := shared.GetEnv("PGS_CUSTOMDOMAINS", "0")
16 port := shared.GetEnv("PGS_WEB_PORT", "3000")
17 protocol := shared.GetEnv("PGS_PROTOCOL", "https")
18- allowRegister := shared.GetEnv("PGS_ALLOW_REGISTER", "1")
19 storageDir := shared.GetEnv("PGS_STORAGE_DIR", ".storage")
20 minioURL := shared.GetEnv("MINIO_URL", "")
21 minioUser := shared.GetEnv("MINIO_ROOT_USER", "")
22 minioPass := shared.GetEnv("MINIO_ROOT_PASSWORD", "")
23 dbURL := shared.GetEnv("DATABASE_URL", "")
24- useImgProxy := shared.GetEnv("USE_IMGPROXY", "1")
25 secret := shared.GetEnv("PICO_SECRET", "")
26 if secret == "" {
27 panic("must provide PICO_SECRET environment variable")
28 }
29
30- intro := "To create an account, enter a username.\n"
31- intro += "After that, go to https://pico.sh/getting-started#next-steps"
32-
33 cfg := shared.ConfigSite{
34- Debug: debug == "1",
35- SubdomainsEnabled: subdomains == "1",
36- CustomdomainsEnabled: customdomains == "1",
37- UseImgProxy: useImgProxy == "1",
38- Secret: secret,
39- ConfigCms: config.ConfigCms{
40- Domain: domain,
41- Email: email,
42- Port: port,
43- Protocol: protocol,
44- DbURL: dbURL,
45- StorageDir: storageDir,
46- MinioURL: minioURL,
47- MinioUser: minioUser,
48- MinioPass: minioPass,
49- Description: "A zero-install static site hosting service for hackers",
50- IntroText: intro,
51- Space: "pgs",
52- MaxSize: maxSize,
53- MaxAssetSize: maxAssetSize,
54- Logger: shared.CreateLogger(debug == "1"),
55- AllowRegister: allowRegister == "1",
56- },
57+ Debug: debug == "1",
58+ Secret: secret,
59+ Domain: domain,
60+ Port: port,
61+ Protocol: protocol,
62+ DbURL: dbURL,
63+ StorageDir: storageDir,
64+ MinioURL: minioURL,
65+ MinioUser: minioUser,
66+ MinioPass: minioPass,
67+ Space: "pgs",
68+ MaxSize: maxSize,
69+ MaxAssetSize: maxAssetSize,
70+ Logger: shared.CreateLogger(debug == "1"),
71 }
72
73 return &cfg
+1,
-1
1@@ -8,7 +8,7 @@
2
3 <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
4
5- <meta name="keywords" content="static, site, hosting" />
6+ <meta name="keywords" content="static, site, hosting, hackers, pico" />
7
8 <link rel="stylesheet" href="https://pico.sh/smol.css" />
9 <link rel="stylesheet" href="main.css" />
+4,
-4
1@@ -1,20 +1,20 @@
2 {{template "base" .}}
3
4-{{define "title"}}{{.Site.Domain}} -- {{.Site.Description}}{{end}}
5+{{define "title"}}{{.Site.Domain}} -- A zero-install static site hosting service for hackers{{end}}
6
7 {{define "meta"}}
8-<meta name="description" content="{{.Site.Description}}" />
9+<meta name="description" content="A zero-install static site hosting service for hackers" />
10
11 <meta property="og:type" content="website">
12 <meta property="og:site_name" content="{{.Site.Domain}}">
13 <meta property="og:url" content="https://{{.Site.Domain}}">
14 <meta property="og:title" content="{{.Site.Domain}}">
15-<meta property="og:description" content="{{.Site.Description}}">
16+<meta property="og:description" content="A zero-install static site hosting service for hackers">
17
18 <meta name="twitter:card" content="summary" />
19 <meta property="twitter:url" content="https://{{.Site.Domain}}">
20 <meta property="twitter:title" content="{{.Site.Domain}}">
21-<meta property="twitter:description" content="{{.Site.Description}}">
22+<meta property="twitter:description" content="A zero-install static site hosting service for hackers">
23 <meta name="twitter:image" content="https://{{.Site.Domain}}/card.png" />
24 <meta name="twitter:image:src" content="https://{{.Site.Domain}}/card.png" />
25
+2,
-1
1@@ -16,6 +16,7 @@ import (
2 uploadassets "github.com/picosh/pico/filehandlers/assets"
3 "github.com/picosh/pico/shared"
4 "github.com/picosh/pico/shared/storage"
5+ "github.com/picosh/pico/tui"
6 wsh "github.com/picosh/pico/wish"
7 "github.com/picosh/ptun"
8 "github.com/picosh/send/list"
9@@ -40,7 +41,7 @@ func createRouter(cfg *shared.ConfigSite, handler *uploadassets.UploadAssetHandl
10 scp.Middleware(handler),
11 wishrsync.Middleware(handler),
12 auth.Middleware(handler),
13- wsh.PtyMdw(bm.Middleware(CmsMiddleware(&cfg.ConfigCms, cfg))),
14+ wsh.PtyMdw(bm.Middleware(tui.CmsMiddleware(cfg))),
15 WishMiddleware(handler),
16 wsh.LogMiddleware(handler.GetLogger()),
17 }
+1,
-1
1@@ -12,7 +12,7 @@ import (
2 "github.com/picosh/pico/db"
3 uploadassets "github.com/picosh/pico/filehandlers/assets"
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/cms/ui/common"
6+ "github.com/picosh/pico/tui/common"
7 "github.com/picosh/send/send/utils"
8 )
9
+1,
-2
1@@ -905,7 +905,7 @@ func createSubdomainRoutes(staticRoutes []shared.Route) []shared.Route {
2
3 func StartApiServer() {
4 cfg := NewConfigSite()
5- dbpool := postgres.NewDB(cfg.ConfigCms.DbURL, cfg.Logger)
6+ dbpool := postgres.NewDB(cfg.DbURL, cfg.Logger)
7 defer dbpool.Close()
8 logger := cfg.Logger
9
10@@ -946,7 +946,6 @@ func StartApiServer() {
11 "Starting server on port",
12 "port", cfg.Port,
13 "domain", cfg.Domain,
14- "email", cfg.Email,
15 )
16
17 logger.Error(http.ListenAndServe(portStr, router).Error())
+23,
-41
1@@ -2,24 +2,18 @@ package prose
2
3 import (
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/cms/config"
6 )
7
8 func NewConfigSite() *shared.ConfigSite {
9 debug := shared.GetEnv("PROSE_DEBUG", "0")
10 domain := shared.GetEnv("PROSE_DOMAIN", "prose.sh")
11- email := shared.GetEnv("PROSE_EMAIL", "hello@prose.sh")
12- subdomains := shared.GetEnv("PROSE_SUBDOMAINS", "0")
13- customdomains := shared.GetEnv("PROSE_CUSTOMDOMAINS", "0")
14 port := shared.GetEnv("PROSE_WEB_PORT", "3000")
15 protocol := shared.GetEnv("PROSE_PROTOCOL", "https")
16- allowRegister := shared.GetEnv("PROSE_ALLOW_REGISTER", "1")
17 storageDir := shared.GetEnv("IMGS_STORAGE_DIR", ".storage")
18 minioURL := shared.GetEnv("MINIO_URL", "")
19 minioUser := shared.GetEnv("MINIO_ROOT_USER", "")
20 minioPass := shared.GetEnv("MINIO_ROOT_PASSWORD", "")
21 dbURL := shared.GetEnv("DATABASE_URL", "")
22- useImgProxy := shared.GetEnv("USE_IMGPROXY", "1")
23 maxSize := uint64(500 * shared.MB)
24 maxImgSize := int64(10 * shared.MB)
25 secret := shared.GetEnv("PICO_SECRET", "")
26@@ -27,42 +21,30 @@ func NewConfigSite() *shared.ConfigSite {
27 panic("must provide PICO_SECRET environment variable")
28 }
29
30- intro := "To get started, enter a username.\n"
31- intro += "To learn next steps go to our docs at https://pico.sh/prose\n"
32-
33 return &shared.ConfigSite{
34- Debug: debug == "1",
35- SubdomainsEnabled: subdomains == "1",
36- CustomdomainsEnabled: customdomains == "1",
37- UseImgProxy: useImgProxy == "1",
38- Secret: secret,
39- ConfigCms: config.ConfigCms{
40- Domain: domain,
41- Email: email,
42- Port: port,
43- Protocol: protocol,
44- DbURL: dbURL,
45- StorageDir: storageDir,
46- MinioURL: minioURL,
47- MinioUser: minioUser,
48- MinioPass: minioPass,
49- Description: "A blog platform for hackers.",
50- IntroText: intro,
51- Space: "prose",
52- AllowedExt: []string{
53- ".md",
54- ".jpg",
55- ".jpeg",
56- ".png",
57- ".gif",
58- ".webp",
59- ".svg",
60- },
61- HiddenPosts: []string{"_readme.md", "_styles.css", "_footer.md", "_404.md"},
62- Logger: shared.CreateLogger(debug == "1"),
63- AllowRegister: allowRegister == "1",
64- MaxSize: maxSize,
65- MaxAssetSize: maxImgSize,
66+ Debug: debug == "1",
67+ Secret: secret,
68+ Domain: domain,
69+ Port: port,
70+ Protocol: protocol,
71+ DbURL: dbURL,
72+ StorageDir: storageDir,
73+ MinioURL: minioURL,
74+ MinioUser: minioUser,
75+ MinioPass: minioPass,
76+ Space: "prose",
77+ AllowedExt: []string{
78+ ".md",
79+ ".jpg",
80+ ".jpeg",
81+ ".png",
82+ ".gif",
83+ ".webp",
84+ ".svg",
85 },
86+ HiddenPosts: []string{"_readme.md", "_styles.css", "_footer.md", "_404.md"},
87+ Logger: shared.CreateLogger(debug == "1"),
88+ MaxSize: maxSize,
89+ MaxAssetSize: maxImgSize,
90 }
91 }
+1,
-1
1@@ -6,7 +6,7 @@
2 <meta name="viewport" content="width=device-width, initial-scale=1" />
3 <title>{{template "title" .}}</title>
4
5- <meta name="keywords" content="blog, blogging, write, writing" />
6+ <meta name="keywords" content="blog, blogging, write, writing, hackers, developers, terminal" />
7
8 <link rel="stylesheet" href="/smol.css" />
9 <link rel="stylesheet" href="/main.css" />
+21,
-2
1@@ -1,10 +1,29 @@
2 {{template "base" .}}
3
4-{{define "title"}}discover prose -- {{.Site.Domain}}{{end}}
5+{{define "title"}}prose.sh -- A blog platform for hackers{{end}}
6
7 {{define "meta"}}
8-<meta name="description" content="discover interesting posts" />
9+<meta name="description" content="A blog platform for hackers" />
10 <link rel="alternate" href="/rss" type="application/rss+xml" title="RSS feed for prose.sh" />
11+<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
12+
13+<meta property="og:type" content="website">
14+<meta property="og:site_name" content="{{.Site.Domain}}">
15+<meta property="og:url" content="https://{{.Site.Domain}}">
16+<meta property="og:title" content="{{.Site.Domain}}">
17+<meta property="og:description" content="A blog platform for hackers">
18+
19+<meta name="twitter:card" content="summary" />
20+<meta property="twitter:url" content="https://{{.Site.Domain}}">
21+<meta property="twitter:title" content="{{.Site.Domain}}">
22+<meta property="twitter:description" content="A blog platform for hackers">
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+<meta property="og:image:width" content="300" />
27+<meta property="og:image:height" content="300" />
28+<meta itemprop="image" content="https://{{.Site.Domain}}/card.png" />
29+<meta property="og:image" content="https://{{.Site.Domain}}/card.png" />
30 {{end}}
31
32 {{define "attrs"}}{{end}}
+1,
-3
1@@ -11,14 +11,12 @@ import (
2 "github.com/charmbracelet/promwish"
3 "github.com/charmbracelet/ssh"
4 "github.com/charmbracelet/wish"
5- bm "github.com/charmbracelet/wish/bubbletea"
6 "github.com/picosh/pico/db/postgres"
7 "github.com/picosh/pico/filehandlers"
8 uploadimgs "github.com/picosh/pico/filehandlers/imgs"
9 "github.com/picosh/pico/shared"
10 "github.com/picosh/pico/shared/storage"
11 wsh "github.com/picosh/pico/wish"
12- "github.com/picosh/pico/wish/cms"
13 "github.com/picosh/send/list"
14 "github.com/picosh/send/pipe"
15 "github.com/picosh/send/proxy"
16@@ -42,7 +40,7 @@ func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
17 scp.Middleware(handler),
18 wishrsync.Middleware(handler),
19 auth.Middleware(handler),
20- wsh.PtyMdw(bm.Middleware(cms.Middleware(&handler.Cfg.ConfigCms, handler.Cfg))),
21+ wsh.PtyMdw(wsh.DeprecatedNotice()),
22 wsh.LogMiddleware(handler.GetLogger()),
23 }
24 }
1@@ -53,7 +53,7 @@ func CheckHandler(w http.ResponseWriter, r *http.Request) {
2
3 if cfg.IsCustomdomains() {
4 hostDomain := r.URL.Query().Get("domain")
5- appDomain := strings.Split(cfg.ConfigCms.Domain, ":")[0]
6+ appDomain := strings.Split(cfg.Domain, ":")[0]
7
8 if !strings.Contains(hostDomain, appDomain) {
9 subdomain := GetCustomDomain(hostDomain, cfg.Space)
1@@ -9,15 +9,13 @@ import (
2 "os"
3 "path"
4 "strings"
5-
6- "github.com/picosh/pico/wish/cms/config"
7 )
8
9+var DefaultEmail = "hello@pico.sh"
10+
11 type SitePageData struct {
12- Domain template.URL
13- HomeURL template.URL
14- Email string
15- Description string
16+ Domain template.URL
17+ HomeURL template.URL
18 }
19
20 type PageData struct {
21@@ -25,14 +23,27 @@ type PageData struct {
22 }
23
24 type ConfigSite struct {
25- config.ConfigCms
26- config.ConfigURL
27- Debug bool
28- SubdomainsEnabled bool
29- CustomdomainsEnabled bool
30- SendgridKey string
31- UseImgProxy bool
32- Secret string
33+ Debug bool
34+ SendgridKey string
35+ Secret string
36+ Domain string
37+ Port string
38+ Protocol string
39+ DbURL string
40+ StorageDir string
41+ MinioURL string
42+ MinioUser string
43+ MinioPass string
44+ Space string
45+ AllowedExt []string
46+ HiddenPosts []string
47+ MaxSize uint64
48+ MaxAssetSize int64
49+ Logger *slog.Logger
50+}
51+
52+func NewConfigSite() *ConfigSite {
53+ return &ConfigSite{}
54 }
55
56 type CreateURL struct {
57@@ -69,19 +80,17 @@ func CreateURLFromRequest(cfg *ConfigSite, r *http.Request) *CreateURL {
58
59 func (c *ConfigSite) GetSiteData() *SitePageData {
60 return &SitePageData{
61- Domain: template.URL(c.Domain),
62- HomeURL: template.URL(c.HomeURL()),
63- Email: c.Email,
64- Description: c.Description,
65+ Domain: template.URL(c.Domain),
66+ HomeURL: template.URL(c.HomeURL()),
67 }
68 }
69
70 func (c *ConfigSite) IsSubdomains() bool {
71- return c.SubdomainsEnabled
72+ return true
73 }
74
75 func (c *ConfigSite) IsCustomdomains() bool {
76- return c.CustomdomainsEnabled
77+ return true
78 }
79
80 func (c *ConfigSite) HomeURL() string {
1@@ -111,7 +111,7 @@ func findRouteConfig(r *http.Request, routes []Route, subdomainRoutes []Route, c
2
3 if cfg.IsCustomdomains() || cfg.IsSubdomains() {
4 hostDomain := strings.ToLower(strings.Split(r.Host, ":")[0])
5- appDomain := strings.ToLower(strings.Split(cfg.ConfigCms.Domain, ":")[0])
6+ appDomain := strings.ToLower(strings.Split(cfg.Domain, ":")[0])
7
8 if hostDomain != appDomain {
9 if strings.Contains(hostDomain, appDomain) {
R wish/cms/ui/account/create.go =>
tui/account/create.go
+7,
-12
1@@ -9,8 +9,7 @@ import (
2 input "github.com/charmbracelet/bubbles/textinput"
3 tea "github.com/charmbracelet/bubbletea"
4 "github.com/picosh/pico/db"
5- "github.com/picosh/pico/wish/cms/config"
6- "github.com/picosh/pico/wish/cms/ui/common"
7+ "github.com/picosh/pico/tui/common"
8 )
9
10 type state int
11@@ -49,7 +48,6 @@ type CreateModel struct {
12 Done bool // true when it's time to exit this view
13 Quit bool // true when the user wants to quit the whole program
14
15- cfg *config.ConfigCms
16 dbpool db.DB
17 publicKey string
18 styles common.Styles
19@@ -94,7 +92,7 @@ func (m *CreateModel) indexBackward() {
20 }
21
22 // NewModel returns a new username model in its initial state.
23-func NewCreateModel(styles common.Styles, cfg *config.ConfigCms, dbpool db.DB, publicKey string) CreateModel {
24+func NewCreateModel(styles common.Styles, dbpool db.DB, publicKey string) CreateModel {
25 im := input.New()
26 im.Cursor.Style = styles.Cursor
27 im.Placeholder = "enter username"
28@@ -103,7 +101,6 @@ func NewCreateModel(styles common.Styles, cfg *config.ConfigCms, dbpool db.DB, p
29 im.Focus()
30
31 return CreateModel{
32- cfg: cfg,
33 Done: false,
34 Quit: false,
35 dbpool: dbpool,
36@@ -119,9 +116,9 @@ func NewCreateModel(styles common.Styles, cfg *config.ConfigCms, dbpool db.DB, p
37 }
38
39 // Init is the Bubble Tea initialization function.
40-func Init(styles common.Styles, cfg *config.ConfigCms, dbpool db.DB, publicKey string) func() (CreateModel, tea.Cmd) {
41+func Init(styles common.Styles, dbpool db.DB, publicKey string) func() (CreateModel, tea.Cmd) {
42 return func() (CreateModel, tea.Cmd) {
43- m := NewCreateModel(styles, cfg, dbpool, publicKey)
44+ m := NewCreateModel(styles, dbpool, publicKey)
45 return m, InitialCmd()
46 }
47 }
48@@ -237,11 +234,9 @@ func Update(msg tea.Msg, m CreateModel) (CreateModel, tea.Cmd) {
49
50 // View renders current view from the model.
51 func View(m CreateModel) string {
52- if !m.cfg.AllowRegister {
53- return "Registration is closed for this service. Press 'esc' to exit."
54- }
55-
56- s := fmt.Sprintf("%s\n\n%s\n", "hacker labs", m.cfg.IntroText)
57+ intro := "To create an account, enter a username.\n"
58+ intro += "After that, go to https://pico.sh/getting-started#next-steps"
59+ s := fmt.Sprintf("%s\n\n%s\n", "hacker labs", intro)
60 s += fmt.Sprintf("Public Key: %s\n\n", m.publicKey)
61 s += m.input.View() + "\n\n"
62
R pgs/cms.go =>
tui/cms.go
+19,
-32
1@@ -1,4 +1,4 @@
2-package pgs
3+package tui
4
5 import (
6 "errors"
7@@ -15,13 +15,11 @@ import (
8 "github.com/picosh/pico/db"
9 "github.com/picosh/pico/db/postgres"
10 "github.com/picosh/pico/shared"
11- "github.com/picosh/pico/shared/storage"
12- "github.com/picosh/pico/wish/cms/config"
13- "github.com/picosh/pico/wish/cms/ui/account"
14- "github.com/picosh/pico/wish/cms/ui/common"
15- "github.com/picosh/pico/wish/cms/ui/info"
16- "github.com/picosh/pico/wish/cms/ui/keys"
17- "github.com/picosh/pico/wish/cms/ui/tokens"
18+ "github.com/picosh/pico/tui/account"
19+ "github.com/picosh/pico/tui/common"
20+ "github.com/picosh/pico/tui/info"
21+ "github.com/picosh/pico/tui/keys"
22+ "github.com/picosh/pico/tui/tokens"
23 )
24
25 type status int
26@@ -72,7 +70,7 @@ func NewSpinner(styles common.Styles) spinner.Model {
27
28 type GotDBMsg db.DB
29
30-func CmsMiddleware(cfg *config.ConfigCms, urls config.ConfigURL) bm.Handler {
31+func CmsMiddleware(cfg *shared.ConfigSite) bm.Handler {
32 return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
33 logger := cfg.Logger
34
35@@ -91,13 +89,6 @@ func CmsMiddleware(cfg *config.ConfigCms, urls config.ConfigURL) bm.Handler {
36
37 dbpool := postgres.NewDB(cfg.DbURL, cfg.Logger)
38
39- var st storage.StorageServe
40- if cfg.MinioURL == "" {
41- st, err = storage.NewStorageFS(cfg.StorageDir)
42- } else {
43- st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
44- }
45-
46 if err != nil {
47 logger.Error(err.Error())
48 }
49@@ -108,10 +99,8 @@ func CmsMiddleware(cfg *config.ConfigCms, urls config.ConfigURL) bm.Handler {
50
51 m := model{
52 cfg: cfg,
53- urls: urls,
54 publicKey: key,
55 dbpool: dbpool,
56- st: st,
57 sshUser: sshUser,
58 status: statusInit,
59 menuChoice: unsetChoice,
60@@ -139,11 +128,9 @@ func CmsMiddleware(cfg *config.ConfigCms, urls config.ConfigURL) bm.Handler {
61
62 // Just a generic tea.Model to demo terminal information of ssh.
63 type model struct {
64- cfg *config.ConfigCms
65- urls config.ConfigURL
66+ cfg *shared.ConfigSite
67 publicKey string
68 dbpool db.DB
69- st storage.StorageServe
70 user *db.User
71 plusFeatureFlag *db.FeatureFlag
72 err error
73@@ -247,18 +234,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
74 m.status = statusReady
75 m.info.User = msg
76 m.user = msg
77- m.info = info.NewModel(m.styles, m.cfg, m.urls, m.user, m.plusFeatureFlag)
78- m.keys = keys.NewModel(m.styles, m.cfg, m.dbpool, m.user)
79- m.tokens = tokens.NewModel(m.styles, m.cfg, m.dbpool, m.user)
80- m.createAccount = account.NewCreateModel(m.styles, m.cfg, m.dbpool, m.publicKey)
81+ m.info = info.NewModel(m.styles, m.user, m.plusFeatureFlag)
82+ m.keys = keys.NewModel(m.styles, m.cfg.Logger, m.dbpool, m.user)
83+ m.tokens = tokens.NewModel(m.styles, m.dbpool, m.user)
84+ m.createAccount = account.NewCreateModel(m.styles, m.dbpool, m.publicKey)
85 }
86
87 switch m.status {
88 case statusInit:
89- m.info = info.NewModel(m.styles, m.cfg, m.urls, m.user, m.plusFeatureFlag)
90- m.keys = keys.NewModel(m.styles, m.cfg, m.dbpool, m.user)
91- m.tokens = tokens.NewModel(m.styles, m.cfg, m.dbpool, m.user)
92- m.createAccount = account.NewCreateModel(m.styles, m.cfg, m.dbpool, m.publicKey)
93+ m.info = info.NewModel(m.styles, m.user, m.plusFeatureFlag)
94+ m.keys = keys.NewModel(m.styles, m.cfg.Logger, m.dbpool, m.user)
95+ m.tokens = tokens.NewModel(m.styles, m.dbpool, m.user)
96+ m.createAccount = account.NewCreateModel(m.styles, m.dbpool, m.publicKey)
97 if m.user == nil {
98 m.status = statusNoAccount
99 } else {
100@@ -288,7 +275,7 @@ func updateChildren(msg tea.Msg, m model) (model, tea.Cmd) {
101 cmd = newCmd
102
103 if m.keys.Exit {
104- m.keys = keys.NewModel(m.styles, m.cfg, m.dbpool, m.user)
105+ m.keys = keys.NewModel(m.styles, m.cfg.Logger, m.dbpool, m.user)
106 m.status = statusReady
107 } else if m.keys.Quit {
108 m.status = statusQuitting
109@@ -304,7 +291,7 @@ func updateChildren(msg tea.Msg, m model) (model, tea.Cmd) {
110 cmd = newCmd
111
112 if m.tokens.Exit {
113- m.tokens = tokens.NewModel(m.styles, m.cfg, m.dbpool, m.user)
114+ m.tokens = tokens.NewModel(m.styles, m.dbpool, m.user)
115 m.status = statusReady
116 } else if m.tokens.Quit {
117 m.status = statusQuitting
118@@ -313,7 +300,7 @@ func updateChildren(msg tea.Msg, m model) (model, tea.Cmd) {
119 case statusNoAccount:
120 m.createAccount, cmd = account.Update(msg, m.createAccount)
121 if m.createAccount.Done {
122- m.createAccount = account.NewCreateModel(m.styles, m.cfg, m.dbpool, m.publicKey) // reset the state
123+ m.createAccount = account.NewCreateModel(m.styles, m.dbpool, m.publicKey) // reset the state
124 m.status = statusReady
125 } else if m.createAccount.Quit {
126 m.status = statusQuitting
R wish/cms/ui/common/styles.go =>
tui/common/styles.go
+8,
-2
1@@ -109,7 +109,8 @@ type Styles struct {
2 CliPadding,
3 CliBorder,
4 CliHeader,
5- App lipgloss.Style
6+ App,
7+ RoundedBorder lipgloss.Style
8 Renderer *lipgloss.Renderer
9 }
10
11@@ -152,7 +153,7 @@ func DefaultStyles(renderer *lipgloss.Renderer) Styles {
12 s.SelectedMenuItem = renderer.NewStyle().Foreground(Fuschia)
13 s.Logo = renderer.NewStyle().
14 Foreground(Cream).
15- Background(lipgloss.Color("#5A56E0")).
16+ Background(Indigo).
17 Padding(0, 1)
18 s.BlurredButtonStyle = renderer.NewStyle().
19 Foreground(Cream).
20@@ -172,6 +173,11 @@ func DefaultStyles(renderer *lipgloss.Renderer) Styles {
21 s.CliPadding = renderer.NewStyle().Padding(0, 1)
22 s.CliHeader = s.CliPadding.Copy().Foreground(Indigo).Bold(true)
23 s.CliBorder = renderer.NewStyle().Foreground(lipgloss.Color("238"))
24+ s.RoundedBorder = renderer.
25+ NewStyle().
26+ Padding(0, 1).
27+ BorderForeground(Indigo).
28+ Border(lipgloss.RoundedBorder(), true, true)
29
30 return s
31 }
R wish/cms/ui/common/views.go =>
tui/common/views.go
+0,
-0
R wish/cms/ui/createkey/create.go =>
tui/createkey/create.go
+2,
-3
1@@ -8,8 +8,7 @@ import (
2 input "github.com/charmbracelet/bubbles/textinput"
3 tea "github.com/charmbracelet/bubbletea"
4 "github.com/picosh/pico/db"
5- "github.com/picosh/pico/wish/cms/config"
6- "github.com/picosh/pico/wish/cms/ui/common"
7+ "github.com/picosh/pico/tui/common"
8 "golang.org/x/crypto/ssh"
9 )
10
11@@ -87,7 +86,7 @@ func (m *Model) indexBackward() {
12 }
13
14 // NewModel returns a new username model in its initial state.
15-func NewModel(styles common.Styles, cfg *config.ConfigCms, dbpool db.DB, user *db.User) Model {
16+func NewModel(styles common.Styles, dbpool db.DB, user *db.User) Model {
17 im := input.New()
18 im.Cursor.Style = styles.Cursor
19 im.Placeholder = "ssh-ed25519 AAAA..."
R wish/cms/ui/createtoken/create.go =>
tui/createtoken/create.go
+2,
-3
1@@ -8,8 +8,7 @@ import (
2 input "github.com/charmbracelet/bubbles/textinput"
3 tea "github.com/charmbracelet/bubbletea"
4 "github.com/picosh/pico/db"
5- "github.com/picosh/pico/wish/cms/config"
6- "github.com/picosh/pico/wish/cms/ui/common"
7+ "github.com/picosh/pico/tui/common"
8 )
9
10 type state int
11@@ -89,7 +88,7 @@ func (m *Model) indexBackward() {
12 }
13
14 // NewModel returns a new username model in its initial state.
15-func NewModel(styles common.Styles, cfg *config.ConfigCms, dbpool db.DB, user *db.User) Model {
16+func NewModel(styles common.Styles, dbpool db.DB, user *db.User) Model {
17 im := input.New()
18 im.Cursor.Style = styles.Cursor
19 im.Placeholder = "A name used for your reference"
R wish/cms/ui/info/info.go =>
tui/info/info.go
+2,
-7
1@@ -3,8 +3,7 @@ package info
2 import (
3 tea "github.com/charmbracelet/bubbletea"
4 "github.com/picosh/pico/db"
5- "github.com/picosh/pico/wish/cms/config"
6- "github.com/picosh/pico/wish/cms/ui/common"
7+ "github.com/picosh/pico/tui/common"
8 )
9
10 type errMsg struct {
11@@ -18,8 +17,6 @@ func (e errMsg) Error() string {
12
13 // Model stores the state of the info user interface.
14 type Model struct {
15- cfg *config.ConfigCms
16- urls config.ConfigURL
17 Quit bool // signals it's time to exit the whole application
18 Err error
19 User *db.User
20@@ -28,13 +25,11 @@ type Model struct {
21 }
22
23 // NewModel returns a new Model in its initial state.
24-func NewModel(styles common.Styles, cfg *config.ConfigCms, urls config.ConfigURL, user *db.User, ff *db.FeatureFlag) Model {
25+func NewModel(styles common.Styles, user *db.User, ff *db.FeatureFlag) Model {
26 return Model{
27 Quit: false,
28 User: user,
29 styles: styles,
30- cfg: cfg,
31- urls: urls,
32 PlusFeatureFlag: ff,
33 }
34 }
R wish/cms/ui/keys/keys.go =>
tui/keys/keys.go
+11,
-12
1@@ -1,15 +1,14 @@
2 package keys
3
4 import (
5- "fmt"
6+ "log/slog"
7
8 pager "github.com/charmbracelet/bubbles/paginator"
9 "github.com/charmbracelet/bubbles/spinner"
10 tea "github.com/charmbracelet/bubbletea"
11 "github.com/picosh/pico/db"
12- "github.com/picosh/pico/wish/cms/config"
13- "github.com/picosh/pico/wish/cms/ui/common"
14- "github.com/picosh/pico/wish/cms/ui/createkey"
15+ "github.com/picosh/pico/tui/common"
16+ "github.com/picosh/pico/tui/createkey"
17 )
18
19 const keysPerPage = 4
20@@ -47,7 +46,7 @@ type (
21
22 // Model is the Tea state model for this user interface.
23 type Model struct {
24- cfg *config.ConfigCms
25+ logger *slog.Logger
26 dbpool db.DB
27 user *db.User
28 styles common.Styles
29@@ -82,14 +81,14 @@ func (m *Model) UpdatePaging(msg tea.Msg) {
30 }
31
32 // NewModel creates a new model with defaults.
33-func NewModel(styles common.Styles, cfg *config.ConfigCms, dbpool db.DB, user *db.User) Model {
34+func NewModel(styles common.Styles, logger *slog.Logger, dbpool db.DB, user *db.User) Model {
35 p := pager.New()
36 p.PerPage = keysPerPage
37 p.Type = pager.Dots
38 p.InactiveDot = styles.InactivePagination.Render("•")
39
40 return Model{
41- cfg: cfg,
42+ logger: logger,
43 dbpool: dbpool,
44 user: user,
45 styles: styles,
46@@ -236,7 +235,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
47
48 switch m.state {
49 case stateNormal:
50- m.createKey = createkey.NewModel(m.styles, m.cfg, m.dbpool, m.user)
51+ m.createKey = createkey.NewModel(m.styles, m.dbpool, m.user)
52 case stateDeletingKey:
53 // If an item is being confirmed for delete, any key (other than the key
54 // used for confirmation above) cancels the deletion
55@@ -269,7 +268,7 @@ func updateChildren(msg tea.Msg, m Model) (Model, tea.Cmd) {
56 m.createKey = createKeyModel
57 cmd = newCmd
58 if m.createKey.Done {
59- m.createKey = createkey.NewModel(m.styles, m.cfg, m.dbpool, m.user) // reset the state
60+ m.createKey = createkey.NewModel(m.styles, m.dbpool, m.user) // reset the state
61 m.state = stateNormal
62 } else if m.createKey.Quit {
63 m.state = stateQuitting
64@@ -293,11 +292,11 @@ func (m Model) View() string {
65 case stateLoading:
66 s = m.spinner.View() + " Loading...\n\n"
67 case stateQuitting:
68- s = fmt.Sprintf("Thanks for using %s!\n", m.cfg.Domain)
69+ s = "Thanks for using pico.sh!\n"
70 case stateCreateKey:
71 s = m.createKey.View()
72 default:
73- s = fmt.Sprintf("Here are the keys linked to your %s account.\n\n", m.cfg.Domain)
74+ s = "Here are the keys linked to your pico.sh account.\n\n"
75
76 // Keys
77 s += keysView(m)
78@@ -409,7 +408,7 @@ func unlinkKey(m Model) tea.Cmd {
79 func deleteAccount(m Model) tea.Cmd {
80 return func() tea.Msg {
81 id := m.keys[m.getSelectedIndex()].UserID
82- m.cfg.Logger.Info("user requested account deletion", "user", m.user.Name, "id", id)
83+ m.logger.Info("user requested account deletion", "user", m.user.Name, "id", id)
84 err := m.dbpool.RemoveUsers([]string{id})
85 if err != nil {
86 return errMsg{err}
R wish/cms/ui/keys/keyview.go =>
tui/keys/keyview.go
+1,
-1
1@@ -5,7 +5,7 @@ import (
2 "strings"
3
4 "github.com/picosh/pico/db"
5- "github.com/picosh/pico/wish/cms/ui/common"
6+ "github.com/picosh/pico/tui/common"
7 "golang.org/x/crypto/ssh"
8 )
9
R wish/cms/ui/tokens/tokens.go =>
tui/tokens/tokens.go
+6,
-11
1@@ -1,15 +1,12 @@
2 package tokens
3
4 import (
5- "fmt"
6-
7 pager "github.com/charmbracelet/bubbles/paginator"
8 "github.com/charmbracelet/bubbles/spinner"
9 tea "github.com/charmbracelet/bubbletea"
10 "github.com/picosh/pico/db"
11- "github.com/picosh/pico/wish/cms/config"
12- "github.com/picosh/pico/wish/cms/ui/common"
13- "github.com/picosh/pico/wish/cms/ui/createtoken"
14+ "github.com/picosh/pico/tui/common"
15+ "github.com/picosh/pico/tui/createtoken"
16 )
17
18 const keysPerPage = 4
19@@ -45,7 +42,6 @@ type (
20
21 // Model is the Tea state model for this user interface.
22 type Model struct {
23- cfg *config.ConfigCms
24 dbpool db.DB
25 user *db.User
26 styles common.Styles
27@@ -80,14 +76,13 @@ func (m *Model) UpdatePaging(msg tea.Msg) {
28 }
29
30 // NewModel creates a new model with defaults.
31-func NewModel(styles common.Styles, cfg *config.ConfigCms, dbpool db.DB, user *db.User) Model {
32+func NewModel(styles common.Styles, dbpool db.DB, user *db.User) Model {
33 p := pager.New()
34 p.PerPage = keysPerPage
35 p.Type = pager.Dots
36 p.InactiveDot = styles.InactivePagination.Render("•")
37
38 return Model{
39- cfg: cfg,
40 dbpool: dbpool,
41 user: user,
42 styles: styles,
43@@ -207,7 +202,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
44
45 switch m.state {
46 case stateNormal:
47- m.createKey = createtoken.NewModel(m.styles, m.cfg, m.dbpool, m.user)
48+ m.createKey = createtoken.NewModel(m.styles, m.dbpool, m.user)
49 case stateDeletingKey:
50 // If an item is being confirmed for delete, any key (other than the key
51 // used for confirmation above) cancels the deletion
52@@ -240,7 +235,7 @@ func updateChildren(msg tea.Msg, m Model) (Model, tea.Cmd) {
53 m.createKey = createKeyModel
54 cmd = newCmd
55 if m.createKey.Done {
56- m.createKey = createtoken.NewModel(m.styles, m.cfg, m.dbpool, m.user) // reset the state
57+ m.createKey = createtoken.NewModel(m.styles, m.dbpool, m.user) // reset the state
58 m.state = stateNormal
59 } else if m.createKey.Quit {
60 m.state = stateQuitting
61@@ -264,7 +259,7 @@ func (m Model) View() string {
62 case stateLoading:
63 s = m.spinner.View() + " Loading...\n\n"
64 case stateQuitting:
65- s = fmt.Sprintf("Thanks for using %s!\n", m.cfg.Domain)
66+ s = "Thanks for using pico.sh!\n"
67 case stateCreateKey:
68 s = m.createKey.View()
69 default:
R wish/cms/ui/tokens/tokenview.go =>
tui/tokens/tokenview.go
+1,
-1
1@@ -4,7 +4,7 @@ import (
2 "fmt"
3
4 "github.com/picosh/pico/db"
5- "github.com/picosh/pico/wish/cms/ui/common"
6+ "github.com/picosh/pico/tui/common"
7 )
8
9 type styledKey struct {
+0,
-431
1@@ -1,431 +0,0 @@
2-package cms
3-
4-import (
5- "errors"
6- "fmt"
7- "math"
8-
9- "github.com/charmbracelet/bubbles/spinner"
10- tea "github.com/charmbracelet/bubbletea"
11- "github.com/charmbracelet/lipgloss"
12- "github.com/charmbracelet/ssh"
13- bm "github.com/charmbracelet/wish/bubbletea"
14- "github.com/muesli/reflow/indent"
15- "github.com/muesli/reflow/wordwrap"
16- "github.com/muesli/reflow/wrap"
17- "github.com/picosh/pico/db"
18- "github.com/picosh/pico/db/postgres"
19- "github.com/picosh/pico/shared"
20- "github.com/picosh/pico/shared/storage"
21- "github.com/picosh/pico/wish/cms/config"
22- "github.com/picosh/pico/wish/cms/ui/account"
23- "github.com/picosh/pico/wish/cms/ui/common"
24- "github.com/picosh/pico/wish/cms/ui/info"
25- "github.com/picosh/pico/wish/cms/ui/keys"
26- "github.com/picosh/pico/wish/cms/ui/posts"
27- "github.com/picosh/pico/wish/cms/ui/tokens"
28-)
29-
30-type status int
31-
32-const (
33- statusInit status = iota
34- statusReady
35- statusNoAccount
36- statusBrowsingPosts
37- statusBrowsingKeys
38- statusBrowsingTokens
39- statusQuitting
40-)
41-
42-func (s status) String() string {
43- return [...]string{
44- "initializing",
45- "ready",
46- "browsing posts",
47- "browsing keys",
48- "quitting",
49- "error",
50- }[s]
51-}
52-
53-// menuChoice represents a chosen menu item.
54-type menuChoice int
55-
56-// menu choices.
57-const (
58- postsChoice menuChoice = iota
59- keysChoice
60- tokensChoice
61- exitChoice
62- unsetChoice // set when no choice has been made
63-)
64-
65-// menu text corresponding to menu choices. these are presented to the user.
66-var menuChoices = map[menuChoice]string{
67- keysChoice: "Manage keys",
68- tokensChoice: "Manage tokens",
69- postsChoice: "Manage posts",
70- exitChoice: "Exit",
71-}
72-
73-func NewSpinner(styles common.Styles) spinner.Model {
74- s := spinner.New()
75- s.Spinner = spinner.Dot
76- s.Style = styles.Spinner
77- return s
78-}
79-
80-type GotDBMsg db.DB
81-
82-func Middleware(cfg *config.ConfigCms, urls config.ConfigURL) bm.Handler {
83- return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
84- logger := cfg.Logger
85-
86- _, _, active := s.Pty()
87- if !active {
88- logger.Info("no active terminal, skipping")
89- return nil, nil
90- }
91- key, err := shared.KeyText(s)
92- if err != nil {
93- logger.Error(err.Error())
94- }
95-
96- sshUser := s.User()
97-
98- dbpool := postgres.NewDB(cfg.DbURL, cfg.Logger)
99-
100- var st storage.StorageServe
101- if cfg.MinioURL == "" {
102- st, err = storage.NewStorageFS(cfg.StorageDir)
103- } else {
104- st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
105- }
106-
107- if err != nil {
108- logger.Error(err.Error())
109- }
110-
111- renderer := lipgloss.NewRenderer(s)
112- renderer.SetOutput(common.OutputFromSession(s))
113- styles := common.DefaultStyles(renderer)
114-
115- m := model{
116- cfg: cfg,
117- urls: urls,
118- publicKey: key,
119- dbpool: dbpool,
120- st: st,
121- sshUser: sshUser,
122- status: statusInit,
123- menuChoice: unsetChoice,
124- styles: styles,
125- spinner: common.NewSpinner(styles),
126- terminalSize: tea.WindowSizeMsg{
127- Width: 80,
128- Height: 24,
129- },
130- }
131-
132- user, err := m.findUser()
133- if err != nil {
134- _, _ = fmt.Fprintln(s.Stderr(), err)
135- return nil, nil
136- }
137- m.user = user
138-
139- ff, _ := m.findPlusFeatureFlag()
140- m.plusFeatureFlag = ff
141-
142- return m, []tea.ProgramOption{tea.WithAltScreen()}
143- }
144-}
145-
146-// Just a generic tea.Model to demo terminal information of ssh.
147-type model struct {
148- cfg *config.ConfigCms
149- urls config.ConfigURL
150- publicKey string
151- dbpool db.DB
152- st storage.StorageServe
153- user *db.User
154- plusFeatureFlag *db.FeatureFlag
155- err error
156- sshUser string
157- status status
158- menuIndex int
159- menuChoice menuChoice
160- terminalSize tea.WindowSizeMsg
161- styles common.Styles
162- info info.Model
163- spinner spinner.Model
164- posts posts.Model
165- keys keys.Model
166- tokens tokens.Model
167- createAccount account.CreateModel
168-}
169-
170-func (m model) Init() tea.Cmd {
171- return m.spinner.Tick
172-}
173-
174-func (m model) findUser() (*db.User, error) {
175- logger := m.cfg.Logger
176- var user *db.User
177-
178- if m.sshUser == "new" {
179- logger.Info("user requesting to register account")
180- return nil, nil
181- }
182-
183- user, err := m.dbpool.FindUserForKey(m.sshUser, m.publicKey)
184-
185- if err != nil {
186- logger.Error(err.Error())
187- // we only want to throw an error for specific cases
188- if errors.Is(err, &db.ErrMultiplePublicKeys{}) {
189- return nil, err
190- }
191- return nil, nil
192- }
193-
194- return user, nil
195-}
196-
197-func (m model) findPlusFeatureFlag() (*db.FeatureFlag, error) {
198- if m.user == nil {
199- return nil, nil
200- }
201-
202- ff, err := m.dbpool.FindFeatureForUser(m.user.ID, "pgs")
203- if err != nil {
204- return nil, err
205- }
206-
207- return ff, nil
208-}
209-
210-func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
211- var (
212- cmds []tea.Cmd
213- cmd tea.Cmd
214- )
215-
216- switch msg := msg.(type) {
217- case tea.WindowSizeMsg:
218- m.terminalSize = msg
219- case tea.KeyMsg:
220- switch msg.Type {
221- case tea.KeyCtrlC:
222- m.dbpool.Close()
223- return m, tea.Quit
224- }
225-
226- if m.status == statusReady { // Process keys for the menu
227- switch msg.String() {
228- // Quit
229- case "q", "esc":
230- m.status = statusQuitting
231- m.dbpool.Close()
232- return m, tea.Quit
233-
234- // Prev menu item
235- case "up", "k":
236- m.menuIndex--
237- if m.menuIndex < 0 {
238- m.menuIndex = len(menuChoices) - 1
239- }
240-
241- // Select menu item
242- case "enter":
243- m.menuChoice = menuChoice(m.menuIndex)
244-
245- // Next menu item
246- case "down", "j":
247- m.menuIndex++
248- if m.menuIndex >= len(menuChoices) {
249- m.menuIndex = 0
250- }
251- }
252- }
253-
254- case account.CreateAccountMsg:
255- m.status = statusReady
256- m.info.User = msg
257- m.user = msg
258- m.info = info.NewModel(m.styles, m.cfg, m.urls, m.user, m.plusFeatureFlag)
259- m.keys = keys.NewModel(m.styles, m.cfg, m.dbpool, m.user)
260- m.tokens = tokens.NewModel(m.styles, m.cfg, m.dbpool, m.user)
261- m.createAccount = account.NewCreateModel(m.styles, m.cfg, m.dbpool, m.publicKey)
262-
263- perPage := math.Floor(float64(m.terminalSize.Height) / 10.0)
264- m.posts = posts.NewModel(m.styles, m.cfg, m.urls, m.dbpool, m.user, m.st, int(perPage))
265- }
266-
267- switch m.status {
268- case statusInit:
269- m.info = info.NewModel(m.styles, m.cfg, m.urls, m.user, m.plusFeatureFlag)
270- m.keys = keys.NewModel(m.styles, m.cfg, m.dbpool, m.user)
271- m.tokens = tokens.NewModel(m.styles, m.cfg, m.dbpool, m.user)
272- m.createAccount = account.NewCreateModel(m.styles, m.cfg, m.dbpool, m.publicKey)
273- if m.user == nil {
274- m.status = statusNoAccount
275- } else {
276- m.status = statusReady
277- }
278-
279- perPage := math.Floor(float64(m.terminalSize.Height) / 10.0)
280- m.posts = posts.NewModel(m.styles, m.cfg, m.urls, m.dbpool, m.user, m.st, int(perPage))
281- }
282-
283- m, cmd = updateChildren(msg, m)
284- if cmd != nil {
285- cmds = append(cmds, cmd)
286- }
287-
288- return m, tea.Batch(cmds...)
289-}
290-
291-func updateChildren(msg tea.Msg, m model) (model, tea.Cmd) {
292- var cmd tea.Cmd
293-
294- switch m.status {
295- case statusBrowsingPosts:
296- newModel, newCmd := m.posts.Update(msg)
297- postsModel, ok := newModel.(posts.Model)
298- if !ok {
299- panic("could not perform assertion on posts model")
300- }
301- m.posts = postsModel
302- cmd = newCmd
303-
304- if m.posts.Exit {
305- perPage := math.Floor(float64(m.terminalSize.Height) / 10.0)
306- m.posts = posts.NewModel(m.styles, m.cfg, m.urls, m.dbpool, m.user, m.st, int(perPage))
307-
308- m.posts = posts.NewModel(m.styles, m.cfg, m.urls, m.dbpool, m.user, m.st, int(perPage))
309- m.status = statusReady
310- } else if m.posts.Quit {
311- m.status = statusQuitting
312- return m, tea.Quit
313- }
314- case statusBrowsingKeys:
315- newModel, newCmd := m.keys.Update(msg)
316- keysModel, ok := newModel.(keys.Model)
317- if !ok {
318- panic("could not perform assertion on posts model")
319- }
320- m.keys = keysModel
321- cmd = newCmd
322-
323- if m.keys.Exit {
324- m.keys = keys.NewModel(m.styles, m.cfg, m.dbpool, m.user)
325- m.status = statusReady
326- } else if m.keys.Quit {
327- m.status = statusQuitting
328- return m, tea.Quit
329- }
330- case statusBrowsingTokens:
331- newModel, newCmd := m.tokens.Update(msg)
332- tokensModel, ok := newModel.(tokens.Model)
333- if !ok {
334- panic("could not perform assertion on posts model")
335- }
336- m.tokens = tokensModel
337- cmd = newCmd
338-
339- if m.tokens.Exit {
340- m.tokens = tokens.NewModel(m.styles, m.cfg, m.dbpool, m.user)
341- m.status = statusReady
342- } else if m.tokens.Quit {
343- m.status = statusQuitting
344- return m, tea.Quit
345- }
346- case statusNoAccount:
347- m.createAccount, cmd = account.Update(msg, m.createAccount)
348- if m.createAccount.Done {
349- m.createAccount = account.NewCreateModel(m.styles, m.cfg, m.dbpool, m.publicKey) // reset the state
350- m.status = statusReady
351- } else if m.createAccount.Quit {
352- m.status = statusQuitting
353- return m, tea.Quit
354- }
355- }
356-
357- // Handle the menu
358- switch m.menuChoice {
359- case postsChoice:
360- m.status = statusBrowsingPosts
361- m.menuChoice = unsetChoice
362- cmd = posts.LoadPosts(m.posts)
363- case keysChoice:
364- m.status = statusBrowsingKeys
365- m.menuChoice = unsetChoice
366- cmd = keys.LoadKeys(m.keys)
367- case tokensChoice:
368- m.status = statusBrowsingTokens
369- m.menuChoice = unsetChoice
370- cmd = tokens.LoadKeys(m.tokens)
371- case exitChoice:
372- m.status = statusQuitting
373- m.dbpool.Close()
374- cmd = tea.Quit
375- }
376-
377- return m, cmd
378-}
379-
380-func (m model) menuView() string {
381- var s string
382- for i := 0; i < len(menuChoices); i++ {
383- e := " "
384- menuItem := menuChoices[menuChoice(i)]
385- if i == m.menuIndex {
386- e = m.styles.SelectionMarker.String() +
387- m.styles.SelectedMenuItem.Render(menuItem)
388- } else {
389- e += menuItem
390- }
391- if i < len(menuChoices)-1 {
392- e += "\n"
393- }
394- s += e
395- }
396-
397- return s
398-}
399-
400-func footerView(m model) string {
401- if m.err != nil {
402- return m.errorView(m.err)
403- }
404- return "\n\n" + common.HelpView(m.styles, "j/k, ↑/↓: choose", "enter: select")
405-}
406-
407-func (m model) errorView(err error) string {
408- head := m.styles.Error.Render("Error: ")
409- body := m.styles.Subtle.Render(err.Error())
410- msg := m.styles.Wrap.Render(head + body)
411- return "\n\n" + indent.String(msg, 2)
412-}
413-
414-func (m model) View() string {
415- w := m.terminalSize.Width - m.styles.App.GetHorizontalFrameSize()
416- s := m.styles.Logo.SetString(m.cfg.Domain).String() + "\n\n"
417- switch m.status {
418- case statusNoAccount:
419- s += account.View(m.createAccount)
420- case statusReady:
421- s += m.info.View()
422- s += "\n\n" + m.menuView()
423- s += footerView(m)
424- case statusBrowsingPosts:
425- s += m.posts.View()
426- case statusBrowsingKeys:
427- s += m.keys.View()
428- case statusBrowsingTokens:
429- s += m.tokens.View()
430- }
431- return m.styles.App.Render(wrap.String(wordwrap.String(s, w), w))
432-}
+0,
-33
1@@ -1,33 +0,0 @@
2-package config
3-
4-import "log/slog"
5-
6-type ConfigURL interface {
7- BlogURL(username string) string
8- PostURL(username string, filename string) string
9-}
10-
11-type ConfigCms struct {
12- Domain string
13- Port string
14- Email string
15- Protocol string
16- DbURL string
17- StorageDir string
18- MinioURL string
19- MinioUser string
20- MinioPass string
21- Description string
22- IntroText string
23- Space string
24- AllowedExt []string
25- HiddenPosts []string
26- Logger *slog.Logger
27- AllowRegister bool
28- MaxSize uint64
29- MaxAssetSize int64
30-}
31-
32-func NewConfigCms() *ConfigCms {
33- return &ConfigCms{}
34-}
+0,
-97
1@@ -1,97 +0,0 @@
2-package posts
3-
4-import (
5- "fmt"
6-
7- "github.com/picosh/pico/db"
8- "github.com/picosh/pico/wish/cms/config"
9- "github.com/picosh/pico/wish/cms/ui/common"
10-)
11-
12-type styledKey struct {
13- styles common.Styles
14- date string
15- gutter string
16- postLabel string
17- dateLabel string
18- dateVal string
19- title string
20- urlLabel string
21- url string
22- views int
23- viewsLabel string
24- model Model
25- expiresAtLabel string
26- expiresAt string
27-}
28-
29-func (m Model) newStyledKey(styles common.Styles, post *db.Post, urls config.ConfigURL) styledKey {
30- publishAt := post.PublishAt
31-
32- expiresAt := styles.LabelDim.Render("never")
33- if post.ExpiresAt != nil {
34- expiresAt = styles.LabelDim.Render(post.ExpiresAt.Format("02 Jan, 2006"))
35- }
36-
37- // Default state
38- return styledKey{
39- styles: styles,
40- gutter: " ",
41- postLabel: "post:",
42- date: publishAt.String(),
43- dateLabel: "publish_at:",
44- dateVal: styles.LabelDim.Render(publishAt.Format("02 Jan, 2006")),
45- title: post.Title,
46- urlLabel: "url:",
47- url: urls.PostURL(post.Username, post.Slug),
48- viewsLabel: "views:",
49- views: post.Views,
50- model: m,
51- expiresAtLabel: "expires_at:",
52- expiresAt: expiresAt,
53- }
54-}
55-
56-// Selected state.
57-func (k *styledKey) selected() {
58- k.gutter = common.VerticalLine(k.styles.Renderer, common.StateSelected)
59- k.postLabel = k.styles.Label.Render("post:")
60- k.dateLabel = k.styles.Label.Render("publish_at:")
61- k.viewsLabel = k.styles.Label.Render("views:")
62- k.urlLabel = k.styles.Label.Render("url:")
63- k.expiresAtLabel = k.styles.Label.Render("expires_at:")
64-}
65-
66-// Deleting state.
67-func (k *styledKey) deleting() {
68- k.gutter = common.VerticalLine(k.styles.Renderer, common.StateDeleting)
69- k.postLabel = k.styles.Delete.Render("post:")
70- k.dateLabel = k.styles.Delete.Render("publish_at:")
71- k.urlLabel = k.styles.Delete.Render("url:")
72- k.viewsLabel = k.styles.Delete.Render("views:")
73- k.title = k.styles.DeleteDim.Render(k.title)
74- k.expiresAtLabel = k.styles.Delete.Render("expires_at:")
75-}
76-
77-func (k styledKey) render(state postState) string {
78- switch state {
79- case postSelected:
80- k.selected()
81- case postDeleting:
82- k.deleting()
83- }
84-
85- mainBody := fmt.Sprintf(
86- "%s %s %s\n%s %s %s\n%s %s %d\n%s %s %s\n",
87- k.gutter, k.postLabel, k.title,
88- k.gutter, k.dateLabel, k.dateVal,
89- k.gutter, k.viewsLabel, k.views,
90- k.gutter, k.urlLabel, k.url,
91- )
92-
93- if k.model.cfg.Space == "pastes" {
94- mainBody += fmt.Sprintf("%s %s %s\n", k.gutter, k.expiresAtLabel, k.expiresAt)
95- }
96-
97- return mainBody + "\n"
98-}
+0,
-369
1@@ -1,369 +0,0 @@
2-package posts
3-
4-import (
5- "errors"
6- "log/slog"
7-
8- pager "github.com/charmbracelet/bubbles/paginator"
9- "github.com/charmbracelet/bubbles/spinner"
10- tea "github.com/charmbracelet/bubbletea"
11-
12- "github.com/picosh/pico/db"
13- "github.com/picosh/pico/shared/storage"
14- "github.com/picosh/pico/wish/cms/config"
15- "github.com/picosh/pico/wish/cms/ui/common"
16-)
17-
18-const keysPerPage = 1
19-
20-type state int
21-
22-const (
23- stateLoading state = iota
24- stateNormal
25- stateDeletingPost
26- stateQuitting
27-)
28-
29-type postState int
30-
31-const (
32- postNormal postState = iota
33- postSelected
34- postDeleting
35-)
36-
37-type PostLoader struct {
38- Posts []*db.Post
39-}
40-
41-type (
42- postsLoadedMsg PostLoader
43- removePostMsg int
44- errMsg struct {
45- err error
46- }
47-)
48-
49-// Model is the Tea state model for this user interface.
50-type Model struct {
51- cfg *config.ConfigCms
52- urls config.ConfigURL
53- dbpool db.DB
54- st storage.StorageServe
55- user *db.User
56- posts []*db.Post
57- styles common.Styles
58- pager pager.Model
59- state state
60- err error
61- index int // index of selected key in relation to the current page
62- Exit bool
63- Quit bool
64- spinner spinner.Model
65- logger *slog.Logger
66-}
67-
68-// getSelectedIndex returns the index of the cursor in relation to the total
69-// number of items.
70-func (m *Model) getSelectedIndex() int {
71- return m.index + m.pager.Page*m.pager.PerPage
72-}
73-
74-// UpdatePaging runs an update against the underlying pagination model as well
75-// as performing some related tasks on this model.
76-func (m *Model) UpdatePaging(msg tea.Msg) {
77- // Handle paging
78- m.pager.SetTotalPages(len(m.posts))
79- m.pager, _ = m.pager.Update(msg)
80-
81- // If selected item is out of bounds, put it in bounds
82- numItems := m.pager.ItemsOnPage(len(m.posts))
83- m.index = min(m.index, numItems-1)
84-}
85-
86-// NewModel creates a new model with defaults.
87-func NewModel(styles common.Styles, cfg *config.ConfigCms, urls config.ConfigURL, dbpool db.DB, user *db.User, stor storage.StorageServe, perPage int) Model {
88- logger := cfg.Logger
89-
90- p := pager.New()
91- p.PerPage = keysPerPage
92- p.Type = pager.Dots
93- p.InactiveDot = styles.InactivePagination.Render("•")
94-
95- if perPage > 0 {
96- p.PerPage = perPage
97- }
98-
99- return Model{
100- cfg: cfg,
101- dbpool: dbpool,
102- st: stor,
103- user: user,
104- styles: styles,
105- pager: p,
106- state: stateLoading,
107- err: nil,
108- posts: []*db.Post{},
109- index: 0,
110- spinner: common.NewSpinner(styles),
111- Exit: false,
112- Quit: false,
113- logger: logger,
114- urls: urls,
115- }
116-}
117-
118-// Init is the Tea initialization function.
119-func (m Model) Init() tea.Cmd {
120- return tea.Batch(
121- m.spinner.Tick,
122- )
123-}
124-
125-// Update is the tea update function which handles incoming messages.
126-func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
127- switch msg := msg.(type) {
128- case tea.KeyMsg:
129- switch msg.String() {
130- case "ctrl+c", "q", "esc":
131- m.Exit = true
132- return m, nil
133-
134- // Select individual items
135- case "up", "k":
136- // Move up
137- m.index--
138- if m.index < 0 && m.pager.Page > 0 {
139- m.index = m.pager.PerPage - 1
140- m.pager.PrevPage()
141- }
142- m.index = max(0, m.index)
143- case "down", "j":
144- // Move down
145- itemsOnPage := m.pager.ItemsOnPage(len(m.posts))
146- m.index++
147- if m.index > itemsOnPage-1 && m.pager.Page < m.pager.TotalPages-1 {
148- m.index = 0
149- m.pager.NextPage()
150- }
151- m.index = min(itemsOnPage-1, m.index)
152-
153- // Delete
154- case "x":
155- if len(m.posts) > 0 {
156- m.state = stateDeletingPost
157- m.UpdatePaging(msg)
158- }
159-
160- return m, nil
161-
162- // Confirm Delete
163- case "y":
164- switch m.state {
165- case stateDeletingPost:
166- m.state = stateNormal
167- return m, removePost(m)
168- }
169- }
170-
171- case errMsg:
172- m.err = msg.err
173- return m, nil
174-
175- case postsLoadedMsg:
176- m.state = stateNormal
177- m.index = 0
178- m.posts = msg.Posts
179-
180- case removePostMsg:
181- if m.state == stateQuitting {
182- return m, tea.Quit
183- }
184- i := m.getSelectedIndex()
185-
186- // Remove key from array
187- m.posts = append(m.posts[:i], m.posts[i+1:]...)
188-
189- // Update pagination
190- m.pager.SetTotalPages(len(m.posts))
191- m.pager.Page = min(m.pager.Page, m.pager.TotalPages-1)
192-
193- // Update cursor
194- m.index = min(m.index, m.pager.ItemsOnPage(len(m.posts)-1))
195-
196- return m, nil
197-
198- case spinner.TickMsg:
199- var cmd tea.Cmd
200- if m.state < stateNormal {
201- m.spinner, cmd = m.spinner.Update(msg)
202- }
203- return m, cmd
204- }
205-
206- m.UpdatePaging(msg)
207-
208- // If an item is being confirmed for delete, any key (other than the key
209- // used for confirmation above) cancels the deletion
210- k, ok := msg.(tea.KeyMsg)
211- if ok && k.String() != "x" {
212- m.state = stateNormal
213- }
214-
215- return m, nil
216-}
217-
218-// View renders the current UI into a string.
219-func (m Model) View() string {
220- if m.err != nil {
221- return m.err.Error()
222- }
223-
224- var s string
225-
226- switch m.state {
227- case stateLoading:
228- s = m.spinner.View() + " Loading...\n\n"
229- case stateQuitting:
230- s = "Thanks for using lists!\n"
231- default:
232- s = "Here are the posts linked to your account.\n\n"
233-
234- s += postsView(m)
235- if m.pager.TotalPages > 1 {
236- s += m.pager.View()
237- }
238-
239- // Footer
240- switch m.state {
241- case stateDeletingPost:
242- s += m.promptView("Delete this post?")
243- default:
244- s += "\n\n" + helpView(m)
245- }
246- }
247-
248- return s
249-}
250-
251-func postsView(m Model) string {
252- var (
253- s string
254- state postState
255- start, end = m.pager.GetSliceBounds(len(m.posts))
256- slice = m.posts[start:end]
257- )
258-
259- destructiveState := m.state == stateDeletingPost
260-
261- if len(m.posts) == 0 {
262- s += "You don't have any posts yet."
263- return s
264- }
265-
266- // Render key info
267- for i, post := range slice {
268- if destructiveState && m.index == i {
269- state = postDeleting
270- } else if m.index == i {
271- state = postSelected
272- } else {
273- state = postNormal
274- }
275- s += m.newStyledKey(m.styles, post, m.urls).render(state)
276- }
277-
278- // If there aren't enough keys to fill the view, fill the missing parts
279- // with whitespace
280- if len(slice) < m.pager.PerPage {
281- for i := len(slice); i < m.pager.PerPage; i++ {
282- s += "\n\n\n"
283- }
284- }
285-
286- return s
287-}
288-
289-func helpView(m Model) string {
290- var items []string
291- if len(m.posts) > 1 {
292- items = append(items, "j/k, ↑/↓: choose")
293- }
294- if m.pager.TotalPages > 1 {
295- items = append(items, "h/l, ←/→: page")
296- }
297- if len(m.posts) > 0 {
298- items = append(items, "x: delete")
299- }
300- items = append(items, "esc: exit")
301- return common.HelpView(m.styles, items...)
302-}
303-
304-func (m Model) promptView(prompt string) string {
305- st := m.styles.Delete.Copy().MarginTop(2).MarginRight(1)
306- return st.Render(prompt) +
307- m.styles.DeleteDim.Render("(y/N)")
308-}
309-
310-func LoadPosts(m Model) tea.Cmd {
311- if m.user == nil {
312- m.logger.Info("user not found!")
313- err := errors.New("user not found")
314- return func() tea.Msg {
315- return errMsg{err}
316- }
317- }
318-
319- return tea.Batch(
320- m.fetchPosts(m.user.ID),
321- m.spinner.Tick,
322- )
323-}
324-
325-func (m Model) fetchPosts(userID string) tea.Cmd {
326- return func() tea.Msg {
327- posts, _ := m.dbpool.FindAllPostsForUser(userID, m.cfg.Space)
328- loader := PostLoader{
329- Posts: posts,
330- }
331- return postsLoadedMsg(loader)
332- }
333-}
334-
335-func removePost(m Model) tea.Cmd {
336- return func() tea.Msg {
337- bucket, err := m.st.UpsertBucket(m.user.ID)
338- if err != nil {
339- return errMsg{err}
340- }
341-
342- err = m.st.DeleteObject(bucket, m.posts[m.getSelectedIndex()].Filename)
343- if err != nil {
344- return errMsg{err}
345- }
346-
347- err = m.dbpool.RemovePosts([]string{m.posts[m.getSelectedIndex()].ID})
348- if err != nil {
349- return errMsg{err}
350- }
351-
352- return removePostMsg(m.index)
353- }
354-}
355-
356-// Utils
357-
358-func min(a, b int) int {
359- if a < b {
360- return a
361- }
362- return b
363-}
364-
365-func max(a, b int) int {
366- if a > b {
367- return a
368- }
369- return b
370-}
+8,
-16
1@@ -16,28 +16,20 @@ func LogMiddleware(logger *slog.Logger) wish.Middleware {
2
3 logger.Info(
4 "connect",
5- "user",
6- s.User(),
7- "ip",
8- s.RemoteAddr().String(),
9- "pty",
10- ok,
11- "term",
12- pty.Term,
13- "windowWidth",
14- pty.Window.Width,
15- "windowHeight",
16- pty.Window.Height,
17+ "user", s.User(),
18+ "ip", s.RemoteAddr().String(),
19+ "pty", ok,
20+ "term", pty.Term,
21+ "windowWidth", pty.Window.Width,
22+ "windowHeight", pty.Window.Height,
23 )
24
25 sh(s)
26
27 logger.Info(
28 "disconnect",
29- "ip",
30- s.RemoteAddr().String(),
31- "duration",
32- time.Since(ct),
33+ "ip", s.RemoteAddr().String(),
34+ "duration", time.Since(ct),
35 )
36 }
37 }
+0,
-1
1@@ -1 +0,0 @@
2-package wish
+26,
-0
1@@ -1,10 +1,36 @@
2 package wish
3
4 import (
5+ "fmt"
6+
7+ "github.com/charmbracelet/lipgloss"
8 "github.com/charmbracelet/ssh"
9 "github.com/charmbracelet/wish"
10+ "github.com/picosh/pico/tui/common"
11 )
12
13+func SessionMessage(sesh ssh.Session, msg string) {
14+ _, _ = sesh.Write([]byte(msg + "\r\n"))
15+}
16+
17+func DeprecatedNotice() wish.Middleware {
18+ return func(next ssh.Handler) ssh.Handler {
19+ return func(sesh ssh.Session) {
20+ renderer := lipgloss.NewRenderer(sesh)
21+ renderer.SetOutput(common.OutputFromSession(sesh))
22+ styles := common.DefaultStyles(renderer)
23+
24+ msg := fmt.Sprintf(
25+ "%s\n\nRun %s to access pico's TUI",
26+ styles.Logo.Render("DEPRECATED"),
27+ styles.Code.Render("ssh pico.sh"),
28+ )
29+ SessionMessage(sesh, styles.RoundedBorder.Render(msg))
30+ next(sesh)
31+ }
32+ }
33+}
34+
35 func PtyMdw(mdw wish.Middleware) wish.Middleware {
36 return func(next ssh.Handler) ssh.Handler {
37 return func(sesh ssh.Session) {