package training import ( "context" "encoding/json" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" ) // CreateMedia creates a new media record func (s *Store) CreateMedia(ctx context.Context, media *TrainingMedia) error { media.ID = uuid.New() media.CreatedAt = time.Now().UTC() media.UpdatedAt = media.CreatedAt if media.Metadata == nil { media.Metadata = json.RawMessage("{}") } _, err := s.pool.Exec(ctx, ` INSERT INTO training_media ( id, module_id, content_id, media_type, status, bucket, object_key, file_size_bytes, duration_seconds, mime_type, voice_model, language, metadata, error_message, generated_by, is_published, created_at, updated_at ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18 ) `, media.ID, media.ModuleID, media.ContentID, string(media.MediaType), string(media.Status), media.Bucket, media.ObjectKey, media.FileSizeBytes, media.DurationSeconds, media.MimeType, media.VoiceModel, media.Language, media.Metadata, media.ErrorMessage, media.GeneratedBy, media.IsPublished, media.CreatedAt, media.UpdatedAt, ) return err } // GetMedia retrieves a media record by ID func (s *Store) GetMedia(ctx context.Context, id uuid.UUID) (*TrainingMedia, error) { var media TrainingMedia var mediaType, status string err := s.pool.QueryRow(ctx, ` SELECT id, module_id, content_id, media_type, status, bucket, object_key, file_size_bytes, duration_seconds, mime_type, voice_model, language, metadata, error_message, generated_by, is_published, created_at, updated_at FROM training_media WHERE id = $1 `, id).Scan( &media.ID, &media.ModuleID, &media.ContentID, &mediaType, &status, &media.Bucket, &media.ObjectKey, &media.FileSizeBytes, &media.DurationSeconds, &media.MimeType, &media.VoiceModel, &media.Language, &media.Metadata, &media.ErrorMessage, &media.GeneratedBy, &media.IsPublished, &media.CreatedAt, &media.UpdatedAt, ) if err == pgx.ErrNoRows { return nil, nil } if err != nil { return nil, err } media.MediaType = MediaType(mediaType) media.Status = MediaStatus(status) return &media, nil } // GetMediaForModule retrieves all media for a module func (s *Store) GetMediaForModule(ctx context.Context, moduleID uuid.UUID) ([]TrainingMedia, error) { rows, err := s.pool.Query(ctx, ` SELECT id, module_id, content_id, media_type, status, bucket, object_key, file_size_bytes, duration_seconds, mime_type, voice_model, language, metadata, error_message, generated_by, is_published, created_at, updated_at FROM training_media WHERE module_id = $1 ORDER BY media_type, created_at DESC `, moduleID) if err != nil { return nil, err } defer rows.Close() var mediaList []TrainingMedia for rows.Next() { var media TrainingMedia var mediaType, status string if err := rows.Scan( &media.ID, &media.ModuleID, &media.ContentID, &mediaType, &status, &media.Bucket, &media.ObjectKey, &media.FileSizeBytes, &media.DurationSeconds, &media.MimeType, &media.VoiceModel, &media.Language, &media.Metadata, &media.ErrorMessage, &media.GeneratedBy, &media.IsPublished, &media.CreatedAt, &media.UpdatedAt, ); err != nil { return nil, err } media.MediaType = MediaType(mediaType) media.Status = MediaStatus(status) mediaList = append(mediaList, media) } if mediaList == nil { mediaList = []TrainingMedia{} } return mediaList, nil } // UpdateMediaStatus updates the status and related fields of a media record func (s *Store) UpdateMediaStatus(ctx context.Context, id uuid.UUID, status MediaStatus, sizeBytes int64, duration float64, errMsg string) error { _, err := s.pool.Exec(ctx, ` UPDATE training_media SET status = $2, file_size_bytes = $3, duration_seconds = $4, error_message = $5, updated_at = NOW() WHERE id = $1 `, id, string(status), sizeBytes, duration, errMsg) return err } // PublishMedia publishes or unpublishes a media record func (s *Store) PublishMedia(ctx context.Context, id uuid.UUID, publish bool) error { _, err := s.pool.Exec(ctx, ` UPDATE training_media SET is_published = $2, updated_at = NOW() WHERE id = $1 `, id, publish) return err } // GetPublishedAudio gets the published audio for a module func (s *Store) GetPublishedAudio(ctx context.Context, moduleID uuid.UUID) (*TrainingMedia, error) { var media TrainingMedia var mediaType, status string err := s.pool.QueryRow(ctx, ` SELECT id, module_id, content_id, media_type, status, bucket, object_key, file_size_bytes, duration_seconds, mime_type, voice_model, language, metadata, error_message, generated_by, is_published, created_at, updated_at FROM training_media WHERE module_id = $1 AND media_type = 'audio' AND is_published = true ORDER BY created_at DESC LIMIT 1 `, moduleID).Scan( &media.ID, &media.ModuleID, &media.ContentID, &mediaType, &status, &media.Bucket, &media.ObjectKey, &media.FileSizeBytes, &media.DurationSeconds, &media.MimeType, &media.VoiceModel, &media.Language, &media.Metadata, &media.ErrorMessage, &media.GeneratedBy, &media.IsPublished, &media.CreatedAt, &media.UpdatedAt, ) if err == pgx.ErrNoRows { return nil, nil } if err != nil { return nil, err } media.MediaType = MediaType(mediaType) media.Status = MediaStatus(status) return &media, nil } // GetPublishedVideo gets the published video for a module func (s *Store) GetPublishedVideo(ctx context.Context, moduleID uuid.UUID) (*TrainingMedia, error) { var media TrainingMedia var mediaType, status string err := s.pool.QueryRow(ctx, ` SELECT id, module_id, content_id, media_type, status, bucket, object_key, file_size_bytes, duration_seconds, mime_type, voice_model, language, metadata, error_message, generated_by, is_published, created_at, updated_at FROM training_media WHERE module_id = $1 AND media_type = 'video' AND is_published = true ORDER BY created_at DESC LIMIT 1 `, moduleID).Scan( &media.ID, &media.ModuleID, &media.ContentID, &mediaType, &status, &media.Bucket, &media.ObjectKey, &media.FileSizeBytes, &media.DurationSeconds, &media.MimeType, &media.VoiceModel, &media.Language, &media.Metadata, &media.ErrorMessage, &media.GeneratedBy, &media.IsPublished, &media.CreatedAt, &media.UpdatedAt, ) if err == pgx.ErrNoRows { return nil, nil } if err != nil { return nil, err } media.MediaType = MediaType(mediaType) media.Status = MediaStatus(status) return &media, nil }