12 Commits

Author SHA1 Message Date
video-prize-ranch
5438365e50 Fix previous/next page buttons 2023-08-29 18:58:11 -04:00
orangix
3d707c561e cache per page 2023-08-29 23:58:30 +02:00
orangix
9fd38bed03 added user favorites 2023-08-20 07:58:38 +02:00
video-prize-ranch
877ee7faa9 Change max age on cache-control in production 2023-08-16 11:46:41 -04:00
video-prize-ranch
d490581c44 Fix overflowing text on mobile 2023-08-16 11:07:32 -04:00
orangix
730aead3a1 fix header on user submissions 2023-08-16 10:26:35 -04:00
video-prize-ranch
2024f7f6cf Add projectsegfau.lt instances to instances.json 2023-08-15 17:05:07 -04:00
orangix
34ecc2a32b 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>
2023-08-15 20:38:14 +00:00
Arya K
e5e1c38058 Update location of Project Segfault instance in README (#130)
Reviewed-on: https://codeberg.org/rimgo/rimgo/pulls/130
Co-authored-by: Arya K <arya@projectsegfau.lt>
Co-committed-by: Arya K <arya@projectsegfau.lt>
2023-08-15 20:30:58 +00:00
video-prize-ranch
493a17385a Remove rimgo.fascinated.cc (closes #125) 2023-08-14 18:59:35 -04:00
orangix
a8abb43f3a 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>
2023-08-14 22:47:59 +00:00
video-prize-ranch
d69d8dba0e Switch image to ko 2023-08-14 14:13:19 -04:00
18 changed files with 653 additions and 66 deletions

View File

@@ -25,7 +25,7 @@ archives:
checksum: checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'
kos: kos:
- repository: codeberg.org/rimgo/ko-test - repository: codeberg.org/rimgo/rimgo
tags: tags:
- '{{.Version}}' - '{{.Version}}'
- latest - latest
@@ -34,6 +34,7 @@ kos:
platforms: platforms:
- linux/amd64 - linux/amd64
- linux/arm64 - linux/arm64
sbom: none
gitea_urls: gitea_urls:
api: https://codeberg.org/api/v1 api: https://codeberg.org/api/v1
download: https://codeberg.org download: https://codeberg.org

View File

@@ -86,11 +86,10 @@ Open an issue to have your instance listed here! See the rules for the instance
| [imgur.010032.xyz](https://imgur.010032.xyz/) | 🇰🇷 KR | Oracle Cloud | ✅ Data not collected | | | [imgur.010032.xyz](https://imgur.010032.xyz/) | 🇰🇷 KR | Oracle Cloud | ✅ Data not collected | |
| [rimgo.kling.gg](https://rimgo.kling.gg/) | 🇳🇱 NL | RamNode | ✅ Data not collected | | | [rimgo.kling.gg](https://rimgo.kling.gg/) | 🇳🇱 NL | RamNode | ✅ Data not collected | |
| [i.01r.xyz](https://i.01r.xyz/) | 🇺🇸 US | Cloudflare | ✅ Data not collected | | | [i.01r.xyz](https://i.01r.xyz/) | 🇺🇸 US | Cloudflare | ✅ Data not collected | |
| [rimgo.projectsegfau.lt](https://rimgo.projectsegfau.lt/) | 🇱🇺 LU, 🇺🇸 US, 🇮🇳 IN | See below | ✅ Data not collected | | | [rimgo.projectsegfau.lt](https://rimgo.projectsegfau.lt/) | 🇫🇷 FR, 🇺🇸 US, 🇮🇳 IN | See below | ✅ Data not collected | |
| [rimgo.eu.projectsegfau.lt](https://rimgo.projectsegfau.lt/) | 🇱🇺 LU | FranTech Solutions | ✅ Data not collected | | | [rimgo.eu.projectsegfau.lt](https://rimgo.eu.projectsegfau.lt/) | 🇫🇷 FR | Orange S.A. | ✅ Data not collected | |
| [rimgo.us.projectsegfau.lt](https://rimgo.projectsegfau.lt/) | 🇺🇸 US | DigitalOcean | ✅ Data not collected | | | [rimgo.us.projectsegfau.lt](https://rimgo.us.projectsegfau.lt/) | 🇺🇸 US | Racknerd | ✅ Data not collected | |
| [rimgo.in.projectsegfau.lt](https://rimgo.projectsegfau.lt/) | 🇮🇳 IN | Airtel | ✅ Data not collected | | | [rimgo.in.projectsegfau.lt](https://rimgo.in.projectsegfau.lt/) | 🇮🇳 IN | Airtel | ✅ Data not collected | |
| [rimgo.fascinated.cc](https://rimgo.fascinated.cc/) | 🇺🇸 US | Cloudflare | ✅ Data not collected | |
| [rimgo.whateveritworks.org](https://rimgo.whateveritworks.org/) | 🇩🇪 DE | Cloudflare | ✅ Data not collected | | | [rimgo.whateveritworks.org](https://rimgo.whateveritworks.org/) | 🇩🇪 DE | Cloudflare | ✅ Data not collected | |
| [rimgo.nohost.network](https://rimgo.nohost.network/) | 🇲🇽 MX | Telmex | ✅ Data not collected | | | [rimgo.nohost.network](https://rimgo.nohost.network/) | 🇲🇽 MX | Telmex | ✅ Data not collected | |
| [rimgo.catsarch.com](https://rimgo.catsarch.com/) | 🇺🇸 US | Comcast | ✅ Data not collected | Self-hosted, provider is ISP | | [rimgo.catsarch.com](https://rimgo.catsarch.com/) | 🇺🇸 US | Comcast | ✅ Data not collected | Self-hosted, provider is ISP |

View File

@@ -16,14 +16,15 @@ import (
type Comment struct { type Comment struct {
Comments []Comment Comments []Comment
User User User User
Post Submission
Id string Id string
Comment string Comment string
Upvotes int64 Upvotes int64
Downvotes int64 Downvotes int64
Platform string Platform string
CreatedAt string CreatedAt string
RelTime string RelTime string
UpdatedAt string UpdatedAt string
DeletedAt string DeletedAt string
} }
@@ -55,7 +56,7 @@ func (client *Client) FetchComments(galleryID string) ([]Comment, error) {
) )
wg.Wait() wg.Wait()
client.Cache.Set(galleryID + "-comments", comments, cache.DefaultExpiration) client.Cache.Set(galleryID+"-comments", comments, cache.DefaultExpiration)
return comments, nil return comments, nil
} }
@@ -130,13 +131,14 @@ func parseComment(data gjson.Result) Comment {
Username: data.Get("account.username").String(), Username: data.Get("account.username").String(),
Avatar: userAvatar, Avatar: userAvatar,
}, },
Post: parseSubmission(data.Get("post")),
Id: data.Get("id").String(), Id: data.Get("id").String(),
Comment: comment, Comment: comment,
Upvotes: data.Get("upvote_count").Int(), Upvotes: data.Get("upvote_count").Int(),
Downvotes: data.Get("downvote_count").Int(), Downvotes: data.Get("downvote_count").Int(),
Platform: data.Get("platform").String(), Platform: data.Get("platform").String(),
CreatedAt: createdAt, CreatedAt: createdAt,
RelTime: humanize.Time(createdTime), RelTime: humanize.Time(createdTime),
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
DeletedAt: deletedAt, DeletedAt: deletedAt,
} }

107
api/trending.go Normal file
View File

@@ -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
}

View File

@@ -8,6 +8,7 @@ import (
"time" "time"
"codeberg.org/rimgo/rimgo/utils" "codeberg.org/rimgo/rimgo/utils"
"github.com/patrickmn/go-cache"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )
@@ -64,12 +65,12 @@ func (client *Client) FetchUser(username string) (User, error) {
CreatedAt: createdTime.Format("January 2, 2006"), CreatedAt: createdTime.Format("January 2, 2006"),
} }
client.Cache.Set(username + "-user", user, 1*time.Hour) client.Cache.Set(username+"-user", user, 1*time.Hour)
return user, nil return user, nil
} }
func (client *Client) FetchSubmissions(username string, sort string, page string) ([]Submission, error) { func (client *Client) FetchSubmissions(username string, sort string, page string) ([]Submission, error) {
cacheData, found := client.Cache.Get(username + "-submissions") cacheData, found := client.Cache.Get(username + "-submissions-" + sort + page)
if found { if found {
return cacheData.([]Submission), nil return cacheData.([]Submission), nil
} }
@@ -89,41 +90,7 @@ func (client *Client) FetchSubmissions(username string, sort string, page string
go func() { go func() {
defer wg.Done() defer wg.Done()
coverData := value.Get("images.#(id==\"" + value.Get("cover").String() + "\")") submissions = append(submissions, parseSubmission(value))
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(),
})
}() }()
return true return true
@@ -131,6 +98,151 @@ func (client *Client) FetchSubmissions(username string, sort string, page string
) )
wg.Wait() wg.Wait()
client.Cache.Set(username + "-submissions", submissions, 15*time.Minute) client.Cache.Set(username+"-submissions-"+sort+page, submissions, 15*time.Minute)
return submissions, nil return submissions, nil
} }
func (client *Client) FetchUserFavorites(username string, sort string, page string) ([]Submission, error) {
cacheData, found := client.Cache.Get(username + "-favorites-" + sort + page)
if found {
return cacheData.([]Submission), nil
}
req, err := http.NewRequest("GET", "https://api.imgur.com/3/account/"+username+"/gallery_favorites/"+page+"/"+sort, nil)
if err != nil {
return []Submission{}, err
}
utils.SetReqHeaders(req)
q := req.URL.Query()
q.Add("client_id", client.ClientID)
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))
submissions := []Submission{}
wg := sync.WaitGroup{}
data.Get("data").ForEach(
func(key, value gjson.Result) bool {
wg.Add(1)
go func() {
defer wg.Done()
submissions = append(submissions, parseSubmission(value))
}()
return true
},
)
wg.Wait()
client.Cache.Set(username+"-favorites-"+sort+page, 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(),
}
}

