Skip to content

Commit 8834dfd

Browse files
feat(divisions): add endpoint to list all division airport requests
1 parent 7d73a13 commit 8834dfd

3 files changed

Lines changed: 125 additions & 0 deletions

File tree

data/openapi.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,6 +1971,30 @@
19711971
}
19721972
}
19731973
},
1974+
"/staff/divisions/airports": {
1975+
"get": {
1976+
"x-hidden": true,
1977+
"summary": "List all division airport requests (staff)",
1978+
"tags": ["Staff", "Divisions"],
1979+
"description": "Returns every division airport request with division metadata. Requires Product Manager or higher.",
1980+
"security": [
1981+
{
1982+
"VatsimToken": []
1983+
}
1984+
],
1985+
"responses": {
1986+
"200": {
1987+
"description": "Division airports listed"
1988+
},
1989+
"401": {
1990+
"description": "Unauthorized"
1991+
},
1992+
"403": {
1993+
"description": "Forbidden"
1994+
}
1995+
}
1996+
}
1997+
},
19741998
"/contributions": {
19751999
"get": {
19762000
"summary": "List contributions",

src/index.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3639,6 +3639,69 @@ staffManageApp.delete('/:vatsimId', async (c) => {
36393639

36403640
app.route('/staff/manage', staffManageApp);
36413641

3642+
const staffDivisionsApp = new Hono<{
3643+
Bindings: Env;
3644+
Variables: {
3645+
user?: UserRecord;
3646+
};
3647+
}>();
3648+
3649+
staffDivisionsApp.use('*', async (c, next) => {
3650+
const vatsimToken = c.req.header('X-Vatsim-Token');
3651+
if (!vatsimToken) return c.text('Unauthorized', 401);
3652+
3653+
const vatsim = ServicePool.getVatsim(c.env);
3654+
const auth = ServicePool.getAuth(c.env);
3655+
const roles = ServicePool.getRoles(c.env);
3656+
3657+
const vatsimUser = await vatsim.getUser(vatsimToken);
3658+
const user = await auth.getUserByVatsimId(vatsimUser.id);
3659+
if (!user) return c.text('User not found', 404);
3660+
3661+
const allowed = await roles.hasPermission(user.id, StaffRole.PRODUCT_MANAGER);
3662+
if (!allowed) return c.text('Forbidden', 403);
3663+
3664+
c.set('user', user);
3665+
await next();
3666+
});
3667+
3668+
/**
3669+
* @openapi
3670+
* /staff/divisions/airports:
3671+
* get:
3672+
* x-hidden: true
3673+
* summary: List all division airport requests (staff)
3674+
* tags:
3675+
* - Staff
3676+
* - Divisions
3677+
* description: Returns every division airport request with division metadata. Requires Product Manager or higher.
3678+
* security:
3679+
* - VatsimToken: []
3680+
* responses:
3681+
* 200:
3682+
* description: Division airports listed
3683+
* 401:
3684+
* description: Unauthorized
3685+
* 403:
3686+
* description: Forbidden
3687+
*/
3688+
staffDivisionsApp.get('/airports', async (c) => {
3689+
const divisions = ServicePool.getDivisions(c.env);
3690+
const airports = await divisions.getAllDivisionAirports();
3691+
const shaped = airports.map((airport) => ({
3692+
division_id: airport.division_id,
3693+
division_name: airport.division_name,
3694+
airport_request_id: airport.id,
3695+
icao: airport.icao,
3696+
status: airport.status,
3697+
requested_by: airport.requested_by,
3698+
approved_by: airport.approved_by ?? null,
3699+
}));
3700+
return c.json({ airports: shaped });
3701+
});
3702+
3703+
app.route('/staff/divisions', staffDivisionsApp);
3704+
36423705
// Contributions endpoints
36433706
const contributionsApp = new Hono<{ Bindings: Env }>();
36443707

src/services/divisions.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ interface DivisionAirport {
2727
updated_at: string;
2828
}
2929

30+
type DivisionAirportWithDivision = DivisionAirport & {
31+
division_name: string;
32+
};
33+
3034
export class DivisionService {
3135
private dbSession: DatabaseSessionService;
3236

@@ -279,6 +283,40 @@ export class DivisionService {
279283
}));
280284
}
281285

286+
async getAllDivisionAirports(): Promise<DivisionAirportWithDivision[]> {
287+
const result = await this.dbSession.executeRead<
288+
DivisionAirportWithDivision & {
289+
division_name: string | null;
290+
}
291+
>(
292+
`SELECT
293+
da.id,
294+
da.division_id,
295+
d.name AS division_name,
296+
da.icao,
297+
da.status,
298+
da.requested_by,
299+
da.approved_by,
300+
da.created_at,
301+
da.updated_at
302+
FROM division_airports da
303+
JOIN divisions d ON d.id = da.division_id
304+
ORDER BY d.name ASC, da.created_at DESC`,
305+
);
306+
307+
return result.results.map((row) => ({
308+
id: row.id,
309+
division_id: row.division_id,
310+
division_name: row.division_name ?? '',
311+
icao: row.icao,
312+
status: row.status,
313+
requested_by: row.requested_by,
314+
approved_by: row.approved_by ?? undefined,
315+
created_at: row.created_at,
316+
updated_at: row.updated_at,
317+
}));
318+
}
319+
282320
async getDivisionMembers(divisionId: number): Promise<(DivisionMember & { display_name: string })[] | null> {
283321
type DivisionMemberRow = {
284322
division_exists: number | null;

0 commit comments

Comments
 (0)