Yimaru-BackEnd/internal/repository/lms_practices.go
Yared Yemane 71bc09a638 Make practice title optional on create.
POST /practices and exam-prep practice create accept missing or null title; persist as empty string. Refresh OpenAPI and document the behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 04:11:09 -07:00

262 lines
7.4 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 int64PtrToPg8(p *int64) pgtype.Int8 {
if p == nil {
return pgtype.Int8{Valid: false}
}
return pgtype.Int8{Int64: *p, Valid: true}
}
func fromPgInt8ID(c pgtype.Int8) *int64 {
if !c.Valid {
return nil
}
v := c.Int64
return &v
}
func optionalInt8UpdateID(val *int64) pgtype.Int8 {
if val == nil {
return pgtype.Int8{Valid: false}
}
return pgtype.Int8{Int64: *val, Valid: true}
}
func derefString(p *string) string {
if p == nil {
return ""
}
return *p
}
func lmsPracticeToDomain(p dbgen.LmsPractice) domain.Practice {
out := domain.Practice{
ID: p.ID,
Title: p.Title,
QuestionSetID: p.QuestionSetID,
PublishStatus: domain.PracticePublishStatusFromDB(p.PublishStatus),
}
if p.CourseID.Valid {
out.ParentKind = domain.ParentKindCourse
out.ParentID = p.CourseID.Int64
} else if p.ModuleID.Valid {
out.ParentKind = domain.ParentKindModule
out.ParentID = p.ModuleID.Int64
} else if p.LessonID.Valid {
out.ParentKind = domain.ParentKindLesson
out.ParentID = p.LessonID.Int64
}
out.StoryDescription = fromPgText(p.StoryDescription)
out.StoryImage = fromPgText(p.StoryImage)
out.QuickTips = fromPgText(p.QuickTips)
out.PersonaID = fromPgInt8ID(p.PersonaID)
out.CreatedAt = p.CreatedAt.Time
if p.UpdatedAt.Valid {
t := p.UpdatedAt.Time
out.UpdatedAt = &t
}
return out
}
func lmsFromListRow(
id, qid int64,
publishStatus string,
title string,
cid, mid, lid pgtype.Int8,
sd, si, qt pgtype.Text, pid pgtype.Int8,
ca, ua pgtype.Timestamptz,
) domain.Practice {
return lmsPracticeToDomain(dbgen.LmsPractice{
ID: id,
CourseID: cid,
ModuleID: mid,
LessonID: lid,
Title: title,
StoryDescription: sd,
StoryImage: si,
PersonaID: pid,
QuestionSetID: qid,
QuickTips: qt,
PublishStatus: publishStatus,
CreatedAt: ca,
UpdatedAt: ua,
})
}
// CreateLmsPractice sets exactly one of courseID, moduleID, lessonID (non-nil).
func (s *Store) CreateLmsPractice(
ctx context.Context,
in domain.CreatePracticeInput,
courseID, moduleID, lessonID *int64,
) (domain.Practice, error) {
ps := domain.ParsePracticePublishStatusInput(in.PublishStatus)
p, err := s.queries.CreateLmsPractice(ctx, dbgen.CreateLmsPracticeParams{
CourseID: int64PtrToPg8(courseID),
ModuleID: int64PtrToPg8(moduleID),
LessonID: int64PtrToPg8(lessonID),
Title: derefString(in.Title),
StoryDescription: toPgText(in.StoryDescription),
StoryImage: toPgText(in.StoryImage),
PersonaID: int64PtrToPg8(in.PersonaID),
QuestionSetID: in.QuestionSetID,
QuickTips: toPgText(in.QuickTips),
PublishStatus: string(ps),
})
if err != nil {
return domain.Practice{}, err
}
return lmsPracticeToDomain(p), nil
}
func (s *Store) GetLmsPracticeByID(ctx context.Context, id int64) (domain.Practice, error) {
p, err := s.queries.GetLmsPracticeByID(ctx, id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.Practice{}, pgx.ErrNoRows
}
return domain.Practice{}, err
}
return lmsPracticeToDomain(p), nil
}
// TryGetLmsPracticeByQuestionSetID returns false when no row exists.
func (s *Store) TryGetLmsPracticeByQuestionSetID(ctx context.Context, questionSetID int64) (domain.Practice, bool, error) {
p, err := s.queries.GetLmsPracticeByQuestionSetID(ctx, questionSetID)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.Practice{}, false, nil
}
return domain.Practice{}, false, err
}
return lmsPracticeToDomain(p), true, nil
}
func (s *Store) ListLmsPracticesByCourseID(ctx context.Context, courseID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error) {
rows, err := s.queries.ListLmsPracticesByCourseID(ctx, dbgen.ListLmsPracticesByCourseIDParams{
CourseID: pgtype.Int8{Int64: courseID, Valid: true},
PublishedOnly: publishedOnly,
Limit: limit,
Offset: offset,
})
if err != nil {
return nil, 0, err
}
if len(rows) == 0 {
return []domain.Practice{}, 0, nil
}
var total int64
out := make([]domain.Practice, 0, len(rows))
for i, r := range rows {
if i == 0 {
total = r.TotalCount
}
out = append(out, lmsFromListRow(
r.ID, r.QuestionSetID, r.PublishStatus, r.Title,
r.CourseID, r.ModuleID, r.LessonID,
r.StoryDescription, r.StoryImage, r.QuickTips,
r.PersonaID, r.CreatedAt, r.UpdatedAt,
))
}
return out, total, nil
}
func (s *Store) ListLmsPracticesByModuleID(ctx context.Context, moduleID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error) {
rows, err := s.queries.ListLmsPracticesByModuleID(ctx, dbgen.ListLmsPracticesByModuleIDParams{
ModuleID: pgtype.Int8{Int64: moduleID, Valid: true},
PublishedOnly: publishedOnly,
Limit: limit,
Offset: offset,
})
if err != nil {
return nil, 0, err
}
if len(rows) == 0 {
return []domain.Practice{}, 0, nil
}
var total int64
out := make([]domain.Practice, 0, len(rows))
for i, r := range rows {
if i == 0 {
total = r.TotalCount
}
out = append(out, lmsFromListRow(
r.ID, r.QuestionSetID, r.PublishStatus, r.Title,
r.CourseID, r.ModuleID, r.LessonID,
r.StoryDescription, r.StoryImage, r.QuickTips,
r.PersonaID, r.CreatedAt, r.UpdatedAt,
))
}
return out, total, nil
}
func (s *Store) ListLmsPracticesByLessonID(ctx context.Context, lessonID int64, publishedOnly bool, limit, offset int32) ([]domain.Practice, int64, error) {
rows, err := s.queries.ListLmsPracticesByLessonID(ctx, dbgen.ListLmsPracticesByLessonIDParams{
LessonID: pgtype.Int8{Int64: lessonID, Valid: true},
PublishedOnly: publishedOnly,
Limit: limit,
Offset: offset,
})
if err != nil {
return nil, 0, err
}
if len(rows) == 0 {
return []domain.Practice{}, 0, nil
}
var total int64
out := make([]domain.Practice, 0, len(rows))
for i, r := range rows {
if i == 0 {
total = r.TotalCount
}
out = append(out, lmsFromListRow(
r.ID, r.QuestionSetID, r.PublishStatus, r.Title,
r.CourseID, r.ModuleID, r.LessonID,
r.StoryDescription, r.StoryImage, r.QuickTips,
r.PersonaID, r.CreatedAt, r.UpdatedAt,
))
}
return out, total, nil
}
func (s *Store) UpdateLmsPractice(ctx context.Context, id int64, input domain.UpdatePracticeInput) (domain.Practice, error) {
var titleText pgtype.Text
if input.Title != nil {
titleText = pgtype.Text{String: *input.Title, Valid: true}
} else {
titleText = pgtype.Text{Valid: false}
}
qs := optionalInt8UpdateID(input.QuestionSetID)
p, err := s.queries.UpdateLmsPractice(ctx, dbgen.UpdateLmsPracticeParams{
ID: id,
Title: titleText,
StoryDescription: optionalTextUpdate(input.StoryDescription),
StoryImage: optionalTextUpdate(input.StoryImage),
PersonaID: optionalInt8UpdateID(input.PersonaID),
QuestionSetID: qs,
QuickTips: optionalTextUpdate(input.QuickTips),
PublishStatus: optionalPublishStatusUpdate(input.PublishStatus),
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.Practice{}, pgx.ErrNoRows
}
return domain.Practice{}, err
}
return lmsPracticeToDomain(p), nil
}
func (s *Store) DeleteLmsPractice(ctx context.Context, id int64) error {
return s.queries.DeleteLmsPractice(ctx, id)
}