package service import ( "context" "errors" "eslogad-be/internal/contract" "eslogad-be/internal/entities" "eslogad-be/internal/transformer" "github.com/google/uuid" ) type VoteEventRepository interface { Create(ctx context.Context, voteEvent *entities.VoteEvent) error GetByID(ctx context.Context, id uuid.UUID) (*entities.VoteEvent, error) GetActiveEvents(ctx context.Context) ([]*entities.VoteEvent, error) List(ctx context.Context, limit, offset int) ([]*entities.VoteEvent, int64, error) Update(ctx context.Context, voteEvent *entities.VoteEvent) error Delete(ctx context.Context, id uuid.UUID) error CreateCandidate(ctx context.Context, candidate *entities.Candidate) error GetCandidatesByEventID(ctx context.Context, eventID uuid.UUID) ([]*entities.Candidate, error) SubmitVote(ctx context.Context, vote *entities.Vote) error HasUserVoted(ctx context.Context, userID, eventID uuid.UUID) (bool, error) GetVoteResults(ctx context.Context, eventID uuid.UUID) (map[uuid.UUID]int64, error) } type VoteEventServiceImpl struct { voteEventRepo VoteEventRepository } func NewVoteEventService(voteEventRepo VoteEventRepository) *VoteEventServiceImpl { return &VoteEventServiceImpl{ voteEventRepo: voteEventRepo, } } func (s *VoteEventServiceImpl) CreateVoteEvent(ctx context.Context, req *contract.CreateVoteEventRequest) (*contract.VoteEventResponse, error) { if req.EndDate.Before(req.StartDate) { return nil, errors.New("end date must be after start date") } voteEvent := &entities.VoteEvent{ Title: req.Title, Description: req.Description, StartDate: req.StartDate, EndDate: req.EndDate, IsActive: true, ResultsOpen: false, } if req.ResultsOpen != nil { voteEvent.ResultsOpen = *req.ResultsOpen } if err := s.voteEventRepo.Create(ctx, voteEvent); err != nil { return nil, err } return transformer.VoteEventToContract(voteEvent), nil } func (s *VoteEventServiceImpl) GetVoteEventByID(ctx context.Context, id uuid.UUID) (*contract.VoteEventResponse, error) { voteEvent, err := s.voteEventRepo.GetByID(ctx, id) if err != nil { return nil, err } return transformer.VoteEventToContract(voteEvent), nil } func (s *VoteEventServiceImpl) GetActiveEvents(ctx context.Context) ([]contract.VoteEventResponse, error) { events, err := s.voteEventRepo.GetActiveEvents(ctx) if err != nil { return nil, err } var responses []contract.VoteEventResponse for _, event := range events { responses = append(responses, *transformer.VoteEventToContract(event)) } return responses, nil } func (s *VoteEventServiceImpl) ListVoteEvents(ctx context.Context, req *contract.ListVoteEventsRequest) (*contract.ListVoteEventsResponse, error) { page := req.Page if page <= 0 { page = 1 } limit := req.Limit if limit <= 0 { limit = 10 } offset := (page - 1) * limit events, total, err := s.voteEventRepo.List(ctx, limit, offset) if err != nil { return nil, err } var responses []contract.VoteEventResponse for _, event := range events { responses = append(responses, *transformer.VoteEventToContract(event)) } return &contract.ListVoteEventsResponse{ VoteEvents: responses, Pagination: transformer.CreatePaginationResponse(int(total), page, limit), }, nil } func (s *VoteEventServiceImpl) UpdateVoteEvent(ctx context.Context, id uuid.UUID, req *contract.UpdateVoteEventRequest) (*contract.VoteEventResponse, error) { if req.EndDate.Before(req.StartDate) { return nil, errors.New("end date must be after start date") } voteEvent, err := s.voteEventRepo.GetByID(ctx, id) if err != nil { return nil, err } voteEvent.Title = req.Title voteEvent.Description = req.Description voteEvent.StartDate = req.StartDate voteEvent.EndDate = req.EndDate voteEvent.IsActive = req.IsActive if req.ResultsOpen != nil { voteEvent.ResultsOpen = *req.ResultsOpen } if err := s.voteEventRepo.Update(ctx, voteEvent); err != nil { return nil, err } return transformer.VoteEventToContract(voteEvent), nil } func (s *VoteEventServiceImpl) DeleteVoteEvent(ctx context.Context, id uuid.UUID) error { return s.voteEventRepo.Delete(ctx, id) } func (s *VoteEventServiceImpl) CreateCandidate(ctx context.Context, req *contract.CreateCandidateRequest) (*contract.CandidateResponse, error) { voteEvent, err := s.voteEventRepo.GetByID(ctx, req.VoteEventID) if err != nil { return nil, err } if !voteEvent.IsActive { return nil, errors.New("cannot add candidates to inactive vote event") } candidate := &entities.Candidate{ VoteEventID: req.VoteEventID, Name: req.Name, ImageURL: req.ImageURL, Description: req.Description, } if err := s.voteEventRepo.CreateCandidate(ctx, candidate); err != nil { return nil, err } return transformer.CandidateToContract(candidate), nil } func (s *VoteEventServiceImpl) GetCandidates(ctx context.Context, eventID uuid.UUID) ([]contract.CandidateResponse, error) { candidates, err := s.voteEventRepo.GetCandidatesByEventID(ctx, eventID) if err != nil { return nil, err } var responses []contract.CandidateResponse for _, candidate := range candidates { responses = append(responses, *transformer.CandidateToContract(candidate)) } return responses, nil } func (s *VoteEventServiceImpl) SubmitVote(ctx context.Context, userID uuid.UUID, req *contract.SubmitVoteRequest) (*contract.VoteResponse, error) { voteEvent, err := s.voteEventRepo.GetByID(ctx, req.VoteEventID) if err != nil { return nil, err } if !voteEvent.IsVotingOpen() { return nil, errors.New("voting is not open for this event") } hasVoted, err := s.voteEventRepo.HasUserVoted(ctx, userID, req.VoteEventID) if err != nil { return nil, err } if hasVoted { return nil, errors.New("user has already voted for this event") } vote := &entities.Vote{ VoteEventID: req.VoteEventID, CandidateID: req.CandidateID, UserID: userID, } if err := s.voteEventRepo.SubmitVote(ctx, vote); err != nil { return nil, err } return transformer.VoteToContract(vote), nil } func (s *VoteEventServiceImpl) GetVoteResults(ctx context.Context, eventID uuid.UUID) (*contract.VoteResultsResponse, error) { candidates, err := s.voteEventRepo.GetCandidatesByEventID(ctx, eventID) if err != nil { return nil, err } voteResults, err := s.voteEventRepo.GetVoteResults(ctx, eventID) if err != nil { return nil, err } var candidatesWithVotes []contract.CandidateWithVotesResponse var totalVotes int64 for _, candidate := range candidates { voteCount := voteResults[candidate.ID] totalVotes += voteCount candidatesWithVotes = append(candidatesWithVotes, contract.CandidateWithVotesResponse{ CandidateResponse: *transformer.CandidateToContract(candidate), VoteCount: voteCount, }) } return &contract.VoteResultsResponse{ VoteEventID: eventID, Candidates: candidatesWithVotes, TotalVotes: totalVotes, }, nil } func (s *VoteEventServiceImpl) CheckVoteStatus(ctx context.Context, userID, eventID uuid.UUID) (*contract.CheckVoteStatusResponse, error) { hasVoted, err := s.voteEventRepo.HasUserVoted(ctx, userID, eventID) if err != nil { return nil, err } response := &contract.CheckVoteStatusResponse{ HasVoted: hasVoted, } return response, nil }