Files
SproutGate/sproutgate-backend/internal/storage/dbmodels.go

322 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
}
}