Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/database/migrations/30_user_profile_version.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE users
DROP COLUMN profile_version;
2 changes: 2 additions & 0 deletions cmd/database/migrations/30_user_profile_version.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE users
ADD COLUMN profile_version VARCHAR(16) NOT NULL DEFAULT '' AFTER avatar_url;
40 changes: 37 additions & 3 deletions db/users.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package db

import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"

"github.com/Quaver/api2/enums"
"github.com/redis/go-redis/v9"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
"strconv"
"strings"
"time"
)

type User struct {
Expand All @@ -28,6 +31,7 @@ type User struct {
Country string `gorm:"column:country" json:"country"`
IP string `gorm:"column:ip" json:"-"`
AvatarUrl *string `gorm:"column:avatar_url" json:"avatar_url"`
ProfileVersion string `gorm:"column:profile_version" json:"profile_version"`
Twitter *string `gorm:"column:twitter" json:"twitter"`
Title *string `gorm:"column:title" json:"title"`
CheckedPreviousAchievements bool `gorm:"column:checked_previous_achievements" json:"-"`
Expand Down Expand Up @@ -128,6 +132,36 @@ func (u *User) SetClanTagAndColor() error {
return nil
}

func generateUserProfileVersion() (string, error) {
bytes := make([]byte, 8)

if _, err := rand.Read(bytes); err != nil {
return "", err
}

return hex.EncodeToString(bytes), nil
}

// UpdateUserProfileVersion rotates the version used to cache-bust user profile assets.
func UpdateUserProfileVersion(user *User) error {
version, err := generateUserProfileVersion()
if err != nil {
return err
}

result := SQL.Model(&User{}).
Where("id = ?", user.Id).
Update("profile_version", version)

if result.Error != nil {
return result.Error
}

user.ProfileVersion = version

return nil
}

// Insert Inserts a new user to the database
func (u *User) Insert() error {
err := SQL.Transaction(func(tx *gorm.DB) error {
Expand Down
64 changes: 64 additions & 0 deletions db/users_profile_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package db

import (
"encoding/hex"
"encoding/json"
"testing"
)

func TestGenerateUserProfileVersion(t *testing.T) {
first, err := generateUserProfileVersion()
if err != nil {
t.Fatal(err)
}

second, err := generateUserProfileVersion()
if err != nil {
t.Fatal(err)
}

if len(first) != userProfileVersionBytes*2 {
t.Fatalf("expected profile version length %d, got %d", userProfileVersionBytes*2, len(first))
}

if _, err := hex.DecodeString(first); err != nil {
t.Fatalf("profile version is not valid hexadecimal: %v", err)
}

if first == second {
t.Fatal("expected consecutive profile versions to differ")
}
}

func TestUserProfileVersionIsSerializedWhenEmpty(t *testing.T) {
data, err := json.Marshal(&User{ProfileVersion: ""})
if err != nil {
t.Fatal(err)
}

var payload map[string]interface{}
if err := json.Unmarshal(data, &payload); err != nil {
t.Fatal(err)
}

value, exists := payload["profile_version"]
if !exists {
t.Fatal("expected profile_version to be present in serialized user")
}

if value != "" {
t.Fatalf("expected empty profile_version, got %v", value)
}
}

func TestUserBeforeCreateLeavesProfileVersionEmpty(t *testing.T) {
user := &User{}

if err := user.BeforeCreate(nil); err != nil {
t.Fatal(err)
}

if user.ProfileVersion != "" {
t.Fatalf("expected profile_version to remain empty, got %q", user.ProfileVersion)
}
}
7 changes: 6 additions & 1 deletion handlers/user_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"github.com/Quaver/api2/azure"
"github.com/Quaver/api2/cache"
"github.com/Quaver/api2/db"
"github.com/Quaver/api2/enums"
"github.com/gin-gonic/gin"
"net/http"
Expand All @@ -29,13 +30,17 @@ func UploadUserProfileCover(c *gin.Context) *APIError {
}

_ = cache.RemoveCacheServerProfileCover(user.Id)

err := azure.Client.UploadFile("profile-covers", fmt.Sprintf("%v.jpg", user.Id), file)

if err != nil {
return APIErrorServerError("Failed to upload file", err)
}

if err := db.UpdateUserProfileVersion(user); err != nil {
return APIErrorServerError("Failed to update profile version", err)
}

c.JSON(http.StatusOK, gin.H{
"message": "Your profile cover has been successfully updated!",
})
Expand Down
Loading