Skip to content

Commit b642a49

Browse files
committed
feat(portal): new transfer portal endpoint
1 parent eae513b commit b642a49

5 files changed

Lines changed: 241 additions & 3 deletions

File tree

src/app/enums.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@ export enum GameStatus {
1111
Postponed = 'postponed',
1212
Cancelled = 'cancelled',
1313
}
14+
15+
export enum TransferEligibility {
16+
Withdrawn = 'Withdrawn',
17+
TBD = 'TBD',
18+
PendingAppeal = 'PendingAppeal',
19+
SittingOne = 'SittingOne',
20+
Immediate = 'Immediate',
21+
}

src/app/recruiting/controller.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Route, Tags, Controller, Get, Query, Middlewares } from 'tsoa';
22

33
import middlewares from '../../config/middleware';
4-
import { Recruit, TeamRecruitingRanking } from './types';
5-
import { getRecruits, getTeamRankings } from './service';
4+
import { Recruit, TeamRecruitingRanking, Transfer } from './types';
5+
import { getRecruits, getTeamRankings, getTransfers } from './service';
66

77
@Route('recruiting')
88
@Middlewares(middlewares.standard)
@@ -41,4 +41,33 @@ export class RecruitingController extends Controller {
4141
): Promise<TeamRecruitingRanking[]> {
4242
return await getTeamRankings(year, team, conference);
4343
}
44+
45+
/**
46+
* Retrieves historical transfer portal activity
47+
* @param season Season filter
48+
* @param sourceTeam Source team filter
49+
* @param destinationTeam Destination team filter
50+
* @param sourceConference Source conference filter
51+
* @param destinationConference Destination conference filter
52+
* @param position Position filter
53+
* @isInt season
54+
*/
55+
@Get('portal')
56+
public async getPortalTransfers(
57+
@Query() year?: number,
58+
@Query() sourceTeam?: string,
59+
@Query() destinationTeam?: string,
60+
@Query() sourceConference?: string,
61+
@Query() destinationConference?: string,
62+
@Query() position?: string,
63+
): Promise<Transfer[]> {
64+
return await getTransfers(
65+
year,
66+
sourceTeam,
67+
destinationTeam,
68+
sourceConference,
69+
destinationConference,
70+
position,
71+
);
72+
}
4473
}

src/app/recruiting/service.ts

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { db } from '../../config/database';
2-
import { Recruit, TeamRecruitingRanking } from './types';
2+
import { TransferEligibility } from '../enums';
3+
import { Recruit, TeamRecruitingRanking, Transfer } from './types';
34

