♻️ Use paperclip to store avatar and more
This commit is contained in:
@ -5,7 +5,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var DatabaseAutoActionRange = []any{
|
||||
var AutoMaintainRange = []any{
|
||||
&models.Account{},
|
||||
&models.AuthFactor{},
|
||||
&models.AccountProfile{},
|
||||
@ -23,7 +23,7 @@ var DatabaseAutoActionRange = []any{
|
||||
}
|
||||
|
||||
func RunMigration(source *gorm.DB) error {
|
||||
if err := source.AutoMigrate(DatabaseAutoActionRange...); err != nil {
|
||||
if err := source.AutoMigrate(AutoMaintainRange...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,8 @@ func (v *Server) Authenticate(_ context.Context, in *proto.AuthRequest) (*proto.
|
||||
Name: user.Name,
|
||||
Nick: user.Nick,
|
||||
Email: user.GetPrimaryEmail().Content,
|
||||
Avatar: fmt.Sprintf("https://%s/api/avatar/%s", viper.GetString("domain"), user.Avatar),
|
||||
Banner: fmt.Sprintf("https://%s/api/avatar/%s", viper.GetString("domain"), user.Banner),
|
||||
Avatar: fmt.Sprintf("%s/api/attachments/%s", viper.GetString("paperclip.endpoint"), user.Avatar),
|
||||
Banner: fmt.Sprintf("%s/api/attachments/%s", viper.GetString("paperclip.endpoint"), user.Banner),
|
||||
Description: &user.Description,
|
||||
},
|
||||
}, nil
|
||||
|
21
pkg/grpc/client.go
Normal file
21
pkg/grpc/client.go
Normal file
@ -0,0 +1,21 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
pcpb "git.solsynth.dev/hydrogen/paperclip/pkg/grpc/proto"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
var Attachments pcpb.AttachmentsClient
|
||||
|
||||
func ConnectPaperclip() error {
|
||||
addr := viper.GetString("paperclip.grpc_endpoint")
|
||||
if conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
|
||||
return err
|
||||
} else {
|
||||
Attachments = pcpb.NewAttachmentsClient(conn)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
@ -47,16 +45,6 @@ func (v Account) GetPrimaryEmail() AccountContact {
|
||||
return val
|
||||
}
|
||||
|
||||
func (v Account) GetAvatarPath() string {
|
||||
basepath := viper.GetString("content")
|
||||
return filepath.Join(basepath, v.Avatar)
|
||||
}
|
||||
|
||||
func (v Account) GetBannerPath() string {
|
||||
basepath := viper.GetString("content")
|
||||
return filepath.Join(basepath, v.Banner)
|
||||
}
|
||||
|
||||
type AccountContactType = int8
|
||||
|
||||
const (
|
||||
|
@ -38,7 +38,7 @@ func getUserinfo(c *fiber.Ctx) error {
|
||||
resp["preferred_username"] = data.Nick
|
||||
|
||||
if len(data.Avatar) > 0 {
|
||||
resp["picture"] = fmt.Sprintf("https://%s/api/avatar/%s", viper.GetString("domain"), data.Avatar)
|
||||
resp["picture"] = fmt.Sprintf("%s/api/attachments/%s", viper.GetString("paperclip.endpoint"), data.Avatar)
|
||||
}
|
||||
|
||||
return c.JSON(resp)
|
||||
|
@ -1,50 +1,34 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"context"
|
||||
"fmt"
|
||||
pcpb "git.solsynth.dev/hydrogen/paperclip/pkg/grpc/proto"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/database"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/grpc"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func getAvatar(c *fiber.Ctx) error {
|
||||
id := c.Params("avatarId")
|
||||
basepath := viper.GetString("content")
|
||||
|
||||
return c.SendFile(filepath.Join(basepath, id))
|
||||
}
|
||||
|
||||
func setAvatar(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
file, err := c.FormFile("avatar")
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
var data struct {
|
||||
AttachmentID string `json:"attachment"`
|
||||
}
|
||||
|
||||
var previous string
|
||||
if len(user.Avatar) > 0 {
|
||||
previous = user.GetAvatarPath()
|
||||
if _, err := grpc.Attachments.CheckAttachmentExists(context.Background(), &pcpb.AttachmentLookupRequest{
|
||||
Uuid: &data.AttachmentID,
|
||||
Usage: lo.ToPtr("p.avatar"),
|
||||
}); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("avatar was not found in repository: %v", err))
|
||||
}
|
||||
|
||||
user.Avatar = uuid.NewString()
|
||||
user.Avatar = data.AttachmentID
|
||||
|
||||
if err := c.SaveFile(file, user.GetAvatarPath()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
database.C.Save(&user)
|
||||
|
||||
// Clean up
|
||||
if len(previous) > 0 {
|
||||
basepath := viper.GetString("content")
|
||||
filepath := filepath.Join(basepath, previous)
|
||||
if info, err := os.Stat(filepath); err == nil && !info.IsDir() {
|
||||
os.Remove(filepath)
|
||||
}
|
||||
}
|
||||
if err := database.C.Save(&user).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
@ -52,31 +36,21 @@ func setAvatar(c *fiber.Ctx) error {
|
||||
|
||||
func setBanner(c *fiber.Ctx) error {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
file, err := c.FormFile("banner")
|
||||
if err != nil {
|
||||
return err
|
||||
var data struct {
|
||||
AttachmentID string `json:"attachment"`
|
||||
}
|
||||
|
||||
var previous string
|
||||
if len(user.Banner) > 0 {
|
||||
previous = user.GetBannerPath()
|
||||
if _, err := grpc.Attachments.CheckAttachmentExists(context.Background(), &pcpb.AttachmentLookupRequest{
|
||||
Uuid: &data.AttachmentID,
|
||||
Usage: lo.ToPtr("p.banner"),
|
||||
}); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("banner was not found in repository: %v", err))
|
||||
}
|
||||
|
||||
user.Banner = uuid.NewString()
|
||||
user.Banner = data.AttachmentID
|
||||
|
||||
if err := c.SaveFile(file, user.GetBannerPath()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
database.C.Save(&user)
|
||||
|
||||
// Clean up
|
||||
if len(previous) > 0 {
|
||||
basepath := viper.GetString("content")
|
||||
filepath := filepath.Join(basepath, previous)
|
||||
if info, err := os.Stat(filepath); err == nil && !info.IsDir() {
|
||||
os.Remove(filepath)
|
||||
}
|
||||
}
|
||||
if err := database.C.Save(&user).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
|
@ -66,8 +66,6 @@ func NewServer() {
|
||||
|
||||
api := A.Group("/api").Name("API")
|
||||
{
|
||||
api.Get("/avatar/:avatarId", getAvatar)
|
||||
|
||||
notify := api.Group("/notifications").Name("Notifications API")
|
||||
{
|
||||
notify.Get("/", authMiddleware, getNotifications)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/sujit-baniya/flash"
|
||||
"html/template"
|
||||
"time"
|
||||
@ -44,5 +45,7 @@ func selfUserinfoPage(c *fiber.Ctx) error {
|
||||
"birthday_at": birthday,
|
||||
"personal_page": template.HTML(markdown.Render(doc, renderer)),
|
||||
"userinfo": data,
|
||||
"avatar": fmt.Sprintf("%s/api/attachments/%s", viper.GetString("paperclip.endpoint"), data.Avatar),
|
||||
"banner": fmt.Sprintf("%s/api/attachments/%s", viper.GetString("paperclip.endpoint"), data.Banner),
|
||||
}, "views/layouts/user-center")
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/sujit-baniya/flash"
|
||||
"html/template"
|
||||
"time"
|
||||
@ -44,5 +45,7 @@ func otherUserinfoPage(c *fiber.Ctx) error {
|
||||
"birthday_at": birthday,
|
||||
"personal_page": template.HTML(markdown.Render(doc, renderer)),
|
||||
"userinfo": data,
|
||||
"avatar": fmt.Sprintf("%s/api/attachments/%s", viper.GetString("paperclip.endpoint"), data.Avatar),
|
||||
"banner": fmt.Sprintf("%s/api/attachments/%s", viper.GetString("paperclip.endpoint"), data.Banner),
|
||||
}, "views/layouts/user-center")
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ func DoAutoDatabaseCleanup() {
|
||||
log.Debug().Time("deadline", deadline).Msg("Now cleaning up entire database...")
|
||||
|
||||
var count int64
|
||||
for _, model := range database.DatabaseAutoActionRange {
|
||||
for _, model := range database.AutoMaintainRange {
|
||||
tx := database.C.Unscoped().Delete(model, "deleted_at >= ?", deadline)
|
||||
if tx.Error != nil {
|
||||
log.Error().Err(tx.Error).Msg("An error occurred when running auth context cleanup...")
|
||||
|
@ -5,13 +5,13 @@
|
||||
|
||||
<div class="banner-container">
|
||||
{{if gt (len .userinfo.Banner) 0}}
|
||||
<img src="/api/avatar/{{.userinfo.Banner}}" alt="Banner" class="banner">
|
||||
<img src="{{.banner}}" alt="Banner" class="banner">
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="left-part name-card">
|
||||
{{if gt (len .userinfo.Avatar) 0}}
|
||||
<img src="/api/avatar/{{.userinfo.Avatar}}" alt="Avatar" class="avatar">
|
||||
<img src="{{.avatar}}" alt="Avatar" class="avatar">
|
||||
{{else}}
|
||||
<div class="avatar empty">
|
||||
<span class="material-symbols-outlined">account_circle</span>
|
||||
|
@ -5,13 +5,13 @@
|
||||
|
||||
<div class="banner-container">
|
||||
{{if gt (len .userinfo.Banner) 0}}
|
||||
<img src="/api/avatar/{{.userinfo.Banner}}" alt="Banner" class="banner">
|
||||
<img src="{{.banner}}" alt="Banner" class="banner">
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="left-part name-card">
|
||||
{{if gt (len .userinfo.Avatar) 0}}
|
||||
<img src="/api/avatar/{{.userinfo.Avatar}}" alt="Avatar" class="avatar">
|
||||
<img src="{{.avatar}}" alt="Avatar" class="avatar">
|
||||
{{else}}
|
||||
<div class="avatar empty">
|
||||
<span class="material-symbols-outlined">account_circle</span>
|
||||
|
@ -9,33 +9,7 @@
|
||||
<div class="responsive-title-gap"></div>
|
||||
|
||||
<div class="personalize-actions">
|
||||
<md-filled-tonal-button class="personalize-action" data-target="avatar">
|
||||
Edit Avatar
|
||||
<span slot="icon" class="material-symbols-outlined">account_circle</span>
|
||||
</md-filled-tonal-button>
|
||||
<input
|
||||
hidden
|
||||
id="avatar-input"
|
||||
class="block-field"
|
||||
name="avatar"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
placeholder="Avatar"
|
||||
>
|
||||
|
||||
<md-filled-tonal-button class="personalize-action" data-target="banner">
|
||||
Edit Banner
|
||||
<span slot="icon" class="material-symbols-outlined">background_replace</span>
|
||||
</md-filled-tonal-button>
|
||||
<input
|
||||
hidden
|
||||
id="banner-input"
|
||||
class="block-field"
|
||||
name="banner"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
placeholder="Banner"
|
||||
>
|
||||
<span>We doesn't support edit avatar / banner through Hydrogen.Passport web yet. Go try our Solian App!</span>
|
||||
</div>
|
||||
|
||||
<form class="action-form" action="/users/me/personalize" method="POST">
|
||||
@ -133,35 +107,4 @@
|
||||
font-size: 20px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll(".personalize-action").forEach((element) => {
|
||||
element.addEventListener("click", (_) => {
|
||||
document.getElementById(`${element.getAttribute("data-target")}-input`).click();
|
||||
})
|
||||
})
|
||||
|
||||
document.getElementById("avatar-input").addEventListener("input", (evt) => {
|
||||
if (!evt.target.files) return
|
||||
const data = new FormData();
|
||||
data.set("avatar", evt.target.files[0])
|
||||
fetch("/api/users/me/avatar", {
|
||||
method: "PUT",
|
||||
body: data,
|
||||
}).then(() => {
|
||||
location.href = "/users/me"
|
||||
})
|
||||
})
|
||||
document.getElementById("banner-input").addEventListener("input", (evt) => {
|
||||
if (!evt.target.files) return
|
||||
const data = new FormData();
|
||||
data.set("banner", evt.target.files[0])
|
||||
fetch("/api/users/me/banner", {
|
||||
method: "PUT",
|
||||
body: data,
|
||||
}).then(() => {
|
||||
location.href = "/users/me"
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</style>
|
Reference in New Issue
Block a user