Initial users support

This commit is contained in:
video-prize-ranch 2022-01-27 20:41:10 -05:00
parent fdd66853f9
commit 8d21d1a576
No known key found for this signature in database
GPG Key ID: D8EAA4C5B12A7281
15 changed files with 344 additions and 66 deletions

View File

@ -75,7 +75,7 @@ func ParseComment(data gjson.Result) types.Comment {
return types.Comment{ return types.Comment{
Comments: comments, Comments: comments,
User: types.User{ User: types.User{
Id: data.Get("account.id").String(), Id: data.Get("account.id").Int(),
Username: data.Get("account.username").String(), Username: data.Get("account.username").String(),
Avatar: userAvatar, Avatar: userAvatar,
}, },

View File

@ -1,16 +1,8 @@
export const fetchUserInfo = async (userID: string): Promise<UserResult> => {
// https://api.imgur.com/account/v1/accounts/hughjaniss?client_id=${CLIENT_ID}
const response = await get(
`https://api.imgur.com/account/v1/accounts/${userID.toLowerCase()}?client_id=${CONFIG.imgur_client_id}&include=`,
);
return JSON.parse(response.body);
}
export const fetchUserPosts = async (userID: string, sort: Sorting = 'newest'): Promise<Post[]> => { export const fetchUserPosts = async (userID: string, sort: Sorting = 'newest'): Promise<Post[]> => {
/* eslint-disable max-len */ /* eslint-disable max-len */
// https://api.imgur.com/3/account/mombotnumber5/submissions/0/newest?album_previews=1&client_id=${CLIENT_ID} // https://api.imgur.com/3/account/mombotnumber5/submissions/0/newest?album_previews=1&client_id=${CLIENT_ID}
const response = await get( const response = await get(
`https://api.imgur.com/3/account/${userID.toLowerCase()}/submissions/0/${sort}?album_previews=1&client_id=${CONFIG.imgur_client_id}`, ``,
); );
return JSON.parse(response.body).data; return JSON.parse(response.body).data;
/* eslint-enable max-len */ /* eslint-enable max-len */

91
api/user.go Normal file
View File

@ -0,0 +1,91 @@
package api
import (
"encoding/json"
"io"
"net/http"
"strings"
"sync"
"time"
"codeberg.org/video-prize-ranch/rimgo/types"
"github.com/spf13/viper"
"github.com/tidwall/gjson"
)
func FetchUser(username string) (types.User, error) {
res, err := http.Get("https://api.imgur.com/account/v1/accounts/" + username + "?client_id=" + viper.GetString("RIMGU_IMGUR_CLIENT_ID"))
if err != nil {
return types.User{}, err
}
body, err := io.ReadAll(res.Body)
if err != nil {
return types.User{}, err
}
var user types.User
err = json.Unmarshal(body, &user)
if err != nil {
return types.User{}, err
}
user.Cover = strings.ReplaceAll(user.Cover, "https://imgur.com", "")
user.Avatar = strings.ReplaceAll(user.Avatar, "https://i.imgur.com", "")
createdTime, _ := time.Parse("2006-01-02T15:04:05Z", user.CreatedAt)
user.CreatedAt = createdTime.Format("January 2, 2006")
return user, nil
}
func FetchSubmissions(username string, sort string, page string) ([]types.Submission, error) {
res, err := http.Get("https://api.imgur.com/3/account/" + username + "/submissions/" + page + "/" + sort + "?album_previews=1&client_id=" + viper.GetString("RIMGU_IMGUR_CLIENT_ID"))
if err != nil {
return []types.Submission{}, err
}
body, err := io.ReadAll(res.Body)
if err != nil {
return []types.Submission{}, err
}
data := gjson.Parse(string(body))
submissions := []types.Submission{}
wg := sync.WaitGroup{}
data.Get("data").ForEach(
func(key, value gjson.Result) bool {
wg.Add(1)
go func() {
defer wg.Done()
cover := value.Get("images.#(id==\"" + value.Get("cover").String() + "\")")
submissions = append(submissions, types.Submission{
Id: value.Get("id").String(),
Link: strings.ReplaceAll(value.Get("link").String(), "https://imgur.com", ""),
Title: value.Get("title").String(),
Cover: types.Media{
Id: cover.Get("id").String(),
Description: cover.Get("description").String(),
Type: strings.Split(cover.Get("type").String(), "/")[0],
Url: strings.ReplaceAll(cover.Get("link").String(), "https://i.imgur.com", ""),
},
Points: cover.Get("points").Int(),
Upvotes: cover.Get("ups").Int(),
Downvotes: cover.Get("downs").Int(),
Comments: cover.Get("comment_count").Int(),
Views: cover.Get("views").Int(),
IsAlbum: cover.Get("is_album").Bool(),
})
}()
return true
},
)
wg.Wait()
return submissions, nil
}

View File

@ -1,7 +1,7 @@
package main package main
import ( import (
"log" "fmt"
"net/http" "net/http"
"codeberg.org/video-prize-ranch/rimgo/pages" "codeberg.org/video-prize-ranch/rimgo/pages"
@ -28,7 +28,7 @@ func main() {
err := viper.ReadInConfig() err := viper.ReadInConfig()
if err != nil { if err != nil {
log.Fatal(err) fmt.Println(err)
} }
engine := handlebars.NewFileSystem(http.FS(views.GetFiles()), ".hbs") engine := handlebars.NewFileSystem(http.FS(views.GetFiles()), ".hbs")
@ -53,8 +53,9 @@ func main() {
app.Get("/:baseName.:extension", pages.HandleMedia) app.Get("/:baseName.:extension", pages.HandleMedia)
app.Get("/a/:galleryID", pages.HandleGallery) app.Get("/a/:galleryID", pages.HandleGallery)
//app.Get("/t/:tagID", pages.HandleAlbum) //app.Get("/t/:tagID", pages.HandleAlbum)
/*app.Get("/user/:userID", pages.HandleUser) app.Get("/user/:userID", pages.HandleUser)
app.Get("/user/:userID/cover", pages.HandleUserCover)*/ app.Get("/user/:userID/cover", pages.HandleUserCover)
app.Get("/user/:userID/avatar", pages.HandleUserAvatar)
app.Get("/gallery/:galleryID", pages.HandleGallery) app.Get("/gallery/:galleryID", pages.HandleGallery)
app.Listen(":" + viper.GetString("RIMGU_PORT")) app.Listen(":" + viper.GetString("RIMGU_PORT"))

View File

@ -1,16 +1,14 @@
package pages package pages
import "github.com/gofiber/fiber/v2" import (
"codeberg.org/video-prize-ranch/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
func FrontpageHandler(c *fiber.Ctx) error { func FrontpageHandler(c *fiber.Ctx) error {
c.Set("Cache-Control", "public,max-age=1800") utils.SetHeaders(c)
c.Set("X-Frame-Options", "DENY") c.Set("Cache-Control", "public,max-age=31557600")
c.Set("Referrer-Policy", "no-referrer") c.Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'; font-src 'self'; block-all-mixed-content")
c.Set("X-Content-Type-Options", "nosniff")
c.Set("X-Robots-Tag", "noindex, noimageindex, nofollow")
c.Set("Strict-Transport-Security", "max-age=31557600")
c.Set("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()")
c.Set("Content-Security-Policy", "default-src 'none'; style-src 'self'; script-src 'none'; img-src 'self'; font-src 'self'; block-all-mixed-content; manifest-src 'self'")
return c.Render("frontpage", fiber.Map{}) return c.Render("frontpage", fiber.Map{})
} }

View File

@ -3,18 +3,13 @@ package pages
import ( import (
"codeberg.org/video-prize-ranch/rimgo/api" "codeberg.org/video-prize-ranch/rimgo/api"
"codeberg.org/video-prize-ranch/rimgo/types" "codeberg.org/video-prize-ranch/rimgo/types"
"codeberg.org/video-prize-ranch/rimgo/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func HandleGallery(c *fiber.Ctx) error { func HandleGallery(c *fiber.Ctx) error {
c.Set("Cache-Control", "public,max-age=604800") utils.SetHeaders(c)
c.Set("X-Frame-Options", "DENY") c.Set("Content-Security-Policy", "default-src 'none'; media-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; block-all-mixed-content")
c.Set("Referrer-Policy", "no-referrer")
c.Set("X-Content-Type-Options", "nosniff")
c.Set("X-Robots-Tag", "noindex, noimageindex, nofollow")
c.Set("Strict-Transport-Security", "max-age=31557600")
c.Set("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()")
c.Set("Content-Security-Policy", "default-src 'none'; media-src 'self'; style-src 'self'; script-src 'none'; img-src 'self'; font-src 'self'; block-all-mixed-content; manifest-src 'self'")
album, err := api.FetchAlbum(c.Params("galleryID")) album, err := api.FetchAlbum(c.Params("galleryID"))
if err != nil { if err != nil {
@ -23,10 +18,13 @@ func HandleGallery(c *fiber.Ctx) error {
comments := []types.Comment{} comments := []types.Comment{}
if album.SharedWithCommunity { if album.SharedWithCommunity {
c.Set("Cache-Control", "public,max-age=604800")
comments, err = api.FetchComments(c.Params("galleryID")) comments, err = api.FetchComments(c.Params("galleryID"))
if err != nil { if err != nil {
return err return err
} }
} else {
c.Set("Cache-Control", "public,max-age=31557600")
} }
return c.Render("gallery", fiber.Map{ return c.Render("gallery", fiber.Map{

View File

@ -1,27 +1,3 @@
export const handleUser = async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
// https://imgur.com/user/MomBotNumber5
if (!CONFIG.use_api) {
return 'User page disabled. Rimgu administrator needs to enable API for this to work.';
}
const userID = request.params.userID;
const user = await fetchUserInfo(userID);
const posts = await fetchUserPosts(userID);
return h.view('posts', {
posts,
user,
pageTitle: CONFIG.page_title,
util,
});
};
export const handleUserCover = async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
const userID = request.params.userID;
const result = await fetchMedia(`/user/${userID}/cover?maxwidth=2560`);
const response = h.response(result.rawBody)
.header('Content-Type', result.headers["content-type"] || `image/jpeg`);
return response;
};
export const handleTag = async (request: Hapi.Request, h: Hapi.ResponseToolkit) => { export const handleTag = async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
// https://imgur.com/t/funny // https://imgur.com/t/funny
if (!CONFIG.use_api) { if (!CONFIG.use_api) {

View File

@ -4,17 +4,35 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"codeberg.org/video-prize-ranch/rimgo/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func HandleMedia(c *fiber.Ctx) error { func HandleMedia(c *fiber.Ctx) error {
res, err := http.Get("https://i.imgur.com/" + c.Params("baseName") + "." + c.Params("extension")) c.Set("Cache-Control", "public,max-age=31557600")
return handleMedia(c, "https://i.imgur.com/" + c.Params("baseName") + "." + c.Params("extension"))
}
func HandleUserCover(c *fiber.Ctx) error {
c.Set("Cache-Control", "public,max-age=604800")
return handleMedia(c, "https://imgur.com/user/" + c.Params("userID") + "/cover?maxwidth=2560")
};
func HandleUserAvatar(c *fiber.Ctx) error {
c.Set("Cache-Control", "public,max-age=604800")
return handleMedia(c, "https://imgur.com/user/" + c.Params("userID") + "/avatar")
};
func handleMedia(c *fiber.Ctx, url string) error {
utils.SetHeaders(c)
c.Set("Content-Security-Policy", "default-src 'none'; media-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; block-all-mixed-content")
res, err := http.Get(url)
if err != nil { if err != nil {
return err return err
} }
c.Set("Content-Type", res.Header.Get("Content-Type")); c.Set("Content-Type", res.Header.Get("Content-Type"));
contentLen, _ := strconv.Atoi(res.Header.Get("Content-Length")) contentLen, _ := strconv.Atoi(res.Header.Get("Content-Length"))
c.SendStream(res.Body, contentLen) return c.SendStream(res.Body, contentLen)
return nil
} }

42
pages/user.go Normal file
View File

@ -0,0 +1,42 @@
package pages
import (
"sync"
"codeberg.org/video-prize-ranch/rimgo/api"
"codeberg.org/video-prize-ranch/rimgo/types"
"codeberg.org/video-prize-ranch/rimgo/utils"
"github.com/gofiber/fiber/v2"
)
func HandleUser(c *fiber.Ctx) error {
utils.SetHeaders(c)
c.Set("Cache-Control", "public,max-age=604800")
c.Set("Content-Security-Policy", "default-src 'none'; media-src 'self'; style-src 'unsafe-inline' 'self'; img-src 'self'; font-src 'self'; block-all-mixed-content")
wg := sync.WaitGroup{}
wg.Add(2)
user, err := types.User{}, error(nil)
go func() {
defer wg.Done()
user, err = api.FetchUser(c.Params("userID"))
}()
if err != nil {
return err
}
submissions, err := []types.Submission{}, error(nil)
go func() {
defer wg.Done()
submissions, err = api.FetchSubmissions(c.Params("userID"), "newest", "0")
}()
if err != nil {
return err
}
wg.Wait()
return c.Render("user", fiber.Map{
"user": user,
"submissions": submissions,
})
}

63
static/css/user.css Normal file
View File

@ -0,0 +1,63 @@
main {
margin: 0 12vw;
}
h2, p {
margin: 0;
}
.userMeta {
padding: 2vw;
border-radius: 8px;
}
.userMeta__upper {
display: flex;
gap: 10px;
align-items: center;
}
.userMeta__upper__bio {
margin-top: 1em;
}
.pfp {
border-radius: 100%;
}
.posts {
margin-top: 1em;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-auto-rows: 1fr;
gap: 1rem;
}
.post {
border-radius: 8px;
background-color: #3f3f3f;
font-weight: 400;
}
img,
video:not(:fullscreen) {
object-fit: cover;
aspect-ratio: 1;
}
.post__title {
margin: 0 6px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.post__meta {
padding: 6px;
display: flex;
gap: 10px;
}
.post__meta .material-icons-outlined {
font-size: 1rem;
}

View File

@ -1,8 +1,24 @@
package types package types
type User struct { type User struct {
Id string Id int64 `json:"id"`
Username string Bio string `json:"bio"`
Avatar string Username string `json:"username"`
CreatedAt string Points int64 `json:"reputation_count"`
Cover string `json:"cover_url"`
Avatar string `json:"avatar_url"`
CreatedAt string `json:"created_at"`
}
type Submission struct {
Id string
Title string
Link string
Cover Media
Points int64
Upvotes int64
Downvotes int64
Comments int64
Views int64
IsAlbum bool
} }

12
utils/setHeaders.go Normal file
View File

@ -0,0 +1,12 @@
package utils
import "github.com/gofiber/fiber/v2"
func SetHeaders(c *fiber.Ctx) {
c.Set("X-Frame-Options", "DENY")
c.Set("Referrer-Policy", "no-referrer")
c.Set("X-Content-Type-Options", "nosniff")
c.Set("X-Robots-Tag", "noindex, noimageindex, nofollow")
c.Set("Strict-Transport-Security", "max-age=31557600")
c.Set("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()")
}

View File

@ -1,14 +1,14 @@
<div class="comment"> <div class="comment">
<div class="comment__user"> <div class="comment__user">
<img src="{{this.User.Avatar}}" class="pfp" width="24" height="24" loading="lazy"> <img src="{{this.User.Avatar}}" class="pfp" width="24" height="24" loading="lazy">
<!--<a href="/user/{{this.User.Username}}">--> <a href="/user/{{this.User.Username}}">
<p class="comment__user__username"><b>{{this.User.Username}}</b></p> <p class="comment__user__username"><b>{{this.User.Username}}</b></p>
<!--</a>--> </a>
</div> </div>
<div> <div>
{{{this.Comment}}} {{{this.Comment}}}
<p title="{{this.CreatedAt}}"> <p>
{{this.RelTime}} <span title="{{this.CreatedAt}}">{{this.RelTime}}</span>
{{#if this.DeletedAt}} {{#if this.DeletedAt}}
<span class="comment__updatedDate">(deleted {{this.DeletedAt}})</span> <span class="comment__updatedDate">(deleted {{this.DeletedAt}})</span>
{{/if}} {{/if}}

32
views/partials/post.hbs Normal file
View File

@ -0,0 +1,32 @@
<a href="{{Link}}">
<div class="post">
{{#equal Cover.Type "video"}}
<video fullscreen controls loop poster="/{{Cover.Id}}.webp" preload="none" width="100%" height="100%">
<source src="{{Cover.Url}}" type="video/mp4" />
</video>
{{/equal}}
{{#equal Cover.Type "image"}}
<img src="{{Cover.Url}}" loading="lazy" width="100%" height="100%">
{{/equal}}
<p class="post__title">{{Title}}</p>
<div class="post__meta">
<p>
<span class="material-icons-outlined">
arrow_upward
</span>
{{Points}}
</p>
<p>
<span class="material-icons-outlined">
comment
</span>
{{Comments}}
<p>
<span class="material-icons-outlined">
visibility
</span>
{{Views}}
</p>
</div>
</div>
</a>

39
views/user.hbs Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{user.Username}} - rimgo</title>
{{> partials/head }}
<link rel="stylesheet" href="/static/fonts/Material-Icons-Outlined.css" />
<link rel="stylesheet" href="/static/css/user.css" />
</head>
<body>
{{> partials/header }}
<main>
<div class="userMeta" style="background-image: url('{{user.Cover}}');">
<div class="userMeta__upper">
<img class="pfp" src="{{user.Avatar}}" width="72" height="72">
<div>
<h2>{{user.Username}}</h2>
<p>{{user.Points}} pts · {{user.CreatedAt}}</p>
</div>
</div>
<p class="userMeta__upper__bio">{{user.Bio}}</p>
</div>
<div class="posts">
{{#each submissions}}
{{> partials/post }}
{{/each}}
</div>
</main>
{{> partials/footer }}
</body>
</html>