Skip to content

Commit b09da32

Browse files
committed
Update package-lock.json and package.json to include new CLI dependencies and improve project structure
- Added "cli" section in package-lock.json with dependencies including firebase-admin, pdf-lib, and puppeteer. - Updated package.json to include "cli/" in the file paths for better organization. - Removed unused dependencies from package-lock.json to streamline the project.
1 parent e746e79 commit b09da32

File tree

5 files changed

+1752
-826
lines changed

5 files changed

+1752
-826
lines changed

cli/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
geenrated-result
3+
*.log
4+
.DS_Store
5+

cli/index.js

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#!/usr/bin/env node
2+
const fs = require('fs');
3+
const os = require('os');
4+
const path = require('path');
5+
const readline = require('readline');
6+
const puppeteer = require('puppeteer');
7+
const { PDFDocument } = require('pdf-lib');
8+
const { initializeApp, cert } = require('firebase-admin/app');
9+
const { getFirestore } = require('firebase-admin/firestore');
10+
11+
const OUTPUT_ROOT = path.resolve(__dirname, 'geenrated-result');
12+
const BASE_URL = 'https://pcks.devflex.co.in';
13+
14+
// Reuse the existing admin SDK credential from the desktop app
15+
const serviceAccount = require('../desktop/src/utils/service_key.json');
16+
17+
initializeApp({
18+
credential: cert(serviceAccount),
19+
});
20+
21+
const DB = getFirestore();
22+
DB.settings({ ignoreUndefinedProperties: true });
23+
24+
function log(message) {
25+
console.log(`[PCKS CLI] ${message}`);
26+
}
27+
28+
function promptUser() {
29+
const rl = readline.createInterface({
30+
input: process.stdin,
31+
output: process.stdout,
32+
});
33+
34+
const ask = (question) =>
35+
new Promise((resolve) =>
36+
rl.question(question, (answer) => resolve(answer.trim()))
37+
);
38+
39+
return (async () => {
40+
log('Welcome to PCKS result downloader (CLI)');
41+
const year = await ask('Enter year (e.g. 2023): ');
42+
43+
const termMap = {
44+
1: 'first',
45+
2: 'annual',
46+
3: 'second',
47+
};
48+
49+
log('Select term:');
50+
log(' 1) First');
51+
log(' 2) Annual');
52+
log(' 3) Second');
53+
const termChoice = await ask('Choice (1/2/3): ');
54+
rl.close();
55+
56+
const term = termMap[termChoice] || termChoice.toLowerCase();
57+
58+
if (!year || !term || !['first', 'annual', 'second'].includes(term)) {
59+
throw new Error(
60+
'Invalid input. Please provide a year and a valid term (first/annual/second).'
61+
);
62+
}
63+
64+
return { year, term };
65+
})();
66+
}
67+
68+
async function fetchAdmissions(year, term) {
69+
const collectionPath = `results/${year}/${term}`;
70+
log(`Fetching admissions from Firestore at ${collectionPath}...`);
71+
const snapshot = await DB.collection(collectionPath).get();
72+
73+
if (snapshot.empty) {
74+
throw new Error('No results found for the provided year and term.');
75+
}
76+
77+
const results = snapshot.docs.map((doc) => ({
78+
id: doc.id,
79+
isCompleted: doc.data().isCompleted,
80+
}));
81+
82+
console.log(results);
83+
84+
const completed = results.filter((item) => item.isCompleted);
85+
log(
86+
`Found ${results.length} records, ${completed.length} marked as completed.`
87+
);
88+
if (!completed.length) {
89+
throw new Error('No completed results found to download.');
90+
}
91+
92+
return completed;
93+
}
94+
95+
async function downloadAndMerge({ year, term, admissions }) {
96+
const runDir = path.join(OUTPUT_ROOT, `${year}-${term}-${Date.now()}`);
97+
fs.mkdirSync(runDir, { recursive: true });
98+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pcks-cli-'));
99+
log(`Using temp directory: ${tempDir}`);
100+
log(`Output directory: ${runDir}`);
101+
102+
let browser;
103+
let page;
104+
const downloaded = [];
105+
106+
try {
107+
browser = await puppeteer.launch();
108+
page = await browser.newPage();
109+
page.setDefaultNavigationTimeout(60_000);
110+
111+
for (let index = 0; index < admissions.length; index++) {
112+
const admissionNo = admissions[index].id;
113+
const url = `${BASE_URL}/dashboard/result/view?batch=${year}&term=${term}&admissionNo=${admissionNo}`;
114+
log(`(${index + 1}/${admissions.length}) Fetching ${url}`);
115+
await page.goto(url, { waitUntil: 'networkidle2' });
116+
// await page.waitForTimeout(1_000);
117+
const pdfPath = path.join(tempDir, `${admissionNo}.pdf`);
118+
await page.emulateMediaType('print');
119+
await page.pdf({
120+
path: pdfPath,
121+
format: 'A4',
122+
margin: { top: '15mm', right: '15mm', bottom: '15mm', left: '15mm' },
123+
});
124+
downloaded.push(pdfPath);
125+
log(`Saved PDF for ${admissionNo} -> ${pdfPath}`);
126+
}
127+
128+
log('Merging PDFs...');
129+
const mergedPdf = await PDFDocument.create();
130+
for (let idx = 0; idx < downloaded.length; idx++) {
131+
const pdfPath = downloaded[idx];
132+
const pdfBytes = fs.readFileSync(pdfPath);
133+
const pdf = await PDFDocument.load(pdfBytes);
134+
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
135+
copiedPages.forEach((page) => mergedPdf.addPage(page));
136+
log(`Merged ${idx + 1}/${downloaded.length}`);
137+
}
138+
139+
const mergedPdfBytes = await mergedPdf.save();
140+
const mergedPath = path.join(runDir, `results-${year}-${term}.pdf`);
141+
fs.writeFileSync(mergedPath, mergedPdfBytes);
142+
log(`Merged PDF saved at ${mergedPath}`);
143+
144+
// Optionally keep individual PDFs alongside merged output for auditing
145+
downloaded.forEach((pdfPath) => {
146+
const dest = path.join(runDir, path.basename(pdfPath));
147+
fs.copyFileSync(pdfPath, dest);
148+
});
149+
log('Copied individual PDFs to output directory.');
150+
151+
return { mergedPath, runDir, tempDir, downloaded };
152+
} finally {
153+
if (browser) {
154+
try {
155+
await browser.close();
156+
} catch (error) {
157+
log(`Error closing browser: ${error.message}`);
158+
}
159+
}
160+
// Clean temp files
161+
try {
162+
downloaded.forEach((file) => fs.rmSync(file, { force: true }));
163+
fs.rmSync(tempDir, { recursive: true, force: true });
164+
} catch (error) {
165+
log(`Error cleaning up temp directory: ${error.message}`);
166+
}
167+
}
168+
}
169+
170+
async function run() {
171+
try {
172+
const { year, term } = await promptUser();
173+
const admissions = await fetchAdmissions(year, term);
174+
await downloadAndMerge({ year, term, admissions });
175+
log('All done. Check the geenrated-result folder for outputs.');
176+
} catch (error) {
177+
log(`Error: ${error.message || error}`);
178+
process.exitCode = 1;
179+
}
180+
}
181+
182+
run();

cli/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "cli",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"private": true,
6+
"type": "commonjs",
7+
"scripts": {
8+
"start": "node index.js"
9+
},
10+
"dependencies": {
11+
"firebase-admin": "^12.0.0",
12+
"pdf-lib": "^1.17.1",
13+
"puppeteer": "^22.8.0"
14+
}
15+
}
16+

0 commit comments

Comments
 (0)