Commit 5917bcb1 by Augusto

small fixes

parent 7730f929
-- 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;
-- 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;
...@@ -213,9 +213,6 @@ export const players = pgTable( ...@@ -213,9 +213,6 @@ export const players = pgTable(
// Transfer and financial information // Transfer and financial information
onLoan: boolean('on_loan').default(false), onLoan: boolean('on_loan').default(false),
agentId: integer('agent_id').references(() => agents.id, {
onDelete: 'set null',
}),
ranking: varchar('ranking', { length: 255 }), ranking: varchar('ranking', { length: 255 }),
roi: varchar('roi', { length: 255 }), // Return on Investment roi: varchar('roi', { length: 255 }), // Return on Investment
marketValue: decimal('market_value', { precision: 15, scale: 2 }), // Monetary value marketValue: decimal('market_value', { precision: 15, scale: 2 }), // Monetary value
...@@ -428,7 +425,9 @@ export const agents = pgTable('agents', { ...@@ -428,7 +425,9 @@ export const agents = pgTable('agents', {
phone: text('phone').notNull(), phone: text('phone').notNull(),
status: text('status').notNull(), status: text('status').notNull(),
address: text('address').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(), createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(), updatedAt: timestamp('updated_at').notNull().defaultNow(),
}); });
...@@ -1042,6 +1041,7 @@ export const profileLinks = pgTable( ...@@ -1042,6 +1041,7 @@ export const profileLinks = pgTable(
}), }),
title: varchar('title', { length: 255 }).notNull(), // e.g., "YouTube", "Instagram", "Portfolio" title: varchar('title', { length: 255 }).notNull(), // e.g., "YouTube", "Instagram", "Portfolio"
url: text('url').notNull(), url: text('url').notNull(),
icon: varchar('icon', { length: 255 }),
order: integer('order').default(0), order: integer('order').default(0),
isActive: boolean('is_active').default(true), isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
......
...@@ -39,11 +39,14 @@ export class AgentsController { ...@@ -39,11 +39,14 @@ export class AgentsController {
example1: { example1: {
summary: 'Create new agent', summary: 'Create new agent',
value: { value: {
firstName: 'John', name: 'John Smith',
lastName: 'Smith', type: 'individual',
email: 'john.smith@agents.com', email: 'john.smith@agents.com',
phone: '+1234567890', 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 { ...@@ -53,14 +56,16 @@ export class AgentsController {
schema: { schema: {
example: { example: {
id: 1, id: 1,
firstName: 'John', name: 'John Smith',
lastName: 'Smith', type: 'individual',
email: 'john.smith@agents.com', email: 'john.smith@agents.com',
phone: '+1234567890', 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', createdAt: '2025-01-15T10:30:00Z',
updatedAt: '2025-01-15T10:30:00Z', updatedAt: '2025-01-15T10:30:00Z',
deletedAt: null,
}, },
}, },
}) })
...@@ -92,14 +97,16 @@ export class AgentsController { ...@@ -92,14 +97,16 @@ export class AgentsController {
schema: { schema: {
example: { example: {
id: 1, id: 1,
firstName: 'John', name: 'John Smith',
lastName: 'Smith', type: 'individual',
email: 'john.smith.new@agents.com', email: 'john.smith.new@agents.com',
phone: '+1987654321', 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', createdAt: '2025-01-15T10:30:00Z',
updatedAt: '2025-01-15T11:00:00Z', updatedAt: '2025-01-15T11:00:00Z',
deletedAt: null,
}, },
}, },
}) })
...@@ -122,14 +129,16 @@ export class AgentsController { ...@@ -122,14 +129,16 @@ export class AgentsController {
schema: { schema: {
example: { example: {
id: 1, id: 1,
firstName: 'John', name: 'John Smith',
lastName: 'Smith', type: 'individual',
email: 'john.smith@agents.com', email: 'john.smith@agents.com',
phone: '+1234567890', 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', createdAt: '2025-01-15T10:30:00Z',
updatedAt: '2025-01-15T10:30:00Z', updatedAt: '2025-01-15T10:30:00Z',
deletedAt: null,
}, },
}, },
}) })
...@@ -149,14 +158,16 @@ export class AgentsController { ...@@ -149,14 +158,16 @@ export class AgentsController {
example: [ example: [
{ {
id: 1, id: 1,
firstName: 'John', name: 'John Smith',
lastName: 'Smith', type: 'individual',
email: 'john.smith@agents.com', email: 'john.smith@agents.com',
phone: '+1234567890', 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', createdAt: '2025-01-15T10:30:00Z',
updatedAt: '2025-01-15T10:30:00Z', updatedAt: '2025-01-15T10:30:00Z',
deletedAt: null,
}, },
], ],
}, },
...@@ -272,14 +283,16 @@ export class AgentsController { ...@@ -272,14 +283,16 @@ export class AgentsController {
example: [ example: [
{ {
id: 1, id: 1,
firstName: 'John', name: 'John Smith',
lastName: 'Smith', type: 'individual',
email: 'john.smith@agents.com', email: 'john.smith@agents.com',
phone: '+1234567890', 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', createdAt: '2025-01-15T10:30:00Z',
updatedAt: '2025-01-15T10:30:00Z', updatedAt: '2025-01-15T10:30:00Z',
deletedAt: null,
}, },
], ],
}, },
...@@ -302,14 +315,16 @@ export class AgentsController { ...@@ -302,14 +315,16 @@ export class AgentsController {
example: [ example: [
{ {
id: 1, id: 1,
firstName: 'John', name: 'John Smith',
lastName: 'Smith', type: 'individual',
email: 'john.smith@agents.com', email: 'john.smith@agents.com',
phone: '+1234567890', 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', createdAt: '2025-01-15T10:30:00Z',
updatedAt: '2025-01-15T10:30:00Z', updatedAt: '2025-01-15T10:30:00Z',
deletedAt: null,
}, },
], ],
}, },
......
...@@ -27,7 +27,7 @@ export class AgentsService { ...@@ -27,7 +27,7 @@ export class AgentsService {
phone: data.phone!, phone: data.phone!,
status: data.status!, status: data.status!,
address: data.address!, address: data.address!,
country: data.country!, countryWyId: data.countryWyId!,
description: data.description ?? null, description: data.description ?? null,
createdAt: now as any, createdAt: now as any,
updatedAt: now as any, updatedAt: now as any,
......
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; 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 { export class CreateAgentDto {
@ApiProperty({ description: 'Agent name' }) @ApiProperty({ description: 'Agent name' })
...@@ -31,10 +37,10 @@ export class CreateAgentDto { ...@@ -31,10 +37,10 @@ export class CreateAgentDto {
@IsNotEmpty() @IsNotEmpty()
address: string; address: string;
@ApiProperty({ description: 'Country' }) @ApiProperty({ description: 'Country WY ID (Wyscout area ID)' })
@IsString() @IsNumber()
@IsNotEmpty() @IsNotEmpty()
country: string; countryWyId: number;
@ApiPropertyOptional({ description: 'Description' }) @ApiPropertyOptional({ description: 'Description' })
@IsOptional() @IsOptional()
......
...@@ -561,6 +561,7 @@ export class PlayerFeaturesService { ...@@ -561,6 +561,7 @@ export class PlayerFeaturesService {
], ],
set: { set: {
notes: data.notes ?? null, notes: data.notes ?? null,
deletedAt: null,
updatedAt: new Date(), updatedAt: new Date(),
}, },
}) })
......
...@@ -53,6 +53,16 @@ export interface Agent { ...@@ -53,6 +53,16 @@ export interface Agent {
updatedAt: string; 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 { export interface StructuredPlayer {
id: number; id: number;
wyId: number; wyId: number;
...@@ -72,13 +82,8 @@ export interface StructuredPlayer { ...@@ -72,13 +82,8 @@ export interface StructuredPlayer {
position: PlayerPosition | null; position: PlayerPosition | null;
otherPositions: PlayerPosition[] | null; otherPositions: PlayerPosition[] | null;
foot: string | null; foot: string | null;
currentTeamId: number | null; currentTeam: Team | null;
currentNationalTeamId: number | null; currentNationalTeam: Team | null;
currentTeamName: string | null;
currentTeamOfficialName: string | null;
currentNationalTeamName: string | null;
currentNationalTeamOfficialName: string | null;
currentTeam: any | null;
gender: string; gender: string;
status: string; status: string;
jerseyNumber: number | null; jerseyNumber: number | null;
......
...@@ -85,8 +85,8 @@ export class PlayersController { ...@@ -85,8 +85,8 @@ export class PlayersController {
return this.playersService.upsertByWyId(body as any); return this.playersService.upsertByWyId(body as any);
} }
@Get('by-id/:id') @Get(':id')
@ApiOperation({ summary: 'Get player by database ID' }) @ApiOperation({ summary: 'Get player by ID' })
@ApiParam({ @ApiParam({
name: 'id', name: 'id',
type: Number, type: Number,
...@@ -104,13 +104,23 @@ export class PlayersController { ...@@ -104,13 +104,23 @@ export class PlayersController {
return player; return player;
} }
@Get(':wyId') @Get('wy/:wyId')
@ApiOperation({ summary: 'Get player by 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 }) @ApiOkResponse({ description: 'Player if found', type: Object })
@ApiNotFoundResponse({ description: 'Player not found' })
async getByWyId( async getByWyId(
@Param('wyId', ParseIntPipe) wyId: number, @Param('wyId', ParseIntPipe) wyId: number,
): Promise<StructuredPlayer | undefined> { ): 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() @Get()
...@@ -286,16 +296,16 @@ export class PlayersController { ...@@ -286,16 +296,16 @@ export class PlayersController {
return this.playersService.findAll(l, o, name, query); return this.playersService.findAll(l, o, name, query);
} }
@Patch(':wyId') @Patch(':id')
@ApiOperation({ @ApiOperation({
summary: 'Update player by wyId', summary: 'Update player by ID',
description: 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({ @ApiParam({
name: 'wyId', name: 'id',
type: Number, type: Number,
description: 'Wyscout ID of the player to update', description: 'Database ID of the player to update',
}) })
@ApiBody({ @ApiBody({
description: 'Player update payload. All fields are optional.', description: 'Player update payload. All fields are optional.',
...@@ -338,31 +348,31 @@ export class PlayersController { ...@@ -338,31 +348,31 @@ export class PlayersController {
}, },
}) })
@ApiNotFoundResponse({ description: 'Player not found' }) @ApiNotFoundResponse({ description: 'Player not found' })
async updateByWyId( async updateById(
@Param('wyId', ParseIntPipe) wyId: number, @Param('id', ParseIntPipe) id: number,
@Body() body: UpdatePlayerDto, @Body() body: UpdatePlayerDto,
): Promise<Player> { ): Promise<Player> {
const result = await this.playersService.updateByWyId(wyId, body); const result = await this.playersService.updateById(id, body);
if (!result) { if (!result) {
throw new NotFoundException(`Player with wyId ${wyId} not found`); throw new NotFoundException(`Player with ID ${id} not found`);
} }
return result; return result;
} }
@Delete(':wyId') @Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT) @HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete player by wyId (soft delete)' }) @ApiOperation({ summary: 'Delete player by ID (soft delete)' })
@ApiParam({ @ApiParam({
name: 'wyId', name: 'id',
type: Number, type: Number,
description: 'Wyscout ID of the player to delete', description: 'Database ID of the player to delete',
}) })
@ApiNoContentResponse({ description: 'Player deleted successfully' }) @ApiNoContentResponse({ description: 'Player deleted successfully' })
@ApiNotFoundResponse({ description: 'Player not found' }) @ApiNotFoundResponse({ description: 'Player not found' })
async deleteByWyId(@Param('wyId', ParseIntPipe) wyId: number): Promise<void> { async deleteById(@Param('id', ParseIntPipe) id: number): Promise<void> {
const result = await this.playersService.deleteByWyId(wyId); const result = await this.playersService.deleteById(id);
if (!result) { if (!result) {
throw new NotFoundException(`Player with wyId ${wyId} not found`); throw new NotFoundException(`Player with ID ${id} not found`);
} }
} }
} }
...@@ -48,6 +48,15 @@ export class CreateLinkDto { ...@@ -48,6 +48,15 @@ export class CreateLinkDto {
order?: number; order?: number;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Icon identifier or URL for the link',
example: 'youtube',
type: String,
})
@IsOptional()
@IsString()
icon?: string;
@ApiPropertyOptional({
description: 'Whether the link is active', description: 'Whether the link is active',
example: true, example: true,
type: Boolean, type: Boolean,
......
...@@ -31,6 +31,15 @@ export class UpdateLinkDto { ...@@ -31,6 +31,15 @@ export class UpdateLinkDto {
order?: number; order?: number;
@ApiPropertyOptional({ @ApiPropertyOptional({
description: 'Icon identifier or URL for the link',
example: 'youtube',
type: String,
})
@IsOptional()
@IsString()
icon?: string;
@ApiPropertyOptional({
description: 'Whether the link is active', description: 'Whether the link is active',
example: true, example: true,
type: Boolean, type: Boolean,
......
...@@ -175,6 +175,7 @@ export class ProfilesService { ...@@ -175,6 +175,7 @@ export class ProfilesService {
coachId: data.coachId ?? null, coachId: data.coachId ?? null,
title: data.title, title: data.title,
url: data.url, url: data.url,
icon: (data as any).icon ?? null,
order: data.order ?? 0, order: data.order ?? 0,
isActive: data.isActive ?? true, isActive: data.isActive ?? true,
} as NewProfileLink) } as NewProfileLink)
...@@ -202,6 +203,7 @@ export class ProfilesService { ...@@ -202,6 +203,7 @@ export class ProfilesService {
const updateData: any = { updatedAt: new Date() }; const updateData: any = { updatedAt: new Date() };
if (data.title !== undefined) updateData.title = data.title; if (data.title !== undefined) updateData.title = data.title;
if (data.url !== undefined) updateData.url = data.url; 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.order !== undefined) updateData.order = data.order;
if (data.isActive !== undefined) updateData.isActive = data.isActive; if (data.isActive !== undefined) updateData.isActive = data.isActive;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment