200 lines
4.5 KiB
Go
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
|
|
}
|