veza/veza-backend-api/internal/core/education/models.go
senke 506195f4e0 feat(v0.12.3): F276-F305 education backend service, handler, and routes
- Course CRUD with slug generation, publish/archive lifecycle
- Lesson management with ordering and transcoding status
- Enrollment system with duplicate prevention
- Progress tracking with auto-completion at 90%
- Certificate issuance requiring full course completion
- Course reviews with rating aggregation
- Unit tests for service and handler layers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:45:26 +01:00

241 lines
9.7 KiB
Go

package education
import (
"time"
"github.com/google/uuid"
"github.com/lib/pq"
"gorm.io/gorm"
)
// CourseStatus represents the publication status of a course
type CourseStatus string
const (
CourseStatusDraft CourseStatus = "draft"
CourseStatusPublished CourseStatus = "published"
CourseStatusArchived CourseStatus = "archived"
)
// CourseLevel represents the difficulty level
type CourseLevel string
const (
CourseLevelBeginner CourseLevel = "beginner"
CourseLevelIntermediate CourseLevel = "intermediate"
CourseLevelAdvanced CourseLevel = "advanced"
)
// PricingModel represents the pricing strategy
type PricingModel string
const (
PricingFixed PricingModel = "fixed"
PricingPayWhatYou PricingModel = "pay_what_you_want"
PricingFree PricingModel = "free"
)
// EnrollmentStatus represents the enrollment state
type EnrollmentStatus string
const (
EnrollmentActive EnrollmentStatus = "active"
EnrollmentExpired EnrollmentStatus = "expired"
EnrollmentRefunded EnrollmentStatus = "refunded"
)
// TranscodingStatus represents video processing state
type TranscodingStatus string
const (
TranscodingPending TranscodingStatus = "pending"
TranscodingProcessing TranscodingStatus = "processing"
TranscodingComplete TranscodingStatus = "complete"
TranscodingFailed TranscodingStatus = "failed"
)
// CertificateStatus represents certificate validity
type CertificateStatus string
const (
CertificateActive CertificateStatus = "active"
CertificateRevoked CertificateStatus = "revoked"
)
// ReviewStatus represents review moderation status
type ReviewStatus string
const (
ReviewApproved ReviewStatus = "approved"
ReviewPending ReviewStatus = "pending"
ReviewRejected ReviewStatus = "rejected"
)
// Course represents an educational course created by a user
type Course struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
CreatorID uuid.UUID `gorm:"type:uuid;not null" json:"creator_id"`
Title string `gorm:"size:255;not null" json:"title"`
Slug string `gorm:"size:255;not null;uniqueIndex" json:"slug"`
Description string `gorm:"type:text;not null;default:''" json:"description"`
Category string `gorm:"size:100" json:"category,omitempty"`
Tags pq.StringArray `gorm:"type:varchar(50)[];default:'{}'" json:"tags"`
CoverImageURL string `gorm:"type:text" json:"cover_image_url,omitempty"`
PriceCents int `gorm:"not null;default:0" json:"price_cents"`
Currency string `gorm:"size:3;not null;default:'USD'" json:"currency"`
PricingModel PricingModel `gorm:"size:50;not null;default:'fixed'" json:"pricing_model"`
MinimumPriceCents int `gorm:"default:0" json:"minimum_price_cents"`
Status CourseStatus `gorm:"size:50;not null;default:'draft'" json:"status"`
Level CourseLevel `gorm:"size:50;default:'beginner'" json:"level"`
Language string `gorm:"size:5;default:'en'" json:"language"`
TotalDurationSeconds int `gorm:"not null;default:0" json:"total_duration_seconds"`
LessonCount int `gorm:"not null;default:0" json:"lesson_count"`
EnrollmentCount int `gorm:"not null;default:0" json:"enrollment_count"`
ReviewCount int `gorm:"not null;default:0" json:"review_count"`
AverageRating float64 `gorm:"type:numeric(3,2);default:0" json:"average_rating"`
PublishedAt *time.Time `json:"published_at,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
}
func (Course) TableName() string {
return "courses"
}
func (c *Course) BeforeCreate(tx *gorm.DB) error {
if c.ID == uuid.Nil {
c.ID = uuid.New()
}
return nil
}
// Lesson represents a single lesson within a course
type Lesson struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
CourseID uuid.UUID `gorm:"type:uuid;not null" json:"course_id"`
OrderIndex int `gorm:"not null" json:"order_index"`
Title string `gorm:"size:255;not null" json:"title"`
Description string `gorm:"type:text" json:"description,omitempty"`
VideoFilePath string `gorm:"size:512" json:"video_file_path,omitempty"`
DurationSeconds int `gorm:"not null;default:0" json:"duration_seconds"`
IsPreviewFree bool `gorm:"not null;default:false" json:"is_preview_free"`
TranscodingStatus TranscodingStatus `gorm:"size:50;not null;default:'pending'" json:"transcoding_status"`
HLSMasterPlaylistURL string `gorm:"type:text" json:"hls_master_playlist_url,omitempty"`
ThumbnailURL string `gorm:"type:text" json:"thumbnail_url,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
func (Lesson) TableName() string {
return "lessons"
}
func (l *Lesson) BeforeCreate(tx *gorm.DB) error {
if l.ID == uuid.Nil {
l.ID = uuid.New()
}
return nil
}
// CourseEnrollment represents a user's enrollment (purchase) in a course
type CourseEnrollment struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"`
CourseID uuid.UUID `gorm:"type:uuid;not null" json:"course_id"`
PurchasedPriceCents int `gorm:"not null;default:0" json:"purchased_price_cents"`
Currency string `gorm:"size:3;not null;default:'USD'" json:"currency"`
Status EnrollmentStatus `gorm:"size:50;not null;default:'active'" json:"status"`
AccessExpiresAt *time.Time `json:"access_expires_at,omitempty"`
PurchasedAt time.Time `gorm:"not null" json:"purchased_at"`
RefundedAt *time.Time `json:"refunded_at,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
func (CourseEnrollment) TableName() string {
return "course_enrollments"
}
func (e *CourseEnrollment) BeforeCreate(tx *gorm.DB) error {
if e.ID == uuid.Nil {
e.ID = uuid.New()
}
return nil
}
// LessonProgress tracks a user's progress on a specific lesson
type LessonProgress struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"`
LessonID uuid.UUID `gorm:"type:uuid;not null" json:"lesson_id"`
EnrollmentID uuid.UUID `gorm:"type:uuid;not null" json:"enrollment_id"`
WatchedPercentage int `gorm:"default:0" json:"watched_percentage"`
WatchedDurationSeconds int `gorm:"default:0" json:"watched_duration_seconds"`
PlaybackPositionSeconds int `gorm:"default:0" json:"playback_position_seconds"`
IsCompleted bool `gorm:"not null;default:false" json:"is_completed"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
func (LessonProgress) TableName() string {
return "lesson_progress"
}
func (p *LessonProgress) BeforeCreate(tx *gorm.DB) error {
if p.ID == uuid.Nil {
p.ID = uuid.New()
}
return nil
}
// Certificate represents a course completion certificate
type Certificate struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"`
CourseID uuid.UUID `gorm:"type:uuid;not null" json:"course_id"`
EnrollmentID uuid.UUID `gorm:"type:uuid;not null" json:"enrollment_id"`
CertificateCode string `gorm:"size:255;not null;uniqueIndex" json:"certificate_code"`
IssueDate time.Time `gorm:"not null" json:"issue_date"`
Status CertificateStatus `gorm:"size:50;not null;default:'active'" json:"status"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
}
func (Certificate) TableName() string {
return "certificates"
}
func (cert *Certificate) BeforeCreate(tx *gorm.DB) error {
if cert.ID == uuid.Nil {
cert.ID = uuid.New()
}
return nil
}
// CourseReview represents a user's review of a course
type CourseReview struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
CourseID uuid.UUID `gorm:"type:uuid;not null" json:"course_id"`
UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"`
EnrollmentID uuid.UUID `gorm:"type:uuid;not null" json:"enrollment_id"`
Rating int `gorm:"not null" json:"rating"`
Title string `gorm:"size:255" json:"title,omitempty"`
Content string `gorm:"type:text;not null" json:"content"`
Status ReviewStatus `gorm:"size:50;not null;default:'approved'" json:"status"`
HelpfulCount int `gorm:"not null;default:0" json:"helpful_count"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
}
func (CourseReview) TableName() string {
return "course_reviews"
}
func (r *CourseReview) BeforeCreate(tx *gorm.DB) error {
if r.ID == uuid.Nil {
r.ID = uuid.New()
}
return nil
}