Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/test.yml-template
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Test

on:
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
Comment on lines +17 to +19
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"devDependencies": {
"@faker-js/faker": "^8.4.1",
"@mate-academy/eslint-config": "latest",
"@mate-academy/scripts": "^1.8.6",
"@mate-academy/scripts": "^2.1.3",
"axios": "^1.7.2",
"eslint": "^8.57.0",
"eslint-plugin-jest": "^28.6.0",
Expand Down
131 changes: 129 additions & 2 deletions src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,135 @@
'use strict';

const http = require('http');
const path = require('path');
const fs = require('fs');
const zlib = require('node:zlib');
const { IncomingForm } = require('formidable');

const compressionTypes = {
gzip: {
createStream: zlib.createGzip,
extension: 'gz',
},
deflate: {
createStream: zlib.createDeflate,
extension: 'dfl',
},
br: {
createStream: zlib.createBrotliCompress,
extension: 'br',
},
};

function sendHtml(res) {
const filePath = path.join(__dirname, 'index.html');

fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Internal Server Error');

return;
}

res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(data);
});
}

function sendBadRequest(response) {
response.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('Bad Request');
}

function sendNotFound(response) {
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('Not Found');
}

function createServer() {
/* Write your code here */
// Return instance of http.Server class
return http.createServer((request, response) => {
const { method, url } = request;

if (method === 'GET' && url === '/') {
sendHtml(response);

return;
}

if (method === 'GET' && url === '/compress') {
sendBadRequest(response);

return;
}

if (method === 'POST' && url === '/compress') {
const form = new IncomingForm({ multiples: false });

form.parse(request, async (error, fields, files) => {
if (error) {
sendBadRequest(response);

return;
}

const compressionType = Array.isArray(fields.compressionType)
? fields.compressionType[0]
: fields.compressionType;
Comment on lines +76 to +78
const file = Array.isArray(files.file) ? files.file[0] : files.file;

if (!file || !compressionType) {
sendBadRequest(response);

return;
}

if (!compressionTypes[compressionType]) {
sendBadRequest(response);

return;
}

const filePath = file.filepath || file.file?.path || file.path;
const filename =
file.originalFilename ||
file.name ||
path.basename(filePath || 'unknown');

if (!filePath || !filename) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The select field must have name="compressionType" to match the task requirement and server expectations. Currently it has name="compressSelector" which will cause the compression type not to be received correctly by the server.

sendBadRequest(response);

return;
}

const { createStream, extension } = compressionTypes[compressionType];
const readStream = fs.createReadStream(filePath);
const compressStream = createStream();
Comment on lines +105 to +107

const handleStreamError = () => {
if (!response.headersSent) {
sendBadRequest(response);
} else {
response.destroy();
}
};

readStream.on('error', handleStreamError);
compressStream.on('error', handleStreamError);

response.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename=${filename}.${extension}`,
});
Comment on lines +120 to +123

readStream.pipe(compressStream).pipe(response);
});

return;
}

sendNotFound(response);
});
}

module.exports = {
Expand Down
111 changes: 111 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}

.container {
width: 80%;
margin: 0 auto;
padding: 0;
display: flex;
height: 100vh;
flex-direction: column;
align-items: center;
justify-content: center;
}

.mainTitle {
text-align: center;
color: #333;
}

.mainDescription {
text-align: center;
color: #666;
margin-bottom: 40px;
}

.form {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}

.label {
display: flex;
flex-direction: column;
gap: 10px;
font-size: 18px;
color: #333;
}

.fileInput {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
}

.selector {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
}

.submitBtn {
padding: 10px 20px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: #fff;
font-size: 16px;
cursor: pointer;
}
</style>
Comment on lines +6 to +76
<title>The Compressor</title>
</head>
<body>
<div class="container">
<h1 class="mainTitle">Welcome to The Compressor</h1>

<p class="mainDescription">
This is a simple web application that allows you to upload a file and compress it
using one of three compression algorithms: gzip, deflate, or brotli (br).
To use the application, simply select a file from your computer, choose the desired compression
type from the dropdown menu, and click the "Compress" button. The compressed file will be generated
and made available for download. Enjoy compressing your files with ease!
</p>

<form action="/compress" method="POST" enctype="multipart/form-data" class="form">
<label class="label">
<span>Choose a file to compress:</span>
<input class="fileInput" type="file" id="file" name="file" required>
</label>

<label class="label">
<span>Choose a compression type:</span>
<select class="selector" id="compressionType" name="compressionType" required>
<option value="gzip">gzip</option>
<option value="deflate">deflate</option>
<option value="br">brotli</option>
</select>
</label>

<button type="submit" class="submitBtn">Compress</button>

</form>
</div>
</body>
</html>
69 changes: 69 additions & 0 deletions src/styles/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}

.container {
width: 80%;
margin: 0 auto;
padding: 0;
display: flex;
height: 100vh;
flex-direction: column;
align-items: center;
justify-content: center;
}

.mainTitle {
text-align: center;
color: #333;
}

.mainDescription {
text-align: center;
color: #666;
margin-bottom: 40px;
}

.form {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}

.label {
display: flex;
flex-direction: column;
gap: 10px;
font-size: 18px;
color: #333;
}

.fileInput {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
}

.selector {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
cursor: pointer;
}

.submitBtn {
padding: 10px 20px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: #fff;
font-size: 16px;
cursor: pointer;
}
Loading