diff --git a/assignments/hackyourtemperature/.env b/assignments/hackyourtemperature/.env new file mode 100644 index 000000000..c0c68b1ca --- /dev/null +++ b/assignments/hackyourtemperature/.env @@ -0,0 +1 @@ +PORT=3000 \ No newline at end of file diff --git a/assignments/hackyourtemperature/.gitignore b/assignments/hackyourtemperature/.gitignore new file mode 100644 index 000000000..9a3d3c991 --- /dev/null +++ b/assignments/hackyourtemperature/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +.env +.vscode +npm-debug.log \ No newline at end of file diff --git a/assignments/hackyourtemperature/__tests__/app.test.js b/assignments/hackyourtemperature/__tests__/app.test.js new file mode 100644 index 000000000..e39456279 --- /dev/null +++ b/assignments/hackyourtemperature/__tests__/app.test.js @@ -0,0 +1,62 @@ +import app from "../src/app.js"; +import supertest from "supertest"; + +const request = supertest(app); + +describe("GET /", () => { + it("Should return text 'hello from backend to frontend'", async () => { + const response = await request.get("/"); + + expect(response.text).toBe("hello from backend to frontend!"); + expect(response.status).toBe(200); + }); +}); + +describe("POST /api/weather", () => { + it("Should response JSON with cityName and temperature properties", async () => { + const response = await request.post("/api/weather").send({ + cityName: "Amsterdam", + }); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty("cityName"); + expect(response.body).toHaveProperty("temperature"); + }); + + it("Should response JSON with cityName not found", async () => { + const cityName = "andromeda"; + const response = await request.post("/api/weather").send({ + cityName: `${cityName}`, + }); + expect(response.status).toBe(404); + expect(response.body).toEqual({ error: `${cityName} not found` }); + }); + + it("Should response 'Request body is missing'", async () => { + const response = await request.post("/api/weather"); + + expect(response.status).toBe(400); + expect(response.body.error).toMatch(/request body is missing/i); + }); + + it("Should response 'Required parameter 'cityName' is missing'", async () => { + const response = await request + .post("/api/weather") + .send({ cityName: " " }); + + expect(response.status).toBe(400); + expect(response.body).toEqual({ + error: "Required parameter 'cityName' is missing", + }); + }); +}); + +describe("Unknown routes", () => { + it("Should respond with 404 for undefined path", async () => { + const response = await request.get("/non-existing-path"); + + expect(response.status).toBe(404); + expect(response.body).toHaveProperty("error"); + expect(response.body.error).toMatch(/not found/i); + }); +}); diff --git a/assignments/config-files/babel.config.cjs b/assignments/hackyourtemperature/babel.config.cjs similarity index 64% rename from assignments/config-files/babel.config.cjs rename to assignments/hackyourtemperature/babel.config.cjs index fbb629af6..392abb66d 100644 --- a/assignments/config-files/babel.config.cjs +++ b/assignments/hackyourtemperature/babel.config.cjs @@ -1,7 +1,6 @@ module.exports = { presets: [ [ - // This is a configuration, here we are telling babel what configuration to use "@babel/preset-env", { targets: { diff --git a/assignments/config-files/jest.config.js b/assignments/hackyourtemperature/jest.config.js similarity index 100% rename from assignments/config-files/jest.config.js rename to assignments/hackyourtemperature/jest.config.js diff --git a/assignments/hackyourtemperature/package.json b/assignments/hackyourtemperature/package.json new file mode 100644 index 000000000..696127971 --- /dev/null +++ b/assignments/hackyourtemperature/package.json @@ -0,0 +1,23 @@ +{ + "name": "hackyourtemperature", + "version": "1.0.0", + "description": "", + "main": "server.js", + "type": "module", + "scripts": { + "start": "node src/server.js", + "dev": "node --watch --env-file=.env src/server.js", + "test": "jest" + }, + "author": "yanaesher", + "license": "ISC", + "dependencies": { + "express": "^5.1.0" + }, + "devDependencies": { + "@babel/preset-env": "^7.27.1", + "babel-jest": "^29.7.0", + "jest": "^29.7.0", + "supertest": "^7.1.0" + } +} diff --git a/assignments/hackyourtemperature/src/app.js b/assignments/hackyourtemperature/src/app.js new file mode 100644 index 000000000..40607eb2c --- /dev/null +++ b/assignments/hackyourtemperature/src/app.js @@ -0,0 +1,20 @@ +import express from "express"; +import weatherRouter from "./routes/temperature.route.js"; + +import { notFound } from "./middlewares/notFound.middleware.js"; +import { errorHandler } from "./middlewares/error.middleware.js"; + +const app = express(); + +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + +app.use("/api/weather", weatherRouter); +app.get("/api", (req, res) => { + res.status(200).send("hello from backend to frontend!"); +}); + +app.use(notFound); +app.use(errorHandler); + +export default app; diff --git a/assignments/hackyourtemperature/src/constants.js b/assignments/hackyourtemperature/src/constants.js new file mode 100644 index 000000000..78fbf616e --- /dev/null +++ b/assignments/hackyourtemperature/src/constants.js @@ -0,0 +1,2 @@ +export const BASE_WEATHER_URL = + "https://api.openweathermap.org/data/2.5/weather"; diff --git a/assignments/hackyourtemperature/src/controllers/weather.controller.js b/assignments/hackyourtemperature/src/controllers/weather.controller.js new file mode 100644 index 000000000..a22188f28 --- /dev/null +++ b/assignments/hackyourtemperature/src/controllers/weather.controller.js @@ -0,0 +1,25 @@ +import { fetchCurrentWeather } from "../services/weather.service.js"; + +export async function getCityCurrentWeather(req, res, next) { + const { cityName } = req.body; + + if (!cityName?.trim()) { + const error = new Error("Required parameter 'cityName' is missing"); + error.status = 400; + + return next(error); + } + + try { + const { name, main } = await fetchCurrentWeather(cityName); + res.status(200).json({ cityName: name, temperature: main.temp }); + } catch (err) { + if (err.status === 404) { + const notFoundMessage = new Error(`${cityName} not found`); + notFoundMessage.status = 404; + return next(notFoundMessage); + } + + next(err); + } +} diff --git a/assignments/hackyourtemperature/src/keys.js b/assignments/hackyourtemperature/src/keys.js new file mode 100644 index 000000000..b10003544 --- /dev/null +++ b/assignments/hackyourtemperature/src/keys.js @@ -0,0 +1 @@ +export const API_WEATHER_KEY = "007dd5ca6a8f91773d26bde4f0099937"; diff --git a/assignments/hackyourtemperature/src/middlewares/error.middleware.js b/assignments/hackyourtemperature/src/middlewares/error.middleware.js new file mode 100644 index 000000000..d252b51e2 --- /dev/null +++ b/assignments/hackyourtemperature/src/middlewares/error.middleware.js @@ -0,0 +1,6 @@ +export function errorHandler(err, req, res, next) { + const statusCode = err.status || 500; + const message = err.message || "Something went wrong"; + + res.status(statusCode).json({ error: message }); +} diff --git a/assignments/hackyourtemperature/src/middlewares/notFound.middleware.js b/assignments/hackyourtemperature/src/middlewares/notFound.middleware.js new file mode 100644 index 000000000..46b644b6f --- /dev/null +++ b/assignments/hackyourtemperature/src/middlewares/notFound.middleware.js @@ -0,0 +1,5 @@ +export function notFound(req, res, next) { + const error = new Error(`Not found`); + error.status = 404; + next(error); +} diff --git a/assignments/hackyourtemperature/src/middlewares/validateBody.js b/assignments/hackyourtemperature/src/middlewares/validateBody.js new file mode 100644 index 000000000..823ecc9e9 --- /dev/null +++ b/assignments/hackyourtemperature/src/middlewares/validateBody.js @@ -0,0 +1,9 @@ +export function validateBody(req, res, next) { + if (!req.body) { + const error = new Error("Request body is missing"); + error.status = 400; + return next(error); + } + + next(); +} diff --git a/assignments/hackyourtemperature/src/routes/temperature.route.js b/assignments/hackyourtemperature/src/routes/temperature.route.js new file mode 100644 index 000000000..165d132f2 --- /dev/null +++ b/assignments/hackyourtemperature/src/routes/temperature.route.js @@ -0,0 +1,8 @@ +import { Router } from "express"; +import { validateBody } from "../middlewares/validateBody.js"; +import { getCityCurrentWeather } from "../controllers/weather.controller.js"; +const router = Router(); + +router.post("/", validateBody, getCityCurrentWeather); + +export default router; diff --git a/assignments/hackyourtemperature/src/server.js b/assignments/hackyourtemperature/src/server.js new file mode 100644 index 000000000..7adc49ba2 --- /dev/null +++ b/assignments/hackyourtemperature/src/server.js @@ -0,0 +1,7 @@ +import app from "./app.js"; + +const PORT = process.env.PORT || 3000; + +app.listen(PORT, () => { + console.log(`Server is running at http://127.0.0.1:${PORT}`); +}); diff --git a/assignments/hackyourtemperature/src/services/weather.service.js b/assignments/hackyourtemperature/src/services/weather.service.js new file mode 100644 index 000000000..5a17d8231 --- /dev/null +++ b/assignments/hackyourtemperature/src/services/weather.service.js @@ -0,0 +1,10 @@ +import { fetchData } from "../utils/fetchHelper.js"; +import { API_WEATHER_KEY } from "../keys.js"; +import { BASE_WEATHER_URL } from "../constants.js"; + +export async function fetchCurrentWeather(cityName) { + const url = `${BASE_WEATHER_URL}?q=${cityName}&appid=${API_WEATHER_KEY}&units=metric`; + + const weatherData = await fetchData(url); + return weatherData; +} diff --git a/assignments/hackyourtemperature/src/utils/fetchHelper.js b/assignments/hackyourtemperature/src/utils/fetchHelper.js new file mode 100644 index 000000000..64974a855 --- /dev/null +++ b/assignments/hackyourtemperature/src/utils/fetchHelper.js @@ -0,0 +1,11 @@ +export async function fetchData(url) { + const response = await fetch(url); + + if (!response.ok) { + const error = new Error(`Failed to fetch: ${response.status}`); + error.status = response.status; + throw error; + } + + return response.json(); +} diff --git a/week1/practice-exercises/1-pad-numbers/padLeft.js b/week1/practice-exercises/1-pad-numbers/padLeft.js index b58f887a3..2436068e1 100644 --- a/week1/practice-exercises/1-pad-numbers/padLeft.js +++ b/week1/practice-exercises/1-pad-numbers/padLeft.js @@ -4,7 +4,7 @@ * e.g. padLeft('foo', 5, '_') -> '__foo' * e.g. padLeft( '2', 2, '0') -> '02' */ -function padLeft(val, num, str) { +export function padLeft(val, num, str) { return '00000'.replace(/0/g, str).slice(0, num - val.length) + val; } diff --git a/week1/practice-exercises/1-pad-numbers/script.js b/week1/practice-exercises/1-pad-numbers/script.js index c11264e39..c0d907c46 100644 --- a/week1/practice-exercises/1-pad-numbers/script.js +++ b/week1/practice-exercises/1-pad-numbers/script.js @@ -1,21 +1,22 @@ - +import { padLeft } from "./padLeft.js"; /** - ** Exercise 1: Pad numbers - * + * + * + ** Exercise 1: Pad numbers + * * In this file use the padLeft function from padLeft.js to * pad the numbers to exactly 5 spaces and log them to the console - * + * * Expected output (replace the underscore with spaces): - * + * * ___12; * __846; * ____2; * _1236; - * + * * Tips: * where to use `exports` and where `require`? */ -let numbers = [ "12", "846", "2", "1236" ]; - -// YOUR CODE GOES HERE +let numbers = ["12", "846", "2", "1236"]; +numbers.forEach((num) => console.log(padLeft(num, 5, "_"))); diff --git a/week1/practice-exercises/2-left-pad/padLeft.js b/week1/practice-exercises/2-left-pad/padLeft.js index b58f887a3..0b7c83b8d 100644 --- a/week1/practice-exercises/2-left-pad/padLeft.js +++ b/week1/practice-exercises/2-left-pad/padLeft.js @@ -7,5 +7,3 @@ function padLeft(val, num, str) { return '00000'.replace(/0/g, str).slice(0, num - val.length) + val; } - -// YOUR CODE GOES HERE \ No newline at end of file diff --git a/week1/practice-exercises/2-left-pad/script.js b/week1/practice-exercises/2-left-pad/script.js index e0e081243..49f50a6b9 100644 --- a/week1/practice-exercises/2-left-pad/script.js +++ b/week1/practice-exercises/2-left-pad/script.js @@ -1,13 +1,5 @@ -/** - ** Exercise 2: To the left, to the left... - * - * Copy and paste your code from the previous exercise. - * Replace the function `padLeft` to use - * this new NPM package called `left-pad` instead then - * Pad the numbers to 8 characters to confirm that it works correctly - * - */ +import padLeft from "left-pad"; -let numbers = [ "12", "846", "2", "1236" ]; +let numbers = ["12", "846", "2", "1236"]; -// YOUR CODE GOES HERE \ No newline at end of file +numbers.forEach((num) => console.log(padLeft(num, 8, "_"))); diff --git a/week1/practice-exercises/package.json b/week1/practice-exercises/package.json new file mode 100644 index 000000000..b06fbd92c --- /dev/null +++ b/week1/practice-exercises/package.json @@ -0,0 +1,16 @@ +{ + "name": "practice-exercises", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "left-pad": "^1.3.0" + } +} diff --git a/week1/prep-exercises/1-web-server/index.html b/week1/prep-exercises/1-web-server/index.html index c64f7dcfa..0b326f19d 100644 --- a/week1/prep-exercises/1-web-server/index.html +++ b/week1/prep-exercises/1-web-server/index.html @@ -1,5 +1,6 @@ + My First Web Server diff --git a/week1/prep-exercises/1-web-server/package.json b/week1/prep-exercises/1-web-server/package.json index 30da55a2d..4ef406a38 100644 --- a/week1/prep-exercises/1-web-server/package.json +++ b/week1/prep-exercises/1-web-server/package.json @@ -3,12 +3,14 @@ "version": "1.0.0", "description": "A simple express server", "main": "server.js", + "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "nodemon server.js" }, - "author": "Andrej Gajduk", - "license": "MIT", - "dependencies": { - "express": "^4.17.1" + "author": "Yana Seniuk", + "license": "ISC", + "devDependencies": { + "nodemon": "^3.1.10" } } diff --git a/week1/prep-exercises/1-web-server/server.js b/week1/prep-exercises/1-web-server/server.js index 90cb5ee65..31d078165 100644 --- a/week1/prep-exercises/1-web-server/server.js +++ b/week1/prep-exercises/1-web-server/server.js @@ -1,14 +1,46 @@ -/** - * Exercise 3: Create an HTTP web server - */ +import * as http from "node:http"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import * as fs from "node:fs/promises"; -const http = require('http'); +const PORT = 3000; -//create a server -let server = http.createServer(function (req, res) { - // YOUR CODE GOES IN HERE - res.write('Hello World!'); // Sends a response back to the client - res.end(); // Ends the response +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +async function handleRoute(file, contentType, status, req, res) { + try { + const filePath = path.join(__dirname, file); + const fileContent = await fs.readFile(filePath, "utf-8"); + + res.setHeader("Content-type", contentType); + res.statusCode = status; + res.end(fileContent); + } catch (err) { + res.setHeader("Content-type", "text/plain"); + res.statusCode = 500; + res.end("Server Error"); + } +} + +const server = http.createServer((req, res) => { + const routes = { + "/": { file: "index.html", contentType: "text/html" }, + "/index.js": { file: "index.js", contentType: "application/javascript" }, + "/style.css": { file: "style.css", contentType: "text/css" }, + }; + + const route = routes[req.url]; + if (route) { + handleRoute(route.file, route.contentType, 200, req, res); + } + + if (!route) { + res.statusCode = 404; + res.end("Not Found"); + } }); -server.listen(3000); // The server starts to listen on port 3000 +server.listen(PORT, () => { + console.log(`Server is running at http://127.0.0.1:${PORT}/`); +}); diff --git a/week1/prep-exercises/1-web-server/style.css b/week1/prep-exercises/1-web-server/style.css new file mode 100644 index 000000000..d599537da --- /dev/null +++ b/week1/prep-exercises/1-web-server/style.css @@ -0,0 +1,3 @@ +#content{ + color: green; +} \ No newline at end of file diff --git a/week2/practice-exercises/1-joke-api/package.json b/week2/practice-exercises/1-joke-api/package.json new file mode 100644 index 000000000..b07667de8 --- /dev/null +++ b/week2/practice-exercises/1-joke-api/package.json @@ -0,0 +1,13 @@ +{ + "name": "1-joke-api", + "version": "1.0.0", + "description": "Did you know that there is an API for Chuck Norris jokes? That's incredible, right!?", + "main": "script.js", + "type": "module", + "scripts": { + + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/week2/practice-exercises/1-joke-api/script.js b/week2/practice-exercises/1-joke-api/script.js index be4d84612..fcefd312c 100644 --- a/week2/practice-exercises/1-joke-api/script.js +++ b/week2/practice-exercises/1-joke-api/script.js @@ -1,18 +1,12 @@ -/** - * 1. Chuck Norris programs do not accept input - * - * `GET` a random joke inside the function, using the API: http://www.icndb.com/api/ - * (use `node-fetch`) and print it to the console. - * Make use of `async/await` and `try/catch` - * - * Hints - * - To install node dependencies you should first initialize npm - * - Print the entire response to the console to see how it is structured. - */ - -function printChuckNorrisJoke() { - // YOUR CODE GOES IN HERE - +async function printChuckNorrisJoke() { + const response = await fetch("https://api.chucknorris.io/jokes/random"); + if (!response.ok) throw new Error("Failed to fetch data"); + const data = await response.json(); + console.log(data.value); } -printChuckNorrisJoke(); \ No newline at end of file +try { + await printChuckNorrisJoke(); +} catch (error) { + console.error(error.message); +} diff --git a/week2/prep-exercises/1-blog-API/MamaFile b/week2/prep-exercises/1-blog-API/MamaFile new file mode 100644 index 000000000..e594d4672 --- /dev/null +++ b/week2/prep-exercises/1-blog-API/MamaFile @@ -0,0 +1 @@ +Vnature \ No newline at end of file diff --git a/week2/prep-exercises/1-blog-API/blog.controller.js b/week2/prep-exercises/1-blog-API/blog.controller.js new file mode 100644 index 000000000..3934bbeef --- /dev/null +++ b/week2/prep-exercises/1-blog-API/blog.controller.js @@ -0,0 +1,84 @@ +import { Router } from "express"; +import fs from "node:fs/promises"; + +const router = Router(); + +//create new file with title and content +router.post("/", async (req, res, next) => { + const title = req.body.title?.trim(); + const content = req.body.content?.trim(); + + if (!title) { + return res.status(400).send("title is required"); + } + + if (!content) { + return res.status(400).send("content is required"); + } + + try { + await fs.writeFile(title, content, "utf-8"); + res.status(201).send("ok"); + } catch (error) { + next(error); + } +}); +//update title and content +router.put("/:title", async (req, res, next) => { + const title = req.params.title; + let { content, title: newTitle } = req.body; + + if (!newTitle?.trim()) { + newTitle = title; + } + + if (!content?.trim()) { + return res + .status(400) + .json({ message: "Content is required and cannot be empty" }); + } + + try { + await fs.access(title); + await fs.rename(title, newTitle); + await fs.writeFile(newTitle, content); + res + .status(200) + .json({ message: "File content updated successfully", file: newTitle }); + } catch (error) { + next(error); + } +}); + +//delete file +router.delete("/:title", async (req, res, next) => { + const title = req.params.title; + try { + await fs.access(title); + await fs.unlink(title); + res.status(200).json({ message: "File deleted successfully" }); + } catch (err) { + const error = new Error(`Cannot delete file: ${err.message}`); + error.status = 400; + next(error); + } +}); + +//get file content +router.get("/:title", async (req, res, next) => { + const fileTitle = req.params.title; + try { + await fs.access(fileTitle); + const file = await fs.readFile(fileTitle, "utf-8"); + return res.status(200).send(file); + } catch (err) { + const error = new Error( + `Could find file with name ${fileTitle}`, + err.message + ); + error.status = 404; + next(error); + } +}); + +export default router; diff --git a/week2/prep-exercises/1-blog-API/package.json b/week2/prep-exercises/1-blog-API/package.json index d89c4bd76..3094d3747 100644 --- a/week2/prep-exercises/1-blog-API/package.json +++ b/week2/prep-exercises/1-blog-API/package.json @@ -3,9 +3,9 @@ "version": "1.0.0", "description": "", "main": "server.js", + "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "node server.js" + "start": "node --watch server.js" }, "author": "", "license": "ISC", diff --git a/week2/prep-exercises/1-blog-API/server.js b/week2/prep-exercises/1-blog-API/server.js index 3f615e8f5..115022ae1 100644 --- a/week2/prep-exercises/1-blog-API/server.js +++ b/week2/prep-exercises/1-blog-API/server.js @@ -1,10 +1,18 @@ -const express = require('express') +import express from "express"; +import router from "./blog.controller.js"; + const app = express(); - - -// YOUR CODE GOES IN HERE -app.get('/', function (req, res) { - res.send('Hello World') -}) - -app.listen(3000) \ No newline at end of file +const PORT = 3000; + +function main() { + app.use(express.json()); + app.use(express.urlencoded({ extended: false })); + + app.use("/api/blogs", router); + + app.listen(PORT, () => { + console.log(`Server is running at port http://127.0.0.1:${PORT}`); + }); +} + +main(); diff --git a/week2/prep-exercises/1-blog-API/services/blog.serice.js b/week2/prep-exercises/1-blog-API/services/blog.serice.js new file mode 100644 index 000000000..47729157e --- /dev/null +++ b/week2/prep-exercises/1-blog-API/services/blog.serice.js @@ -0,0 +1,5 @@ +export class BlogService { + + + +} \ No newline at end of file