View File

@@ -105,11 +105,25 @@
"cloudflare": true "cloudflare": true
}, },
{ {
"url": "https://rimgo.fascinated.cc", "url": "https://rimgo.eu.projectsegfau.lt",
"countries": [
"fr"
],
"cloudflare": false
},
{
"url": "https://rimgo.us.projectsegfau.lt",
"countries": [ "countries": [
"us" "us"
], ],
"cloudflare": true "cloudflare": false
},
{
"url": "https://rimgo.in.projectsegfau.lt",
"countries": [
"in"
],
"cloudflare": false
}, },
{ {
"url": "https://rimgo.whateveritworks.org", "url": "https://rimgo.whateveritworks.org",

17
main.go
View File

@@ -72,7 +72,6 @@ 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,
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 {
@@ -88,14 +87,15 @@ func main() {
}) })
} else { } else {
app.Use("/static", filesystem.New(filesystem.Config{ app.Use("/static", filesystem.New(filesystem.Config{
Root: http.FS(static.GetFiles()), MaxAge: 2592000,
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,12 +116,15 @@ 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)
app.Get("/t/:tag/:postID", pages.HandlePost) app.Get("/t/:tag/:postID", pages.HandlePost)
app.Get("/user/:userID", pages.HandleUser)
app.Get("/r/:sub/:postID", pages.HandlePost) app.Get("/r/:sub/:postID", pages.HandlePost)
app.Get("/user/:userID", pages.HandleUser)
app.Get("/user/:userID/favorites", pages.HandleUserFavorites)
app.Get("/user/:userID/comments", pages.HandleUserComments)
app.Get("/user/:userID/cover", pages.HandleUserCover) app.Get("/user/:userID/cover", pages.HandleUserCover)
app.Get("/user/:userID/avatar", pages.HandleUserAvatar) app.Get("/user/:userID/avatar", pages.HandleUserAvatar)
app.Get("/gallery/:postID", pages.HandlePost) app.Get("/gallery/:postID", pages.HandlePost)

58
pages/trending.go Normal file
View File

@@ -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,
})
}

View File

@@ -51,7 +51,92 @@ func HandleUser(c *fiber.Ctx) error {
"user": user, "user": user,
"submissions": submissions, "submissions": submissions,
"page": page, "page": page,
"nextPage": pageNumber + 1, "nextPage": pageNumber + 1,
"prevPage": pageNumber - 1, "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,
})
}
func HandleUserFavorites(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")
page := "0"
if c.Query("page") != "" {
page = c.Query("page")
}
pageNumber, err := strconv.Atoi(c.Query("page"))
if err != nil {
pageNumber = 0
}
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)
}
favorites, err := ApiClient.FetchUserFavorites(c.Params("userID"), "newest", page)
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("userFavorites", fiber.Map{
"user": user,
"favorites": favorites,
"page": page,
"nextPage": pageNumber + 1,
"prevPage": pageNumber - 1,
}) })
} }

