diff --git a/src/models/file.ts b/src/models/file.ts index 746c4d1..2871455 100644 --- a/src/models/file.ts +++ b/src/models/file.ts @@ -7,6 +7,7 @@ const FileSchema = new mongoose.Schema({ type: Boolean, default: false }, + file_key: String, data_id: Number, // TODO - Wait until https://github.com/typegoose/auto-increment/pull/21 is merged and then change this to BigInt task_id: String, boss_app_id: String, diff --git a/src/services/npdi.ts b/src/services/npdi.ts index fc62645..48f3942 100644 --- a/src/services/npdi.ts +++ b/src/services/npdi.ts @@ -1,24 +1,31 @@ -import path from 'node:path'; import express from 'express'; -import { fileErrCallback } from '@/util'; -import { __appRoot } from '@/app-root'; import { restrictHostnames } from '@/middleware/host-limit'; import { config } from '@/config-manager'; +import { File } from '@/models/file'; +import { getCdnFileAsStream, streamFileToResponse } from '@/cdn'; const npdi = express.Router(); -npdi.get('/p01/data/1/:titleHash/:dataID/:fileHash', (request, response) => { - const { titleHash, fileHash } = request.params; - const contentPath = path.normalize(`${__appRoot}/../cdn/content/encrypted/${titleHash}/${fileHash}`); +npdi.get('/p01/data/1/:titleHash/:dataID/:fileHash', async (request, response) => { + const { dataID } = request.params; - response.sendFile(contentPath, { - headers: { - // * The misspelling here is intentional, it's what the official server sets - 'Content-Type': 'applicatoin/octet-stream', - 'Content-Disposition': 'attachment', - 'Content-Transfer-Encoding': 'binary' - } - }, fileErrCallback(response)); + const file = await File.findOne({ + data_id: dataID + }); + if (!file) { + return response.sendStatus(404); + } + + const fileStream = await getCdnFileAsStream('taskFile', file.file_key); + if (!fileStream) { + throw new Error('File not found in CDN'); + } + return streamFileToResponse(response, fileStream, { + // * The misspelling here is intentional, it's what the official server sets + 'Content-Type': 'applicatoin/octet-stream', + 'Content-Disposition': 'attachment', + 'Content-Transfer-Encoding': 'binary' + }); }); const router = express.Router(); diff --git a/src/services/npdl.ts b/src/services/npdl.ts index 0b17c3e..40704c8 100644 --- a/src/services/npdl.ts +++ b/src/services/npdl.ts @@ -1,10 +1,8 @@ -import { Stream } from 'node:stream'; import express from 'express'; import { getTaskFile } from '@/database'; -import { getCDNFileStream } from '@/util'; -import { logger } from '@/logger'; import { config } from '@/config-manager'; import { restrictHostnames } from '@/middleware/host-limit'; +import { getCdnFileAsStream, streamFileToResponse } from '@/cdn'; const npdl = express.Router(); @@ -31,21 +29,14 @@ npdl.get([ return; } - const key = `${appID}/${taskID}/${file.hash}`; - const readStream = await getCDNFileStream(key); - + const readStream = await getCdnFileAsStream('taskFile', file.file_key); if (!readStream) { response.sendStatus(404); return; } - response.setHeader('Last-Modified', new Date(Number(file.updated)).toUTCString()); - - Stream.pipeline(readStream, response, (err) => { - if (err) { - logger.error('Error with response stream: ' + err.message); - response.end(); - } + return streamFileToResponse(response, readStream, { + 'Last-Modified': new Date(Number(file.updated)).toUTCString() }); }); diff --git a/src/services/npts.ts b/src/services/npts.ts index 8a88be8..ff0235b 100644 --- a/src/services/npts.ts +++ b/src/services/npts.ts @@ -1,27 +1,55 @@ import path from 'node:path'; import express from 'express'; +import xmlbuilder from 'xmlbuilder'; import { fileErrCallback } from '@/util'; import { __appRoot } from '@/app-root'; import { config } from '@/config-manager'; import { restrictHostnames } from '@/middleware/host-limit'; +import { Task } from '@/models/task'; +import { File } from '@/models/file'; const npts = express.Router(); -npts.get('/p01/tasksheet/:id/:hash/:fileName', (request, response) => { - const { id, hash, fileName } = request.params; - const tasksheetPath = path.normalize(`${__appRoot}/../cdn/tasksheet/${id}/${hash}/${fileName}`); +npts.get('/p01/tasksheet/:id/:hash/:taskId', async (request, response) => { + const { id, taskId } = request.params; - response.sendFile(tasksheetPath, { - headers: { - 'Content-Type': 'text/xml' + const task = await Task.findOne({ + title_id: id, + id: taskId + }); + if (!task) { + return response.sendStatus(404); + } + + const files = await File.find({ + task_id: taskId + }); + + const xmlContent = { + TaskSheet: { + TitleId: task.title_id, + TaskId: task.id, + ServiceStatus: task.status, + Files: { + File: files.map(f => ({ + Filename: f.name, + DataId: f.data_id, + Type: f.type, + Url: `https://${config.domains.npdi}/p01/data/1/${task.title_id}/${f.data_id}/${f.hash}` + })) + } } - }, fileErrCallback(response)); + }; + + response.set('Content-Type', 'application/xml; charset=utf-8'); + response.send(xmlbuilder.create(xmlContent, { headless: true }).end({ pretty: true })); }); npts.get('/p01/tasksheet/:id/:hash/:subfolder/:fileName', (request, response) => { const { id, hash, subfolder, fileName } = request.params; const tasksheetPath = path.normalize(`${__appRoot}/../cdn/tasksheet/${id}/${hash}/_subfolder/${subfolder}/${fileName}`); + // TODO add subfolder support response.sendFile(tasksheetPath, { headers: { 'Content-Type': 'text/xml' diff --git a/src/types/mongoose/file.ts b/src/types/mongoose/file.ts index 4988404..845f3c8 100644 --- a/src/types/mongoose/file.ts +++ b/src/types/mongoose/file.ts @@ -2,6 +2,7 @@ import type { Model, HydratedDocument } from 'mongoose'; export interface IFile { deleted: boolean; + file_key: string; data_id: bigint; task_id: string; boss_app_id: string;