From c4f35a52a7dc710cb8e6d86cdbb399a24c35e55f Mon Sep 17 00:00:00 2001 From: Kyrylo Golikov Date: Fri, 2 Jan 2026 20:55:23 +0100 Subject: [PATCH 1/4] solution --- src/createServer.js | 133 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index 1cf1dda..7a9e786 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,8 +1,137 @@ 'use strict'; +const http = require('http'); +const fs = require('fs/promises'); +const path = require('path'); + +function renderForm() { + return ` + + + + + + Expense form + + +

Add expense

+ +
+ +

+ + +

+ + +

+ + +
+ + + `; +} + +function readBody(req) { + return new Promise((resolve, reject) => { + let body = ''; + + req.on('data', (chunk) => { + body += chunk.toString(); + + if (body.length > 1e6) { + req.destroy(); + reject(new Error('Body too large')); + } + }); + + req.on('end', () => resolve(body)); + req.on('error', reject); + }); +} + +function parseExpense(req, body) { + const contentType = (req.headers['content-type'] || '').toLowerCase(); + + if (contentType.includes('application/json')) { + const data = JSON.parse(body || '{}'); + + return { + date: data.date ?? '', + title: data.title ?? '', + amount: data.amount ?? '', + }; + } + + const params = new URLSearchParams(body); + + return { + date: params.get('date') || '', + title: params.get('title') || '', + amount: params.get('amount') || '', + }; +} + +function isValidExpense(expense) { + return Boolean(expense.date && expense.title && expense.amount); +} + function createServer() { - /* Write your code here */ - // Return instance of http.Server class + return http.createServer(async (req, res) => { + if (req.method === 'GET' && req.url === '/') { + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(renderForm()); + + return; + } + + const isExpensePost = + req.method === 'POST' && + (req.url === '/add-expense' || req.url === '/submit-expense'); + + if (isExpensePost) { + try { + const body = await readBody(req); + const expense = parseExpense(req, body); + + if (!isValidExpense(expense)) { + res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Bad request: missing required fields'); + + return; + } + + const dbDir = path.join(__dirname, '..', 'db'); + const filePath = path.join(dbDir, 'expense.json'); + + await fs.mkdir(dbDir, { recursive: true }); + await fs.writeFile(filePath, JSON.stringify(expense, null, 2), 'utf-8'); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(expense)); + + return; + } catch (err) { + res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Server error'); + + return; + } + } + + res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Not found'); + }); } module.exports = { From 7de877d1f057196f06f287937cc3e29e9ed10927 Mon Sep 17 00:00:00 2001 From: Kyrylo Golikov Date: Fri, 2 Jan 2026 21:16:54 +0100 Subject: [PATCH 2/4] solution --- src/createServer.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index 7a9e786..d1191cc 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -117,8 +117,22 @@ function createServer() { await fs.mkdir(dbDir, { recursive: true }); await fs.writeFile(filePath, JSON.stringify(expense, null, 2), 'utf-8'); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(expense)); + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + + res.end(` + + + + + Saved expense + + +

Saved expense

+
${JSON.stringify(expense, null, 2)}
+ Back + + + `); return; } catch (err) { From e9bc78268ff448ef0c3203917a51b519fdceeed2 Mon Sep 17 00:00:00 2001 From: Kyrylo Golikov Date: Fri, 2 Jan 2026 21:29:58 +0100 Subject: [PATCH 3/4] solution --- src/createServer.js | 72 +++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 52 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index d1191cc..15b7f81 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -10,31 +10,14 @@ function renderForm() { - Expense form

Add expense

-
- -

- - -

- - -

- + + +
@@ -61,15 +44,15 @@ function readBody(req) { } function parseExpense(req, body) { - const contentType = (req.headers['content-type'] || '').toLowerCase(); + const type = req.headers['content-type'] || ''; - if (contentType.includes('application/json')) { + if (type.includes('application/json')) { const data = JSON.parse(body || '{}'); return { - date: data.date ?? '', - title: data.title ?? '', - amount: data.amount ?? '', + date: data.date || '', + title: data.title || '', + amount: data.amount || '', }; } @@ -83,7 +66,7 @@ function parseExpense(req, body) { } function isValidExpense(expense) { - return Boolean(expense.date && expense.title && expense.amount); + return expense.date && expense.title && expense.amount; } function createServer() { @@ -95,18 +78,17 @@ function createServer() { return; } - const isExpensePost = + if ( req.method === 'POST' && - (req.url === '/add-expense' || req.url === '/submit-expense'); - - if (isExpensePost) { + (req.url === '/add-expense' || req.url === '/submit-expense') + ) { try { const body = await readBody(req); const expense = parseExpense(req, body); if (!isValidExpense(expense)) { - res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' }); - res.end('Bad request: missing required fields'); + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Bad request'); return; } @@ -117,33 +99,19 @@ function createServer() { await fs.mkdir(dbDir, { recursive: true }); await fs.writeFile(filePath, JSON.stringify(expense, null, 2), 'utf-8'); - res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); - - res.end(` - - - - - Saved expense - - -

Saved expense

-
${JSON.stringify(expense, null, 2)}
- Back - - - `); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(expense)); return; - } catch (err) { - res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' }); - res.end('Server error'); + } catch { + res.writeHead(500); + res.end(); return; } } - res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not found'); }); } From 2c505ae229cfef99488830a8b0e7cb8722737be8 Mon Sep 17 00:00:00 2001 From: Kyrylo Golikov Date: Fri, 2 Jan 2026 21:48:52 +0100 Subject: [PATCH 4/4] solution --- src/createServer.js | 27 +++++---------------------- src/index.html | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 src/index.html diff --git a/src/createServer.js b/src/createServer.js index 15b7f81..6324144 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -4,27 +4,6 @@ const http = require('http'); const fs = require('fs/promises'); const path = require('path'); -function renderForm() { - return ` - - - - - Expense form - - -

Add expense

-
- - - - -
- - - `; -} - function readBody(req) { return new Promise((resolve, reject) => { let body = ''; @@ -72,8 +51,12 @@ function isValidExpense(expense) { function createServer() { return http.createServer(async (req, res) => { if (req.method === 'GET' && req.url === '/') { + const htmlPath = path.join(__dirname, '..', 'index.html'); + + const html = await fs.readFile(htmlPath, 'utf-8'); + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); - res.end(renderForm()); + res.end(html); return; } diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..547518e --- /dev/null +++ b/src/index.html @@ -0,0 +1,17 @@ + + + + + Expense form + + +

Add expense

+ +
+ + + + +
+ +