package data import ( "github.com/go-viper/mapstructure/v2" "github.com/google/uuid" "github.com/meilisearch/meilisearch-go" "gorm.io/gorm" ) // Permission Level // Banned User: 0 // Normal User: 10 // Admin User: 20 // Super User: 30 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"` Nickname string `json:"nickname"` Subtitle string `json:"subtitle"` Avatar string `json:"avatar"` PermissionLevel uint `json:"permission_level" gorm:"default:10;not null"` } type UserSearchDoc struct { UserId string `json:"user_id"` Email string `json:"email"` Type string `json:"type"` Nickname string `json:"nickname"` Subtitle string `json:"subtitle"` Avatar string `json:"avatar"` PermissionLevel uint `json:"permission_level"` } func (self *User) GetByEmail(email string) (*User, error) { var user User err := Database. Where("email = ?", email). First(&user).Error if err != nil { if err == gorm.ErrRecordNotFound { return nil, nil } return nil, err } return &user, nil } func (self *User) GetByUserId(userId uuid.UUID) (*User, error) { var user User err := Database. 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() error { self.UUID = uuid.New() self.UserId = uuid.New() // DB transaction only if err := Database.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(); err != nil { // TODO: async retry / log return err } return nil } func (self *User) UpdateByUserID(userId uuid.UUID) error { return Database.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() (*[]User, error) { var users []User err := Database.Find(&users).Error if err != nil { return nil, err } return &users, nil } func (self *User) FastListUsers(limit, offset int64) (*[]UserSearchDoc, error) { index := MeiliSearch.Index("user") // Fast read from MeiliSearch, no DB involved result, err := index.Search("", &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() error { doc := UserSearchDoc{ UserId: self.UserId.String(), Email: self.Email, Nickname: self.Nickname, Subtitle: self.Subtitle, Avatar: self.Avatar, PermissionLevel: self.PermissionLevel, } index := MeiliSearch.Index("user") primaryKey := "user_id" opts := &meilisearch.DocumentOptions{ PrimaryKey: &primaryKey, } if _, err := index.UpdateDocuments( []UserSearchDoc{doc}, opts, ); err != nil { return err } return nil } func (self *User) DeleteSearchIndex() error { index := MeiliSearch.Index("user") _, err := index.DeleteDocument(self.UserId.String(), nil) return err }