Spaces:
Running
Running
| 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 | |
| } | |