|
|
|
|
@@ -1,8 +1,9 @@
|
|
|
|
|
import { GeneralService } from '../share/generalservice.js';
|
|
|
|
|
import { sendError } from '../utils/response.js';
|
|
|
|
|
import { verifyToken } from '../utils/token.js'; // ✅ เพิ่ม verifyToken
|
|
|
|
|
import fs from 'fs';
|
|
|
|
|
import path from 'path';
|
|
|
|
|
import archiver from 'archiver'; // ⚠️ ต้อง npm install archiver
|
|
|
|
|
import archiver from 'archiver';
|
|
|
|
|
|
|
|
|
|
export class projectDownload {
|
|
|
|
|
|
|
|
|
|
@@ -10,65 +11,104 @@ export class projectDownload {
|
|
|
|
|
this.generalService = new GeneralService();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GET Route ไม่รับ Body ดังนั้นรับ Parameter จาก Query String หรือ Params
|
|
|
|
|
async onNavigate(req, res) {
|
|
|
|
|
this.generalService.devhint(1, 'projectDownload.js', 'onNavigate() start');
|
|
|
|
|
|
|
|
|
|
// รับ prjseq จาก Query String (เช่น /projectdownload?prjseq=123)
|
|
|
|
|
const prjseq = req.query.prjseq;
|
|
|
|
|
const docType = req.query.docType;
|
|
|
|
|
|
|
|
|
|
// ✅ 1. แกะ Token เพื่อหา Database Schema (เพราะต้อง Query DB)
|
|
|
|
|
let token = req.headers.authorization?.split(' ')[1];
|
|
|
|
|
let database = 'dbo'; // Default
|
|
|
|
|
|
|
|
|
|
if (token) {
|
|
|
|
|
const decoded = verifyToken(token);
|
|
|
|
|
if (decoded && decoded.organization) {
|
|
|
|
|
database = decoded.organization;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!prjseq) {
|
|
|
|
|
return res.status(400).json(sendError('กรุณาระบุ prjseq', 'Missing prjseq parameter'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await this.onProjectDownload(req, res, prjseq);
|
|
|
|
|
return await this.onProjectDownload(req, res, prjseq, docType, database);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async onProjectDownload(req, res, prjseq) {
|
|
|
|
|
async onProjectDownload(req, res, prjseq, docType, database) {
|
|
|
|
|
try {
|
|
|
|
|
const folderPath = `uploads/projects/${prjseq}`;
|
|
|
|
|
// ✅ 2. Query เช็คค่า prjdoc ใน Database ก่อน (Source of Truth)
|
|
|
|
|
const sql = `SELECT prjdoc FROM ${database}.prjmst WHERE prjseq = $1`;
|
|
|
|
|
const result = await this.generalService.executeQueryParam(database, sql, [prjseq]);
|
|
|
|
|
|
|
|
|
|
// 1. ตรวจสอบว่า Folder มีอยู่จริงหรือไม่
|
|
|
|
|
if (!fs.existsSync(folderPath)) {
|
|
|
|
|
return res.status(404).json(sendError('ไม่พบเอกสารของโครงการนี้', 'Project documents not found'));
|
|
|
|
|
// ถ้าไม่เจอโครงการเลย
|
|
|
|
|
if (result.length === 0) {
|
|
|
|
|
return res.json(sendError('ไม่พบข้อมูลโครงการนี้ในระบบ', 'Project not found in DB', 404));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. ตั้งค่า Response Header ให้เป็นไฟล์ Zip
|
|
|
|
|
const archive = archiver('zip', {
|
|
|
|
|
zlib: { level: 9 } // บีบอัดสูงสุด
|
|
|
|
|
const prjdoc = result[0].prjdoc;
|
|
|
|
|
|
|
|
|
|
// ✅ 3. เช็คว่า prjdoc ว่างหรือไม่? (null, undefined, หรือ string ว่าง)
|
|
|
|
|
if (!prjdoc || prjdoc.trim() === '') {
|
|
|
|
|
return res.json(sendError('ไม่พบเอกสารแนบในระบบ (prjdoc ว่าง)', 'No documents recorded in database', 404));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ✅ 4. แปลงรายชื่อไฟล์จาก DB (Comma Separated) เป็น Array
|
|
|
|
|
// ตัวอย่าง: "file1.jpg,file2.pdf" -> ["file1.jpg", "file2.pdf"]
|
|
|
|
|
let dbFiles = prjdoc.split(',').map(f => f.trim()).filter(f => f !== '');
|
|
|
|
|
|
|
|
|
|
const folderPath = `uploads/projects/${prjseq}`;
|
|
|
|
|
|
|
|
|
|
// เช็คว่ามีโฟลเดอร์จริงไหม
|
|
|
|
|
if (!fs.existsSync(folderPath)) {
|
|
|
|
|
return res.json(sendError('ไม่พบไฟล์ใน Server (โฟลเดอร์สูญหาย)', 'Project folder missing on server', 404));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ✅ 5. กรองไฟล์: ต้องมีชื่อใน DB **และ** มีไฟล์อยู่จริงบน Disk
|
|
|
|
|
// (และผ่าน filter docType ถ้ามี)
|
|
|
|
|
let validFiles = dbFiles.filter(filename => {
|
|
|
|
|
// Filter docType (ถ้าส่งมา)
|
|
|
|
|
if (docType && !filename.toLowerCase().endsWith(`.${docType.toLowerCase()}`)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// เช็คว่าไฟล์มีอยู่จริงไหม
|
|
|
|
|
const fullPath = path.join(folderPath, filename);
|
|
|
|
|
return fs.existsSync(fullPath);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ถ้ากรองแล้วไม่เหลือไฟล์เลย
|
|
|
|
|
if (validFiles.length === 0) {
|
|
|
|
|
return res.json(sendError('ไม่พบไฟล์เอกสารที่สามารถดาวน์โหลดได้', 'No valid files found', 404));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- เริ่มกระบวนการ Zip ---
|
|
|
|
|
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
|
|
|
const zipFilename = `project_${prjseq}_documents.zip`;
|
|
|
|
|
|
|
|
|
|
res.attachment(zipFilename); // บอก Browser ว่าให้ Download เป็นชื่อนี้
|
|
|
|
|
res.attachment(zipFilename);
|
|
|
|
|
|
|
|
|
|
// 3. Handle Events
|
|
|
|
|
archive.on('error', function(err) {
|
|
|
|
|
console.error('Zip Error:', err);
|
|
|
|
|
res.status(500).send({error: err.message});
|
|
|
|
|
console.error('Archiver Error:', err);
|
|
|
|
|
if (!res.headersSent) {
|
|
|
|
|
res.status(500).send({error: err.message});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// เมื่อบีบอัดเสร็จและส่งข้อมูลครบ
|
|
|
|
|
archive.on('end', function() {
|
|
|
|
|
console.log(`Archive wrote ${archive.pointer()} bytes`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 4. Pipe Archive -> Response (Stream)
|
|
|
|
|
archive.pipe(res);
|
|
|
|
|
|
|
|
|
|
// 5. เอาไฟล์ใน Folder ใส่ Zip
|
|
|
|
|
// name: false หมายถึงเอาไฟล์ใส่ root ของ zip เลย ไม่ต้องซ้อน folder
|
|
|
|
|
archive.directory(folderPath, false);
|
|
|
|
|
validFiles.forEach(filename => {
|
|
|
|
|
const filePath = path.join(folderPath, filename);
|
|
|
|
|
archive.file(filePath, { name: filename });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 6. สั่งจบการบีบอัด (จะเริ่มส่งข้อมูลทันที)
|
|
|
|
|
await archive.finalize();
|
|
|
|
|
|
|
|
|
|
// ⚠️ หมายเหตุ: เราไม่ return อะไรกลับไป เพราะเรา Pipe Stream ใส่ res โดยตรงแล้ว
|
|
|
|
|
// globalResponseHandler จะไม่ทำงานเพราะเราไม่ได้ res.json() ซึ่งถูกต้องแล้วสำหรับการโหลดไฟล์
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Download Controller Error:', error);
|
|
|
|
|
return res.status(500).json(sendError('เกิดข้อผิดพลาดในการดาวน์โหลด', 'Download Error'));
|
|
|
|
|
if (!res.headersSent) {
|
|
|
|
|
return res.json(sendError('เกิดข้อผิดพลาดในการดาวน์โหลด', 'Download Error', 500));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|