Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
ScoutingSystemV2
/
ScoutingSystemElite-Api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
5917bcb1
authored
Nov 26, 2025
by
Augusto
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
small fixes
parent
7730f929
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
527 additions
and
124 deletions
+527
-124
drizzle/0015_update_agents_country_to_fk.sql
+21
-0
drizzle/0016_remove_agent_id_from_players.sql
+9
-0
src/database/schema.ts
+4
-4
src/modules/agents/agents.controller.ts
+42
-27
src/modules/agents/agents.service.ts
+1
-1
src/modules/agents/dto/create-agent.dto.ts
+10
-4
src/modules/player-features/player-features.service.ts
+1
-0
src/modules/players/interfaces/player.interface.ts
+12
-7
src/modules/players/players.controller.ts
+31
-21
src/modules/players/players.service.ts
+376
-60
src/modules/profiles/dto/create-link.dto.ts
+9
-0
src/modules/profiles/dto/update-link.dto.ts
+9
-0
src/modules/profiles/profiles.service.ts
+2
-0
No files found.
drizzle/0015_update_agents_country_to_fk.sql
0 → 100644
View file @
5917bcb1
-- Update agents table to use countryWyId foreign key instead of country text field
-- This migration:
-- 1. Adds the new countryWyId column with a foreign key reference to areas.wy_id
-- 2. Drops the old country column
-- 3. Adds an index on countryWyId for query performance
-- Add new countryWyId column (initially nullable to allow migration)
ALTER
TABLE
agents
ADD
COLUMN
country_wy_id
INTEGER
;
-- Add index for query performance
CREATE
INDEX
idx_agents_country_wy_id
ON
agents
(
country_wy_id
);
-- Drop the old country column
ALTER
TABLE
agents
DROP
COLUMN
country
;
-- Add foreign key constraint (after dropping old column to avoid conflicts)
ALTER
TABLE
agents
ADD
CONSTRAINT
agents_country_wy_id_fk
FOREIGN
KEY
(
country_wy_id
)
REFERENCES
areas
(
wy_id
)
ON
DELETE
RESTRICT
;
-- Make countryWyId NOT NULL after migration
ALTER
TABLE
agents
ALTER
COLUMN
country_wy_id
SET
NOT
NULL
;
drizzle/0016_remove_agent_id_from_players.sql
0 → 100644
View file @
5917bcb1
-- Remove agentId column from players table
-- Agent relationships are now managed through the playerAgents junction table
-- This migration drops the direct foreign key reference to agents
-- Drop the foreign key constraint if it exists
ALTER
TABLE
players
DROP
CONSTRAINT
IF
EXISTS
players_agent_id_fk
;
-- Drop the agentId column
ALTER
TABLE
players
DROP
COLUMN
IF
EXISTS
agent_id
;
src/database/schema.ts
View file @
5917bcb1
...
...
@@ -213,9 +213,6 @@ export const players = pgTable(
// Transfer and financial information
onLoan
:
boolean
(
'on_loan'
).
default
(
false
),
agentId
:
integer
(
'agent_id'
).
references
(()
=>
agents
.
id
,
{
onDelete
:
'set null'
,
}),
ranking
:
varchar
(
'ranking'
,
{
length
:
255
}),
roi
:
varchar
(
'roi'
,
{
length
:
255
}),
// Return on Investment
marketValue
:
decimal
(
'market_value'
,
{
precision
:
15
,
scale
:
2
}),
// Monetary value
...
...
@@ -428,7 +425,9 @@ export const agents = pgTable('agents', {
phone
:
text
(
'phone'
).
notNull
(),
status
:
text
(
'status'
).
notNull
(),
address
:
text
(
'address'
).
notNull
(),
country
:
text
(
'country'
).
notNull
(),
countryWyId
:
integer
(
'country_wy_id'
)
.
notNull
()
.
references
(()
=>
areas
.
wyId
,
{
onDelete
:
'restrict'
}),
createdAt
:
timestamp
(
'created_at'
).
notNull
().
defaultNow
(),
updatedAt
:
timestamp
(
'updated_at'
).
notNull
().
defaultNow
(),
});
...
...
@@ -1042,6 +1041,7 @@ export const profileLinks = pgTable(
}),
title
:
varchar
(
'title'
,
{
length
:
255
}).
notNull
(),
// e.g., "YouTube", "Instagram", "Portfolio"
url
:
text
(
'url'
).
notNull
(),
icon
:
varchar
(
'icon'
,
{
length
:
255
}),
order
:
integer
(
'order'
).
default
(
0
),
isActive
:
boolean
(
'is_active'
).
default
(
true
),
createdAt
:
timestamp
(
'created_at'
,
{
withTimezone
:
true
}).
defaultNow
(),
...
...
src/modules/agents/agents.controller.ts
View file @
5917bcb1
...
...
@@ -39,11 +39,14 @@ export class AgentsController {
example1
:
{
summary
:
'Create new agent'
,
value
:
{
firstName
:
'John
'
,
lastName
:
'Smith
'
,
name
:
'John Smith
'
,
type
:
'individual
'
,
email
:
'john.smith@agents.com'
,
phone
:
'+1234567890'
,
company
:
'Elite Sports Management'
,
status
:
'active'
,
address
:
'123 Main St, New York'
,
countryWyId
:
1
,
description
:
'Elite sports agent'
,
},
},
},
...
...
@@ -53,14 +56,16 @@ export class AgentsController {
schema
:
{
example
:
{
id
:
1
,
firstName
:
'John
'
,
lastName
:
'Smith
'
,
name
:
'John Smith
'
,
type
:
'individual
'
,
email
:
'john.smith@agents.com'
,
phone
:
'+1234567890'
,
company
:
'Elite Sports Management'
,
status
:
'active'
,
address
:
'123 Main St, New York'
,
countryWyId
:
1
,
description
:
'Elite sports agent'
,
createdAt
:
'2025-01-15T10:30:00Z'
,
updatedAt
:
'2025-01-15T10:30:00Z'
,
deletedAt
:
null
,
},
},
})
...
...
@@ -92,14 +97,16 @@ export class AgentsController {
schema
:
{
example
:
{
id
:
1
,
firstName
:
'John
'
,
lastName
:
'Smith
'
,
name
:
'John Smith
'
,
type
:
'individual
'
,
email
:
'john.smith.new@agents.com'
,
phone
:
'+1987654321'
,
company
:
'Elite Sports Management'
,
status
:
'active'
,
address
:
'123 Main St, New York'
,
countryWyId
:
1
,
description
:
'Elite sports agent'
,
createdAt
:
'2025-01-15T10:30:00Z'
,
updatedAt
:
'2025-01-15T11:00:00Z'
,
deletedAt
:
null
,
},
},
})
...
...
@@ -122,14 +129,16 @@ export class AgentsController {
schema
:
{
example
:
{
id
:
1
,
firstName
:
'John
'
,
lastName
:
'Smith
'
,
name
:
'John Smith
'
,
type
:
'individual
'
,
email
:
'john.smith@agents.com'
,
phone
:
'+1234567890'
,
company
:
'Elite Sports Management'
,
status
:
'active'
,
address
:
'123 Main St, New York'
,
countryWyId
:
1
,
description
:
'Elite sports agent'
,
createdAt
:
'2025-01-15T10:30:00Z'
,
updatedAt
:
'2025-01-15T10:30:00Z'
,
deletedAt
:
null
,
},
},
})
...
...
@@ -149,14 +158,16 @@ export class AgentsController {
example
:
[
{
id
:
1
,
firstName
:
'John
'
,
lastName
:
'Smith
'
,
name
:
'John Smith
'
,
type
:
'individual
'
,
email
:
'john.smith@agents.com'
,
phone
:
'+1234567890'
,
company
:
'Elite Sports Management'
,
status
:
'active'
,
address
:
'123 Main St, New York'
,
countryWyId
:
1
,
description
:
'Elite sports agent'
,
createdAt
:
'2025-01-15T10:30:00Z'
,
updatedAt
:
'2025-01-15T10:30:00Z'
,
deletedAt
:
null
,
},
],
},
...
...
@@ -272,14 +283,16 @@ export class AgentsController {
example
:
[
{
id
:
1
,
firstName
:
'John
'
,
lastName
:
'Smith
'
,
name
:
'John Smith
'
,
type
:
'individual
'
,
email
:
'john.smith@agents.com'
,
phone
:
'+1234567890'
,
company
:
'Elite Sports Management'
,
status
:
'active'
,
address
:
'123 Main St, New York'
,
countryWyId
:
1
,
description
:
'Elite sports agent'
,
createdAt
:
'2025-01-15T10:30:00Z'
,
updatedAt
:
'2025-01-15T10:30:00Z'
,
deletedAt
:
null
,
},
],
},
...
...
@@ -302,14 +315,16 @@ export class AgentsController {
example
:
[
{
id
:
1
,
firstName
:
'John
'
,
lastName
:
'Smith
'
,
name
:
'John Smith
'
,
type
:
'individual
'
,
email
:
'john.smith@agents.com'
,
phone
:
'+1234567890'
,
company
:
'Elite Sports Management'
,
status
:
'active'
,
address
:
'123 Main St, New York'
,
countryWyId
:
1
,
description
:
'Elite sports agent'
,
createdAt
:
'2025-01-15T10:30:00Z'
,
updatedAt
:
'2025-01-15T10:30:00Z'
,
deletedAt
:
null
,
},
],
},
...
...
src/modules/agents/agents.service.ts
View file @
5917bcb1
...
...
@@ -27,7 +27,7 @@ export class AgentsService {
phone
:
data
.
phone
!
,
status
:
data
.
status
!
,
address
:
data
.
address
!
,
country
:
data
.
country
!
,
country
WyId
:
data
.
countryWyId
!
,
description
:
data
.
description
??
null
,
createdAt
:
now
as
any
,
updatedAt
:
now
as
any
,
...
...
src/modules/agents/dto/create-agent.dto.ts
View file @
5917bcb1
import
{
ApiProperty
,
ApiPropertyOptional
}
from
'@nestjs/swagger'
;
import
{
IsEmail
,
IsNotEmpty
,
IsOptional
,
IsString
}
from
'class-validator'
;
import
{
IsEmail
,
IsNotEmpty
,
IsOptional
,
IsString
,
IsNumber
,
}
from
'class-validator'
;
export
class
CreateAgentDto
{
@
ApiProperty
({
description
:
'Agent name'
})
...
...
@@ -31,10 +37,10 @@ export class CreateAgentDto {
@
IsNotEmpty
()
address
:
string
;
@
ApiProperty
({
description
:
'Country'
})
@
Is
String
()
@
ApiProperty
({
description
:
'Country
WY ID (Wyscout area ID)
'
})
@
Is
Number
()
@
IsNotEmpty
()
country
:
string
;
country
WyId
:
number
;
@
ApiPropertyOptional
({
description
:
'Description'
})
@
IsOptional
()
...
...
src/modules/player-features/player-features.service.ts
View file @
5917bcb1
...
...
@@ -561,6 +561,7 @@ export class PlayerFeaturesService {
],
set
:
{
notes
:
data
.
notes
??
null
,
deletedAt
:
null
,
updatedAt
:
new
Date
(),
},
})
...
...
src/modules/players/interfaces/player.interface.ts
View file @
5917bcb1
...
...
@@ -53,6 +53,16 @@ export interface Agent {
updatedAt
:
string
;
}
export
interface
Team
{
id
:
number
|
null
;
wyId
:
number
|
null
;
gsmId
:
number
|
null
;
name
:
string
|
null
;
officialName
:
string
|
null
;
createdAt
:
string
|
null
;
updatedAt
:
string
|
null
;
}
export
interface
StructuredPlayer
{
id
:
number
;
wyId
:
number
;
...
...
@@ -72,13 +82,8 @@ export interface StructuredPlayer {
position
:
PlayerPosition
|
null
;
otherPositions
:
PlayerPosition
[]
|
null
;
foot
:
string
|
null
;
currentTeamId
:
number
|
null
;
currentNationalTeamId
:
number
|
null
;
currentTeamName
:
string
|
null
;
currentTeamOfficialName
:
string
|
null
;
currentNationalTeamName
:
string
|
null
;
currentNationalTeamOfficialName
:
string
|
null
;
currentTeam
:
any
|
null
;
currentTeam
:
Team
|
null
;
currentNationalTeam
:
Team
|
null
;
gender
:
string
;
status
:
string
;
jerseyNumber
:
number
|
null
;
...
...
src/modules/players/players.controller.ts
View file @
5917bcb1
...
...
@@ -85,8 +85,8 @@ export class PlayersController {
return
this
.
playersService
.
upsertByWyId
(
body
as
any
);
}
@
Get
(
'
by-id/
:id'
)
@
ApiOperation
({
summary
:
'Get player by
database
ID'
})
@
Get
(
':id'
)
@
ApiOperation
({
summary
:
'Get player by ID'
})
@
ApiParam
({
name
:
'id'
,
type
:
Number
,
...
...
@@ -104,13 +104,23 @@ export class PlayersController {
return
player
;
}
@
Get
(
':wyId'
)
@
ApiOperation
({
summary
:
'Get player by wyId'
})
@
Get
(
'wy/:wyId'
)
@
ApiOperation
({
summary
:
'Get player by Wyscout ID (wyId)'
})
@
ApiParam
({
name
:
'wyId'
,
type
:
Number
,
description
:
'Wyscout ID (wyId) of the player'
,
})
@
ApiOkResponse
({
description
:
'Player if found'
,
type
:
Object
})
@
ApiNotFoundResponse
({
description
:
'Player not found'
})
async
getByWyId
(
@
Param
(
'wyId'
,
ParseIntPipe
)
wyId
:
number
,
):
Promise
<
StructuredPlayer
|
undefined
>
{
return
this
.
playersService
.
findByWyId
(
wyId
);
const
player
=
await
this
.
playersService
.
findByWyId
(
wyId
);
if
(
!
player
)
{
throw
new
NotFoundException
(
`Player with wyId
${
wyId
}
not found`
);
}
return
player
;
}
@
Get
()
...
...
@@ -286,16 +296,16 @@ export class PlayersController {
return
this
.
playersService
.
findAll
(
l
,
o
,
name
,
query
);
}
@
Patch
(
':
wyI
d'
)
@
Patch
(
':
i
d'
)
@
ApiOperation
({
summary
:
'Update player by
wyId
'
,
summary
:
'Update player by
ID
'
,
description
:
'Updates an existing player. Only provided fields will be updated.'
,
'Updates an existing player
by database ID
. Only provided fields will be updated.'
,
})
@
ApiParam
({
name
:
'
wyI
d'
,
name
:
'
i
d'
,
type
:
Number
,
description
:
'
Wyscout
ID of the player to update'
,
description
:
'
Database
ID of the player to update'
,
})
@
ApiBody
({
description
:
'Player update payload. All fields are optional.'
,
...
...
@@ -338,31 +348,31 @@ export class PlayersController {
},
})
@
ApiNotFoundResponse
({
description
:
'Player not found'
})
async
updateBy
Wy
Id
(
@
Param
(
'
wyId'
,
ParseIntPipe
)
wyI
d
:
number
,
async
updateById
(
@
Param
(
'
id'
,
ParseIntPipe
)
i
d
:
number
,
@
Body
()
body
:
UpdatePlayerDto
,
):
Promise
<
Player
>
{
const
result
=
await
this
.
playersService
.
updateBy
WyId
(
wyI
d
,
body
);
const
result
=
await
this
.
playersService
.
updateBy
Id
(
i
d
,
body
);
if
(
!
result
)
{
throw
new
NotFoundException
(
`Player with
wyId
${
wyI
d
}
not found`
);
throw
new
NotFoundException
(
`Player with
ID
${
i
d
}
not found`
);
}
return
result
;
}
@
Delete
(
':
wyI
d'
)
@
Delete
(
':
i
d'
)
@
HttpCode
(
HttpStatus
.
NO_CONTENT
)
@
ApiOperation
({
summary
:
'Delete player by
wyId
(soft delete)'
})
@
ApiOperation
({
summary
:
'Delete player by
ID
(soft delete)'
})
@
ApiParam
({
name
:
'
wyI
d'
,
name
:
'
i
d'
,
type
:
Number
,
description
:
'
Wyscout
ID of the player to delete'
,
description
:
'
Database
ID of the player to delete'
,
})
@
ApiNoContentResponse
({
description
:
'Player deleted successfully'
})
@
ApiNotFoundResponse
({
description
:
'Player not found'
})
async
deleteBy
WyId
(@
Param
(
'wyId'
,
ParseIntPipe
)
wyI
d
:
number
):
Promise
<
void
>
{
const
result
=
await
this
.
playersService
.
deleteBy
WyId
(
wyI
d
);
async
deleteBy
Id
(@
Param
(
'id'
,
ParseIntPipe
)
i
d
:
number
):
Promise
<
void
>
{
const
result
=
await
this
.
playersService
.
deleteBy
Id
(
i
d
);
if
(
!
result
)
{
throw
new
NotFoundException
(
`Player with
wyId
${
wyI
d
}
not found`
);
throw
new
NotFoundException
(
`Player with
ID
${
i
d
}
not found`
);
}
}
}
src/modules/players/players.service.ts
View file @
5917bcb1
...
...
@@ -6,6 +6,7 @@ import {
areas
,
positions
,
agents
,
playerAgents
,
type
NewPlayer
,
type
Player
,
type
Position
,
...
...
@@ -291,26 +292,6 @@ export class PlayersService {
:
null
,
otherPositions
:
null
,
// Will be populated separately if needed
foot
:
rawPlayer
.
foot
??
null
,
currentTeamId
:
rawPlayer
.
currentTeamId
??
rawPlayer
.
current_team_id
??
null
,
currentNationalTeamId
:
rawPlayer
.
currentNationalTeamId
??
rawPlayer
.
current_national_team_id
??
null
,
currentTeamName
:
rawPlayer
.
currentTeamName
??
rawPlayer
.
current_team_name
??
null
,
currentTeamOfficialName
:
rawPlayer
.
currentTeamOfficialName
??
rawPlayer
.
current_team_official_name
??
null
,
currentNationalTeamName
:
rawPlayer
.
currentNationalTeamName
??
rawPlayer
.
current_national_team_name
??
null
,
currentNationalTeamOfficialName
:
rawPlayer
.
currentNationalTeamOfficialName
??
rawPlayer
.
current_national_team_official_name
??
null
,
currentTeam
:
(()
=>
{
// Get stored team name and official name
const
storedTeamName
=
...
...
@@ -325,12 +306,16 @@ export class PlayersService {
// If we have a relation currentTeam, merge stored values into it
if
(
rawPlayer
.
currentTeam
)
{
return
{
...
rawPlayer
.
currentTeam
,
id
:
rawPlayer
.
currentTeam
.
id
,
wyId
:
rawPlayer
.
currentTeam
.
wyId
,
gsmId
:
rawPlayer
.
currentTeam
.
gsmId
,
name
:
storedTeamName
??
rawPlayer
.
currentTeam
.
name
??
null
,
officialName
:
storedTeamOfficialName
??
rawPlayer
.
currentTeam
.
officialName
??
null
,
createdAt
:
rawPlayer
.
currentTeam
.
createdAt
,
updatedAt
:
rawPlayer
.
currentTeam
.
updatedAt
,
};
}
...
...
@@ -342,27 +327,42 @@ export class PlayersService {
gsmId
:
null
,
name
:
storedTeamName
,
officialName
:
storedTeamOfficialName
,
shortName
:
null
,
description
:
null
,
type
:
null
,
category
:
null
,
gender
:
null
,
areaWyId
:
null
,
city
:
null
,
coachWyId
:
null
,
competitionWyId
:
null
,
seasonWyId
:
null
,
league
:
null
,
season
:
null
,
status
:
null
,
isActive
:
null
,
apiLastSyncedAt
:
null
,
apiSyncStatus
:
null
,
createdAt
:
null
,
updatedAt
:
null
,
deletedAt
:
null
,
area
:
null
,
coach
:
null
,
};
}
return
null
;
})(),
currentNationalTeam
:
(()
=>
{
// Get stored national team name and official name
const
storedNationalTeamName
=
rawPlayer
.
currentNationalTeamName
??
rawPlayer
.
current_national_team_name
??
null
;
const
storedNationalTeamOfficialName
=
rawPlayer
.
currentNationalTeamOfficialName
??
rawPlayer
.
current_national_team_official_name
??
null
;
const
nationalTeamId
=
rawPlayer
.
currentNationalTeamId
??
rawPlayer
.
current_national_team_id
??
null
;
// If we have stored values, create object
if
(
storedNationalTeamName
||
storedNationalTeamOfficialName
||
nationalTeamId
)
{
return
{
id
:
null
,
wyId
:
nationalTeamId
,
gsmId
:
null
,
name
:
storedNationalTeamName
,
officialName
:
storedNationalTeamOfficialName
,
createdAt
:
null
,
updatedAt
:
null
,
};
}
...
...
@@ -804,20 +804,6 @@ export class PlayersService {
email
:
players
.
email
,
phone
:
players
.
phone
,
onLoan
:
players
.
onLoan
,
agentId
:
players
.
agentId
,
agent
:
{
id
:
agents
.
id
,
name
:
agents
.
name
,
description
:
agents
.
description
,
type
:
agents
.
type
,
email
:
agents
.
email
,
phone
:
agents
.
phone
,
status
:
agents
.
status
,
address
:
agents
.
address
,
country
:
agents
.
country
,
createdAt
:
agents
.
createdAt
,
updatedAt
:
agents
.
updatedAt
,
},
ranking
:
players
.
ranking
,
roi
:
players
.
roi
,
marketValue
:
players
.
marketValue
,
...
...
@@ -855,8 +841,7 @@ export class PlayersService {
})
.
from
(
players
)
.
leftJoin
(
areas
,
eq
(
players
.
birthAreaWyId
,
areas
.
wyId
))
.
leftJoin
(
positions
,
eq
(
players
.
positionId
,
positions
.
id
))
.
leftJoin
(
agents
,
eq
(
players
.
agentId
,
agents
.
id
));
.
leftJoin
(
positions
,
eq
(
players
.
positionId
,
positions
.
id
));
// Add passport area join using a subquery approach
// We'll need to do a second query or use a different approach
...
...
@@ -1089,6 +1074,53 @@ export class PlayersService {
});
}
// Fetch agents for all players from playerAgents junction table
const
playerIds
=
rawData
.
map
((
p
)
=>
p
.
id
);
let
playerAgentsMap
:
Map
<
number
,
any
>
=
new
Map
();
if
(
playerIds
.
length
>
0
)
{
const
playerAgentRows
=
await
db
.
select
({
playerId
:
playerAgents
.
playerId
,
agentId
:
playerAgents
.
agentId
,
agentName
:
agents
.
name
,
agentDescription
:
agents
.
description
,
agentType
:
agents
.
type
,
agentEmail
:
agents
.
email
,
agentPhone
:
agents
.
phone
,
agentStatus
:
agents
.
status
,
agentAddress
:
agents
.
address
,
agentCountryWyId
:
agents
.
countryWyId
,
agentCreatedAt
:
agents
.
createdAt
,
agentUpdatedAt
:
agents
.
updatedAt
,
})
.
from
(
playerAgents
)
.
leftJoin
(
agents
,
eq
(
playerAgents
.
agentId
,
agents
.
id
))
.
where
(
inArray
(
playerAgents
.
playerId
,
playerIds
));
for
(
const
row
of
playerAgentRows
)
{
if
(
row
.
playerId
&&
!
playerAgentsMap
.
has
(
row
.
playerId
))
{
playerAgentsMap
.
set
(
row
.
playerId
,
{
agentId
:
row
.
agentId
,
agent
:
row
.
agentId
?
{
id
:
row
.
agentId
,
name
:
row
.
agentName
||
''
,
description
:
row
.
agentDescription
||
null
,
type
:
row
.
agentType
||
''
,
email
:
row
.
agentEmail
||
''
,
phone
:
row
.
agentPhone
||
''
,
status
:
row
.
agentStatus
||
''
,
address
:
row
.
agentAddress
||
''
,
country
:
row
.
agentCountryWyId
?.
toString
()
||
''
,
createdAt
:
row
.
agentCreatedAt
?.
toISOString
()
||
''
,
updatedAt
:
row
.
agentUpdatedAt
?.
toISOString
()
||
''
,
}
:
null
,
});
}
}
}
// Attach reports and other positions to each player
structuredData
=
structuredData
.
map
((
player
,
index
)
=>
{
const
rawPlayer
=
rawData
[
index
];
...
...
@@ -1099,10 +1131,14 @@ export class PlayersService {
.
filter
((
pos
)
=>
pos
!==
undefined
)
:
null
;
const
playerAgentData
=
playerAgentsMap
.
get
(
rawPlayer
.
id
);
return
{
...
player
,
reports
:
playerReportsMap
.
get
(
player
.
wyId
)
??
[],
otherPositions
:
otherPositionsArray
,
agentId
:
playerAgentData
?.
agentId
??
null
,
agent
:
playerAgentData
?.
agent
??
null
,
};
});
...
...
@@ -1361,7 +1397,6 @@ export class PlayersService {
email
:
normalizeToNull
(
data
.
email
),
phone
:
normalizeToNull
(
data
.
phone
),
onLoan
:
data
.
onLoan
??
false
,
agentId
:
data
.
agentId
??
null
,
ranking
:
normalizeToNull
(
data
.
ranking
),
roi
:
normalizeToNull
(
data
.
roi
),
marketValue
:
normalizeToNull
(
data
.
marketValue
),
...
...
@@ -1464,7 +1499,6 @@ export class PlayersService {
email
:
players
.
email
,
phone
:
players
.
phone
,
onLoan
:
players
.
onLoan
,
agentId
:
players
.
agentId
,
ranking
:
players
.
ranking
,
roi
:
players
.
roi
,
marketValue
:
players
.
marketValue
,
...
...
@@ -1674,6 +1708,46 @@ export class PlayersService {
rating
:
report
.
rating
?
parseFloat
(
report
.
rating
as
string
)
:
null
,
}));
// Fetch agent from playerAgents junction table
const
playerAgentRow
=
await
db
.
select
({
agentId
:
playerAgents
.
agentId
,
agentName
:
agents
.
name
,
agentDescription
:
agents
.
description
,
agentType
:
agents
.
type
,
agentEmail
:
agents
.
email
,
agentPhone
:
agents
.
phone
,
agentStatus
:
agents
.
status
,
agentAddress
:
agents
.
address
,
agentCountryWyId
:
agents
.
countryWyId
,
agentCreatedAt
:
agents
.
createdAt
,
agentUpdatedAt
:
agents
.
updatedAt
,
})
.
from
(
playerAgents
)
.
leftJoin
(
agents
,
eq
(
playerAgents
.
agentId
,
agents
.
id
))
.
where
(
eq
(
playerAgents
.
playerId
,
rawPlayer
.
id
))
.
limit
(
1
);
if
(
playerAgentRow
&&
playerAgentRow
.
length
>
0
)
{
const
row
=
playerAgentRow
[
0
];
structuredPlayer
.
agentId
=
row
.
agentId
;
if
(
row
.
agentId
&&
row
.
agentName
)
{
structuredPlayer
.
agent
=
{
id
:
row
.
agentId
,
name
:
row
.
agentName
||
''
,
description
:
row
.
agentDescription
||
null
,
type
:
row
.
agentType
||
''
,
email
:
row
.
agentEmail
||
''
,
phone
:
row
.
agentPhone
||
''
,
status
:
row
.
agentStatus
||
''
,
address
:
row
.
agentAddress
||
''
,
country
:
row
.
agentCountryWyId
?.
toString
()
||
''
,
createdAt
:
row
.
agentCreatedAt
?.
toISOString
()
||
''
,
updatedAt
:
row
.
agentUpdatedAt
?.
toISOString
()
||
''
,
};
}
}
return
structuredPlayer
;
}
...
...
@@ -1752,7 +1826,6 @@ export class PlayersService {
email
:
players
.
email
,
phone
:
players
.
phone
,
onLoan
:
players
.
onLoan
,
agentId
:
players
.
agentId
,
ranking
:
players
.
ranking
,
roi
:
players
.
roi
,
marketValue
:
players
.
marketValue
,
...
...
@@ -1955,6 +2028,46 @@ export class PlayersService {
rating
:
report
.
rating
?
parseFloat
(
report
.
rating
as
string
)
:
null
,
}));
// Fetch agent from playerAgents junction table
const
playerAgentRow
=
await
db
.
select
({
agentId
:
playerAgents
.
agentId
,
agentName
:
agents
.
name
,
agentDescription
:
agents
.
description
,
agentType
:
agents
.
type
,
agentEmail
:
agents
.
email
,
agentPhone
:
agents
.
phone
,
agentStatus
:
agents
.
status
,
agentAddress
:
agents
.
address
,
agentCountryWyId
:
agents
.
countryWyId
,
agentCreatedAt
:
agents
.
createdAt
,
agentUpdatedAt
:
agents
.
updatedAt
,
})
.
from
(
playerAgents
)
.
leftJoin
(
agents
,
eq
(
playerAgents
.
agentId
,
agents
.
id
))
.
where
(
eq
(
playerAgents
.
playerId
,
rawPlayer
.
id
))
.
limit
(
1
);
if
(
playerAgentRow
&&
playerAgentRow
.
length
>
0
)
{
const
row
=
playerAgentRow
[
0
];
structuredPlayer
.
agentId
=
row
.
agentId
;
if
(
row
.
agentId
&&
row
.
agentName
)
{
structuredPlayer
.
agent
=
{
id
:
row
.
agentId
,
name
:
row
.
agentName
||
''
,
description
:
row
.
agentDescription
||
null
,
type
:
row
.
agentType
||
''
,
email
:
row
.
agentEmail
||
''
,
phone
:
row
.
agentPhone
||
''
,
status
:
row
.
agentStatus
||
''
,
address
:
row
.
agentAddress
||
''
,
country
:
row
.
agentCountryWyId
?.
toString
()
||
''
,
createdAt
:
row
.
agentCreatedAt
?.
toISOString
()
||
''
,
updatedAt
:
row
.
agentUpdatedAt
?.
toISOString
()
||
''
,
};
}
}
return
structuredPlayer
;
}
...
...
@@ -2118,7 +2231,6 @@ export class PlayersService {
if
(
data
.
phone
!==
undefined
)
updateData
.
phone
=
normalizeToNull
(
data
.
phone
);
if
(
data
.
onLoan
!==
undefined
)
updateData
.
onLoan
=
data
.
onLoan
;
if
(
data
.
agentId
!==
undefined
)
updateData
.
agentId
=
data
.
agentId
;
if
(
data
.
ranking
!==
undefined
)
updateData
.
ranking
=
normalizeToNull
(
data
.
ranking
);
if
(
data
.
roi
!==
undefined
)
updateData
.
roi
=
normalizeToNull
(
data
.
roi
);
...
...
@@ -2145,6 +2257,187 @@ export class PlayersService {
return
result
as
Player
;
}
async
updateById
(
id
:
number
,
data
:
Partial
<
NewPlayer
|
any
>
,
):
Promise
<
Player
|
null
>
{
const
db
=
this
.
databaseService
.
getDatabase
();
// Check if player exists
const
existingPlayer
=
await
db
.
select
()
.
from
(
players
)
.
leftJoin
(
areas
,
eq
(
players
.
birthAreaWyId
,
areas
.
wyId
))
.
leftJoin
(
positions
,
eq
(
players
.
positionId
,
positions
.
id
))
.
where
(
eq
(
players
.
id
,
id
))
.
limit
(
1
);
if
(
!
existingPlayer
||
existingPlayer
.
length
===
0
)
{
return
null
;
}
// Normalize date/timestamp fields that may arrive as strings
const
toDate
=
(
value
:
unknown
):
Date
|
undefined
=>
{
if
(
value
==
null
)
return
undefined
;
if
(
value
instanceof
Date
)
return
value
;
const
d
=
new
Date
(
value
as
any
);
return
isNaN
(
d
.
getTime
())
?
undefined
:
d
;
};
// Convert to date string (YYYY-MM-DD) for date columns
const
toDateString
=
(
value
:
unknown
):
string
|
null
=>
{
if
(
value
==
null
||
value
===
''
)
return
null
;
if
(
value
instanceof
Date
)
{
return
value
.
toISOString
().
split
(
'T'
)[
0
];
}
if
(
typeof
value
===
'string'
)
{
if
(
/^
\d{4}
-
\d{2}
-
\d{2}
$/
.
test
(
value
))
{
return
value
;
}
const
d
=
new
Date
(
value
);
if
(
!
isNaN
(
d
.
getTime
()))
{
return
d
.
toISOString
().
split
(
'T'
)[
0
];
}
}
return
null
;
};
// Normalize empty strings to null for nullable fields
const
normalizeToNull
=
(
value
:
unknown
):
any
=>
{
if
(
value
===
''
||
value
===
null
||
value
===
undefined
)
return
null
;
return
value
;
};
// Build update object with only provided fields
const
updateData
:
Partial
<
NewPlayer
>
=
{
updatedAt
:
new
Date
(),
};
// Transform and include only fields that are provided
if
(
data
.
firstName
!==
undefined
)
updateData
.
firstName
=
data
.
firstName
;
if
(
data
.
lastName
!==
undefined
)
updateData
.
lastName
=
data
.
lastName
;
if
(
data
.
middleName
!==
undefined
)
updateData
.
middleName
=
normalizeToNull
(
data
.
middleName
);
if
(
data
.
shortName
!==
undefined
)
updateData
.
shortName
=
normalizeToNull
(
data
.
shortName
);
if
(
data
.
gsmId
!==
undefined
)
updateData
.
gsmId
=
normalizeToNull
(
data
.
gsmId
);
if
(
data
.
teamWyId
!==
undefined
)
updateData
.
teamWyId
=
normalizeToNull
(
data
.
teamWyId
);
if
(
data
.
currentTeamId
!==
undefined
)
updateData
.
currentTeamId
=
normalizeToNull
(
data
.
currentTeamId
);
if
(
data
.
currentNationalTeamId
!==
undefined
)
updateData
.
currentNationalTeamId
=
normalizeToNull
(
data
.
currentNationalTeamId
,
);
if
(
data
.
currentTeamName
!==
undefined
)
updateData
.
currentTeamName
=
normalizeToNull
(
data
.
currentTeamName
);
if
(
data
.
currentTeamOfficialName
!==
undefined
)
updateData
.
currentTeamOfficialName
=
normalizeToNull
(
data
.
currentTeamOfficialName
,
);
if
(
data
.
currentNationalTeamName
!==
undefined
)
updateData
.
currentNationalTeamName
=
normalizeToNull
(
data
.
currentNationalTeamName
,
);
if
(
data
.
currentNationalTeamOfficialName
!==
undefined
)
updateData
.
currentNationalTeamOfficialName
=
normalizeToNull
(
data
.
currentNationalTeamOfficialName
,
);
if
(
data
.
dateOfBirth
!==
undefined
)
updateData
.
dateOfBirth
=
toDateString
(
data
.
dateOfBirth
)
as
any
;
if
(
data
.
heightCm
!==
undefined
)
updateData
.
heightCm
=
normalizeToNull
(
data
.
heightCm
);
if
(
data
.
weightKg
!==
undefined
)
updateData
.
weightKg
=
normalizeToNull
(
data
.
weightKg
);
if
(
data
.
foot
!==
undefined
)
updateData
.
foot
=
normalizeToNull
(
data
.
foot
);
if
(
data
.
gender
!==
undefined
)
updateData
.
gender
=
normalizeToNull
(
data
.
gender
);
if
(
data
.
position
!==
undefined
||
data
.
positionId
!==
undefined
)
{
updateData
.
positionId
=
await
this
.
resolvePositionId
(
data
.
position
,
data
.
positionId
,
);
}
if
(
data
.
otherPositions
!==
undefined
||
data
.
otherPositionIds
!==
undefined
)
{
updateData
.
otherPositionIds
=
await
this
.
resolvePositionIds
(
data
.
otherPositions
,
data
.
otherPositionIds
,
);
}
if
(
data
.
roleCode2
!==
undefined
)
updateData
.
roleCode2
=
normalizeToNull
(
data
.
roleCode2
);
if
(
data
.
roleCode3
!==
undefined
)
updateData
.
roleCode3
=
normalizeToNull
(
data
.
roleCode3
);
if
(
data
.
roleName
!==
undefined
)
updateData
.
roleName
=
normalizeToNull
(
data
.
roleName
);
if
(
data
.
birthAreaWyId
!==
undefined
)
updateData
.
birthAreaWyId
=
normalizeToNull
(
data
.
birthAreaWyId
);
if
(
data
.
secondBirthAreaWyId
!==
undefined
)
updateData
.
secondBirthAreaWyId
=
normalizeToNull
(
data
.
secondBirthAreaWyId
,
);
if
(
data
.
passportAreaWyId
!==
undefined
)
updateData
.
passportAreaWyId
=
normalizeToNull
(
data
.
passportAreaWyId
);
if
(
data
.
secondPassportAreaWyId
!==
undefined
)
updateData
.
secondPassportAreaWyId
=
normalizeToNull
(
data
.
secondPassportAreaWyId
,
);
if
(
data
.
status
!==
undefined
)
updateData
.
status
=
data
.
status
;
// Support both imageDataUrl (internal) and imageDataURL (external alias)
if
(
data
.
imageDataUrl
!==
undefined
||
(
data
as
any
).
imageDataURL
!==
undefined
)
{
const
imageValue
=
data
.
imageDataUrl
!==
undefined
?
data
.
imageDataUrl
:
(
data
as
any
).
imageDataURL
;
updateData
.
imageDataUrl
=
normalizeToNull
(
imageValue
);
}
if
(
data
.
jerseyNumber
!==
undefined
)
updateData
.
jerseyNumber
=
normalizeToNull
(
data
.
jerseyNumber
);
if
(
data
.
apiLastSyncedAt
!==
undefined
)
updateData
.
apiLastSyncedAt
=
toDate
(
data
.
apiLastSyncedAt
)
as
any
;
if
(
data
.
apiSyncStatus
!==
undefined
)
updateData
.
apiSyncStatus
=
data
.
apiSyncStatus
;
if
(
data
.
isActive
!==
undefined
)
updateData
.
isActive
=
data
.
isActive
;
// New fields added for contact and financial information
if
(
data
.
email
!==
undefined
)
updateData
.
email
=
normalizeToNull
(
data
.
email
);
if
(
data
.
phone
!==
undefined
)
updateData
.
phone
=
normalizeToNull
(
data
.
phone
);
if
(
data
.
onLoan
!==
undefined
)
updateData
.
onLoan
=
data
.
onLoan
;
if
(
data
.
ranking
!==
undefined
)
updateData
.
ranking
=
normalizeToNull
(
data
.
ranking
);
if
(
data
.
roi
!==
undefined
)
updateData
.
roi
=
normalizeToNull
(
data
.
roi
);
if
(
data
.
marketValue
!==
undefined
)
updateData
.
marketValue
=
normalizeToNull
(
data
.
marketValue
);
if
(
data
.
valueRange
!==
undefined
)
updateData
.
valueRange
=
normalizeToNull
(
data
.
valueRange
);
if
(
data
.
transferValue
!==
undefined
)
updateData
.
transferValue
=
normalizeToNull
(
data
.
transferValue
);
if
(
data
.
salary
!==
undefined
)
updateData
.
salary
=
normalizeToNull
(
data
.
salary
);
if
(
data
.
contractEndsAt
!==
undefined
)
updateData
.
contractEndsAt
=
toDateString
(
data
.
contractEndsAt
)
as
any
;
if
(
data
.
feasible
!==
undefined
)
updateData
.
feasible
=
data
.
feasible
;
if
(
data
.
morphology
!==
undefined
)
updateData
.
morphology
=
normalizeToNull
(
data
.
morphology
);
const
[
result
]
=
await
db
.
update
(
players
)
.
set
(
updateData
)
.
where
(
eq
(
players
.
id
,
id
))
.
returning
();
return
result
as
Player
;
}
async
deleteByWyId
(
wyId
:
number
):
Promise
<
Player
|
null
>
{
const
db
=
this
.
databaseService
.
getDatabase
();
...
...
@@ -2167,4 +2460,27 @@ export class PlayersService {
return
result
as
Player
;
}
async
deleteById
(
id
:
number
):
Promise
<
Player
|
null
>
{
const
db
=
this
.
databaseService
.
getDatabase
();
// Check if player exists
const
existingPlayer
=
await
this
.
findById
(
id
);
if
(
!
existingPlayer
)
{
return
null
;
}
// Soft delete: set isActive to false and deletedAt to current timestamp
const
[
result
]
=
await
db
.
update
(
players
)
.
set
({
isActive
:
false
,
deletedAt
:
new
Date
(),
updatedAt
:
new
Date
(),
})
.
where
(
eq
(
players
.
id
,
id
))
.
returning
();
return
result
as
Player
;
}
}
src/modules/profiles/dto/create-link.dto.ts
View file @
5917bcb1
...
...
@@ -48,6 +48,15 @@ export class CreateLinkDto {
order
?:
number
;
@
ApiPropertyOptional
({
description
:
'Icon identifier or URL for the link'
,
example
:
'youtube'
,
type
:
String
,
})
@
IsOptional
()
@
IsString
()
icon
?:
string
;
@
ApiPropertyOptional
({
description
:
'Whether the link is active'
,
example
:
true
,
type
:
Boolean
,
...
...
src/modules/profiles/dto/update-link.dto.ts
View file @
5917bcb1
...
...
@@ -31,6 +31,15 @@ export class UpdateLinkDto {
order
?:
number
;
@
ApiPropertyOptional
({
description
:
'Icon identifier or URL for the link'
,
example
:
'youtube'
,
type
:
String
,
})
@
IsOptional
()
@
IsString
()
icon
?:
string
;
@
ApiPropertyOptional
({
description
:
'Whether the link is active'
,
example
:
true
,
type
:
Boolean
,
...
...
src/modules/profiles/profiles.service.ts
View file @
5917bcb1
...
...
@@ -175,6 +175,7 @@ export class ProfilesService {
coachId
:
data
.
coachId
??
null
,
title
:
data
.
title
,
url
:
data
.
url
,
icon
:
(
data
as
any
).
icon
??
null
,
order
:
data
.
order
??
0
,
isActive
:
data
.
isActive
??
true
,
}
as
NewProfileLink
)
...
...
@@ -202,6 +203,7 @@ export class ProfilesService {
const
updateData
:
any
=
{
updatedAt
:
new
Date
()
};
if
(
data
.
title
!==
undefined
)
updateData
.
title
=
data
.
title
;
if
(
data
.
url
!==
undefined
)
updateData
.
url
=
data
.
url
;
if
((
data
as
any
).
icon
!==
undefined
)
updateData
.
icon
=
(
data
as
any
).
icon
;
if
(
data
.
order
!==
undefined
)
updateData
.
order
=
data
.
order
;
if
(
data
.
isActive
!==
undefined
)
updateData
.
isActive
=
data
.
isActive
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment