repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

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
M imgs/api.go
+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),
M prose/api.go
+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 
A prose/html/imgs.page.tmpl
+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}}
M prose/public/main.css
+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 }