Files
cms-server/data/user.go
Asai Neko 0f1c5b1293
All checks were successful
Client CMS Check Build (NixCN CMS) TeamCity build finished
Backend Check Build (NixCN CMS) TeamCity build finished
Fix new user create 500 error
Signed-off-by: Asai Neko <sugar@sne.moe>
2026-01-29 13:42:00 +08:00

200 lines
4.5 KiB
Go

package data
import (
"context"
"github.com/go-viper/mapstructure/v2"
"github.com/google/uuid"
"github.com/meilisearch/meilisearch-go"
"gorm.io/gorm"
)
type User struct {
Id uint `json:"id" gorm:"primarykey;autoincrement"`
UUID uuid.UUID `json:"uuid" gorm:"type:uuid;uniqueindex;not null"`
UserId uuid.UUID `json:"user_id" gorm:"type:uuid;uniqueindex;not null"`
Email string `json:"email" gorm:"type:varchar(255);uniqueindex;not null"`
Username string `json:"username" gorm:"type:varchar(255);uniqueindex;not null"`
Nickname string `json:"nickname" gorm:"type:text"`
Subtitle string `json:"subtitle" gorm:"type:text"`
Avatar string `json:"avatar" gorm:"type:text"`
Bio string `json:"bio" gorm:"type:text"`
PermissionLevel uint `json:"permission_level" gorm:"default:10;not null"`
AllowPublic bool `json:"allow_public" gorm:"default:false;not null"`
}
type UserSearchDoc struct {
UserId string `json:"user_id"`
Email string `json:"email"`
Username string `json:"username"`
Type string `json:"type"`
Nickname string `json:"nickname"`
Subtitle string `json:"subtitle"`
Avatar string `json:"avatar"`
}
func (self *User) SetEmail(s string) *User {
self.Email = s
return self
}
func (self *User) SetUsername(s string) *User {
self.Username = s
return self
}
func (self *User) SetNickname(s string) *User {
self.Nickname = s
return self
}
func (self *User) SetSubtitle(s string) *User {
self.Subtitle = s
return self
}
func (self *User) SetAvatar(s string) *User {
self.Avatar = s
return self
}
func (self *User) SetBio(s string) *User {
self.Bio = s
return self
}
func (self *User) SetPermissionLevel(s uint) *User {
self.PermissionLevel = s
return self
}
func (self *User) SetAllowPublic(s bool) *User {
self.AllowPublic = s
return self
}
func (self *User) GetByEmail(ctx context.Context, email *string) (*User, error) {
var user User
err := Database.WithContext(ctx).
Where("email = ?", email).
First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (self *User) GetByUserId(ctx context.Context, userId *uuid.UUID) (*User, error) {
var user User
err := Database.WithContext(ctx).
Where("user_id = ?", userId).
First(&user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &user, err
}
func (self *User) Create(ctx context.Context) error {
self.UUID = uuid.New()
self.UserId = uuid.New()
// DB transaction only
if err := Database.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(self).Error; err != nil {
return err
}
return nil
}); err != nil {
return err
}
// Search index (eventual consistency)
if err := self.UpdateSearchIndex(&ctx); err != nil {
// TODO: async retry / log
return err
}
return nil
}
func (self *User) UpdateByUserID(ctx context.Context, userId *uuid.UUID) error {
return Database.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&User{}).Where("user_id = ?", userId).Updates(&self).Error; err != nil {
return err
}
return nil
})
}
func (self *User) GetFullTable(ctx context.Context) (*[]User, error) {
var users []User
err := Database.WithContext(ctx).Find(&users).Error
if err != nil {
return nil, err
}
return &users, nil
}
func (self *User) FastListUsers(ctx context.Context, limit, offset *int64) (*[]UserSearchDoc, error) {
index := MeiliSearch.Index("user")
// Fast read from MeiliSearch, no DB involved
result, err := index.SearchWithContext(ctx, "", &meilisearch.SearchRequest{
Limit: *limit,
Offset: *offset,
})
if err != nil {
return nil, err
}
var list []UserSearchDoc
if err := mapstructure.Decode(result.Hits, &list); err != nil {
return nil, err
}
return &list, nil
}
func (self *User) UpdateSearchIndex(ctx *context.Context) error {
doc := UserSearchDoc{
UserId: self.UserId.String(),
Email: self.Email,
Username: self.Username,
Nickname: self.Nickname,
Subtitle: self.Subtitle,
Avatar: self.Avatar,
}
index := MeiliSearch.Index("user")
primaryKey := "user_id"
opts := &meilisearch.DocumentOptions{
PrimaryKey: &primaryKey,
}
if _, err := index.UpdateDocumentsWithContext(
*ctx,
[]UserSearchDoc{doc},
opts,
); err != nil {
return err
}
return nil
}
func (self *User) DeleteSearchIndex(ctx *context.Context) error {
index := MeiliSearch.Index("user")
_, err := index.DeleteDocumentWithContext(*ctx, self.UserId.String(), nil)
return err
}