diff --git a/src/app.js b/src/app.js index 6e754b2..f0c515a 100644 --- a/src/app.js +++ b/src/app.js @@ -3,7 +3,4 @@ const { createServer } = require('./createServer'); -createServer().listen(5701, () => { - console.log(`Server is running on http://localhost:${5701} 🚀`); - console.log('Available at http://localhost:5701'); -}); +createServer().listen(5701); diff --git a/src/createServer.js b/src/createServer.js index 1cf1dda..af88f5d 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,8 +1,203 @@ 'use strict'; +/* eslint-disable no-console */ + +const { createReadStream, createWriteStream, mkdirSync } = require('fs'); +const http = require('http'); +const path = require('path'); + function createServer() { - /* Write your code here */ - // Return instance of http.Server class + const server = new http.Server(); + + function getHandler(req, res) { + if (req.method !== 'GET') { + return; + } + + if (req.url !== '/' && req.url !== '') { + res.statusCode = 404; + res.setHeader('Content-Type', 'text/plain'); + res.end('Not Found'); + + return; + } + + res.statusCode = 200; + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + + const form = createReadStream(path.join(__dirname, 'index.html')); + + form.on('error', (err) => { + console.error('Read stream error:', err); + + if (!res.headersSent) { + res.statusCode = 500; + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + + res.end(); + } else { + res.destroy(); + } + }); + + res.on('close', () => { + form.destroy(); + }); + + form.pipe(res); + } + + function postHandler(req, res) { + if (req.method !== 'POST') { + return; + } + + if (req.url !== '/add-expense' && req.url !== '/submit-expense') { + res.statusCode = 404; + res.setHeader('Content-Type', 'text/plain'); + res.end('Not Found'); + + return; + } + + const file = path.resolve('db', 'expense.json'); + + let raw = ''; + + req.setEncoding('utf8'); + + req.on('data', (chunk) => { + raw += chunk; + }); + + req.on('end', () => { + const contentType = req.headers['content-type'] || ''; + let payload; + + try { + if (contentType.includes('application/json')) { + payload = raw ? JSON.parse(raw) : {}; + } else if (contentType.includes('application/x-www-form-urlencoded')) { + const params = new URLSearchParams(raw); + + payload = Object.fromEntries(params.entries()); + } else { + payload = {}; + } + } catch (parseErr) { + res.statusCode = 400; + res.setHeader('Content-Type', 'text/plain'); + res.end('Bad Request'); + + return; + } + + const expense = { + date: payload.date, + title: payload.title, + amount: payload.amount, + }; + + // Validation + const parsedAmount = + typeof expense.amount === 'string' + ? parseFloat(expense.amount) + : Number(expense.amount); + const parsedDate = new Date(expense.date); + + const hasAll = Boolean(expense.date && expense.title && expense.amount); + const amountValid = + !Number.isNaN(parsedAmount) && Number.isFinite(parsedAmount); + const dateValid = !Number.isNaN(parsedDate.getTime()); + + if (!hasAll || !amountValid || !dateValid) { + if (contentType.includes('application/json')) { + res.statusCode = 400; + res.setHeader('Content-Type', 'text/plain'); + res.end('Invalid payload'); + } else { + res.statusCode = 400; + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + + res.end( + `
Please provide a valid date, title, and numeric amount.
`, + ); + } + + return; + } + + const normalizedExpense = { + date: expense.date, + title: expense.title, + amount: String(expense.amount), + }; + + try { + mkdirSync(path.dirname(file), { recursive: true }); + } catch (mkdirErr) { + console.error('Directory create error:', mkdirErr); + res.statusCode = 500; + res.setHeader('Content-Type', 'text/plain'); + res.end('Internal Server Error'); + + return; + } + + const writer = createWriteStream(file); + + writer.on('error', (error) => { + console.error('Write stream error:', error); + res.statusCode = 500; + res.setHeader('Content-Type', 'text/plain'); + res.end('Internal Server Error'); + }); + + const pretty = JSON.stringify(normalizedExpense, null, 2); + + writer.end(pretty, () => { + res.statusCode = 200; + + if (contentType.includes('application/json')) { + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify(normalizedExpense)); + } else { + const escaped = pretty + .replace(/&/g, '&') + .replace(//g, '>'); + + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + + res.end( + `${escaped}`,
+ );
+ }
+ });
+ });
+
+ req.on('error', (err) => {
+ console.error('Request error:', err);
+ res.statusCode = 400;
+ res.setHeader('Content-Type', 'text/plain');
+ res.end('Bad Request');
+ });
+ }
+
+ server.on('request', getHandler);
+ server.on('request', postHandler);
+
+ server.on('request', (req, res) => {
+ if (req.method !== 'GET' && req.method !== 'POST') {
+ res.statusCode = 405;
+ res.setHeader('Content-Type', 'text/plain');
+ res.end('Method Not Allowed');
+ }
+ });
+
+ server.on('error', () => {});
+
+ return server;
}
module.exports = {
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..f5e408f
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+