feat: referal completed

This commit is contained in:
dawitel 2025-04-12 03:27:50 +03:00
parent 49a97484c7
commit f796b97afe
25 changed files with 2239 additions and 1015 deletions

View File

@ -1,4 +1,5 @@
# FortuneBet-Backend
# Directory Structure
├── cmd
@ -9,13 +10,19 @@
│ │ ├── 000001_fortune.up.sql
│ │ ├── 000002_notification.down.sql
│ │ ├── 000002_notification.up.sql
│ │ ├── 000003_referal.down.sql
│ │ ├── 000003_referal.up.sql
│ └── query
│ ├── auth.sql
│ ├── bet.sql
│ ├── notification.sql
│ ├── otp.sql
│ ├── referal.sql
│ ├── ticket.sql
│ ├── transactions.sql
│ ├── transfer.sql
│ ├── user.sql
│ ├── wallet.sql
├── docs
│ ├── docs.go
│ ├── swagger.json
@ -28,8 +35,12 @@
│ ├── models.go
│ ├── notification.sql.go
│ ├── otp.sql.go
│ ├── referal.sql.go
│ ├── ticket.sql.go
│ ├── transactions.sql.go
│ ├── transfer.sql.go
│ ├── user.sql.go
│ ├── wallet.sql.go
└── internal
├── config
│ ├── config.go
@ -41,9 +52,13 @@
│ ├── event.go
│ ├── notification.go
│ ├── otp.go
│ ├── referal.go
│ ├── role.go
│ ├── ticket.go
│ ├── transaction.go
│ ├── transfer.go
│ ├── user.go
│ ├── wallet.go
├── logger
│ ├── logger.go
├── mocks
@ -59,9 +74,13 @@
│ ├── bet.go
│ ├── notification.go
│ ├── otp.go
│ ├── referal.go
│ ├── store.go
│ ├── ticket.go
│ ├── transaction.go
│ ├── transfer.go
│ ├── user.go
│ ├── wallet.go
├── services
│ ├── authentication
│ │ ├── impl.go
@ -73,6 +92,9 @@
│ ├── notfication
│ │ ├── port.go
│ │ ├── service.go
│ ├── referal
│ │ ├── port.go
│ │ ├── service.go
│ ├── sportsbook
│ │ ├── events.go
│ │ ├── odds.go
@ -80,20 +102,33 @@
│ ├── ticket
│ │ ├── port.go
│ │ ├── service.go
│ └── user
│ ├── common.go
│ ├── transaction
│ │ ├── port.go
│ │ ├── service.go
│ ├── transfer
│ │ ├── chapa.go
│ │ ├── port.go
│ │ ├── service.go
│ ├── user
│ │ ├── common.go
│ │ ├── port.go
│ │ ├── register.go
│ │ ├── reset.go
│ │ ├── service.go
│ │ ├── user.go
│ └── wallet
│ ├── port.go
│ ├── register.go
│ ├── reset.go
│ ├── service.go
│ ├── user.go
└── web_server
├── handlers
│ ├── auth_handler.go
│ ├── bet_handler.go
│ ├── handlers.go
│ ├── notification_handler.go
│ ├── ticket_handler.go
│ ├── transaction_handler.go
│ ├── user.go
│ ├── wallet_handler.go
├── jwt
│ ├── jwt.go
│ ├── jwt_test.go

View File

@ -149,6 +149,3 @@ INSERT INTO users (
NULL,
FALSE
);

View File

@ -0,0 +1,17 @@
DROP TABLE IF EXISTS referrals;
DROP TABLE IF EXISTS referral_settings;
DROP TYPE IF EXISTS ReferralStatus;
ALTER TABLE users
DROP COLUMN referral_code;
ALTER TABLE users
DROP COLUMN referred_by;
ALTER TABLE wallet
DROP COLUMN bonus_balance;
ALTER TABLE wallet
DROP COLUMN cash_balance;

View File

@ -0,0 +1,53 @@
CREATE TYPE ReferralStatus AS ENUM ('PENDING', 'COMPLETED', 'EXPIRED', 'CANCELLED');
CREATE TABLE IF NOT EXISTS referral_settings (
id BIGSERIAL PRIMARY KEY,
referral_reward_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
cashback_percentage DECIMAL(5, 2) NOT NULL DEFAULT 0.00,
bet_referral_bonus_percentage NUMERIC DEFAULT 5.0,
max_referrals INTEGER NOT NULL DEFAULT 0,
expires_after_days INTEGER NOT NULL DEFAULT 30,
updated_by VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
version INTEGER NOT NULL DEFAULT 0,
CONSTRAINT referral_reward_amount_positive CHECK (referral_reward_amount >= 0),
CONSTRAINT cashback_percentage_range CHECK (
cashback_percentage >= 0
AND cashback_percentage <= 100
)
);
CREATE TABLE IF NOT EXISTS referrals (
id BIGSERIAL PRIMARY KEY,
referral_code VARCHAR(10) NOT NULL UNIQUE,
referrer_id VARCHAR(255) NOT NULL,
referred_id VARCHAR(255) UNIQUE,
status ReferralStatus NOT NULL DEFAULT 'PENDING',
reward_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
cashback_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMPTZ NOT NULL,
FOREIGN KEY (referrer_id) REFERENCES users (id),
FOREIGN KEY (referred_id) REFERENCES users (id),
CONSTRAINT reward_amount_positive CHECK (reward_amount >= 0),
CONSTRAINT cashback_amount_positive CHECK (cashback_amount >= 0)
);
CREATE INDEX idx_referrals_referral_code ON referrals (referral_code);
CREATE INDEX idx_referrals_referrer_id ON referrals (referrer_id);
CREATE INDEX idx_referrals_status ON referrals (status);
ALTER TABLE users
ADD COLUMN IF NOT EXISTS referral_code VARCHAR(10) UNIQUE,
ADD COLUMN IF NOT EXISTS referred_by VARCHAR(10);
-- Modify wallet table to track bonus money separately
ALTER TABLE wallets
ADD COLUMN IF NOT EXISTS bonus_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
ADD COLUMN IF NOT EXISTS cash_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
ADD CONSTRAINT bonus_balance_positive CHECK (bonus_balance >= 0),
ADD CONSTRAINT cash_balance_positive CHECK (cash_balance >= 0);

65
db/query/referal.sql Normal file
View File

@ -0,0 +1,65 @@
-- name: CreateReferral :one
INSERT INTO referrals (
referral_code,
referrer_id,
status,
reward_amount,
expires_at
) VALUES (
$1, $2, $3, $4, $5
) RETURNING *;
-- name: GetReferralByCode :one
SELECT * FROM referrals
WHERE referral_code = $1;
-- name: UpdateReferral :one
UPDATE referrals
SET
referred_id = $2,
status = $3,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING *;
-- name: GetReferralStats :one
SELECT
COUNT(*) as total_referrals,
COUNT(CASE WHEN status = 'COMPLETED' THEN 1 END) as completed_referrals,
COALESCE(SUM(reward_amount), 0) as total_reward_earned,
COALESCE(SUM(CASE WHEN status = 'PENDING' THEN reward_amount END), 0) as pending_rewards
FROM referrals
WHERE referrer_id = $1;
-- name: GetReferralSettings :one
SELECT * FROM referral_settings
WHERE id = 'default'
LIMIT 1;
-- name: UpdateReferralSettings :one
UPDATE referral_settings
SET
referral_reward_amount = $2,
cashback_percentage = $3,
bet_referral_bonus_percentage= $4,
max_referrals = $5,
expires_after_days = $6,
updated_by = $7,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING *;
-- name: CreateReferralSettings :one
INSERT INTO referral_settings (
referral_reward_amount,
cashback_percentage,
max_referrals,
bet_referral_bonus_percentage,
expires_after_days,
updated_by
) VALUES (
$1, $2, $3, $4, $5, $6
) RETURNING *;
-- name: GetReferralByReferredID :one
SELECT * FROM referrals WHERE referred_id = $1 LIMIT 1;

View File

