raffle ticket limit

This commit is contained in:
Asher Samuel 2025-10-06 14:47:01 +03:00
parent c00110a503
commit 62258b7ecb
45 changed files with 215 additions and 83 deletions

View File

@ -509,6 +509,8 @@ CREATE TABLE IF NOT EXISTS raffles (
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL,
-- -1 means there is no limit for the raffle
ticket_limit INT NOT NULL DEFAULT -1,
type VARCHAR(50) NOT NULL CHECK (type IN ('virtual', 'sport')),
status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'completed'))
);
@ -762,4 +764,4 @@ ADD CONSTRAINT fk_event_settings_company FOREIGN KEY (company_id) REFERENCES com
ADD CONSTRAINT fk_event_settings_event FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE;
ALTER TABLE company_odd_settings
ADD CONSTRAINT fk_odds_settings_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE,
ADD CONSTRAINT fk_odds_settings_odds_market FOREIGN KEY (odds_market_id) REFERENCES odds_market (id) ON DELETE CASCADE;
ADD CONSTRAINT fk_odds_settings_odds_market FOREIGN KEY (odds_market_id) REFERENCES odds_market (id) ON DELETE CASCADE;

View File

@ -1,6 +1,6 @@
-- name: CreateRaffle :one
INSERT INTO raffles (company_id, name, expires_at, type)
VALUES ($1, $2, $3, $4)
INSERT INTO raffles (company_id, name, expires_at, ticket_limit, type)
VALUES ($1, $2, $3, $4, $5)
RETURNING *;
-- name: GetRafflesOfCompany :many
@ -71,3 +71,19 @@ FROM raffle_sport_filters
WHERE raffle_id = $1
AND sport_id = $2
AND league_id = $3;
-- name: CheckSportRaffleHasFilter :one
SELECT EXISTS (
SELECT 1 FROM raffle_sport_filters WHERE raffle_id = $1
) AS has_filter;
-- name: GetRaffleTicketLimit :one
SELECT ticket_limit
FROM raffles
WHERE id = $1;
-- name: GetRaffleTicketCount :one
SELECT COUNT(*)
FROM raffle_tickets
WHERE raffle_id = $1
AND user_id = $2;

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: auth.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: bet.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: bet_stat.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: bonus.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: branch.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: cashier.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: company.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: copyfrom.go
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: direct_deposit.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: disabled_odds.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: enet_pulse.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: event_history.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: events.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: events_stat.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: flags.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: institutions.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: issue_reporting.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: leagues.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: location.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
package dbgen
@ -554,13 +554,14 @@ type Otp struct {
}
type Raffle struct {
ID int32 `json:"id"`
CompanyID int32 `json:"company_id"`
Name string `json:"name"`
CreatedAt pgtype.Timestamp `json:"created_at"`
ExpiresAt pgtype.Timestamp `json:"expires_at"`
Type string `json:"type"`
Status string `json:"status"`
ID int32 `json:"id"`
CompanyID int32 `json:"company_id"`
Name string `json:"name"`
CreatedAt pgtype.Timestamp `json:"created_at"`
ExpiresAt pgtype.Timestamp `json:"expires_at"`
TicketLimit int32 `json:"ticket_limit"`
Type string `json:"type"`
Status string `json:"status"`
}
type RaffleGameFilter struct {

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: monitor.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: notification.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: odd_history.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: odds.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: otp.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: raffle.sql
package dbgen
@ -35,6 +35,19 @@ func (q *Queries) AddSportRaffleFilter(ctx context.Context, arg AddSportRaffleFi
return i, err
}
const CheckSportRaffleHasFilter = `-- name: CheckSportRaffleHasFilter :one
SELECT EXISTS (
SELECT 1 FROM raffle_sport_filters WHERE raffle_id = $1
) AS has_filter
`
func (q *Queries) CheckSportRaffleHasFilter(ctx context.Context, raffleID int32) (bool, error) {
row := q.db.QueryRow(ctx, CheckSportRaffleHasFilter, raffleID)
var has_filter bool
err := row.Scan(&has_filter)
return has_filter, err
}
const CheckValidSportRaffleFilter = `-- name: CheckValidSportRaffleFilter :one
SELECT COUNT(*) > 0 AS exists
FROM raffle_sport_filters
@ -57,16 +70,17 @@ func (q *Queries) CheckValidSportRaffleFilter(ctx context.Context, arg CheckVali
}
const CreateRaffle = `-- name: CreateRaffle :one
INSERT INTO raffles (company_id, name, expires_at, type)
VALUES ($1, $2, $3, $4)
RETURNING id, company_id, name, created_at, expires_at, type, status
INSERT INTO raffles (company_id, name, expires_at, ticket_limit, type)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, company_id, name, created_at, expires_at, ticket_limit, type, status
`
type CreateRaffleParams struct {
CompanyID int32 `json:"company_id"`
Name string `json:"name"`
ExpiresAt pgtype.Timestamp `json:"expires_at"`
Type string `json:"type"`
CompanyID int32 `json:"company_id"`
Name string `json:"name"`
ExpiresAt pgtype.Timestamp `json:"expires_at"`
TicketLimit int32 `json:"ticket_limit"`
Type string `json:"type"`
}
func (q *Queries) CreateRaffle(ctx context.Context, arg CreateRaffleParams) (Raffle, error) {
@ -74,6 +88,7 @@ func (q *Queries) CreateRaffle(ctx context.Context, arg CreateRaffleParams) (Raf
arg.CompanyID,
arg.Name,
arg.ExpiresAt,
arg.TicketLimit,
arg.Type,
)
var i Raffle
@ -83,6 +98,7 @@ func (q *Queries) CreateRaffle(ctx context.Context, arg CreateRaffleParams) (Raf
&i.Name,
&i.CreatedAt,
&i.ExpiresAt,
&i.TicketLimit,
&i.Type,
&i.Status,
)
@ -140,7 +156,7 @@ func (q *Queries) CreateRaffleWinner(ctx context.Context, arg CreateRaffleWinner
const DeleteRaffle = `-- name: DeleteRaffle :one
DELETE FROM raffles
WHERE id = $1
RETURNING id, company_id, name, created_at, expires_at, type, status
RETURNING id, company_id, name, created_at, expires_at, ticket_limit, type, status
`
func (q *Queries) DeleteRaffle(ctx context.Context, id int32) (Raffle, error) {
@ -152,6 +168,7 @@ func (q *Queries) DeleteRaffle(ctx context.Context, id int32) (Raffle, error) {
&i.Name,
&i.CreatedAt,
&i.ExpiresAt,
&i.TicketLimit,
&i.Type,
&i.Status,
)
@ -219,8 +236,40 @@ func (q *Queries) GetRaffleStanding(ctx context.Context, arg GetRaffleStandingPa
return items, nil
}
const GetRaffleTicketCount = `-- name: GetRaffleTicketCount :one
SELECT COUNT(*)
FROM raffle_tickets
WHERE raffle_id = $1
AND user_id = $2
`
type GetRaffleTicketCountParams struct {
RaffleID int32 `json:"raffle_id"`
UserID int32 `json:"user_id"`
}
func (q *Queries) GetRaffleTicketCount(ctx context.Context, arg GetRaffleTicketCountParams) (int64, error) {
row := q.db.QueryRow(ctx, GetRaffleTicketCount, arg.RaffleID, arg.UserID)
var count int64
err := row.Scan(&count)
return count, err
}
const GetRaffleTicketLimit = `-- name: GetRaffleTicketLimit :one
SELECT ticket_limit
FROM raffles
WHERE id = $1
`
func (q *Queries) GetRaffleTicketLimit(ctx context.Context, id int32) (int32, error) {
row := q.db.QueryRow(ctx, GetRaffleTicketLimit, id)
var ticket_limit int32
err := row.Scan(&ticket_limit)
return ticket_limit, err
}
const GetRafflesOfCompany = `-- name: GetRafflesOfCompany :many
SELECT id, company_id, name, created_at, expires_at, type, status FROM raffles WHERE company_id = $1
SELECT id, company_id, name, created_at, expires_at, ticket_limit, type, status FROM raffles WHERE company_id = $1
`
func (q *Queries) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]Raffle, error) {
@ -238,6 +287,7 @@ func (q *Queries) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]R
&i.Name,
&i.CreatedAt,
&i.ExpiresAt,
&i.TicketLimit,
&i.Type,
&i.Status,
); err != nil {

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: referal.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: report.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: result.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: result_log.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: settings.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: shop_transactions.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: ticket.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: transfer.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: user.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: virtual_games.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.30.0
// source: wallet.sql
package dbgen

View File

@ -3,13 +3,14 @@ package domain
import "time"
type Raffle struct {
ID int32
CompanyID int32
Name string
CreatedAt time.Time
ExpiresAt time.Time
Type string
Status string
ID int32
CompanyID int32
Name string
CreatedAt time.Time
ExpiresAt time.Time
TicketLimit int32
Type string
Status string
}
type RaffleFilter struct {
@ -64,10 +65,11 @@ type RaffleTicketRes struct {
}
type CreateRaffle struct {
CompanyID int32 `json:"company_id" validate:"required"`
Name string `json:"name" validate:"required"`
ExpiresAt *time.Time `json:"expires_at" validate:"required"`
Type string `json:"type" validate:"required"`
CompanyID int32 `json:"company_id" validate:"required"`
Name string `json:"name" validate:"required"`
ExpiresAt *time.Time `json:"expires_at" validate:"required"`
TicketLimit int32 `json:"ticket_limit" validate:"required"`
Type string `json:"type" validate:"required"`
}
type CreateRaffleTicket struct {

View File

@ -10,13 +10,14 @@ import (
func convertRaffleOutcome(raffle dbgen.Raffle) domain.Raffle {
return domain.Raffle{
ID: raffle.ID,
CompanyID: raffle.CompanyID,
Name: raffle.Name,
CreatedAt: raffle.CreatedAt.Time,
ExpiresAt: raffle.ExpiresAt.Time,
Type: raffle.Type,
Status: raffle.Status,
ID: raffle.ID,
CompanyID: raffle.CompanyID,
Name: raffle.Name,
CreatedAt: raffle.CreatedAt.Time,
ExpiresAt: raffle.ExpiresAt.Time,
TicketLimit: raffle.TicketLimit,
Type: raffle.Type,
Status: raffle.Status,
}
}
@ -48,7 +49,8 @@ func convertCreateRaffle(raffle domain.CreateRaffle) dbgen.CreateRaffleParams {
Time: *raffle.ExpiresAt,
Valid: true,
},
Type: raffle.Type,
TicketLimit: raffle.TicketLimit,
Type: raffle.Type,
}
}
@ -191,3 +193,18 @@ func (s *Store) CheckValidSportRaffleFilter(ctx context.Context, raffleID int32,
return res, nil
}
func (s *Store) GetRaffleTicketLimit(ctx context.Context, raffleID int32) (int32, error) {
return s.queries.GetRaffleTicketLimit(ctx, raffleID)
}
func (s *Store) GetRaffleTicketCount(ctx context.Context, raffleID, userID int32) (int64, error) {
return s.queries.GetRaffleTicketCount(ctx, dbgen.GetRaffleTicketCountParams{
RaffleID: raffleID,
UserID: userID,
})
}
func (s *Store) CheckSportRaffleHasFilter(ctx context.Context, raffleID int32) (bool, error) {
return s.queries.CheckSportRaffleHasFilter(ctx, raffleID)
}

View File

@ -16,9 +16,12 @@ type RaffleStore interface {
CreateRaffleWinner(ctx context.Context, raffleWinnerParams domain.RaffleWinnerParams) error
SetRaffleComplete(ctx context.Context, raffleID int32) error
CheckValidSportRaffleFilter(ctx context.Context, raffleID int32, sportID, leagueID int64) (bool, error)
CheckSportRaffleHasFilter(ctx context.Context, raffleID int32) (bool, 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
UnSuspendRaffleTicket(ctx context.Context, raffleID int32) error
GetRaffleTicketCount(ctx context.Context, raffleID, userID int32) (int64, error)
GetRaffleTicketLimit(ctx context.Context, raffleID int32) (int32, error)
}

View File

@ -64,3 +64,15 @@ func (s *Service) UnSuspendRaffleTicket(ctx context.Context, raffleID int32) err
func (s *Service) CheckValidSportRaffleFilter(ctx context.Context, raffleID int32, sportID, leagueID int64) (bool, error) {
return s.raffleStore.CheckValidSportRaffleFilter(ctx, raffleID, sportID, leagueID)
}
func (s *Service) CheckSportRaffleHasFilter(ctx context.Context, raffleID int32) (bool, error) {
return s.raffleStore.CheckSportRaffleHasFilter(ctx, raffleID)
}
func (s *Service) GetRaffleTicketCount(ctx context.Context, raffleID, userID int32) (int64, error) {
return s.raffleStore.GetRaffleTicketCount(ctx, raffleID, userID)
}
func (s *Service) GetRaffleTicketLimit(ctx context.Context, raffleID int32) (int32, error) {
return s.raffleStore.GetRaffleTicketLimit(ctx, raffleID)
}

View File

@ -259,24 +259,53 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
sportAndLeagueIDs = append(sportAndLeagueIDs, ids)
}
fmt.Println("sportAndLeagueIDs: ", sportAndLeagueIDs)
for _, raffle := range raffles {
// TODO: only fetch pending raffles from db
if raffle.Status == "completed" {
continue
}
// only require one sport and league combo to be valide to make the raffle ticket
foundValid := false
raffleTicketLimit, err := h.raffleSvc.GetRaffleTicketLimit(c.Context(), raffle.ID)
if err != nil {
continue
}
// check raffle ticke count
userTicketCount, err := h.raffleSvc.GetRaffleTicketCount(c.Context(), raffle.ID, int32(userID))
if err != nil {
continue
}
if userTicketCount == int64(raffleTicketLimit) {
h.mongoLoggerSvc.Info("User reached max ticket count allowed for current raffle",
zap.Int("status_code", fiber.StatusForbidden),
zap.Int64("raffleID", int64(raffle.ID)),
zap.Int64("userID", userID),
zap.Int64("companyID", companyID),
zap.Time("timestamp", time.Now()),
)
continue
}
// empty raffle filter means there is no filter (all is allowed)
hasFilter, err := h.raffleSvc.CheckSportRaffleHasFilter(c.Context(), raffle.ID)
if err != nil {
continue
}
foundValid := !hasFilter
// only require one sport and league combo to be valid to make the raffle ticket
for _, sportAndLeagueID := range sportAndLeagueIDs {
if foundValid {
break
}
res, err := h.raffleSvc.CheckValidSportRaffleFilter(c.Context(), raffle.ID, sportAndLeagueID[0], sportAndLeagueID[1])
if err != nil {
continue
}
fmt.Println(sportAndLeagueID, res)
foundValid = foundValid || res
}
@ -289,7 +318,7 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
UserID: int32(userID),
}
_, err := h.raffleSvc.CreateRaffleTicket(c.Context(), raffleTicket)
_, err = h.raffleSvc.CreateRaffleTicket(c.Context(), raffleTicket)
if err != nil {
h.mongoLoggerSvc.Error("Failed to create raffle ticket",
zap.Int("status_code", fiber.StatusInternalServerError),