package state import ( "fmt" "time" "xorm.io/xorm" "xorm.io/xorm/names" ) type BaseModel struct { ID int64 `xorm:"pk autoincr" json:"id"` CreatedAt time.Time `xorm:"created" json:"created_at"` UpdatedAt time.Time `xorm:"updated" json:"updated_at"` // DeletedAt *time.Time `xorm:"deleted" json:"deleted_at,omitempty"` } type Namespace struct { BaseModel `xorm:"extends"` Name string `xorm:"varchar(255) unique notnull" json:"name"` PasswordHash string `xorm:"varchar(255)" json:"-"` } type Repo struct { BaseModel `xorm:"extends"` NamespaceID int64 `xorm:"notnull unique(cred)" json:"namespace_id"` Name string `xorm:"varchar(255) notnull unique(cred)" json:"reponame"` IsPrivate bool `xorm:"default false" json:"is_private"` } type SSHKey struct { BaseModel `xorm:"extends"` PublicKey string `xorm:"text notnull" json:"-"` Fingerprint string `xorm:"varchar(255) unique notnull" json:"fingerprint"` KeyType string `xorm:"varchar(50) notnull" json:"key_type"` Comment string `xorm:"varchar(255)" json:"comment,omitempty"` LastUsedAt *time.Time `xorm:"" json:"last_used_at,omitempty"` } type PAT struct { BaseModel `xorm:"extends"` TokenHash string `xorm:"varchar(64) unique notnull" json:"-"` Prefix string `xorm:"varchar(20)" json:"prefix"` Name string `xorm:"varchar(255)" json:"name"` ExpiresAt *time.Time `xorm:"" json:"expires_at,omitempty"` LastUsedAt *time.Time `xorm:"" json:"last_used_at,omitempty"` } type ACL struct { BaseModel `xorm:"extends"` CredType int `xorm:"notnull unique(cred) index" json:"cred_type"` CredID int64 `xorm:"notnull unique(cred)" json:"cred_id"` TargetType int `xorm:"notnull unique(cred) index" json:"target_type"` TargetID int64 `xorm:"notnull unique(cred)" json:"target_id"` Perm string `xorm:"varchar(20) default 'read'" json:"perm"` } type CredType int const ( CredTypePAT CredType = 0 CredTypeSSH CredType = 1 CredTypeUserPswd CredType = 2 ) type TargetType int const ( TargetTypeRepo TargetType = 0 TargetTypeNS TargetType = 1 ) func InitEngine(driverName, dataSourceName string) (*xorm.Engine, error) { engine, err := xorm.NewEngine(driverName, dataSourceName) if err != nil { return nil, err } engine.SetTableMapper(names.GonicMapper{}) engine.SetColumnMapper(names.GonicMapper{}) if driverName == sqliteDriver { if err := migrateLegacySQLite(engine); err != nil { _ = engine.Close() return nil, err } } if err := engine.Sync2( new(Namespace), new(Repo), new(SSHKey), new(PAT), new(ACL), ); err != nil { return nil, err } return engine, nil } // migrateLegacySQLite handles model field renames that Sync2 otherwise treats // as new NOT NULL columns. The migration is idempotent and preserves old repos. func migrateLegacySQLite(engine *xorm.Engine) error { rows, err := engine.QueryString("PRAGMA table_info(repo)") if err != nil { return fmt.Errorf("inspect legacy repo schema: %w", err) } columns := make(map[string]bool, len(rows)) for _, row := range rows { columns[row["name"]] = true } if columns["repo_name"] && !columns["name"] { if _, err := engine.Exec("ALTER TABLE repo RENAME COLUMN repo_name TO name"); err != nil { return fmt.Errorf("migrate repo column repo_name to name: %w", err) } } return nil }