From 4731e38cef388d48676ff4f1b3e5bc11243c9599 Mon Sep 17 00:00:00 2001 From: Stas Date: Mon, 6 Oct 2025 21:00:12 +0200 Subject: [PATCH 1/3] solution --- public/index.html | 23 +++++++++ src/createServer.js | 113 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 public/index.html diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..76917c2 --- /dev/null +++ b/public/index.html @@ -0,0 +1,23 @@ + + + + + + Form data + + +
+
+ Form data + + + + + + +
+ + +
+ + diff --git a/src/createServer.js b/src/createServer.js index 1cf1dda..ca8e7d7 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,8 +1,117 @@ 'use strict'; +const http = require('http'); +const url = require('url'); +const path = require('path'); +const fs = require('fs'); + function createServer() { - /* Write your code here */ - // Return instance of http.Server class + const server = http.createServer(async (req, res) => { + function cheackValidation(obj) { + if (typeof obj.date !== 'string' || !obj.date.trim()) { + throw new Error(`Field of date not correct: ${obj.date}`); + } + + if (typeof obj.title !== 'string' || !obj.title.trim()) { + throw new Error(`Field of title not correct: ${obj.title}`); + } + + if (!Number.isFinite(obj.amount) || obj.amount <= 0) { + throw new Error(`Field of amount not correct: ${obj.amount}`); + } + + const date = new Date(obj.date); + + if (isNaN(date.getTime()) || !date.toISOString().startsWith(obj.date)) { + throw new Error(`Field of date not correct: ${obj.date}`); + } + + return true; + } + + if (req.method === 'POST') { + let rawBody = ''; + + req.on('data', (chunk) => { + rawBody = rawBody + chunk; + }); + + req.on('end', () => { + const body = new URLSearchParams(rawBody); + const data = Object.fromEntries(body.entries()); + + for (const key in data) { + if (key === 'amount') { + data[key] = +data[key]; + } + } + + try { + cheackValidation(data); + } catch (err) { + res.writeHead(400, { 'Content-Type': 'text/html' }); + + res.end( + `
${err.message}
${JSON.stringify(
+              {
+                error: 'Missing required fields',
+                invalid: data,
+              },
+              null,
+              2,
+            )}
`, + ); + + return; + } + + const folder = path.join(__dirname, '..', 'db'); + const filePath = path.join(folder, 'expense.json'); + + if (!fs.existsSync(folder)) { + fs.mkdirSync(folder, { recursive: true }); + } + + fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); + + if (fs.existsSync(filePath)) { + } + + res.writeHead(200, { 'Content-Type': 'text/html' }); + + res.end(` +
+    ${JSON.stringify(data, null, 2)}
+  
+`); + }); + } + + if (req.method === 'GET') { + const normalizedUrl = new url.URL( + req.url || '', + `http://${req.headers.host}`, + ); + const origin = + path.basename(normalizedUrl.pathname.slice(1)) || 'index.html'; + + const originPathName = path.join(__dirname, '..', 'public', origin); + + const fsStream = fs.createReadStream(originPathName); + + fsStream.on('error', (err) => { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end(`Error reading file: ${String(err)}`); + }); + + fsStream.on('open', () => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + fsStream.pipe(res); + }); + } + }); + + return server; } module.exports = { From d115e807412b7a3231cbb4248c06fa85195a1473 Mon Sep 17 00:00:00 2001 From: Stas Date: Mon, 6 Oct 2025 22:05:25 +0200 Subject: [PATCH 2/3] solution fix critical blockers --- src/createServer.js | 50 ++++++++++++++++++++++++--------------------- src/index.html | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 23 deletions(-) create mode 100644 src/index.html diff --git a/src/createServer.js b/src/createServer.js index ca8e7d7..176e429 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -7,7 +7,7 @@ const fs = require('fs'); function createServer() { const server = http.createServer(async (req, res) => { - function cheackValidation(obj) { + function checkValidation(obj) { if (typeof obj.date !== 'string' || !obj.date.trim()) { throw new Error(`Field of date not correct: ${obj.date}`); } @@ -16,13 +16,15 @@ function createServer() { throw new Error(`Field of title not correct: ${obj.title}`); } - if (!Number.isFinite(obj.amount) || obj.amount <= 0) { - throw new Error(`Field of amount not correct: ${obj.amount}`); + const number = +obj.amount; + + if (!Number.isFinite(number) || number <= 0) { + throw new Error(`Field of amount not correct: ${number}`); } const date = new Date(obj.date); - if (isNaN(date.getTime()) || !date.toISOString().startsWith(obj.date)) { + if (isNaN(date.getTime())) { throw new Error(`Field of date not correct: ${obj.date}`); } @@ -32,29 +34,38 @@ function createServer() { if (req.method === 'POST') { let rawBody = ''; + const contentType = req.headers['content-type']; + req.on('data', (chunk) => { rawBody = rawBody + chunk; }); req.on('end', () => { - const body = new URLSearchParams(rawBody); - const data = Object.fromEntries(body.entries()); + let data = null; + + if (contentType === 'application/json') { + data = JSON.parse(rawBody); + } - for (const key in data) { - if (key === 'amount') { - data[key] = +data[key]; - } + if (contentType === 'application/x-www-form-urlencoded') { + data = Object.fromEntries(new URLSearchParams(rawBody)); + } + + if (data === null) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Invalid JSON'); + + return; } try { - cheackValidation(data); + checkValidation(data); } catch (err) { res.writeHead(400, { 'Content-Type': 'text/html' }); res.end( `
${err.message}
${JSON.stringify(
               {
-                error: 'Missing required fields',
                 invalid: data,
               },
               null,
@@ -74,28 +85,21 @@ function createServer() {
 
         fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
 
-        if (fs.existsSync(filePath)) {
-        }
-
-        res.writeHead(200, { 'Content-Type': 'text/html' });
+        res.writeHead(200, { 'Content-Type': 'application/json' });
 
-        res.end(`
-  
-    ${JSON.stringify(data, null, 2)}
-  
-`); + res.end(JSON.stringify(data, null, 2)); }); } if (req.method === 'GET') { const normalizedUrl = new url.URL( req.url || '', - `http://${req.headers.host}`, + `http://${req.headers.host}` || 'http://localhost:5701', ); const origin = path.basename(normalizedUrl.pathname.slice(1)) || 'index.html'; - const originPathName = path.join(__dirname, '..', 'public', origin); + const originPathName = path.join(__dirname, origin); const fsStream = fs.createReadStream(originPathName); diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..6fcd423 --- /dev/null +++ b/src/index.html @@ -0,0 +1,37 @@ + + + + + + Form data + + +
+ + + + + + + + +
+ + From bf7355a88dffc600f0f6705e4aa2392681d59fb6 Mon Sep 17 00:00:00 2001 From: Stas Date: Tue, 7 Oct 2025 19:04:20 +0200 Subject: [PATCH 3/3] solution add recomendations --- package.json | 5 ++++- public/index.html | 23 ----------------------- src/createServer.js | 28 ++++++++++++++++++---------- 3 files changed, 22 insertions(+), 34 deletions(-) delete mode 100644 public/index.html diff --git a/package.json b/package.json index 8a92721..761c111 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "license": "GPL-3.0", "devDependencies": { "@mate-academy/eslint-config": "latest", - "@mate-academy/scripts": "^1.8.6", + "@mate-academy/scripts": "^2.1.2", "axios": "^1.7.2", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", @@ -27,5 +27,8 @@ }, "mateAcademy": { "projectType": "javascript" + }, + "dependencies": { + "mime-types": "^3.0.1" } } diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 76917c2..0000000 --- a/public/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - Form data - - -
-
- Form data - - - - - - -
- - -
- - diff --git a/src/createServer.js b/src/createServer.js index 176e429..8a006cf 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -4,6 +4,7 @@ const http = require('http'); const url = require('url'); const path = require('path'); const fs = require('fs'); +const mime = require('mime-types'); function createServer() { const server = http.createServer(async (req, res) => { @@ -43,17 +44,21 @@ function createServer() { req.on('end', () => { let data = null; - if (contentType === 'application/json') { + const normalizedContentType = contentType.includes(';') + ? contentType.split(';')[0] + : contentType; + + if (normalizedContentType === 'application/json') { data = JSON.parse(rawBody); } - if (contentType === 'application/x-www-form-urlencoded') { + if (normalizedContentType === 'application/x-www-form-urlencoded') { data = Object.fromEntries(new URLSearchParams(rawBody)); } if (data === null) { res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('Invalid JSON'); + res.end(`Unsupported Content-Type: ${normalizedContentType}`); return; } @@ -85,26 +90,29 @@ function createServer() { fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); - res.writeHead(200, { 'Content-Type': 'application/json' }); + res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(JSON.stringify(data, null, 2)); + res.end(`
${JSON.stringify(data, null, 2)}
`); }); } if (req.method === 'GET') { - const normalizedUrl = new url.URL( - req.url || '', - `http://${req.headers.host}` || 'http://localhost:5701', - ); + const base = req.headers.host + ? `http://${req.headers.host}` + : 'http://localhost:5701'; + + const normalizedUrl = new url.URL(req.url || '', base); const origin = path.basename(normalizedUrl.pathname.slice(1)) || 'index.html'; + const mimeType = mime.lookup(origin) || 'text/plain'; + const originPathName = path.join(__dirname, origin); const fsStream = fs.createReadStream(originPathName); fsStream.on('error', (err) => { - res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.writeHead(404, { 'Content-Type': mimeType }); res.end(`Error reading file: ${String(err)}`); });