fix: moving ticket logic into service
This commit is contained in:
parent
93d64d06d7
commit
b0803c968a
|
|
@ -102,7 +102,6 @@ func main() {
|
|||
userSvc := user.NewService(store, store, cfg)
|
||||
eventSvc := event.New(cfg.Bet365Token, store)
|
||||
oddsSvc := odds.New(store, cfg, logger)
|
||||
ticketSvc := ticket.NewService(store)
|
||||
notificationRepo := repository.NewNotificationRepository(store)
|
||||
virtuaGamesRepo := repository.NewVirtualGameRepository(store)
|
||||
notificationSvc := notificationservice.New(notificationRepo, logger, cfg)
|
||||
|
|
@ -120,6 +119,7 @@ func main() {
|
|||
branchSvc := branch.NewService(store)
|
||||
companySvc := company.NewService(store)
|
||||
leagueSvc := league.New(store)
|
||||
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger)
|
||||
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, logger, domain.MongoDBLogger)
|
||||
resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc)
|
||||
referalRepo := repository.NewReferralRepository(store)
|
||||
|
|
|
|||
|
|
@ -76,4 +76,6 @@ DROP TABLE IF EXISTS refresh_tokens;
|
|||
DROP TABLE IF EXISTS otps;
|
||||
DROP TABLE IF EXISTS odds;
|
||||
DROP TABLE IF EXISTS events;
|
||||
DROP TABLE IF EXISTS leagues;
|
||||
DROP TABLE IF EXISTS leagues;
|
||||
DROP TABLE IF EXISTS teams;
|
||||
DROP TABLE IF EXISTS settings;
|
||||
|
|
@ -264,6 +264,12 @@ CREATE TABLE teams (
|
|||
bet365_id INT,
|
||||
logo_url TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
-- Views
|
||||
CREATE VIEW companies_details AS
|
||||
SELECT companies.*,
|
||||
|
|
|
|||
5
db/migrations/000007_setting_data.up.sql
Normal file
5
db/migrations/000007_setting_data.up.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-- Settings Initial Data
|
||||
INSERT INTO settings (key, value)
|
||||
VALUES ('total_winnings_limit', '1000000') ON CONFLICT (key) DO
|
||||
UPDATE
|
||||
SET value = EXCLUDED.value;
|
||||
9
db/query/settings.sql
Normal file
9
db/query/settings.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
-- name: GetSettings :many
|
||||
SELECT *
|
||||
from settings;
|
||||
-- name: SaveSetting :one
|
||||
INSERT INTO settings (key, value, updated_at)
|
||||
VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO
|
||||
UPDATE
|
||||
SET value = EXCLUDED.value
|
||||
RETURNING *;
|
||||
405
docs/docs.go
405
docs/docs.go
|
|
@ -811,76 +811,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/veli/launch/{game_id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Generates authenticated launch URL for Veli games",
|
||||
"tags": [
|
||||
"Veli Games"
|
||||
],
|
||||
"summary": "Launch a Veli game",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Game ID (e.g., veli_aviator_v1)",
|
||||
"name": "game_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"default": "USD",
|
||||
"description": "Currency code",
|
||||
"name": "currency",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"real",
|
||||
"demo"
|
||||
],
|
||||
"type": "string",
|
||||
"default": "real",
|
||||
"description": "Game mode",
|
||||
"name": "mode",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Returns launch URL",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"description": "Login customer",
|
||||
|
|
@ -3377,7 +3307,7 @@ const docTemplate = `{
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.TicketRes"
|
||||
"$ref": "#/definitions/domain.TicketRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -3414,7 +3344,7 @@ const docTemplate = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateTicketReq"
|
||||
"$ref": "#/definitions/domain.CreateTicketReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3422,7 +3352,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateTicketRes"
|
||||
"$ref": "#/definitions/domain.CreateTicketRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -3466,7 +3396,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.TicketRes"
|
||||
"$ref": "#/definitions/domain.TicketRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -3484,6 +3414,38 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/top-leagues": {
|
||||
"get": {
|
||||
"description": "Retrieve all top leagues",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"prematch"
|
||||
],
|
||||
"summary": "Retrieve all top leagues",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.UpcomingEvent"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/transaction": {
|
||||
"get": {
|
||||
"description": "Gets all the transactions",
|
||||
|
|
@ -4577,70 +4539,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/webhooks/veli": {
|
||||
"post": {
|
||||
"description": "Processes game round settlements from Veli",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Veli Games"
|
||||
],
|
||||
"summary": "Veli Games webhook handler",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Callback payload",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.VeliCallback"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Callback processed",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid payload",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Invalid signature",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Processing error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
|
|
@ -4956,6 +4854,52 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTicketOutcomeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"description": "TicketID int64 ` + "`" + `json:\"ticket_id\" example:\"1\"` + "`" + `",
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"market_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"odd_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTicketReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.CreateTicketOutcomeReq"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTicketRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_number": {
|
||||
"type": "integer",
|
||||
"example": 3
|
||||
},
|
||||
"fast_code": {
|
||||
"type": "integer",
|
||||
"example": 1234
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.DashboardSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5132,6 +5076,10 @@ const docTemplate = `{
|
|||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"is_featured": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "BPL"
|
||||
|
|
@ -5193,6 +5141,17 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.OtpProvider": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"twilio",
|
||||
"aformessage"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"TwilioSms",
|
||||
"AfroMessage"
|
||||
]
|
||||
},
|
||||
"domain.OutcomeStatus": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
|
|
@ -5482,6 +5441,29 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.TicketRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.TicketOutcome"
|
||||
}
|
||||
},
|
||||
"total_odds": {
|
||||
"type": "number",
|
||||
"example": 4.22
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.UpcomingEvent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5551,51 +5533,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.VeliCallback": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"description": "Transaction amount",
|
||||
"type": "number"
|
||||
},
|
||||
"currency": {
|
||||
"description": "e.g., \"USD\"",
|
||||
"type": "string"
|
||||
},
|
||||
"event_type": {
|
||||
"description": "\"bet_placed\", \"game_result\", etc.",
|
||||
"type": "string"
|
||||
},
|
||||
"game_id": {
|
||||
"description": "e.g., \"veli_aviator_v1\"",
|
||||
"type": "string"
|
||||
},
|
||||
"multiplier": {
|
||||
"description": "For games with multipliers (Aviator/Plinko)",
|
||||
"type": "number"
|
||||
},
|
||||
"round_id": {
|
||||
"description": "Unique round identifier (replaces transaction_id)",
|
||||
"type": "string"
|
||||
},
|
||||
"session_id": {
|
||||
"description": "Matches VirtualGameSession.SessionToken",
|
||||
"type": "string"
|
||||
},
|
||||
"signature": {
|
||||
"description": "HMAC-SHA256",
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"description": "Unix timestamp",
|
||||
"type": "integer"
|
||||
},
|
||||
"user_id": {
|
||||
"description": "Veli's user identifier",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.VirtualGame": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5994,52 +5931,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateTicketOutcomeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"description": "TicketID int64 ` + "`" + `json:\"ticket_id\" example:\"1\"` + "`" + `",
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"market_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"odd_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateTicketReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.CreateTicketOutcomeReq"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateTicketRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_number": {
|
||||
"type": "integer",
|
||||
"example": 3
|
||||
},
|
||||
"fast_code": {
|
||||
"type": "integer",
|
||||
"example": 1234
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateTransactionReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -6249,6 +6140,9 @@ const docTemplate = `{
|
|||
},
|
||||
"handlers.RegisterCodeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provider"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
|
|
@ -6257,11 +6151,22 @@ const docTemplate = `{
|
|||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"provider": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OtpProvider"
|
||||
}
|
||||
],
|
||||
"example": "twilio"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.RegisterUserReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provider"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
|
|
@ -6287,6 +6192,14 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"provider": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OtpProvider"
|
||||
}
|
||||
],
|
||||
"example": "twilio"
|
||||
},
|
||||
"referal_code": {
|
||||
"type": "string",
|
||||
"example": "ABC123"
|
||||
|
|
@ -6295,6 +6208,9 @@ const docTemplate = `{
|
|||
},
|
||||
"handlers.ResetCodeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provider"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
|
|
@ -6303,6 +6219,14 @@ const docTemplate = `{
|
|||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"provider": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OtpProvider"
|
||||
}
|
||||
],
|
||||
"example": "twilio"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -6371,29 +6295,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.TicketRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.TicketOutcome"
|
||||
}
|
||||
},
|
||||
"total_odds": {
|
||||
"type": "number",
|
||||
"example": 4.22
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.TransactionRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -803,76 +803,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/veli/launch/{game_id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Generates authenticated launch URL for Veli games",
|
||||
"tags": [
|
||||
"Veli Games"
|
||||
],
|
||||
"summary": "Launch a Veli game",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Game ID (e.g., veli_aviator_v1)",
|
||||
"name": "game_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"default": "USD",
|
||||
"description": "Currency code",
|
||||
"name": "currency",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"real",
|
||||
"demo"
|
||||
],
|
||||
"type": "string",
|
||||
"default": "real",
|
||||
"description": "Game mode",
|
||||
"name": "mode",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Returns launch URL",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"description": "Login customer",
|
||||
|
|
@ -3369,7 +3299,7 @@
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.TicketRes"
|
||||
"$ref": "#/definitions/domain.TicketRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -3406,7 +3336,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateTicketReq"
|
||||
"$ref": "#/definitions/domain.CreateTicketReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3414,7 +3344,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateTicketRes"
|
||||
"$ref": "#/definitions/domain.CreateTicketRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -3458,7 +3388,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.TicketRes"
|
||||
"$ref": "#/definitions/domain.TicketRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -3476,6 +3406,38 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/top-leagues": {
|
||||
"get": {
|
||||
"description": "Retrieve all top leagues",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"prematch"
|
||||
],
|
||||
"summary": "Retrieve all top leagues",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.UpcomingEvent"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/transaction": {
|
||||
"get": {
|
||||
"description": "Gets all the transactions",
|
||||
|
|
@ -4569,70 +4531,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/webhooks/veli": {
|
||||
"post": {
|
||||
"description": "Processes game round settlements from Veli",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Veli Games"
|
||||
],
|
||||
"summary": "Veli Games webhook handler",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Callback payload",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.VeliCallback"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Callback processed",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid payload",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Invalid signature",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Processing error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
|
|
@ -4948,6 +4846,52 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTicketOutcomeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"description": "TicketID int64 `json:\"ticket_id\" example:\"1\"`",
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"market_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"odd_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTicketReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.CreateTicketOutcomeReq"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTicketRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_number": {
|
||||
"type": "integer",
|
||||
"example": 3
|
||||
},
|
||||
"fast_code": {
|
||||
"type": "integer",
|
||||
"example": 1234
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.DashboardSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5124,6 +5068,10 @@
|
|||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"is_featured": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "BPL"
|
||||
|
|
@ -5185,6 +5133,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.OtpProvider": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"twilio",
|
||||
"aformessage"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"TwilioSms",
|
||||
"AfroMessage"
|
||||
]
|
||||
},
|
||||
"domain.OutcomeStatus": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
|
|
@ -5474,6 +5433,29 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.TicketRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.TicketOutcome"
|
||||
}
|
||||
},
|
||||
"total_odds": {
|
||||
"type": "number",
|
||||
"example": 4.22
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.UpcomingEvent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5543,51 +5525,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.VeliCallback": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"description": "Transaction amount",
|
||||
"type": "number"
|
||||
},
|
||||
"currency": {
|
||||
"description": "e.g., \"USD\"",
|
||||
"type": "string"
|
||||
},
|
||||
"event_type": {
|
||||
"description": "\"bet_placed\", \"game_result\", etc.",
|
||||
"type": "string"
|
||||
},
|
||||
"game_id": {
|
||||
"description": "e.g., \"veli_aviator_v1\"",
|
||||
"type": "string"
|
||||
},
|
||||
"multiplier": {
|
||||
"description": "For games with multipliers (Aviator/Plinko)",
|
||||
"type": "number"
|
||||
},
|
||||
"round_id": {
|
||||
"description": "Unique round identifier (replaces transaction_id)",
|
||||
"type": "string"
|
||||
},
|
||||
"session_id": {
|
||||
"description": "Matches VirtualGameSession.SessionToken",
|
||||
"type": "string"
|
||||
},
|
||||
"signature": {
|
||||
"description": "HMAC-SHA256",
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"description": "Unix timestamp",
|
||||
"type": "integer"
|
||||
},
|
||||
"user_id": {
|
||||
"description": "Veli's user identifier",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.VirtualGame": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5986,52 +5923,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateTicketOutcomeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_id": {
|
||||
"description": "TicketID int64 `json:\"ticket_id\" example:\"1\"`",
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"market_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"odd_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateTicketReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.CreateTicketOutcomeReq"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateTicketRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_number": {
|
||||
"type": "integer",
|
||||
"example": 3
|
||||
},
|
||||
"fast_code": {
|
||||
"type": "integer",
|
||||
"example": 1234
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.CreateTransactionReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -6241,6 +6132,9 @@
|
|||
},
|
||||
"handlers.RegisterCodeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provider"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
|
|
@ -6249,11 +6143,22 @@
|
|||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"provider": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OtpProvider"
|
||||
}
|
||||
],
|
||||
"example": "twilio"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.RegisterUserReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provider"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
|
|
@ -6279,6 +6184,14 @@
|
|||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"provider": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OtpProvider"
|
||||
}
|
||||
],
|
||||
"example": "twilio"
|
||||
},
|
||||
"referal_code": {
|
||||
"type": "string",
|
||||
"example": "ABC123"
|
||||
|
|
@ -6287,6 +6200,9 @@
|
|||
},
|
||||
"handlers.ResetCodeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provider"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
|
|
@ -6295,6 +6211,14 @@
|
|||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
},
|
||||
"provider": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.OtpProvider"
|
||||
}
|
||||
],
|
||||
"example": "twilio"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -6363,29 +6287,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.TicketRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"example": 100
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.TicketOutcome"
|
||||
}
|
||||
},
|
||||
"total_odds": {
|
||||
"type": "number",
|
||||
"example": 4.22
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.TransactionRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -211,6 +211,38 @@ definitions:
|
|||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
type: object
|
||||
domain.CreateTicketOutcomeReq:
|
||||
properties:
|
||||
event_id:
|
||||
description: TicketID int64 `json:"ticket_id" example:"1"`
|
||||
example: 1
|
||||
type: integer
|
||||
market_id:
|
||||
example: 1
|
||||
type: integer
|
||||
odd_id:
|
||||
example: 1
|
||||
type: integer
|
||||
type: object
|
||||
domain.CreateTicketReq:
|
||||
properties:
|
||||
amount:
|
||||
example: 100
|
||||
type: number
|
||||
outcomes:
|
||||
items:
|
||||
$ref: '#/definitions/domain.CreateTicketOutcomeReq'
|
||||
type: array
|
||||
type: object
|
||||
domain.CreateTicketRes:
|
||||
properties:
|
||||
created_number:
|
||||
example: 3
|
||||
type: integer
|
||||
fast_code:
|
||||
example: 1234
|
||||
type: integer
|
||||
type: object
|
||||
domain.DashboardSummary:
|
||||
properties:
|
||||
active_admins:
|
||||
|
|
@ -337,6 +369,9 @@ definitions:
|
|||
is_active:
|
||||
example: false
|
||||
type: boolean
|
||||
is_featured:
|
||||
example: false
|
||||
type: boolean
|
||||
name:
|
||||
example: BPL
|
||||
type: string
|
||||
|
|
@ -378,6 +413,14 @@ definitions:
|
|||
source:
|
||||
type: string
|
||||
type: object
|
||||
domain.OtpProvider:
|
||||
enum:
|
||||
- twilio
|
||||
- aformessage
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- TwilioSms
|
||||
- AfroMessage
|
||||
domain.OutcomeStatus:
|
||||
enum:
|
||||
- 0
|
||||
|
|
@ -583,6 +626,22 @@ definitions:
|
|||
example: 1
|
||||
type: integer
|
||||
type: object
|
||||
domain.TicketRes:
|
||||
properties:
|
||||
amount:
|
||||
example: 100
|
||||
type: number
|
||||
id:
|
||||
example: 1
|
||||
type: integer
|
||||
outcomes:
|
||||
items:
|
||||
$ref: '#/definitions/domain.TicketOutcome'
|
||||
type: array
|
||||
total_odds:
|
||||
example: 4.22
|
||||
type: number
|
||||
type: object
|
||||
domain.UpcomingEvent:
|
||||
properties:
|
||||
away_kit_image:
|
||||
|
|
@ -632,39 +691,6 @@ definitions:
|
|||
- $ref: '#/definitions/domain.EventStatus'
|
||||
description: Match Status for event
|
||||
type: object
|
||||
domain.VeliCallback:
|
||||
properties:
|
||||
amount:
|
||||
description: Transaction amount
|
||||
type: number
|
||||
currency:
|
||||
description: e.g., "USD"
|
||||
type: string
|
||||
event_type:
|
||||
description: '"bet_placed", "game_result", etc.'
|
||||
type: string
|
||||
game_id:
|
||||
description: e.g., "veli_aviator_v1"
|
||||
type: string
|
||||
multiplier:
|
||||
description: For games with multipliers (Aviator/Plinko)
|
||||
type: number
|
||||
round_id:
|
||||
description: Unique round identifier (replaces transaction_id)
|
||||
type: string
|
||||
session_id:
|
||||
description: Matches VirtualGameSession.SessionToken
|
||||
type: string
|
||||
signature:
|
||||
description: HMAC-SHA256
|
||||
type: string
|
||||
timestamp:
|
||||
description: Unix timestamp
|
||||
type: integer
|
||||
user_id:
|
||||
description: Veli's user identifier
|
||||
type: string
|
||||
type: object
|
||||
domain.VirtualGame:
|
||||
properties:
|
||||
category:
|
||||
|
|
@ -946,38 +972,6 @@ definitions:
|
|||
example: SportsBook
|
||||
type: string
|
||||
type: object
|
||||
handlers.CreateTicketOutcomeReq:
|
||||
properties:
|
||||
event_id:
|
||||
description: TicketID int64 `json:"ticket_id" example:"1"`
|
||||
example: 1
|
||||
type: integer
|
||||
market_id:
|
||||
example: 1
|
||||
type: integer
|
||||
odd_id:
|
||||
example: 1
|
||||
type: integer
|
||||
type: object
|
||||
handlers.CreateTicketReq:
|
||||
properties:
|
||||
amount:
|
||||
example: 100
|
||||
type: number
|
||||
outcomes:
|
||||
items:
|
||||
$ref: '#/definitions/handlers.CreateTicketOutcomeReq'
|
||||
type: array
|
||||
type: object
|
||||
handlers.CreateTicketRes:
|
||||
properties:
|
||||
created_number:
|
||||
example: 3
|
||||
type: integer
|
||||
fast_code:
|
||||
example: 1234
|
||||
type: integer
|
||||
type: object
|
||||
handlers.CreateTransactionReq:
|
||||
properties:
|
||||
account_name:
|
||||
|
|
@ -1126,6 +1120,12 @@ definitions:
|
|||
phone_number:
|
||||
example: "1234567890"
|
||||
type: string
|
||||
provider:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OtpProvider'
|
||||
example: twilio
|
||||
required:
|
||||
- provider
|
||||
type: object
|
||||
handlers.RegisterUserReq:
|
||||
properties:
|
||||
|
|
@ -1147,9 +1147,15 @@ definitions:
|
|||
phone_number:
|
||||
example: "1234567890"
|
||||
type: string
|
||||
provider:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OtpProvider'
|
||||
example: twilio
|
||||
referal_code:
|
||||
example: ABC123
|
||||
type: string
|
||||
required:
|
||||
- provider
|
||||
type: object
|
||||
handlers.ResetCodeReq:
|
||||
properties:
|
||||
|
|
@ -1159,6 +1165,12 @@ definitions:
|
|||
phone_number:
|
||||
example: "1234567890"
|
||||
type: string
|
||||
provider:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OtpProvider'
|
||||
example: twilio
|
||||
required:
|
||||
- provider
|
||||
type: object
|
||||
handlers.ResetPasswordReq:
|
||||
properties:
|
||||
|
|
@ -1205,22 +1217,6 @@ definitions:
|
|||
example: SportsBook
|
||||
type: string
|
||||
type: object
|
||||
handlers.TicketRes:
|
||||
properties:
|
||||
amount:
|
||||
example: 100
|
||||
type: number
|
||||
id:
|
||||
example: 1
|
||||
type: integer
|
||||
outcomes:
|
||||
items:
|
||||
$ref: '#/definitions/domain.TicketOutcome'
|
||||
type: array
|
||||
total_odds:
|
||||
example: 4.22
|
||||
type: number
|
||||
type: object
|
||||
handlers.TransactionRes:
|
||||
properties:
|
||||
account_name:
|
||||
|
|
@ -2077,52 +2073,6 @@ paths:
|
|||
summary: Process Alea Play game callback
|
||||
tags:
|
||||
- Alea Virtual Games
|
||||
/api/veli/launch/{game_id}:
|
||||
get:
|
||||
description: Generates authenticated launch URL for Veli games
|
||||
parameters:
|
||||
- description: Game ID (e.g., veli_aviator_v1)
|
||||
in: path
|
||||
name: game_id
|
||||
required: true
|
||||
type: string
|
||||
- default: USD
|
||||
description: Currency code
|
||||
in: query
|
||||
name: currency
|
||||
type: string
|
||||
- default: real
|
||||
description: Game mode
|
||||
enum:
|
||||
- real
|
||||
- demo
|
||||
in: query
|
||||
name: mode
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Returns launch URL
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Invalid request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Launch a Veli game
|
||||
tags:
|
||||
- Veli Games
|
||||
/auth/login:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -3766,7 +3716,7 @@ paths:
|
|||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/handlers.TicketRes'
|
||||
$ref: '#/definitions/domain.TicketRes'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
|
|
@ -3789,14 +3739,14 @@ paths:
|
|||
name: createTicket
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.CreateTicketReq'
|
||||
$ref: '#/definitions/domain.CreateTicketReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.CreateTicketRes'
|
||||
$ref: '#/definitions/domain.CreateTicketRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -3825,7 +3775,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.TicketRes'
|
||||
$ref: '#/definitions/domain.TicketRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -3837,6 +3787,27 @@ paths:
|
|||
summary: Get ticket by ID
|
||||
tags:
|
||||
- ticket
|
||||
/top-leagues:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve all top leagues
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.UpcomingEvent'
|
||||
type: array
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Retrieve all top leagues
|
||||
tags:
|
||||
- prematch
|
||||
/transaction:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -4551,48 +4522,6 @@ paths:
|
|||
summary: Activate and Deactivate Wallet
|
||||
tags:
|
||||
- wallet
|
||||
/webhooks/veli:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Processes game round settlements from Veli
|
||||
parameters:
|
||||
- description: Callback payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.VeliCallback'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Callback processed
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Invalid payload
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"403":
|
||||
description: Invalid signature
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Processing error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Veli Games webhook handler
|
||||
tags:
|
||||
- Veli Games
|
||||
securityDefinitions:
|
||||
Bearer:
|
||||
in: header
|
||||
|
|
|
|||
|
|
@ -324,6 +324,13 @@ type Result struct {
|
|||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
}
|
||||
|
||||
type Setting struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
}
|
||||
|
||||
type SupportedOperation struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
|
|||
65
gen/db/settings.sql.go
Normal file
65
gen/db/settings.sql.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// source: settings.sql
|
||||
|
||||
package dbgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const GetSettings = `-- name: GetSettings :many
|
||||
SELECT key, value, created_at, updated_at
|
||||
from settings
|
||||
`
|
||||
|
||||
func (q *Queries) GetSettings(ctx context.Context) ([]Setting, error) {
|
||||
rows, err := q.db.Query(ctx, GetSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Setting
|
||||
for rows.Next() {
|
||||
var i Setting
|
||||
if err := rows.Scan(
|
||||
&i.Key,
|
||||
&i.Value,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const SaveSetting = `-- name: SaveSetting :one
|
||||
INSERT INTO settings (key, value, updated_at)
|
||||
VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO
|
||||
UPDATE
|
||||
SET value = EXCLUDED.value
|
||||
RETURNING key, value, created_at, updated_at
|
||||
`
|
||||
|
||||
type SaveSettingParams struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (q *Queries) SaveSetting(ctx context.Context, arg SaveSettingParams) (Setting, error) {
|
||||
row := q.db.QueryRow(ctx, SaveSetting, arg.Key, arg.Value)
|
||||
var i Setting
|
||||
err := row.Scan(
|
||||
&i.Key,
|
||||
&i.Value,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
15
internal/domain/settings.go
Normal file
15
internal/domain/settings.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
type Setting struct {
|
||||
Key string
|
||||
Value string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type SettingRes struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
|
@ -53,3 +53,31 @@ type CreateTicket struct {
|
|||
TotalOdds float32
|
||||
IP string
|
||||
}
|
||||
|
||||
type CreateTicketOutcomeReq struct {
|
||||
// TicketID int64 `json:"ticket_id" example:"1"`
|
||||
EventID int64 `json:"event_id" example:"1"`
|
||||
OddID int64 `json:"odd_id" example:"1"`
|
||||
MarketID int64 `json:"market_id" example:"1"`
|
||||
// HomeTeamName string `json:"home_team_name" example:"Manchester"`
|
||||
// AwayTeamName string `json:"away_team_name" example:"Liverpool"`
|
||||
// MarketName string `json:"market_name" example:"Fulltime Result"`
|
||||
// Odd float32 `json:"odd" example:"1.5"`
|
||||
// OddName string `json:"odd_name" example:"1"`
|
||||
// Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
|
||||
}
|
||||
|
||||
type CreateTicketReq struct {
|
||||
Outcomes []CreateTicketOutcomeReq `json:"outcomes"`
|
||||
Amount float32 `json:"amount" example:"100.0"`
|
||||
}
|
||||
type CreateTicketRes struct {
|
||||
FastCode int64 `json:"fast_code" example:"1234"`
|
||||
CreatedNumber int64 `json:"created_number" example:"3"`
|
||||
}
|
||||
type TicketRes struct {
|
||||
ID int64 `json:"id" example:"1"`
|
||||
Outcomes []TicketOutcome `json:"outcomes"`
|
||||
Amount float32 `json:"amount" example:"100.0"`
|
||||
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
||||
}
|
||||
|
|
|
|||
49
internal/repository/settings.go
Normal file
49
internal/repository/settings.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) {
|
||||
settings, err := s.queries.GetSettings(ctx)
|
||||
|
||||
if err != nil {
|
||||
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
|
||||
}
|
||||
|
||||
var result []domain.Setting = make([]domain.Setting, 0, len(settings))
|
||||
for _, setting := range settings {
|
||||
result = append(result, domain.Setting{
|
||||
Key: setting.Key,
|
||||
Value: setting.Value,
|
||||
UpdatedAt: setting.UpdatedAt.Time,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) {
|
||||
dbSetting, err := s.queries.SaveSetting(ctx, dbgen.SaveSettingParams{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
domain.MongoDBLogger.Error("failed to update setting", zap.String("key", key), zap.String("value", value), zap.Error(err))
|
||||
|
||||
return domain.Setting{}, err
|
||||
}
|
||||
|
||||
setting := domain.Setting{
|
||||
Key: dbSetting.Key,
|
||||
Value: dbSetting.Value,
|
||||
}
|
||||
|
||||
return setting, err
|
||||
|
||||
}
|
||||
|
|
@ -29,6 +29,11 @@ var (
|
|||
ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
|
||||
ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
|
||||
ErrEventHasBeenRemoved = errors.New("Event has been removed")
|
||||
|
||||
ErrEventHasNotEnded = errors.New("Event has not ended yet")
|
||||
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
|
||||
ErrBranchIDRequired = errors.New("Branch ID required for this role")
|
||||
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
|
|
@ -41,7 +46,15 @@ type Service struct {
|
|||
mongoLogger *zap.Logger
|
||||
}
|
||||
|
||||
func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.ServiceImpl, walletSvc wallet.Service, branchSvc branch.Service, logger *slog.Logger, mongoLogger *zap.Logger) *Service {
|
||||
func NewService(
|
||||
betStore BetStore,
|
||||
eventSvc event.Service,
|
||||
prematchSvc odds.ServiceImpl,
|
||||
walletSvc wallet.Service,
|
||||
branchSvc branch.Service,
|
||||
logger *slog.Logger,
|
||||
mongoLogger *zap.Logger,
|
||||
) *Service {
|
||||
return &Service{
|
||||
betStore: betStore,
|
||||
eventSvc: eventSvc,
|
||||
|
|
@ -53,13 +66,6 @@ func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.Serv
|
|||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrEventHasNotEnded = errors.New("Event has not ended yet")
|
||||
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
|
||||
ErrBranchIDRequired = errors.New("Branch ID required for this role")
|
||||
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
|
||||
)
|
||||
|
||||
func (s *Service) GenerateCashoutID() (string, error) {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
const length int = 13
|
||||
|
|
|
|||
12
internal/services/settings/port.go
Normal file
12
internal/services/settings/port.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
type SettingStore interface {
|
||||
GetSettings(ctx context.Context) ([]domain.Setting, error)
|
||||
SaveSetting(ctx context.Context, key, value string) (domain.Setting, error)
|
||||
}
|
||||
25
internal/services/settings/service.go
Normal file
25
internal/services/settings/service.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
settingStore SettingStore
|
||||
}
|
||||
|
||||
func NewService(settingStore SettingStore) *Service {
|
||||
return &Service{
|
||||
settingStore: settingStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) GetSettings(ctx context.Context) ([]domain.Setting, error) {
|
||||
return s.settingStore.GetSettings(ctx)
|
||||
}
|
||||
|
||||
func (s *Service) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) {
|
||||
return s.settingStore.SaveSetting(ctx, key, value)
|
||||
}
|
||||
|
|
@ -2,24 +2,231 @@ package ticket
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
|
||||
// ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
|
||||
ErrEventHasNotEnded = errors.New("Event has not ended yet")
|
||||
ErrNoEventsAvailable = errors.New("Not enough events available with the given filters")
|
||||
ErrEventHasBeenRemoved = errors.New("Event has been removed")
|
||||
ErrTooManyOutcomesForTicket = errors.New("Too many odds/outcomes for a single ticket")
|
||||
ErrTicketAmountTooHigh = errors.New("Cannot create a ticket with an amount above limit")
|
||||
ErrTicketLimitForSingleUser = errors.New("Number of Ticket Limit reached")
|
||||
ErrTicketWinningTooHigh = errors.New("Total Winnings over set limit")
|
||||
|
||||
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
ticketStore TicketStore
|
||||
eventSvc event.Service
|
||||
prematchSvc odds.ServiceImpl
|
||||
mongoLogger *zap.Logger
|
||||
}
|
||||
|
||||
func NewService(ticketStore TicketStore) *Service {
|
||||
func NewService(
|
||||
ticketStore TicketStore,
|
||||
eventSvc event.Service,
|
||||
prematchSvc odds.ServiceImpl,
|
||||
mongoLogger *zap.Logger,
|
||||
) *Service {
|
||||
return &Service{
|
||||
ticketStore: ticketStore,
|
||||
eventSvc: eventSvc,
|
||||
prematchSvc: prematchSvc,
|
||||
mongoLogger: mongoLogger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) {
|
||||
return s.ticketStore.CreateTicket(ctx, ticket)
|
||||
func (s *Service) GenerateTicketOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateTicketOutcome, error) {
|
||||
eventIDStr := strconv.FormatInt(eventID, 10)
|
||||
marketIDStr := strconv.FormatInt(marketID, 10)
|
||||
oddIDStr := strconv.FormatInt(oddID, 10)
|
||||
event, err := s.eventSvc.GetUpcomingEventByID(ctx, eventIDStr)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to fetch upcoming event by ID",
|
||||
zap.Int64("event_id", eventID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return domain.CreateTicketOutcome{}, ErrEventHasBeenRemoved
|
||||
}
|
||||
|
||||
// Checking to make sure the event hasn't already started
|
||||
currentTime := time.Now()
|
||||
if event.StartTime.Before(currentTime) {
|
||||
s.mongoLogger.Error("event has already started",
|
||||
zap.Int64("event_id", eventID),
|
||||
zap.Time("event_start_time", event.StartTime),
|
||||
zap.Time("current_time", currentTime),
|
||||
)
|
||||
return domain.CreateTicketOutcome{}, ErrEventHasNotEnded
|
||||
}
|
||||
|
||||
odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr)
|
||||
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get raw odds by market ID",
|
||||
zap.Int64("event_id", eventID),
|
||||
zap.Int64("market_id", marketID),
|
||||
zap.Error(err),
|
||||
)
|
||||
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil)
|
||||
|
||||
}
|
||||
type rawOddType struct {
|
||||
ID string
|
||||
Name string
|
||||
Odds string
|
||||
Header string
|
||||
Handicap string
|
||||
}
|
||||
var selectedOdd rawOddType
|
||||
var isOddFound bool = false
|
||||
for _, raw := range odds.RawOdds {
|
||||
var rawOdd rawOddType
|
||||
rawBytes, err := json.Marshal(raw)
|
||||
err = json.Unmarshal(rawBytes, &rawOdd)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to unmarshal raw ods",
|
||||
zap.Int64("event_id", eventID),
|
||||
zap.String("rawOddID", rawOdd.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if rawOdd.ID == oddIDStr {
|
||||
selectedOdd = rawOdd
|
||||
isOddFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if !isOddFound {
|
||||
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
|
||||
s.mongoLogger.Error("Invalid Odd ID",
|
||||
zap.Int64("event_id", eventID),
|
||||
zap.String("oddIDStr", oddIDStr),
|
||||
)
|
||||
return domain.CreateTicketOutcome{}, ErrRawOddInvalid
|
||||
}
|
||||
|
||||
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to parse selected odd value",
|
||||
zap.String("odd", selectedOdd.Odds),
|
||||
zap.Int64("odd_id", oddID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return domain.CreateTicketOutcome{}, err
|
||||
}
|
||||
|
||||
newOutcome := domain.CreateTicketOutcome{
|
||||
EventID: eventID,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
HomeTeamName: event.HomeTeam,
|
||||
AwayTeamName: event.AwayTeam,
|
||||
MarketName: odds.MarketName,
|
||||
Odd: float32(parsedOdd),
|
||||
OddName: selectedOdd.Name,
|
||||
OddHeader: selectedOdd.Header,
|
||||
OddHandicap: selectedOdd.Handicap,
|
||||
Expires: event.StartTime,
|
||||
}
|
||||
|
||||
// outcomes = append(outcomes, )
|
||||
|
||||
return newOutcome, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq, clientIP string) (domain.Ticket, int64, error) {
|
||||
// TODO Validate Outcomes Here and make sure they didn't expire
|
||||
// Validation for creating tickets
|
||||
if len(req.Outcomes) > 30 {
|
||||
// return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
|
||||
return domain.Ticket{}, 0, ErrTooManyOutcomesForTicket
|
||||
|
||||
}
|
||||
|
||||
if req.Amount > 100000 {
|
||||
// return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with an amount above 100,000 birr", nil, nil)
|
||||
return domain.Ticket{}, 0, ErrTicketAmountTooHigh
|
||||
}
|
||||
|
||||
count, err := s.CountTicketByIP(ctx, clientIP)
|
||||
|
||||
if err != nil {
|
||||
// return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching user info", nil, nil)
|
||||
return domain.Ticket{}, 0, err
|
||||
}
|
||||
|
||||
if count > 50 {
|
||||
// return response.WriteJSON(c, fiber.StatusBadRequest, "Ticket Limit reached", nil, nil)
|
||||
return domain.Ticket{}, 0, ErrTicketLimitForSingleUser
|
||||
}
|
||||
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
|
||||
var totalOdds float32 = 1
|
||||
for _, outcomeReq := range req.Outcomes {
|
||||
newOutcome, err := s.GenerateTicketOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to generate outcome",
|
||||
zap.Int64("event_id", outcomeReq.EventID),
|
||||
zap.Int64("market_id", outcomeReq.MarketID),
|
||||
zap.Int64("odd_id", outcomeReq.OddID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return domain.Ticket{}, 0, err
|
||||
}
|
||||
totalOdds *= float32(newOutcome.Odd)
|
||||
outcomes = append(outcomes, newOutcome)
|
||||
}
|
||||
totalWinnings := req.Amount * totalOdds
|
||||
if totalWinnings > 1000000 {
|
||||
s.mongoLogger.Error("Total Winnings over limit", zap.Float32("Total Odds", totalOdds), zap.Float32("amount", req.Amount))
|
||||
// return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with 1,000,000 winnings", nil, nil)
|
||||
return domain.Ticket{}, 0, ErrTicketWinningTooHigh
|
||||
}
|
||||
|
||||
ticket, err := s.ticketStore.CreateTicket(ctx, domain.CreateTicket{
|
||||
Amount: domain.ToCurrency(req.Amount),
|
||||
TotalOdds: totalOdds,
|
||||
IP: clientIP,
|
||||
})
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("Error Creating Ticket", zap.Float32("Total Odds", totalOdds), zap.Float32("amount", req.Amount))
|
||||
return domain.Ticket{}, 0, err
|
||||
}
|
||||
|
||||
// Add the ticket id now that it has fetched from the database
|
||||
for index := range outcomes {
|
||||
outcomes[index].TicketID = ticket.ID
|
||||
}
|
||||
|
||||
rows, err := s.CreateTicketOutcome(ctx, outcomes)
|
||||
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("Error Creating Ticket Outcomes", zap.Any("outcomes", outcomes))
|
||||
return domain.Ticket{}, rows, err
|
||||
}
|
||||
|
||||
return ticket, rows, nil
|
||||
}
|
||||
|
||||
// func (s *Service) CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) {
|
||||
// return s.ticketStore.CreateTicket(ctx, ticket)
|
||||
// }
|
||||
|
||||
func (s *Service) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) {
|
||||
return s.ticketStore.CreateTicketOutcome(ctx, outcomes)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,27 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type CreateTicketOutcomeReq struct {
|
||||
// TicketID int64 `json:"ticket_id" example:"1"`
|
||||
EventID int64 `json:"event_id" example:"1"`
|
||||
OddID int64 `json:"odd_id" example:"1"`
|
||||
MarketID int64 `json:"market_id" example:"1"`
|
||||
// HomeTeamName string `json:"home_team_name" example:"Manchester"`
|
||||
// AwayTeamName string `json:"away_team_name" example:"Liverpool"`
|
||||
// MarketName string `json:"market_name" example:"Fulltime Result"`
|
||||
// Odd float32 `json:"odd" example:"1.5"`
|
||||
// OddName string `json:"odd_name" example:"1"`
|
||||
// Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
|
||||
}
|
||||
|
||||
type CreateTicketReq struct {
|
||||
Outcomes []CreateTicketOutcomeReq `json:"outcomes"`
|
||||
Amount float32 `json:"amount" example:"100.0"`
|
||||
}
|
||||
type CreateTicketRes struct {
|
||||
FastCode int64 `json:"fast_code" example:"1234"`
|
||||
CreatedNumber int64 `json:"created_number" example:"3"`
|
||||
}
|
||||
type TicketRes struct {
|
||||
ID int64 `json:"id" example:"1"`
|
||||
Outcomes []domain.TicketOutcome `json:"outcomes"`
|
||||
Amount float32 `json:"amount" example:"100.0"`
|
||||
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
||||
}
|
||||
|
||||
// CreateTicket godoc
|
||||
// @Summary Create a temporary ticket
|
||||
// @Description Creates a temporary ticket
|
||||
// @Tags ticket
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param createTicket body CreateTicketReq true "Creates ticket"
|
||||
// @Success 200 {object} CreateTicketRes
|
||||
// @Param createTicket body domain.CreateTicketReq true "Creates ticket"
|
||||
// @Success 200 {object} domain.CreateTicketRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /ticket [post]
|
||||
func (h *Handler) CreateTicket(c *fiber.Ctx) error {
|
||||
var req CreateTicketReq
|
||||
var req domain.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")
|
||||
|
|
@ -60,122 +31,17 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
// TODO Validate Outcomes Here and make sure they didn't expire
|
||||
// Validation for creating tickets
|
||||
if len(req.Outcomes) > 30 {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
|
||||
}
|
||||
|
||||
if req.Amount > 100000 {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with an amount above 100,000 birr", nil, nil)
|
||||
}
|
||||
|
||||
clientIP := c.IP()
|
||||
count, err := h.ticketSvc.CountTicketByIP(c.Context(), clientIP)
|
||||
if err != nil {
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching user info", nil, nil)
|
||||
}
|
||||
|
||||
if count > 50 {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Ticket Limit reached", nil, nil)
|
||||
}
|
||||
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
|
||||
var totalOdds float32 = 1
|
||||
for _, outcome := range req.Outcomes {
|
||||
eventIDStr := strconv.FormatInt(outcome.EventID, 10)
|
||||
marketIDStr := strconv.FormatInt(outcome.MarketID, 10)
|
||||
oddIDStr := strconv.FormatInt(outcome.OddID, 10)
|
||||
event, err := h.eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr)
|
||||
if err != nil {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil)
|
||||
}
|
||||
|
||||
// Checking to make sure the event hasn't already started
|
||||
currentTime := time.Now()
|
||||
if event.StartTime.Before(currentTime) {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
|
||||
}
|
||||
|
||||
odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
|
||||
|
||||
if err != nil {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil)
|
||||
}
|
||||
type rawOddType struct {
|
||||
ID string
|
||||
Name string
|
||||
Odds string
|
||||
Header string
|
||||
Handicap string
|
||||
}
|
||||
var selectedOdd rawOddType
|
||||
var isOddFound bool = false
|
||||
for _, raw := range odds.RawOdds {
|
||||
var rawOdd rawOddType
|
||||
rawBytes, err := json.Marshal(raw)
|
||||
err = json.Unmarshal(rawBytes, &rawOdd)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to unmarshal raw odd:", "error", err)
|
||||
continue
|
||||
}
|
||||
if rawOdd.ID == oddIDStr {
|
||||
selectedOdd = rawOdd
|
||||
isOddFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if !isOddFound {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
|
||||
}
|
||||
|
||||
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
||||
totalOdds = totalOdds * float32(parsedOdd)
|
||||
outcomes = append(outcomes, domain.CreateTicketOutcome{
|
||||
EventID: outcome.EventID,
|
||||
OddID: outcome.OddID,
|
||||
MarketID: outcome.MarketID,
|
||||
HomeTeamName: event.HomeTeam,
|
||||
AwayTeamName: event.AwayTeam,
|
||||
MarketName: odds.MarketName,
|
||||
Odd: float32(parsedOdd),
|
||||
OddName: selectedOdd.Name,
|
||||
OddHeader: selectedOdd.Header,
|
||||
OddHandicap: selectedOdd.Handicap,
|
||||
Expires: event.StartTime,
|
||||
})
|
||||
|
||||
}
|
||||
totalWinnings := req.Amount * totalOdds
|
||||
if totalWinnings > 1000000 {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with 1,000,000 winnings", nil, nil)
|
||||
}
|
||||
ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{
|
||||
Amount: domain.ToCurrency(req.Amount),
|
||||
TotalOdds: totalOdds,
|
||||
IP: clientIP,
|
||||
})
|
||||
if err != nil {
|
||||
h.logger.Error("CreateTicketReq failed", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Internal server error",
|
||||
})
|
||||
}
|
||||
|
||||
// Add the ticket id now that it has fetched from the database
|
||||
for index := range outcomes {
|
||||
outcomes[index].TicketID = ticket.ID
|
||||
}
|
||||
|
||||
rows, err := h.ticketSvc.CreateTicketOutcome(c.Context(), outcomes)
|
||||
newTicket, rows, err := h.ticketSvc.CreateTicket(c.Context(), req, c.IP())
|
||||
|
||||
if err != nil {
|
||||
h.logger.Error("CreateTicketReq failed to create outcomes", "error", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Internal server error",
|
||||
})
|
||||
switch err {
|
||||
case ticket.ErrEventHasBeenRemoved, ticket.ErrEventHasNotEnded, ticket.ErrRawOddInvalid:
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
res := CreateTicketRes{
|
||||
FastCode: ticket.ID,
|
||||
res := domain.CreateTicketRes{
|
||||
FastCode: newTicket.ID,
|
||||
CreatedNumber: rows,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Ticket Created", res, nil)
|
||||
|
|
@ -189,7 +55,7 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Ticket ID"
|
||||
// @Success 200 {object} TicketRes
|
||||
// @Success 200 {object} domain.TicketRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /ticket/{id} [get]
|
||||
|
|
@ -207,7 +73,7 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket")
|
||||
}
|
||||
|
||||
res := TicketRes{
|
||||
res := domain.TicketRes{
|
||||
ID: ticket.ID,
|
||||
Outcomes: ticket.Outcomes,
|
||||
Amount: ticket.Amount.Float32(),
|
||||
|
|
@ -222,7 +88,7 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
|
|||
// @Tags ticket
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} TicketRes
|
||||
// @Success 200 {array} domain.TicketRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /ticket [get]
|
||||
|
|
@ -234,9 +100,9 @@ func (h *Handler) GetAllTickets(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve tickets")
|
||||
}
|
||||
|
||||
res := make([]TicketRes, len(tickets))
|
||||
res := make([]domain.TicketRes, len(tickets))
|
||||
for i, ticket := range tickets {
|
||||
res[i] = TicketRes{
|
||||
res[i] = domain.TicketRes{
|
||||
ID: ticket.ID,
|
||||
Outcomes: ticket.Outcomes,
|
||||
Amount: ticket.Amount.Float32(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user