repos / pico

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

commit
d6d75f7
parent
78af75d
author
Eric Bower
date
2022-08-27 04:23:24 +0000 UTC
fix(rss): limit blog rss feeds to 10
6 files changed,  +191, -135
M db/db.go
+2, -2
 1@@ -135,7 +135,7 @@ type DB interface {
 2 
 3 	FindPosts() ([]*Post, error)
 4 	FindPost(postID string) (*Post, error)
 5-	FindPostsForUser(userID string, space string) ([]*Post, error)
 6+	FindPostsForUser(pager *Pager, userID string, space string) (*Paginate[*Post], error)
 7 	FindAllPostsForUser(userID string, space string) ([]*Post, error)
 8 	FindPostsBeforeDate(date *time.Time, space string) ([]*Post, error)
 9 	FindUpdatedPostsForUser(userID string, space string) ([]*Post, error)
10@@ -149,7 +149,7 @@ type DB interface {
11 	RemovePosts(postIDs []string) error
12 
13 	ReplaceTagsForPost(tags []string, postID string) error
14-	FindUserPostsByTag(tag, userID, space string) ([]*Post, error)
15+	FindUserPostsByTag(pager *Pager, tag, userID, space string) (*Paginate[*Post], error)
16 	FindPostsByTag(pager *Pager, tag, space string) (*Paginate[*Post], error)
17 	FindPopularTags(space string) ([]string, error)
18 	FindTagsForPost(postID string) ([]string, error)
M db/postgres/storage.go
+53, -12
  1@@ -69,11 +69,13 @@ var (
  2 	LEFT OUTER JOIN app_users ON app_users.id = posts.user_id
  3 	LEFT OUTER JOIN post_tags ON post_tags.post_id = posts.id
  4 	WHERE
  5+		hidden = FALSE AND
  6 		user_id = $1 AND
  7 		publish_at::date <= CURRENT_DATE AND
  8 		cur_space = $2
  9 	GROUP BY %s
 10-	ORDER BY publish_at DESC, slug DESC`, SelectPost, SelectPost)
 11+	ORDER BY publish_at DESC, slug DESC
 12+	LIMIT $3 OFFSET $4`, SelectPost, SelectPost)
 13 
 14 	sqlSelectAllPostsForUser = fmt.Sprintf(`
 15 	SELECT %s
 16@@ -113,11 +115,13 @@ var (
 17 	LEFT OUTER JOIN app_users ON app_users.id = posts.user_id
 18 	LEFT OUTER JOIN post_tags ON post_tags.post_id = posts.id
 19 	WHERE
 20+		hidden = FALSE AND
 21 		user_id = $1 AND
 22 		(post_tags.name = $2 OR hidden = true) AND
 23 		publish_at::date <= CURRENT_DATE AND
 24 		cur_space = $3
 25-	ORDER BY publish_at DESC`, SelectPost)
 26+	ORDER BY publish_at DESC
 27+	LIMIT $4 OFFSET $5`, SelectPost)
 28 
 29 	/* sqlSelectUserPostsWithTags = fmt.Sprintf(`
 30 	SELECT %s, STRING_AGG(coalesce(post_tags.name, ''), ',') tags
 31@@ -705,11 +709,17 @@ func (me *PsqlDB) RemovePosts(postIDs []string) error {
 32 	return err
 33 }
 34 
 35-func (me *PsqlDB) FindPostsForUser(userID string, space string) ([]*db.Post, error) {
 36+func (me *PsqlDB) FindPostsForUser(page *db.Pager, userID string, space string) (*db.Paginate[*db.Post], error) {
 37 	var posts []*db.Post
 38-	rs, err := me.Db.Query(sqlSelectPostsForUser, userID, space)
 39+	rs, err := me.Db.Query(
 40+		sqlSelectPostsForUser,
 41+		userID,
 42+		space,
 43+		page.Num,
 44+		page.Num*page.Page,
 45+	)
 46 	if err != nil {
 47-		return posts, err
 48+		return nil, err
 49 	}
 50 	for rs.Next() {
 51 		post, err := CreatePostWithTagsFromRow(rs)
 52@@ -719,10 +729,22 @@ func (me *PsqlDB) FindPostsForUser(userID string, space string) ([]*db.Post, err
 53 
 54 		posts = append(posts, post)
 55 	}
 56+
 57 	if rs.Err() != nil {
 58-		return posts, rs.Err()
 59+		return nil, rs.Err()
 60 	}
 61-	return posts, nil
 62+
 63+	var count int
 64+	err = me.Db.QueryRow(sqlSelectPostCount, space).Scan(&count)
 65+	if err != nil {
 66+		return nil, err
 67+	}
 68+
 69+	pager := &db.Paginate[*db.Post]{
 70+		Data:  posts,
 71+		Total: int(math.Ceil(float64(count) / float64(page.Num))),
 72+	}
 73+	return pager, nil
 74 }
 75 
 76 func (me *PsqlDB) FindAllPostsForUser(userID string, space string) ([]*db.Post, error) {
 77@@ -867,11 +889,18 @@ func (me *PsqlDB) ReplaceTagsForPost(tags []string, postID string) error {
 78 	return err
 79 }
 80 
 81-func (me *PsqlDB) FindUserPostsByTag(tag, userID, space string) ([]*db.Post, error) {
 82+func (me *PsqlDB) FindUserPostsByTag(page *db.Pager, tag, userID, space string) (*db.Paginate[*db.Post], error) {
 83 	var posts []*db.Post
 84-	rs, err := me.Db.Query(sqlSelectUserPostsByTag, userID, tag, space)
 85+	rs, err := me.Db.Query(
 86+		sqlSelectUserPostsByTag,
 87+		userID,
 88+		tag,
 89+		space,
 90+		page.Num,
 91+		page.Num*page.Page,
 92+	)
 93 	if err != nil {
 94-		return posts, err
 95+		return nil, err
 96 	}
 97 	for rs.Next() {
 98 		post, err := CreatePostFromRow(rs)
 99@@ -881,10 +910,22 @@ func (me *PsqlDB) FindUserPostsByTag(tag, userID, space string) ([]*db.Post, err
100 
101 		posts = append(posts, post)
102 	}
103+
104 	if rs.Err() != nil {
105-		return posts, rs.Err()
106+		return nil, rs.Err()
107 	}
108-	return posts, nil
109+
110+	var count int
111+	err = me.Db.QueryRow(sqlSelectPostCount, space).Scan(&count)
112+	if err != nil {
113+		return nil, err
114+	}
115+
116+	pager := &db.Paginate[*db.Post]{
117+		Data:  posts,
118+		Total: int(math.Ceil(float64(count) / float64(page.Num))),
119+	}
120+	return pager, nil
121 }
122 
123 func (me *PsqlDB) FindPostsByTag(pager *db.Pager, tag, space string) (*db.Paginate[*db.Post], error) {
M imgs/api.go
+7, -3
 1@@ -115,11 +115,14 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
 2 
 3 	tag := r.URL.Query().Get("tag")
 4 	var posts []*db.Post
 5+	var p *db.Paginate[*db.Post]
 6+	pager := &db.Pager{Num: 1000, Page: 0}
 7 	if tag == "" {
 8-		posts, err = dbpool.FindPostsForUser(user.ID, cfg.Space)
 9+		p, err = dbpool.FindPostsForUser(pager, user.ID, cfg.Space)
10 	} else {
11-		posts, err = dbpool.FindUserPostsByTag(tag, user.ID, cfg.Space)
12+		p, err = dbpool.FindUserPostsByTag(pager, tag, user.ID, cfg.Space)
13 	}
14+	posts = p.Data
15 
16 	if err != nil {
17 		logger.Error(err)
18@@ -379,7 +382,8 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
19 		return
20 	}
21 
22-	posts, err := dbpool.FindPostsForUser(user.ID, cfg.Space)
23+	pager, err := dbpool.FindPostsForUser(&db.Pager{Num: 10, Page: 0}, user.ID, cfg.Space)
24+	posts := pager.Data
25 
26 	if err != nil {
27 		logger.Error(err)
M lists/api.go
+48, -46
  1@@ -93,11 +93,14 @@ func getPostsForUser(r *http.Request, user *db.User, tag string) ([]*db.Post, er
  2 	var err error
  3 
  4 	posts := make([]*db.Post, 0)
  5+	pager := &db.Pager{Num: 1000, Page: 0}
  6+	var p *db.Paginate[*db.Post]
  7 	if tag == "" {
  8-		posts, err = dbpool.FindPostsForUser(user.ID, cfg.Space)
  9+		p, err = dbpool.FindPostsForUser(pager, user.ID, cfg.Space)
 10 	} else {
 11-		posts, err = dbpool.FindUserPostsByTag(tag, user.ID, cfg.Space)
 12+		p, err = dbpool.FindUserPostsByTag(pager, tag, user.ID, cfg.Space)
 13 	}
 14+	posts = p.Data
 15 
 16 	if err != nil {
 17 		return posts, err
 18@@ -156,43 +159,46 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
 19 		Title: GetBlogName(username),
 20 		Bio:   "",
 21 	}
 22+	header, err := dbpool.FindPostWithFilename("_header.txt", user.ID, cfg.Space)
 23+	if err == nil {
 24+		parsedText := ParseText(header.Text)
 25+		if parsedText.MetaData.Title != "" {
 26+			headerTxt.Title = parsedText.MetaData.Title
 27+		}
 28+
 29+		if parsedText.MetaData.Description != "" {
 30+			headerTxt.Bio = parsedText.MetaData.Description
 31+		}
 32+
 33+		headerTxt.Nav = parsedText.Items
 34+		if len(headerTxt.Nav) > 0 {
 35+			headerTxt.HasItems = true
 36+		}
 37+	}
 38+
 39 	readmeTxt := &ReadmeTxt{}
 40+	readme, err := dbpool.FindPostWithFilename("_header.txt", user.ID, cfg.Space)
 41+	if err == nil {
 42+		parsedText := ParseText(readme.Text)
 43+		readmeTxt.Items = parsedText.Items
 44+		readmeTxt.ListType = parsedText.MetaData.ListType
 45+		if len(readmeTxt.Items) > 0 {
 46+			readmeTxt.HasItems = true
 47+		}
 48+	}
 49 
 50 	postCollection := make([]PostItemData, 0, len(posts))
 51 	for _, post := range posts {
 52-		if post.Filename == "_header.txt" {
 53-			parsedText := ParseText(post.Text)
 54-			if parsedText.MetaData.Title != "" {
 55-				headerTxt.Title = parsedText.MetaData.Title
 56-			}
 57-
 58-			if parsedText.MetaData.Description != "" {
 59-				headerTxt.Bio = parsedText.MetaData.Description
 60-			}
 61-
 62-			headerTxt.Nav = parsedText.Items
 63-			if len(headerTxt.Nav) > 0 {
 64-				headerTxt.HasItems = true
 65-			}
 66-		} else if post.Filename == "_readme.txt" {
 67-			parsedText := ParseText(post.Text)
 68-			readmeTxt.Items = parsedText.Items
 69-			readmeTxt.ListType = parsedText.MetaData.ListType
 70-			if len(readmeTxt.Items) > 0 {
 71-				readmeTxt.HasItems = true
 72-			}
 73-		} else {
 74-			p := PostItemData{
 75-				URL:            template.URL(cfg.FullPostURL(post.Username, post.Slug, onSubdomain, withUserName)),
 76-				BlogURL:        template.URL(cfg.FullBlogURL(post.Username, onSubdomain, withUserName)),
 77-				Title:          shared.FilenameToTitle(post.Filename, post.Title),
 78-				PublishAt:      post.PublishAt.Format("02 Jan, 2006"),
 79-				PublishAtISO:   post.PublishAt.Format(time.RFC3339),
 80-				UpdatedTimeAgo: shared.TimeAgo(post.UpdatedAt),
 81-				UpdatedAtISO:   post.UpdatedAt.Format(time.RFC3339),
 82-			}
 83-			postCollection = append(postCollection, p)
 84+		p := PostItemData{
 85+			URL:            template.URL(cfg.FullPostURL(post.Username, post.Slug, onSubdomain, withUserName)),
 86+			BlogURL:        template.URL(cfg.FullBlogURL(post.Username, onSubdomain, withUserName)),
 87+			Title:          shared.FilenameToTitle(post.Filename, post.Title),
 88+			PublishAt:      post.PublishAt.Format("02 Jan, 2006"),
 89+			PublishAtISO:   post.PublishAt.Format(time.RFC3339),
 90+			UpdatedTimeAgo: shared.TimeAgo(post.UpdatedAt),
 91+			UpdatedAtISO:   post.UpdatedAt.Format(time.RFC3339),
 92 		}
 93+		postCollection = append(postCollection, p)
 94 	}
 95 
 96 	data := BlogPageData{
 97@@ -517,19 +523,15 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
 98 	headerTxt := &HeaderTxt{
 99 		Title: GetBlogName(username),
100 	}
101+	header, err := dbpool.FindPostWithFilename("_header.txt", user.ID, cfg.Space)
102+	if err == nil {
103+		parsedText := ParseText(header.Text)
104+		if parsedText.MetaData.Title != "" {
105+			headerTxt.Title = parsedText.MetaData.Title
106+		}
107 
108-	for _, post := range posts {
109-		if post.Filename == "_header.txt" {
110-			parsedText := ParseText(post.Text)
111-			if parsedText.MetaData.Title != "" {
112-				headerTxt.Title = parsedText.MetaData.Title
113-			}
114-
115-			if parsedText.MetaData.Description != "" {
116-				headerTxt.Bio = parsedText.MetaData.Description
117-			}
118-
119-			break
120+		if parsedText.MetaData.Description != "" {
121+			headerTxt.Bio = parsedText.MetaData.Description
122 		}
123 	}
124 
M pastes/api.go
+3, -1
 1@@ -86,7 +86,9 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
 2 		http.Error(w, "blog not found", http.StatusNotFound)
 3 		return
 4 	}
 5-	posts, err := dbpool.FindPostsForUser(user.ID, cfg.Space)
 6+	pager, err := dbpool.FindPostsForUser(&db.Pager{Num: 1000, Page: 0}, user.ID, cfg.Space)
 7+	posts := pager.Data
 8+
 9 	if err != nil {
10 		logger.Error(err)
11 		http.Error(w, "could not fetch posts for blog", http.StatusInternalServerError)
M prose/api.go
+78, -71
  1@@ -154,12 +154,15 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
  2 	}
  3 
  4 	tag := r.URL.Query().Get("tag")
  5+	pager := &db.Pager{Num: 1000, Page: 0}
  6 	var posts []*db.Post
  7+	var p *db.Paginate[*db.Post]
  8 	if tag == "" {
  9-		posts, err = dbpool.FindPostsForUser(user.ID, cfg.Space)
 10+		p, err = dbpool.FindPostsForUser(pager, user.ID, cfg.Space)
 11 	} else {
 12-		posts, err = dbpool.FindUserPostsByTag(tag, user.ID, cfg.Space)
 13+		p, err = dbpool.FindUserPostsByTag(pager, tag, user.ID, cfg.Space)
 14 	}
 15+	posts = p.Data
 16 
 17 	if err != nil {
 18 		logger.Error(err)
 19@@ -167,16 +170,16 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
 20 		return
 21 	}
 22 
 23+	ts, err := shared.RenderTemplate(cfg, []string{
 24+		cfg.StaticPath("html/blog.page.tmpl"),
 25+	})
 26+
 27 	hostDomain := strings.Split(r.Host, ":")[0]
 28 	appDomain := strings.Split(cfg.ConfigCms.Domain, ":")[0]
 29 
 30 	onSubdomain := cfg.IsSubdomains() && strings.Contains(hostDomain, appDomain)
 31 	withUserName := (!onSubdomain && hostDomain == appDomain) || !cfg.IsCustomdomains()
 32 
 33-	ts, err := shared.RenderTemplate(cfg, []string{
 34-		cfg.StaticPath("html/blog.page.tmpl"),
 35-	})
 36-
 37 	if err != nil {
 38 		logger.Error(err)
 39 		http.Error(w, err.Error(), http.StatusInternalServerError)
 40@@ -189,57 +192,61 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
 41 	}
 42 	readmeTxt := &ReadmeTxt{}
 43 
 44-	hasCSS := false
 45-	postCollection := make([]PostItemData, 0, len(posts))
 46-	for _, post := range posts {
 47-		if post.Filename == "_styles.css" && len(post.Text) > 0 {
 48-			hasCSS = true
 49-		} else if post.Filename == "_readme.md" {
 50-			parsedText, err := shared.ParseText(post.Text, imgs.ImgBaseURL(post.Username))
 51-			if err != nil {
 52-				logger.Error(err)
 53-			}
 54-			headerTxt.Bio = parsedText.Description
 55-			if parsedText.Title != "" {
 56-				headerTxt.Title = parsedText.Title
 57-			}
 58+	readme, err := dbpool.FindPostWithFilename("_readme.md", user.ID, cfg.Space)
 59+	if err == nil {
 60+		parsedText, err := shared.ParseText(readme.Text, imgs.ImgBaseURL(readme.Username))
 61+		if err != nil {
 62+			logger.Error(err)
 63+		}
 64+		headerTxt.Bio = parsedText.Description
 65+		if parsedText.Title != "" {
 66+			headerTxt.Title = parsedText.Title
 67+		}
 68 
 69-			headerTxt.Nav = []shared.Link{}
 70-			for _, nav := range parsedText.Nav {
 71-				u, _ := url.Parse(nav.URL)
 72-				finURL := nav.URL
 73-				if !u.IsAbs() {
 74-					finURL = cfg.FullPostURL(
 75-						post.Username,
 76-						nav.URL,
 77-						onSubdomain,
 78-						withUserName,
 79-					)
 80-				}
 81-				headerTxt.Nav = append(headerTxt.Nav, shared.Link{
 82-					URL:  finURL,
 83-					Text: nav.Text,
 84-				})
 85+		headerTxt.Nav = []shared.Link{}
 86+		for _, nav := range parsedText.Nav {
 87+			u, _ := url.Parse(nav.URL)
 88+			finURL := nav.URL
 89+			if !u.IsAbs() {
 90+				finURL = cfg.FullPostURL(
 91+					readme.Username,
 92+					nav.URL,
 93+					onSubdomain,
 94+					withUserName,
 95+				)
 96 			}
 97+			headerTxt.Nav = append(headerTxt.Nav, shared.Link{
 98+				URL:  finURL,
 99+				Text: nav.Text,
100+			})
101+		}
102 
103-			readmeTxt.Contents = template.HTML(parsedText.Html)
104-			if len(readmeTxt.Contents) > 0 {
105-				readmeTxt.HasText = true
106-			}
107-		} else {
108-			p := PostItemData{
109-				URL:            template.URL(cfg.FullPostURL(post.Username, post.Slug, onSubdomain, withUserName)),
110-				BlogURL:        template.URL(cfg.FullBlogURL(post.Username, onSubdomain, withUserName)),
111-				Title:          shared.FilenameToTitle(post.Filename, post.Title),
112-				PublishAt:      post.PublishAt.Format("02 Jan, 2006"),
113-				PublishAtISO:   post.PublishAt.Format(time.RFC3339),
114-				UpdatedTimeAgo: shared.TimeAgo(post.UpdatedAt),
115-				UpdatedAtISO:   post.UpdatedAt.Format(time.RFC3339),
116-			}
117-			postCollection = append(postCollection, p)
118+		readmeTxt.Contents = template.HTML(parsedText.Html)
119+		if len(readmeTxt.Contents) > 0 {
120+			readmeTxt.HasText = true
121 		}
122 	}
123 
124+	hasCSS := false
125+	_, err = dbpool.FindPostWithFilename("_styles.css", user.ID, cfg.Space)
126+	if err == nil {
127+		hasCSS = true
128+	}
129+
130+	postCollection := make([]PostItemData, 0, len(posts))
131+	for _, post := range posts {
132+		p := PostItemData{
133+			URL:            template.URL(cfg.FullPostURL(post.Username, post.Slug, onSubdomain, withUserName)),
134+			BlogURL:        template.URL(cfg.FullBlogURL(post.Username, onSubdomain, withUserName)),
135+			Title:          shared.FilenameToTitle(post.Filename, post.Title),
136+			PublishAt:      post.PublishAt.Format("02 Jan, 2006"),
137+			PublishAtISO:   post.PublishAt.Format(time.RFC3339),
138+			UpdatedTimeAgo: shared.TimeAgo(post.UpdatedAt),
139+			UpdatedAtISO:   post.UpdatedAt.Format(time.RFC3339),
140+		}
141+		postCollection = append(postCollection, p)
142+	}
143+
144 	data := BlogPageData{
145 		Site:      *cfg.GetSiteData(),
146 		PageTitle: headerTxt.Title,
147@@ -541,12 +548,15 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
148 	}
149 
150 	tag := r.URL.Query().Get("tag")
151+	pager := &db.Pager{Num: 10, Page: 0}
152 	var posts []*db.Post
153+	var p *db.Paginate[*db.Post]
154 	if tag == "" {
155-		posts, err = dbpool.FindPostsForUser(user.ID, cfg.Space)
156+		p, err = dbpool.FindPostsForUser(pager, user.ID, cfg.Space)
157 	} else {
158-		posts, err = dbpool.FindUserPostsByTag(tag, user.ID, cfg.Space)
159+		p, err = dbpool.FindUserPostsByTag(pager, tag, user.ID, cfg.Space)
160 	}
161+	posts = p.Data
162 
163 	if err != nil {
164 		logger.Error(err)
165@@ -565,30 +575,27 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
166 		Title: GetBlogName(username),
167 	}
168 
169+	readme, err := dbpool.FindPostWithFilename("_readme.md", user.ID, cfg.Space)
170+	if err == nil {
171+		parsedText, err := shared.ParseText(readme.Text, imgs.ImgBaseURL(readme.Username))
172+		if err != nil {
173+			logger.Error(err)
174+		}
175+		if parsedText.Title != "" {
176+			headerTxt.Title = parsedText.Title
177+		}
178+
179+		if parsedText.Description != "" {
180+			headerTxt.Bio = parsedText.Description
181+		}
182+	}
183+
184 	hostDomain := strings.Split(r.Host, ":")[0]
185 	appDomain := strings.Split(cfg.ConfigCms.Domain, ":")[0]
186 
187 	onSubdomain := cfg.IsSubdomains() && strings.Contains(hostDomain, appDomain)
188 	withUserName := (!onSubdomain && hostDomain == appDomain) || !cfg.IsCustomdomains()
189 
190-	for _, post := range posts {
191-		if post.Filename == "_readme.md" {
192-			parsedText, err := shared.ParseText(post.Text, imgs.ImgBaseURL(post.Username))
193-			if err != nil {
194-				logger.Error(err)
195-			}
196-			if parsedText.Title != "" {
197-				headerTxt.Title = parsedText.Title
198-			}
199-
200-			if parsedText.Description != "" {
201-				headerTxt.Bio = parsedText.Description
202-			}
203-
204-			break
205-		}
206-	}
207-
208 	feed := &feeds.Feed{
209 		Title:       headerTxt.Title,
210 		Link:        &feeds.Link{Href: cfg.FullBlogURL(username, onSubdomain, withUserName)},