@ -55,7 +55,7 @@ func (q *Queries) GetRefreshToken(ctx context.Context, token string) (RefreshTok
}
const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one
SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, suspended_at, suspended FROM users
SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, suspended_at, suspended, referral_code, referred_by FROM users
WHERE email = $1 OR phone_number = $2
`
@ -81,6 +81,8 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho
&i.UpdatedAt,
&i.SuspendedAt,
&i.Suspended,
&i.ReferralCode,
&i.ReferredBy,
)
return i, err
}

View File

@ -5,9 +5,56 @@
package dbgen
import (
"database/sql/driver"
"fmt"
"github.com/jackc/pgx/v5/pgtype"
)
type Referralstatus string
const (
ReferralstatusPENDING Referralstatus = "PENDING"
ReferralstatusCOMPLETED Referralstatus = "COMPLETED"
ReferralstatusEXPIRED Referralstatus = "EXPIRED"
ReferralstatusCANCELLED Referralstatus = "CANCELLED"
)
func (e *Referralstatus) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = Referralstatus(s)
case string:
*e = Referralstatus(s)
default:
return fmt.Errorf("unsupported scan type for Referralstatus: %T", src)
}
return nil
}
type NullReferralstatus struct {
Referralstatus Referralstatus
Valid bool // Valid is true if Referralstatus is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullReferralstatus) Scan(value interface{}) error {
if value == nil {
ns.Referralstatus, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.Referralstatus.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullReferralstatus) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.Referralstatus), nil
}
type Bet struct {
ID int64
Amount int64
@ -62,6 +109,32 @@ type Otp struct {
ExpiresAt pgtype.Timestamptz
}
type Referral struct {
ID int64
ReferralCode string
ReferrerID string
ReferredID pgtype.Text
Status Referralstatus
RewardAmount pgtype.Numeric
CashbackAmount pgtype.Numeric
CreatedAt pgtype.Timestamptz
UpdatedAt pgtype.Timestamptz
ExpiresAt pgtype.Timestamptz
}
type ReferralSetting struct {
ID int64
ReferralRewardAmount pgtype.Numeric
CashbackPercentage pgtype.Numeric
BetReferralBonusPercentage pgtype.Numeric
MaxReferrals int32
ExpiresAfterDays int32
UpdatedBy string
CreatedAt pgtype.Timestamptz
UpdatedAt pgtype.Timestamptz
Version int32
}
type RefreshToken struct {
ID int64
UserID int64
@ -112,6 +185,8 @@ type User struct {
UpdatedAt pgtype.Timestamptz
SuspendedAt pgtype.Timestamptz
Suspended bool
ReferralCode pgtype.Text
ReferredBy pgtype.Text
}
type Wallet struct {
@ -123,6 +198,8 @@ type Wallet struct {
IsActive bool
CreatedAt pgtype.Timestamp
UpdatedAt pgtype.Timestamp
BonusBalance pgtype.Numeric
CashBalance pgtype.Numeric
}
type WalletTransfer struct {

285
gen/db/referal.sql.go Normal file
View File

@ -0,0 +1,285 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// source: referal.sql
package dbgen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const CreateReferral = `-- name: CreateReferral :one
INSERT INTO referrals (
referral_code,
referrer_id,
status,
reward_amount,
expires_at
) VALUES (
$1, $2, $3, $4, $5
) RETURNING id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at
`
type CreateReferralParams struct {
ReferralCode string
ReferrerID string
Status Referralstatus
RewardAmount pgtype.Numeric
ExpiresAt pgtype.Timestamptz
}
func (q *Queries) CreateReferral(ctx context.Context, arg CreateReferralParams) (Referral, error) {
row := q.db.QueryRow(ctx, CreateReferral,
arg.ReferralCode,
arg.ReferrerID,
arg.Status,
arg.RewardAmount,
arg.ExpiresAt,
)
var i Referral
err := row.Scan(
&i.ID,
&i.ReferralCode,
&i.ReferrerID,
&i.ReferredID,
&i.Status,
&i.RewardAmount,
&i.CashbackAmount,
&i.CreatedAt,
&i.UpdatedAt,
&i.ExpiresAt,
)
return i, err
}
const CreateReferralSettings = `-- name: CreateReferralSettings :one
INSERT INTO referral_settings (
referral_reward_amount,
cashback_percentage,
max_referrals,
bet_referral_bonus_percentage,
expires_after_days,
updated_by
) VALUES (
$1, $2, $3, $4, $5, $6
) RETURNING id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version
`
type CreateReferralSettingsParams struct {
ReferralRewardAmount pgtype.Numeric
CashbackPercentage pgtype.Numeric
MaxReferrals int32
BetReferralBonusPercentage pgtype.Numeric
ExpiresAfterDays int32
UpdatedBy string
}
func (q *Queries) CreateReferralSettings(ctx context.Context, arg CreateReferralSettingsParams) (ReferralSetting, error) {
row := q.db.QueryRow(ctx, CreateReferralSettings,
arg.ReferralRewardAmount,
arg.CashbackPercentage,
arg.MaxReferrals,
arg.BetReferralBonusPercentage,
arg.ExpiresAfterDays,
arg.UpdatedBy,
)
var i ReferralSetting
err := row.Scan(
&i.ID,
&i.ReferralRewardAmount,
&i.CashbackPercentage,
&i.BetReferralBonusPercentage,
&i.MaxReferrals,
&i.ExpiresAfterDays,
&i.UpdatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.Version,
)
return i, err
}
const GetReferralByCode = `-- name: GetReferralByCode :one
SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals
WHERE referral_code = $1
`
func (q *Queries) GetReferralByCode(ctx context.Context, referralCode string) (Referral, error) {
row := q.db.QueryRow(ctx, GetReferralByCode, referralCode)
var i Referral
err := row.Scan(
&i.ID,
&i.ReferralCode,
&i.ReferrerID,
&i.ReferredID,
&i.Status,
&i.RewardAmount,
&i.CashbackAmount,
&i.CreatedAt,
&i.UpdatedAt,
&i.ExpiresAt,
)
return i, err
}
const GetReferralByReferredID = `-- name: GetReferralByReferredID :one
SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals WHERE referred_id = $1 LIMIT 1
`
func (q *Queries) GetReferralByReferredID(ctx context.Context, referredID pgtype.Text) (Referral, error) {
row := q.db.QueryRow(ctx, GetReferralByReferredID, referredID)
var i Referral
err := row.Scan(
&i.ID,
&i.ReferralCode,
&i.ReferrerID,
&i.ReferredID,
&i.Status,
&i.RewardAmount,
&i.CashbackAmount,
&i.CreatedAt,
&i.UpdatedAt,
&i.ExpiresAt,
)
return i, err
}
const GetReferralSettings = `-- name: GetReferralSettings :one
SELECT id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version FROM referral_settings
WHERE id = 'default'
LIMIT 1
`
func (q *Queries) GetReferralSettings(ctx context.Context) (ReferralSetting, error) {
row := q.db.QueryRow(ctx, GetReferralSettings)
var i ReferralSetting
err := row.Scan(
&i.ID,
&i.ReferralRewardAmount,
&i.CashbackPercentage,
&i.BetReferralBonusPercentage,
&i.MaxReferrals,
&i.ExpiresAfterDays,
&i.UpdatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.Version,
)
return i, err
}
const GetReferralStats = `-- name: GetReferralStats :one
SELECT
COUNT(*) as total_referrals,
COUNT(CASE WHEN status = 'COMPLETED' THEN 1 END) as completed_referrals,
COALESCE(SUM(reward_amount), 0) as total_reward_earned,
COALESCE(SUM(CASE WHEN status = 'PENDING' THEN reward_amount END), 0) as pending_rewards
FROM referrals
WHERE referrer_id = $1
`
type GetReferralStatsRow struct {
TotalReferrals int64
CompletedReferrals int64
TotalRewardEarned float64
PendingRewards float64
}
func (q *Queries) GetReferralStats(ctx context.Context, referrerID string) (GetReferralStatsRow, error) {
row := q.db.QueryRow(ctx, GetReferralStats, referrerID)
var i GetReferralStatsRow
err := row.Scan(
&i.TotalReferrals,
&i.CompletedReferrals,
&i.TotalRewardEarned,
&i.PendingRewards,
)
return i, err
}
const UpdateReferral = `-- name: UpdateReferral :one
UPDATE referrals
SET
referred_id = $2,
status = $3,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at
`
type UpdateReferralParams struct {
ID int64
ReferredID pgtype.Text
Status Referralstatus
}
func (q *Queries) UpdateReferral(ctx context.Context, arg UpdateReferralParams) (Referral, error) {
row := q.db.QueryRow(ctx, UpdateReferral, arg.ID, arg.ReferredID, arg.Status)
var i Referral
err := row.Scan(
&i.ID,
&i.ReferralCode,
&i.ReferrerID,
&i.ReferredID,
&i.Status,
&i.RewardAmount,
&i.CashbackAmount,
&i.CreatedAt,
&i.UpdatedAt,
&i.ExpiresAt,
)
return i, err
}
const UpdateReferralSettings = `-- name: UpdateReferralSettings :one
UPDATE referral_settings
SET
referral_reward_amount = $2,
cashback_percentage = $3,
bet_referral_bonus_percentage= $4,
max_referrals = $5,
expires_after_days = $6,
updated_by = $7,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version
`
type UpdateReferralSettingsParams struct {
ID int64
ReferralRewardAmount pgtype.Numeric
CashbackPercentage pgtype.Numeric
BetReferralBonusPercentage pgtype.Numeric
MaxReferrals int32
ExpiresAfterDays int32
UpdatedBy string
}
func (q *Queries) UpdateReferralSettings(ctx context.Context, arg UpdateReferralSettingsParams) (ReferralSetting, error) {
row := q.db.QueryRow(ctx, UpdateReferralSettings,
arg.ID,
arg.ReferralRewardAmount,
arg.CashbackPercentage,
arg.BetReferralBonusPercentage,
arg.MaxReferrals,
arg.ExpiresAfterDays,
arg.UpdatedBy,
)
var i ReferralSetting
err := row.Scan(
&i.ID,
&i.ReferralRewardAmount,
&i.CashbackPercentage,
&i.BetReferralBonusPercentage,
&i.MaxReferrals,
&i.ExpiresAfterDays,
&i.UpdatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.Version,
)
return i, err
}

View File

@ -193,7 +193,7 @@ func (q *Queries) GetUserByEmail(ctx context.Context, email pgtype.Text) (GetUse
}
const GetUserByID = `-- name: GetUserByID :one
SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, suspended_at, suspended
SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, suspended_at, suspended, referral_code, referred_by
FROM users
WHERE id = $1
`
@ -215,6 +215,8 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
&i.UpdatedAt,
&i.SuspendedAt,
&i.Suspended,
&i.ReferralCode,
&i.ReferredBy,
)
return i, err
}

View File

@ -43,7 +43,7 @@ func (q *Queries) CreateCustomerWallet(ctx context.Context, arg CreateCustomerWa
}
const CreateWallet = `-- name: CreateWallet :one
INSERT INTO wallets (is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3) RETURNING id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at
INSERT INTO wallets (is_withdraw, is_bettable, user_id) VALUES ($1, $2, $3) RETURNING id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance
`
type CreateWalletParams struct {
@ -64,12 +64,14 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal
&i.IsActive,
&i.CreatedAt,
&i.UpdatedAt,
&i.BonusBalance,
&i.CashBalance,
)
return i, err
}
const GetAllWallets = `-- name: GetAllWallets :many
SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at FROM wallets
SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance FROM wallets
`
func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
@ -90,6 +92,8 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
&i.IsActive,
&i.CreatedAt,
&i.UpdatedAt,
&i.BonusBalance,
&i.CashBalance,
); err != nil {
return nil, err
}
@ -156,7 +160,7 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, arg GetCustomerWalletPa
}
const GetWalletByID = `-- name: GetWalletByID :one
SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at FROM wallets WHERE id = $1
SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance FROM wallets WHERE id = $1
`
func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
@ -171,12 +175,14 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
&i.IsActive,
&i.CreatedAt,
&i.UpdatedAt,
&i.BonusBalance,
&i.CashBalance,
)
return i, err
}
const GetWalletByUserID = `-- name: GetWalletByUserID :many
SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at FROM wallets WHERE user_id = $1
SELECT id, balance, is_withdraw, is_bettable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance FROM wallets WHERE user_id = $1
`
func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet, error) {
@ -197,6 +203,8 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet
&i.IsActive,
&i.CreatedAt,
&i.UpdatedAt,
&i.BonusBalance,
&i.CashBalance,
); err != nil {
return nil, err
}

View File

@ -0,0 +1,65 @@
package domain
import (
"database/sql/driver"
"fmt"
"time"
)
type ReferralStatus string
const (
ReferralPending ReferralStatus = "PENDING"
ReferralCompleted ReferralStatus = "COMPLETED"
ReferralExpired ReferralStatus = "EXPIRED"
ReferralCancelled ReferralStatus = "CANCELLED"
)
func (rs *ReferralStatus) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*rs = ReferralStatus(s)
case string:
*rs = ReferralStatus(s)
default:
return fmt.Errorf("unsupported scan type for ReferralStatus: %T", src)
}
return nil
}
func (rs ReferralStatus) Value() (driver.Value, error) {
return string(rs), nil
}
type ReferralStats struct {
TotalReferrals int
CompletedReferrals int
TotalRewardEarned float64
PendingRewards float64
}
type ReferralSettings struct {
ID int64
ReferralRewardAmount float64
CashbackPercentage float64
BetReferralBonusPercentage float64
MaxReferrals int32
ExpiresAfterDays int32
UpdatedBy string
CreatedAt time.Time
UpdatedAt time.Time
Version int32
}
type Referral struct {
ID int64
ReferralCode string
ReferrerID string
ReferredID *string
Status ReferralStatus
RewardAmount float64
CashbackAmount float64
CreatedAt time.Time
UpdatedAt time.Time
ExpiresAt time.Time
}

View File

@ -35,8 +35,7 @@ type RegisterUserReq struct {
Password string
//Role string
Otp string
ReferalCode string
//
ReferralCode string `json:"referral_code"`
OtpMedium OtpMedium
}
type ResetPasswordReq struct {

View File

@ -0,0 +1,240 @@
package repository
import (
"context"
"database/sql"
"errors"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype"
)
type ReferralRepository interface {
CreateReferral(ctx context.Context, referral *domain.Referral) error
GetReferralByCode(ctx context.Context, code string) (*domain.Referral, error)
UpdateReferral(ctx context.Context, referral *domain.Referral) error
GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error)
GetSettings(ctx context.Context) (*domain.ReferralSettings, error)
UpdateSettings(ctx context.Context, settings *domain.ReferralSettings) error
CreateSettings(ctx context.Context, settings *domain.ReferralSettings) error
GetReferralByReferredID(ctx context.Context, referredID string) (*domain.Referral, error) // New method
}
type ReferralRepo struct {
store *Store
}
func NewReferralRepository(store *Store) ReferralRepository {
return &ReferralRepo{store: store}
}
func (r *ReferralRepo) CreateReferral(ctx context.Context, referral *domain.Referral) error {
rewardAmount := pgtype.Numeric{}
if err := rewardAmount.Scan(referral.RewardAmount); err != nil {
return err
}
params := dbgen.CreateReferralParams{
ReferralCode: referral.ReferralCode,
ReferrerID: referral.ReferrerID,
Status: dbgen.Referralstatus(referral.Status),
RewardAmount: rewardAmount,
ExpiresAt: pgtype.Timestamptz{Time: referral.ExpiresAt, Valid: true},
}
_, err := r.store.queries.CreateReferral(ctx, params)
return err
}
func (r *ReferralRepo) GetReferralByCode(ctx context.Context, code string) (*domain.Referral, error) {
dbReferral, err := r.store.queries.GetReferralByCode(ctx, code)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, err
}
return r.mapToDomainReferral(&dbReferral), nil
}
func (r *ReferralRepo) UpdateReferral(ctx context.Context, referral *domain.Referral) error {
var referredID pgtype.Text
if referral.ReferredID != nil {
referredID = pgtype.Text{String: *referral.ReferredID, Valid: true}
}
params := dbgen.UpdateReferralParams{
ID: referral.ID,
ReferredID: referredID,
Status: dbgen.Referralstatus(referral.Status),
}
_, err := r.store.queries.UpdateReferral(ctx, params)
return err
}
func (r *ReferralRepo) GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) {
stats, err := r.store.queries.GetReferralStats(ctx, userID)
if err != nil {
return nil, err
}
return &domain.ReferralStats{
TotalReferrals: int(stats.TotalReferrals),
CompletedReferrals: int(stats.CompletedReferrals),
TotalRewardEarned: float64(stats.TotalRewardEarned),
PendingRewards: float64(stats.PendingRewards),
}, nil
}
func (r *ReferralRepo) GetSettings(ctx context.Context) (*domain.ReferralSettings, error) {
settings, err := r.store.queries.GetReferralSettings(ctx)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, err
}
return r.mapToDomainSettings(&settings), nil
}
func (r *ReferralRepo) UpdateSettings(ctx context.Context, settings *domain.ReferralSettings) error {
rewardAmount := pgtype.Numeric{}
if err := rewardAmount.Scan(settings.ReferralRewardAmount); err != nil {
return err
}
cashbackPercentage := pgtype.Numeric{}
if err := cashbackPercentage.Scan(settings.CashbackPercentage); err != nil {
return err
}
betReferralBonusPercentage := pgtype.Numeric{}
if err := betReferralBonusPercentage.Scan(settings.BetReferralBonusPercentage); err != nil {
return err
}
params := dbgen.UpdateReferralSettingsParams{
ID: settings.ID,
ReferralRewardAmount: rewardAmount,
CashbackPercentage: cashbackPercentage,
BetReferralBonusPercentage: betReferralBonusPercentage, // New field
MaxReferrals: settings.MaxReferrals,
ExpiresAfterDays: settings.ExpiresAfterDays,
UpdatedBy: settings.UpdatedBy,
}
_, err := r.store.queries.UpdateReferralSettings(ctx, params)
return err
}
func (r *ReferralRepo) CreateSettings(ctx context.Context, settings *domain.ReferralSettings) error {
rewardAmount := pgtype.Numeric{}
if err := rewardAmount.Scan(settings.ReferralRewardAmount); err != nil {
return err
}
cashbackPercentage := pgtype.Numeric{}
if err := cashbackPercentage.Scan(settings.CashbackPercentage); err != nil {
return err
}
betReferralBonusPercentage := pgtype.Numeric{}
if err := betReferralBonusPercentage.Scan(settings.BetReferralBonusPercentage); err != nil {
return err
}
params := dbgen.CreateReferralSettingsParams{
ReferralRewardAmount: rewardAmount,
CashbackPercentage: cashbackPercentage,
BetReferralBonusPercentage: betReferralBonusPercentage, // New field
MaxReferrals: settings.MaxReferrals,
ExpiresAfterDays: settings.ExpiresAfterDays,
UpdatedBy: settings.UpdatedBy,
}
_, err := r.store.queries.CreateReferralSettings(ctx, params)
return err
}
func (r *ReferralRepo) GetReferralByReferredID(ctx context.Context, referredID string) (*domain.Referral, error) {
dbReferral, err := r.store.queries.GetReferralByReferredID(ctx, pgtype.Text{String: referredID, Valid: true})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, err
}
return r.mapToDomainReferral(&dbReferral), nil
}
func (r *ReferralRepo) mapToDomainReferral(dbRef *dbgen.Referral) *domain.Referral {
var referredID *string
if dbRef.ReferredID.Valid {
referredID = &dbRef.ReferredID.String
}
rewardAmount := 0.0
if dbRef.RewardAmount.Valid {
if f8, err := dbRef.RewardAmount.Float64Value(); err == nil {
rewardAmount = f8.Float64
}
}
cashbackAmount := 0.0
if dbRef.CashbackAmount.Valid {
if f8, err := dbRef.CashbackAmount.Float64Value(); err == nil {
cashbackAmount = f8.Float64
}
}
return &domain.Referral{
ID: dbRef.ID,
ReferralCode: dbRef.ReferralCode,
ReferrerID: dbRef.ReferrerID,
ReferredID: referredID,
Status: domain.ReferralStatus(dbRef.Status),
RewardAmount: rewardAmount,
CashbackAmount: cashbackAmount,
CreatedAt: dbRef.CreatedAt.Time,
UpdatedAt: dbRef.UpdatedAt.Time,
ExpiresAt: dbRef.ExpiresAt.Time,
}
}
func (r *ReferralRepo) mapToDomainSettings(dbSettings *dbgen.ReferralSetting) *domain.ReferralSettings {
rewardAmount := 0.0
if dbSettings.ReferralRewardAmount.Valid {
if f8, err := dbSettings.ReferralRewardAmount.Float64Value(); err == nil {
rewardAmount = f8.Float64
}
}
cashbackPercentage := 0.0
if dbSettings.CashbackPercentage.Valid {
if f8, err := dbSettings.CashbackPercentage.Float64Value(); err == nil {
cashbackPercentage = f8.Float64
}
}
betReferralBonusPercentage := 0.0
if dbSettings.BetReferralBonusPercentage.Valid {
if f8, err := dbSettings.BetReferralBonusPercentage.Float64Value(); err == nil {
betReferralBonusPercentage = f8.Float64
}
}
return &domain.ReferralSettings{
ID: dbSettings.ID,
ReferralRewardAmount: rewardAmount,
CashbackPercentage: cashbackPercentage,
BetReferralBonusPercentage: betReferralBonusPercentage, // New field
MaxReferrals: dbSettings.MaxReferrals,
ExpiresAfterDays: dbSettings.ExpiresAfterDays,
UpdatedBy: dbSettings.UpdatedBy,
CreatedAt: dbSettings.CreatedAt.Time,
UpdatedAt: dbSettings.UpdatedAt.Time,
Version: dbSettings.Version,
}
}

View File

@ -0,0 +1,18 @@
package referralservice
import (
"context"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
type ReferralStore interface {
GenerateReferralCode() (string, error)
CreateReferral(ctx context.Context, userID string) (*domain.Referral, error)
ProcessReferral(ctx context.Context, referredID, referralCode string) error
ProcessDepositBonus(ctx context.Context, userID string, amount float64) error
GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error)
UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error
GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error)
ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error
}

View File

@ -0,0 +1,214 @@
package referralservice
import (
"context"
"crypto/rand"
"encoding/base32"
"errors"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
)
type Service struct {
repo repository.ReferralRepository
walletSvc wallet.Service
store *repository.Store
}
func NewService(repo repository.ReferralRepository, walletSvc wallet.Service, store *repository.Store) *Service {
return &Service{
repo: repo,
walletSvc: walletSvc,
store: store,
}
}
var (
ErrInvalidReferral = errors.New("invalid or expired referral")
ErrInvalidReferralSignup = errors.New("referral requires phone signup")
ErrUserNotFound = errors.New("user not found")
ErrNoReferralFound = errors.New("no referral found for this user")
)
func (s *Service) GenerateReferralCode() (string, error) {
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base32.StdEncoding.EncodeToString(b)[:10], nil
}
func (s *Service) CreateReferral(ctx context.Context, userPhone string) (*domain.Referral, error) {
settings, err := s.repo.GetSettings(ctx)
if err != nil {
return nil, err
}
code, err := s.GenerateReferralCode()
if err != nil {
return nil, err
}
userID, err := strconv.ParseInt(userPhone, 10, 64)
if err != nil {
return nil, errors.New("invalid phone number format")
}
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
if err != nil {
return nil, err
}
if len(wallets) == 0 {
_, err = s.walletSvc.CreateWallet(ctx, domain.CreateWallet{
IsWithdraw: true,
IsBettable: true,
UserID: userID,
})
if err != nil {
return nil, err
}
}
referral := &domain.Referral{
ReferrerID: userPhone,
ReferralCode: code,
Status: domain.ReferralPending,
RewardAmount: settings.ReferralRewardAmount,
ExpiresAt: time.Now().Add(time.Duration(settings.ExpiresAfterDays) * 24 * time.Hour),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := s.repo.CreateReferral(ctx, referral); err != nil {
return nil, err
}
return referral, nil
}
func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCode string) error {
referral, err := s.repo.GetReferralByCode(ctx, referralCode)
if err != nil {
return err
}
if referral == nil || referral.Status != domain.ReferralPending || referral.ExpiresAt.Before(time.Now()) {
return ErrInvalidReferral
}
user, err := s.store.GetUserByPhone(ctx, referredPhone)
if err != nil {
if errors.Is(err, domain.ErrUserNotFound) {
return ErrUserNotFound
}
return err
}
if !user.PhoneVerified {
return ErrInvalidReferralSignup
}
referral.ReferredID = &referredPhone
referral.Status = domain.ReferralCompleted
referral.UpdatedAt = time.Now()
if err := s.repo.UpdateReferral(ctx, referral); err != nil {
return err
}
referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64)
if err != nil {
return errors.New("invalid referrer phone number format")
}
wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID)
if err != nil {
return err
}
if len(wallets) == 0 {
return errors.New("referrer has no wallet")
}
walletID := wallets[0].ID
currentBonus := float64(wallets[0].Balance)
return s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBonus+referral.RewardAmount)*100)))
}
func (s *Service) ProcessDepositBonus(ctx context.Context, userPhone string, amount float64) error {
settings, err := s.repo.GetSettings(ctx)
if err != nil {
return err
}
userID, err := strconv.ParseInt(userPhone, 10, 64)
if err != nil {
return errors.New("invalid phone number format")
}
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
if err != nil {
return err
}
if len(wallets) == 0 {
return errors.New("user has no wallet")
}
walletID := wallets[0].ID
bonus := amount * (settings.CashbackPercentage / 100)
currentBonus := float64(wallets[0].Balance)
return s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBonus+bonus)*100)))
}
func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error {
settings, err := s.repo.GetSettings(ctx)
if err != nil {
return err
}
referral, err := s.repo.GetReferralByReferredID(ctx, userPhone)
if err != nil {
return err
}
if referral == nil || referral.Status != domain.ReferralCompleted {
return ErrNoReferralFound
}
referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64)
if err != nil {
return errors.New("invalid referrer phone number format")
}
wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID)
if err != nil {
return err
}
if len(wallets) == 0 {
return errors.New("referrer has no wallet")
}
bonusPercentage := settings.BetReferralBonusPercentage
if bonusPercentage == 0 {
bonusPercentage = 5.0
}
bonus := betAmount * (bonusPercentage / 100)
walletID := wallets[0].ID
currentBalance := float64(wallets[0].Balance)
return s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBalance+bonus)*100)))
}
func (s *Service) GetReferralStats(ctx context.Context, userPhone string) (*domain.ReferralStats, error) {
return s.repo.GetReferralStats(ctx, userPhone)
}
func (s *Service) UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error {
settings.UpdatedAt = time.Now()
return s.repo.UpdateSettings(ctx, settings)
}
func (s *Service) GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error) {
return s.repo.GetSettings(ctx)
}

View File

@ -6,6 +6,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
@ -22,13 +23,14 @@ type App struct {
fiber *fiber.App
logger *slog.Logger
NotidicationStore notificationservice.NotificationStore
referralSvc referralservice.ReferralStore
port int
authSvc *authentication.Service
userSvc *user.Service
ticketSvc *ticket.Service
betSvc *bet.Service
walletSvc *wallet.Service
transactionSvc *transaction.Service
ticketSvc *ticket.Service
validator *customvalidator.CustomValidator
JwtConfig jwtutil.JwtConfig
Logger *slog.Logger
@ -45,6 +47,7 @@ func NewApp(
walletSvc *wallet.Service,
transactionSvc *transaction.Service,
notidicationStore notificationservice.NotificationStore,
referralSvc referralservice.ReferralStore,
) *App {
app := fiber.New(fiber.Config{
CaseSensitive: true,
@ -65,6 +68,7 @@ func NewApp(
walletSvc: walletSvc,
transactionSvc: transactionSvc,
NotidicationStore: notidicationStore,
referralSvc: referralSvc,
Logger: logger,
}

View File

@ -2,26 +2,13 @@ package handlers
import (
"errors"
"log/slog"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
"github.com/gofiber/fiber/v2"
)
type loginCustomerReq struct {
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
}
type loginCustomerRes struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
// LoginCustomer godoc
// @Summary Login customer
// @Description Login customer
@ -34,50 +21,51 @@ type loginCustomerRes struct {
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /auth/login [post]
func LoginCustomer(
logger *slog.Logger, authSvc *authentication.Service,
validator *customvalidator.CustomValidator, JwtConfig jwtutil.JwtConfig) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
type loginCustomerReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Password string `json:"password" validate:"required" example:"password123"`
}
type loginCustomerRes struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
var req loginCustomerReq
if err := c.BodyParser(&req); err != nil {
logger.Error("Login failed", "error", err)
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
h.logger.Error("Failed to parse LoginCustomer request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
successRes, err := authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
if err != nil {
logger.Info("Login failed", "error", err)
if errors.Is(err, authentication.ErrInvalidPassword) {
response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid password or not registered", nil, nil)
return nil
h.logger.Info("Login attempt failed", "email", req.Email, "phone", req.PhoneNumber, "error", err)
switch {
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials")
default:
h.logger.Error("Login failed", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
}
if errors.Is(err, authentication.ErrUserNotFound) {
response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid password or not registered", nil, nil)
return nil
}
logger.Error("Login failed", "error", err)
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
return nil
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
if err != nil {
h.logger.Error("Failed to create access token", "userID", successRes.UserId, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
}
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, JwtConfig.JwtAccessKey, JwtConfig.JwtAccessExpiry)
res := loginCustomerRes{
AccessToken: accessToken,
RefreshToken: successRes.RfToken,
}
return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil)
}
}
type refreshToken struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
// RefreshToken godoc
// @Summary Refresh token
@ -91,50 +79,52 @@ type refreshToken struct {
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /auth/refresh [post]
func RefreshToken(logger *slog.Logger, authSvc *authentication.Service,
validator *customvalidator.CustomValidator, JwtConfig jwtutil.JwtConfig) fiber.Handler {
return func(c *fiber.Ctx) error {
var req refreshToken
func (h *Handler) RefreshToken(c *fiber.Ctx) error {
type refreshTokenReq struct {
AccessToken string `json:"access_token" validate:"required" example:"<jwt-token>"`
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
}
type loginCustomerRes struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
var req refreshTokenReq
if err := c.BodyParser(&req); err != nil {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
h.logger.Error("Failed to parse RefreshToken request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
rf, err := authSvc.RefreshToken(c.Context(), req.RefreshToken)
rf, err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken)
if err != nil {
logger.Info("Refresh token failed", "error", err)
if errors.Is(err, authentication.ErrExpiredToken) {
response.WriteJSON(c, fiber.StatusUnauthorized, "The refresh token has expired", nil, nil)
return nil
h.logger.Info("Refresh token attempt failed", "refreshToken", req.RefreshToken, "error", err)
switch {
case errors.Is(err, authentication.ErrExpiredToken):
return fiber.NewError(fiber.StatusUnauthorized, "The refresh token has expired")
case errors.Is(err, authentication.ErrRefreshTokenNotFound):
return fiber.NewError(fiber.StatusUnauthorized, "Refresh token not found")
default:
h.logger.Error("Refresh token failed", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
}
if errors.Is(err, authentication.ErrRefreshTokenNotFound) {
response.WriteJSON(c, fiber.StatusUnauthorized, "Refresh token not found", nil, nil)
return nil
}
logger.Error("Refresh token failed", "error", err)
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
return nil
}
accessToken, err := jwtutil.CreateJwt(0, "", JwtConfig.JwtAccessKey, JwtConfig.JwtAccessExpiry)
// Assuming the refreshed token includes userID and role info; adjust if needed
accessToken, err := jwtutil.CreateJwt(0, "", h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
if err != nil {
logger.Error("Create jwt failed", "error", err)
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
return nil
h.logger.Error("Failed to create new access token", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
}
res := loginCustomerRes{
AccessToken: accessToken,
RefreshToken: rf,
}
return response.WriteJSON(c, fiber.StatusOK, "refresh successful", res, nil)
}
}
type logoutReq struct {
RefreshToken string `json:"refresh_token"`
return response.WriteJSON(c, fiber.StatusOK, "Refresh successful", res, nil)
}
// LogOutCustomer godoc
@ -149,34 +139,34 @@ type logoutReq struct {
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /auth/logout [post]
func LogOutCustomer(
logger *slog.Logger, authSvc *authentication.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) LogOutCustomer(c *fiber.Ctx) error {
type logoutReq struct {
RefreshToken string `json:"refresh_token" validate:"required" example:"<refresh-token>"`
}
var req logoutReq
if err := c.BodyParser(&req); err != nil {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
h.logger.Error("Failed to parse LogOutCustomer request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
err := authSvc.Logout(c.Context(), req.RefreshToken)
err := h.authSvc.Logout(c.Context(), req.RefreshToken)
if err != nil {
logger.Info("Logout failed", "error", err)
if errors.Is(err, authentication.ErrExpiredToken) {
response.WriteJSON(c, fiber.StatusUnauthorized, "The refresh token has expired", nil, nil)
return nil
h.logger.Info("Logout attempt failed", "refreshToken", req.RefreshToken, "error", err)
switch {
case errors.Is(err, authentication.ErrExpiredToken):
return fiber.NewError(fiber.StatusUnauthorized, "The refresh token has expired")
case errors.Is(err, authentication.ErrRefreshTokenNotFound):
return fiber.NewError(fiber.StatusUnauthorized, "Refresh token not found")
default:
h.logger.Error("Logout failed", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
}
if errors.Is(err, authentication.ErrRefreshTokenNotFound) {
response.WriteJSON(c, fiber.StatusUnauthorized, "Refresh token not found", nil, nil)
return nil
}
logger.Error("Logout failed", "error", err)
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
return nil
}
return response.WriteJSON(c, fiber.StatusOK, "Logout successful", nil, nil)
}
}

View File

@ -1,26 +1,13 @@
package handlers
import (
"log/slog"
"strconv"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
"github.com/gofiber/fiber/v2"
)
type CreateBetReq struct {
Outcomes []int64 `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
Status domain.BetStatus `json:"status" example:"1"`
FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
}
type BetRes struct {
ID int64 `json:"id" example:"1"`
Outcomes []domain.Outcome `json:"outcomes"`
@ -34,20 +21,6 @@ type BetRes struct {
IsShopBet bool `json:"is_shop_bet" example:"false"`
}
func convertBet(bet domain.Bet) BetRes {
return BetRes{
ID: bet.ID,
Outcomes: bet.Outcomes,
Amount: bet.Amount.Float64(),
TotalOdds: bet.TotalOdds,
Status: bet.Status,
FullName: bet.FullName,
PhoneNumber: bet.PhoneNumber,
BranchID: bet.BranchID.Value,
UserID: bet.UserID.Value,
}
}
// CreateBet godoc
// @Summary Create a bet
// @Description Creates a bet
@ -59,42 +32,41 @@ func convertBet(bet domain.Bet) BetRes {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /bet [post]
func CreateBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
// TODO: Check the token, and find the role and get the branch id from there
// TODO Reduce amount from the branch wallet
var isShopBet bool = true
var branchID int64 = 1
var userID int64
func (h *Handler) CreateBet(c *fiber.Ctx) error {
type CreateBetReq struct {
Outcomes []int64 `json:"outcomes" validate:"required" example:"[1, 2, 3]"`
Amount float32 `json:"amount" validate:"required" example:"100.0"`
TotalOdds float32 `json:"total_odds" validate:"required" example:"4.22"`
Status domain.BetStatus `json:"status" validate:"required" example:"1"`
FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" validate:"required" example:"1234567890"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
}
var req CreateBetReq
if err := c.BodyParser(&req); err != nil {
logger.Error("CreateBetReq failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse CreateBet request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
// TODO Validate Outcomes Here and make sure they didn't expire
// TODO: Check the token, find the role, and get the branch ID from there
isShopBet := true
branchID := int64(1)
var userID int64
bet, err := betSvc.CreateBet(c.Context(), domain.CreateBet{
// TODO: Validate Outcomes Here and make sure they didn't expire
bet, err := h.betSvc.CreateBet(c.Context(), domain.CreateBet{
Outcomes: req.Outcomes,
Amount: domain.Currency(req.Amount),
TotalOdds: req.TotalOdds,
Status: req.Status,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BranchID: domain.ValidInt64{
Value: branchID,
Valid: isShopBet,
@ -105,16 +77,22 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalida
},
IsShopBet: req.IsShopBet,
})
if err != nil {
logger.Error("CreateBetReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
h.logger.Error("Failed to create bet", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet")
}
// TODO: Reduce amount from the branch wallet (assuming walletSvc integration)
// This would typically be done here or in the bet service
if !req.IsShopBet && req.PhoneNumber != "" {
if err := h.referralSvc.ProcessBetReferral(c.Context(), req.PhoneNumber, float64(req.Amount)); err != nil {
h.logger.Warn("Failed to process bet referral", "phone", req.PhoneNumber, "amount", req.Amount, "error", err)
}
}
res := convertBet(bet)
return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Bet created successfully", res, nil)
}
// GetAllBet godoc
@ -127,22 +105,19 @@ func CreateBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalida
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /bet [get]
func GetAllBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
bets, err := betSvc.GetAllBets(c.Context())
func (h *Handler) GetAllBet(c *fiber.Ctx) error {
bets, err := h.betSvc.GetAllBets(c.Context())
if err != nil {
logger.Error("Failed to get bets", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bets", err, nil)
h.logger.Error("Failed to get bets", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets")
}
var res []BetRes = make([]BetRes, len(bets))
for _, bet := range bets {
res = append(res, convertBet(bet))
res := make([]BetRes, len(bets))
for i, bet := range bets {
res[i] = convertBet(bet)
}
return response.WriteJSON(c, fiber.StatusOK, "All Bets Retrieved", res, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil)
}
// GetBetByID godoc
@ -156,32 +131,35 @@ func GetAllBet(logger *slog.Logger, betSvc *bet.Service, validator *customvalida
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /bet/{id} [get]
func GetBetByID(logger *slog.Logger, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64)
if err != nil {
logger.Error("Invalid bet ID", "betID", betID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil)
func (h *Handler) GetBetByID(c *fiber.Ctx) error {
type BetRes struct {
ID int64 `json:"id" example:"1"`
Outcomes []domain.Outcome `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
Status domain.BetStatus `json:"status" example:"1"`
FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
BranchID int64 `json:"branch_id" example:"2"`
UserID int64 `json:"user_id" example:"2"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
}
bet, err := betSvc.GetBetByID(c.Context(), id)
betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64)
if err != nil {
logger.Error("Failed to get bet by ID", "betID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bet", err, nil)
h.logger.Error("Invalid bet ID", "betID", betID, "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
}
bet, err := h.betSvc.GetBetByID(c.Context(), id)
if err != nil {
h.logger.Error("Failed to get bet by ID", "betID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bet")
}
res := convertBet(bet)
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
}
}
type UpdateCashOutReq struct {
CashedOut bool
}
// UpdateCashOut godoc
@ -196,41 +174,36 @@ type UpdateCashOutReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /bet/{id} [patch]
func UpdateCashOut(logger *slog.Logger, betSvc *bet.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) UpdateCashOut(c *fiber.Ctx) error {
type UpdateCashOutReq struct {
CashedOut bool `json:"cashed_out" validate:"required" example:"true"`
}
betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64)
if err != nil {
logger.Error("Invalid bet ID", "betID", betID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil)
h.logger.Error("Invalid bet ID", "betID", betID, "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
}
var req UpdateCashOutReq
if err := c.BodyParser(&req); err != nil {
logger.Error("UpdateCashOutReq failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse UpdateCashOut request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
err = betSvc.UpdateCashOut(c.Context(), id, req.CashedOut)
err = h.betSvc.UpdateCashOut(c.Context(), id, req.CashedOut)
if err != nil {
logger.Error("Failed to update cash out bet", "betID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update cash out bet", err, nil)
h.logger.Error("Failed to update cash out bet", "betID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update cash out bet")
}
return response.WriteJSON(c, fiber.StatusOK, "Bet updated successfully", nil, nil)
}
}
// DeleteBet godoc
// @Summary Deletes bet by id
@ -243,24 +216,34 @@ func UpdateCashOut(logger *slog.Logger, betSvc *bet.Service,
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /bet/{id} [delete]
func DeleteBet(logger *slog.Logger, betSvc *bet.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) DeleteBet(c *fiber.Ctx) error {
betID := c.Params("id")
id, err := strconv.ParseInt(betID, 10, 64)
if err != nil {
logger.Error("Invalid bet ID", "betID", betID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil)
h.logger.Error("Invalid bet ID", "betID", betID, "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
}
err = betSvc.DeleteBet(c.Context(), id)
err = h.betSvc.DeleteBet(c.Context(), id)
if err != nil {
logger.Error("Failed to delete by ID", "betID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to delete bet", err, nil)
h.logger.Error("Failed to delete bet by ID", "betID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete bet")
}
return response.WriteJSON(c, fiber.StatusOK, "Bet removed successfully", nil, nil)
}
func convertBet(bet domain.Bet) BetRes {
return BetRes{
ID: bet.ID,
Outcomes: bet.Outcomes,
Amount: bet.Amount.Float64(),
TotalOdds: bet.TotalOdds,
Status: bet.Status,
FullName: bet.FullName,
PhoneNumber: bet.PhoneNumber,
BranchID: bet.BranchID.Value,
UserID: bet.UserID.Value,
IsShopBet: bet.IsShopBet,
}
}

View File

@ -3,20 +3,45 @@ package handlers
import (
"log/slog"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
)
type Handler struct {
logger *slog.Logger
notificationSvc notificationservice.NotificationStore
userSvc *user.Service
referralSvc referralservice.ReferralStore
walletSvc *wallet.Service
transactionSvc *transaction.Service
ticketSvc *ticket.Service
betSvc *bet.Service
authSvc *authentication.Service
jwtConfig jwtutil.JwtConfig
validator *customvalidator.CustomValidator
}
func New(logger *slog.Logger, notificationSvc notificationservice.NotificationStore, validator *customvalidator.CustomValidator) *Handler {
func New(logger *slog.Logger, notificationSvc notificationservice.NotificationStore, validator *customvalidator.CustomValidator, walletSvc *wallet.Service,
referralSvc referralservice.ReferralStore, userSvc *user.Service, transactionSvc *transaction.Service, ticketSvc *ticket.Service, betSvc *bet.Service, authSvc *authentication.Service, jwtConfig jwtutil.JwtConfig) *Handler {
return &Handler{
logger: logger,
notificationSvc: notificationSvc,
walletSvc: walletSvc,
referralSvc: referralSvc,
validator: validator,
userSvc: userSvc,
transactionSvc: transactionSvc,
ticketSvc: ticketSvc,
betSvc: betSvc,
authSvc: authSvc,
jwtConfig: jwtConfig,
}
}

View File

@ -0,0 +1,145 @@
package handlers
import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
)
func (h *Handler) CreateReferral(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.logger.Error("Failed to get user", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
}
referral, err := h.referralSvc.CreateReferral(c.Context(), user.PhoneNumber)
if err != nil {
h.logger.Error("Failed to create referral", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral")
}
return response.WriteJSON(c, fiber.StatusOK, "Referral created successfully", referral, nil)
}
// GetReferralStats godoc
// @Summary Get referral statistics
// @Description Retrieves referral statistics for the authenticated user
// @Tags referral
// @Accept json
// @Produce json
// @Success 200 {object} domain.ReferralStats
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /referral/stats [get]
func (h *Handler) GetReferralStats(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.logger.Error("Failed to get user", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
}
stats, err := h.referralSvc.GetReferralStats(c.Context(), user.PhoneNumber)
if err != nil {
h.logger.Error("Failed to get referral stats", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve referral stats")
}
return response.WriteJSON(c, fiber.StatusOK, "Referral stats retrieved successfully", stats, nil)
}
// UpdateReferralSettings godoc
// @Summary Update referral settings
// @Description Updates referral settings (admin only)
// @Tags referral
// @Accept json
// @Produce json
// @Param settings body domain.ReferralSettings true "Referral settings"
// @Success 200 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /referral/settings [put]
func (h *Handler) UpdateReferralSettings(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.logger.Error("Failed to get user", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
}
if user.Role != domain.RoleAdmin {
return fiber.NewError(fiber.StatusForbidden, "Admin access required")
}
var settings domain.ReferralSettings
if err := c.BodyParser(&settings); err != nil {
h.logger.Error("Failed to parse settings", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
settings.UpdatedBy = user.PhoneNumber
if err := h.referralSvc.UpdateReferralSettings(c.Context(), &settings); err != nil {
h.logger.Error("Failed to update referral settings", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update referral settings")
}
return response.WriteJSON(c, fiber.StatusOK, "Referral settings updated successfully", nil, nil)
}
// GetReferralSettings godoc
// @Summary Get referral settings
// @Description Retrieves current referral settings (admin only)
// @Tags referral
// @Accept json
// @Produce json
// @Success 200 {object} domain.ReferralSettings
// @Failure 401 {object} response.APIResponse
// @Failure 403 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /referral/settings [get]
func (h *Handler) GetReferralSettings(c *fiber.Ctx) error {
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.logger.Error("Failed to get user", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user")
}
if user.Role != domain.RoleAdmin {
return fiber.NewError(fiber.StatusForbidden, "Admin access required")
}
settings, err := h.referralSvc.GetReferralSettings(c.Context())
if err != nil {
h.logger.Error("Failed to get referral settings", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve referral settings")
}
return response.WriteJSON(c, fiber.StatusOK, "Referral settings retrieved successfully", settings, nil)
}

View File

@ -1,25 +1,13 @@
package handlers
import (
"log/slog"
"strconv"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
"github.com/gofiber/fiber/v2"
)
type CreateTicketReq struct {
Outcomes []int64 `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
}
type CreateTicketRes struct {
FastCode int64 `json:"fast_code" example:"1234"`
}
// CreateTicket godoc
// @Summary Create a temporary ticket
// @Description Creates a temporary ticket
@ -31,48 +19,43 @@ type CreateTicketRes struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /ticket [post]
func CreateTicket(logger *slog.Logger, ticketSvc *ticket.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) CreateTicket(c *fiber.Ctx) error {
type CreateTicketReq struct {
Outcomes []int64 `json:"outcomes" validate:"required" example:"[1, 2, 3]"`
Amount float32 `json:"amount" validate:"required" example:"100.0"`
TotalOdds float32 `json:"total_odds" validate:"required" example:"4.22"`
}
type CreateTicketRes struct {
FastCode int64 `json:"fast_code" example:"1234"`
}
var req CreateTicketReq
if err := c.BodyParser(&req); err != nil {
logger.Error("CreateTicketReq failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse CreateTicket request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
// TODO Validate Outcomes Here and make sure they didn't expire
// TODO: Validate Outcomes Here and make sure they didn't expire
ticket, err := ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{
ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{
Outcomes: req.Outcomes,
Amount: domain.Currency(req.Amount),
TotalOdds: req.TotalOdds,
})
if err != nil {
logger.Error("CreateTicketReq failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Internal server error",
})
h.logger.Error("Failed to create ticket", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create ticket")
}
res := CreateTicketRes{
FastCode: ticket.ID,
}
return response.WriteJSON(c, fiber.StatusOK, "Ticket Created", res, nil)
}
}
type TicketRes struct {
ID int64 `json:"id" example:"1"`
Outcomes []domain.Outcome `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
return response.WriteJSON(c, fiber.StatusOK, "Ticket created successfully", res, nil)
}
// GetTicketByID godoc
@ -86,22 +69,25 @@ type TicketRes struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /ticket/{id} [get]
func GetTicketByID(logger *slog.Logger, ticketSvc *ticket.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
ticketID := c.Params("id")
id, err := strconv.ParseInt(ticketID, 10, 64)
if err != nil {
logger.Error("Invalid ticket ID", "ticketID", ticketID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid ticket ID", err, nil)
func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
type TicketRes struct {
ID int64 `json:"id" example:"1"`
Outcomes []domain.Outcome `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
}
ticket, err := ticketSvc.GetTicketByID(c.Context(), id)
ticketID := c.Params("id")
id, err := strconv.ParseInt(ticketID, 10, 64)
if err != nil {
logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve ticket", err, nil)
h.logger.Error("Invalid ticket ID", "ticketID", ticketID, "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid ticket ID")
}
ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id)
if err != nil {
h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve ticket")
}
res := TicketRes{
@ -110,10 +96,7 @@ func GetTicketByID(logger *slog.Logger, ticketSvc *ticket.Service,
Amount: ticket.Amount.Float64(),
TotalOdds: ticket.TotalOdds,
}
return response.WriteJSON(c, fiber.StatusOK, "Ticket retrieved successfully", res, nil)
}
}
// GetAllTickets godoc
@ -126,28 +109,29 @@ func GetTicketByID(logger *slog.Logger, ticketSvc *ticket.Service,
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /ticket [get]
func GetAllTickets(logger *slog.Logger, ticketSvc *ticket.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
tickets, err := ticketSvc.GetAllTickets(c.Context())
if err != nil {
logger.Error("Failed to get tickets", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve tickets", err, nil)
func (h *Handler) GetAllTickets(c *fiber.Ctx) error {
type TicketRes struct {
ID int64 `json:"id" example:"1"`
Outcomes []domain.Outcome `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
}
var res []TicketRes = make([]TicketRes, len(tickets))
tickets, err := h.ticketSvc.GetAllTickets(c.Context())
if err != nil {
h.logger.Error("Failed to get tickets", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve tickets")
}
for _, ticket := range tickets {
res = append(res, TicketRes{
res := make([]TicketRes, len(tickets))
for i, ticket := range tickets {
res[i] = TicketRes{
ID: ticket.ID,
Outcomes: ticket.Outcomes,
Amount: ticket.Amount.Float64(),
TotalOdds: ticket.TotalOdds,
})
}
}
return response.WriteJSON(c, fiber.StatusOK, "All Tickets retrieved", res, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "All tickets retrieved successfully", res, nil)
}

View File

@ -1,13 +1,10 @@
package handlers
import (
"log/slog"
"strconv"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
"github.com/gofiber/fiber/v2"
)
@ -75,28 +72,41 @@ func convertTransaction(transaction domain.Transaction) TransactionRes {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /transaction [post]
func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
// Update transaction handler to include deposit bonus
func (h *Handler) CreateTransaction(c *fiber.Ctx) error {
type CreateTransactionReq struct {
Amount float32 `json:"amount" validate:"required" example:"100.0"`
BranchID int64 `json:"branch_id" example:"1"`
CashierID int64 `json:"cashier_id" example:"1"`
BetID int64 `json:"bet_id" example:"1"`
PaymentOption domain.PaymentOption `json:"payment_option" validate:"required" example:"1"`
FullName string `json:"full_name" example:"John Smith"`
PhoneNumber string `json:"phone_number" validate:"required" example:"0911111111"`
BankCode string `json:"bank_code"`
BeneficiaryName string `json:"beneficiary_name"`
AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"`
}
var req CreateTransactionReq
if err := c.BodyParser(&req); err != nil {
logger.Error("CreateTransactionReq failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("CreateTransaction failed to parse request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
transaction, err := transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{
isDeposit := req.PaymentOption == domain.BANK
transaction, err := h.transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{
Amount: domain.Currency(req.Amount),
BranchID: req.BranchID,
CashierID: req.CashierID,
BetID: req.BetID,
PaymentOption: domain.PaymentOption(req.PaymentOption),
PaymentOption: req.PaymentOption,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BankCode: req.BankCode,
@ -105,16 +115,19 @@ func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service,
AccountNumber: req.AccountNumber,
ReferenceNumber: req.ReferenceNumber,
})
if err != nil {
logger.Error("CreateTransactionReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
h.logger.Error("CreateTransaction failed", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create transaction")
}
if isDeposit {
if err := h.referralSvc.ProcessDepositBonus(c.Context(), req.PhoneNumber, float64(req.Amount)); err != nil {
h.logger.Warn("Failed to process deposit bonus", "phone", req.PhoneNumber, "amount", req.Amount, "error", err)
}
}
res := convertTransaction(transaction)
return response.WriteJSON(c, fiber.StatusOK, "Transaction Created", res, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil)
}
// GetAllTransactions godoc
@ -127,22 +140,19 @@ func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service,
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /transaction [get]
func GetAllTransactions(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
transactions, err := transactionSvc.GetAllTransactions(c.Context())
func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
transactions, err := h.transactionSvc.GetAllTransactions(c.Context())
if err != nil {
logger.Error("Failed to get transaction", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transaction", err, nil)
h.logger.Error("Failed to get transactions", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transactions")
}
var res []TransactionRes = make([]TransactionRes, len(transactions))
for _, transaction := range transactions {
res = append(res, convertTransaction(transaction))
res := make([]TransactionRes, len(transactions))
for i, transaction := range transactions {
res[i] = convertTransaction(transaction)
}
return response.WriteJSON(c, fiber.StatusOK, "All Transactions Retrieved", res, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "All transactions retrieved", res, nil)
}
// GetTransactionByID godoc
@ -156,77 +166,63 @@ func GetAllTransactions(logger *slog.Logger, transactionSvc *transaction.Service
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /transaction/{id} [get]
func GetTransactionByID(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
transactionID := c.Params("id")
id, err := strconv.ParseInt(transactionID, 10, 64)
if err != nil {
logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil)
h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID")
}
transaction, err := transactionSvc.GetTransactionByID(c.Context(), id)
transaction, err := h.transactionSvc.GetTransactionByID(c.Context(), id)
if err != nil {
logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transaction", err, nil)
h.logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transaction")
}
res := convertTransaction(transaction)
return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil)
}
}
type UpdateTransactionVerifiedReq struct {
Verified bool
}
// UpdateTransactionVerified godoc
// @Summary Updates the cashed out field
// @Description Updates the cashed out field
// @Summary Updates the verified field of a transaction
// @Description Updates the verified status of a transaction
// @Tags transaction
// @Accept json
// @Produce json
// @Param id path int true "Transaction ID"
// @Param updateCashOut body UpdateTransactionVerifiedReq true "Updates Transaction Verification"
// @Param updateVerified body UpdateTransactionVerifiedReq true "Updates Transaction Verification"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /transaction/{id} [patch]
func UpdateTransactionVerified(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error {
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" validate:"required" example:"true"`
}
transactionID := c.Params("id")
id, err := strconv.ParseInt(transactionID, 10, 64)
if err != nil {
logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil)
h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID")
}
var req UpdateTransactionVerifiedReq
if err := c.BodyParser(&req); err != nil {
logger.Error("UpdateTransactionVerifiedReq failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
err = transactionSvc.UpdateTransactionVerified(c.Context(), id, req.Verified)
err = h.transactionSvc.UpdateTransactionVerified(c.Context(), id, req.Verified)
if err != nil {
logger.Error("Failed to update transaction verification", "transactionID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update transaction verification", err, nil)
h.logger.Error("Failed to update transaction verification", "transactionID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transaction verification")
}
return response.WriteJSON(c, fiber.StatusOK, "Transaction updated successfully", nil, nil)
}
}

View File

@ -2,27 +2,14 @@ package handlers
import (
"errors"
"log/slog"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
"github.com/gofiber/fiber/v2"
)
type CheckPhoneEmailExistReq struct {
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
}
type CheckPhoneEmailExistRes struct {
EmailExist bool `json:"email_exist"`
PhoneNumberExist bool `json:"phone_number_exist"`
}
// CheckPhoneEmailExist godoc
// @Summary Check if phone number or email exist
// @Description Check if phone number or email exist
@ -34,39 +21,37 @@ type CheckPhoneEmailExistRes struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/checkPhoneEmailExist [post]
func CheckPhoneEmailExist(logger *slog.Logger, userSvc *user.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
type CheckPhoneEmailExistReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required" example:"1234567890"`
}
type CheckPhoneEmailExistRes struct {
EmailExist bool `json:"email_exist"`
PhoneNumberExist bool `json:"phone_number_exist"`
}
var req CheckPhoneEmailExistReq
if err := c.BodyParser(&req); err != nil {
logger.Error("CheckPhoneEmailExist failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse CheckPhoneEmailExist request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
emailExist, phoneExist, err := userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email)
emailExist, phoneExist, err := h.userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email)
if err != nil {
logger.Error("CheckPhoneEmailExist failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Internal server error",
})
h.logger.Error("Failed to check phone/email existence", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check phone/email existence")
}
res := CheckPhoneEmailExistRes{
EmailExist: emailExist,
PhoneNumberExist: phoneExist,
}
return response.WriteJSON(c, fiber.StatusOK, "Check Success", res, nil)
}
}
type RegisterCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
return response.WriteJSON(c, fiber.StatusOK, "Check successful", res, nil)
}
// SendRegisterCode godoc
@ -80,52 +65,41 @@ type RegisterCodeReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/sendRegisterCode [post]
func SendRegisterCode(logger *slog.Logger, userSvc *user.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
type RegisterCodeReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
}
var req RegisterCodeReq
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse SendRegisterCode request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
var sentTo string
var medium domain.OtpMedium
if req.Email != "" {
sentTo = req.Email
medium = domain.OtpMediumEmail
}
if req.PhoneNumber != "" {
} else if req.PhoneNumber != "" {
sentTo = req.PhoneNumber
medium = domain.OtpMediumSms
} else {
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
}
if err := userSvc.SendRegisterCode(c.Context(), medium, sentTo); err != nil {
logger.Error("SendRegisterCode failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Internal server error",
})
if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo); err != nil {
h.logger.Error("Failed to send register code", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code")
}
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
}
}
type RegisterUserReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
//Role string
Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"`
//
}
// RegisterUser godoc
// @Summary Register user
@ -138,20 +112,25 @@ type RegisterUserReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/register [post]
func RegisterUser(logger *slog.Logger, userSvc *user.Service, walletSvc *wallet.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) RegisterUser(c *fiber.Ctx) error {
type RegisterUserReq struct {
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
Password string `json:"password" example:"password123"`
Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"`
}
var req RegisterUserReq
if err := c.BodyParser(&req); err != nil {
logger.Error("RegisterUser failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse RegisterUser request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
user := domain.RegisterUserReq{
FirstName: req.FirstName,
@ -160,18 +139,20 @@ func RegisterUser(logger *slog.Logger, userSvc *user.Service, walletSvc *wallet.
PhoneNumber: req.PhoneNumber,
Password: req.Password,
Otp: req.Otp,
ReferalCode: req.ReferalCode,
ReferralCode: req.ReferalCode,
OtpMedium: domain.OtpMediumEmail,
}
medium, err := getMedium(req.Email, req.PhoneNumber)
if err != nil {
logger.Error("RegisterUser failed", "error", err)
h.logger.Error("RegisterUser failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
user.OtpMedium = medium
newUser, err := userSvc.RegisterUser(c.Context(), user)
newUser, err := h.userSvc.RegisterUser(c.Context(), user)
if err != nil {
if errors.Is(err, domain.ErrOtpAlreadyUsed) {
return response.WriteJSON(c, fiber.StatusBadRequest, "Otp already used", nil, nil)
@ -185,28 +166,31 @@ func RegisterUser(logger *slog.Logger, userSvc *user.Service, walletSvc *wallet.
if errors.Is(err, domain.ErrOtpNotFound) {
return response.WriteJSON(c, fiber.StatusBadRequest, "User already exist", nil, nil)
}
logger.Error("RegisterUser failed", "error", err)
h.logger.Error("RegisterUser failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Internal server error",
})
}
// TODO: Integrate company when we move to multi-vendor system
_, err = walletSvc.CreateCustomerWallet(c.Context(), newUser.ID, 0)
_, err = h.walletSvc.CreateWallet(c.Context(), domain.CreateWallet{
UserID: newUser.ID,
IsWithdraw: true,
IsBettable: true,
})
if err != nil {
logger.Error("CreateCustomerWallet failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create customer wallet for user", err, nil)
h.logger.Error("Failed to create wallet for user", "userID", newUser.ID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet")
}
if req.ReferalCode != "" {
err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode)
if err != nil {
h.logger.Warn("Failed to process referral during registration", "phone", req.PhoneNumber, "code", req.ReferalCode, "error", err)
}
}
return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil)
}
}
type ResetCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"`
}
// SendResetCode godoc
// @Summary Send reset code
@ -219,46 +203,40 @@ type ResetCodeReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/sendResetCode [post]
func SendResetCode(logger *slog.Logger, userSvc *user.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) SendResetCode(c *fiber.Ctx) error {
type ResetCodeReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
}
var req ResetCodeReq
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse SendResetCode request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
var sentTo string
var medium domain.OtpMedium
if req.Email != "" {
sentTo = req.Email
medium = domain.OtpMediumEmail
}
if req.PhoneNumber != "" {
} else if req.PhoneNumber != "" {
sentTo = req.PhoneNumber
medium = domain.OtpMediumSms
} else {
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
}
if err := userSvc.SendResetCode(c.Context(), medium, sentTo); err != nil {
logger.Error("SendResetCode failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Internal server error",
})
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo); err != nil {
h.logger.Error("Failed to send reset code", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code")
}
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
}
}
type ResetPasswordReq struct {
Email string
PhoneNumber string
Password string
Otp string
}
// ResetPassword godoc
@ -272,45 +250,58 @@ type ResetPasswordReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/resetPassword [post]
func ResetPassword(logger *slog.Logger, userSvc *user.Service,
validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) ResetPassword(c *fiber.Ctx) error {
type ResetPasswordReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Password string `json:"password" validate:"required,min=8" example:"newpassword123"`
Otp string `json:"otp" validate:"required" example:"123456"`
}
var req ResetPasswordReq
if err := c.BodyParser(&req); err != nil {
logger.Error("ResetPassword failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse ResetPassword request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
valErrs, ok := validator.Validate(c, req)
if !ok {
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
return nil
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
user := domain.ResetPasswordReq{
medium, err := getMedium(req.Email, req.PhoneNumber)
if err != nil {
h.logger.Error("Failed to determine medium for ResetPassword", "error", err)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
resetReq := domain.ResetPasswordReq{
Email: req.Email,
PhoneNumber: req.PhoneNumber,
Password: req.Password,
Otp: req.Otp,
}
medium, err := getMedium(req.Email, req.PhoneNumber)
if err != nil {
logger.Error("ResetPassword failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
user.OtpMedium = medium
if err := userSvc.ResetPassword(c.Context(), user); err != nil {
logger.Error("ResetPassword failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Internal server error",
})
}
return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil)
}
OtpMedium: medium,
}
if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil {
h.logger.Error("Failed to reset password", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset password")
}
return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil)
}
// UserProfile godoc
// @Summary Get user profile
// @Description Get user profile
// @Tags user
// @Accept json
// @Produce json
// @Success 200 {object} UserProfileRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /user/profile [get]
func (h *Handler) UserProfile(c *fiber.Ctx) error {
type UserProfileRes struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
@ -326,26 +317,16 @@ type UserProfileRes struct {
Suspended bool `json:"suspended"`
}
// UserProfile godoc
// @Summary Get user profile
// @Description Get user profile
// @Tags user
// @Accept json
// @Produce json
// @Success 200 {object} UserProfileRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security Bearer
// @Router /user/profile [get]
func UserProfile(logger *slog.Logger, userSvc *user.Service) fiber.Handler {
return func(c *fiber.Ctx) error {
userId := c.Locals("user_id").(int64)
user, err := userSvc.GetUserByID(c.Context(), userId)
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
logger.Error("GetUserProfile failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Internal server error",
})
h.logger.Error("Failed to get user profile", "userID", userID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile")
}
res := UserProfileRes{
@ -364,7 +345,8 @@ func UserProfile(logger *slog.Logger, userSvc *user.Service) fiber.Handler {
}
return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
}
}
// Helper function (unchanged)
func getMedium(email, phoneNumber string) (domain.OtpMedium, error) {
if email != "" {
return domain.OtpMediumEmail, nil

View File

@ -1,28 +1,14 @@
package handlers
import (
"log/slog"
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
"github.com/gofiber/fiber/v2"
)
type WalletRes struct {
ID int64 `json:"id" example:"1"`
Balance float32 `json:"amount" example:"100.0"`
IsWithdraw bool `json:"is_withdraw" example:"true"`
IsBettable bool `json:"is_bettable" example:"true"`
IsActive bool `json:"is_active" example:"true"`
UserID int64 `json:"user_id" example:"1"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}
// GetWalletByID godoc
// @Summary Get wallet by ID
// @Description Retrieve wallet details by wallet ID
@ -34,21 +20,29 @@ type WalletRes struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /wallet/{id} [get]
func GetWalletByID(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64)
if err != nil {
logger.Error("Invalid wallet ID", "walletID", walletID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil)
func (h *Handler) GetWalletByID(c *fiber.Ctx) error {
type WalletRes struct {
ID int64 `json:"id" example:"1"`
Balance float32 `json:"amount" example:"100.0"`
IsWithdraw bool `json:"is_withdraw" example:"true"`
IsBettable bool `json:"is_bettable" example:"true"`
IsActive bool `json:"is_active" example:"true"`
UserID int64 `json:"user_id" example:"1"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}
wallet, err := walletSvc.GetWalletByID(c.Context(), id)
walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64)
if err != nil {
logger.Error("Failed to get wallet by ID", "walletID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallet", err, nil)
h.logger.Error("Invalid wallet ID", "walletID", walletID, "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID")
}
wallet, err := h.walletSvc.GetWalletByID(c.Context(), id)
if err != nil {
h.logger.Error("Failed to get wallet by ID", "walletID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet")
}
res := WalletRes{
@ -64,7 +58,6 @@ func GetWalletByID(logger *slog.Logger, walletSvc *wallet.Service, validator *cu
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
}
}
// GetAllWallets godoc
// @Summary Get all wallets
@ -76,18 +69,27 @@ func GetWalletByID(logger *slog.Logger, walletSvc *wallet.Service, validator *cu
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /wallet [get]
func GetAllWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
wallets, err := walletSvc.GetAllWallets(c.Context())
if err != nil {
logger.Error("Failed to get wallets", "error", err)
func (h *Handler) GetAllWallets(c *fiber.Ctx) error {
type WalletRes struct {
ID int64 `json:"id" example:"1"`
Balance float32 `json:"amount" example:"100.0"`
IsWithdraw bool `json:"is_withdraw" example:"true"`
IsBettable bool `json:"is_bettable" example:"true"`
IsActive bool `json:"is_active" example:"true"`
UserID int64 `json:"user_id" example:"1"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}
var res []WalletRes = make([]WalletRes, len(wallets))
wallets, err := h.walletSvc.GetAllWallets(c.Context())
if err != nil {
h.logger.Error("Failed to get wallets", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallets")
}
for _, wallet := range wallets {
res = append(res, WalletRes{
res := make([]WalletRes, len(wallets))
for i, wallet := range wallets {
res[i] = WalletRes{
ID: wallet.ID,
Balance: wallet.Balance.Float64(),
IsWithdraw: wallet.IsWithdraw,
@ -96,15 +98,10 @@ func GetAllWallets(logger *slog.Logger, walletSvc *wallet.Service, validator *cu
UserID: wallet.UserID,
UpdatedAt: wallet.UpdatedAt,
CreatedAt: wallet.CreatedAt,
})
}
return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil)
}
}
type UpdateWalletActiveReq struct {
IsActive bool
return response.WriteJSON(c, fiber.StatusOK, "All wallets retrieved successfully", res, nil)
}
// UpdateWalletActive godoc
@ -119,47 +116,36 @@ type UpdateWalletActiveReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /wallet/{id} [patch]
func UpdateWalletActive(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error {
type UpdateWalletActiveReq struct {
IsActive bool `json:"is_active" validate:"required" example:"true"`
}
walletID := c.Params("id")
id, err := strconv.ParseInt(walletID, 10, 64)
if err != nil {
logger.Error("Invalid bet ID", "walletID", walletID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil)
h.logger.Error("Invalid wallet ID", "walletID", walletID, "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID")
}
var req UpdateWalletActiveReq
if err := c.BodyParser(&req); err != nil {
logger.Error("UpdateWalletActiveReq failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request",
})
h.logger.Error("Failed to parse UpdateWalletActive request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
err = walletSvc.UpdateWalletActive(c.Context(), id, req.IsActive)
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
err = h.walletSvc.UpdateWalletActive(c.Context(), id, req.IsActive)
if err != nil {
logger.Error("Failed to update", "walletID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update wallet", err, nil)
h.logger.Error("Failed to update wallet active status", "walletID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update wallet")
}
return response.WriteJSON(c, fiber.StatusOK, "Wallet successfully updated", nil, nil)
}
}
type CustomerWalletRes struct {
ID int64 `json:"id" example:"1"`
RegularID int64 `json:"regular_id" example:"1"`
RegularBalance float32 `json:"regular_balance" example:"100.0"`
StaticID int64 `json:"static_id" example:"1"`
StaticBalance float32 `json:"static_balance" example:"100.0"`
CustomerID int64 `json:"customer_id" example:"1"`
CompanyID int64 `json:"company_id" example:"1"`
RegularUpdatedAt time.Time `json:"regular_updated_at"`
StaticUpdatedAt time.Time `json:"static_updated_at"`
CreatedAt time.Time `json:"created_at"`
}
// GetCustomerWallet godoc
// @Summary Get customer wallet
@ -173,27 +159,51 @@ type CustomerWalletRes struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /user/wallet [get]
func GetCustomerWallet(logger *slog.Logger, walletSvc *wallet.Service, validator *customvalidator.CustomValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
type CustomerWalletRes struct {
ID int64 `json:"id" example:"1"`
RegularID int64 `json:"regular_id" example:"1"`
RegularBalance float32 `json:"regular_balance" example:"100.0"`
StaticID int64 `json:"static_id" example:"1"`
StaticBalance float32 `json:"static_balance" example:"100.0"`
CustomerID int64 `json:"customer_id" example:"1"`
CompanyID int64 `json:"company_id" example:"1"`
RegularUpdatedAt time.Time `json:"regular_updated_at"`
StaticUpdatedAt time.Time `json:"static_updated_at"`
CreatedAt time.Time `json:"created_at"`
}
userId := c.Locals("user_id").(int64)
role := string(c.Locals("role").(domain.Role))
userID, ok := c.Locals("user_id").(int64)
if !ok || userID == 0 {
h.logger.Error("Invalid user ID in context")
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
}
role, ok := c.Locals("role").(domain.Role)
if !ok {
h.logger.Error("Invalid role in context", "userID", userID)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid role")
}
if role != domain.RoleCustomer {
h.logger.Error("Unauthorized access", "userID", userID, "role", role)
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized access")
}
companyID, err := strconv.ParseInt(c.Get("company_id"), 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid company_id")
h.logger.Error("Invalid company_id header", "value", c.Get("company_id"), "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid company_id")
}
logger.Info("Company ID: " + strconv.FormatInt(companyID, 10))
if role != string(domain.RoleCustomer) {
logger.Error("Unauthorized access", "userId", userId, "role", role)
return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil)
}
wallet, err := walletSvc.GetCustomerWallet(c.Context(), userId, companyID)
h.logger.Info("Fetching customer wallet", "userID", userID, "companyID", companyID)
wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID, companyID)
if err != nil {
logger.Error("Failed to get customer wallet", "userId", userId, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallet", err, nil)
h.logger.Error("Failed to get customer wallet", "userID", userID, "companyID", companyID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve wallet")
}
res := CustomerWalletRes{
ID: wallet.ID,
RegularID: wallet.RegularID,
@ -206,7 +216,6 @@ func GetCustomerWallet(logger *slog.Logger, walletSvc *wallet.Service, validator
StaticUpdatedAt: wallet.StaticUpdatedAt,
CreatedAt: wallet.CreatedAt,
}
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
}

View File

@ -12,68 +12,97 @@ import (
)
func (a *App) initAppRoutes() {
handler := handlers.New(a.logger, a.NotidicationStore, a.validator)
h := handlers.New(
a.logger,
a.NotidicationStore,
a.validator,
a.walletSvc,
a.referralSvc,
a.userSvc,
a.transactionSvc,
a.ticketSvc,
a.betSvc,
a.authSvc,
a.JwtConfig,
)
a.fiber.Post("/auth/login", handlers.LoginCustomer(a.logger, a.authSvc, a.validator, a.JwtConfig))
a.fiber.Post("/auth/refresh", a.authMiddleware, handlers.RefreshToken(a.logger, a.authSvc, a.validator, a.JwtConfig))
a.fiber.Post("/auth/logout", a.authMiddleware, handlers.LogOutCustomer(a.logger, a.authSvc, a.validator))
// Auth Routes
a.fiber.Post("/auth/login", h.LoginCustomer)
a.fiber.Post("/auth/refresh", a.authMiddleware, h.RefreshToken)
a.fiber.Post("/auth/logout", a.authMiddleware, h.LogOutCustomer)
a.fiber.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
userId := c.Locals("user_id").(int64)
role := string(c.Locals("role").(domain.Role))
refreshToken := (c.Locals("refresh_token").(string))
userID, ok := c.Locals("user_id").(int64)
if !ok {
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user ID")
}
role, ok := c.Locals("role").(domain.Role)
if !ok {
return fiber.NewError(fiber.StatusUnauthorized, "Invalid role")
}
refreshToken, ok := c.Locals("refresh_token").(string)
if !ok {
return fiber.NewError(fiber.StatusUnauthorized, "Invalid refresh token")
}
companyID, err := strconv.ParseInt(c.Get("company_id"), 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid company_id")
return fiber.NewError(fiber.StatusBadRequest, "Invalid company_id")
}
// a.logger.Info("User ID: " + string(userId.(string))) //panic: interface conversion: interface {} is int64, not string
a.logger.Info("User ID: " + strconv.FormatInt(userId, 10))
fmt.Printf("User ID: %d\n", userId)
a.logger.Info("Role: " + role)
a.logger.Info("User ID: " + strconv.FormatInt(userID, 10))
fmt.Printf("User ID: %d\n", userID)
a.logger.Info("Role: " + string(role))
a.logger.Info("Refresh Token: " + refreshToken)
a.logger.Info("Company ID: " + strconv.FormatInt(companyID, 10))
return c.SendString("Test endpoint")
})
a.fiber.Post("/user/resetPassword", handlers.ResetPassword(a.logger, a.userSvc, a.validator))
a.fiber.Post("/user/sendResetCode", handlers.SendResetCode(a.logger, a.userSvc, a.validator))
a.fiber.Post("/user/register", handlers.RegisterUser(a.logger, a.userSvc, a.walletSvc, a.validator))
a.fiber.Post("/user/sendRegisterCode", handlers.SendRegisterCode(a.logger, a.userSvc, a.validator))
a.fiber.Post("/user/checkPhoneEmailExist", handlers.CheckPhoneEmailExist(a.logger, a.userSvc, a.validator))
a.fiber.Get("/user/profile", a.authMiddleware, handlers.UserProfile(a.logger, a.userSvc))
a.fiber.Get("/user/wallet", a.authMiddleware, handlers.GetCustomerWallet(a.logger, a.walletSvc, a.validator))
// User Routes
a.fiber.Post("/user/resetPassword", h.ResetPassword)
a.fiber.Post("/user/sendResetCode", h.SendResetCode)
a.fiber.Post("/user/register", h.RegisterUser)
a.fiber.Post("/user/sendRegisterCode", h.SendRegisterCode)
a.fiber.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist)
a.fiber.Get("/user/profile", a.authMiddleware, h.UserProfile)
// Wallet Routes
a.fiber.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet)
a.fiber.Get("/wallet", h.GetAllWallets)
a.fiber.Get("/wallet/:id", h.GetWalletByID)
a.fiber.Patch("/wallet/:id", h.UpdateWalletActive)
// Referral Routes
a.fiber.Post("/referral/create", a.authMiddleware, h.CreateReferral)
a.fiber.Get("/referral/stats", a.authMiddleware, h.GetReferralStats)
a.fiber.Get("/referral/settings", h.GetReferralSettings)
a.fiber.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings)
// Swagger
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
// Ticket
a.fiber.Post("/ticket", handlers.CreateTicket(a.logger, a.ticketSvc, a.validator))
a.fiber.Get("/ticket", handlers.GetAllTickets(a.logger, a.ticketSvc, a.validator))
a.fiber.Get("/ticket/:id", handlers.GetTicketByID(a.logger, a.ticketSvc, a.validator))
// Ticket Routes
a.fiber.Post("/ticket", h.CreateTicket)
a.fiber.Get("/ticket", h.GetAllTickets)
a.fiber.Get("/ticket/:id", h.GetTicketByID)
// Bet
a.fiber.Post("/bet", handlers.CreateBet(a.logger, a.betSvc, a.validator))
a.fiber.Get("/bet", handlers.GetAllBet(a.logger, a.betSvc, a.validator))
a.fiber.Get("/bet/:id", handlers.GetBetByID(a.logger, a.betSvc, a.validator))
a.fiber.Patch("/bet/:id", handlers.UpdateCashOut(a.logger, a.betSvc, a.validator))
a.fiber.Delete("/bet/:id", handlers.DeleteBet(a.logger, a.betSvc, a.validator))
// Bet Routes
a.fiber.Post("/bet", h.CreateBet)
a.fiber.Get("/bet", h.GetAllBet)
a.fiber.Get("/bet/:id", h.GetBetByID)
a.fiber.Patch("/bet/:id", h.UpdateCashOut)
a.fiber.Delete("/bet/:id", h.DeleteBet)
// Wallet
a.fiber.Get("/wallet", handlers.GetAllWallets(a.logger, a.walletSvc, a.validator))
a.fiber.Get("/wallet/:id", handlers.GetWalletByID(a.logger, a.walletSvc, a.validator))
a.fiber.Put("/wallet/:id", handlers.UpdateWalletActive(a.logger, a.walletSvc, a.validator))
// Transaction Routes
a.fiber.Post("/transaction", h.CreateTransaction)
a.fiber.Get("/transaction", h.GetAllTransactions)
a.fiber.Get("/transaction/:id", h.GetTransactionByID)
a.fiber.Patch("/transaction/:id", h.UpdateTransactionVerified)
// Transactions /transactions
a.fiber.Post("/transaction", handlers.CreateTransaction(a.logger, a.transactionSvc, a.validator))
a.fiber.Get("/transaction", handlers.GetAllTransactions(a.logger, a.transactionSvc, a.validator))
a.fiber.Get("/transaction/:id", handlers.GetTransactionByID(a.logger, a.transactionSvc, a.validator))
a.fiber.Patch("/transaction/:id", handlers.UpdateTransactionVerified(a.logger, a.transactionSvc, a.validator))
a.fiber.Get("/notifications/ws/connect/:recipientID", handler.ConnectSocket)
a.fiber.Post("/notifications/mark-as-read", handler.MarkNotificationAsRead)
a.fiber.Post("/notifications/create", handler.CreateAndSendNotification)
// Notification Routes
a.fiber.Get("/notifications/ws/connect/:recipientID", h.ConnectSocket)
a.fiber.Post("/notifications/mark-as-read", h.MarkNotificationAsRead)
a.fiber.Post("/notifications/create", h.CreateAndSendNotification)
}
///user/profile get
// @Router /user/resetPassword [post]
// @Router /user/sendResetCode [post]
// @Router /user/register [post]