resolve conflict
This commit is contained in:
commit
c8edbd07a5
|
|
@ -515,7 +515,13 @@ CREATE TABLE IF NOT EXISTS raffle_tickets (
|
|||
raffle_id INT NOT NULL REFERENCES raffles(id) ON DELETE CASCADE,
|
||||
user_id INT NOT NULL,
|
||||
is_active BOOL DEFAULT true,
|
||||
UNIQUE (raffle_id, user_id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS raffle_winners (
|
||||
id SERIAL PRIMARY KEY,
|
||||
raffle_id INT NOT NULL REFERENCES raffles(id) ON DELETE CASCADE,
|
||||
user_id INT NOT NULL,
|
||||
rank INT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
------ Views
|
||||
CREATE VIEW companies_details AS
|
||||
|
|
|
|||
|
|
@ -32,3 +32,30 @@ SELECT
|
|||
FROM raffle_tickets rt
|
||||
JOIN raffles r ON rt.raffle_id = r.id
|
||||
WHERE rt.user_id = $1;
|
||||
|
||||
-- name: GetRaffleStanding :many
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
rt.raffle_id,
|
||||
u.first_name,
|
||||
u.last_name,
|
||||
u.phone_number,
|
||||
u.email,
|
||||
COUNT(*) AS ticket_count
|
||||
FROM raffle_tickets rt
|
||||
JOIN users u ON rt.user_id = u.id
|
||||
WHERE rt.is_active = true
|
||||
AND rt.raffle_id = $1
|
||||
GROUP BY u.id, rt.raffle_id, u.first_name, u.last_name, u.phone_number, u.email
|
||||
ORDER BY ticket_count DESC
|
||||
LIMIT $2;
|
||||
|
||||
-- name: CreateRaffleWinner :one
|
||||
INSERT INTO raffle_winners (raffle_id, user_id, rank)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING *;
|
||||
|
||||
-- name: SetRaffleComplete :exec
|
||||
UPDATE raffles
|
||||
SET status = 'completed'
|
||||
WHERE id = $1;
|
||||
|
|
|
|||
|
|
@ -514,6 +514,14 @@ type ReferralCode struct {
|
|||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type RaffleWinner struct {
|
||||
ID int32 `json:"id"`
|
||||
RaffleID int32 `json:"raffle_id"`
|
||||
UserID int32 `json:"user_id"`
|
||||
Rank int32 `json:"rank"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
}
|
||||
|
||||
type RefreshToken struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
|
|
|
|||
|
|
@ -67,6 +67,31 @@ func (q *Queries) CreateRaffleTicket(ctx context.Context, arg CreateRaffleTicket
|
|||
return i, err
|
||||
}
|
||||
|
||||
const CreateRaffleWinner = `-- name: CreateRaffleWinner :one
|
||||
INSERT INTO raffle_winners (raffle_id, user_id, rank)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, raffle_id, user_id, rank, created_at
|
||||
`
|
||||
|
||||
type CreateRaffleWinnerParams struct {
|
||||
RaffleID int32 `json:"raffle_id"`
|
||||
UserID int32 `json:"user_id"`
|
||||
Rank int32 `json:"rank"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateRaffleWinner(ctx context.Context, arg CreateRaffleWinnerParams) (RaffleWinner, error) {
|
||||
row := q.db.QueryRow(ctx, CreateRaffleWinner, arg.RaffleID, arg.UserID, arg.Rank)
|
||||
var i RaffleWinner
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.RaffleID,
|
||||
&i.UserID,
|
||||
&i.Rank,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const DeleteRaffle = `-- name: DeleteRaffle :one
|
||||
DELETE FROM raffles
|
||||
WHERE id = $1
|
||||
|
|
@ -88,6 +113,67 @@ func (q *Queries) DeleteRaffle(ctx context.Context, id int32) (Raffle, error) {
|
|||
return i, err
|
||||
}
|
||||
|
||||
const GetRaffleStanding = `-- name: GetRaffleStanding :many
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
rt.raffle_id,
|
||||
u.first_name,
|
||||
u.last_name,
|
||||
u.phone_number,
|
||||
u.email,
|
||||
COUNT(*) AS ticket_count
|
||||
FROM raffle_tickets rt
|
||||
JOIN users u ON rt.user_id = u.id
|
||||
WHERE rt.is_active = true
|
||||
AND rt.raffle_id = $1
|
||||
GROUP BY u.id, rt.raffle_id, u.first_name, u.last_name, u.phone_number, u.email
|
||||
ORDER BY ticket_count DESC
|
||||
LIMIT $2
|
||||
`
|
||||
|
||||
type GetRaffleStandingParams struct {
|
||||
RaffleID int32 `json:"raffle_id"`
|
||||
Limit int32 `json:"limit"`
|
||||
}
|
||||
|
||||
type GetRaffleStandingRow struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
RaffleID int32 `json:"raffle_id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||
Email pgtype.Text `json:"email"`
|
||||
TicketCount int64 `json:"ticket_count"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetRaffleStanding(ctx context.Context, arg GetRaffleStandingParams) ([]GetRaffleStandingRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetRaffleStanding, arg.RaffleID, arg.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetRaffleStandingRow
|
||||
for rows.Next() {
|
||||
var i GetRaffleStandingRow
|
||||
if err := rows.Scan(
|
||||
&i.UserID,
|
||||
&i.RaffleID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.PhoneNumber,
|
||||
&i.Email,
|
||||
&i.TicketCount,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetRafflesOfCompany = `-- name: GetRafflesOfCompany :many
|
||||
SELECT id, company_id, name, created_at, expires_at, type, status FROM raffles WHERE company_id = $1
|
||||
`
|
||||
|
|
@ -169,6 +255,17 @@ func (q *Queries) GetUserRaffleTickets(ctx context.Context, userID int32) ([]Get
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const SetRaffleComplete = `-- name: SetRaffleComplete :exec
|
||||
UPDATE raffles
|
||||
SET status = 'completed'
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) SetRaffleComplete(ctx context.Context, id int32) error {
|
||||
_, err := q.db.Exec(ctx, SetRaffleComplete, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateRaffleTicketStatus = `-- name: UpdateRaffleTicketStatus :exec
|
||||
UPDATE raffle_tickets
|
||||
SET is_active = $1
|
||||
|
|
|
|||
|
|
@ -12,6 +12,22 @@ type Raffle struct {
|
|||
Status string
|
||||
}
|
||||
|
||||
type RaffleStanding struct {
|
||||
UserID int64
|
||||
RaffleID int32
|
||||
FirstName string
|
||||
LastName string
|
||||
PhoneNumber string
|
||||
Email string
|
||||
TicketCount int64
|
||||
}
|
||||
|
||||
type RaffleWinnerParams struct {
|
||||
RaffleID int32
|
||||
UserID int32
|
||||
Rank int32
|
||||
}
|
||||
|
||||
type RaffleTicket struct {
|
||||
ID int32
|
||||
RaffleID int32
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package helpers
|
|||
import (
|
||||
random "crypto/rand"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"math/big"
|
||||
"math/rand/v2"
|
||||
|
|
@ -42,3 +44,17 @@ func GenerateCashoutID() (string, error) {
|
|||
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func MaskPhone(phone string) string {
|
||||
if phone == "" {
|
||||
return ""
|
||||
}
|
||||
return phone[:4] + "**" + phone[len(phone)-2:]
|
||||
}
|
||||
|
||||
func MaskEmail(email string) string {
|
||||
if email == "" {
|
||||
return ""
|
||||
}
|
||||
return email[:3] + "**" + email[strings.Index(email, "@"):]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,18 @@ func convertCreateRaffle(raffle domain.CreateRaffle) dbgen.CreateRaffleParams {
|
|||
}
|
||||
}
|
||||
|
||||
func convertRaffleStanding(raffleStanding dbgen.GetRaffleStandingRow) domain.RaffleStanding {
|
||||
return domain.RaffleStanding{
|
||||
UserID: raffleStanding.UserID,
|
||||
RaffleID: raffleStanding.RaffleID,
|
||||
FirstName: raffleStanding.FirstName,
|
||||
LastName: raffleStanding.LastName,
|
||||
PhoneNumber: raffleStanding.PhoneNumber.String,
|
||||
Email: raffleStanding.Email.String,
|
||||
TicketCount: raffleStanding.TicketCount,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) CreateRaffle(ctx context.Context, raffle domain.CreateRaffle) (domain.Raffle, error) {
|
||||
raffleRes, err := s.queries.CreateRaffle(ctx, convertCreateRaffle(raffle))
|
||||
if err != nil {
|
||||
|
|
@ -126,3 +138,34 @@ func (s *Store) UnSuspendRaffleTicket(ctx context.Context, raffleID int32) error
|
|||
}
|
||||
|
||||
// TODO: could also add -> suspend a specific user's raffle tickets
|
||||
|
||||
func (s *Store) GetRaffleStanding(ctx context.Context, raffleID, limit int32) ([]domain.RaffleStanding, error) {
|
||||
raffleStanding, err := s.queries.GetRaffleStanding(ctx, dbgen.GetRaffleStandingParams{
|
||||
RaffleID: raffleID,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := []domain.RaffleStanding{}
|
||||
for _, standing := range raffleStanding {
|
||||
res = append(res, convertRaffleStanding(standing))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateRaffleWinner(ctx context.Context, raffleWinnerParams domain.RaffleWinnerParams) error {
|
||||
_, err := s.queries.CreateRaffleWinner(ctx, dbgen.CreateRaffleWinnerParams{
|
||||
RaffleID: raffleWinnerParams.RaffleID,
|
||||
UserID: raffleWinnerParams.UserID,
|
||||
Rank: raffleWinnerParams.Rank,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) SetRaffleComplete(ctx context.Context, raffleID int32) error {
|
||||
return s.queries.SetRaffleComplete(ctx, raffleID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ type RaffleStore interface {
|
|||
CreateRaffle(ctx context.Context, raffle domain.CreateRaffle) (domain.Raffle, error)
|
||||
DeleteRaffle(ctx context.Context, raffleID int32) (domain.Raffle, error)
|
||||
GetRafflesOfCompany(ctx context.Context, companyID int32) ([]dbgen.Raffle, error)
|
||||
GetRaffleStanding(ctx context.Context, raffleID, limit int32) ([]domain.RaffleStanding, error)
|
||||
CreateRaffleWinner(ctx context.Context, raffleWinnerParams domain.RaffleWinnerParams) error
|
||||
SetRaffleComplete(ctx context.Context, raffleID int32) error
|
||||
CreateRaffleTicket(ctx context.Context, raffleTicketParams domain.CreateRaffleTicket) (domain.RaffleTicket, error)
|
||||
GetUserRaffleTickets(ctx context.Context, userID int32) ([]domain.RaffleTicketRes, error)
|
||||
SuspendRaffleTicket(ctx context.Context, raffleTicketID int32) error
|
||||
|
|
|
|||
|
|
@ -29,6 +29,18 @@ func (s *Service) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]d
|
|||
return s.raffleStore.GetRafflesOfCompany(ctx, companyID)
|
||||
}
|
||||
|
||||
func (s *Service) GetRaffleStanding(ctx context.Context, raffleID, limit int32) ([]domain.RaffleStanding, error) {
|
||||
return s.raffleStore.GetRaffleStanding(ctx, raffleID, limit)
|
||||
}
|
||||
|
||||
func (s *Service) CreateRaffleWinner(ctx context.Context, raffleWinnerParams domain.RaffleWinnerParams) error {
|
||||
return s.raffleStore.CreateRaffleWinner(ctx, raffleWinnerParams)
|
||||
}
|
||||
|
||||
func (s *Service) SetRaffleComplete(ctx context.Context, raffleID int32) error {
|
||||
return s.raffleStore.SetRaffleComplete(ctx, raffleID)
|
||||
}
|
||||
|
||||
func (s *Service) CreateRaffleTicket(ctx context.Context, raffleTicketParams domain.CreateRaffleTicket) (domain.RaffleTicket, error) {
|
||||
return s.raffleStore.CreateRaffleTicket(ctx, raffleTicketParams)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
|
|
@ -101,9 +103,102 @@ func (h *Handler) GetRafflesOfCompany(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusOK, "Company Raffles fetched successfully", companyRaffles, nil)
|
||||
}
|
||||
|
||||
func (h *Handler) GetRaffleStanding(c *fiber.Ctx) error {
|
||||
raffleIDStr := c.Params("id")
|
||||
limitStr := c.Params("limit")
|
||||
|
||||
// if error happens while parsing, it just uses zero values
|
||||
// resulting in empty standing
|
||||
raffleID, _ := strconv.Atoi(raffleIDStr)
|
||||
limit, _ := strconv.Atoi(limitStr)
|
||||
|
||||
raffleStanding, err := h.raffleSvc.GetRaffleStanding(c.Context(), int32(raffleID), int32(limit))
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to fetch raffle standing",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch raffle standing")
|
||||
}
|
||||
|
||||
maskedRaffleStanding := []domain.RaffleStanding{}
|
||||
for _, standing := range raffleStanding {
|
||||
maskedStanding := domain.RaffleStanding{
|
||||
UserID: standing.UserID,
|
||||
RaffleID: standing.RaffleID,
|
||||
FirstName: standing.FirstName,
|
||||
LastName: standing.LastName,
|
||||
PhoneNumber: helpers.MaskPhone(standing.PhoneNumber),
|
||||
Email: helpers.MaskEmail(standing.Email),
|
||||
TicketCount: standing.TicketCount,
|
||||
}
|
||||
|
||||
maskedRaffleStanding = append(maskedRaffleStanding, maskedStanding)
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Raffles standing fetched successfully", maskedRaffleStanding, nil)
|
||||
}
|
||||
|
||||
func (h *Handler) GetRaffleWinners(c *fiber.Ctx) error {
|
||||
raffleIDStr := c.Params("id")
|
||||
limitStr := c.Params("limit")
|
||||
|
||||
// if error happens while parsing, it just uses zero values
|
||||
// resulting in empty standing
|
||||
raffleID, _ := strconv.Atoi(raffleIDStr)
|
||||
limit, _ := strconv.Atoi(limitStr)
|
||||
|
||||
raffleStanding, err := h.raffleSvc.GetRaffleStanding(c.Context(), int32(raffleID), int32(limit))
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to fetch raffle standing",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch raffle standing")
|
||||
}
|
||||
|
||||
// set raffle as complete
|
||||
if err := h.raffleSvc.SetRaffleComplete(c.Context(), int32(raffleID)); err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to set raffle complete",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to set raffle complete")
|
||||
}
|
||||
|
||||
// add winners to table
|
||||
var errs []error
|
||||
for i, standing := range raffleStanding {
|
||||
err = h.raffleSvc.CreateRaffleWinner(c.Context(), domain.RaffleWinnerParams{
|
||||
RaffleID: standing.RaffleID,
|
||||
UserID: int32(standing.UserID),
|
||||
Rank: int32(i + 1),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
h.mongoLoggerSvc.Error("Failed to create raffle winners",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(errors.Join(errs...)),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create raffle winners")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) CreateRaffleTicket(c *fiber.Ctx) error {
|
||||
var req domain.CreateRaffleTicket
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
fmt.Println("parser error: ", err)
|
||||
h.mongoLoggerSvc.Info("Failed to parse raffle ticket request",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
|
|
@ -127,6 +222,7 @@ func (h *Handler) CreateRaffleTicket(c *fiber.Ctx) error {
|
|||
|
||||
raffleTicket, err := h.raffleSvc.CreateRaffleTicket(c.Context(), req)
|
||||
if err != nil {
|
||||
fmt.Println("raffle ticket create error: ", err)
|
||||
h.mongoLoggerSvc.Error("Failed to create raffle ticket",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
|
|
|
|||
|
|
@ -201,6 +201,8 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Post("/raffle/create", a.authMiddleware, h.CreateRaffle)
|
||||
a.fiber.Get("/raffle/delete/:id", a.authMiddleware, h.DeleteRaffle)
|
||||
a.fiber.Get("/raffle/company/:id", a.authMiddleware, h.GetRafflesOfCompany)
|
||||
a.fiber.Get("/raffle/standing/:id/:limit", a.authMiddleware, h.GetRaffleStanding)
|
||||
a.fiber.Get("raffle/winners/:id/:limit", a.authMiddleware, h.GetRaffleWinners)
|
||||
a.fiber.Post("/raffle-ticket/create", a.authMiddleware, h.CreateRaffleTicket)
|
||||
a.fiber.Get("/raffle-ticket/:id", a.authMiddleware, h.GetUserRaffleTickets)
|
||||
a.fiber.Get("/raffle-ticket/suspend/:id", a.authMiddleware, h.SuspendRaffleTicket)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user