forgejo-tickets/internal/models/models.go

128 lines
5.5 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type TicketStatus string
const (
TicketStatusOpen TicketStatus = "open"
TicketStatusInProgress TicketStatus = "in_progress"
TicketStatusClosed TicketStatus = "closed"
)
type TokenType string
const (
TokenTypeVerifyEmail TokenType = "verify_email"
TokenTypeResetPassword TokenType = "reset_password"
)
type User struct {
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"`
Email string `gorm:"uniqueIndex;not null" json:"email"`
PasswordHash *string `json:"-"`
Name string `gorm:"not null" json:"name"`
EmailVerified bool `gorm:"not null;default:false" json:"email_verified"`
CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at"`
UpdatedAt time.Time `gorm:"not null;default:now()" json:"updated_at"`
}
type OAuthAccount struct {
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"`
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"`
User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE" json:"-"`
Provider string `gorm:"not null" json:"provider"`
ProviderUserID string `gorm:"not null" json:"provider_user_id"`
Email string `gorm:"not null" json:"email"`
CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at"`
}
func (OAuthAccount) TableName() string { return "oauth_accounts" }
type Session struct {
Token string `gorm:"primaryKey" json:"token"`
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"`
User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE" json:"-"`
Data []byte `gorm:"not null" json:"data"`
ExpiresAt time.Time `gorm:"not null;index" json:"expires_at"`
}
type Repo struct {
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"`
Name string `gorm:"not null" json:"name"`
Slug string `gorm:"uniqueIndex;not null" json:"slug"`
ForgejoOwner string `gorm:"not null" json:"forgejo_owner"`
ForgejoRepo string `gorm:"not null" json:"forgejo_repo"`
WebhookSecret string `gorm:"not null" json:"webhook_secret"`
Active bool `gorm:"not null;default:true" json:"active"`
CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at"`
}
type Ticket struct {
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"`
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"`
User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE" json:"-"`
RepoID uuid.UUID `gorm:"type:uuid;not null;index" json:"repo_id"`
Repo Repo `gorm:"foreignKey:RepoID;constraint:OnDelete:CASCADE" json:"-"`
Title string `gorm:"not null" json:"title"`
Description string `gorm:"not null" json:"description"`
Status TicketStatus `gorm:"type:ticket_status;not null;default:'open';index" json:"status"`
ForgejoIssueNumber *int64 `json:"forgejo_issue_number"`
CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at"`
UpdatedAt time.Time `gorm:"not null;default:now()" json:"updated_at"`
}
type TicketComment struct {
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"`
TicketID uuid.UUID `gorm:"type:uuid;not null;index" json:"ticket_id"`
Ticket Ticket `gorm:"foreignKey:TicketID;constraint:OnDelete:CASCADE" json:"-"`
UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"`
User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE" json:"-"`
Body string `gorm:"not null" json:"body"`
ForgejoCommentID *int64 `json:"forgejo_comment_id"`
CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at"`
}
type EmailToken struct {
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"`
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"`
User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE" json:"-"`
TokenHash string `gorm:"uniqueIndex;not null" json:"token_hash"`
TokenType TokenType `gorm:"type:token_type;not null" json:"token_type"`
ExpiresAt time.Time `gorm:"not null" json:"expires_at"`
UsedAt *time.Time `json:"used_at"`
}
// AutoMigrate runs GORM auto-migration for all models.
// Note: enum types and partial indexes must be created via SQL migrations.
func AutoMigrate(db *gorm.DB) error {
// Create enum types if they don't exist
db.Exec("DO $$ BEGIN CREATE TYPE ticket_status AS ENUM ('open', 'in_progress', 'closed'); EXCEPTION WHEN duplicate_object THEN null; END $$;")
db.Exec("DO $$ BEGIN CREATE TYPE token_type AS ENUM ('verify_email', 'reset_password'); EXCEPTION WHEN duplicate_object THEN null; END $$;")
if err := db.AutoMigrate(
&User{},
&OAuthAccount{},
&Session{},
&Repo{},
&Ticket{},
&TicketComment{},
&EmailToken{},
); err != nil {
return err
}
// Create unique composite index for oauth_accounts
db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_oauth_provider_user ON oauth_accounts(provider, provider_user_id)")
// Create partial unique index for ticket forgejo issue lookup
db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_tickets_repo_forgejo_issue ON tickets(repo_id, forgejo_issue_number) WHERE forgejo_issue_number IS NOT NULL")
return nil
}