From 4cf59e23aa729121a5bd68b42993d4060974d30c Mon Sep 17 00:00:00 2001 From: Ivan Date: Mon, 9 Mar 2026 13:36:28 +0100 Subject: [PATCH 1/2] solution --- .github/workflows/test.yml-template | 23 +++++ db/expense.json | 6 +- package-lock.json | 9 +- package.json | 2 +- public/index.html | 78 +++++++++++++++++ public/styles.css | 89 ++++++++++++++++++++ src/createServer.js | 125 +++++++++++++++++++++++++++- 7 files changed, 322 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/test.yml-template create mode 100644 public/index.html create mode 100644 public/styles.css diff --git a/.github/workflows/test.yml-template b/.github/workflows/test.yml-template new file mode 100644 index 0000000..bb13dfc --- /dev/null +++ b/.github/workflows/test.yml-template @@ -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 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm test diff --git a/db/expense.json b/db/expense.json index 1bc75a6..3a1656d 100644 --- a/db/expense.json +++ b/db/expense.json @@ -1,5 +1,5 @@ { - "date": "2024-01-25", - "title": "Test Expense", - "amount": "100" + "date": "2026-03-21", + "title": "eeeee", + "amount": "3" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 28a4d31..4fbbc4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "GPL-3.0", "devDependencies": { "@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", @@ -1468,10 +1468,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.6.tgz", - "integrity": "sha512-b4om/whj4G9emyi84ORE3FRZzCRwRIesr8tJHXa8EvJdOaAPDpzcJ8A0sFfMsWH9NUOVmOwkBtOXDu5eZZ00Ig==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", diff --git a/package.json b/package.json index 8a92721..cd53c9e 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.3", "axios": "^1.7.2", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..d7ad771 --- /dev/null +++ b/public/index.html @@ -0,0 +1,78 @@ + + + + + + Expense Form + + + +
+

Add Expense

+ + + + + + + + + + + +
+ +
+ + + + diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..dfff418 --- /dev/null +++ b/public/styles.css @@ -0,0 +1,89 @@ +body { + font-family: Arial, sans-serif; + max-width: 600px; + margin: 50px auto; + padding: 20px; + background-color: #f5f5f5; +} + +form { + background: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + margin-bottom: 20px; +} + +h1 { + color: #333; + margin-bottom: 30px; +} + +label { + display: block; + margin-bottom: 5px; + color: #555; + font-weight: bold; +} + +input { + width: 100%; + padding: 10px; + margin-bottom: 20px; + border: 1px solid #ddd; + border-radius: 4px; + box-sizing: border-box; + font-size: 14px; +} + +button { + background-color: #4CAF50; + color: white; + padding: 12px 30px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + width: 100%; +} + +button:hover { + background-color: #45a049; +} + +#result { + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + display: none; +} + +#result:not(:empty) { + display: block; +} + +.success { + color: #4CAF50; +} + +.success h2 { + margin-top: 0; +} + +.error { + color: #f44336; +} + +.error h2 { + margin-top: 0; +} + +pre { + background-color: #f5f5f5; + padding: 15px; + border-radius: 4px; + overflow-x: auto; + color: #333; + font-size: 14px; +} diff --git a/src/createServer.js b/src/createServer.js index 1cf1dda..79ec05e 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,8 +1,129 @@ 'use strict'; +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +/** + * Helper function to send HTTP responses + * @param {http.ServerResponse} res - Response object + * @param {number} statusCode - HTTP status code (200, 404, 500, etc.) + * @param {string} contentType - Content-Type header value + * @param {string|object} data - Data to send (will be stringified if object) + */ +function sendResponse(res, statusCode, contentType, data) { + res.writeHead(statusCode, { 'Content-Type': contentType }); + + // If data is an object and content type is JSON, stringify it + if (typeof data === 'object' && contentType === 'application/json') { + res.end(JSON.stringify(data)); + } else { + res.end(data); + } +} + function createServer() { - /* Write your code here */ - // Return instance of http.Server class + const server = http.createServer((req, res) => { + const { method, url } = req; + + // Serve index.html + if (method === 'GET' && url === '/') { + const filePath = path.resolve(__dirname, '../public/index.html'); + + fs.readFile(filePath, 'utf8', (err, data) => { + if (err) { + sendResponse(res, 500, 'text/plain', 'Error loading page'); + + return; + } + + sendResponse(res, 200, 'text/html', data); + }); + + return; + } + + // Serve styles.css + if (method === 'GET' && url === '/styles.css') { + const filePath = path.resolve(__dirname, '../public/styles.css'); + + fs.readFile(filePath, 'utf8', (err, data) => { + if (err) { + sendResponse(res, 500, 'text/plain', 'Error loading stylesheet'); + + return; + } + + sendResponse(res, 200, 'text/css', data); + }); + + return; + } + + // Handle POST request - save expense data + if (method === 'POST' && url === '/add-expense') { + let body = ''; + + req.on('data', (chunk) => { + body += chunk.toString(); + }); + + req.on('end', () => { + let expense; + + try { + // Parse JSON body + expense = JSON.parse(body); + } catch (error) { + sendResponse(res, 400, 'application/json', { error: 'invalid JSON' }); + + return; + } + + // Validate required fields + const requiredFields = ['date', 'title', 'amount']; + const missingFields = requiredFields.filter((field) => !expense[field]); + + if (missingFields.length > 0) { + sendResponse( + res, + 400, + 'text/plain', + `Missing required fields: ${missingFields.join(', ')}`, + ); + + return; + } + + // Save to file + const dataPath = path.resolve(__dirname, '../db/expense.json'); + const dirPath = path.dirname(dataPath); + + // Enshore directory exists + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + + try { + fs.writeFileSync(dataPath, JSON.stringify(expense, null, 2)); + + // Return sucsess response with JSON + sendResponse(res, 200, 'application/json', expense); + } catch (error) { + sendResponse(res, 500, 'application/json', { + error: 'Failed to save expense', + }); + } + }); + + return; + } + + // Handle 404 for all other routes + sendResponse(res, 404, 'text/plain', 'Not found'); + }); + + return server; } module.exports = { From c2bab5428677c81c796faf502a0e3afa54a88edd Mon Sep 17 00:00:00 2001 From: Ivan Date: Mon, 9 Mar 2026 13:54:07 +0100 Subject: [PATCH 2/2] fixing response with using html --- db/expense.json | 6 +++--- public/index.html | 6 +++--- src/createServer.js | 6 ++++-- tests/formDataServer.test.js | 6 ++++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/db/expense.json b/db/expense.json index 3a1656d..d5378c6 100644 --- a/db/expense.json +++ b/db/expense.json @@ -1,5 +1,5 @@ { - "date": "2026-03-21", - "title": "eeeee", - "amount": "3" + "date": "2026-03-20", + "title": "dsff", + "amount": "5" } \ No newline at end of file diff --git a/public/index.html b/public/index.html index d7ad771..180cb3c 100644 --- a/public/index.html +++ b/public/index.html @@ -46,13 +46,13 @@

Add Expense

body: JSON.stringify(formData), }); - const data = await response.json(); + const data = await response.text(); if (response.ok) { resultDiv.innerHTML = `

Expense Saved Successfully!

-
${JSON.stringify(data, null, 2)}
+ ${data}
`; form.reset(); @@ -60,7 +60,7 @@

Expense Saved Successfully!

resultDiv.innerHTML = `

Error

-
${JSON.stringify(data, null, 2)}
+

${data}

`; } diff --git a/src/createServer.js b/src/createServer.js index 79ec05e..b5bcadc 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -107,8 +107,10 @@ function createServer() { try { fs.writeFileSync(dataPath, JSON.stringify(expense, null, 2)); - // Return sucsess response with JSON - sendResponse(res, 200, 'application/json', expense); + // Return HTML page with well-formatted JSON + const html = `
${JSON.stringify(expense, null, 2)}
`; + + sendResponse(res, 200, 'text/html', html); } catch (error) { sendResponse(res, 500, 'application/json', { error: 'Failed to save expense', diff --git a/tests/formDataServer.test.js b/tests/formDataServer.test.js index 0ee1766..f9a94d4 100644 --- a/tests/formDataServer.test.js +++ b/tests/formDataServer.test.js @@ -83,8 +83,10 @@ describe('Form Data Server', () => { }; const response = await axios.post(`${HOST}/add-expense`, expense); - expect(response.headers['content-type']).toBe('application/json'); - expect(response.data).toStrictEqual(expense); + expect(response.headers['content-type']).toBe('text/html'); + expect(response.data).toBe( + `
${JSON.stringify(expense, null, 2)}
`, + ); }); it('should return 404 for invalid url', async () => {