View File

@@ -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

View File

@@ -7,7 +7,7 @@ body {
} }
p a { p a {
text-decoration: underline @apply break-words underline
} }
.posts { .posts {

View File

@@ -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">

View File

@@ -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 class="md-container">{{{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>

View File

@@ -25,7 +25,7 @@
</header> </header>
<main> <main>
<div class="flex gap-4 items-center my-4"> <div class="flex flex-col gap-2 md:flex-row md:gap-4 md:items-center my-4">
{{#if post.User.Username}} {{#if post.User.Username}}
<a href="/user/{{post.User.Username}}" class="flex gap-2 items-center"> <a href="/user/{{post.User.Username}}" class="flex gap-2 items-center">
<img src="{{post.User.Avatar}}" class="rounded-full" width="36" height="36" /> <img src="{{post.User.Avatar}}" class="rounded-full" width="36" height="36" />

79
views/trending.hbs Normal file
View File

@@ -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>

View File

@@ -15,12 +15,18 @@
</section> </section>
<header class="p-4 rounded-xl text-white mb-4" style="background-image: url('{{user.Cover}}');"> <header class="p-4 rounded-xl text-white mb-4" style="background-image: url('{{user.Cover}}');">
<div class="flex items-center gap-2"> <div class="flex flex-col sm:flex-row items-center gap-2">
<img class="rounded-full" src="{{user.Avatar}}" width="72" height="72"> <img class="rounded-full" src="{{user.Avatar}}" width="72" height="72">
<div> <div class="items-center sm:items-start text-center sm:text-left">
<h2 class="font-bold text-2xl">{{user.Username}}</h2> <h2 class="font-bold text-2xl">{{user.Username}}</h2>
<p>{{user.Points}} pts · {{user.CreatedAt}}</p> <p>{{user.Points}} pts · {{user.CreatedAt}}</p>
</div> </div>
<hr class="sm:border-0 flex-grow">
<div class="flex flex-col items-center sm:items-end">
<a href="/user/{{user.Username}}"><b>Submissions</b></a>
<a href="/user/{{user.Username}}/favorites">Favorites</a>
<a href="/user/{{user.Username}}/comments">Comments</a>
</div>
</div> </div>
<p class="mt-2">{{user.Bio}}</p> <p class="mt-2">{{user.Bio}}</p>
</header> </header>

45
views/userComments.hbs Normal file
View File

@@ -0,0 +1,45 @@
<!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}}/favorites">Favorites</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>

52
views/userFavorites.hbs Normal file
View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{user.Username}}'s favorites - 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}}/favorites"><b>Favorites</b></a>
<a href="/user/{{user.Username}}/comments">Comments</a>
</div>
</div>
<p class="mt-2">{{user.Bio}}</p>
</header>
<main>
<div class="posts">
{{#each favorites}}
{{> partials/post }}
{{/each}}
</div>
<div class="flex mt-4 font-bold justify-between">
{{#noteq page "0" }}
<a href="{{channel.RelUrl}}?page={{prevPage}}">Previous page</a>
{{/noteq}}
<a href="{{channel.RelUrl}}?page={{nextPage}}">Next page</a>
</div>
</main>
{{> partials/footer }}
</body>
</html>