feat: migrate to PostgreSQL GORM and MinIO file storage
Use UUID models and soft deletion, persist file relationships with deduplicated monthly MinIO objects, and add admin file management. Update mini-program media handling and add local dev configuration.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
package model
|
||||
|
||||
// File represents one physical object. References and upload ownership are independent.
|
||||
type File struct {
|
||||
BaseModel
|
||||
Size int64 `gorm:"not null;check:size >= 0" json:"size"`
|
||||
Suffix string `gorm:"size:20;not null" json:"suffix"`
|
||||
ContentType string `gorm:"size:255;not null" json:"content_type"`
|
||||
ObjectKey string `gorm:"size:1024;not null;uniqueIndex:ux_files_object,priority:2" json:"object_key"`
|
||||
Md5 string `gorm:"size:32;not null;uniqueIndex:ux_files_md5,priority:2" json:"md5"`
|
||||
Sha256 string `gorm:"size:64;not null" json:"-"`
|
||||
Url string `gorm:"type:text;not null" json:"url"`
|
||||
Bucket string `gorm:"size:255;not null;uniqueIndex:ux_files_md5,priority:1;uniqueIndex:ux_files_object,priority:1" json:"bucket"`
|
||||
OriginalName string `gorm:"size:255" json:"original_name"`
|
||||
}
|
||||
|
||||
func (File) TableName() string { return "past_files" }
|
||||
|
||||
// FileUpload records independent ownership of an upload, including deduplicated uploads.
|
||||
type FileUpload struct {
|
||||
BaseModel
|
||||
UserId string `gorm:"type:uuid;not null;uniqueIndex:ux_file_upload,where:deleted_at IS NULL,priority:1" json:"user_id"`
|
||||
FileId string `gorm:"type:uuid;not null;uniqueIndex:ux_file_upload,where:deleted_at IS NULL,priority:2;index" json:"file_id"`
|
||||
User User `gorm:"foreignKey:UserId;references:Id;belongsTo:User;constraint:OnDelete:RESTRICT" json:"-"`
|
||||
File File `gorm:"foreignKey:FileId;references:Id;constraint:OnDelete:RESTRICT" json:"-"`
|
||||
}
|
||||
|
||||
// RecordFile is an ordered one-to-many link for diary/timeline images.
|
||||
// OwnerTable is restricted to the supported polymorphic parents; creation and
|
||||
// replacement are serialized with their parent transaction.
|
||||
type RecordFile struct {
|
||||
BaseModel
|
||||
OwnerTable string `gorm:"size:50;not null;uniqueIndex:ux_record_file_position,where:deleted_at IS NULL,priority:1;check:owner_table IN ('diaries','timeline')" json:"owner_table"`
|
||||
OwnerId string `gorm:"type:uuid;not null;uniqueIndex:ux_record_file_position,where:deleted_at IS NULL,priority:2" json:"owner_id"`
|
||||
Field string `gorm:"size:50;not null;uniqueIndex:ux_record_file_position,where:deleted_at IS NULL,priority:3;check:field = 'images'" json:"field"`
|
||||
Position int `gorm:"not null;uniqueIndex:ux_record_file_position,where:deleted_at IS NULL,priority:4;check:position >= 0" json:"position"`
|
||||
FileId string `gorm:"type:uuid;not null;index" json:"file_id"`
|
||||
File File `gorm:"foreignKey:FileId;references:Id;constraint:OnDelete:RESTRICT" json:"-"`
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
// Package model defines the PostgreSQL schema shared by all repositories.
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BaseModel supplies identity, automatic timestamps and GORM soft deletion.
|
||||
type BaseModel struct {
|
||||
Id string `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
|
||||
CreatedAt time.Time `gorm:"not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
type UnbindingFileCleanup struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
Path string `gorm:"column:path;type:varchar(500);not null" json:"path"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
BaseModel
|
||||
UserId string `gorm:"column:user_id;type:varchar(20);uniqueIndex:ux_users_user_id,where:deleted_at IS NULL" json:"user_id"`
|
||||
Openid string `gorm:"column:openid;type:varchar(100);uniqueIndex:ux_users_openid,where:deleted_at IS NULL" json:"openid"`
|
||||
Phone string `gorm:"column:phone;type:varchar(50);default:''" json:"phone"`
|
||||
Username string `gorm:"column:username;type:varchar(100);default:'';uniqueIndex:ux_users_username,where:deleted_at IS NULL" json:"username"`
|
||||
Password string `gorm:"column:password;type:varchar(255);default:''" json:"password"`
|
||||
Nickname string `gorm:"column:nickname;type:varchar(100);default:''" json:"nickname"`
|
||||
Avatar string `gorm:"column:avatar;type:varchar(500);default:''" json:"avatar"`
|
||||
Gender string `gorm:"column:gender;type:varchar(10);default:''" json:"gender"`
|
||||
CoupleId *string `gorm:"column:couple_id;type:uuid" json:"couple_id"`
|
||||
LastLoginAt *time.Time `gorm:"column:last_login_at;type:timestamptz" json:"last_login_at"`
|
||||
PeriodVisible int64 `gorm:"column:period_visible;type:bigint;not null;default:1" json:"period_visible"`
|
||||
Age int64 `gorm:"column:age;type:bigint;default:0" json:"age"`
|
||||
Birthday string `gorm:"column:birthday;type:varchar(20);default:''" json:"birthday"`
|
||||
Region string `gorm:"column:region;type:varchar(100);default:''" json:"region"`
|
||||
Location string `gorm:"column:location;type:varchar(200);default:''" json:"location"`
|
||||
Lat float64 `gorm:"column:lat;type:numeric(10,6);default:0" json:"lat"`
|
||||
Lng float64 `gorm:"column:lng;type:numeric(10,6);default:0" json:"lng"`
|
||||
LastLoginLocation string `gorm:"column:last_login_location;type:varchar(200);default:''" json:"last_login_location"`
|
||||
IpGeoDate *string `gorm:"column:ip_geo_date;type:date" json:"ip_geo_date"`
|
||||
LastIp string `gorm:"column:last_ip;type:varchar(64);default:''" json:"last_ip"`
|
||||
SessionKey string `gorm:"column:session_key;type:varchar(128);default:''" json:"session_key"`
|
||||
StorageVipExpireAt *time.Time `gorm:"column:storage_vip_expire_at;type:timestamptz" json:"storage_vip_expire_at"`
|
||||
StorageQuotaMb int64 `gorm:"column:storage_quota_mb;type:bigint;default:0" json:"storage_quota_mb"`
|
||||
StoragePlan string `gorm:"column:storage_plan;type:varchar(20);default:''" json:"storage_plan"`
|
||||
StoragePlanStartedAt *time.Time `gorm:"column:storage_plan_started_at;type:timestamptz" json:"storage_plan_started_at"`
|
||||
SmokingCardVisible int64 `gorm:"column:smoking_card_visible;type:bigint;not null;default:1" json:"smoking_card_visible"`
|
||||
AvatarFileId *string `gorm:"type:uuid;index" json:"avatar_file_id"`
|
||||
AvatarFileIdRecord *File `gorm:"foreignKey:AvatarFileId;references:Id;constraint:OnUpdate:CASCADE,OnDelete:RESTRICT" json:"-"`
|
||||
}
|
||||
|
||||
type Couple struct {
|
||||
BaseModel
|
||||
CoupleSpaceId string `gorm:"column:couple_space_id;type:varchar(50);uniqueIndex:ux_couples_couple_space_id,where:deleted_at IS NULL" json:"couple_space_id"`
|
||||
User1Id string `gorm:"column:user1_id;type:uuid;not null" json:"user1_id"`
|
||||
User2Id string `gorm:"column:user2_id;type:uuid;not null" json:"user2_id"`
|
||||
LoveStartDate string `gorm:"column:love_start_date;type:varchar(20);default:''" json:"love_start_date"`
|
||||
LoveStory string `gorm:"column:love_story;type:text" json:"love_story"`
|
||||
Status string `gorm:"column:status;type:varchar(20);default:'active'" json:"status"`
|
||||
DataDeleted int64 `gorm:"column:data_deleted;type:bigint;default:0" json:"data_deleted"`
|
||||
PeriodVisible int64 `gorm:"column:period_visible;type:bigint;default:0" json:"period_visible"`
|
||||
PeriodSourceUserId *string `gorm:"column:period_source_user_id;type:uuid" json:"period_source_user_id"`
|
||||
}
|
||||
|
||||
type Invitation struct {
|
||||
BaseModel
|
||||
FromUserId string `gorm:"column:from_user_id;type:uuid;not null" json:"from_user_id"`
|
||||
ToUserId *string `gorm:"column:to_user_id;type:uuid" json:"to_user_id"`
|
||||
ToPhone string `gorm:"column:to_phone;type:varchar(50);default:''" json:"to_phone"`
|
||||
Status string `gorm:"column:status;type:varchar(20);default:'pending'" json:"status"`
|
||||
Message string `gorm:"column:message;type:text" json:"message"`
|
||||
InviteCode string `gorm:"column:invite_code;type:varchar(100);default:''" json:"invite_code"`
|
||||
Type string `gorm:"column:type;type:varchar(20);default:'phone'" json:"type"`
|
||||
RespondedAt *time.Time `gorm:"column:responded_at;type:timestamptz" json:"responded_at"`
|
||||
}
|
||||
|
||||
type UnbindRequest struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
RequesterId string `gorm:"column:requester_id;type:uuid;not null" json:"requester_id"`
|
||||
Status string `gorm:"column:status;type:varchar(20);default:'pending'" json:"status"`
|
||||
RespondedAt *time.Time `gorm:"column:responded_at;type:timestamptz" json:"responded_at"`
|
||||
CancelledAt *time.Time `gorm:"column:cancelled_at;type:timestamptz" json:"cancelled_at"`
|
||||
CancelledBy *string `gorm:"column:cancelled_by;type:uuid" json:"cancelled_by"`
|
||||
CoolingUntil *time.Time `gorm:"column:cooling_until;type:timestamptz" json:"cooling_until"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;type:timestamptz" json:"completed_at"`
|
||||
}
|
||||
|
||||
type InviteLink struct {
|
||||
BaseModel
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
Code string `gorm:"column:code;type:varchar(100);not null;uniqueIndex:ux_invite_links_code,where:deleted_at IS NULL" json:"code"`
|
||||
Used int64 `gorm:"column:used;type:bigint;default:0" json:"used"`
|
||||
}
|
||||
|
||||
type Timeline struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
Title string `gorm:"column:title;type:varchar(255);not null" json:"title"`
|
||||
Content string `gorm:"column:content;type:text" json:"content"`
|
||||
EventDate string `gorm:"column:event_date;type:varchar(20);not null" json:"event_date"`
|
||||
Location string `gorm:"column:location;type:varchar(200);default:''" json:"location"`
|
||||
Images string `gorm:"column:images;type:text" json:"images"`
|
||||
Icon string `gorm:"column:icon;type:varchar(20);default:'❤️'" json:"icon"`
|
||||
SortOrder int64 `gorm:"column:sort_order;type:bigint;default:0" json:"sort_order"`
|
||||
CreatedBy *string `gorm:"column:created_by;type:uuid" json:"created_by"`
|
||||
RepeatInterval int64 `gorm:"column:repeat_interval;type:bigint;default:0" json:"repeat_interval"`
|
||||
RepeatUnit string `gorm:"column:repeat_unit;type:varchar(10);default:''" json:"repeat_unit"`
|
||||
BgImage string `gorm:"column:bg_image;type:varchar(500);default:''" json:"bg_image"`
|
||||
BgImageFileId *string `gorm:"type:uuid;index" json:"bg_image_file_id"`
|
||||
BgImageFileIdRecord *File `gorm:"foreignKey:BgImageFileId;references:Id;constraint:OnUpdate:CASCADE,OnDelete:RESTRICT" json:"-"`
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
Title string `gorm:"column:title;type:varchar(255);not null" json:"title"`
|
||||
Description string `gorm:"column:description;type:text" json:"description"`
|
||||
Cover string `gorm:"column:cover;type:varchar(500);default:''" json:"cover"`
|
||||
SortOrder int64 `gorm:"column:sort_order;type:bigint;default:0" json:"sort_order"`
|
||||
Lat float64 `gorm:"column:lat;type:numeric(10,6);default:0" json:"lat"`
|
||||
Lng float64 `gorm:"column:lng;type:numeric(10,6);default:0" json:"lng"`
|
||||
Location string `gorm:"column:location;type:varchar(200);default:''" json:"location"`
|
||||
CreatedBy *string `gorm:"column:created_by;type:uuid" json:"created_by"`
|
||||
CoverFileId *string `gorm:"type:uuid;index" json:"cover_file_id"`
|
||||
CoverFileIdRecord *File `gorm:"foreignKey:CoverFileId;references:Id;constraint:OnUpdate:CASCADE,OnDelete:RESTRICT" json:"-"`
|
||||
}
|
||||
|
||||
type Photo struct {
|
||||
BaseModel
|
||||
AlbumId string `gorm:"column:album_id;type:uuid;not null" json:"album_id"`
|
||||
Path string `gorm:"column:path;type:varchar(500);not null" json:"path"`
|
||||
Description string `gorm:"column:description;type:text" json:"description"`
|
||||
TakenAt string `gorm:"column:taken_at;type:varchar(50);default:''" json:"taken_at"`
|
||||
Location string `gorm:"column:location;type:varchar(200);default:''" json:"location"`
|
||||
Lat float64 `gorm:"column:lat;type:numeric(10,6);default:0" json:"lat"`
|
||||
Lng float64 `gorm:"column:lng;type:numeric(10,6);default:0" json:"lng"`
|
||||
Activity string `gorm:"column:activity;type:varchar(200);default:''" json:"activity"`
|
||||
BatchId string `gorm:"column:batch_id;type:varchar(50);default:''" json:"batch_id"`
|
||||
SortOrder int64 `gorm:"column:sort_order;type:bigint;default:0" json:"sort_order"`
|
||||
CreatedBy *string `gorm:"column:created_by;type:uuid" json:"created_by"`
|
||||
FileId *string `gorm:"type:uuid;index" json:"file_id"`
|
||||
FileIdRecord *File `gorm:"foreignKey:FileId;references:Id;constraint:OnUpdate:CASCADE,OnDelete:RESTRICT" json:"-"`
|
||||
}
|
||||
|
||||
type Diary struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
Title string `gorm:"column:title;type:varchar(255);not null" json:"title"`
|
||||
Content string `gorm:"column:content;type:text" json:"content"`
|
||||
Mood string `gorm:"column:mood;type:varchar(20);default:'😊'" json:"mood"`
|
||||
Weather string `gorm:"column:weather;type:varchar(20);default:'☀️'" json:"weather"`
|
||||
DiaryDate string `gorm:"column:diary_date;type:varchar(20);not null" json:"diary_date"`
|
||||
Images string `gorm:"column:images;type:text" json:"images"`
|
||||
IsAnniversary int64 `gorm:"column:is_anniversary;type:bigint;default:0" json:"is_anniversary"`
|
||||
Region string `gorm:"column:region;type:varchar(100);default:''" json:"region"`
|
||||
Lat float64 `gorm:"column:lat;type:numeric(10,6);default:0" json:"lat"`
|
||||
Lng float64 `gorm:"column:lng;type:numeric(10,6);default:0" json:"lng"`
|
||||
Location string `gorm:"column:location;type:varchar(200);default:''" json:"location"`
|
||||
CreatedBy *string `gorm:"column:created_by;type:uuid" json:"created_by"`
|
||||
}
|
||||
|
||||
type Song struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
SongName string `gorm:"column:song_name;type:varchar(255);not null" json:"song_name"`
|
||||
Artist string `gorm:"column:artist;type:varchar(255);not null" json:"artist"`
|
||||
Note string `gorm:"column:note;type:varchar(120);default:''" json:"note"`
|
||||
CreatedBy *string `gorm:"column:created_by;type:uuid" json:"created_by"`
|
||||
}
|
||||
|
||||
type Checkin struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
Title string `gorm:"column:title;type:varchar(255);not null" json:"title"`
|
||||
Icon string `gorm:"column:icon;type:varchar(20);default:'✅'" json:"icon"`
|
||||
Type string `gorm:"column:type;type:varchar(10);default:'dual'" json:"type"`
|
||||
Frequency string `gorm:"column:frequency;type:varchar(10);default:'daily'" json:"frequency"`
|
||||
IsCompleted int64 `gorm:"column:is_completed;type:bigint;default:0" json:"is_completed"`
|
||||
CompletedByMe int64 `gorm:"column:completed_by_me;type:bigint;default:0" json:"completed_by_me"`
|
||||
CompletedByPartner int64 `gorm:"column:completed_by_partner;type:bigint;default:0" json:"completed_by_partner"`
|
||||
MyCheckinTime *time.Time `gorm:"column:my_checkin_time;type:timestamptz" json:"my_checkin_time"`
|
||||
PartnerCheckinTime *time.Time `gorm:"column:partner_checkin_time;type:timestamptz" json:"partner_checkin_time"`
|
||||
MyStreak int64 `gorm:"column:my_streak;type:bigint;default:0" json:"my_streak"`
|
||||
PartnerStreak int64 `gorm:"column:partner_streak;type:bigint;default:0" json:"partner_streak"`
|
||||
MyPhoto string `gorm:"column:my_photo;type:varchar(500);default:''" json:"my_photo"`
|
||||
PartnerPhoto string `gorm:"column:partner_photo;type:varchar(500);default:''" json:"partner_photo"`
|
||||
MyNote string `gorm:"column:my_note;type:varchar(200);default:''" json:"my_note"`
|
||||
PartnerNote string `gorm:"column:partner_note;type:varchar(200);default:''" json:"partner_note"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;type:timestamptz" json:"completed_at"`
|
||||
SortOrder int64 `gorm:"column:sort_order;type:bigint;default:0" json:"sort_order"`
|
||||
CreatedBy *string `gorm:"column:created_by;type:uuid" json:"created_by"`
|
||||
MyPhotoFileId *string `gorm:"type:uuid;index" json:"my_photo_file_id"`
|
||||
MyPhotoFileIdRecord *File `gorm:"foreignKey:MyPhotoFileId;references:Id;constraint:OnUpdate:CASCADE,OnDelete:RESTRICT" json:"-"`
|
||||
PartnerPhotoFileId *string `gorm:"type:uuid;index" json:"partner_photo_file_id"`
|
||||
PartnerPhotoFileIdRecord *File `gorm:"foreignKey:PartnerPhotoFileId;references:Id;constraint:OnUpdate:CASCADE,OnDelete:RESTRICT" json:"-"`
|
||||
}
|
||||
|
||||
type CheckinLog struct {
|
||||
BaseModel
|
||||
CheckinId string `gorm:"column:checkin_id;type:uuid;not null" json:"checkin_id"`
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
Photo string `gorm:"column:photo;type:varchar(500);default:''" json:"photo"`
|
||||
Note string `gorm:"column:note;type:varchar(200);default:''" json:"note"`
|
||||
PhotoFileId *string `gorm:"type:uuid;index" json:"photo_file_id"`
|
||||
PhotoFileIdRecord *File `gorm:"foreignKey:PhotoFileId;references:Id;constraint:OnUpdate:CASCADE,OnDelete:RESTRICT" json:"-"`
|
||||
}
|
||||
|
||||
type SmokingAccount struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null;uniqueIndex:ix_smoking_accounts_smoking_owner,where:deleted_at IS NULL,priority:1" json:"couple_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null;uniqueIndex:ix_smoking_accounts_smoking_owner,where:deleted_at IS NULL,priority:2" json:"user_id"`
|
||||
Stock int64 `gorm:"column:stock;type:bigint;not null;default:0" json:"stock"`
|
||||
CheckinId *string `gorm:"column:checkin_id;type:uuid" json:"checkin_id"`
|
||||
}
|
||||
|
||||
type SmokingLog struct {
|
||||
BaseModel
|
||||
AccountId string `gorm:"column:account_id;type:uuid;not null;uniqueIndex:ix_smoking_logs_smoking_request,where:deleted_at IS NULL,priority:1;index:ix_smoking_logs_smoking_recent,priority:1" json:"account_id"`
|
||||
Kind string `gorm:"column:kind;type:varchar(20);not null" json:"kind"`
|
||||
Quantity int64 `gorm:"column:quantity;type:bigint;not null" json:"quantity"`
|
||||
Delta int64 `gorm:"column:delta;type:bigint;not null" json:"delta"`
|
||||
Balance int64 `gorm:"column:balance;type:bigint;not null" json:"balance"`
|
||||
AmountCents int64 `gorm:"column:amount_cents;type:bigint;not null;default:0" json:"amount_cents"`
|
||||
Note string `gorm:"column:note;type:varchar(200);not null;default:''" json:"note"`
|
||||
RequestKey string `gorm:"column:request_key;type:varchar(64);not null;uniqueIndex:ix_smoking_logs_smoking_request,where:deleted_at IS NULL,priority:2" json:"request_key"`
|
||||
PayloadHash string `gorm:"column:payload_hash;type:char(64);not null" json:"payload_hash"`
|
||||
CheckinLogId *string `gorm:"column:checkin_log_id;type:uuid" json:"checkin_log_id"`
|
||||
OriginalId *string `gorm:"column:original_id;type:uuid" json:"original_id"`
|
||||
Undone int64 `gorm:"column:undone;type:bigint;not null;default:0" json:"undone"`
|
||||
}
|
||||
|
||||
type PeriodRecord struct {
|
||||
BaseModel
|
||||
CoupleId *string `gorm:"column:couple_id;type:uuid" json:"couple_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
StartDate string `gorm:"column:start_date;type:date;not null" json:"start_date"`
|
||||
EndDate string `gorm:"column:end_date;type:date;not null" json:"end_date"`
|
||||
CycleLength int64 `gorm:"column:cycle_length;type:bigint;default:28" json:"cycle_length"`
|
||||
PeriodLength int64 `gorm:"column:period_length;type:bigint;default:5" json:"period_length"`
|
||||
Symptoms string `gorm:"column:symptoms;type:varchar(500);default:''" json:"symptoms"`
|
||||
Note string `gorm:"column:note;type:text" json:"note"`
|
||||
}
|
||||
|
||||
type PeriodDailyRecord struct {
|
||||
BaseModel
|
||||
CoupleId *string `gorm:"column:couple_id;type:uuid;uniqueIndex:ix_period_daily_records_uk_date_couple,where:deleted_at IS NULL,priority:2" json:"couple_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
RecordDate string `gorm:"column:record_date;type:date;not null;uniqueIndex:ix_period_daily_records_uk_date_couple,where:deleted_at IS NULL,priority:1" json:"record_date"`
|
||||
Period string `gorm:"column:period;type:varchar(1);default:'0'" json:"period"`
|
||||
Sex string `gorm:"column:sex;type:varchar(1);default:'0'" json:"sex"`
|
||||
Symptoms string `gorm:"column:symptoms;type:varchar(200);default:''" json:"symptoms"`
|
||||
Mood string `gorm:"column:mood;type:varchar(100);default:''" json:"mood"`
|
||||
Discharge string `gorm:"column:discharge;type:varchar(100);default:''" json:"discharge"`
|
||||
Temperature *float64 `gorm:"column:temperature;type:numeric(4,1)" json:"temperature"`
|
||||
Weight *float64 `gorm:"column:weight;type:numeric(5,1)" json:"weight"`
|
||||
Note string `gorm:"column:note;type:text" json:"note"`
|
||||
PeriodDetail string `gorm:"column:period_detail;type:text" json:"period_detail"`
|
||||
}
|
||||
|
||||
type PeriodSexRecord struct {
|
||||
BaseModel
|
||||
CoupleId *string `gorm:"column:couple_id;type:uuid;index:ix_period_sex_records_idx_date_couple,priority:2" json:"couple_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
RecordDate string `gorm:"column:record_date;type:date;not null;index:ix_period_sex_records_idx_date_couple,priority:1" json:"record_date"`
|
||||
RecordTime *string `gorm:"column:record_time;type:time without time zone" json:"record_time"`
|
||||
Measure string `gorm:"column:measure;type:varchar(100);default:''" json:"measure"`
|
||||
MeasureCustom string `gorm:"column:measure_custom;type:varchar(200);default:''" json:"measure_custom"`
|
||||
}
|
||||
|
||||
type Setting struct {
|
||||
BaseModel
|
||||
Key string `gorm:"column:key;type:varchar(255);uniqueIndex:ux_settings_key,where:deleted_at IS NULL" json:"key"`
|
||||
Value string `gorm:"column:value;type:text" json:"value"`
|
||||
FileId *string `gorm:"type:uuid;index" json:"file_id"`
|
||||
FileIdRecord *File `gorm:"foreignKey:FileId;references:Id;constraint:OnUpdate:CASCADE,OnDelete:RESTRICT" json:"-"`
|
||||
}
|
||||
|
||||
type DiaryComment struct {
|
||||
BaseModel
|
||||
DiaryId string `gorm:"column:diary_id;type:uuid;not null;index:ix_diary_comments_idx_diary,priority:1" json:"diary_id"`
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
Content string `gorm:"column:content;type:text;not null" json:"content"`
|
||||
ParentId *string `gorm:"column:parent_id;type:uuid" json:"parent_id"`
|
||||
ReplyToUserId *string `gorm:"column:reply_to_user_id;type:uuid" json:"reply_to_user_id"`
|
||||
ReplyToUsername string `gorm:"column:reply_to_username;type:varchar(100);default:''" json:"reply_to_username"`
|
||||
}
|
||||
|
||||
type SubscribeTemplate struct {
|
||||
BaseModel
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null;uniqueIndex:ix_subscribe_templates_uk_user_template,where:deleted_at IS NULL,priority:1" json:"user_id"`
|
||||
TemplateId string `gorm:"column:template_id;type:varchar(100);not null" json:"template_id"`
|
||||
TemplateType string `gorm:"column:template_type;type:varchar(50);not null;uniqueIndex:ix_subscribe_templates_uk_user_template,where:deleted_at IS NULL,priority:2" json:"template_type"`
|
||||
}
|
||||
|
||||
type ActivityLog struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null;index:ix_activity_log_idx_couple_time,priority:1" json:"couple_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
ActionType string `gorm:"column:action_type;type:varchar(50);not null" json:"action_type"`
|
||||
Description string `gorm:"column:description;type:varchar(500);not null" json:"description"`
|
||||
Detail string `gorm:"column:detail;type:text" json:"detail"`
|
||||
}
|
||||
|
||||
type Admin struct {
|
||||
BaseModel
|
||||
Username string `gorm:"column:username;type:varchar(100);not null;uniqueIndex:ux_admins_username,where:deleted_at IS NULL" json:"username"`
|
||||
Password string `gorm:"column:password;type:varchar(255);not null" json:"password"`
|
||||
}
|
||||
|
||||
type UserToken struct {
|
||||
BaseModel
|
||||
Token string `gorm:"column:token;type:varchar(128);not null;uniqueIndex:ux_user_tokens_token,where:deleted_at IS NULL;index:ix_user_tokens_idx_token,priority:1" json:"token"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
ExpiresAt *time.Time `gorm:"column:expires_at;type:timestamptz;not null;index:ix_user_tokens_idx_expires,priority:1" json:"expires_at"`
|
||||
}
|
||||
|
||||
type SubscribeMessageLog struct {
|
||||
BaseModel
|
||||
ActivityId *string `gorm:"column:activity_id;type:uuid;index:ix_subscribe_message_logs_idx_activity,priority:1" json:"activity_id"`
|
||||
CoupleId *string `gorm:"column:couple_id;type:uuid;index:ix_subscribe_message_logs_idx_couple,priority:1" json:"couple_id"`
|
||||
UserId *string `gorm:"column:user_id;type:uuid" json:"user_id"`
|
||||
ToUserId string `gorm:"column:to_user_id;type:uuid;not null;index:ix_subscribe_message_logs_idx_recipient_time,priority:1" json:"to_user_id"`
|
||||
ToOpenid string `gorm:"column:to_openid;type:varchar(128);not null;default:'';index:ix_subscribe_message_logs_idx_openid,priority:1" json:"to_openid"`
|
||||
ActionType string `gorm:"column:action_type;type:varchar(50);not null;default:'';index:ix_subscribe_message_logs_idx_event_time,priority:1" json:"action_type"`
|
||||
MessageTitle string `gorm:"column:message_title;type:varchar(255);not null;default:''" json:"message_title"`
|
||||
MessageContent string `gorm:"column:message_content;type:text" json:"message_content"`
|
||||
TemplateType string `gorm:"column:template_type;type:varchar(50);not null" json:"template_type"`
|
||||
TemplateId string `gorm:"column:template_id;type:varchar(128);not null;default:''" json:"template_id"`
|
||||
Page string `gorm:"column:page;type:varchar(255);not null;default:''" json:"page"`
|
||||
Status string `gorm:"column:status;type:varchar(20);not null;index:ix_subscribe_message_logs_idx_status_time,priority:1" json:"status"`
|
||||
ErrorCode *int64 `gorm:"column:error_code;type:bigint" json:"error_code"`
|
||||
HttpStatus *int64 `gorm:"column:http_status;type:bigint" json:"http_status"`
|
||||
ErrorMsg string `gorm:"column:error_msg;type:text" json:"error_msg"`
|
||||
Attempts int64 `gorm:"column:attempts;type:bigint;not null;default:0" json:"attempts"`
|
||||
DurationMs int64 `gorm:"column:duration_ms;type:bigint;not null;default:0" json:"duration_ms"`
|
||||
SentAt *time.Time `gorm:"column:sent_at;type:timestamptz" json:"sent_at"`
|
||||
}
|
||||
|
||||
type NotifyLog struct {
|
||||
BaseModel
|
||||
ActivityId string `gorm:"column:activity_id;type:uuid;not null;uniqueIndex:ix_notify_log_uk_activity,where:deleted_at IS NULL,priority:1" json:"activity_id"`
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null" json:"couple_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
ActionType string `gorm:"column:action_type;type:varchar(50);not null" json:"action_type"`
|
||||
TemplateType string `gorm:"column:template_type;type:varchar(50);not null" json:"template_type"`
|
||||
ToUserId string `gorm:"column:to_user_id;type:uuid;not null;uniqueIndex:ix_notify_log_uk_activity,where:deleted_at IS NULL,priority:2" json:"to_user_id"`
|
||||
Status string `gorm:"column:status;type:varchar(20);not null;default:'';index:ix_notify_log_idx_status_time,priority:1" json:"status"`
|
||||
ErrorMsg string `gorm:"column:error_msg;type:text" json:"error_msg"`
|
||||
SentAt *time.Time `gorm:"column:sent_at;type:timestamptz" json:"sent_at"`
|
||||
}
|
||||
|
||||
type Pet struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null;uniqueIndex:ux_pets_couple_id,where:deleted_at IS NULL" json:"couple_id"`
|
||||
Name string `gorm:"column:name;type:varchar(50);not null;default:'小狗狗'" json:"name"`
|
||||
Breed string `gorm:"column:breed;type:varchar(20);not null;default:'golden'" json:"breed"`
|
||||
Gender int64 `gorm:"column:gender;type:bigint;not null;default:1" json:"gender"`
|
||||
Stage int64 `gorm:"column:stage;type:bigint;not null;default:1" json:"stage"`
|
||||
Level int64 `gorm:"column:level;type:bigint;not null;default:1" json:"level"`
|
||||
Exp int64 `gorm:"column:exp;type:bigint;not null;default:0" json:"exp"`
|
||||
Happiness int64 `gorm:"column:happiness;type:bigint;not null;default:100" json:"happiness"`
|
||||
Hunger int64 `gorm:"column:hunger;type:bigint;not null;default:100" json:"hunger"`
|
||||
Energy int64 `gorm:"column:energy;type:bigint;not null;default:100" json:"energy"`
|
||||
Cleanliness int64 `gorm:"column:cleanliness;type:bigint;not null;default:100" json:"cleanliness"`
|
||||
AdoptedAt string `gorm:"column:adopted_at;type:date;not null" json:"adopted_at"`
|
||||
CreatedBy string `gorm:"column:created_by;type:uuid;not null" json:"created_by"`
|
||||
}
|
||||
|
||||
type PetDaily struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null;uniqueIndex:ix_pet_dailies_uk_couple_date,where:deleted_at IS NULL,priority:1" json:"couple_id"`
|
||||
Date string `gorm:"column:date;type:date;not null;uniqueIndex:ix_pet_dailies_uk_couple_date,where:deleted_at IS NULL,priority:2" json:"date"`
|
||||
FeedU1 int64 `gorm:"column:feed_u1;type:bigint;default:0" json:"feed_u1"`
|
||||
FeedU2 int64 `gorm:"column:feed_u2;type:bigint;default:0" json:"feed_u2"`
|
||||
WaterU1 int64 `gorm:"column:water_u1;type:bigint;default:0" json:"water_u1"`
|
||||
WaterU2 int64 `gorm:"column:water_u2;type:bigint;default:0" json:"water_u2"`
|
||||
WalkU1 int64 `gorm:"column:walk_u1;type:bigint;default:0" json:"walk_u1"`
|
||||
WalkU2 int64 `gorm:"column:walk_u2;type:bigint;default:0" json:"walk_u2"`
|
||||
BathU1 int64 `gorm:"column:bath_u1;type:bigint;default:0" json:"bath_u1"`
|
||||
BathU2 int64 `gorm:"column:bath_u2;type:bigint;default:0" json:"bath_u2"`
|
||||
SleepU1 int64 `gorm:"column:sleep_u1;type:bigint;default:0" json:"sleep_u1"`
|
||||
SleepU2 int64 `gorm:"column:sleep_u2;type:bigint;default:0" json:"sleep_u2"`
|
||||
PlayU1 int64 `gorm:"column:play_u1;type:bigint;default:0" json:"play_u1"`
|
||||
PlayU2 int64 `gorm:"column:play_u2;type:bigint;default:0" json:"play_u2"`
|
||||
AllDone int64 `gorm:"column:all_done;type:bigint;default:0" json:"all_done"`
|
||||
}
|
||||
|
||||
type PetAchievement struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null;uniqueIndex:ix_pet_achievements_uk_couple_ach,where:deleted_at IS NULL,priority:1" json:"couple_id"`
|
||||
AchievementKey string `gorm:"column:achievement_key;type:varchar(50);not null;uniqueIndex:ix_pet_achievements_uk_couple_ach,where:deleted_at IS NULL,priority:2" json:"achievement_key"`
|
||||
UnlockedAt *time.Time `gorm:"column:unlocked_at;type:timestamptz;default:CURRENT_TIMESTAMP" json:"unlocked_at"`
|
||||
}
|
||||
|
||||
type PetItem struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null;index:ix_pet_items_idx_couple,priority:1" json:"couple_id"`
|
||||
ItemKey string `gorm:"column:item_key;type:varchar(50);not null" json:"item_key"`
|
||||
ItemType string `gorm:"column:item_type;type:varchar(20);not null" json:"item_type"`
|
||||
Quantity int64 `gorm:"column:quantity;type:bigint;default:1" json:"quantity"`
|
||||
Equipped int64 `gorm:"column:equipped;type:bigint;default:0" json:"equipped"`
|
||||
}
|
||||
|
||||
type PetDynamic struct {
|
||||
BaseModel
|
||||
CoupleId string `gorm:"column:couple_id;type:uuid;not null;index:ix_pet_dynamics_idx_couple_time,priority:1" json:"couple_id"`
|
||||
UserId *string `gorm:"column:user_id;type:uuid" json:"user_id"`
|
||||
Action string `gorm:"column:action;type:varchar(30);not null" json:"action"`
|
||||
Message string `gorm:"column:message;type:text" json:"message"`
|
||||
}
|
||||
|
||||
type StorageOrder struct {
|
||||
BaseModel
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null;index:ix_storage_orders_idx_user,priority:1" json:"user_id"`
|
||||
CoupleId *string `gorm:"column:couple_id;type:uuid" json:"couple_id"`
|
||||
OutTradeNo string `gorm:"column:out_trade_no;type:varchar(64);not null;uniqueIndex:ux_storage_orders_out_trade_no,where:deleted_at IS NULL" json:"out_trade_no"`
|
||||
PlanKey string `gorm:"column:plan_key;type:varchar(20);not null" json:"plan_key"`
|
||||
ProductId string `gorm:"column:product_id;type:varchar(64);not null;default:''" json:"product_id"`
|
||||
AmountCents int64 `gorm:"column:amount_cents;type:bigint;not null;default:0" json:"amount_cents"`
|
||||
Env int64 `gorm:"column:env;type:bigint;not null;default:1" json:"env"`
|
||||
Status string `gorm:"column:status;type:varchar(20);not null;default:'pending';index:ix_storage_orders_idx_status,priority:1" json:"status"`
|
||||
WxOrderId string `gorm:"column:wx_order_id;type:varchar(64);not null;default:''" json:"wx_order_id"`
|
||||
Attach string `gorm:"column:attach;type:text" json:"attach"`
|
||||
PaidAt *time.Time `gorm:"column:paid_at;type:timestamptz" json:"paid_at"`
|
||||
}
|
||||
|
||||
type DiaryLike struct {
|
||||
BaseModel
|
||||
DiaryId string `gorm:"column:diary_id;type:uuid;not null;uniqueIndex:ix_diary_likes_diary_user,where:deleted_at IS NULL,priority:1" json:"diary_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null;uniqueIndex:ix_diary_likes_diary_user,where:deleted_at IS NULL,priority:2" json:"user_id"`
|
||||
}
|
||||
|
||||
type DiaryFavorite struct {
|
||||
BaseModel
|
||||
DiaryId string `gorm:"column:diary_id;type:uuid;not null;uniqueIndex:ix_diary_favorites_diary_user,where:deleted_at IS NULL,priority:1" json:"diary_id"`
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null;uniqueIndex:ix_diary_favorites_diary_user,where:deleted_at IS NULL,priority:2" json:"user_id"`
|
||||
}
|
||||
|
||||
type SmokingWallet struct {
|
||||
BaseModel
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null;uniqueIndex:ux_smoking_wallets_user_id,where:deleted_at IS NULL" json:"user_id"`
|
||||
Stock int64 `gorm:"column:stock;type:bigint;not null;default:0" json:"stock"`
|
||||
}
|
||||
|
||||
type SmokingEntry struct {
|
||||
BaseModel
|
||||
UserId string `gorm:"column:user_id;type:uuid;not null;uniqueIndex:ix_smoking_entries_personal_request,where:deleted_at IS NULL,priority:1;index:ix_smoking_entries_personal_recent,priority:1" json:"user_id"`
|
||||
Kind string `gorm:"column:kind;type:varchar(20);not null" json:"kind"`
|
||||
Quantity int64 `gorm:"column:quantity;type:bigint;not null" json:"quantity"`
|
||||
Delta int64 `gorm:"column:delta;type:bigint;not null" json:"delta"`
|
||||
Balance int64 `gorm:"column:balance;type:bigint;not null" json:"balance"`
|
||||
AmountCents int64 `gorm:"column:amount_cents;type:bigint;not null;default:0" json:"amount_cents"`
|
||||
Note string `gorm:"column:note;type:varchar(200);not null;default:''" json:"note"`
|
||||
RequestKey string `gorm:"column:request_key;type:varchar(64);not null;uniqueIndex:ix_smoking_entries_personal_request,where:deleted_at IS NULL,priority:2" json:"request_key"`
|
||||
PayloadHash string `gorm:"column:payload_hash;type:char(64);not null" json:"payload_hash"`
|
||||
OriginalId *string `gorm:"column:original_id;type:uuid" json:"original_id"`
|
||||
Undone int64 `gorm:"column:undone;type:bigint;not null;default:0" json:"undone"`
|
||||
}
|
||||
|
||||
// Models is the complete AutoMigrate registry.
|
||||
func Models() []any {
|
||||
return []any{&File{}, &FileUpload{}, &RecordFile{}, &UnbindingFileCleanup{}, &User{}, &Couple{}, &Invitation{}, &UnbindRequest{}, &InviteLink{}, &Timeline{}, &Album{}, &Photo{}, &Diary{}, &Song{}, &Checkin{}, &CheckinLog{}, &SmokingAccount{}, &SmokingLog{}, &PeriodRecord{}, &PeriodDailyRecord{}, &PeriodSexRecord{}, &Setting{}, &DiaryComment{}, &SubscribeTemplate{}, &ActivityLog{}, &Admin{}, &UserToken{}, &SubscribeMessageLog{}, &NotifyLog{}, &Pet{}, &PetDaily{}, &PetAchievement{}, &PetItem{}, &PetDynamic{}, &StorageOrder{}, &DiaryLike{}, &DiaryFavorite{}, &SmokingWallet{}, &SmokingEntry{}}
|
||||
}
|
||||
|
||||
// LegacyTables names logical tables used by existing report queries.
|
||||
func LegacyTables() map[string]any {
|
||||
return map[string]any{"files": &File{}, "file_uploads": &FileUpload{}, "record_files": &RecordFile{}, "unbinding_file_cleanup": &UnbindingFileCleanup{}, "users": &User{}, "couples": &Couple{}, "invitations": &Invitation{}, "unbind_requests": &UnbindRequest{}, "invite_links": &InviteLink{}, "timeline": &Timeline{}, "albums": &Album{}, "photos": &Photo{}, "diaries": &Diary{}, "songs": &Song{}, "checkins": &Checkin{}, "checkin_logs": &CheckinLog{}, "smoking_accounts": &SmokingAccount{}, "smoking_logs": &SmokingLog{}, "period_records": &PeriodRecord{}, "period_daily_records": &PeriodDailyRecord{}, "period_sex_records": &PeriodSexRecord{}, "settings": &Setting{}, "diary_comments": &DiaryComment{}, "subscribe_templates": &SubscribeTemplate{}, "activity_log": &ActivityLog{}, "admins": &Admin{}, "user_tokens": &UserToken{}, "subscribe_message_logs": &SubscribeMessageLog{}, "notify_log": &NotifyLog{}, "pets": &Pet{}, "pet_dailies": &PetDaily{}, "pet_achievements": &PetAchievement{}, "pet_items": &PetItem{}, "pet_dynamics": &PetDynamic{}, "storage_orders": &StorageOrder{}, "diary_likes": &DiaryLike{}, "diary_favorites": &DiaryFavorite{}, "smoking_wallets": &SmokingWallet{}, "smoking_entries": &SmokingEntry{}}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"gorm.io/gorm/schema"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFileForeignKeysTargetPrimaryIDs(t *testing.T) {
|
||||
cache := &sync.Map{}
|
||||
n := schema.NamingStrategy{TablePrefix: "past_", SingularTable: true}
|
||||
owner, err := schema.Parse(&FileUpload{}, cache, n)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
relation := owner.Relationships.Relations["User"]
|
||||
if relation.Type != schema.BelongsTo || len(relation.References) != 1 || relation.References[0].ForeignKey.Schema.Name != "FileUpload" || relation.References[0].PrimaryKey.Name != "Id" {
|
||||
t.Fatal("upload ownership must reference users.id, never its display number")
|
||||
}
|
||||
user, err := schema.Parse(&User{}, cache, n)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if user.LookUpField("user_id").DataType != schema.DataType("varchar(20)") {
|
||||
t.Fatal("file association changed the public display number type")
|
||||
}
|
||||
for _, m := range []any{&RecordFile{}, &User{}, &Photo{}, &Album{}, &Checkin{}, &CheckinLog{}, &Setting{}, &Timeline{}} {
|
||||
s, e := schema.Parse(m, cache, n)
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
for _, r := range s.Relationships.Relations {
|
||||
if r.Type != schema.BelongsTo {
|
||||
t.Fatalf("%s.%s inferred wrong relationship %s", s.Name, r.Name, r.Type)
|
||||
}
|
||||
for _, ref := range r.References {
|
||||
if ref.PrimaryKey.Name != "Id" {
|
||||
t.Fatal("file reference does not target a primary UUID")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user