Trending page (#128)
Fixes #46 Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/128 Co-authored-by: orangix <orangix@noreply.codeberg.org> Co-committed-by: orangix <orangix@noreply.codeberg.org>
This commit is contained in:
		
							parent
							
								
									d69d8dba0e
								
							
						
					
					
						commit
						a8abb43f3a
					
				|  | @ -0,0 +1,107 @@ | ||||||
|  | package api | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"codeberg.org/rimgo/rimgo/utils" | ||||||
|  | 	"github.com/patrickmn/go-cache" | ||||||
|  | 	"github.com/tidwall/gjson" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (client *Client) FetchTrending(section, sort, page string) ([]Submission, error) { | ||||||
|  | 	cacheData, found := client.Cache.Get(fmt.Sprintf("trending-%s-%s-%s", section, sort, page)) | ||||||
|  | 	if found { | ||||||
|  | 		return cacheData.([]Submission), nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req, err := http.NewRequest("GET", "https://api.imgur.com/post/v1/posts", nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return []Submission{}, err | ||||||
|  | 	} | ||||||
|  | 	utils.SetReqHeaders(req) | ||||||
|  | 
 | ||||||
|  | 	q := req.URL.Query() | ||||||
|  | 	q.Add("client_id", client.ClientID) | ||||||
|  | 	q.Add("include", "cover") | ||||||
|  | 	q.Add("page", page) | ||||||
|  | 
 | ||||||
|  | 	switch sort { | ||||||
|  | 	case "newest": | ||||||
|  | 		q.Add("filter[window]", "week") | ||||||
|  | 		q.Add("sort", "-time") | ||||||
|  | 	case "best": | ||||||
|  | 		q.Add("filter[window]", "all") | ||||||
|  | 		q.Add("sort", "-top") | ||||||
|  | 	case "popular": | ||||||
|  | 		fallthrough | ||||||
|  | 	default: | ||||||
|  | 		q.Add("filter[window]", "week") | ||||||
|  | 		q.Add("sort", "-viral") | ||||||
|  | 		sort = "popular" | ||||||
|  | 	} | ||||||
|  | 	switch section { | ||||||
|  | 	case "hot": | ||||||
|  | 		q.Add("filter[section]", "eq:hot") | ||||||
|  | 	case "new": | ||||||
|  | 		q.Add("filter[section]", "eq:new") | ||||||
|  | 	case "top": | ||||||
|  | 		q.Add("filter[section]", "eq:top") | ||||||
|  | 		q.Add("filter[window]", "day") | ||||||
|  | 	default: | ||||||
|  | 		q.Add("filter[section]", "eq:hot") | ||||||
|  | 		section = "hot" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req.URL.RawQuery = q.Encode() | ||||||
|  | 
 | ||||||
|  | 	res, err := http.DefaultClient.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return []Submission{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	body, err := io.ReadAll(res.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return []Submission{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	data := gjson.Parse(string(body)) | ||||||
|  | 
 | ||||||
|  | 	wg := sync.WaitGroup{} | ||||||
|  | 	posts := make([]Submission, 0) | ||||||
|  | 	data.ForEach( | ||||||
|  | 		func(key, value gjson.Result) bool { | ||||||
|  | 			wg.Add(1) | ||||||
|  | 
 | ||||||
|  | 			go func() { | ||||||
|  | 				defer wg.Done() | ||||||
|  | 				posts = append(posts, Submission{ | ||||||
|  | 					Id:    value.Get("id").String(), | ||||||
|  | 					Title: value.Get("title").String(), | ||||||
|  | 					Link:  strings.ReplaceAll(value.Get("url").String(), "https://imgur.com", ""), | ||||||
|  | 					Cover: Media{ | ||||||
|  | 						Id:   value.Get("cover_id").String(), | ||||||
|  | 						Type: value.Get("cover.type").String(), | ||||||
|  | 						Url:  strings.ReplaceAll(value.Get("cover.url").String(), "https://i.imgur.com", ""), | ||||||
|  | 					}, | ||||||
|  | 					Points:    value.Get("point_count").Int(), | ||||||
|  | 					Upvotes:   value.Get("upvote_count").Int(), | ||||||
|  | 					Downvotes: value.Get("downvote_count").Int(), | ||||||
|  | 					Comments:  value.Get("comment_count").Int(), | ||||||
|  | 					Views:     value.Get("view_count").Int(), | ||||||
|  | 					IsAlbum:   value.Get("is_album").Bool(), | ||||||
|  | 				}) | ||||||
|  | 			}() | ||||||
|  | 
 | ||||||
|  | 			return true | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	wg.Wait() | ||||||
|  | 
 | ||||||
|  | 	client.Cache.Set(fmt.Sprintf("trending-%s-%s-%s", section, sort, page), posts, cache.DefaultExpiration) | ||||||
|  | 	return posts, nil | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								main.go
								
								
								
								
							
							
						
						
									
										11
									
								
								main.go
								
								
								
								
							|  | @ -73,7 +73,7 @@ func main() { | ||||||
| 	if os.Getenv("ENV") == "dev" { | 	if os.Getenv("ENV") == "dev" { | ||||||
| 		app.Use("/static", filesystem.New(filesystem.Config{ | 		app.Use("/static", filesystem.New(filesystem.Config{ | ||||||
| 			MaxAge: 2592000, | 			MaxAge: 2592000, | ||||||
| 			Root: http.Dir("./static"), | 			Root:   http.Dir("./static"), | ||||||
| 		})) | 		})) | ||||||
| 		app.Get("/errors/429", func(c *fiber.Ctx) error { | 		app.Get("/errors/429", func(c *fiber.Ctx) error { | ||||||
| 			return c.Render("errors/429", nil) | 			return c.Render("errors/429", nil) | ||||||
|  | @ -91,11 +91,11 @@ func main() { | ||||||
| 			Root: http.FS(static.GetFiles()), | 			Root: http.FS(static.GetFiles()), | ||||||
| 		})) | 		})) | ||||||
| 		app.Use(cache.New(cache.Config{ | 		app.Use(cache.New(cache.Config{ | ||||||
| 			Expiration:           30 * time.Minute, | 			Expiration: 30 * time.Minute, | ||||||
| 			MaxBytes:             25000000, | 			MaxBytes:   25000000, | ||||||
| 			KeyGenerator: func(c *fiber.Ctx) string { | 			KeyGenerator: func(c *fiber.Ctx) string { | ||||||
|         return c.OriginalURL() | 				return c.OriginalURL() | ||||||
|     	}, | 			}, | ||||||
| 			CacheControl:         true, | 			CacheControl:         true, | ||||||
| 			StoreResponseHeaders: true, | 			StoreResponseHeaders: true, | ||||||
| 		})) | 		})) | ||||||
|  | @ -116,6 +116,7 @@ func main() { | ||||||
| 	app.Get("/about", pages.HandleAbout) | 	app.Get("/about", pages.HandleAbout) | ||||||
| 	app.Get("/privacy", pages.HandlePrivacy) | 	app.Get("/privacy", pages.HandlePrivacy) | ||||||
| 	app.Get("/search", pages.HandleSearch) | 	app.Get("/search", pages.HandleSearch) | ||||||
|  | 	app.Get("/trending", pages.HandleTrending) | ||||||
| 	app.Get("/a/:postID", pages.HandlePost) | 	app.Get("/a/:postID", pages.HandlePost) | ||||||
| 	app.Get("/a/:postID/embed", pages.HandleEmbed) | 	app.Get("/a/:postID/embed", pages.HandleEmbed) | ||||||
| 	app.Get("/t/:tag", pages.HandleTag) | 	app.Get("/t/:tag", pages.HandleTag) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,58 @@ | ||||||
|  | package pages | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"strconv" | ||||||
|  | 
 | ||||||
|  | 	"codeberg.org/rimgo/rimgo/utils" | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func HandleTrending(c *fiber.Ctx) error { | ||||||
|  | 	utils.SetHeaders(c) | ||||||
|  | 	c.Set("X-Frame-Options", "DENY") | ||||||
|  | 	c.Set("Cache-Control", "public,max-age=604800") | ||||||
|  | 	c.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; style-src 'unsafe-inline' 'self'; media-src 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") | ||||||
|  | 
 | ||||||
|  | 	page := "1" | ||||||
|  | 	if c.Query("page") != "" { | ||||||
|  | 		page = c.Query("page") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pageNumber, err := strconv.Atoi(c.Query("page")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		pageNumber = 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	section := c.Query("section") | ||||||
|  | 	switch section { | ||||||
|  | 	case "hot", "new", "top": | ||||||
|  | 	default: | ||||||
|  | 		section = "hot" | ||||||
|  | 	} | ||||||
|  | 	sort := c.Query("sort") | ||||||
|  | 	switch sort { | ||||||
|  | 	case "newest", "best", "popular": | ||||||
|  | 	default: | ||||||
|  | 		sort = "popular" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	displayPrevPage := true | ||||||
|  | 	if page == "1" { | ||||||
|  | 		displayPrevPage = false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	results, err := ApiClient.FetchTrending(section, sort, page) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return c.Render("trending", fiber.Map{ | ||||||
|  | 		"results":     results, | ||||||
|  | 		"section":     section, | ||||||
|  | 		"sort":        sort, | ||||||
|  | 		"page":        pageNumber, | ||||||
|  | 		"displayPrev": displayPrevPage, | ||||||
|  | 		"nextPage":    pageNumber + 1, | ||||||
|  | 		"prevPage":    pageNumber - 1, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path fill="currentColor" d="M200 64v104a8 8 0 0 1-16 0V83.31L69.66 197.66a8 8 0 0 1-11.32-11.32L172.69 72H88a8 8 0 0 1 0-16h104a8 8 0 0 1 8 8Z"/></svg> | ||||||
| After Width: | Height: | Size: 239 B | 
|  | @ -13,6 +13,7 @@ | ||||||
|   <header class="my-8 p-8 rounded-xl flex flex-col gap-4 items-center justify-center bg-gradient-to-r from-blue-400 to-emerald-400"> |   <header class="my-8 p-8 rounded-xl flex flex-col gap-4 items-center justify-center bg-gradient-to-r from-blue-400 to-emerald-400"> | ||||||
|     <h2 class="font-bold text-white text-2xl">The fast, private image viewer for Imgur.</h2> |     <h2 class="font-bold text-white text-2xl">The fast, private image viewer for Imgur.</h2> | ||||||
|     {{> partials/searchBar }} |     {{> partials/searchBar }} | ||||||
|  |     <a class="flex gap-1 items-center" href="/trending">Or see what's trending <img class="invert" src="/static/icons/PhArrowUpRight.svg" alt="" height="18" width="18" /></a> | ||||||
|   </header> |   </header> | ||||||
|    |    | ||||||
|   <main class="my-8"> |   <main class="my-8"> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,79 @@ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | 
 | ||||||
|  | <head> | ||||||
|  |   <title>Trending - rimgo</title> | ||||||
|  | 
 | ||||||
|  |   {{> partials/head }} | ||||||
|  | </head> | ||||||
|  | 
 | ||||||
|  | <body class="font-sans text-lg bg-slate-800 text-white"> | ||||||
|  |   {{> partials/nav }} | ||||||
|  | 
 | ||||||
|  |   <section class="my-4 w-full flex flex-col items-center"> | ||||||
|  |     {{> partials/searchBar }} | ||||||
|  |   </section> | ||||||
|  | 
 | ||||||
|  |   <header class="p-4 rounded-xl text-white mb-4 bg-gradient-to-r from-blue-400 to-emerald-400"> | ||||||
|  |     <div class="flex flex-col items-center justify-center text-center"> | ||||||
|  |       <h2 class="text-2xl font-extrabold">Trending</h2> | ||||||
|  |     </div> | ||||||
|  |     <div class="flex flex-col sm:flex-row sm:justify-between"> | ||||||
|  |       <div class="flex flex-col"> | ||||||
|  |         {{#equal section "hot"}} | ||||||
|  |         <a href="?section=hot&sort={{sort}}"><b>Hot</b></a> | ||||||
|  |         <a href="?section=new&sort={{sort}}">New</a> | ||||||
|  |         <a href="?section=top&sort={{sort}}">Top</a> | ||||||
|  |         {{/equal}} | ||||||
|  |         {{#equal section "new"}} | ||||||
|  |         <a href="?section=hot&sort={{sort}}">Hot</a> | ||||||
|  |         <a href="?section=new&sort={{sort}}"><b>New</b></a> | ||||||
|  |         <a href="?section=top&sort={{sort}}">Top</a> | ||||||
|  |         {{/equal}} | ||||||
|  |         {{#equal section "top"}} | ||||||
|  |         <a href="?section=hot&sort={{sort}}">Hot</a> | ||||||
|  |         <a href="?section=new&sort={{sort}}">New</a> | ||||||
|  |         <a href="?section=top&sort={{sort}}"><b>Top</b></a> | ||||||
|  |         {{/equal}} | ||||||
|  |       </div> | ||||||
|  |       <hr class="sm:hidden my-2" /> | ||||||
|  |       <div class="flex flex-col sm:items-end"> | ||||||
|  |         {{#equal sort "popular"}} | ||||||
|  |         <a href="?section={{section}}&sort=popular"><b>Popular</b></a> | ||||||
|  |         <a href="?section={{section}}&sort=newest">Newest</a> | ||||||
|  |         <a href="?section={{section}}&sort=best">Best</a> | ||||||
|  |         {{/equal}} | ||||||
|  |         {{#equal sort "newest"}} | ||||||
|  |         <a href="?section={{section}}&sort=popular">Popular</a> | ||||||
|  |         <a href="?section={{section}}&sort=newest"><b>Newest</b></a> | ||||||
|  |         <a href="?section={{section}}&sort=best">Best</a> | ||||||
|  |         {{/equal}} | ||||||
|  |         {{#equal sort "best"}} | ||||||
|  |         <a href="?section={{section}}&sort=popular">Popular</a> | ||||||
|  |         <a href="?section={{section}}&sort=newest">Newest</a> | ||||||
|  |         <a href="?section={{section}}&sort=best"><b>Best</b></a> | ||||||
|  |         {{/equal}} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </header> | ||||||
|  | 
 | ||||||
|  |   <main> | ||||||
|  |     <div class="posts"> | ||||||
|  |       {{#each results}} | ||||||
|  |       {{> partials/post }} | ||||||
|  |       {{/each}} | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="flex justify-between mt-4 font-bold"> | ||||||
|  |       {{#if displayPrev}} | ||||||
|  |       <a href="/trending?section={{section}}&sort={{sort}}&page={{prevPage}}">Previous page</a> | ||||||
|  |       {{/if}} | ||||||
|  |       <p>Page {{page}}</p> | ||||||
|  |       <a href="/trending?section={{section}}&sort={{sort}}&page={{nextPage}}">Next page</a> | ||||||
|  |     </div> | ||||||
|  |   </main> | ||||||
|  | 
 | ||||||
|  |   {{> partials/footer }} | ||||||
|  | </body> | ||||||
|  | 
 | ||||||
|  | </html> | ||||||
		Loading…
	
		Reference in New Issue