- 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>
241 lines
9.7 KiB
Go
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
|
|
}
|