Skip to content

Commit 18f1ede

Browse files
committed
feat(pdf): add pdf sanitization
1 parent c6dff29 commit 18f1ede

File tree

11 files changed

+239
-40
lines changed

11 files changed

+239
-40
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ docker-compose.*yml
55
node_modules
66
npm-debug.log
77
dist
8+
uploads
9+
qpdf

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ docs/build/
114114
# OLD DATABASE, DO NOT PUSH, IT CONTAINS SENSITIVE DATA
115115
migration/etuutt_old/etuutt_old.sql
116116
# UE Data
117-
scripts/**/*.csv
117+
scripts/**/*
118+
!scripts/**/
119+
!scripts/**/*.ts
120+
uploads/
121+
qpdf/
118122

119123
# Dev files
120124
dev

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"private": true,
77
"license": "UNLICENSED",
88
"scripts": {
9+
"postinstall": "rm -rf qpdf && wget -q \"https://github.com/qpdf/qpdf/releases/download/v12.1.0/qpdf-12.1.0-bin-linux-x86_64.zip\" -O qpdf.zip && unzip qpdf.zip -d qpdf && rm qpdf.zip",
910
"build": "npx nest build",
1011
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
1112
"start": "NODE_ENV=development npx nest start",
@@ -29,10 +30,10 @@
2930
"test:db:editor": "env-cmd -f .env.test -- pnpm prisma studio",
3031
"seed:base": "env-cmd -f .env.dev -- ts-node scripts/seed/base.ts",
3132
"seed:ue": "env-cmd -f .env.dev -- ts-node scripts/seed/ue.ts",
32-
"seed:ue:aliases": "env-cmd -f .env.dev -- ts-node scripts/seed/aliases.ts",
33+
"seed:ue:annals": "env-cmd -f .env.dev -- ts-node scripts/seed/ue-annals.ts",
3334
"seed:base:prod": "node scripts/seed/base.js",
3435
"seed:ue:prod": "node scripts/seed/ue.js",
35-
"seed:ue:aliases:prod": "node scripts/seed/aliases.js",
36+
"seed:ue:annals:prod": "node scripts/seed/ue-annals.js",
3637
"script:deps:graph": "ts-node scripts/dependency_graph.ts"
3738
},
3839
"dependencies": {
@@ -56,6 +57,7 @@
5657
"multer": "1.4.5-lts.1",
5758
"pactum-matchers": "^1.1.7",
5859
"passport-jwt": "^4.0.1",
60+
"pdf-lib": "^1.17.1",
5961
"pdfjs-dist": "^5.0.375",
6062
"pdfkit": "^0.14.0",
6163
"prisma": "^5.17.0",

pnpm-lock.yaml

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

prisma/schema.prisma

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ model UeAnnal {
310310

311311
model UeAnnalType {
312312
id String @id @default(uuid())
313-
name String @db.VarChar(255)
313+
name String @unique @db.VarChar(255)
314314
315315
annals UeAnnal[]
316316
}
@@ -812,7 +812,7 @@ model UTTBranch {
812812

813813
model UTTBranchOption {
814814
id String @id @default(uuid())
815-
code String @db.VarChar(10)
815+
code String @db.VarChar(10) @unique
816816
name String @db.VarChar(255)
817817
branchCode String
818818
descriptionTranslationId String @unique

prisma/seed/tsconfig.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"files": ["../../src/types.d.ts"]
4+
}

scripts/seed/aliases.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ async function main() {
3232
console.info('\x1b[42;30m✅ Aliases have been created/updated\x1b[0m');
3333
}
3434

35-
main();
35+
export { main as seedUeAliases };

scripts/seed/base.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,22 @@ async function main() {
180180
}),
181181
),
182182
);
183+
184+
console.log('Updating Annal Types');
185+
const annalTypes = ['Final', 'Médian', 'Partiel', 'Examen de TP', 'Projet', 'Devoir Maison'];
186+
await Promise.all(
187+
annalTypes.map((type) =>
188+
prisma.ueAnnalType.upsert({
189+
where: {
190+
name: type,
191+
},
192+
update: {},
193+
create: {
194+
name: type,
195+
},
196+
}),
197+
),
198+
);
183199
}
184200

