quzuu-api-test / services /problem_set_service.go
lifedebugger's picture
Deploy files from GitHub repository
ba58d23
package services
import (
"context"
"errors"
"io"
"abdanhafidz.com/go-boilerplate/models/dto"
entity "abdanhafidz.com/go-boilerplate/models/entity"
http_error "abdanhafidz.com/go-boilerplate/models/error"
"abdanhafidz.com/go-boilerplate/repositories"
"github.com/google/uuid"
"gorm.io/gorm"
)
var ()
type ProblemSetService interface {
CreateProblemSet(ctx context.Context, ps entity.ProblemSet) error
GetProblemSet(ctx context.Context, id uuid.UUID) (entity.ProblemSet, error)
ListProblemSets(ctx context.Context) ([]entity.ProblemSet, error)
ListProblemSetsWithPagination(ctx context.Context, p entity.Pagination) ([]entity.ProblemSet, int64, error)
UpdateProblemSet(ctx context.Context, ps entity.ProblemSet) error
DeleteProblemSet(ctx context.Context, id uuid.UUID) error
InjectProblemSetsFromCSV(ctx context.Context, fileName string, reader io.Reader) (dto.ProblemSetInjectResponse, error)
AddQuestion(ctx context.Context, q entity.Questions) error
BulkAddQuestions(ctx context.Context, problemSetId uuid.UUID, questions []entity.Questions) ([]entity.Questions, error)
UpdateQuestion(ctx context.Context, q entity.Questions) error
BulkUpdateQuestions(ctx context.Context, problemSetId uuid.UUID, questions []entity.Questions) ([]entity.Questions, error)
DeleteQuestion(ctx context.Context, qID uuid.UUID) error
ListQuestions(ctx context.Context, psID uuid.UUID) ([]entity.Questions, error)
ListQuestionsWithPagination(ctx context.Context, psID uuid.UUID, p entity.Pagination) ([]entity.Questions, int64, error)
ListProblemSetsByExam(ctx context.Context, examId uuid.UUID, p entity.Pagination) ([]entity.ProblemSetExamAssign, int64, error)
ListCandidateProblemSetsByExam(ctx context.Context, examId uuid.UUID, p entity.Pagination) ([]entity.ProblemSet, int64, error)
AssignProblemSetToExam(ctx context.Context, examId uuid.UUID, problemSetId uuid.UUID) error
RemoveAssignedProblemSet(ctx context.Context, assignId uuid.UUID) error
RemoveAssignedProblemSetByExam(ctx context.Context, examId uuid.UUID, problemSetId uuid.UUID) error
GetQuestionById(ctx context.Context, qID uuid.UUID) (entity.Questions, error)
ListQuestionsByExam(ctx context.Context, examId uuid.UUID) ([]entity.Questions, error)
}
type problemSetService struct {
problemSetRepository repositories.ProblemSetRepository
questionsRepository repositories.QuestionsRepository
problemSetExamAssignRepository repositories.ProblemSetExamAssignRepository
problemSetInjectRepository repositories.ProblemSetInjectRepository
}
func NewProblemSetService(
problemSetRepository repositories.ProblemSetRepository,
questionsRepository repositories.QuestionsRepository,
problemSetExamAssignRepository repositories.ProblemSetExamAssignRepository,
problemSetInjectRepository repositories.ProblemSetInjectRepository,
) ProblemSetService {
return &problemSetService{
problemSetRepository: problemSetRepository,
questionsRepository: questionsRepository,
problemSetExamAssignRepository: problemSetExamAssignRepository,
problemSetInjectRepository: problemSetInjectRepository,
}
}
// ---------------- Problem Set CRUD ----------------
func (s *problemSetService) CreateProblemSet(ctx context.Context, ps entity.ProblemSet) error {
return s.problemSetRepository.Create(ctx, ps)
}
func (s *problemSetService) GetProblemSet(ctx context.Context, id uuid.UUID) (entity.ProblemSet, error) {
ps, err := s.problemSetRepository.Get(ctx, id)
if err != nil {
return entity.ProblemSet{}, http_error.PROBLEM_SET_NOT_FOUND
}
return ps, nil
}
func (s *problemSetService) ListProblemSets(ctx context.Context) ([]entity.ProblemSet, error) {
return s.problemSetRepository.List(ctx)
}
func (s *problemSetService) ListProblemSetsWithPagination(ctx context.Context, p entity.Pagination) ([]entity.ProblemSet, int64, error) {
return s.problemSetRepository.ListWithPagination(ctx, p)
}
func (s *problemSetService) UpdateProblemSet(ctx context.Context, ps entity.ProblemSet) error {
_, err := s.problemSetRepository.Get(ctx, ps.Id)
if err != nil {
return http_error.PROBLEM_SET_NOT_FOUND
}
return s.problemSetRepository.Update(ctx, ps)
}
func (s *problemSetService) DeleteProblemSet(ctx context.Context, id uuid.UUID) error {
_, err := s.problemSetRepository.Get(ctx, id)
if err != nil {
return http_error.PROBLEM_SET_NOT_FOUND
}
return s.problemSetRepository.Delete(ctx, id)
}
// ---------------- Questions ----------------
func (s *problemSetService) AddQuestion(ctx context.Context, q entity.Questions) error {
_, err := s.problemSetRepository.Get(ctx, q.ProblemSetId)
if err != nil {
return http_error.PROBLEM_SET_NOT_FOUND
}
normalizeDescriptionQuestion(&q)
return s.questionsRepository.Create(ctx, q)
}
func (s *problemSetService) BulkAddQuestions(ctx context.Context, problemSetId uuid.UUID, questions []entity.Questions) ([]entity.Questions, error) {
if len(questions) == 0 {
return nil, http_error.BAD_REQUEST_ERROR
}
_, err := s.problemSetRepository.Get(ctx, problemSetId)
if err != nil {
return nil, http_error.PROBLEM_SET_NOT_FOUND
}
ids := make([]uuid.UUID, 0, len(questions))
for i := range questions {
questions[i].ProblemSetId = problemSetId
normalizeDescriptionQuestion(&questions[i])
ids = append(ids, questions[i].Id)
}
if err := s.questionsRepository.CreateBulk(ctx, questions); err != nil {
return nil, err
}
created, err := s.questionsRepository.ListByProblemSetAndIDs(ctx, problemSetId, ids)
if err != nil {
return nil, err
}
byID := make(map[uuid.UUID]entity.Questions, len(created))
for _, item := range created {
byID[item.Id] = item
}
ordered := make([]entity.Questions, 0, len(questions))
for _, item := range questions {
if question, ok := byID[item.Id]; ok {
ordered = append(ordered, question)
}
}
return ordered, nil
}
func (s *problemSetService) UpdateQuestion(ctx context.Context, q entity.Questions) error {
_, err := s.questionsRepository.Get(ctx, q.Id)
if err != nil {
return http_error.QUESTION_NOT_FOUND
}
normalizeDescriptionQuestion(&q)
return s.questionsRepository.Update(ctx, q)
}
func (s *problemSetService) BulkUpdateQuestions(ctx context.Context, problemSetId uuid.UUID, questions []entity.Questions) ([]entity.Questions, error) {
if len(questions) == 0 {
return nil, http_error.BAD_REQUEST_ERROR
}
_, err := s.problemSetRepository.Get(ctx, problemSetId)
if err != nil {
return nil, http_error.PROBLEM_SET_NOT_FOUND
}
ids := make([]uuid.UUID, 0, len(questions))
seen := make(map[uuid.UUID]struct{}, len(questions))
for _, question := range questions {
if _, exists := seen[question.Id]; exists {
return nil, http_error.BAD_REQUEST_ERROR
}
seen[question.Id] = struct{}{}
ids = append(ids, question.Id)
}
existing, err := s.questionsRepository.ListByProblemSetAndIDs(ctx, problemSetId, ids)
if err != nil {
return nil, err
}
if len(existing) != len(ids) {
return nil, http_error.QUESTION_NOT_FOUND
}
for i := range questions {
questions[i].ProblemSetId = problemSetId
normalizeDescriptionQuestion(&questions[i])
}
if err := s.questionsRepository.UpdateBulk(ctx, questions); err != nil {
return nil, err
}
updated, err := s.questionsRepository.ListByProblemSetAndIDs(ctx, problemSetId, ids)
if err != nil {
return nil, err
}
byID := make(map[uuid.UUID]entity.Questions, len(updated))
for _, item := range updated {
byID[item.Id] = item
}
ordered := make([]entity.Questions, 0, len(questions))
for _, item := range questions {
if question, ok := byID[item.Id]; ok {
ordered = append(ordered, question)
}
}
return ordered, nil
}
func (s *problemSetService) DeleteQuestion(ctx context.Context, qID uuid.UUID) error {
_, err := s.questionsRepository.Get(ctx, qID)
if err != nil {
return http_error.QUESTION_NOT_FOUND
}
return s.questionsRepository.Delete(ctx, qID)
}
func (s *problemSetService) ListQuestions(ctx context.Context, psID uuid.UUID) ([]entity.Questions, error) {
_, err := s.problemSetRepository.Get(ctx, psID)
if err != nil {
return nil, http_error.PROBLEM_SET_NOT_FOUND
}
return s.questionsRepository.ListByProblemSet(ctx, psID)
}
func (s *problemSetService) ListQuestionsWithPagination(ctx context.Context, psID uuid.UUID, p entity.Pagination) ([]entity.Questions, int64, error) {
_, err := s.problemSetRepository.Get(ctx, psID)
if err != nil {
return nil, 0, http_error.PROBLEM_SET_NOT_FOUND
}
return s.questionsRepository.ListByProblemSetWithPagination(ctx, psID, p)
}
// ---------------- Exam ↔ Problem Set (Mapping Table) ----------------
func (s *problemSetService) AssignProblemSetToExam(ctx context.Context, examId uuid.UUID, problemSetId uuid.UUID) error {
_, err := s.problemSetRepository.Get(ctx, problemSetId)
if err != nil {
return http_error.PROBLEM_SET_NOT_FOUND
}
if _, err := s.problemSetExamAssignRepository.GetByExamAndProblemSet(ctx, examId, problemSetId); err == nil {
return http_error.DUPLICATE_DATA
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
assign := entity.ProblemSetExamAssign{
Id: uuid.New(),
ExamId: examId,
ProblemSetId: problemSetId,
}
return s.problemSetExamAssignRepository.Create(ctx, assign)
}
func (s *problemSetService) RemoveAssignedProblemSet(ctx context.Context, assignId uuid.UUID) error {
return s.problemSetExamAssignRepository.Delete(ctx, assignId)
}
func (s *problemSetService) ListProblemSetsByExam(ctx context.Context, examId uuid.UUID, p entity.Pagination) ([]entity.ProblemSetExamAssign, int64, error) {
return s.problemSetExamAssignRepository.ListByExam(ctx, examId, p)
}
func (s *problemSetService) ListCandidateProblemSetsByExam(ctx context.Context, examId uuid.UUID, p entity.Pagination) ([]entity.ProblemSet, int64, error) {
return s.problemSetExamAssignRepository.ListCandidateByExam(ctx, examId, p)
}
func (s *problemSetService) RemoveAssignedProblemSetByExam(ctx context.Context, examId uuid.UUID, problemSetId uuid.UUID) error {
_, err := s.problemSetExamAssignRepository.GetByExamAndProblemSet(ctx, examId, problemSetId)
if errors.Is(err, gorm.ErrRecordNotFound) {
return http_error.DATA_NOT_FOUND
}
if err != nil {
return err
}
return s.problemSetExamAssignRepository.DeleteByExamAndProblemSet(ctx, examId, problemSetId)
}
func (s *problemSetService) GetQuestionById(ctx context.Context, qID uuid.UUID) (entity.Questions, error) {
question, err := s.questionsRepository.Get(ctx, qID)
if err != nil {
return entity.Questions{}, err
}
return question, err
}
func (s *problemSetService) ListQuestionsByExam(ctx context.Context, examId uuid.UUID) ([]entity.Questions, error) {
assign, err := s.problemSetExamAssignRepository.GetByExam(ctx, examId)
if err != nil {
return []entity.Questions{}, err
}
return s.questionsRepository.ListByProblemSet(ctx, assign.ProblemSetId)
}
func normalizeDescriptionQuestion(q *entity.Questions) {
if q == nil || q.Type != "description" {
return
}
q.Options = nil
q.AnsKey = nil
q.CorrMark = 0
q.IncorrMark = 0
q.NullMark = 0
}