feat: 更新SproutGate前后端代码
This commit is contained in:
321
sproutgate-backend/internal/storage/dbmodels.go
Normal file
321
sproutgate-backend/internal/storage/dbmodels.go
Normal file
@@ -0,0 +1,321 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"sproutgate-backend/internal/models"
|
||||
)
|
||||
|
||||
// ─── JSON 序列化辅助类型 ────────────────────────────────────────────────────────
|
||||
|
||||
// StringSlice 将 []string 序列化为 JSON 字符串存入 TEXT 列。
|
||||
type StringSlice []string
|
||||
|
||||
func (s StringSlice) Value() (driver.Value, error) {
|
||||
if s == nil {
|
||||
return "[]", nil
|
||||
}
|
||||
b, err := json.Marshal([]string(s))
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
func (s *StringSlice) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*s = StringSlice{}
|
||||
return nil
|
||||
}
|
||||
var raw []byte
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
raw = v
|
||||
case string:
|
||||
raw = []byte(v)
|
||||
default:
|
||||
return fmt.Errorf("StringSlice.Scan: unsupported type %T", value)
|
||||
}
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
*s = StringSlice{}
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(raw, (*[]string)(s))
|
||||
}
|
||||
|
||||
// AuthClientSlice 将 []models.AuthClientEntry 序列化为 JSON 存入 TEXT 列。
|
||||
type AuthClientSlice []models.AuthClientEntry
|
||||
|
||||
func (a AuthClientSlice) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return "[]", nil
|
||||
}
|
||||
b, err := json.Marshal([]models.AuthClientEntry(a))
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
func (a *AuthClientSlice) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*a = AuthClientSlice{}
|
||||
return nil
|
||||
}
|
||||
var raw []byte
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
raw = v
|
||||
case string:
|
||||
raw = []byte(v)
|
||||
default:
|
||||
return fmt.Errorf("AuthClientSlice.Scan: unsupported type %T", value)
|
||||
}
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
*a = AuthClientSlice{}
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(raw, (*[]models.AuthClientEntry)(a))
|
||||
}
|
||||
|
||||
// ─── GORM DB 模型 ─────────────────────────────────────────────────────────────
|
||||
|
||||
// DBUser 对应数据库 users 表。
|
||||
type DBUser struct {
|
||||
Account string `gorm:"primaryKey;column:account;size:255"`
|
||||
PasswordHash string `gorm:"column:password_hash;not null"`
|
||||
Username string `gorm:"column:username;not null;size:255"`
|
||||
Email string `gorm:"column:email;not null;size:255;index"`
|
||||
Level int `gorm:"column:level;default:0"`
|
||||
SproutCoins int `gorm:"column:sprout_coins;default:0"`
|
||||
LastCheckInDate string `gorm:"column:last_check_in_date;size:20"`
|
||||
LastCheckInAt string `gorm:"column:last_check_in_at;size:128"`
|
||||
LastVisitDate string `gorm:"column:last_visit_date;size:20"`
|
||||
LastVisitAt string `gorm:"column:last_visit_at;size:128"`
|
||||
LastVisitIP string `gorm:"column:last_visit_ip;size:45"`
|
||||
LastVisitDisplayLocation string `gorm:"column:last_visit_display_location;size:512"`
|
||||
CheckInTimes StringSlice `gorm:"column:check_in_times;type:mediumtext"`
|
||||
VisitTimes StringSlice `gorm:"column:visit_times;type:mediumtext"`
|
||||
SecondaryEmails StringSlice `gorm:"column:secondary_emails;type:text"`
|
||||
Phone string `gorm:"column:phone;size:50"`
|
||||
AvatarURL string `gorm:"column:avatar_url;size:1024"`
|
||||
WebsiteURL string `gorm:"column:website_url;size:1024"`
|
||||
Bio string `gorm:"column:bio;type:text"`
|
||||
CreatedAt string `gorm:"column:created_at;size:50;not null;<-:create"`
|
||||
UpdatedAt string `gorm:"column:updated_at;size:50;not null"`
|
||||
Banned bool `gorm:"column:banned;default:false"`
|
||||
BanReason string `gorm:"column:ban_reason;type:text"`
|
||||
BannedAt string `gorm:"column:banned_at;size:50"`
|
||||
TokenEpoch int64 `gorm:"column:token_epoch;default:0"`
|
||||
AuthClients AuthClientSlice `gorm:"column:auth_clients;type:mediumtext"`
|
||||
}
|
||||
|
||||
func (DBUser) TableName() string { return "users" }
|
||||
|
||||
// DBUserFromRecord 将领域模型转换为 GORM 模型(导出供 migrate 工具使用)。
|
||||
func DBUserFromRecord(r models.UserRecord) DBUser {
|
||||
return dbUserFromRecord(r)
|
||||
}
|
||||
|
||||
func dbUserFromRecord(r models.UserRecord) DBUser {
|
||||
return DBUser{
|
||||
Account: r.Account,
|
||||
PasswordHash: r.PasswordHash,
|
||||
Username: r.Username,
|
||||
Email: r.Email,
|
||||
Level: r.Level,
|
||||
SproutCoins: r.SproutCoins,
|
||||
LastCheckInDate: r.LastCheckInDate,
|
||||
LastCheckInAt: r.LastCheckInAt,
|
||||
LastVisitDate: r.LastVisitDate,
|
||||
LastVisitAt: r.LastVisitAt,
|
||||
LastVisitIP: r.LastVisitIP,
|
||||
LastVisitDisplayLocation: r.LastVisitDisplayLocation,
|
||||
CheckInTimes: StringSlice(r.CheckInTimes),
|
||||
VisitTimes: StringSlice(r.VisitTimes),
|
||||
SecondaryEmails: StringSlice(r.SecondaryEmails),
|
||||
Phone: r.Phone,
|
||||
AvatarURL: r.AvatarURL,
|
||||
WebsiteURL: r.WebsiteURL,
|
||||
Bio: r.Bio,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
Banned: r.Banned,
|
||||
BanReason: r.BanReason,
|
||||
BannedAt: r.BannedAt,
|
||||
TokenEpoch: r.TokenEpoch,
|
||||
AuthClients: AuthClientSlice(r.AuthClients),
|
||||
}
|
||||
}
|
||||
|
||||
func (d DBUser) toRecord() models.UserRecord {
|
||||
return models.UserRecord{
|
||||
Account: d.Account,
|
||||
PasswordHash: d.PasswordHash,
|
||||
Username: d.Username,
|
||||
Email: d.Email,
|
||||
Level: d.Level,
|
||||
SproutCoins: d.SproutCoins,
|
||||
LastCheckInDate: d.LastCheckInDate,
|
||||
LastCheckInAt: d.LastCheckInAt,
|
||||
LastVisitDate: d.LastVisitDate,
|
||||
LastVisitAt: d.LastVisitAt,
|
||||
LastVisitIP: d.LastVisitIP,
|
||||
LastVisitDisplayLocation: d.LastVisitDisplayLocation,
|
||||
CheckInTimes: []string(d.CheckInTimes),
|
||||
VisitTimes: []string(d.VisitTimes),
|
||||
SecondaryEmails: []string(d.SecondaryEmails),
|
||||
Phone: d.Phone,
|
||||
AvatarURL: d.AvatarURL,
|
||||
WebsiteURL: d.WebsiteURL,
|
||||
Bio: d.Bio,
|
||||
CreatedAt: d.CreatedAt,
|
||||
UpdatedAt: d.UpdatedAt,
|
||||
Banned: d.Banned,
|
||||
BanReason: d.BanReason,
|
||||
BannedAt: d.BannedAt,
|
||||
TokenEpoch: d.TokenEpoch,
|
||||
AuthClients: []models.AuthClientEntry(d.AuthClients),
|
||||
}
|
||||
}
|
||||
|
||||
// DBPendingUser 对应 pending_users 表。
|
||||
type DBPendingUser struct {
|
||||
Account string `gorm:"primaryKey;column:account;size:255"`
|
||||
PasswordHash string `gorm:"column:password_hash;not null"`
|
||||
Username string `gorm:"column:username;not null;size:255"`
|
||||
Email string `gorm:"column:email;not null;size:255;index"`
|
||||
CodeHash string `gorm:"column:code_hash;not null"`
|
||||
ExpiresAt string `gorm:"column:expires_at;size:50;not null"`
|
||||
CreatedAt string `gorm:"column:created_at;size:50;not null;<-:create"`
|
||||
InviteCode string `gorm:"column:invite_code;size:32"`
|
||||
}
|
||||
|
||||
func (DBPendingUser) TableName() string { return "pending_users" }
|
||||
|
||||
func dbPendingFromModel(p models.PendingUser) DBPendingUser {
|
||||
return DBPendingUser{
|
||||
Account: p.Account,
|
||||
PasswordHash: p.PasswordHash,
|
||||
Username: p.Username,
|
||||
Email: p.Email,
|
||||
CodeHash: p.CodeHash,
|
||||
ExpiresAt: p.ExpiresAt,
|
||||
CreatedAt: p.CreatedAt,
|
||||
InviteCode: p.InviteCode,
|
||||
}
|
||||
}
|
||||
|
||||
func (d DBPendingUser) toModel() models.PendingUser {
|
||||
return models.PendingUser{
|
||||
Account: d.Account,
|
||||
PasswordHash: d.PasswordHash,
|
||||
Username: d.Username,
|
||||
Email: d.Email,
|
||||
CodeHash: d.CodeHash,
|
||||
ExpiresAt: d.ExpiresAt,
|
||||
CreatedAt: d.CreatedAt,
|
||||
InviteCode: d.InviteCode,
|
||||
}
|
||||
}
|
||||
|
||||
// DBResetPassword 对应 password_resets 表。
|
||||
type DBResetPassword struct {
|
||||
Account string `gorm:"primaryKey;column:account;size:255"`
|
||||
Email string `gorm:"column:email;not null;size:255"`
|
||||
CodeHash string `gorm:"column:code_hash;not null"`
|
||||
ExpiresAt string `gorm:"column:expires_at;size:50;not null"`
|
||||
CreatedAt string `gorm:"column:created_at;size:50;not null;<-:create"`
|
||||
}
|
||||
|
||||
func (DBResetPassword) TableName() string { return "password_resets" }
|
||||
|
||||
func dbResetFromModel(r models.ResetPassword) DBResetPassword {
|
||||
return DBResetPassword{
|
||||
Account: r.Account,
|
||||
Email: r.Email,
|
||||
CodeHash: r.CodeHash,
|
||||
ExpiresAt: r.ExpiresAt,
|
||||
CreatedAt: r.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (d DBResetPassword) toModel() models.ResetPassword {
|
||||
return models.ResetPassword{
|
||||
Account: d.Account,
|
||||
Email: d.Email,
|
||||
CodeHash: d.CodeHash,
|
||||
ExpiresAt: d.ExpiresAt,
|
||||
CreatedAt: d.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// DBSecondaryEmailVerification 对应 secondary_email_verifications 表。
|
||||
// 联合主键 (account, email)。
|
||||
type DBSecondaryEmailVerification struct {
|
||||
Account string `gorm:"primaryKey;column:account;size:255"`
|
||||
Email string `gorm:"primaryKey;column:email;size:255"`
|
||||
CodeHash string `gorm:"column:code_hash;not null"`
|
||||
ExpiresAt string `gorm:"column:expires_at;size:50;not null"`
|
||||
CreatedAt string `gorm:"column:created_at;size:50;not null;<-:create"`
|
||||
}
|
||||
|
||||
func (DBSecondaryEmailVerification) TableName() string { return "secondary_email_verifications" }
|
||||
|
||||
func dbSecondaryFromModel(s models.SecondaryEmailVerification) DBSecondaryEmailVerification {
|
||||
return DBSecondaryEmailVerification{
|
||||
Account: s.Account,
|
||||
Email: s.Email,
|
||||
CodeHash: s.CodeHash,
|
||||
ExpiresAt: s.ExpiresAt,
|
||||
CreatedAt: s.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (d DBSecondaryEmailVerification) toModel() models.SecondaryEmailVerification {
|
||||
return models.SecondaryEmailVerification{
|
||||
Account: d.Account,
|
||||
Email: d.Email,
|
||||
CodeHash: d.CodeHash,
|
||||
ExpiresAt: d.ExpiresAt,
|
||||
CreatedAt: d.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// DBAppConfig 对应 app_configs 表,存储应用配置键值对(value 为 JSON 文本)。
|
||||
type DBAppConfig struct {
|
||||
ConfigKey string `gorm:"primaryKey;column:config_key;size:128"`
|
||||
ConfigValue string `gorm:"column:config_value;type:text;not null"`
|
||||
}
|
||||
|
||||
func (DBAppConfig) TableName() string { return "app_configs" }
|
||||
|
||||
// DBInviteCode 对应 invite_codes 表。
|
||||
type DBInviteCode struct {
|
||||
Code string `gorm:"primaryKey;column:code;size:32"`
|
||||
Note string `gorm:"column:note;size:512"`
|
||||
MaxUses int `gorm:"column:max_uses;default:0"`
|
||||
Uses int `gorm:"column:uses;default:0"`
|
||||
ExpiresAt string `gorm:"column:expires_at;size:50"`
|
||||
CreatedAt string `gorm:"column:created_at;size:50;not null;<-:create"`
|
||||
}
|
||||
|
||||
func (DBInviteCode) TableName() string { return "invite_codes" }
|
||||
|
||||
func dbInviteFromEntry(e InviteEntry) DBInviteCode {
|
||||
return DBInviteCode{
|
||||
Code: e.Code,
|
||||
Note: e.Note,
|
||||
MaxUses: e.MaxUses,
|
||||
Uses: e.Uses,
|
||||
ExpiresAt: e.ExpiresAt,
|
||||
CreatedAt: e.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (d DBInviteCode) toEntry() InviteEntry {
|
||||
return InviteEntry{
|
||||
Code: d.Code,
|
||||
Note: d.Note,
|
||||
MaxUses: d.MaxUses,
|
||||
Uses: d.Uses,
|
||||
ExpiresAt: d.ExpiresAt,
|
||||
CreatedAt: d.CreatedAt,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user