Skip to content

Commit c433702

Browse files
committed
feat: add path to request piscine csv directly
to be used in automations (e.g. Grafana)
1 parent 29f49df commit c433702

File tree

4 files changed

+88
-54
lines changed

4 files changed

+88
-54
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
URL_ORIGIN=http://localhost:4000
22
SESSION_SECRET=enter_your_session_secret_here
3+
DIRECT_AUTH_SECRET=enter_your_direct_auth_secret_here
34
INTRA_API_UID=enter_your_uid_here
45
INTRA_API_SECRET=enter_your_secret_here
56
INTRA_CAMPUS_ID=14

src/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const URL_ORIGIN = process.env.URL_ORIGIN!;
22
export const SESSION_SECRET = process.env.SESSION_SECRET!;
3+
export const DIRECT_AUTH_SECRET = process.env.DIRECT_AUTH_SECRET || '';
34
export const INTRA_API_UID = process.env.INTRA_API_UID!;
45
export const INTRA_API_SECRET = process.env.INTRA_API_SECRET!;
56
export const CAMPUS_ID: number = parseInt(process.env.INTRA_CAMPUS_ID!);

src/routes/piscines.ts

Lines changed: 72 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,69 @@
1-
import { Express } from 'express';
1+
import { Express, Response } from 'express';
22
import passport from 'passport';
33
import { PrismaClient } from '@prisma/client';
4-
import { formatDate, getAllCPiscines, getLatestCPiscine, hasLimitedPiscineHistoryAccess, numberToMonth, projectStatusToString } from '../utils';
4+
import { checkDirectAuthSecret, formatDate, getAllCPiscines, getLatestCPiscine, hasLimitedPiscineHistoryAccess, numberToMonth, projectStatusToString } from '../utils';
55
import { checkIfStudentOrStaff, checkIfCatOrStaff, checkIfPiscineHistoryAccess } from '../handlers/middleware';
66
import { getCPiscineData } from '../handlers/piscine';
77
import { IntraUser } from '../intra/oauth';
88

