diff --git a/cmd/main.go b/cmd/main.go index ef59a25..66bbb0c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -45,6 +45,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/messenger" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" @@ -147,6 +148,7 @@ func main() { recommendationRepo := repository.NewRecommendationRepository(store) referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger) + raffleSvc := raffle.NewService(store) virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger) aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger) veliCLient := veli.NewClient(cfg, walletSvc) @@ -271,6 +273,7 @@ func main() { eventSvc, leagueSvc, referalSvc, + raffleSvc, bonusSvc, virtualGameSvc, aleaService, diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 898d587..28a61da 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -518,6 +518,22 @@ CREATE TABLE IF NOT EXISTS supported_operations ( name VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL ); +CREATE TABLE IF NOT EXISTS raffles ( + id SERIAL PRIMARY KEY, + company_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + expires_at TIMESTAMP NOT NULL, + type VARCHAR(50) NOT NULL CHECK (type IN ('virtual', 'sport')), + status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'completed')) +); +CREATE TABLE IF NOT EXISTS raffle_tickets ( + id SERIAL PRIMARY KEY, + 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 VIEW bet_with_outcomes AS SELECT bets.*, CONCAT (users.first_name, ' ', users.last_name) AS full_name, @@ -706,4 +722,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; \ No newline at end of file + ADD CONSTRAINT fk_odds_settings_odds_market FOREIGN KEY (odds_market_id) REFERENCES odds_market (id) ON DELETE CASCADE; diff --git a/db/query/raffle.sql b/db/query/raffle.sql new file mode 100644 index 0000000..4c43a10 --- /dev/null +++ b/db/query/raffle.sql @@ -0,0 +1,34 @@ +-- name: CreateRaffle :one +INSERT INTO raffles (company_id, name, expires_at, type) +VALUES ($1, $2, $3, $4) +RETURNING *; + +-- name: GetRafflesOfCompany :many +SELECT * FROM raffles WHERE company_id = $1; + +-- name: DeleteRaffle :one +DELETE FROM raffles +WHERE id = $1 +RETURNING *; + +-- name: UpdateRaffleTicketStatus :exec +UPDATE raffle_tickets +SET is_active = $1 +WHERE id = $2; + +-- name: CreateRaffleTicket :one +INSERT INTO raffle_tickets (raffle_id, user_id) +VALUES ($1, $2) +RETURNING *; + +-- name: GetUserRaffleTickets :many +SELECT + rt.id AS ticket_id, + rt.user_id, + r.name, + r.type, + r.expires_at, + r.status +FROM raffle_tickets rt +JOIN raffles r ON rt.raffle_id = r.id +WHERE rt.user_id = $1; diff --git a/gen/db/auth.sql.go b/gen/db/auth.sql.go index 1817514..c5984b9 100644 --- a/gen/db/auth.sql.go +++ b/gen/db/auth.sql.go @@ -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 diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index ff64087..573c4c2 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -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 diff --git a/gen/db/bet_stat.sql.go b/gen/db/bet_stat.sql.go index 275ef07..9a7b494 100644 --- a/gen/db/bet_stat.sql.go +++ b/gen/db/bet_stat.sql.go @@ -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 diff --git a/gen/db/bonus.sql.go b/gen/db/bonus.sql.go index 12677b8..f62227b 100644 --- a/gen/db/bonus.sql.go +++ b/gen/db/bonus.sql.go @@ -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 diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index a9a57b8..89d2959 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -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 diff --git a/gen/db/cashier.sql.go b/gen/db/cashier.sql.go index c15f497..e262575 100644 --- a/gen/db/cashier.sql.go +++ b/gen/db/cashier.sql.go @@ -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 diff --git a/gen/db/company.sql.go b/gen/db/company.sql.go index 506eaca..18bc509 100644 --- a/gen/db/company.sql.go +++ b/gen/db/company.sql.go @@ -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 diff --git a/gen/db/copyfrom.go b/gen/db/copyfrom.go index 1212253..f7a4793 100644 --- a/gen/db/copyfrom.go +++ b/gen/db/copyfrom.go @@ -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 diff --git a/gen/db/db.go b/gen/db/db.go index 84de07c..8134784 100644 --- a/gen/db/db.go +++ b/gen/db/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 package dbgen diff --git a/gen/db/direct_deposit.sql.go b/gen/db/direct_deposit.sql.go index be02750..ff5a3b2 100644 --- a/gen/db/direct_deposit.sql.go +++ b/gen/db/direct_deposit.sql.go @@ -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 diff --git a/gen/db/disabled_odds.sql.go b/gen/db/disabled_odds.sql.go index 85dcd2e..917acce 100644 --- a/gen/db/disabled_odds.sql.go +++ b/gen/db/disabled_odds.sql.go @@ -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 diff --git a/gen/db/event_history.sql.go b/gen/db/event_history.sql.go index ab29359..64762c3 100644 --- a/gen/db/event_history.sql.go +++ b/gen/db/event_history.sql.go @@ -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 diff --git a/gen/db/events.sql.go b/gen/db/events.sql.go index 3ee77ad..1c7a0c3 100644 --- a/gen/db/events.sql.go +++ b/gen/db/events.sql.go @@ -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 diff --git a/gen/db/events_stat.sql.go b/gen/db/events_stat.sql.go index 677fa2a..615e2fa 100644 --- a/gen/db/events_stat.sql.go +++ b/gen/db/events_stat.sql.go @@ -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 diff --git a/gen/db/flags.sql.go b/gen/db/flags.sql.go index 653543f..4b82cac 100644 --- a/gen/db/flags.sql.go +++ b/gen/db/flags.sql.go @@ -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 diff --git a/gen/db/institutions.sql.go b/gen/db/institutions.sql.go index 324ac3e..61ca108 100644 --- a/gen/db/institutions.sql.go +++ b/gen/db/institutions.sql.go @@ -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 diff --git a/gen/db/issue_reporting.sql.go b/gen/db/issue_reporting.sql.go index 7fcb4af..e35fba1 100644 --- a/gen/db/issue_reporting.sql.go +++ b/gen/db/issue_reporting.sql.go @@ -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 diff --git a/gen/db/leagues.sql.go b/gen/db/leagues.sql.go index 57d3c28..1fed73e 100644 --- a/gen/db/leagues.sql.go +++ b/gen/db/leagues.sql.go @@ -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 diff --git a/gen/db/location.sql.go b/gen/db/location.sql.go index 008aa61..254c73a 100644 --- a/gen/db/location.sql.go +++ b/gen/db/location.sql.go @@ -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 diff --git a/gen/db/models.go b/gen/db/models.go index e41c659..133b635 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 package dbgen @@ -538,6 +538,23 @@ type Otp struct { ExpiresAt pgtype.Timestamptz `json:"expires_at"` } +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"` +} + +type RaffleTicket struct { + ID int32 `json:"id"` + RaffleID int32 `json:"raffle_id"` + UserID int32 `json:"user_id"` + IsActive pgtype.Bool `json:"is_active"` +} + type Referral struct { ID int64 `json:"id"` CompanyID int64 `json:"company_id"` diff --git a/gen/db/monitor.sql.go b/gen/db/monitor.sql.go index a9a7ecb..b5f248f 100644 --- a/gen/db/monitor.sql.go +++ b/gen/db/monitor.sql.go @@ -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 diff --git a/gen/db/notification.sql.go b/gen/db/notification.sql.go index ba9882b..9ce7e42 100644 --- a/gen/db/notification.sql.go +++ b/gen/db/notification.sql.go @@ -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 diff --git a/gen/db/odd_history.sql.go b/gen/db/odd_history.sql.go index 0a0333d..dd69a51 100644 --- a/gen/db/odd_history.sql.go +++ b/gen/db/odd_history.sql.go @@ -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 diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index 79da894..d194d14 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -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 diff --git a/gen/db/otp.sql.go b/gen/db/otp.sql.go index 7dba175..c96aaaa 100644 --- a/gen/db/otp.sql.go +++ b/gen/db/otp.sql.go @@ -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 diff --git a/gen/db/raffle.sql.go b/gen/db/raffle.sql.go new file mode 100644 index 0000000..4b77106 --- /dev/null +++ b/gen/db/raffle.sql.go @@ -0,0 +1,186 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: raffle.sql + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +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 +` + +type CreateRaffleParams struct { + CompanyID int32 `json:"company_id"` + Name string `json:"name"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + Type string `json:"type"` +} + +func (q *Queries) CreateRaffle(ctx context.Context, arg CreateRaffleParams) (Raffle, error) { + row := q.db.QueryRow(ctx, CreateRaffle, + arg.CompanyID, + arg.Name, + arg.ExpiresAt, + arg.Type, + ) + var i Raffle + err := row.Scan( + &i.ID, + &i.CompanyID, + &i.Name, + &i.CreatedAt, + &i.ExpiresAt, + &i.Type, + &i.Status, + ) + return i, err +} + +const CreateRaffleTicket = `-- name: CreateRaffleTicket :one +INSERT INTO raffle_tickets (raffle_id, user_id) +VALUES ($1, $2) +RETURNING id, raffle_id, user_id, is_active +` + +type CreateRaffleTicketParams struct { + RaffleID int32 `json:"raffle_id"` + UserID int32 `json:"user_id"` +} + +func (q *Queries) CreateRaffleTicket(ctx context.Context, arg CreateRaffleTicketParams) (RaffleTicket, error) { + row := q.db.QueryRow(ctx, CreateRaffleTicket, arg.RaffleID, arg.UserID) + var i RaffleTicket + err := row.Scan( + &i.ID, + &i.RaffleID, + &i.UserID, + &i.IsActive, + ) + return i, err +} + +const DeleteRaffle = `-- name: DeleteRaffle :one +DELETE FROM raffles +WHERE id = $1 +RETURNING id, company_id, name, created_at, expires_at, type, status +` + +func (q *Queries) DeleteRaffle(ctx context.Context, id int32) (Raffle, error) { + row := q.db.QueryRow(ctx, DeleteRaffle, id) + var i Raffle + err := row.Scan( + &i.ID, + &i.CompanyID, + &i.Name, + &i.CreatedAt, + &i.ExpiresAt, + &i.Type, + &i.Status, + ) + return i, err +} + +const GetRafflesOfCompany = `-- name: GetRafflesOfCompany :many +SELECT id, company_id, name, created_at, expires_at, type, status FROM raffles WHERE company_id = $1 +` + +func (q *Queries) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]Raffle, error) { + rows, err := q.db.Query(ctx, GetRafflesOfCompany, companyID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Raffle + for rows.Next() { + var i Raffle + if err := rows.Scan( + &i.ID, + &i.CompanyID, + &i.Name, + &i.CreatedAt, + &i.ExpiresAt, + &i.Type, + &i.Status, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetUserRaffleTickets = `-- name: GetUserRaffleTickets :many +SELECT + rt.id AS ticket_id, + rt.user_id, + r.name, + r.type, + r.expires_at, + r.status +FROM raffle_tickets rt +JOIN raffles r ON rt.raffle_id = r.id +WHERE rt.user_id = $1 +` + +type GetUserRaffleTicketsRow struct { + TicketID int32 `json:"ticket_id"` + UserID int32 `json:"user_id"` + Name string `json:"name"` + Type string `json:"type"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + Status string `json:"status"` +} + +func (q *Queries) GetUserRaffleTickets(ctx context.Context, userID int32) ([]GetUserRaffleTicketsRow, error) { + rows, err := q.db.Query(ctx, GetUserRaffleTickets, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetUserRaffleTicketsRow + for rows.Next() { + var i GetUserRaffleTicketsRow + if err := rows.Scan( + &i.TicketID, + &i.UserID, + &i.Name, + &i.Type, + &i.ExpiresAt, + &i.Status, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const UpdateRaffleTicketStatus = `-- name: UpdateRaffleTicketStatus :exec +UPDATE raffle_tickets +SET is_active = $1 +WHERE id = $2 +` + +type UpdateRaffleTicketStatusParams struct { + IsActive pgtype.Bool `json:"is_active"` + ID int32 `json:"id"` +} + +func (q *Queries) UpdateRaffleTicketStatus(ctx context.Context, arg UpdateRaffleTicketStatusParams) error { + _, err := q.db.Exec(ctx, UpdateRaffleTicketStatus, arg.IsActive, arg.ID) + return err +} diff --git a/gen/db/referal.sql.go b/gen/db/referal.sql.go index 6db003a..e2044ca 100644 --- a/gen/db/referal.sql.go +++ b/gen/db/referal.sql.go @@ -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 diff --git a/gen/db/report.sql.go b/gen/db/report.sql.go index 1a1ccde..d6193c1 100644 --- a/gen/db/report.sql.go +++ b/gen/db/report.sql.go @@ -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 diff --git a/gen/db/result.sql.go b/gen/db/result.sql.go index bff7b1e..899561b 100644 --- a/gen/db/result.sql.go +++ b/gen/db/result.sql.go @@ -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 diff --git a/gen/db/result_log.sql.go b/gen/db/result_log.sql.go index 468795e..3f11e16 100644 --- a/gen/db/result_log.sql.go +++ b/gen/db/result_log.sql.go @@ -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 diff --git a/gen/db/settings.sql.go b/gen/db/settings.sql.go index f67fecc..eb50e0e 100644 --- a/gen/db/settings.sql.go +++ b/gen/db/settings.sql.go @@ -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 diff --git a/gen/db/shop_transactions.sql.go b/gen/db/shop_transactions.sql.go index bcd884e..7664dbb 100644 --- a/gen/db/shop_transactions.sql.go +++ b/gen/db/shop_transactions.sql.go @@ -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 diff --git a/gen/db/ticket.sql.go b/gen/db/ticket.sql.go index bc9bb5f..45603ba 100644 --- a/gen/db/ticket.sql.go +++ b/gen/db/ticket.sql.go @@ -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 diff --git a/gen/db/transfer.sql.go b/gen/db/transfer.sql.go index 35e38d4..926fc8c 100644 --- a/gen/db/transfer.sql.go +++ b/gen/db/transfer.sql.go @@ -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 diff --git a/gen/db/user.sql.go b/gen/db/user.sql.go index 43d9156..67d103d 100644 --- a/gen/db/user.sql.go +++ b/gen/db/user.sql.go @@ -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 diff --git a/gen/db/virtual_games.sql.go b/gen/db/virtual_games.sql.go index 33697d7..cef2965 100644 --- a/gen/db/virtual_games.sql.go +++ b/gen/db/virtual_games.sql.go @@ -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 diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index 7e3eb7f..f41a944 100644 --- a/gen/db/wallet.sql.go +++ b/gen/db/wallet.sql.go @@ -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 diff --git a/internal/domain/raffle.go b/internal/domain/raffle.go new file mode 100644 index 0000000..c6adc2c --- /dev/null +++ b/internal/domain/raffle.go @@ -0,0 +1,48 @@ +package domain + +import "time" + +type Raffle struct { + ID int32 + CompanyID int32 + Name string + CreatedAt time.Time + ExpiresAt time.Time + Type string + Status string +} + +type RaffleTicket struct { + ID int32 + RaffleID int32 + UserID int32 + IsActive bool +} + +type RaffleTicketRes struct { + TicketID int32 + UserID int32 + Name string + Type string + ExpiresAt time.Time + Status string +} + +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"` +} + +type CreateRaffleTicket struct { + RaffleID int32 `json:"raffle_id" validate:"required"` + UserID int32 `json:"user_id" validate:"required"` +} + +// aside from ID, atleast one of the fields should be required +type UpdateRaffleParams struct { + ID int32 `json:"id" validate:"required"` + Name string `json:"name" validate:"required_without_all=ExpiresAt"` + ExpiresAt *time.Time `json:"expires_at" validate:"required_without_all=Name"` +} diff --git a/internal/repository/raffel.go b/internal/repository/raffel.go new file mode 100644 index 0000000..db230bd --- /dev/null +++ b/internal/repository/raffel.go @@ -0,0 +1,128 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" +) + +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, + } +} + +func convertRaffleTicketOutcome(raffle dbgen.RaffleTicket) domain.RaffleTicket { + return domain.RaffleTicket{ + ID: raffle.ID, + RaffleID: raffle.RaffleID, + UserID: raffle.UserID, + IsActive: raffle.IsActive.Bool, + } +} + +func convertJoinedRaffleTicketOutcome(raffle dbgen.GetUserRaffleTicketsRow) domain.RaffleTicketRes { + return domain.RaffleTicketRes{ + TicketID: raffle.TicketID, + UserID: raffle.UserID, + Name: raffle.Name, + Type: raffle.Type, + ExpiresAt: raffle.ExpiresAt.Time, + Status: raffle.Status, + } +} + +func convertCreateRaffle(raffle domain.CreateRaffle) dbgen.CreateRaffleParams { + return dbgen.CreateRaffleParams{ + CompanyID: raffle.CompanyID, + Name: raffle.Name, + ExpiresAt: pgtype.Timestamp{ + Time: *raffle.ExpiresAt, + Valid: true, + }, + Type: raffle.Type, + } +} + +func (s *Store) CreateRaffle(ctx context.Context, raffle domain.CreateRaffle) (domain.Raffle, error) { + raffleRes, err := s.queries.CreateRaffle(ctx, convertCreateRaffle(raffle)) + if err != nil { + return domain.Raffle{}, err + } + + return convertRaffleOutcome(raffleRes), nil +} + +func (s *Store) DeleteRaffle(ctx context.Context, raffleID int32) (domain.Raffle, error) { + raffleRes, err := s.queries.DeleteRaffle(ctx, raffleID) + if err != nil { + return domain.Raffle{}, err + } + + return convertRaffleOutcome(raffleRes), nil +} + +func (s *Store) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]dbgen.Raffle, error) { + raffles, err := s.queries.GetRafflesOfCompany(ctx, companyID) + if err != nil { + return nil, err + } + + return raffles, nil +} + +func (s *Store) CreateRaffleTicket(ctx context.Context, raffleTicketParams domain.CreateRaffleTicket) (domain.RaffleTicket, error) { + raffleTicket, err := s.queries.CreateRaffleTicket(ctx, dbgen.CreateRaffleTicketParams{ + RaffleID: raffleTicketParams.RaffleID, + UserID: raffleTicketParams.UserID, + }) + if err != nil { + return domain.RaffleTicket{}, err + } + + return convertRaffleTicketOutcome(raffleTicket), nil +} + +func (s *Store) GetUserRaffleTickets(ctx context.Context, userID int32) ([]domain.RaffleTicketRes, error) { + raffleTickets, err := s.queries.GetUserRaffleTickets(ctx, userID) + if err != nil { + return nil, err + } + + res := []domain.RaffleTicketRes{} + for _, raffle := range raffleTickets { + res = append(res, convertJoinedRaffleTicketOutcome(raffle)) + } + + return res, nil +} + +func (s *Store) SuspendRaffleTicket(ctx context.Context, raffleTicketID int32) error { + return s.queries.UpdateRaffleTicketStatus(ctx, dbgen.UpdateRaffleTicketStatusParams{ + ID: raffleTicketID, + IsActive: pgtype.Bool{ + Bool: false, + Valid: true, + }, + }) +} + +func (s *Store) UnSuspendRaffleTicket(ctx context.Context, raffleID int32) error { + return s.queries.UpdateRaffleTicketStatus(ctx, dbgen.UpdateRaffleTicketStatusParams{ + ID: raffleID, + IsActive: pgtype.Bool{ + Bool: true, + Valid: true, + }, + }) +} + +// TODO: could also add -> suspend a specific user's raffle tickets diff --git a/internal/repository/settings.go b/internal/repository/settings.go index 333f280..f456bff 100644 --- a/internal/repository/settings.go +++ b/internal/repository/settings.go @@ -165,9 +165,9 @@ func (s *Store) GetOverrideSettingsList(ctx context.Context, companyID int64) (d func (s *Store) DeleteCompanySetting(ctx context.Context, companyID int64, key string) error { return s.queries.DeleteCompanySetting(ctx, dbgen.DeleteCompanySettingParams{ CompanyID: companyID, - Key: key, + Key: key, }) } -func (s *Store) DeleteAllCompanySetting(ctx context.Context, companyID int64,) error { +func (s *Store) DeleteAllCompanySetting(ctx context.Context, companyID int64) error { return s.queries.DeleteAllCompanySetting(ctx, companyID) } diff --git a/internal/services/raffle/port.go b/internal/services/raffle/port.go new file mode 100644 index 0000000..c8c4b8e --- /dev/null +++ b/internal/services/raffle/port.go @@ -0,0 +1,18 @@ +package raffle + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +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) + 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 +} diff --git a/internal/services/raffle/service.go b/internal/services/raffle/service.go new file mode 100644 index 0000000..1246fb7 --- /dev/null +++ b/internal/services/raffle/service.go @@ -0,0 +1,46 @@ +package raffle + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service struct { + raffleStore RaffleStore +} + +func NewService(raffleStore RaffleStore) *Service { + return &Service{ + raffleStore: raffleStore, + } +} + +func (s *Service) CreateRaffle(ctx context.Context, raffle domain.CreateRaffle) (domain.Raffle, error) { + return s.raffleStore.CreateRaffle(ctx, raffle) +} + +func (s *Service) DeleteRaffle(ctx context.Context, raffleID int32) (domain.Raffle, error) { + return s.raffleStore.DeleteRaffle(ctx, raffleID) +} + +func (s *Service) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]dbgen.Raffle, error) { + return s.raffleStore.GetRafflesOfCompany(ctx, companyID) +} + +func (s *Service) CreateRaffleTicket(ctx context.Context, raffleTicketParams domain.CreateRaffleTicket) (domain.RaffleTicket, error) { + return s.raffleStore.CreateRaffleTicket(ctx, raffleTicketParams) +} + +func (s *Service) GetUserRaffleTickets(ctx context.Context, userID int32) ([]domain.RaffleTicketRes, error) { + return s.raffleStore.GetUserRaffleTickets(ctx, userID) +} + +func (s *Service) SuspendRaffleTicket(ctx context.Context, raffleTicketID int32) error { + return s.raffleStore.SuspendRaffleTicket(ctx, raffleTicketID) +} + +func (s *Service) UnSuspendRaffleTicket(ctx context.Context, raffleID int32) error { + return s.raffleStore.UnSuspendRaffleTicket(ctx, raffleID) +} diff --git a/internal/web_server/app.go b/internal/web_server/app.go index 02e84e1..8dc3f40 100644 --- a/internal/web_server/app.go +++ b/internal/web_server/app.go @@ -18,6 +18,7 @@ import ( issuereporting "github.com/SamuelTariku/FortuneBet-Backend/internal/services/issue_reporting" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/league" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" @@ -57,6 +58,7 @@ type App struct { logger *slog.Logger NotidicationStore *notificationservice.Service referralSvc referralservice.ReferralStore + raffleSvc raffle.RaffleStore bonusSvc *bonus.Service port int settingSvc *settings.Service @@ -108,6 +110,7 @@ func NewApp( eventSvc event.Service, leagueSvc league.Service, referralSvc referralservice.ReferralStore, + raffleSvc raffle.RaffleStore, bonusSvc *bonus.Service, virtualGameSvc virtualgameservice.VirtualGameService, aleaVirtualGameService alea.AleaVirtualGameService, @@ -158,6 +161,7 @@ func NewApp( companySvc: companySvc, NotidicationStore: notidicationStore, referralSvc: referralSvc, + raffleSvc: raffleSvc, bonusSvc: bonusSvc, Logger: logger, prematchSvc: prematchSvc, diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index 664a1f0..de240a3 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -236,6 +236,8 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI return domain.CreateBetRes{}, err } + // create raffle ticket here + return res, nil } diff --git a/internal/web_server/handlers/handlers.go b/internal/web_server/handlers/handlers.go index a59dbc9..673a871 100644 --- a/internal/web_server/handlers/handlers.go +++ b/internal/web_server/handlers/handlers.go @@ -18,6 +18,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/league" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" @@ -49,6 +50,7 @@ type Handler struct { notificationSvc *notificationservice.Service userSvc *user.Service referralSvc referralservice.ReferralStore + raffleSvc raffle.RaffleStore bonusSvc *bonus.Service reportSvc report.ReportStore chapaSvc *chapa.Service @@ -88,6 +90,7 @@ func New( chapaSvc *chapa.Service, walletSvc *wallet.Service, referralSvc referralservice.ReferralStore, + raffleSvc raffle.RaffleStore, bonusSvc *bonus.Service, virtualGameSvc virtualgameservice.VirtualGameService, aleaVirtualGameSvc alea.AleaVirtualGameService, @@ -122,6 +125,7 @@ func New( chapaSvc: chapaSvc, walletSvc: walletSvc, referralSvc: referralSvc, + raffleSvc: raffleSvc, bonusSvc: bonusSvc, validator: validator, userSvc: userSvc, diff --git a/internal/web_server/handlers/raffle_handler.go b/internal/web_server/handlers/raffle_handler.go new file mode 100644 index 0000000..d1b59ba --- /dev/null +++ b/internal/web_server/handlers/raffle_handler.go @@ -0,0 +1,216 @@ +package handlers + +import ( + "fmt" + "strconv" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +func (h *Handler) CreateRaffle(c *fiber.Ctx) error { + var req domain.CreateRaffle + if err := c.BodyParser(&req); err != nil { + h.mongoLoggerSvc.Info("Failed to parse raffle request", + zap.Int("status_code", fiber.StatusBadRequest), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + if valErrs, ok := h.validator.Validate(c, req); !ok { + var errMsg string + for field, msg := range valErrs { + errMsg += fmt.Sprintf("%s: %s; ", field, msg) + } + h.mongoLoggerSvc.Info("Failed to validate settings", + zap.String("errMsg", errMsg), + zap.Int("status_code", fiber.StatusBadRequest), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, errMsg) + } + + raffle, err := h.raffleSvc.CreateRaffle(c.Context(), req) + if err != nil { + h.mongoLoggerSvc.Error("Failed to create raffle", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create raffle") + } + + return response.WriteJSON(c, fiber.StatusOK, "Raffle created successfully", raffle, nil) +} + +func (h *Handler) DeleteRaffle(c *fiber.Ctx) error { + stringRaffleID := c.Params("id") + raffleID, err := strconv.Atoi(stringRaffleID) + if err != nil { + h.mongoLoggerSvc.Info("failed to parse raffle id", + zap.String("stringRaffleID", stringRaffleID), + zap.Int("status_code", fiber.StatusBadRequest), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Invalid raffle id") + } + + raffle, err := h.raffleSvc.DeleteRaffle(c.Context(), int32(raffleID)) + if err != nil { + fmt.Println("raffle delete error: ", err) + h.mongoLoggerSvc.Error("Failed to delete raffle", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete raffle") + } + + return response.WriteJSON(c, fiber.StatusOK, "Raffle deleted successfully", raffle, nil) +} + +func (h *Handler) GetRafflesOfCompany(c *fiber.Ctx) error { + stringCompanyID := c.Params("id") + companyID, err := strconv.Atoi(stringCompanyID) + if err != nil || companyID == 0 { + h.mongoLoggerSvc.Info("failed to parse company id", + zap.String("stringCompanyID", stringCompanyID), + zap.Int("status_code", fiber.StatusBadRequest), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Invalid company ID") + } + + companyRaffles, err := h.raffleSvc.GetRafflesOfCompany(c.Context(), int32(companyID)) + if err != nil { + h.mongoLoggerSvc.Error("Failed to fetch company raffle", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch company raffle") + } + + return response.WriteJSON(c, fiber.StatusOK, "Company Raffles fetched successfully", companyRaffles, nil) +} + +func (h *Handler) CreateRaffleTicket(c *fiber.Ctx) error { + var req domain.CreateRaffleTicket + if err := c.BodyParser(&req); err != nil { + h.mongoLoggerSvc.Info("Failed to parse raffle ticket request", + zap.Int("status_code", fiber.StatusBadRequest), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + if valErrs, ok := h.validator.Validate(c, req); !ok { + var errMsg string + for field, msg := range valErrs { + errMsg += fmt.Sprintf("%s: %s; ", field, msg) + } + h.mongoLoggerSvc.Info("Failed to validate settings", + zap.String("errMsg", errMsg), + zap.Int("status_code", fiber.StatusBadRequest), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, errMsg) + } + + raffleTicket, err := h.raffleSvc.CreateRaffleTicket(c.Context(), req) + if err != nil { + h.mongoLoggerSvc.Error("Failed to create raffle ticket", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create raffle ticket") + } + + return response.WriteJSON(c, fiber.StatusOK, "Raffle created successfully", raffleTicket, nil) +} + +func (h *Handler) GetUserRaffleTickets(c *fiber.Ctx) error { + stringUserID := c.Params("id") + userID, err := strconv.Atoi(stringUserID) + if err != nil { + h.mongoLoggerSvc.Info("failed to parse company id", + zap.String("stringUserID", stringUserID), + zap.Int("status_code", fiber.StatusBadRequest), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID") + } + + raffleTickets, err := h.raffleSvc.GetUserRaffleTickets(c.Context(), int32(userID)) + if err != nil { + h.mongoLoggerSvc.Error("Failed to fetch user raffle tickets", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch user raffle tickets") + } + + return response.WriteJSON(c, fiber.StatusOK, "User raffle tickets fetched successfully", raffleTickets, nil) +} + +func (h *Handler) SuspendRaffleTicket(c *fiber.Ctx) error { + stringRaffleTicketID := c.Params("id") + raffleTicketID, err := strconv.Atoi(stringRaffleTicketID) + if err != nil { + h.mongoLoggerSvc.Info("failed to parse raffle ticket id", + zap.String("stringUserID", stringRaffleTicketID), + zap.Int("status_code", fiber.StatusBadRequest), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Invalid raffel ticket id") + } + + if err := h.raffleSvc.SuspendRaffleTicket(c.Context(), int32(raffleTicketID)); err != nil { + h.mongoLoggerSvc.Error("Failed to suspend raffle ticket", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to suspend raffle ticket") + } + + return response.WriteJSON(c, fiber.StatusOK, "User raffle tickets suspended successfully", nil, nil) +} + +func (h *Handler) UnSuspendRaffleTicket(c *fiber.Ctx) error { + stringRaffleTicketID := c.Params("id") + raffleTicketID, err := strconv.Atoi(stringRaffleTicketID) + if err != nil { + h.mongoLoggerSvc.Info("failed to parse raffle ticket id", + zap.String("stringUserID", stringRaffleTicketID), + zap.Int("status_code", fiber.StatusBadRequest), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Invalid raffel ticket id") + } + + if err := h.raffleSvc.UnSuspendRaffleTicket(c.Context(), int32(raffleTicketID)); err != nil { + h.mongoLoggerSvc.Error("Failed to unsuspend raffle ticket", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to unsuspend raffle ticket") + } + + return response.WriteJSON(c, fiber.StatusOK, "User raffle tickets unsuspended successfully", nil, nil) + +} diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index c337f4a..9e68e79 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -34,6 +34,7 @@ func (a *App) initAppRoutes() { a.chapaSvc, a.walletSvc, a.referralSvc, + a.raffleSvc, a.bonusSvc, a.virtualGameSvc, a.aleaVirtualGameService, @@ -193,6 +194,15 @@ func (a *App) initAppRoutes() { groupV1.Get("/referral/settings", a.authMiddleware, h.GetReferralSettings) groupV1.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings) + // Raffle Routes + 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.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) + a.fiber.Get("/raffle-ticket/unsuspend/:id", a.authMiddleware, h.UnSuspendRaffleTicket) + // Bonus Routes groupV1.Get("/bonus", a.authMiddleware, h.GetBonusMultiplier) groupV1.Post("/bonus/create", a.authMiddleware, h.CreateBonusMultiplier)