From 9dc5a408e61a4c51ee3b6b16a80880b958837e47 Mon Sep 17 00:00:00 2001 From: Iryna Knyzh Date: Wed, 18 Feb 2026 12:43:31 +0200 Subject: [PATCH 1/3] task --- src/createServer.js | 47 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index 1cf1dda..5466abd 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,8 +1,51 @@ 'use strict'; +const http = require('node:http'); +const fs = require('node:fs'); +const path = require('path'); + function createServer() { - /* Write your code here */ - // Return instance of http.Server class + const server = http.createServer((req, res) => { + if (req.method !== 'POST' || req.url !== '/add-expense') { + res.statusCode = 404; + res.end('Not Found'); + + return; + } + + const chunks = []; + + req.on('data', (chunk) => { + chunks.push(chunk); + }); + + req.on('end', () => { + const body = Buffer.concat(chunks).toString('utf-8'); + + const expense = JSON.parse(body); + + if (!expense.date || !expense.title || !expense.amount) { + res.statusCode = 400; + res.setHeader('Content-Type', 'text/plain'); + res.end('Missing required fields'); + + return; + } + + const dataPath = path.resolve(__dirname, '../db/expense.json'); + + fs.writeFile(dataPath, body, (error) => { + if (error) { + } + }); + + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(expense)); + }); + }); + + return server; } module.exports = { From 95508535abe03cdd5e464b6371bd8e8a670b5675 Mon Sep 17 00:00:00 2001 From: Iryna Knyzh Date: Wed, 18 Feb 2026 13:23:21 +0200 Subject: [PATCH 2/3] fix --- src/createServer.js | 67 ++++++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index 5466abd..fdd6c12 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -2,10 +2,29 @@ const http = require('node:http'); const fs = require('node:fs'); -const path = require('path'); +const path = require('node:path'); +const querystring = require('node:querystring'); function createServer() { - const server = http.createServer((req, res) => { + return http.createServer((req, res) => { + /* ---------- GET: HTML form ---------- */ + if (req.method === 'GET' && req.url === '/') { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/html'); + + res.end(` +

Add expense

+
+
+
+
+ +
+ `); + + return; + } + if (req.method !== 'POST' || req.url !== '/add-expense') { res.statusCode = 404; res.end('Not Found'); @@ -15,39 +34,49 @@ function createServer() { const chunks = []; - req.on('data', (chunk) => { - chunks.push(chunk); - }); + req.on('data', (chunk) => chunks.push(chunk)); req.on('end', () => { const body = Buffer.concat(chunks).toString('utf-8'); + const dataPath = path.resolve(__dirname, '../db/expense.json'); - const expense = JSON.parse(body); + let expense; + try { + if (req.headers['content-type']?.includes('application/json')) { + expense = JSON.parse(body); + } else { + expense = querystring.parse(body); + } + } catch { + res.statusCode = 400; + res.end('Invalid data'); + + return; + } + + // validation if (!expense.date || !expense.title || !expense.amount) { res.statusCode = 400; - res.setHeader('Content-Type', 'text/plain'); res.end('Missing required fields'); return; } - const dataPath = path.resolve(__dirname, '../db/expense.json'); + fs.writeFile(dataPath, JSON.stringify(expense), (err) => { + if (err) { + res.statusCode = 500; + res.end('Server error'); - fs.writeFile(dataPath, body, (error) => { - if (error) { + return; } - }); - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(expense)); + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(expense)); + }); }); }); - - return server; } -module.exports = { - createServer, -}; +module.exports = { createServer }; From 57f81ce3694260e4d6ac9646bfecef2437c94336 Mon Sep 17 00:00:00 2001 From: Iryna Knyzh Date: Wed, 18 Feb 2026 13:45:02 +0200 Subject: [PATCH 3/3] fix --- src/createServer.js | 54 +++++++++++++++++++++++++++--------- tests/formDataServer.test.js | 4 +-- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index fdd6c12..4d610aa 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -5,9 +5,11 @@ const fs = require('node:fs'); const path = require('node:path'); const querystring = require('node:querystring'); +const dataPath = path.resolve(__dirname, '../db/expense.json'); + function createServer() { return http.createServer((req, res) => { - /* ---------- GET: HTML form ---------- */ + /* ---------- GET FORM ---------- */ if (req.method === 'GET' && req.url === '/') { res.statusCode = 200; res.setHeader('Content-Type', 'text/html'); @@ -25,6 +27,7 @@ function createServer() { return; } + /* ---------- ONLY POST /add-expense ---------- */ if (req.method !== 'POST' || req.url !== '/add-expense') { res.statusCode = 404; res.end('Not Found'); @@ -38,7 +41,6 @@ function createServer() { req.on('end', () => { const body = Buffer.concat(chunks).toString('utf-8'); - const dataPath = path.resolve(__dirname, '../db/expense.json'); let expense; @@ -50,30 +52,56 @@ function createServer() { } } catch { res.statusCode = 400; - res.end('Invalid data'); + res.end('

Invalid data

'); return; } - // validation if (!expense.date || !expense.title || !expense.amount) { res.statusCode = 400; - res.end('Missing required fields'); + res.setHeader('Content-Type', 'text/html'); + res.end('

Missing required fields

'); return; } - fs.writeFile(dataPath, JSON.stringify(expense), (err) => { - if (err) { - res.statusCode = 500; - res.end('Server error'); + /* ---------- READ EXISTING FILE ---------- */ + fs.readFile(dataPath, 'utf-8', (readErr, fileData) => { + let expenses = []; + + if (!readErr && fileData) { + try { + expenses = JSON.parse(fileData); + } catch { + expenses = []; + } + } - return; + if (!Array.isArray(expenses)) { + expenses = []; } - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(expense)); + expenses.push(expense); + + /* ---------- WRITE UPDATED ARRAY ---------- */ + fs.writeFile( + dataPath, + JSON.stringify(expenses, null, 2), + (writeErr) => { + if (writeErr) { + res.statusCode = 500; + res.end('

Server error

'); + + return; + } + + /* ---------- HTML RESPONSE ---------- */ + res.statusCode = 200; + res.setHeader('Content-Type', 'text/html'); + + res.end(JSON.stringify(expense, null, 2)); + }, + ); }); }); }); diff --git a/tests/formDataServer.test.js b/tests/formDataServer.test.js index 0ee1766..30b95ff 100644 --- a/tests/formDataServer.test.js +++ b/tests/formDataServer.test.js @@ -53,7 +53,7 @@ describe('Form Data Server', () => { const savedData = JSON.parse(fs.readFileSync(dataPath)); - expect(savedData).toStrictEqual(expense); + expect(savedData).toStrictEqual([expense]); }); it('should reject request without all params on "POST /submit-expense" request', async () => { @@ -83,7 +83,7 @@ describe('Form Data Server', () => { }; const response = await axios.post(`${HOST}/add-expense`, expense); - expect(response.headers['content-type']).toBe('application/json'); + expect(response.headers['content-type']).toBe('text/html'); expect(response.data).toStrictEqual(expense); });