small fixes and getting it ready for deployment

This commit is contained in:
Samuel Tariku 2025-04-29 21:11:25 +03:00
parent 9e4b0d9942
commit 98e30b5051
16 changed files with 611 additions and 257 deletions

View File

@ -161,6 +161,7 @@ CREATE TABLE IF NOT EXISTS transactions (
account_number VARCHAR(255) NOT NULL, account_number VARCHAR(255) NOT NULL,
reference_number VARCHAR(255) NOT NULL, reference_number VARCHAR(255) NOT NULL,
verified BOOLEAN NOT NULL DEFAULT false, verified BOOLEAN NOT NULL DEFAULT false,
approved_by BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
@ -243,7 +244,6 @@ CREATE TABLE odds (
UNIQUE (event_id, market_id, name, handicap), UNIQUE (event_id, market_id, name, handicap),
UNIQUE (event_id, market_id) UNIQUE (event_id, market_id)
); );
CREATE TABLE companies ( CREATE TABLE companies (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
@ -397,4 +397,4 @@ VALUES (
TRUE, TRUE,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP CURRENT_TIMESTAMP
); );

View File

@ -1,16 +1,51 @@
-- name: CreateTransaction :one -- name: CreateTransaction :one
INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING *; INSERT INTO transactions (
amount,
branch_id,
cashier_id,
bet_id,
type,
payment_option,
full_name,
phone_number,
bank_code,
beneficiary_name,
account_name,
account_number,
reference_number,
number_of_outcomes
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
$14
)
RETURNING *;
-- name: GetAllTransactions :many -- name: GetAllTransactions :many
SELECT * FROM transactions; SELECT *
FROM transactions;
-- name: GetTransactionByID :one -- name: GetTransactionByID :one
SELECT * FROM transactions WHERE id = $1; SELECT *
FROM transactions
WHERE id = $1;
-- name: GetTransactionByBranch :many -- name: GetTransactionByBranch :many
SELECT * FROM transactions WHERE branch_id = $1; SELECT *
FROM transactions
WHERE branch_id = $1;
-- name: UpdateTransactionVerified :exec -- name: UpdateTransactionVerified :exec
UPDATE transactions SET verified = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1; UPDATE transactions
SET verified = $2,
approved_by = $3,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1;

View File

@ -55,7 +55,7 @@ const docTemplate = `{
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/response.APIResponse" "$ref": "#/definitions/handlers.AdminRes"
} }
}, },
"400": { "400": {
@ -1449,7 +1449,7 @@ const docTemplate = `{
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/response.APIResponse" "$ref": "#/definitions/handlers.ManagersRes"
} }
}, },
"400": { "400": {
@ -3445,7 +3445,7 @@ const docTemplate = `{
"OUTCOME_STATUS_PENDING", "OUTCOME_STATUS_PENDING",
"OUTCOME_STATUS_WIN", "OUTCOME_STATUS_WIN",
"OUTCOME_STATUS_LOSS", "OUTCOME_STATUS_LOSS",
"OUTCOME_STATUS_ERROR" "OUTCOME_STATUS_VOID"
] ]
}, },
"domain.PaymentOption": { "domain.PaymentOption": {
@ -3703,6 +3703,50 @@ const docTemplate = `{
} }
} }
}, },
"handlers.AdminRes": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"email": {
"type": "string"
},
"email_verified": {
"type": "boolean"
},
"first_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"last_login": {
"type": "string"
},
"last_name": {
"type": "string"
},
"phone_number": {
"type": "string"
},
"phone_verified": {
"type": "boolean"
},
"role": {
"$ref": "#/definitions/domain.Role"
},
"suspended": {
"type": "boolean"
},
"suspended_at": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"handlers.BetRes": { "handlers.BetRes": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -3851,9 +3895,6 @@ const docTemplate = `{
}, },
"handlers.CheckPhoneEmailExistReq": { "handlers.CheckPhoneEmailExistReq": {
"type": "object", "type": "object",
"required": [
"phone_number"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
@ -3971,10 +4012,6 @@ const docTemplate = `{
} }
], ],
"example": 1 "example": 1
},
"total_odds": {
"type": "number",
"example": 4.22
} }
} }
}, },
@ -4140,10 +4177,6 @@ const docTemplate = `{
"items": { "items": {
"$ref": "#/definitions/handlers.CreateTicketOutcomeReq" "$ref": "#/definitions/handlers.CreateTicketOutcomeReq"
} }
},
"total_odds": {
"type": "number",
"example": 4.22
} }
} }
}, },
@ -4267,6 +4300,50 @@ const docTemplate = `{
} }
} }
}, },
"handlers.ManagersRes": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"email": {
"type": "string"
},
"email_verified": {
"type": "boolean"
},
"first_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"last_login": {
"type": "string"
},
"last_name": {
"type": "string"
},
"phone_number": {
"type": "string"
},
"phone_verified": {
"type": "boolean"
},
"role": {
"$ref": "#/definitions/domain.Role"
},
"suspended": {
"type": "boolean"
},
"suspended_at": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"handlers.RegisterCodeReq": { "handlers.RegisterCodeReq": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -47,7 +47,7 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/response.APIResponse" "$ref": "#/definitions/handlers.AdminRes"
} }
}, },
"400": { "400": {
@ -1441,7 +1441,7 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/response.APIResponse" "$ref": "#/definitions/handlers.ManagersRes"
} }
}, },
"400": { "400": {
@ -3437,7 +3437,7 @@
"OUTCOME_STATUS_PENDING", "OUTCOME_STATUS_PENDING",
"OUTCOME_STATUS_WIN", "OUTCOME_STATUS_WIN",
"OUTCOME_STATUS_LOSS", "OUTCOME_STATUS_LOSS",
"OUTCOME_STATUS_ERROR" "OUTCOME_STATUS_VOID"
] ]
}, },
"domain.PaymentOption": { "domain.PaymentOption": {
@ -3695,6 +3695,50 @@
} }
} }
}, },
"handlers.AdminRes": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"email": {
"type": "string"
},
"email_verified": {
"type": "boolean"
},
"first_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"last_login": {
"type": "string"
},
"last_name": {
"type": "string"
},
"phone_number": {
"type": "string"
},
"phone_verified": {
"type": "boolean"
},
"role": {
"$ref": "#/definitions/domain.Role"
},
"suspended": {
"type": "boolean"
},
"suspended_at": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"handlers.BetRes": { "handlers.BetRes": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -3843,9 +3887,6 @@
}, },
"handlers.CheckPhoneEmailExistReq": { "handlers.CheckPhoneEmailExistReq": {
"type": "object", "type": "object",
"required": [
"phone_number"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
@ -3963,10 +4004,6 @@
} }
], ],
"example": 1 "example": 1
},
"total_odds": {
"type": "number",
"example": 4.22
} }
} }
}, },
@ -4132,10 +4169,6 @@
"items": { "items": {
"$ref": "#/definitions/handlers.CreateTicketOutcomeReq" "$ref": "#/definitions/handlers.CreateTicketOutcomeReq"
} }
},
"total_odds": {
"type": "number",
"example": 4.22
} }
} }
}, },
@ -4259,6 +4292,50 @@
} }
} }
}, },
"handlers.ManagersRes": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"email": {
"type": "string"
},
"email_verified": {
"type": "boolean"
},
"first_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"last_login": {
"type": "string"
},
"last_name": {
"type": "string"
},
"phone_number": {
"type": "string"
},
"phone_verified": {
"type": "boolean"
},
"role": {
"$ref": "#/definitions/domain.Role"
},
"suspended": {
"type": "boolean"
},
"suspended_at": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"handlers.RegisterCodeReq": { "handlers.RegisterCodeReq": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -90,7 +90,7 @@ definitions:
- OUTCOME_STATUS_PENDING - OUTCOME_STATUS_PENDING
- OUTCOME_STATUS_WIN - OUTCOME_STATUS_WIN
- OUTCOME_STATUS_LOSS - OUTCOME_STATUS_LOSS
- OUTCOME_STATUS_ERROR - OUTCOME_STATUS_VOID
domain.PaymentOption: domain.PaymentOption:
enum: enum:
- 0 - 0
@ -272,6 +272,35 @@ definitions:
description: Converted from "time" field in UNIX format description: Converted from "time" field in UNIX format
type: string type: string
type: object type: object
handlers.AdminRes:
properties:
created_at:
type: string
email:
type: string
email_verified:
type: boolean
first_name:
type: string
id:
type: integer
last_login:
type: string
last_name:
type: string
phone_number:
type: string
phone_verified:
type: boolean
role:
$ref: '#/definitions/domain.Role'
suspended:
type: boolean
suspended_at:
type: string
updated_at:
type: string
type: object
handlers.BetRes: handlers.BetRes:
properties: properties:
amount: amount:
@ -384,8 +413,6 @@ definitions:
phone_number: phone_number:
example: "1234567890" example: "1234567890"
type: string type: string
required:
- phone_number
type: object type: object
handlers.CheckPhoneEmailExistRes: handlers.CheckPhoneEmailExistRes:
properties: properties:
@ -461,9 +488,6 @@ definitions:
allOf: allOf:
- $ref: '#/definitions/domain.OutcomeStatus' - $ref: '#/definitions/domain.OutcomeStatus'
example: 1 example: 1
total_odds:
example: 4.22
type: number
type: object type: object
handlers.CreateBranchOperationReq: handlers.CreateBranchOperationReq:
properties: properties:
@ -581,9 +605,6 @@ definitions:
items: items:
$ref: '#/definitions/handlers.CreateTicketOutcomeReq' $ref: '#/definitions/handlers.CreateTicketOutcomeReq'
type: array type: array
total_odds:
example: 4.22
type: number
type: object type: object
handlers.CreateTicketRes: handlers.CreateTicketRes:
properties: properties:
@ -668,6 +689,35 @@ definitions:
static_updated_at: static_updated_at:
type: string type: string
type: object type: object
handlers.ManagersRes:
properties:
created_at:
type: string
email:
type: string
email_verified:
type: boolean
first_name:
type: string
id:
type: integer
last_login:
type: string
last_name:
type: string
phone_number:
type: string
phone_verified:
type: boolean
role:
$ref: '#/definitions/domain.Role'
suspended:
type: boolean
suspended_at:
type: string
updated_at:
type: string
type: object
handlers.RegisterCodeReq: handlers.RegisterCodeReq:
properties: properties:
email: email:
@ -1055,7 +1105,7 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
$ref: '#/definitions/response.APIResponse' $ref: '#/definitions/handlers.AdminRes'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@ -1975,7 +2025,7 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
$ref: '#/definitions/response.APIResponse' $ref: '#/definitions/handlers.ManagersRes'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:

View File

@ -350,6 +350,7 @@ type Transaction struct {
AccountNumber string `json:"account_number"` AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"` ReferenceNumber string `json:"reference_number"`
Verified bool `json:"verified"` Verified bool `json:"verified"`
ApprovedBy pgtype.Int8 `json:"approved_by"`
CreatedAt pgtype.Timestamp `json:"created_at"` CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }

View File

@ -7,26 +7,61 @@ package dbgen
import ( import (
"context" "context"
"github.com/jackc/pgx/v5/pgtype"
) )
const CreateTransaction = `-- name: CreateTransaction :one const CreateTransaction = `-- name: CreateTransaction :one
INSERT INTO transactions (amount, branch_id, cashier_id, bet_id, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at INSERT INTO transactions (
amount,
branch_id,
cashier_id,
bet_id,
type,
payment_option,
full_name,
phone_number,
bank_code,
beneficiary_name,
account_name,
account_number,
reference_number,
number_of_outcomes
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
$14
)
RETURNING id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, approved_by, created_at, updated_at
` `
type CreateTransactionParams struct { type CreateTransactionParams struct {
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
BranchID int64 `json:"branch_id"` BranchID int64 `json:"branch_id"`
CashierID int64 `json:"cashier_id"` CashierID int64 `json:"cashier_id"`
BetID int64 `json:"bet_id"` BetID int64 `json:"bet_id"`
Type int64 `json:"type"` Type int64 `json:"type"`
PaymentOption int64 `json:"payment_option"` PaymentOption int64 `json:"payment_option"`
FullName string `json:"full_name"` FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
BankCode string `json:"bank_code"` BankCode string `json:"bank_code"`
BeneficiaryName string `json:"beneficiary_name"` BeneficiaryName string `json:"beneficiary_name"`
AccountName string `json:"account_name"` AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"` AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"` ReferenceNumber string `json:"reference_number"`
NumberOfOutcomes int64 `json:"number_of_outcomes"`
} }
func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (Transaction, error) { func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (Transaction, error) {
@ -44,6 +79,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa
arg.AccountName, arg.AccountName,
arg.AccountNumber, arg.AccountNumber,
arg.ReferenceNumber, arg.ReferenceNumber,
arg.NumberOfOutcomes,
) )
var i Transaction var i Transaction
err := row.Scan( err := row.Scan(
@ -63,6 +99,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa
&i.AccountNumber, &i.AccountNumber,
&i.ReferenceNumber, &i.ReferenceNumber,
&i.Verified, &i.Verified,
&i.ApprovedBy,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
) )
@ -70,7 +107,8 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa
} }
const GetAllTransactions = `-- name: GetAllTransactions :many const GetAllTransactions = `-- name: GetAllTransactions :many
SELECT id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions SELECT id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, approved_by, created_at, updated_at
FROM transactions
` `
func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) { func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error) {
@ -99,6 +137,7 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error)
&i.AccountNumber, &i.AccountNumber,
&i.ReferenceNumber, &i.ReferenceNumber,
&i.Verified, &i.Verified,
&i.ApprovedBy,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
); err != nil { ); err != nil {
@ -113,7 +152,9 @@ func (q *Queries) GetAllTransactions(ctx context.Context) ([]Transaction, error)
} }
const GetTransactionByBranch = `-- name: GetTransactionByBranch :many const GetTransactionByBranch = `-- name: GetTransactionByBranch :many
SELECT id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE branch_id = $1 SELECT id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, approved_by, created_at, updated_at
FROM transactions
WHERE branch_id = $1
` `
func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([]Transaction, error) { func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([]Transaction, error) {
@ -142,6 +183,7 @@ func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([
&i.AccountNumber, &i.AccountNumber,
&i.ReferenceNumber, &i.ReferenceNumber,
&i.Verified, &i.Verified,
&i.ApprovedBy,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
); err != nil { ); err != nil {
@ -156,7 +198,9 @@ func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([
} }
const GetTransactionByID = `-- name: GetTransactionByID :one const GetTransactionByID = `-- name: GetTransactionByID :one
SELECT id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, created_at, updated_at FROM transactions WHERE id = $1 SELECT id, amount, branch_id, cashier_id, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, approved_by, created_at, updated_at
FROM transactions
WHERE id = $1
` `
func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction, error) { func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction, error) {
@ -179,6 +223,7 @@ func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction
&i.AccountNumber, &i.AccountNumber,
&i.ReferenceNumber, &i.ReferenceNumber,
&i.Verified, &i.Verified,
&i.ApprovedBy,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
) )
@ -186,15 +231,20 @@ func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction
} }
const UpdateTransactionVerified = `-- name: UpdateTransactionVerified :exec const UpdateTransactionVerified = `-- name: UpdateTransactionVerified :exec
UPDATE transactions SET verified = $2, updated_at = CURRENT_TIMESTAMP WHERE id = $1 UPDATE transactions
SET verified = $2,
approved_by = $3,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
` `
type UpdateTransactionVerifiedParams struct { type UpdateTransactionVerifiedParams struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Verified bool `json:"verified"` Verified bool `json:"verified"`
ApprovedBy pgtype.Int8 `json:"approved_by"`
} }
func (q *Queries) UpdateTransactionVerified(ctx context.Context, arg UpdateTransactionVerifiedParams) error { func (q *Queries) UpdateTransactionVerified(ctx context.Context, arg UpdateTransactionVerifiedParams) error {
_, err := q.db.Exec(ctx, UpdateTransactionVerified, arg.ID, arg.Verified) _, err := q.db.Exec(ctx, UpdateTransactionVerified, arg.ID, arg.Verified, arg.ApprovedBy)
return err return err
} }

View File

@ -1,5 +1,7 @@
package domain package domain
import "time"
type TransactionType int type TransactionType int
const ( const (
@ -36,6 +38,9 @@ type Transaction struct {
AccountNumber string AccountNumber string
ReferenceNumber string ReferenceNumber string
Verified bool Verified bool
ApprovedBy ValidInt64
UpdatedAt time.Time
CreatedAt time.Time
} }
type CreateTransaction struct { type CreateTransaction struct {

View File

@ -5,10 +5,12 @@ import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype"
) )
func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction {
return domain.Transaction{ return domain.Transaction{
ID: transaction.ID,
Amount: domain.Currency(transaction.Amount), Amount: domain.Currency(transaction.Amount),
BranchID: transaction.BranchID, BranchID: transaction.BranchID,
CashierID: transaction.CashierID, CashierID: transaction.CashierID,
@ -23,24 +25,32 @@ func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction {
AccountName: transaction.AccountName, AccountName: transaction.AccountName,
AccountNumber: transaction.AccountNumber, AccountNumber: transaction.AccountNumber,
ReferenceNumber: transaction.ReferenceNumber, ReferenceNumber: transaction.ReferenceNumber,
ApprovedBy: domain.ValidInt64{
Value: transaction.ApprovedBy.Int64,
Valid: transaction.ApprovedBy.Valid,
},
CreatedAt: transaction.CreatedAt.Time,
UpdatedAt: transaction.UpdatedAt.Time,
Verified: transaction.Verified,
} }
} }
func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.CreateTransactionParams { func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.CreateTransactionParams {
return dbgen.CreateTransactionParams{ return dbgen.CreateTransactionParams{
Amount: int64(transaction.Amount), Amount: int64(transaction.Amount),
BranchID: transaction.BranchID, BranchID: transaction.BranchID,
CashierID: transaction.CashierID, CashierID: transaction.CashierID,
BetID: transaction.BetID, BetID: transaction.BetID,
Type: int64(transaction.Type), Type: int64(transaction.Type),
PaymentOption: int64(transaction.PaymentOption), PaymentOption: int64(transaction.PaymentOption),
FullName: transaction.FullName, FullName: transaction.FullName,
PhoneNumber: transaction.PhoneNumber, PhoneNumber: transaction.PhoneNumber,
BankCode: transaction.BankCode, BankCode: transaction.BankCode,
BeneficiaryName: transaction.BeneficiaryName, BeneficiaryName: transaction.BeneficiaryName,
AccountName: transaction.AccountName, AccountName: transaction.AccountName,
AccountNumber: transaction.AccountNumber, AccountNumber: transaction.AccountNumber,
ReferenceNumber: transaction.ReferenceNumber, ReferenceNumber: transaction.ReferenceNumber,
NumberOfOutcomes: transaction.NumberOfOutcomes,
} }
} }
@ -89,9 +99,13 @@ func (s *Store) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.
return result, nil return result, nil
} }
func (s *Store) UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error { func (s *Store) UpdateTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64) error {
err := s.queries.UpdateTransactionVerified(ctx, dbgen.UpdateTransactionVerifiedParams{ err := s.queries.UpdateTransactionVerified(ctx, dbgen.UpdateTransactionVerifiedParams{
ID: id, ID: id,
ApprovedBy: pgtype.Int8{
Int64: approvedBy,
Valid: true,
},
Verified: verified, Verified: verified,
}) })
return err return err

View File

@ -11,5 +11,5 @@ type TransactionStore interface {
GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error)
GetAllTransactions(ctx context.Context) ([]domain.Transaction, error) GetAllTransactions(ctx context.Context) ([]domain.Transaction, error)
GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error)
UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error UpdateTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64) error
} }

View File

@ -28,7 +28,6 @@ func (s *Service) GetAllTransactions(ctx context.Context) ([]domain.Transaction,
func (s *Service) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) { func (s *Service) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) {
return s.transactionStore.GetTransactionByBranch(ctx, id) return s.transactionStore.GetTransactionByBranch(ctx, id)
} }
func (s *Service) UpdateTransactionVerified(ctx context.Context, id int64, verified bool) error { func (s *Service) UpdateTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64) error {
return s.transactionStore.UpdateTransactionVerified(ctx, id, verified, approvedBy)
return s.transactionStore.UpdateTransactionVerified(ctx, id, verified)
} }

View File

@ -5,6 +5,7 @@ import (
"context" "context"
"log" "log"
"time"
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
@ -25,6 +26,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
log.Printf("FetchUpcomingEvents error: %v", err) log.Printf("FetchUpcomingEvents error: %v", err)
} }
time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
}, },
}, },
@ -36,18 +38,17 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
// } // }
// }, // },
// }, // },
// { {
// // spec: "0 */15 * * * *", // Every 15 minutes // spec: "0 */15 * * * *", // Every 15 minutes
// spec: "0 0 * * * *", // TODO: Every hour because of the 3600 requests per hour limit spec: "0 0 * * * *", // TODO: Every hour because of the 3600 requests per hour limit
// task: func() { task: func() {
if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
log.Printf("FetchNonLiveOdds error: %v", err)
}
time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { },
// log.Printf("FetchNonLiveOdds error: %v", err) },
// }
// time.Sleep(2 * time.Second) //This will restrict the fetching to 1800 requests per hour
// },
// },
// { // {
// spec: "0 */15 * * * *", // spec: "0 */15 * * * *",
// task: func() { // task: func() {
@ -71,7 +72,6 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
} }
for _, job := range schedule { for _, job := range schedule {
job.task()
if _, err := c.AddFunc(job.spec, job.task); err != nil { if _, err := c.AddFunc(job.spec, job.task); err != nil {
log.Fatalf("Failed to schedule cron job: %v", err) log.Fatalf("Failed to schedule cron job: %v", err)
} }

View File

@ -18,10 +18,10 @@ type CreateBetOutcomeReq struct {
type CreateBetReq struct { type CreateBetReq struct {
Outcomes []CreateBetOutcomeReq `json:"outcomes"` Outcomes []CreateBetOutcomeReq `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"` Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
Status domain.OutcomeStatus `json:"status" example:"1"` Status domain.OutcomeStatus `json:"status" example:"1"`
FullName string `json:"full_name" example:"John"` FullName string `json:"full_name" example:"John"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
BranchID *int64 `json:"branch_id,omitempty" example:"1"`
} }
type CreateBetRes struct { type CreateBetRes struct {
@ -99,6 +99,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
// Get user_id from middleware // Get user_id from middleware
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
role := c.Locals("role").(domain.Role)
var req CreateBetReq var req CreateBetReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -111,109 +112,13 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
// Validating user by role
// Differentiating between offline and online bets
user, err := h.userSvc.GetUserByID(c.Context(), userID)
if err != nil {
h.logger.Error("CreateBetReq failed, user id invalid")
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
}
cashoutID, err := h.betSvc.GenerateCashoutID()
if err != nil {
h.logger.Error("CreateBetReq failed, unable to create cashout id")
return response.WriteJSON(c, fiber.StatusInternalServerError, "Invalid request", err, nil)
}
var bet domain.Bet
if user.Role == domain.RoleCashier {
// Get the branch from the branch ID
branch, err := h.branchSvc.GetBranchByCashier(c.Context(), user.ID)
if err != nil {
h.logger.Error("CreateBetReq failed, branch id invalid")
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
}
// Deduct a percentage of the amount
// TODO move to service layer. Make it fetch dynamically from company
var deductedAmount = req.Amount / 10
err = h.walletSvc.DeductFromWallet(c.Context(), branch.WalletID, domain.ToCurrency(deductedAmount))
if err != nil {
h.logger.Error("CreateBetReq failed, unable to deduct from WalletID")
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
}
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
Amount: domain.ToCurrency(req.Amount),
TotalOdds: req.TotalOdds,
Status: req.Status,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BranchID: domain.ValidInt64{
Value: branch.ID,
Valid: true,
},
UserID: domain.ValidInt64{
Value: userID,
Valid: false,
},
IsShopBet: true,
CashoutID: cashoutID,
})
} else if user.Role == domain.RoleSuperAdmin {
// This is just for testing
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
Amount: domain.ToCurrency(req.Amount),
TotalOdds: req.TotalOdds,
Status: req.Status,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BranchID: domain.ValidInt64{
Value: 1,
Valid: true,
},
UserID: domain.ValidInt64{
Value: userID,
Valid: true,
},
IsShopBet: true,
CashoutID: cashoutID,
})
} else {
// TODO if user is customer, get id from the token then get the wallet id from there and reduce the amount
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
Amount: domain.ToCurrency(req.Amount),
TotalOdds: req.TotalOdds,
Status: req.Status,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BranchID: domain.ValidInt64{
Value: 0,
Valid: false,
},
UserID: domain.ValidInt64{
Value: userID,
Valid: true,
},
IsShopBet: false,
CashoutID: cashoutID,
})
}
if err != nil {
h.logger.Error("CreateBetReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
}
//
// TODO Validate Outcomes Here and make sure they didn't expire // TODO Validate Outcomes Here and make sure they didn't expire
// Validation for creating tickets // Validation for creating tickets
if len(req.Outcomes) > 30 { if len(req.Outcomes) > 30 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
} }
var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes)) var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes))
var totalOdds float32 = 1
for _, outcome := range req.Outcomes { for _, outcome := range req.Outcomes {
eventIDStr := strconv.FormatInt(outcome.EventID, 10) eventIDStr := strconv.FormatInt(outcome.EventID, 10)
marketIDStr := strconv.FormatInt(outcome.MarketID, 10) marketIDStr := strconv.FormatInt(outcome.MarketID, 10)
@ -262,9 +167,9 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
} }
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
totalOdds = totalOdds * float32(parsedOdd)
outcomes = append(outcomes, domain.CreateBetOutcome{ outcomes = append(outcomes, domain.CreateBetOutcome{
BetID: bet.ID,
EventID: outcome.EventID, EventID: outcome.EventID,
OddID: outcome.OddID, OddID: outcome.OddID,
MarketID: outcome.MarketID, MarketID: outcome.MarketID,
@ -279,6 +184,108 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
}) })
} }
// Validating user by role
// Differentiating between offline and online bets
cashoutID, err := h.betSvc.GenerateCashoutID()
if err != nil {
h.logger.Error("CreateBetReq failed, unable to create cashout id")
return response.WriteJSON(c, fiber.StatusInternalServerError, "Invalid request", err, nil)
}
var bet domain.Bet
if role == domain.RoleCashier {
// Get the branch from the branch ID
branch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID)
if err != nil {
h.logger.Error("CreateBetReq failed, branch id invalid")
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
}
// Deduct a percentage of the amount
// TODO move to service layer. Make it fetch dynamically from company
var deductedAmount = req.Amount / 10
err = h.walletSvc.DeductFromWallet(c.Context(), branch.WalletID, domain.ToCurrency(deductedAmount))
if err != nil {
h.logger.Error("CreateBetReq failed, unable to deduct from WalletID")
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
}
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
Amount: domain.ToCurrency(req.Amount),
TotalOdds: totalOdds,
Status: req.Status,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BranchID: domain.ValidInt64{
Value: branch.ID,
Valid: true,
},
UserID: domain.ValidInt64{
Value: userID,
Valid: false,
},
IsShopBet: true,
CashoutID: cashoutID,
})
} else if role == domain.RoleSuperAdmin || role == domain.RoleAdmin || role == domain.RoleBranchManager {
// If a non cashier wants to create a bet, they will need to provide the Branch ID
// TODO: restrict the Branch ID of Admin and Branch Manager to only the branches within their own company
if req.BranchID == nil {
h.logger.Error("CreateBetReq failed, Branch ID is required for this type of user")
return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID is required for this type of user", nil, nil)
}
// h.logger.Info("Branch ID", slog.Int64("branch_id", *req.BranchID))
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
Amount: domain.ToCurrency(req.Amount),
TotalOdds: totalOdds,
Status: req.Status,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BranchID: domain.ValidInt64{
Value: *req.BranchID,
Valid: true,
},
UserID: domain.ValidInt64{
Value: userID,
Valid: true,
},
IsShopBet: true,
CashoutID: cashoutID,
})
} else {
// TODO if user is customer, get id from the token then get the wallet id from there and reduce the amount
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
Amount: domain.ToCurrency(req.Amount),
TotalOdds: totalOdds,
Status: req.Status,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
BranchID: domain.ValidInt64{
Value: 0,
Valid: false,
},
UserID: domain.ValidInt64{
Value: userID,
Valid: true,
},
IsShopBet: false,
CashoutID: cashoutID,
})
}
if err != nil {
h.logger.Error("CreateBetReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
}
// Updating the bet id for outcomes
for index := range outcomes {
outcomes[index].BetID = bet.ID
}
rows, err := h.betSvc.CreateBetOutcome(c.Context(), outcomes) rows, err := h.betSvc.CreateBetOutcome(c.Context(), outcomes)
if err != nil { if err != nil {

View File

@ -23,9 +23,8 @@ type CreateTicketOutcomeReq struct {
} }
type CreateTicketReq struct { type CreateTicketReq struct {
Outcomes []CreateTicketOutcomeReq `json:"outcomes"` Outcomes []CreateTicketOutcomeReq `json:"outcomes"`
Amount float32 `json:"amount" example:"100.0"` Amount float32 `json:"amount" example:"100.0"`
TotalOdds float32 `json:"total_odds" example:"4.22"`
} }
type CreateTicketRes struct { type CreateTicketRes struct {
FastCode int64 `json:"fast_code" example:"1234"` FastCode int64 `json:"fast_code" example:"1234"`
@ -66,6 +65,7 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
} }
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes)) var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
var totalOdds float32 = 1
for _, outcome := range req.Outcomes { for _, outcome := range req.Outcomes {
eventIDStr := strconv.FormatInt(outcome.EventID, 10) eventIDStr := strconv.FormatInt(outcome.EventID, 10)
marketIDStr := strconv.FormatInt(outcome.MarketID, 10) marketIDStr := strconv.FormatInt(outcome.MarketID, 10)
@ -100,7 +100,7 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
rawBytes, err := json.Marshal(raw) rawBytes, err := json.Marshal(raw)
err = json.Unmarshal(rawBytes, &rawOdd) err = json.Unmarshal(rawBytes, &rawOdd)
if err != nil { if err != nil {
h.logger.Error("Failed to unmarshal raw odd:", err) h.logger.Error("Failed to unmarshal raw odd:", "error", err)
continue continue
} }
if rawOdd.ID == oddIDStr { if rawOdd.ID == oddIDStr {
@ -114,7 +114,7 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
} }
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
totalOdds = totalOdds * float32(parsedOdd)
outcomes = append(outcomes, domain.CreateTicketOutcome{ outcomes = append(outcomes, domain.CreateTicketOutcome{
EventID: outcome.EventID, EventID: outcome.EventID,
OddID: outcome.OddID, OddID: outcome.OddID,
@ -129,10 +129,9 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
Expires: event.StartTime, Expires: event.StartTime,
}) })
} }
ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{ ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{
Amount: domain.ToCurrency(req.Amount), Amount: domain.ToCurrency(req.Amount),
TotalOdds: req.TotalOdds, TotalOdds: totalOdds,
}) })
if err != nil { if err != nil {
h.logger.Error("CreateTicketReq failed", "error", err) h.logger.Error("CreateTicketReq failed", "error", err)

View File

@ -1,7 +1,9 @@
package handlers package handlers
import ( import (
"log/slog"
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
@ -25,6 +27,9 @@ type TransactionRes struct {
AccountNumber string `json:"account_number"` AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"` ReferenceNumber string `json:"reference_number"`
Verified bool `json:"verified" example:"true"` Verified bool `json:"verified" example:"true"`
ApprovedBy *int64 `json:"approved_by" example:"1"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
} }
type CreateTransactionReq struct { type CreateTransactionReq struct {
@ -40,26 +45,36 @@ type CreateTransactionReq struct {
AccountName string `json:"account_name"` AccountName string `json:"account_name"`
AccountNumber string `json:"account_number"` AccountNumber string `json:"account_number"`
ReferenceNumber string `json:"reference_number"` ReferenceNumber string `json:"reference_number"`
BranchID *int64 `json:"branch_id,omitempty" example:"1"`
} }
func convertTransaction(transaction domain.Transaction) TransactionRes { func convertTransaction(transaction domain.Transaction) TransactionRes {
return TransactionRes{ newTransaction := TransactionRes{
ID: transaction.ID, ID: transaction.ID,
Amount: transaction.Amount.Float32(), Amount: transaction.Amount.Float32(),
BranchID: transaction.BranchID, BranchID: transaction.BranchID,
CashierID: transaction.CashierID, CashierID: transaction.CashierID,
BetID: transaction.BetID, BetID: transaction.BetID,
Type: int64(transaction.Type), Type: int64(transaction.Type),
PaymentOption: transaction.PaymentOption, PaymentOption: transaction.PaymentOption,
FullName: transaction.FullName, FullName: transaction.FullName,
PhoneNumber: transaction.PhoneNumber, PhoneNumber: transaction.PhoneNumber,
BankCode: transaction.BankCode, BankCode: transaction.BankCode,
BeneficiaryName: transaction.BeneficiaryName, BeneficiaryName: transaction.BeneficiaryName,
AccountName: transaction.AccountName, AccountName: transaction.AccountName,
AccountNumber: transaction.AccountNumber, AccountNumber: transaction.AccountNumber,
ReferenceNumber: transaction.ReferenceNumber, ReferenceNumber: transaction.ReferenceNumber,
Verified: transaction.Verified, Verified: transaction.Verified,
NumberOfOutcomes: transaction.NumberOfOutcomes,
CreatedAt: transaction.CreatedAt,
UpdatedAt: transaction.UpdatedAt,
} }
if transaction.ApprovedBy.Valid {
newTransaction.ApprovedBy = &transaction.ApprovedBy.Value
}
return newTransaction
} }
// CreateTransaction godoc // CreateTransaction godoc
@ -75,35 +90,17 @@ func convertTransaction(transaction domain.Transaction) TransactionRes {
// @Router /transaction [post] // @Router /transaction [post]
func (h *Handler) CreateTransaction(c *fiber.Ctx) error { func (h *Handler) CreateTransaction(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
user, err := h.userSvc.GetUserByID(c.Context(), userID) role := c.Locals("role").(domain.Role)
// user, err := h.userSvc.GetUserByID(c.Context(), userID)
if user.Role == domain.RoleCustomer { // TODO: Make a "Only Company" middleware auth and move this into that
h.logger.Error("CreateTransactionReq failed") if role == domain.RoleCustomer {
h.logger.Error("CreateTransactionReq failed due to unauthorized access")
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "unauthorized access", "error": "unauthorized access",
}) })
} }
// TODO: Add validation to make sure that the bet hasn't already been cashed out by someone else
var branchID int64
if user.Role == domain.RoleAdmin || user.Role == domain.RoleBranchManager || user.Role == domain.RoleSuperAdmin {
branch, err := h.branchSvc.GetBranchByID(c.Context(), 1)
if err != nil {
h.logger.Error("CreateTransactionReq no branches")
return response.WriteJSON(c, fiber.StatusBadRequest, "This user type doesn't have branches", err, nil)
}
branchID = branch.ID
} else {
branch, err := h.branchSvc.GetBranchByCashier(c.Context(), user.ID)
if err != nil {
h.logger.Error("CreateTransactionReq failed, branch id invalid")
return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID invalid", err, nil)
}
branchID = branch.ID
}
var req CreateTransactionReq var req CreateTransactionReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateTransaction failed to parse request", "error", err) h.logger.Error("CreateTransaction failed to parse request", "error", err)
@ -116,13 +113,51 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
// TODO: Validate the bet id and add the number of outcomes var branchID int64
if role == domain.RoleAdmin || role == domain.RoleBranchManager || role == domain.RoleSuperAdmin {
if req.BranchID == nil {
h.logger.Error("CreateTransactionReq Branch ID is required for this user role")
return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID is required for this user role", nil, nil)
}
branch, err := h.branchSvc.GetBranchByID(c.Context(), *req.BranchID)
if err != nil {
h.logger.Error("CreateTransactionReq no branches")
return response.WriteJSON(c, fiber.StatusBadRequest, "cannot find Branch ID", err, nil)
}
branchID = branch.ID
} else {
branch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID)
if err != nil {
h.logger.Error("CreateTransactionReq failed, branch id invalid")
return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID invalid", err, nil)
}
branchID = branch.ID
}
bet, err := h.betSvc.GetBetByID(c.Context(), req.BetID)
if err != nil {
h.logger.Error("CreateTransactionReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Bet ID invalid", err, nil)
}
// if bet.Status != domain.OUTCOME_STATUS_WIN {
// h.logger.Error("CreateTransactionReq failed, bet has not won")
// return response.WriteJSON(c, fiber.StatusBadRequest, "User has not won bet", err, nil)
// }
if bet.CashedOut {
h.logger.Error(("Bet has already been cashed out"))
return response.WriteJSON(c, fiber.StatusBadRequest, "This bet has already been cashed out", err, nil)
}
transaction, err := h.transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{ transaction, err := h.transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{
BranchID: branchID, BranchID: branchID,
CashierID: userID, CashierID: userID,
Amount: domain.ToCurrency(req.Amount), Amount: domain.ToCurrency(req.Amount),
BetID: req.BetID, BetID: bet.ID,
NumberOfOutcomes: 1, NumberOfOutcomes: int64(len(bet.Outcomes)),
Type: domain.TransactionType(req.Type), Type: domain.TransactionType(req.Type),
PaymentOption: domain.PaymentOption(req.PaymentOption), PaymentOption: domain.PaymentOption(req.PaymentOption),
FullName: req.FullName, FullName: req.FullName,
@ -236,7 +271,7 @@ func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
} }
type UpdateTransactionVerifiedReq struct { type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" validate:"required" example:"true"` Verified bool `json:"verified" example:"true"`
} }
// UpdateTransactionVerified godoc // UpdateTransactionVerified godoc
@ -250,10 +285,12 @@ type UpdateTransactionVerifiedReq struct {
// @Success 200 {object} response.APIResponse // @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /transaction/{id} [patch] // @Router /transaction/{id} [put]
func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error {
transactionID := c.Params("id") transactionID := c.Params("id")
userID := c.Locals("user_id").(int64)
// companyID := c.Locals("company_id").(domain.ValidInt64)
id, err := strconv.ParseInt(transactionID, 10, 64) id, err := strconv.ParseInt(transactionID, 10, 64)
if err != nil { if err != nil {
h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err)
@ -266,11 +303,14 @@ func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
} }
h.logger.Info("Update Transaction Verified", slog.Bool("verified", req.Verified))
if valErrs, ok := h.validator.Validate(c, req); !ok { if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
err = h.transactionSvc.UpdateTransactionVerified(c.Context(), id, req.Verified) // TODO: make it so that only people within the company can verify a transaction
err = h.transactionSvc.UpdateTransactionVerified(c.Context(), id, req.Verified, userID)
if err != nil { if err != nil {
h.logger.Error("Failed to update transaction verification", "transactionID", id, "error", err) h.logger.Error("Failed to update transaction verification", "transactionID", id, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transaction verification") return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transaction verification")

View File

@ -159,7 +159,7 @@ func (a *App) initAppRoutes() {
a.fiber.Post("/transaction", a.authMiddleware, h.CreateTransaction) a.fiber.Post("/transaction", a.authMiddleware, h.CreateTransaction)
a.fiber.Get("/transaction", a.authMiddleware, h.GetAllTransactions) a.fiber.Get("/transaction", a.authMiddleware, h.GetAllTransactions)
a.fiber.Get("/transaction/:id", a.authMiddleware, h.GetTransactionByID) a.fiber.Get("/transaction/:id", a.authMiddleware, h.GetTransactionByID)
a.fiber.Patch("/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified) a.fiber.Put("/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified)
// Notification Routes // Notification Routes
a.fiber.Get("/notifications/ws/connect/:recipientID", h.ConnectSocket) a.fiber.Get("/notifications/ws/connect/:recipientID", h.ConnectSocket)