45
export const getRecruits = async (
56
year?: number,
@@ -180,3 +181,143 @@ export const getTeamRankings = async (
180181
rating: Number(r.points),
181182
}));
182183
};
184+
185+
export const getTransfers = async (
186+
season?: number,
187+
sourceTeam?: string,
188+
destinationTeam?: string,
189+
sourceConference?: string,
190+
destinationConference?: string,
191+
position?: string,
192+
): Promise<Transfer[]> => {
193+
let query = db
194+
.selectFrom('transfer')
195+
.innerJoin('recruitPosition', 'recruitPosition.id', 'transfer.positionId')
196+
.leftJoin('team as fromTeam', 'fromTeam.id', 'transfer.fromTeamId')
197+
.leftJoin('conferenceTeam as fromConferenceTeam', (join) =>
198+
join
199+
.onRef('fromConferenceTeam.teamId', '=', 'fromTeam.id')
200+
.onRef('fromConferenceTeam.startYear', '<=', 'transfer.season')
201+
.on((eb) =>
202+
eb.or([
203+
eb('fromConferenceTeam.endYear', '>=', eb.ref('transfer.season')),
204+
eb('fromConferenceTeam.endYear', 'is', null),
205+
]),
206+
),
207+
)
208+
.leftJoin(
209+
'conference as fromConference',
210+
'fromConference.id',
211+
'fromConferenceTeam.conferenceId',
212+
)
213+
.leftJoin('team as toTeam', 'toTeam.id', 'transfer.toTeamId')
214+
.leftJoin('conferenceTeam as toConferenceTeam', (join) =>
215+
join
216+
.onRef('toConferenceTeam.teamId', '=', 'toTeam.id')
217+
.onRef('toConferenceTeam.startYear', '<=', 'transfer.season')
218+
.on((eb) =>
219+
eb.or([
220+
eb('toConferenceTeam.endYear', '>=', eb.ref('transfer.season')),
221+
eb('toConferenceTeam.endYear', 'is', null),
222+
]),
223+
),
224+
)
225+
.leftJoin(
226+
'conference as toConference',
227+
'toConference.id',
228+
'toConferenceTeam.conferenceId',
229+
)
230+
.select([
231+
'transfer.id',
232+
'transfer.sourceId',
233+
'transfer.season',
234+
'transfer.firstName',
235+
'transfer.lastName',
236+
'recruitPosition.position as position',
237+
'fromTeam.id as sourceTeamId',
238+
'fromTeam.school as sourceTeam',
239+
'fromConference.abbreviation as sourceConference',
240+
'toTeam.id as destinationTeamId',
241+
'toTeam.school as destinationTeam',
242+
'toConference.abbreviation as destinationConference',
243+
'transfer.stars',
244+
'transfer.rating',
245+
'transfer.eligibility',
246+
'transfer.yearsRemaining',
247+
])
248+
.orderBy('transfer.season', 'desc')
249+
.orderBy('transfer.lastName')
250+
.orderBy('transfer.firstName');
251+
252+
if (season) {
253+
query = query.where('transfer.season', '=', season);
254+
}
255+
256+
if (sourceTeam) {
257+
query = query.where(
258+
(eb) => eb.fn('lower', [eb.ref('fromTeam.school')]),
259+
'=',
260+
sourceTeam.toLowerCase(),
261+
);
262+
}
263+
264+
if (destinationTeam) {
265+
query = query.where(
266+
(eb) => eb.fn('lower', [eb.ref('toTeam.school')]),
267+
'=',
268+
destinationTeam.toLowerCase(),
269+
);
270+
}
271+
272+
if (sourceConference) {
273+
query = query.where(
274+
(eb) => eb.fn('lower', [eb.ref('fromConference.abbreviation')]),
275+
'=',
276+
sourceConference.toLowerCase(),
277+
);
278+
}
279+
280+
if (destinationConference) {
281+
query = query.where(
282+
(eb) => eb.fn('lower', [eb.ref('toConference.abbreviation')]),
283+
'=',
284+
destinationConference.toLowerCase(),
285+
);
286+
}
287+
288+
if (position) {
289+
query = query.where(
290+
(eb) => eb.fn('lower', [eb.ref('recruitPosition.position')]),
291+
'=',
292+
position.toLowerCase(),
293+
);
294+
}
295+
296+
const transfers = await query.execute();
297+
return transfers.map((t) => ({
298+
id: t.id,
299+
sourceId: t.sourceId,
300+
year: t.season,
301+
firstName: t.firstName,
302+
lastName: t.lastName,
303+
position: t.position,
304+
origin: t.sourceTeamId
305+
? {
306+
id: t.sourceTeamId,
307+
name: t.sourceTeam,
308+
conference: t.sourceConference,
309+
}
310+
: null,
311+
destination: t.destinationTeamId
312+
? {
313+
id: t.destinationTeamId,
314+
name: t.destinationTeam,
315+
conference: t.destinationConference,
316+
}
317+
: null,
318+
stars: t.stars,
319+
rating: t.rating ? Number(t.rating) : null,
320+
eligibility: t.eligibility ? (t.eligibility as TransferEligibility) : null,
321+
yearsRemaining: t.yearsRemaining,
322+
}));
323+
};

src/app/recruiting/types.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { TransferEligibility } from '../enums';
2+
13
export interface Recruit {
24
/**
35
* @isInt
@@ -68,3 +70,43 @@ export interface TeamRecruitingRanking {
6870
ranking: number;
6971
rating: number;
7072
}
73+
74+
export interface Transfer {
75+
/**
76+
* @isInt
77+
*/
78+
id: number;
79+
sourceId: string;
80+
/**
81+
* @isInt
82+
*/
83+
year: number;
84+
firstName: string;
85+
lastName: string;
86+
position: string;
87+
origin: {
88+
/** * @isInt
89+
*/
90+
id: number | null;
91+
name: string | null;
92+
conference: string | null;
93+
} | null;
94+
destination: {
95+
/**
96+
* @isInt
97+
*/
98+
id: number | null;
99+
name: string | null;
100+
conference: string | null;
101+
} | null;
102+
eligibility: TransferEligibility | null;
103+
/**
104+
* @isInt
105+
*/
106+
yearsRemaining: number | null;
107+
/**
108+
* @isInt
109+
*/
110+
stars: number | null;
111+
rating: number | null;
112+
}

src/config/types/db.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,23 @@ export interface Tournament {
611611
shortName: string;
612612
}
613613

614+
export interface Transfer {
615+
athleteId: number | null;
616+
eligibility: string | null;
617+
firstName: string;
618+
fromTeamId: number | null;
619+
id: Generated<number>;
620+
lastName: string;
621+
positionId: number;
622+
rating: Numeric | null;
623+
season: number;
624+
sourceId: string;
625+
stars: number | null;
626+
toTeamId: number | null;
627+
transferDate: Timestamp | null;
628+
yearsRemaining: number | null;
629+
}
630+
614631
export interface Venue {
615632
capacity: number | null;
616633
city: string | null;
@@ -661,5 +678,6 @@ export interface DB {
661678
teamRecruiting: TeamRecruiting;
662679
teamStatsLeaderboard: TeamStatsLeaderboard;
663680
tournament: Tournament;
681+
transfer: Transfer;
664682
venue: Venue;
665683
}

0 commit comments

Comments
 (0)