feat: referal completed
This commit is contained in:
parent
49a97484c7
commit
f796b97afe
47
README.md
47
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
--
|
||||
suspended_at TIMESTAMPTZ NULL, -- this can be NULL if the user is not suspended
|
||||
suspended BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
CHECK (email IS NOT NULL OR phone_number IS NOT NULL)
|
||||
CHECK (email IS NOT NULL OR phone_number IS NOT NULL)
|
||||
);
|
||||
CREATE TABLE refresh_tokens (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
|
|
@ -28,13 +28,13 @@ CREATE TABLE refresh_tokens (
|
|||
CREATE TABLE otps (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
sent_to VARCHAR(255) NOT NULL,
|
||||
medium VARCHAR(50) NOT NULL,
|
||||
otp_for VARCHAR(50) NOT NULL,
|
||||
otp VARCHAR(10) NOT NULL,
|
||||
medium VARCHAR(50) NOT NULL,
|
||||
otp_for VARCHAR(50) NOT NULL,
|
||||
otp VARCHAR(10) NOT NULL,
|
||||
used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMPTZ NOT NULL
|
||||
used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bets (
|
||||
|
|
@ -132,23 +132,20 @@ CREATE TABLE IF NOT EXISTS transactions (
|
|||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
INSERT INTO users (
|
||||
first_name, last_name, email, phone_number, password, role,
|
||||
email_verified, phone_verified, created_at, updated_at,
|
||||
first_name, last_name, email, phone_number, password, role,
|
||||
email_verified, phone_verified, created_at, updated_at,
|
||||
suspended_at, suspended
|
||||
) VALUES (
|
||||
'John',
|
||||
'Doe',
|
||||
'john.doe@example.com',
|
||||
NULL,
|
||||
crypt('password123', gen_salt('bf'))::bytea,
|
||||
'customer',
|
||||
TRUE,
|
||||
FALSE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL,
|
||||
FALSE
|
||||
'John',
|
||||
'Doe',
|
||||
'john.doe@example.com',
|
||||
NULL,
|
||||
crypt('password123', gen_salt('bf'))::bytea,
|
||||
'customer',
|
||||
TRUE,
|
||||
FALSE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL,
|
||||
FALSE
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
17
db/migrations/000003_referal.down.sql
Normal file
17
db/migrations/000003_referal.down.sql
Normal 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;
|
||||
53
db/migrations/000003_referal.up.sql
Normal file
53
db/migrations/000003_referal.up.sql
Normal 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
65
db/query/referal.sql
Normal 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;
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,17 +185,21 @@ type User struct {
|
|||
UpdatedAt pgtype.Timestamptz
|
||||
SuspendedAt pgtype.Timestamptz
|
||||
Suspended bool
|
||||
ReferralCode pgtype.Text
|
||||
ReferredBy pgtype.Text
|
||||
}
|
||||
|
||||
type Wallet struct {
|
||||
ID int64
|
||||
Balance int64
|
||||
IsWithdraw bool
|
||||
IsBettable bool
|
||||
UserID int64
|
||||
IsActive bool
|
||||
CreatedAt pgtype.Timestamp
|
||||
UpdatedAt pgtype.Timestamp
|
||||
ID int64
|
||||
Balance int64
|
||||
IsWithdraw bool
|
||||
IsBettable bool
|
||||
UserID int64
|
||||
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
285
gen/db/referal.sql.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
65
internal/domain/referal.go
Normal file
65
internal/domain/referal.go
Normal 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
|
||||
}
|
||||
|
|
@ -34,10 +34,9 @@ type RegisterUserReq struct {
|
|||
PhoneNumber string
|
||||
Password string
|
||||
//Role string
|
||||
Otp string
|
||||
ReferalCode string
|
||||
//
|
||||
OtpMedium OtpMedium
|
||||
Otp string
|
||||
ReferralCode string `json:"referral_code"`
|
||||
OtpMedium OtpMedium
|
||||
}
|
||||
type ResetPasswordReq struct {
|
||||
Email string
|
||||
|
|
|
|||
240
internal/repository/referal.go
Normal file
240
internal/repository/referal.go
Normal 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,
|
||||
}
|
||||
}
|
||||
18
internal/services/referal/port.go
Normal file
18
internal/services/referal/port.go
Normal 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
|
||||
}
|
||||
214
internal/services/referal/service.go
Normal file
214
internal/services/referal/service.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,49 +21,50 @@ 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 {
|
||||
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)
|
||||
}
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
successRes, err := 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
|
||||
}
|
||||
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, JwtConfig.JwtAccessKey, JwtConfig.JwtAccessExpiry)
|
||||
res := loginCustomerRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: successRes.RfToken,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil)
|
||||
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"`
|
||||
}
|
||||
}
|
||||
|
||||
type refreshToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
var req loginCustomerReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse LoginCustomer request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
|
||||
if err != 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")
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
res := loginCustomerRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: successRes.RfToken,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil)
|
||||
}
|
||||
|
||||
// RefreshToken godoc
|
||||
|
|
@ -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
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||
}
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
rf, err := 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
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
logger.Error("Create jwt failed", "error", err)
|
||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
res := loginCustomerRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: rf,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "refresh successful", res, nil)
|
||||
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"`
|
||||
}
|
||||
}
|
||||
|
||||
type logoutReq struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
var req refreshTokenReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse RefreshToken request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
rf, err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken)
|
||||
if err != 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")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var req logoutReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||
}
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
err := 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
|
||||
}
|
||||
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)
|
||||
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 {
|
||||
h.logger.Error("Failed to parse LogOutCustomer request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
err := h.authSvc.Logout(c.Context(), req.RefreshToken)
|
||||
if err != 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")
|
||||
}
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Logout successful", nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,62 +32,67 @@ 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
|
||||
|
||||
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",
|
||||
})
|
||||
}
|
||||
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO Validate Outcomes Here and make sure they didn't expire
|
||||
|
||||
bet, err := 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,
|
||||
},
|
||||
UserID: domain.ValidInt64{
|
||||
Value: userID,
|
||||
Valid: !isShopBet,
|
||||
},
|
||||
IsShopBet: req.IsShopBet,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Error("CreateBetReq failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
|
||||
}
|
||||
|
||||
res := convertBet(bet)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil)
|
||||
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 {
|
||||
h.logger.Error("Failed to parse CreateBet request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
// TODO: Check the token, find the role, and get the branch ID from there
|
||||
isShopBet := true
|
||||
branchID := int64(1)
|
||||
var userID int64
|
||||
|
||||
// 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,
|
||||
},
|
||||
UserID: domain.ValidInt64{
|
||||
Value: userID,
|
||||
Valid: !isShopBet,
|
||||
},
|
||||
IsShopBet: req.IsShopBet,
|
||||
})
|
||||
if 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 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())
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to get bets", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bets", err, nil)
|
||||
}
|
||||
|
||||
var res []BetRes = make([]BetRes, len(bets))
|
||||
for _, bet := range bets {
|
||||
res = append(res, convertBet(bet))
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "All Bets Retrieved", res, nil)
|
||||
func (h *Handler) GetAllBet(c *fiber.Ctx) error {
|
||||
bets, err := h.betSvc.GetAllBets(c.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get bets", "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets")
|
||||
}
|
||||
|
||||
res := make([]BetRes, len(bets))
|
||||
for i, bet := range bets {
|
||||
res[i] = convertBet(bet)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
bet, err := betSvc.GetBetByID(c.Context(), id)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
res := convertBet(bet)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, 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"`
|
||||
}
|
||||
}
|
||||
|
||||
type UpdateCashOutReq struct {
|
||||
CashedOut bool
|
||||
betID := c.Params("id")
|
||||
id, err := strconv.ParseInt(betID, 10, 64)
|
||||
if 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)
|
||||
}
|
||||
|
||||
// UpdateCashOut godoc
|
||||
|
|
@ -196,40 +174,35 @@ 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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",
|
||||
})
|
||||
}
|
||||
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = 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)
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Bet updated successfully", nil, nil)
|
||||
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 {
|
||||
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 {
|
||||
h.logger.Error("Failed to parse UpdateCashOut request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
err = h.betSvc.UpdateCashOut(c.Context(), id, req.CashedOut)
|
||||
if 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
|
||||
|
|
@ -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 {
|
||||
betID := c.Params("id")
|
||||
id, err := strconv.ParseInt(betID, 10, 64)
|
||||
func (h *Handler) DeleteBet(c *fiber.Ctx) error {
|
||||
betID := c.Params("id")
|
||||
id, err := strconv.ParseInt(betID, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("Invalid bet ID", "betID", betID, "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Invalid bet ID", "betID", betID, "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil)
|
||||
}
|
||||
err = h.betSvc.DeleteBet(c.Context(), id)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to delete bet by ID", "betID", id, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete bet")
|
||||
}
|
||||
|
||||
err = betSvc.DeleteBet(c.Context(), id)
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Bet removed successfully", nil, nil)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
145
internal/web_server/handlers/referal_handlers.go
Normal file
145
internal/web_server/handlers/referal_handlers.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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 {
|
||||
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",
|
||||
})
|
||||
}
|
||||
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO Validate Outcomes Here and make sure they didn't expire
|
||||
|
||||
ticket, err := 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",
|
||||
})
|
||||
}
|
||||
res := CreateTicketRes{
|
||||
FastCode: ticket.ID,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Ticket Created", res, nil)
|
||||
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 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"`
|
||||
type CreateTicketRes struct {
|
||||
FastCode int64 `json:"fast_code" example:"1234"`
|
||||
}
|
||||
|
||||
var req CreateTicketReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse CreateTicket request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{
|
||||
Outcomes: req.Outcomes,
|
||||
Amount: domain.Currency(req.Amount),
|
||||
TotalOdds: req.TotalOdds,
|
||||
})
|
||||
if err != nil {
|
||||
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 successfully", res, nil)
|
||||
}
|
||||
|
||||
// GetTicketByID godoc
|
||||
|
|
@ -86,34 +69,34 @@ 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)
|
||||
}
|
||||
|
||||
ticket, err := ticketSvc.GetTicketByID(c.Context(), id)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
res := TicketRes{
|
||||
ID: ticket.ID,
|
||||
Outcomes: ticket.Outcomes,
|
||||
Amount: ticket.Amount.Float64(),
|
||||
TotalOdds: ticket.TotalOdds,
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Ticket retrieved successfully", res, 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"`
|
||||
}
|
||||
|
||||
ticketID := c.Params("id")
|
||||
id, err := strconv.ParseInt(ticketID, 10, 64)
|
||||
if 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{
|
||||
ID: ticket.ID,
|
||||
Outcomes: ticket.Outcomes,
|
||||
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)
|
||||
}
|
||||
|
||||
var res []TicketRes = make([]TicketRes, len(tickets))
|
||||
|
||||
for _, ticket := range tickets {
|
||||
res = append(res, 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)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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 successfully", res, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,46 +72,62 @@ 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 {
|
||||
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",
|
||||
})
|
||||
}
|
||||
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
transaction, err := 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),
|
||||
FullName: req.FullName,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
BankCode: req.BankCode,
|
||||
BeneficiaryName: req.BeneficiaryName,
|
||||
AccountName: req.AccountName,
|
||||
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)
|
||||
}
|
||||
|
||||
res := convertTransaction(transaction)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Transaction Created", res, nil)
|
||||
// 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 {
|
||||
h.logger.Error("CreateTransaction failed to parse request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
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: req.PaymentOption,
|
||||
FullName: req.FullName,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
BankCode: req.BankCode,
|
||||
BeneficiaryName: req.BeneficiaryName,
|
||||
AccountName: req.AccountName,
|
||||
AccountNumber: req.AccountNumber,
|
||||
ReferenceNumber: req.ReferenceNumber,
|
||||
})
|
||||
if 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 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())
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to get transaction", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transaction", err, nil)
|
||||
}
|
||||
|
||||
var res []TransactionRes = make([]TransactionRes, len(transactions))
|
||||
for _, transaction := range transactions {
|
||||
res = append(res, convertTransaction(transaction))
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "All Transactions Retrieved", res, nil)
|
||||
func (h *Handler) GetAllTransactions(c *fiber.Ctx) error {
|
||||
transactions, err := h.transactionSvc.GetAllTransactions(c.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get transactions", "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transactions")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
transaction, err := 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)
|
||||
}
|
||||
|
||||
res := convertTransaction(transaction)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil)
|
||||
func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
|
||||
transactionID := c.Params("id")
|
||||
id, err := strconv.ParseInt(transactionID, 10, 64)
|
||||
if err != nil {
|
||||
h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID")
|
||||
}
|
||||
}
|
||||
|
||||
type UpdateTransactionVerifiedReq struct {
|
||||
Verified bool
|
||||
transaction, err := h.transactionSvc.GetTransactionByID(c.Context(), id)
|
||||
if 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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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",
|
||||
})
|
||||
}
|
||||
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = 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)
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Transaction updated successfully", nil, nil)
|
||||
|
||||
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 {
|
||||
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 {
|
||||
h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
err = h.transactionSvc.UpdateTransactionVerified(c.Context(), id, req.Verified)
|
||||
if 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
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",
|
||||
})
|
||||
}
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
emailExist, phoneExist, err := 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",
|
||||
})
|
||||
}
|
||||
res := CheckPhoneEmailExistRes{
|
||||
EmailExist: emailExist,
|
||||
PhoneNumberExist: phoneExist,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Check Success", res, nil)
|
||||
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"`
|
||||
}
|
||||
}
|
||||
|
||||
type RegisterCodeReq struct {
|
||||
Email string `json:"email" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||
var req CheckPhoneEmailExistReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse CheckPhoneEmailExist request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
emailExist, phoneExist, err := h.userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email)
|
||||
if err != nil {
|
||||
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 successful", res, nil)
|
||||
}
|
||||
|
||||
// SendRegisterCode godoc
|
||||
|
|
@ -80,51 +65,40 @@ 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 {
|
||||
var req RegisterCodeReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Invalid request",
|
||||
})
|
||||
}
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
var sentTo string
|
||||
var medium domain.OtpMedium
|
||||
if req.Email != "" {
|
||||
sentTo = req.Email
|
||||
medium = domain.OtpMediumEmail
|
||||
}
|
||||
if req.PhoneNumber != "" {
|
||||
sentTo = req.PhoneNumber
|
||||
medium = domain.OtpMediumSms
|
||||
}
|
||||
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",
|
||||
})
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
|
||||
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"`
|
||||
}
|
||||
}
|
||||
|
||||
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"`
|
||||
//
|
||||
var req RegisterCodeReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse SendRegisterCode request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
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
|
||||
} else if req.PhoneNumber != "" {
|
||||
sentTo = req.PhoneNumber
|
||||
medium = domain.OtpMediumSms
|
||||
} else {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// RegisterUser godoc
|
||||
|
|
@ -138,74 +112,84 @@ 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 {
|
||||
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",
|
||||
})
|
||||
}
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
user := domain.RegisterUserReq{
|
||||
FirstName: req.FirstName,
|
||||
LastName: req.LastName,
|
||||
Email: req.Email,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
Password: req.Password,
|
||||
Otp: req.Otp,
|
||||
ReferalCode: req.ReferalCode,
|
||||
OtpMedium: domain.OtpMediumEmail,
|
||||
}
|
||||
medium, err := getMedium(req.Email, req.PhoneNumber)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
if errors.Is(err, domain.ErrOtpAlreadyUsed) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Otp already used", nil, nil)
|
||||
}
|
||||
if errors.Is(err, domain.ErrOtpExpired) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Otp expired", nil, nil)
|
||||
}
|
||||
if errors.Is(err, domain.ErrInvalidOtp) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid otp", nil, nil)
|
||||
}
|
||||
if errors.Is(err, domain.ErrOtpNotFound) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "User already exist", nil, nil)
|
||||
}
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("CreateCustomerWallet failed", "error", err)
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create customer wallet for user", err, nil)
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil)
|
||||
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"`
|
||||
}
|
||||
}
|
||||
|
||||
type ResetCodeReq struct {
|
||||
Email string `json:"email" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||
var req RegisterUserReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse RegisterUser request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
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,
|
||||
LastName: req.LastName,
|
||||
Email: req.Email,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
Password: req.Password,
|
||||
Otp: req.Otp,
|
||||
ReferralCode: req.ReferalCode,
|
||||
OtpMedium: domain.OtpMediumEmail,
|
||||
}
|
||||
medium, err := getMedium(req.Email, req.PhoneNumber)
|
||||
if err != nil {
|
||||
h.logger.Error("RegisterUser failed", "error", err)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
user.OtpMedium = medium
|
||||
|
||||
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)
|
||||
}
|
||||
if errors.Is(err, domain.ErrOtpExpired) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Otp expired", nil, nil)
|
||||
}
|
||||
if errors.Is(err, domain.ErrInvalidOtp) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid otp", nil, nil)
|
||||
}
|
||||
if errors.Is(err, domain.ErrOtpNotFound) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "User already exist", nil, nil)
|
||||
}
|
||||
h.logger.Error("RegisterUser failed", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Internal server error",
|
||||
})
|
||||
}
|
||||
|
||||
_, err = h.walletSvc.CreateWallet(c.Context(), domain.CreateWallet{
|
||||
UserID: newUser.ID,
|
||||
IsWithdraw: true,
|
||||
IsBettable: true,
|
||||
})
|
||||
if 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)
|
||||
}
|
||||
|
||||
// SendResetCode godoc
|
||||
|
|
@ -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 {
|
||||
var req ResetCodeReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Invalid request",
|
||||
})
|
||||
}
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
var sentTo string
|
||||
var medium domain.OtpMedium
|
||||
if req.Email != "" {
|
||||
sentTo = req.Email
|
||||
medium = domain.OtpMediumEmail
|
||||
}
|
||||
if req.PhoneNumber != "" {
|
||||
sentTo = req.PhoneNumber
|
||||
medium = domain.OtpMediumSms
|
||||
}
|
||||
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",
|
||||
})
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
|
||||
|
||||
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"`
|
||||
}
|
||||
}
|
||||
|
||||
type ResetPasswordReq struct {
|
||||
Email string
|
||||
PhoneNumber string
|
||||
Password string
|
||||
Otp string
|
||||
var req ResetCodeReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse SendResetCode request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
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
|
||||
} else if req.PhoneNumber != "" {
|
||||
sentTo = req.PhoneNumber
|
||||
medium = domain.OtpMediumSms
|
||||
} else {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// ResetPassword godoc
|
||||
|
|
@ -272,58 +250,44 @@ 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 {
|
||||
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",
|
||||
})
|
||||
}
|
||||
valErrs, ok := validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
user := 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)
|
||||
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"`
|
||||
}
|
||||
}
|
||||
|
||||
type UserProfileRes struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
Role domain.Role `json:"role"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
SuspendedAt time.Time `json:"suspended_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
var req ResetPasswordReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse ResetPassword request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
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,
|
||||
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
|
||||
|
|
@ -337,34 +301,52 @@ type UserProfileRes struct {
|
|||
// @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)
|
||||
if err != nil {
|
||||
logger.Error("GetUserProfile failed", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Internal server error",
|
||||
})
|
||||
}
|
||||
|
||||
res := UserProfileRes{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email,
|
||||
PhoneNumber: user.PhoneNumber,
|
||||
Role: user.Role,
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
SuspendedAt: user.SuspendedAt,
|
||||
Suspended: user.Suspended,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
|
||||
func (h *Handler) UserProfile(c *fiber.Ctx) error {
|
||||
type UserProfileRes struct {
|
||||
ID int64 `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
Role domain.Role `json:"role"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
PhoneVerified bool `json:"phone_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
SuspendedAt time.Time `json:"suspended_at"`
|
||||
Suspended bool `json:"suspended"`
|
||||
}
|
||||
|
||||
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 profile", "userID", userID, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile")
|
||||
}
|
||||
|
||||
res := UserProfileRes{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email,
|
||||
PhoneNumber: user.PhoneNumber,
|
||||
Role: user.Role,
|
||||
EmailVerified: user.EmailVerified,
|
||||
PhoneVerified: user.PhoneVerified,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
SuspendedAt: user.SuspendedAt,
|
||||
Suspended: user.Suspended,
|
||||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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,36 +20,43 @@ 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)
|
||||
}
|
||||
|
||||
wallet, err := walletSvc.GetWalletByID(c.Context(), id)
|
||||
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)
|
||||
}
|
||||
|
||||
res := WalletRes{
|
||||
ID: wallet.ID,
|
||||
Balance: wallet.Balance.Float64(),
|
||||
IsWithdraw: wallet.IsWithdraw,
|
||||
IsBettable: wallet.IsBettable,
|
||||
IsActive: wallet.IsActive,
|
||||
UserID: wallet.UserID,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, 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"`
|
||||
}
|
||||
|
||||
walletID := c.Params("id")
|
||||
id, err := strconv.ParseInt(walletID, 10, 64)
|
||||
if 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{
|
||||
ID: wallet.ID,
|
||||
Balance: wallet.Balance.Float64(),
|
||||
IsWithdraw: wallet.IsWithdraw,
|
||||
IsBettable: wallet.IsBettable,
|
||||
IsActive: wallet.IsActive,
|
||||
UserID: wallet.UserID,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
// GetAllWallets godoc
|
||||
|
|
@ -76,35 +69,39 @@ 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)
|
||||
}
|
||||
|
||||
var res []WalletRes = make([]WalletRes, len(wallets))
|
||||
|
||||
for _, wallet := range wallets {
|
||||
res = append(res, WalletRes{
|
||||
ID: wallet.ID,
|
||||
Balance: wallet.Balance.Float64(),
|
||||
IsWithdraw: wallet.IsWithdraw,
|
||||
IsBettable: wallet.IsBettable,
|
||||
IsActive: wallet.IsActive,
|
||||
UserID: wallet.UserID,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "All Wallets retrieved", res, nil)
|
||||
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"`
|
||||
}
|
||||
}
|
||||
|
||||
type UpdateWalletActiveReq struct {
|
||||
IsActive bool
|
||||
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")
|
||||
}
|
||||
|
||||
res := make([]WalletRes, len(wallets))
|
||||
for i, wallet := range wallets {
|
||||
res[i] = WalletRes{
|
||||
ID: wallet.ID,
|
||||
Balance: wallet.Balance.Float64(),
|
||||
IsWithdraw: wallet.IsWithdraw,
|
||||
IsBettable: wallet.IsBettable,
|
||||
IsActive: wallet.IsActive,
|
||||
UserID: wallet.UserID,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "All wallets retrieved successfully", res, nil)
|
||||
}
|
||||
|
||||
// UpdateWalletActive godoc
|
||||
|
|
@ -119,46 +116,35 @@ 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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",
|
||||
})
|
||||
}
|
||||
|
||||
err = 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)
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Wallet successfully updated", nil, nil)
|
||||
func (h *Handler) UpdateWalletActive(c *fiber.Ctx) error {
|
||||
type UpdateWalletActiveReq struct {
|
||||
IsActive bool `json:"is_active" validate:"required" example:"true"`
|
||||
}
|
||||
}
|
||||
|
||||
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"`
|
||||
walletID := c.Params("id")
|
||||
id, err := strconv.ParseInt(walletID, 10, 64)
|
||||
if 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 {
|
||||
h.logger.Error("Failed to parse UpdateWalletActive request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
// GetCustomerWallet godoc
|
||||
|
|
@ -173,40 +159,63 @@ 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 {
|
||||
|
||||
userId := c.Locals("user_id").(int64)
|
||||
role := string(c.Locals("role").(domain.Role))
|
||||
|
||||
companyID, err := strconv.ParseInt(c.Get("company_id"), 10, 64)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).SendString("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)
|
||||
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)
|
||||
}
|
||||
res := CustomerWalletRes{
|
||||
ID: wallet.ID,
|
||||
RegularID: wallet.RegularID,
|
||||
RegularBalance: wallet.RegularBalance.Float64(),
|
||||
StaticID: wallet.StaticID,
|
||||
StaticBalance: wallet.StaticBalance.Float64(),
|
||||
CustomerID: wallet.CustomerID,
|
||||
CompanyID: wallet.CompanyID,
|
||||
RegularUpdatedAt: wallet.RegularUpdatedAt,
|
||||
StaticUpdatedAt: wallet.StaticUpdatedAt,
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
|
||||
|
||||
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, 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 {
|
||||
h.logger.Error("Invalid company_id header", "value", c.Get("company_id"), "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid company_id")
|
||||
}
|
||||
|
||||
h.logger.Info("Fetching customer wallet", "userID", userID, "companyID", companyID)
|
||||
|
||||
wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID, companyID)
|
||||
if 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,
|
||||
RegularBalance: wallet.RegularBalance.Float64(),
|
||||
StaticID: wallet.StaticID,
|
||||
StaticBalance: wallet.StaticBalance.Float64(),
|
||||
CustomerID: wallet.CustomerID,
|
||||
CompanyID: wallet.CompanyID,
|
||||
RegularUpdatedAt: wallet.RegularUpdatedAt,
|
||||
StaticUpdatedAt: wallet.StaticUpdatedAt,
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user