package repository import ( "context" "errors" "strings" dbgen "Yimaru-Backend/gen/db" "Yimaru-Backend/internal/domain" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" ) func programToDomain(p dbgen.Program) domain.Program { out := domain.Program{ ID: p.ID, Name: p.Name, Category: p.Category, } out.Description = fromPgText(p.Description) out.Thumbnail = fromPgText(p.Thumbnail) out.CreatedAt = p.CreatedAt.Time if p.UpdatedAt.Valid { t := p.UpdatedAt.Time out.UpdatedAt = &t } out.SortOrder = int(p.SortOrder) return out } func (s *Store) CreateProgram(ctx context.Context, input domain.CreateProgramInput) (domain.Program, error) { if input.SortOrder != nil { q, tx, err := s.BeginTx(ctx) if err != nil { return domain.Program{}, err } defer func() { _ = tx.Rollback(ctx) }() target := int32(*input.SortOrder) if _, err := tx.Exec(ctx, `UPDATE programs SET sort_order = sort_order + 1 WHERE sort_order >= $1`, target); err != nil { return domain.Program{}, err } p, err := q.CreateProgram(ctx, dbgen.CreateProgramParams{ Name: input.Name, Description: toPgText(input.Description), Category: input.Category, Thumbnail: toPgText(input.Thumbnail), SortOrder: pgtype.Int4{Int32: target, Valid: true}, }) if err != nil { return domain.Program{}, err } if err := tx.Commit(ctx); err != nil { return domain.Program{}, err } return programToDomain(p), nil } p, err := s.queries.CreateProgram(ctx, dbgen.CreateProgramParams{ Name: input.Name, Description: toPgText(input.Description), Category: input.Category, Thumbnail: toPgText(input.Thumbnail), SortOrder: pgtype.Int4{Valid: false}, }) if err != nil { return domain.Program{}, err } return programToDomain(p), nil } func (s *Store) ListAllProgramIDs(ctx context.Context) ([]int64, error) { return s.queries.ListAllProgramIDs(ctx) } func (s *Store) GetProgramByID(ctx context.Context, id int64) (domain.Program, error) { p, err := s.queries.GetProgramByID(ctx, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.Program{}, pgx.ErrNoRows } return domain.Program{}, err } return programToDomain(p), nil } func (s *Store) ListPrograms(ctx context.Context, limit, offset int32) ([]domain.Program, int64, error) { rows, err := s.queries.ListPrograms(ctx, dbgen.ListProgramsParams{ Limit: limit, Offset: offset, }) if err != nil { return nil, 0, err } if len(rows) == 0 { return []domain.Program{}, 0, nil } var total int64 out := make([]domain.Program, 0, len(rows)) for i, r := range rows { if i == 0 { total = r.TotalCount } out = append(out, programToDomain(dbgen.Program{ ID: r.ID, Name: r.Name, Description: r.Description, Category: r.Category, Thumbnail: r.Thumbnail, CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt, SortOrder: r.SortOrder, })) } return out, total, nil } func optionalTextUpdate(val *string) pgtype.Text { if val == nil { return pgtype.Text{Valid: false} } return pgtype.Text{String: *val, Valid: true} } func optionalPublishStatusUpdate(val *string) pgtype.Text { if val == nil { return pgtype.Text{Valid: false} } s := strings.TrimSpace(strings.ToUpper(*val)) switch s { case string(domain.PracticePublishDraft), string(domain.PracticePublishPublished): return pgtype.Text{String: s, Valid: true} default: return pgtype.Text{Valid: false} } } func optionalInt4Update(v *int) pgtype.Int4 { if v == nil { return pgtype.Int4{Valid: false} } return pgtype.Int4{Int32: int32(*v), Valid: true} } func (s *Store) UpdateProgram(ctx context.Context, id int64, input domain.UpdateProgramInput) (domain.Program, error) { sortParam := optionalInt4Update(input.SortOrder) if input.SortOrder != nil { cur, err := s.GetProgramByID(ctx, id) if err != nil { return domain.Program{}, err } oldPos := int32(cur.SortOrder) newPos := int32(*input.SortOrder) if oldPos != newPos { q, tx, err := s.BeginTx(ctx) if err != nil { return domain.Program{}, err } defer func() { _ = tx.Rollback(ctx) }() if err := repositionProgramSortOrder(ctx, tx, id, oldPos, newPos); err != nil { return domain.Program{}, err } var nameText pgtype.Text if input.Name != nil { nameText = pgtype.Text{String: *input.Name, Valid: true} } else { nameText = pgtype.Text{Valid: false} } p, err := q.UpdateProgram(ctx, dbgen.UpdateProgramParams{ ID: id, Name: nameText, Description: optionalTextUpdate(input.Description), Category: optionalTextUpdate(input.Category), Thumbnail: optionalTextUpdate(input.Thumbnail), SortOrder: pgtype.Int4{Valid: false}, }) if err != nil { return domain.Program{}, err } if err := tx.Commit(ctx); err != nil { return domain.Program{}, err } return programToDomain(p), nil } sortParam = pgtype.Int4{Valid: false} } var nameText pgtype.Text if input.Name != nil { nameText = pgtype.Text{String: *input.Name, Valid: true} } else { nameText = pgtype.Text{Valid: false} } p, err := s.queries.UpdateProgram(ctx, dbgen.UpdateProgramParams{ ID: id, Name: nameText, Description: optionalTextUpdate(input.Description), Category: optionalTextUpdate(input.Category), Thumbnail: optionalTextUpdate(input.Thumbnail), SortOrder: sortParam, }) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.Program{}, pgx.ErrNoRows } return domain.Program{}, err } return programToDomain(p), nil } func (s *Store) DeleteProgram(ctx context.Context, id int64) error { return s.queries.DeleteProgram(ctx, id) }