User comments support (#131)
#10 Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/131 Co-authored-by: orangix <uleo8b8g@anonaddy.me> Co-committed-by: orangix <uleo8b8g@anonaddy.me>
This commit is contained in:
		
							parent
							
								
									e5e1c38058
								
							
						
					
					
						commit
						34ecc2a32b
					
				|  | @ -17,6 +17,7 @@ import ( | |||
| type Comment struct { | ||||
| 	Comments  []Comment | ||||
| 	User      User | ||||
| 	Post      Submission | ||||
| 	Id        string | ||||
| 	Comment   string | ||||
| 	Upvotes   int64 | ||||
|  | @ -130,6 +131,7 @@ func parseComment(data gjson.Result) Comment { | |||
| 			Username: data.Get("account.username").String(), | ||||
| 			Avatar:   userAvatar, | ||||
| 		}, | ||||
| 		Post:      parseSubmission(data.Get("post")), | ||||
| 		Id:        data.Get("id").String(), | ||||
| 		Comment:   comment, | ||||
| 		Upvotes:   data.Get("upvote_count").Int(), | ||||
|  |  | |||
							
								
								
									
										133
									
								
								api/user.go
								
								
								
								
							
							
						
						
									
										133
									
								
								api/user.go
								
								
								
								
							|  | @ -8,6 +8,7 @@ import ( | |||
| 	"time" | ||||
| 
 | ||||
| 	"codeberg.org/rimgo/rimgo/utils" | ||||
| 	"github.com/patrickmn/go-cache" | ||||
| 	"github.com/tidwall/gjson" | ||||
| ) | ||||
| 
 | ||||
|  | @ -89,41 +90,7 @@ func (client *Client) FetchSubmissions(username string, sort string, page string | |||
| 			go func() { | ||||
| 				defer wg.Done() | ||||
| 
 | ||||
| 				coverData := value.Get("images.#(id==\"" + value.Get("cover").String() + "\")") | ||||
| 				cover := Media{ | ||||
| 					Id:          value.Get("id").String(), | ||||
| 					Description: value.Get("description").String(), | ||||
| 					Type:        strings.Split(value.Get("type").String(), "/")[0], | ||||
| 					Url:         strings.ReplaceAll(value.Get("link").String(), "https://i.imgur.com", ""), | ||||
| 				} | ||||
| 				if coverData.Exists() { | ||||
| 					cover = Media{ | ||||
| 						Id:          coverData.Get("id").String(), | ||||
| 						Description: coverData.Get("description").String(), | ||||
| 						Type:        strings.Split(coverData.Get("type").String(), "/")[0], | ||||
| 						Url:         strings.ReplaceAll(coverData.Get("link").String(), "https://i.imgur.com", ""), | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				id := value.Get("id").String() | ||||
| 				 | ||||
| 				link := "/a/" + id | ||||
| 				if value.Get("in_gallery").Bool() { | ||||
| 					link = "/gallery/" + id | ||||
| 				} | ||||
| 
 | ||||
| 				submissions = append(submissions, Submission{ | ||||
| 					Id:    id, | ||||
| 					Link:  link, | ||||
| 					Title: value.Get("title").String(), | ||||
| 					Cover: cover, | ||||
| 					Points:    value.Get("points").Int(), | ||||
| 					Upvotes:   value.Get("ups").Int(), | ||||
| 					Downvotes: value.Get("downs").Int(), | ||||
| 					Comments:  value.Get("comment_count").Int(), | ||||
| 					Views:     value.Get("views").Int(), | ||||
| 					IsAlbum:   value.Get("is_album").Bool(), | ||||
| 				}) | ||||
| 				submissions = append(submissions, parseSubmission(value)) | ||||
| 			}() | ||||
| 
 | ||||
| 			return true | ||||
|  | @ -134,3 +101,99 @@ func (client *Client) FetchSubmissions(username string, sort string, page string | |||
| 	client.Cache.Set(username+"-submissions", submissions, 15*time.Minute) | ||||
| 	return submissions, nil | ||||
| } | ||||
| 
 | ||||
| func (client *Client) FetchUserComments(username string) ([]Comment, error) { | ||||
| 	cacheData, found := client.Cache.Get(username + "-usercomments") | ||||
| 	if found { | ||||
| 		return cacheData.([]Comment), nil | ||||
| 	} | ||||
| 
 | ||||
| 	req, err := http.NewRequest("GET", "https://api.imgur.com/comment/v1/comments", nil) | ||||
| 	if err != nil { | ||||
| 		return []Comment{}, err | ||||
| 	} | ||||
| 	utils.SetReqHeaders(req) | ||||
| 
 | ||||
| 	q := req.URL.Query() | ||||
| 	q.Add("client_id", client.ClientID) | ||||
| 	q.Add("filter[account]", "eq:"+username) | ||||
| 	q.Add("include", "account,post") | ||||
| 	q.Add("sort", "new") | ||||
| 
 | ||||
| 	req.URL.RawQuery = q.Encode() | ||||
| 
 | ||||
| 	res, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return []Comment{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	body, err := io.ReadAll(res.Body) | ||||
| 	if err != nil { | ||||
| 		return []Comment{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	data := gjson.Parse(string(body)) | ||||
| 
 | ||||
| 	comments := make([]Comment, 0) | ||||
| 	data.Get("data").ForEach( | ||||
| 		func(key, value gjson.Result) bool { | ||||
| 			comments = append(comments, parseComment(value)) | ||||
| 			return true | ||||
| 		}, | ||||
| 	) | ||||
| 
 | ||||
| 	client.Cache.Set(username+"-usercomments", comments, cache.DefaultExpiration) | ||||
| 	return comments, nil | ||||
| } | ||||
| 
 | ||||
| func parseSubmission(value gjson.Result) Submission { | ||||
| 	var cover Media | ||||
| 	c := value.Get("cover") | ||||
| 	coverData := value.Get("images.#(id==\"" + c.String() + "\")") | ||||
| 	switch { | ||||
| 	case c.Type == gjson.String && coverData.Exists(): | ||||
| 		cover = Media{ | ||||
| 			Id:          coverData.Get("id").String(), | ||||
| 			Description: coverData.Get("description").String(), | ||||
| 			Type:        strings.Split(coverData.Get("type").String(), "/")[0], | ||||
| 			Url:         strings.ReplaceAll(coverData.Get("link").String(), "https://i.imgur.com", ""), | ||||
| 		} | ||||
| 	// This case is when fetching comments
 | ||||
| 	case c.Type != gjson.Null: | ||||
| 		cover = Media{ | ||||
| 			Id:  c.Get("id").String(), | ||||
| 			Url: strings.ReplaceAll(c.Get("url").String(), "https://i.imgur.com", ""), | ||||
| 		} | ||||
| 		// Replace with thumbnails here because it's easier.
 | ||||
| 		if strings.HasSuffix(cover.Url, ".mp4") { | ||||
| 			cover.Url = cover.Url[:len(cover.Url)-3] + "webp" | ||||
| 		} | ||||
| 	default: | ||||
| 		cover = Media{ | ||||
| 			Id:          value.Get("id").String(), | ||||
| 			Description: value.Get("description").String(), | ||||
| 			Type:        strings.Split(value.Get("type").String(), "/")[0], | ||||
| 			Url:         strings.ReplaceAll(value.Get("link").String(), "https://i.imgur.com", ""), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	id := value.Get("id").String() | ||||
| 
 | ||||
| 	link := "/a/" + id | ||||
| 	if value.Get("in_gallery").Bool() { | ||||
| 		link = "/gallery/" + id | ||||
| 	} | ||||
| 
 | ||||
| 	return Submission{ | ||||
| 		Id:        id, | ||||
| 		Link:      link, | ||||
| 		Title:     value.Get("title").String(), | ||||
| 		Cover:     cover, | ||||
| 		Points:    value.Get("points").Int(), | ||||
| 		Upvotes:   value.Get("ups").Int(), | ||||
| 		Downvotes: value.Get("downs").Int(), | ||||
| 		Comments:  value.Get("comment_count").Int(), | ||||
| 		Views:     value.Get("views").Int(), | ||||
| 		IsAlbum:   value.Get("is_album").Bool(), | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										3
									
								
								main.go
								
								
								
								
							
							
						
						
									
										3
									
								
								main.go
								
								
								
								
							|  | @ -121,8 +121,9 @@ func main() { | |||
| 	app.Get("/a/:postID/embed", pages.HandleEmbed) | ||||
| 	app.Get("/t/:tag", pages.HandleTag) | ||||
| 	app.Get("/t/:tag/:postID", pages.HandlePost) | ||||
| 	app.Get("/user/:userID", pages.HandleUser) | ||||
| 	app.Get("/r/:sub/:postID", pages.HandlePost) | ||||
| 	app.Get("/user/:userID", pages.HandleUser) | ||||
| 	app.Get("/user/:userID/comments", pages.HandleUserComments) | ||||
| 	app.Get("/user/:userID/cover", pages.HandleUserCover) | ||||
| 	app.Get("/user/:userID/avatar", pages.HandleUserAvatar) | ||||
| 	app.Get("/gallery/:postID", pages.HandlePost) | ||||
|  |  | |||
|  | @ -55,3 +55,39 @@ func HandleUser(c *fiber.Ctx) error { | |||
| 		"prevPage":    pageNumber - 1, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func HandleUserComments(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'; media-src 'self'; style-src 'unsafe-inline' 'self'; img-src 'self'; manifest-src 'self'; block-all-mixed-content") | ||||
| 
 | ||||
| 	user, err := ApiClient.FetchUser(c.Params("userID")) | ||||
| 	if err != nil && err.Error() == "ratelimited by imgur" { | ||||
| 		return c.Status(429).Render("errors/429", fiber.Map{ | ||||
| 			"path": c.Path(), | ||||
| 		}) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if user.Username == "" { | ||||
| 		return c.Status(404).Render("errors/404", nil) | ||||
| 	} | ||||
| 
 | ||||
| 	comments, err := ApiClient.FetchUserComments(c.Params("userID")) | ||||
| 	if err != nil && err.Error() == "ratelimited by imgur" { | ||||
| 		c.Status(429) | ||||
| 		return c.Render("errors/429", fiber.Map{ | ||||
| 			"path": c.Path(), | ||||
| 		}) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return c.Render("userComments", fiber.Map{ | ||||
| 		"user":     user, | ||||
| 		"comments": comments, | ||||
| 	}) | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,22 @@ | |||
| <a href="{{Post.Link}}"> | ||||
|   <div class="sm:grid gap-4 w-full" style="grid-template-columns: 120px 1fr;"> | ||||
|     <img class="object-cover block w-full h-[300px] sm:w-[120px] sm:h-[140px] rounded-lg rounded-b-none sm:rounded-b-lg" src="{{this.Post.Cover.Url}}" alt=""> | ||||
|     <div class="flex flex-col gap-2 bg-slate-600 p-4 rounded-lg rounded-t-none sm:rounded-t-lg w-full"> | ||||
|       <div class="flex flex-col h-full"> | ||||
|         <p>{{{this.Comment}}}</p> | ||||
|         <div class="flex-grow"></div> | ||||
|         <div class="flex gap-2"> | ||||
|           <span title="{{this.CreatedAt}}">{{this.RelTime}}</span> | ||||
|           {{#if this.DeletedAt}} | ||||
|           <span class="text-md">(deleted {{this.DeletedAt}})</span> | ||||
|           {{/if}} | ||||
|           | | ||||
|           <img class="invert icon" src="/static/icons/PhArrowFatUp.svg" alt="Likes" width="24px" height="24px"> | ||||
|           {{this.Upvotes}} | ||||
|           <img class="invert icon" src="/static/icons/PhArrowFatDown.svg" alt="Dislikes" width="24px" height="24px"> | ||||
|           {{this.Downvotes}} | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </a> | ||||
|  | @ -21,6 +21,11 @@ | |||
|         <h2 class="font-bold text-2xl">{{user.Username}}</h2> | ||||
|         <p>{{user.Points}} pts · {{user.CreatedAt}}</p> | ||||
|       </div> | ||||
|       <hr class="sm:border-0 flex-grow"> | ||||
|       <div class="flex flex-col sm:items-end"> | ||||
|         <a href="/user/{{user.Username}}"><b>Submissions</b></a> | ||||
|         <a href="/user/{{user.Username}}/comments">Comments</a> | ||||
|       </div> | ||||
|     </div> | ||||
|     <p class="mt-2">{{user.Bio}}</p> | ||||
|   </header> | ||||
|  |  | |||
|  | @ -0,0 +1,44 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| 
 | ||||
| <head> | ||||
|   <title>{{user.Username}}'s comments - 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" style="background-image: url('{{user.Cover}}');"> | ||||
|     <div class="flex flex-col sm:flex-row items-center gap-2"> | ||||
|       <img class="rounded-full" src="{{user.Avatar}}" width="72" height="72"> | ||||
|       <div class="items-center sm:items-start text-center sm:text-left"> | ||||
|         <h2 class="font-bold text-2xl">{{user.Username}}</h2> | ||||
|         <p>{{user.Points}} pts · {{user.CreatedAt}}</p> | ||||
|       </div> | ||||
|       <hr class="sm:border-0 flex-grow"> | ||||
|       <div class="flex flex-col items-center sm:items-end"> | ||||
|         <a href="/user/{{user.Username}}">Submissions</a> | ||||
|         <a href="/user/{{user.Username}}/comments"><b>Comments</b></a> | ||||
|       </div> | ||||
|     </div> | ||||
|     <p class="mt-2">{{user.Bio}}</p> | ||||
|   </header> | ||||
| 
 | ||||
|   <main> | ||||
|     <div class="comments flex flex-col gap-4"> | ||||
|       {{#each comments}} | ||||
|       {{> partials/contextComment }} | ||||
|       {{/each}} | ||||
|     </div> | ||||
|   </main> | ||||
| 
 | ||||
|   {{> partials/footer }} | ||||
| </body> | ||||
| 
 | ||||
| </html> | ||||
		Loading…
	
		Reference in New Issue