Yimaru-BackEnd/internal/repository/lms_courses.go
2026-06-10 05:20:12 -07:00

218 lines
6.3 KiB
Go

package repository
import (
"context"
"errors"
dbgen "Yimaru-Backend/gen/db"
"Yimaru-Backend/internal/domain"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
func courseToDomain(c dbgen.Course) domain.Course {
out := domain.Course{
ID: c.ID,
ProgramID: c.ProgramID,
Name: c.Name,
PublishStatus: domain.ContentPublishStatusFromDB(c.PublishStatus),
AccessTier: domain.ContentAccessTierFromDB(c.AccessTier),
}
out.Description = fromPgText(c.Description)
out.Thumbnail = fromPgText(c.Thumbnail)
out.CreatedAt = c.CreatedAt.Time
if c.UpdatedAt.Valid {
t := c.UpdatedAt.Time
out.UpdatedAt = &t
}
out.SortOrder = int(c.SortOrder)
return out
}
func (s *Store) CreateCourse(ctx context.Context, programID int64, input domain.CreateCourseInput) (domain.Course, error) {
pub := string(domain.ContentPublishStatusFromCreateInput(input.PublishStatus))
if input.SortOrder != nil {
q, tx, err := s.BeginTx(ctx)
if err != nil {
return domain.Course{}, err
}
defer func() { _ = tx.Rollback(ctx) }()
target := int32(*input.SortOrder)
if err := shiftCoursesSortOrderForInsert(ctx, tx, programID, target); err != nil {
return domain.Course{}, err
}
c, err := q.CreateCourse(ctx, dbgen.CreateCourseParams{
ProgramID: programID,
Name: input.Name,
Description: toPgText(input.Description),
Thumbnail: toPgText(input.Thumbnail),
SortOrder: pgtype.Int4{Int32: target, Valid: true},
PublishStatus: pub,
AccessTier: contentAccessTierForCreate(input.AccessTier),
})
if err != nil {
return domain.Course{}, err
}
if err := tx.Commit(ctx); err != nil {
return domain.Course{}, err
}
return courseToDomain(c), nil
}
c, err := s.queries.CreateCourse(ctx, dbgen.CreateCourseParams{
ProgramID: programID,
Name: input.Name,
Description: toPgText(input.Description),
Thumbnail: toPgText(input.Thumbnail),
SortOrder: pgtype.Int4{Valid: false},
PublishStatus: pub,
AccessTier: contentAccessTierForCreate(input.AccessTier),
})
if err != nil {
return domain.Course{}, err
}
return courseToDomain(c), nil
}
func (s *Store) ListCourseIDsByProgram(ctx context.Context, programID int64) ([]int64, error) {
return s.queries.ListCourseIDsByProgram(ctx, programID)
}
func (s *Store) ListPublishedCourseIDsByProgram(ctx context.Context, programID int64) ([]int64, error) {
return s.queries.ListPublishedCourseIDsByProgram(ctx, programID)
}
func (s *Store) GetCourseByID(ctx context.Context, id int64) (domain.Course, error) {
c, err := s.queries.GetCourseByID(ctx, id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.Course{}, pgx.ErrNoRows
}
return domain.Course{}, err
}
out := courseToDomain(dbgen.Course{
ID: c.ID,
ProgramID: c.ProgramID,
Name: c.Name,
Description: c.Description,
Thumbnail: c.Thumbnail,
SortOrder: c.SortOrder,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
PublishStatus: c.PublishStatus,
AccessTier: c.AccessTier,
})
out.HasPractice = c.HasPractice
return out, nil
}
func (s *Store) ListCoursesByProgramID(ctx context.Context, programID int64, publishedOnly bool, limit, offset int32) ([]domain.Course, int64, error) {
rows, err := s.queries.ListCoursesByProgramID(ctx, dbgen.ListCoursesByProgramIDParams{
ProgramID: programID,
Limit: limit,
Offset: offset,
PublishedOnly: publishedOnly,
})
if err != nil {
return nil, 0, err
}
if len(rows) == 0 {
return []domain.Course{}, 0, nil
}
var total int64
out := make([]domain.Course, 0, len(rows))
for i, r := range rows {
if i == 0 {
total = r.TotalCount
}
co := courseToDomain(dbgen.Course{
ID: r.ID,
ProgramID: r.ProgramID,
Name: r.Name,
Description: r.Description,
Thumbnail: r.Thumbnail,
CreatedAt: r.CreatedAt,
UpdatedAt: r.UpdatedAt,
SortOrder: r.SortOrder,
PublishStatus: r.PublishStatus,
AccessTier: r.AccessTier,
})
co.ModuleCount = int(r.ModuleCount)
co.LessonCount = int(r.LessonCount)
co.PracticeCount = int(r.PracticeCount)
co.HasPractice = r.HasPractice
out = append(out, co)
}
return out, total, nil
}
func (s *Store) UpdateCourse(ctx context.Context, id int64, input domain.UpdateCourseInput) (domain.Course, error) {
sortParam := optionalInt4Update(input.SortOrder)
var nameText pgtype.Text
if input.Name != nil {
nameText = pgtype.Text{String: *input.Name, Valid: true}
} else {
nameText = pgtype.Text{Valid: false}
}
if input.SortOrder != nil {
cur, err := s.GetCourseByID(ctx, id)
if err != nil {
return domain.Course{}, err
}
oldPos := int32(cur.SortOrder)
newPos := int32(*input.SortOrder)
if oldPos != newPos {
q, tx, err := s.BeginTx(ctx)
if err != nil {
return domain.Course{}, err
}
defer func() { _ = tx.Rollback(ctx) }()
if err := repositionCourseSortOrder(ctx, tx, cur.ProgramID, id, oldPos, newPos); err != nil {
return domain.Course{}, err
}
c, err := q.UpdateCourse(ctx, dbgen.UpdateCourseParams{
ID: id,
Name: nameText,
Description: optionalTextUpdate(input.Description),
Thumbnail: optionalTextUpdate(input.Thumbnail),
SortOrder: pgtype.Int4{Valid: false},
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
AccessTier: optionalAccessTierUpdate(input.AccessTier),
})
if err != nil {
return domain.Course{}, err
}
if err := tx.Commit(ctx); err != nil {
return domain.Course{}, err
}
out := courseToDomain(c)
out.HasPractice = cur.HasPractice
return out, nil
}
sortParam = pgtype.Int4{Valid: false}
}
c, err := s.queries.UpdateCourse(ctx, dbgen.UpdateCourseParams{
ID: id,
Name: nameText,
Description: optionalTextUpdate(input.Description),
Thumbnail: optionalTextUpdate(input.Thumbnail),
SortOrder: sortParam,
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
AccessTier: optionalAccessTierUpdate(input.AccessTier),
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.Course{}, pgx.ErrNoRows
}
return domain.Course{}, err
}
return courseToDomain(c), nil
}
func (s *Store) DeleteCourse(ctx context.Context, id int64) error {
return s.queries.DeleteCourse(ctx, id)
}