185201
main();

scripts/seed/ue-annals.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { readdir, readFile, writeFile } from 'fs/promises';
2+
import { PrismaClient } from '@prisma/client';
3+
4+
const prisma = new PrismaClient();
5+
6+
const annalTypes = {
7+
final: 'Final',
8+
median: 'Médian',
9+
dm: 'Devoir Maison',
10+
partiel: 'Partiel',
11+
};
12+
13+
async function main() {
14+
const folderPath = process.argv[2] || 'scripts/seed/ues';
15+
16+
const ueDirectories = Object.fromEntries(
17+
(
18+
await Promise.all(
19+
(
20+
await readdir(folderPath, { withFileTypes: true })
21+
)
22+
.filter((dirent) => dirent.isDirectory())
23+
.map(async (dirent) => {
24+
const dirname = dirent.name;
25+
return (await readdir(`${folderPath}/${dirname}`, { withFileTypes: true }))
26+
.filter((dir) => dir.isDirectory())
27+
.map((dir) => [dir.name, `${folderPath}/${dirname}/${dir.name}`]);
28+
}),
29+
)
30+
).flat(),
31+
) as Record<string, string>;
32+
33+
const databaseUeofs = (
34+
await prisma.ueof.findMany({
35+
select: { code: true },
36+
})
37+
).map((ueof) => ueof.code);
38+
39+
// console.log(ueDirectories);
40+
for (const [ueName, uePath] of Object.entries(ueDirectories)) {
41+
const annals = (await readdir(uePath, { withFileTypes: true })).filter((dirent) =>
42+
dirent.name.match(/\.(png|jpeg|jpg|pdf|tiff)$/),
43+
);
44+
const other = (await readdir(uePath, { withFileTypes: true })).filter(
45+
(dirent) => !dirent.name.match(/\.(png|jpeg|jpg|pdf|tiff)$/) && !dirent.name.match(/^ue(?:\.md)?\.docx$/),
46+
);
47+
if (other.length > 0) console.log(other);
48+
49+
// Import annal files
50+
for (const annal of annals) {
51+
const name = annal.name
52+
.replace(/\.[^.]+$/, '')
53+
.replace(
54+
/[-_](?=(?<sem1>[AP]\d{4})?(?<ue1>[^-_]*(?![AP]\d{4}))?)[^-_]*[-_](?=(?<sem2>[AP]\d{4})?(?<ue2>[^-_]*(?![AP]\d{4}))?)[^-_]*/i,
55+
'-$<sem1>$<sem2>-$<ue1>$<ue2>',
56+
)
57+
.replace(/(?<=-[AP])20(?=\d{2}-)/, '');
58+
const [type, semester] = name.split('-');
59+
if (!(type in annalTypes)) console.warn(`Unknown annal type: ${type}`);
60+
const ueofs = databaseUeofs
61+
.filter(
62+
(ueof) =>
63+
ueof.startsWith(`${ueName.replace(/A$/, '')}_`) &&
64+
(Number(semester.slice(1)) >= Number(ueof.slice(-2)) || ueof.endsWith('U23')),
65+
)
66+
.sort((a, b) => {
67+
const ueofVector = Number(ueName.endsWith('A')) && Number(/_EN_/.test(b)) - Number(/_EN_/.test(a));
68+
if (ueofVector) return ueofVector;
69+
return Number(a.slice(-2)) - Number(b.slice(-2));
70+
});
71+
72+
// console.info(`[NAME FIX] ${annal.name} -> ${name}`);
73+
// console.log(`[UEOF SELECTION] ${ueName} -> ${ueofs[0]}`);
74+
75+
if (!ueofs.length) console.warn(`[UEOF SELECTION FAILED] ${ueName}`);
76+
else {
77+
const { id } = await prisma.ueAnnal.create({
78+
data: {
79+
type: {
80+
connect: {
81+
name: annalTypes[type],
82+
},
83+
},
84+
uploadComplete: true,
85+
ueof: {
86+
connect: {
87+
code: ueofs[0],
88+
},
89+
},
90+
semester: {
91+
connectOrCreate: {
92+
where: {
93+
code: semester,
94+
},
95+
create: {
96+
code: semester,
97+
start: new Date(),
98+
end: new Date(),
99+
},
100+
},
101+
},
102+
},
103+
});
104+
await writeFile(`uploads/exams/${id}.pdf`, (await readFile(`${uePath}/${annal.name}`)) as Uint8Array);
105+
}
106+
}
107+
}
108+
109+
console.info('\x1b[42;30m✅ Import complete\x1b[0m');
110+
}
111+
112+
main();

