- commit
- b3f3878
- parent
- 3c82bf5
- author
- Eric Bower
- date
- 2024-01-30 04:44:13 +0000 UTC
feat(prose): directory of images at `x.prose.sh/i`
4 files changed,
+150,
-14
+83,
-14
1@@ -20,22 +20,83 @@ import (
2 "github.com/picosh/pico/shared/storage"
3 )
4
5-type PostItemData struct {
6- BlogURL template.URL
7- URL template.URL
8- ImgURL template.URL
9- PublishAtISO string
10- PublishAt string
11- Caption string
12-}
13-
14 type PostPageData struct {
15 ImgURL template.URL
16 }
17
18+type BlogPageData struct {
19+ Site *shared.SitePageData
20+ PageTitle string
21+ URL template.URL
22+ Username string
23+ Posts []template.URL
24+}
25+
26 var Space = "imgs"
27
28-func rssHandler(w http.ResponseWriter, r *http.Request) {
29+func ImgsListHandler(w http.ResponseWriter, r *http.Request) {
30+ username := shared.GetUsernameFromRequest(r)
31+ dbpool := shared.GetDB(r)
32+ logger := shared.GetLogger(r)
33+ cfg := shared.GetCfg(r)
34+
35+ user, err := dbpool.FindUserForName(username)
36+ if err != nil {
37+ logger.Infof("blog not found: %s", username)
38+ http.Error(w, "blog not found", http.StatusNotFound)
39+ return
40+ }
41+
42+ tag := r.URL.Query().Get("tag")
43+ var posts []*db.Post
44+ var p *db.Paginate[*db.Post]
45+ pager := &db.Pager{Num: 1000, Page: 0}
46+ if tag == "" {
47+ p, err = dbpool.FindPostsForUser(pager, user.ID, Space)
48+ } else {
49+ p, err = dbpool.FindUserPostsByTag(pager, tag, user.ID, Space)
50+ }
51+ posts = p.Data
52+
53+ if err != nil {
54+ logger.Error(err)
55+ http.Error(w, "could not fetch posts for blog", http.StatusInternalServerError)
56+ return
57+ }
58+
59+ ts, err := shared.RenderTemplate(cfg, []string{
60+ cfg.StaticPath("html/imgs.page.tmpl"),
61+ })
62+
63+ if err != nil {
64+ logger.Error(err)
65+ http.Error(w, err.Error(), http.StatusInternalServerError)
66+ return
67+ }
68+
69+ curl := shared.CreateURLFromRequest(cfg, r)
70+ postCollection := make([]template.URL, 0, len(posts))
71+ for _, post := range posts {
72+ url := cfg.ImgURL(curl, post.Username, post.Slug)
73+ postCollection = append(postCollection, template.URL(url))
74+ }
75+
76+ data := BlogPageData{
77+ Site: cfg.GetSiteData(),
78+ PageTitle: fmt.Sprintf("%s imgs", username),
79+ URL: template.URL(cfg.FullBlogURL(curl, username)),
80+ Username: username,
81+ Posts: postCollection,
82+ }
83+
84+ err = ts.Execute(w, data)
85+ if err != nil {
86+ logger.Error(err)
87+ http.Error(w, err.Error(), http.StatusInternalServerError)
88+ }
89+}
90+
91+func ImgsRssHandler(w http.ResponseWriter, r *http.Request) {
92 dbpool := shared.GetDB(r)
93 logger := shared.GetLogger(r)
94 cfg := shared.GetCfg(r)
95@@ -171,6 +232,12 @@ func FindImgPost(r *http.Request, user *db.User, slug string) (*db.Post, error)
96 return dbpool.FindPostWithSlug(slug, user.ID, Space)
97 }
98
99+func redirectHandler(w http.ResponseWriter, r *http.Request) {
100+ username := shared.GetUsernameFromRequest(r)
101+ url := fmt.Sprintf("https://%s.prose.sh/i", username)
102+ http.Redirect(w, r, url, http.StatusMovedPermanently)
103+}
104+
105 func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
106 routes := []shared.Route{
107 shared.NewRoute("GET", "/check", shared.CheckHandler),
108@@ -183,11 +250,12 @@ func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
109
110 routes = append(
111 routes,
112- shared.NewRoute("GET", "/rss", rssHandler),
113- shared.NewRoute("GET", "/rss.xml", rssHandler),
114- shared.NewRoute("GET", "/atom.xml", rssHandler),
115- shared.NewRoute("GET", "/feed.xml", rssHandler),
116+ shared.NewRoute("GET", "/rss", ImgsRssHandler),
117+ shared.NewRoute("GET", "/rss.xml", ImgsRssHandler),
118+ shared.NewRoute("GET", "/atom.xml", ImgsRssHandler),
119+ shared.NewRoute("GET", "/feed.xml", ImgsRssHandler),
120
121+ shared.NewRoute("GET", "/([^/]+)", redirectHandler),
122 shared.NewRoute("GET", "/([^/]+)/o/([^/]+)", ImgRequest),
123 shared.NewRoute("GET", "/([^/]+)/([^/]+)", ImgRequest),
124 shared.NewRoute("GET", "/([^/]+)/([^/]+)/([a-z0-9]+)", ImgRequest),
125@@ -206,6 +274,7 @@ func createSubdomainRoutes(staticRoutes []shared.Route) []shared.Route {
126
127 routes = append(
128 routes,
129+ shared.NewRoute("GET", "/", redirectHandler),
130 shared.NewRoute("GET", "/o/([^/]+)", ImgRequest),
131 shared.NewRoute("GET", "/([^/]+)", ImgRequest),
132 shared.NewRoute("GET", "/([^/]+)/([a-z0-9]+)", ImgRequest),
+2,
-0
1@@ -829,6 +829,7 @@ func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
2 shared.NewRoute("GET", "/raw/([^/]+)/(.+)", postRawHandler),
3 shared.NewRoute("GET", "/([^/]+)/(.+)/([a-z0-9]+)", imgs.ImgRequest),
4 shared.NewRoute("GET", "/([^/]+)/(.+).(jpg|jpeg|png|gif|webp|svg)", imgs.ImgRequest),
5+ shared.NewRoute("GET", "/([^/]+)/i", imgs.ImgsListHandler),
6 shared.NewRoute("GET", "/([^/]+)/(.+)", postHandler),
7 )
8
9@@ -857,6 +858,7 @@ func createSubdomainRoutes(staticRoutes []shared.Route) []shared.Route {
10 shared.NewRoute("GET", "/raw/(.+)", postRawHandler),
11 shared.NewRoute("GET", "/(.+)/([a-z0-9]+)", imgs.ImgRequest),
12 shared.NewRoute("GET", "/(.+).(jpg|jpeg|png|gif|webp|svg)", imgs.ImgRequest),
13+ shared.NewRoute("GET", "/i", imgs.ImgsListHandler),
14 shared.NewRoute("GET", "/(.+)", postHandler),
15 )
16
+29,
-0
1@@ -0,0 +1,29 @@
2+{{template "base" .}}
3+
4+{{define "title"}}{{.PageTitle}}{{end}}
5+
6+{{define "meta"}}{{end}}
7+
8+{{define "attrs"}}id="blog"{{end}}
9+
10+{{define "body"}}
11+<header class="text-center">
12+ <h1 class="text-2xl font-bold">{{.PageTitle}}</h1>
13+ <nav>
14+ <a href="{{.URL}}" class="text-lg">< blog</a>
15+ </nav>
16+ <hr />
17+</header>
18+<main>
19+ <section class="albums">
20+ {{range .Posts}}
21+ <a href="{{.}}" class="thumbnail-link">
22+ <article class="thumbnail-container">
23+ <img class="thumbnail" src="{{.}}/300x" />
24+ </article>
25+ </a>
26+ {{end}}
27+ </section>
28+</main>
29+{{template "footer" .}}
30+{{end}}
+36,
-0
1@@ -32,6 +32,37 @@ table {
2 display: none;
3 }
4
5+.albums {
6+ width: 100%;
7+ display: grid;
8+ grid-template-columns: repeat(3, 1fr);
9+ grid-template-rows: repeat(auto-fill, 200px);
10+ grid-row-gap: 0.5rem;
11+ grid-column-gap: 1rem;
12+}
13+
14+.thumbnail-container {
15+ position: relative;
16+ background-color: #000;
17+ display: flex;
18+ flex-direction: column;
19+ align-items: center;
20+ justify-content: center;
21+ padding: 3px;
22+ min-height: 200px;
23+}
24+
25+.thumbnail {
26+ z-index: 1;
27+ object-fit: contain;
28+ max-width: 200px;
29+ height: auto;
30+}
31+
32+.thumbnail-link {
33+ z-index: 1;
34+}
35+
36 @media only screen and (max-width: 600px) {
37 .layout-aside main {
38 flex-direction: column;
39@@ -44,4 +75,9 @@ table {
40 #readme {
41 display: block;
42 }
43+
44+ .albums {
45+ grid-template-columns: repeat(1, 1fr);
46+ justify-content: center;
47+ }
48 }