meti-backend/internal/service/vote_event_service.go

259 lines
7.1 KiB
Go
Raw Normal View History

2025-08-15 21:17:19 +07:00
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
}