Conversation
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I’m requesting changes because there are functional issues that block correct behavior and violate the requirements. Specifically:
- Incorrect formidable parsing: at
src/createServer.jsyou readfields.compressionType?.[0]andfiles.file?.[0]. Formidable typically returns a string for fields and a single file object. This can cause validation to fail and compression not to run. Fix by normalizing values, e.g.,const compressionType = String(fields.compressionType || '').trim().toLowerCase();andconst fileData = files.filethen usefileData.filepath/fileData.originalFilename. - Wrong output extension mapping: you set
ext = compressionType, producing.gzip/.deflate/.br. The requirement isgzip -> .gz,deflate -> .dfl,br -> .br. Add a mapping likeconst extMap = { gzip: 'gz', deflate: 'dfl', br: 'br' }; const ext = extMap[compressionType];and use it inContent-Disposition. - Missing stream error handling: the read/compression pipeline lacks
errorlisteners. Attacherrorhandlers to both the file read stream and the zlib stream (or usestream.pipeline) and ensure a non-200 status is returned on failure to meet the error-handling requirement. - Routing cleanup: the
/compressnon-POST path should return 400, and avoid the unreachable 404 in that block.
Minor: fix typos in messages (e.g., “Unsupported compression type”, “Not Found”). The form is well structured—once the parsing and error handling are corrected, the flow should pass. Keep going—these are straightforward fixes that will make your server robust.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
src/createServer.js
Outdated
| const compressionType = (fields.compressionType?.[0] || '') | ||
| .trim() | ||
| .toLowerCase(); | ||
|
|
||
| const supported = ['gzip', 'deflate', 'br']; | ||
|
|
||
| if (!supported.includes(compressionType)) { | ||
| res.statusCode = 400; | ||
|
|
||
| return res.end('Unsupported compresion type'); | ||
| } | ||
|
|
||
| const fileData = files.file?.[0]; | ||
|
|
||
| if (!fileData) { | ||
| res.statusCode = 400; | ||
|
|
||
| return res.end('Error parsing file'); | ||
| } | ||
|
|
||
| const uploadedFilePath = files.file?.[0].filepath; | ||
| const originalName = files.file?.[0]?.originalFilename; | ||
| const originalNameSafe = originalName || 'file'; |
There was a problem hiding this comment.
Parsing of form fields and files is incorrect. You treat fields.compressionType and files.file as arrays (fields.compressionType?.[0], files.file?.[0]), but formidable typically returns a string for a field and an object for a file (e.g. fields.compressionType is a string, files.file is an object with filepath and originalFilename). Because of this the extraction and validation will fail and branches may not execute. Update to read values like:
const compressionType = String(fields.compressionType || '').trim().toLowerCase();const fileData = files.file; // then use fileData.filepath and fileData.originalFilename
This fixes validation for missing fields/files and lets compression run correctly (required by the task).
src/createServer.js
Outdated
| const ext = compressionType; | ||
|
|
||
| res.writeHead(200, { | ||
| 'Content-Type': 'application/octet-stream', | ||
| 'Content-Disposition': `attachment; filename=${originalNameSafe}.${ext}`, | ||
| }); |
There was a problem hiding this comment.
The output filename extension is incorrect. You set const ext = compressionType and then use filename=${originalNameSafe}.${ext} which produces filenames like file.txt.gzip. The task requires specific extensions: gzip -> .gz, deflate -> .dfl, br -> .br. Replace with a mapping, for example:
const extMap = { gzip: 'gz', deflate: 'dfl', br: 'br' };
const ext = extMap[compressionType];and use that ext for the Content-Disposition filename. This change is required by the checklist.
src/createServer.js
Outdated
| 'Content-Disposition': `attachment; filename=${originalNameSafe}.${ext}`, | ||
| }); | ||
|
|
||
| fs.createReadStream(uploadedFilePath).pipe(compressStream).pipe(res); |
There was a problem hiding this comment.
No error handling is attached to the read/compression streams. You pipe fs.createReadStream(uploadedFilePath).pipe(compressStream).pipe(res) but do not listen for error events on the read stream or the compression stream. If an error occurs while reading or compressing, the server may crash or leave the client hanging. Add error listeners to the read stream and the compression stream (and handle them by setting an appropriate non-200 response or ending the response). This satisfies the requirement that streaming/compression errors result in a non-200 outcome.
src/createServer.js
Outdated
| } else if (req.url === '/compress') { | ||
| if (req.method !== 'POST') { | ||
| res.statusCode = 400; | ||
|
|
||
| return res.end('GET not allowed on /compress'); | ||
| } | ||
| res.statusCode = 404; | ||
|
|
||
| return res.end('Not Found'); |
There was a problem hiding this comment.
The /compress routing block contains unreachable / redundant code. You already handle req.url === '/compress' && req.method === 'POST' earlier. The subsequent else if (req.url === '/compress') branch sets 400 for non-POST (good) but then sets 404/'Not Found' unconditionally after the if, which is unreachable for the non-POST case and contradicts the requirement (GET to /compress should return 400). Simplify this block to only handle non-POST methods and return 400. Ensure non-existent endpoints return 404 elsewhere (per requirements).
src/createServer.js
Outdated
| return res.end('Unsupported compresion type'); | ||
| } | ||
|
|
||
| const fileData = files.file?.[0]; | ||
|
|
||
| if (!fileData) { | ||
| res.statusCode = 400; | ||
|
|
||
| return res.end('Error parsing file'); | ||
| } | ||
|
|
||
| const uploadedFilePath = files.file?.[0].filepath; | ||
| const originalName = files.file?.[0]?.originalFilename; | ||
| const originalNameSafe = originalName || 'file'; | ||
|
|
||
| if (!uploadedFilePath) { | ||
| res.statusCode = 400; | ||
|
|
||
| return res.end('File path not found'); | ||
| } | ||
|
|
||
| let compressStream; | ||
|
|
||
| switch (compressionType) { | ||
| case 'gzip': | ||
| compressStream = zlib.createGzip(); | ||
| break; | ||
| case 'deflate': | ||
| compressStream = zlib.createDeflate(); | ||
| break; | ||
| case 'br': | ||
| compressStream = zlib.createBrotliCompress(); | ||
| break; | ||
| } | ||
|
|
||
| const ext = compressionType; | ||
|
|
||
| res.writeHead(200, { | ||
| 'Content-Type': 'application/octet-stream', | ||
| 'Content-Disposition': `attachment; filename=${originalNameSafe}.${ext}`, | ||
| }); | ||
|
|
||
| fs.createReadStream(uploadedFilePath).pipe(compressStream).pipe(res); | ||
| }); | ||
| } else if (req.url === '/compress') { | ||
| if (req.method !== 'POST') { | ||
| res.statusCode = 400; | ||
|
|
||
| return res.end('GET not allowed on /compress'); | ||
| } | ||
| res.statusCode = 404; | ||
|
|
||
| return res.end('Not Found'); | ||
| } else { | ||
| res.statusCode = 404; | ||
|
|
||
| return res.end('Not FOound'); |
There was a problem hiding this comment.
Minor: fix error message typos for clarity. At line 43 you have 'Unsupported compresion type' (typo) and at line 99 'Not FOound'. While message text may not be asserted in tests, fixing typos reduces confusion and improves maintainability. Also verify the messages align with the intended status codes (400/404) as required.
| res.end(` | ||
| <form action="/compress" method="POST" enctype="multipart/form-data"> | ||
| <input type="file" name="file" required /> | ||
| <select name="compressionType" required> | ||
| <option value="gzip">gzip</option> | ||
| <option value="deflate">deflate</option> | ||
| <option value="br">br</option> | ||
| </select> | ||
| <button type="submit">Compress</button> | ||
| </form> | ||
| `); |
There was a problem hiding this comment.
The HTML form served at / correctly uses name="file" and name="compressionType" with options gzip, deflate, br, and sends POST to /compress with enctype="multipart/form-data". The form itself meets the task requirements — ensure the server parsing is adjusted to match these exact field names (see parsing comment above).
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I’m requesting changes because there are several critical blockers that prevent the app from reliably meeting the requirements.
- Form parsing: at lines where you read
fields.compressionType?.[0], Formidable returns strings (not arrays), which can makecompressionTypeundefined and break validation. Normalize withString(fields.compressionType || '').trim().toLowerCase()to ensure correct type and casing. Similarly,files.file?.[0]is incorrect;files.fileis a single file object in most versions. Use one normalized object and extractfilepath/pathandoriginalFilenamesafely. - Output filename: the
Content-Dispositioncurrently appends.${compressionType}(e.g.,.gzip), which violates the required mapping. Mapgzip -> .gz,deflate -> .dfl,br -> .brand use that extension in the header so the client gets the correctly named file. - Stream robustness: you call
res.writeHead(200)before piping and attach separate error handlers, which can lead to wrong status codes if a later stream fails. Usestream.pipeline(readStream, compressStream, res, cb)and only set headers before pipeline, relying on the callback to send a 500 if an error occurs before headers are sent; otherwise destroy the response.
Minor fixes: remove unreachable 404 in the non-POST /compress branch (return 400 directly), add a default in the switch to guard unexpected values, and fix the typo in the final 404 message (“Not Found”). You’ve got the core structure and zlib usage right—tightening the parsing, extension mapping, and pipeline error handling will make it pass the checklist reliably.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
src/createServer.js
Outdated
| const compressionType = (fields.compressionType?.[0] || '') | ||
| .trim() | ||
| .toLowerCase(); |
There was a problem hiding this comment.
Form parsing: don't treat fields.compressionType as an array. Formidable typically returns field values as strings, not arrays. The code uses fields.compressionType?.[0] which can be undefined and break validation. Normalize the field into a string, trim and lower-case it, for example:
const compressionType = String(fields.compressionType || '').trim().toLowerCase();This fixes reliable extraction of the compression type (required by the checklist).
| const fileData = files.file?.[0]; | ||
|
|
||
| if (!fileData) { | ||
| res.statusCode = 400; | ||
|
|
||
| return res.end('Error parsing file'); | ||
| } | ||
|
|
||
| const uploadedFile = files.file?.[0]; | ||
| const uploadedFilePath = uploadedFile?.filepath; | ||
|
|
||
| const originalNameSafe = uploadedFile.originalFilename || 'file'; |
There was a problem hiding this comment.
File extraction: files.file is a single file object, not an array. The code uses files.file?.[0] at lines 46 and 54 and duplicates variables (fileData and uploadedFile). Use a single normalized value such as:
const fileData = files.file; // or files.file[0] only if your formidable version actually returns an array
const uploadedFilePath = fileData && (fileData.filepath || fileData.path);
const originalNameSafe = fileData && (fileData.originalFilename || fileData.originalname || 'file');Then validate uploadedFilePath. This fixes missing-file validation and ensures you create the read stream from the correct path (required behavior).
| res.writeHead(200, { | ||
| 'Content-Type': 'application/octet-stream', | ||
| 'Content-Disposition': `attachment; filename=${originalNameSafe}.${compressionType}`, | ||
| }); |
There was a problem hiding this comment.
Filename extension mapping is incorrect. The response currently appends the raw compressionType (e.g. .gzip or .deflate) at line 93. The requirement is to append specific extensions: gzip -> .gz, deflate -> .dfl, br -> .br. Add a mapping and use it when setting Content-Disposition, for example:
const extMap = { gzip: 'gz', deflate: 'dfl', br: 'br' };
const ext = extMap[compressionType];
'Content-Disposition': `attachment; filename="${originalNameSafe}.${ext}"`,Update the header so the client receives the correctly named file.
| const readStream = fs.createReadStream(uploadedFilePath); | ||
|
|
||
| readStream.on('error', () => { | ||
| res.statusCode = 500; | ||
| res.end('Error reading file'); | ||
| }); | ||
|
|
||
| compressStream.on('error', () => { | ||
| res.statusCode = 500; | ||
| res.end('Error compressing file'); | ||
| }); | ||
|
|
||
| res.writeHead(200, { | ||
| 'Content-Type': 'application/octet-stream', | ||
| 'Content-Disposition': `attachment; filename=${originalNameSafe}.${compressionType}`, | ||
| }); | ||
|
|
||
| readStream.pipe(compressStream).pipe(res); |
There was a problem hiding this comment.
Use stream.pipeline (or equivalent) and delay sending success headers until streaming starts. Current code creates readStream, attaches separate error handlers and calls res.writeHead(200) before piping. If an error occurs later headers are already sent and you cannot change the status code. Replace the manual pipe with:
const { pipeline } = require('stream');
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', `attachment; filename="${originalNameSafe}.${ext}"`);
pipeline(readStream, compressStream, res, (err) => {
if (err) {
// if headers not sent, send 500; otherwise destroy response
if (!res.headersSent) {
res.statusCode = 500;
res.end('Error processing file');
} else {
res.destroy(err);
}
}
});This ensures robust error handling for streams and prevents the server from crashing (required).
src/createServer.js
Outdated
| } else if (req.url === '/compress') { | ||
| if (req.method !== 'POST') { | ||
| res.statusCode = 400; | ||
|
|
||
| return res.end('GET not allowed on /compress'); | ||
| } | ||
| res.statusCode = 404; | ||
|
|
||
| return res.end('Not Found'); |
There was a problem hiding this comment.
Routing: the /compress non-POST branch contains unreachable code. At lines 98–106 you check if (req.method !== 'POST') { return 400 } and then set 404 after that — that 404 is unreachable. Instead, explicitly return 400 for non-POST requests to /compress and remove the unreachable 404. The task requires GET /compress to return 400.
src/createServer.js
Outdated
| res.statusCode = 404; | ||
|
|
||
| return res.end('Not Foound'); |
There was a problem hiding this comment.
Typo in final 404 message. The last res.end('Not Foound') has a typo. Use Not Found for consistency and clarity.
| switch (compressionType) { | ||
| case 'gzip': | ||
| compressStream = zlib.createGzip(); | ||
| break; | ||
| case 'deflate': | ||
| compressStream = zlib.createDeflate(); | ||
| break; | ||
| case 'br': | ||
| compressStream = zlib.createBrotliCompress(); | ||
| break; | ||
| } |
There was a problem hiding this comment.
Add a default in the switch to be defensive. You validated the compression type earlier, but adding a default branch in the switch (compressionType) helps maintain clarity and avoids compressStream being undefined if something unexpected happens. For example, return a 400 if no stream is created.
mateacademy-ai-mentor
left a comment
There was a problem hiding this comment.
Review Summary
I am approving your solution as an educational override to help you keep progressing while learning. You set up the server, form, zlib usage, and routes well, and your HTML form correctly includes the file input and compressionType options.
Critical issues to address next:
- Multipart parsing: On lines 46 and 54,
files.file?.[0]assumes an array; Formidable usually provides a single file atfiles.file. This can makefileDataundefined and break validation. Normalize once, e.g.,const uploadedFile = files.file; const uploadedFilePath = uploadedFile && (uploadedFile.filepath || uploadedFile.path); const originalNameSafe = uploadedFile?.originalFilename || 'file';and validate those. - Unsupported compression handling: Your
switchdefault (lines 77–79) silently falls back to gzip. Per requirements, return 400 for unknown types. Prefer a map from type → {factory, extension} and reject unknown keys before compression. - Output filename extension: On line 96,
filename=${originalNameSafe}.${compressionType}yieldsfile.txt.gzip. Map explicitly:gzip -> .gz,deflate -> .dfl,br -> .br, and useattachment; filename="${outName}". - Stream robustness: Using direct
readStream.pipe(compressStream).pipe(res)(lines 82–99) risks sending 200 even if a later error occurs. Usestream.pipeline(readStream, compressStream, res, cb)and set headers before starting, handling errors in the callback to avoid incorrect 200s.
Minor improvements:
- Quote the filename in
Content-Dispositionto handle spaces. - Remove redundant
files.file?.[0]references to simplify the code.
You’re very close—tightening file parsing, extensions, and pipeline error handling will make the solution solid. Keep up the great work!
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Footnotes
| const fileData = files.file?.[0]; | ||
|
|
||
| if (!fileData) { | ||
| res.statusCode = 400; | ||
|
|
||
| return res.end('Error parsing file'); | ||
| } | ||
|
|
||
| const uploadedFile = files.file?.[0]; | ||
| const uploadedFilePath = uploadedFile?.filepath; | ||
|
|
||
| const originalNameSafe = uploadedFile.originalFilename || 'file'; |
There was a problem hiding this comment.
Incorrect access to the uploaded file object. You use files.file?.[0] in multiple places (lines 46 and 54). Formidable normally provides a single file object at files.file (not an array), so indexing with [0] can make fileData undefined and break validation. Also originalFilename is read without ensuring the file object exists (line 57). This violates the requirement to reliably parse multipart form fields and the uploaded file. Consider normalizing once, e.g. const uploadedFile = files.file; const uploadedFilePath = uploadedFile && (uploadedFile.filepath || uploadedFile.path); const originalNameSafe = uploadedFile?.originalFilename || 'file'; and then validate uploadedFilePath/originalNameSafe. This will avoid unsafe indexing and satisfy the form parsing checklist.
src/createServer.js
Outdated
| switch (compressionType) { | ||
| case 'gzip': | ||
| compressStream = zlib.createGzip(); | ||
| break; | ||
| case 'deflate': | ||
| compressStream = zlib.createDeflate(); | ||
| break; | ||
| case 'br': | ||
| compressStream = zlib.createBrotliCompress(); | ||
| break; | ||
| default: | ||
| compressStream = zlib.createGzip(); | ||
| break; |
There was a problem hiding this comment.
The default branch in the compression switch silently falls back to zlib.createGzip() (lines 77–79). That can hide unexpected values and contradicts the requirement to return 400 for unsupported compression types. Instead remove the permissive default (or make it throw/return 400). A clearer pattern is to use a map from compressionType to both the zlib factory and the required extension, and reject unknown keys with a 400 before attempting compression. This also helps produce the correct output filename extension.
| res.writeHead(200, { | ||
| 'Content-Type': 'application/octet-stream', | ||
| 'Content-Disposition': `attachment; filename=${originalNameSafe}.${compressionType}`, |
There was a problem hiding this comment.
Incorrect output filename extension in Content-Disposition (line 96). You currently use filename=${originalNameSafe}.${compressionType}, e.g. file.txt.gzip. The task requires explicit mapping to extensions: gzip -> .gz, deflate -> .dfl, br -> .br. Add a mapping and use it when building the filename, e.g. const extMap = { gzip: '.gz', deflate: '.dfl', br: '.br' }; const outName = originalNameSafe + extMap[compressionType]; and wrap filename in quotes: attachment; filename="${outName}". This ensures the client receives the correctly named file.
| const readStream = fs.createReadStream(uploadedFilePath); | ||
|
|
||
| readStream.on('error', () => { | ||
| res.statusCode = 500; | ||
| res.end('Error reading file'); | ||
| }); | ||
|
|
||
| compressStream.on('error', () => { | ||
| res.statusCode = 500; | ||
| res.end('Error compressing file'); | ||
| }); | ||
|
|
||
| res.writeHead(200, { | ||
| 'Content-Type': 'application/octet-stream', | ||
| 'Content-Disposition': `attachment; filename=${originalNameSafe}.${compressionType}`, | ||
| }); | ||
|
|
||
| readStream.pipe(compressStream).pipe(res); |
There was a problem hiding this comment.
Fragile stream handling: currently you attach separate error handlers to readStream and compressStream and call readStream.pipe(compressStream).pipe(res) (lines 82–99). This can lead to a 200 response already sent even if the pipeline later fails. Use stream.pipeline(readStream, compressStream, res, cb) from the stream module to centralize error handling. Example approach:
- Build
readStreamandcompressStream. - Set response headers (Content-Type and Content-Disposition) before piping.
- Call
pipeline(readStream, compressStream, res, (err) => { if (err) { if (!res.headersSent) { res.writeHead(500); res.end('Error compressing file'); } else { res.destroy(); } } });
This ensures stream errors are handled properly and the server does not incorrectly return 200 on failure, meeting the streams and error-handling requirements.
| <form action="/compress" method="POST" enctype="multipart/form-data"> | ||
| <input type="file" name="file" required /> | ||
| <select name="compressionType" required> | ||
| <option value="gzip">gzip</option> | ||
| <option value="deflate">deflate</option> | ||
| <option value="br">br</option> | ||
| </select> | ||
| <button type="submit">Compress</button> | ||
| </form> |
There was a problem hiding this comment.
Positive: the HTML form at / contains a file input and a select named compressionType with the exact options gzip, deflate, br, and submits via POST to /compress (lines 14–22). That satisfies the front-end checklist items for the form. Keep this as-is.
No description provided.