9+
const respondPiscineCSV = async function(prisma: PrismaClient, res: Response, year: number, month: number) {
10+
const { users, logtimes, dropouts, potentialDropouts, activeStudents, projects } = await getCPiscineData(prisma, year, month);
11+
12+
const now = new Date();
13+
res.setHeader('Content-Type', 'text/csv');
14+
res.setHeader('Content-Disposition', `attachment; filename="piscine-${year}-${month}-export-${formatDate(now).replace(' ', '-')}.csv"`);
15+
16+
const headers = [
17+
'login',
18+
'active_student',
19+
'dropout',
20+
'potential_dropout',
21+
'last_login_at',
22+
'logtime_week_one',
23+
'logtime_week_two',
24+
'logtime_week_three',
25+
'logtime_week_four',
26+
'logtime_total',
27+
'level',
28+
];
29+
30+
// Loop over all projects and add their slugs to the headers
31+
for (const project of projects) {
32+
headers.push(project.slug.replace(/\,\-_\s/g, ''));
33+
}
34+
35+
res.write(headers.join(',') + '\n');
36+
37+
for (const user of users) {
38+
const logtime = logtimes[user.login];
39+
const dropout = dropouts[user.login] ? 'yes' : 'no';
40+
const potentialDropout = potentialDropouts[user.login] ? 'yes' : 'no';
41+
const activeStudent = activeStudents[user.login] ? 'yes' : 'no';
42+
43+
const row = [
44+
user.login,
45+
activeStudent,
46+
dropout,
47+
potentialDropout,
48+
formatDate(user.locations[0]?.begin_at),
49+
logtime.weekOne / 60 / 60, // Convert seconds to hours
50+
logtime.weekTwo / 60 / 60,
51+
logtime.weekThree / 60 / 60,
52+
logtime.weekFour / 60 / 60,
53+
logtime.total / 60 / 60,
54+
user.cursus_users[0]?.level || 0,
55+
];
56+
57+
// Add every user's project to the row
58+
for (const project_user of user.project_users) {
59+
row.push(projectStatusToString(project_user, false));
60+
}
61+
62+
res.write(row.join(',') + '\n');
63+
}
64+
res.end();
65+
};
66+
967
export const setupPiscinesRoutes = function(app: Express, prisma: PrismaClient): void {
1068
app.get('/piscines', passport.authenticate('session'), checkIfStudentOrStaff, async (req, res) => {
1169
// Redirect to latest year and month defined in the database
@@ -38,60 +96,20 @@ export const setupPiscinesRoutes = function(app: Express, prisma: PrismaClient):
3896
const year = parseInt(req.params.year);
3997
const month = parseInt(req.params.month);
4098

41-
const { users, logtimes, dropouts, potentialDropouts, activeStudents, projects } = await getCPiscineData(prisma, year, month);
42-
43-
const now = new Date();
44-
res.setHeader('Content-Type', 'text/csv');
45-
res.setHeader('Content-Disposition', `attachment; filename="piscine-${year}-${month}-export-${formatDate(now).replace(' ', '-')}.csv"`);
46-
47-
const headers = [
48-
'login',
49-
'active_student',
50-
'dropout',
51-
'potential_dropout',
52-
'last_login_at',
53-
'logtime_week_one',
54-
'logtime_week_two',
55-
'logtime_week_three',
56-
'logtime_week_four',
57-
'logtime_total',
58-
'level',
59-
];
99+
return await respondPiscineCSV(prisma, res, year, month);
100+
});
60101

61-
// Loop over all projects and add their slugs to the headers
62-
for (const project of projects) {
63-
headers.push(project.slug.replace(/\,\-_\s/g, ''));
102+
app.get('/piscines/:year/:month/csv/keyauth', async (req, res) => {
103+
// Check if the key in the Authorization header is valid
104+
if (!checkDirectAuthSecret(req)) {
105+
res.status(401);
106+
return res.send('Unauthorized');
64107
}
65108

66-
res.write(headers.join(',') + '\n');
67-
68-
for (const user of users) {
69-
const logtime = logtimes[user.login];
70-
const dropout = dropouts[user.login] ? 'yes' : 'no';
71-
const potentialDropout = potentialDropouts[user.login] ? 'yes' : 'no';
72-
const activeStudent = activeStudents[user.login] ? 'yes' : 'no';
73-
74-
const row = [
75-
user.login,
76-
activeStudent,
77-
dropout,
78-
potentialDropout,
79-
formatDate(user.locations[0]?.begin_at),
80-
logtime.weekOne / 60 / 60, // Convert seconds to hours
81-
logtime.weekTwo / 60 / 60,
82-
logtime.weekThree / 60 / 60,
83-
logtime.weekFour / 60 / 60,
84-
logtime.total / 60 / 60,
85-
user.cursus_users[0]?.level || 0,
86-
];
87-
88-
// Add every user's project to the row
89-
for (const project_user of user.project_users) {
90-
row.push(projectStatusToString(project_user, false));
91-
}
92-
93-
res.write(row.join(',') + '\n');
94-
}
95-
res.end();
109+
// Parse year and month parameters
110+
const year = parseInt(req.params.year);
111+
const month = parseInt(req.params.month);
112+
113+
return await respondPiscineCSV(prisma, res, year, month);
96114
});
97115
};

src/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { INTRA_PISCINE_ASSISTANT_GROUP_ID } from './env';
33
import { DISCO_PISCINE_CURSUS_IDS, PISCINE_CURSUS_IDS } from "./intra/cursus";
44
import { IntraUser } from "./intra/oauth";
55
import NodeCache from "node-cache";
6+
import { Request } from "express";
67

78
const cursusCache = new NodeCache();
89
const PISCINE_MIN_USER_COUNT = 40;
@@ -443,3 +444,16 @@ export const projectStatusToString = function(projectUser: ProjectUser, useNbsp:
443444
return projectUser.status;
444445
}
445446
};
447+
448+
export const checkDirectAuthSecret = function(req: Request): boolean {
449+
// Parse parameters from Authorization header
450+
const authHeader = req.headers['authorization'];
451+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
452+
return false;
453+
}
454+
if (!process.env.DIRECT_AUTH_SECRET || process.env.DIRECT_AUTH_SECRET === 'enter_your_direct_auth_secret_here') {
455+
return false;
456+
}
457+
const apiKey = authHeader.substring(7); // Remove 'Bearer ' prefix
458+
return apiKey === process.env.DIRECT_AUTH_SECRET;
459+
};

0 commit comments

Comments
 (0)