scripts/seed/ue.ts

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createInterface } from 'readline/promises';
33
import { parse } from '@fast-csv/parse';
44
import { PrismaClient } from '@prisma/client';
55
import type { TextItem } from 'pdfjs-dist/types/src/display/api';
6+
import { seedUeAliases } from './aliases';
67

78
const prisma = new PrismaClient();
89

@@ -291,7 +292,7 @@ async function main() {
291292
},
292293
},
293294
branchOptions: {
294-
connect: credit.branchOptions.map((id) => ({ id })),
295+
connect: credit.branchOptions.map((id) => ({ code: id })),
295296
},
296297
})),
297298
},
@@ -358,39 +359,45 @@ async function main() {
358359
});
359360
}
360361

362+
console.info('\x1b[42;30mImporting UE aliases\x1b[0m');
363+
await seedUeAliases();
364+
361365
const aliases = await prisma.ueAlias.findMany();
362366

363367
console.info('\x1b[42;30mImporting UE requirements...\x1b[0m');
364-
365-
try {
366-
await Promise.all(
367-
ues.map((ueof) =>
368-
prisma.ueof.update({
369-
where: {
370-
code: ueof.ueof_code,
371-
},
372-
data: {
373-
requirements: {
374-
connect: ueof.requirements
375-
.map((code) => {
376-
const result = aliases.find((alias) => alias.code === code);
377-
return result ? result.standsFor : code;
378-
})
379-
.filter((code) => code)
380-
.map((code) => ({
381-
code: code,
382-
})),
383-
},
368+
const results = await Promise.allSettled(
369+
ues.map((ueof) =>
370+
prisma.ueof.update({
371+
where: {
372+
code: ueof.ueof_code,
373+
},
374+
data: {
375+
requirements: {
376+
connect: ueof.requirements
377+
.map((code) => {
378+
const result = aliases.find((alias) => alias.code === code);
379+
return result ? result.standsFor : code;
380+
})
381+
.filter((code) => code)
382+
.map((code) => ({
383+
code: code,
384+
})),
384385
},
385-
}),
386-
),
387-
);
388-
} catch (error) {
389-
console.error(
390-
'\x1b[41;30mAn error occurred while importing UE requirements. Try `$ pnpm seed:ue:aliases` first.\x1b[0m',
386+
},
387+
}),
388+
),
389+
);
390+
const fails = [];
391+
for (let i = 0; i < results.length; i++) if (results[i].status === 'rejected') fails.push(ues[i]);
392+
if (fails.length)
393+
return console.warn(
394+
`\x1b[41;30m[UNKNOWN_UE_REFERENCE] Unknown UE reference${fails.length ? 's' : ''}: ${[
395+
...new Set(fails.map((ueof) => ueof.requirements).flat()),
396+
]
397+
.filter((mUe) => ues.every((ue) => ue.code !== mUe))
398+
.join(', ')}. Consider adding UE aliases\x1b[0m`,
391399
);
392-
return;
393-
}
400+
394401
console.info('\x1b[42;30mFetching current INSUEs\x1b[0m');
395402
try {
396403
const insues = await fetchINSUE();

0 commit comments

Comments
 (0)