diff --git a/assignments/hackyourtemperature/_tests_/app.test.js b/assignments/hackyourtemperature/_tests_/app.test.js new file mode 100644 index 000000000..952d2a270 --- /dev/null +++ b/assignments/hackyourtemperature/_tests_/app.test.js @@ -0,0 +1,31 @@ +import { app } from '../app.js'; +import supertest from 'supertest'; + +const request = supertest(app); + +describe('POST /weather', () => { + it('should get 400 if cityName is empty', async () => { + const response = await request.post('/weather').send({}); + expect(response.status).toBe(400); + expect(response.body).toEqual({ + weatherText: 'City name is required!' + }); + }); + + it('should get 404 if cityName is gibberish', async () => { + const response = await request + .post('/weather') + .send({ cityName: 'nonesence123' }); + expect(response.status).toBe(404); + expect(response.body).toEqual({ error: 'City name is required!' }); + }); + + it('should get 200 if correct cityName is sent', async () => { + const response = await request + .post('/weather') + .send({ cityName: 'Amsterdam' }); + + expect(response.status).toBe(200); + expect(response.body.weatherText).toContain('Amsterdam'); + }); +}); diff --git a/assignments/hackyourtemperature/app.js b/assignments/hackyourtemperature/app.js new file mode 100644 index 000000000..0a7756407 --- /dev/null +++ b/assignments/hackyourtemperature/app.js @@ -0,0 +1,41 @@ +import express from 'express'; +import { keys } from './sources/keys.js'; +import fetch from 'node-fetch'; + +const app = express(); + +app.use(express.json()); + +app.get('/', (req, res) => { + res.send('hello from backend to frontend!'); +}); + +app.post('/weather', async (req, res) => { + try { + const { cityName } = req.body; + + if (!cityName ) { + return res + .status(400) + .json({ weatherText: 'City name is required!' }); + } + + const response = await fetch( + `https://api.openweathermap.org/data/2.5/weather?q=${cityName }&appid=${keys.API_KEY}` + ); + + if (!response.ok) { + return res.status(404).json({ weatherText: 'City is not found!' }); + } + + const jsonData = await response.json(); + const temperature = jsonData.main.temp; + res.status(200).json({ + weatherText: `${cityName }: ${temperature}` + }); + } catch (error) { + res.status(500).json({ error: 'Internal server error' }); + } +}); + +export { app }; \ No newline at end of file diff --git a/assignments/hackyourtemperature/babel.config.cjs b/assignments/hackyourtemperature/babel.config.cjs new file mode 100644 index 000000000..ed0bad0b9 --- /dev/null +++ b/assignments/hackyourtemperature/babel.config.cjs @@ -0,0 +1,13 @@ +module.exports = { + presets: [ + [ + // This is a configuration, here we are telling babel what configuration to use + '@babel/preset-env', + { + targets: { + node: 'current' + } + } + ] + ] +}; \ No newline at end of file diff --git a/assignments/hackyourtemperature/jest.config.js b/assignments/hackyourtemperature/jest.config.js new file mode 100644 index 000000000..0d98cced9 --- /dev/null +++ b/assignments/hackyourtemperature/jest.config.js @@ -0,0 +1,8 @@ +export default { + // Tells jest that any file that has 2 .'s in it and ends with either js or jsx should be run through the babel-jest transformer + transform: { + '^.+\\.jsx?$': 'babel-jest' + }, + // By default our `node_modules` folder is ignored by jest, this tells jest to transform those as well + transformIgnorePatterns: [] +}; diff --git a/assignments/hackyourtemperature/package.json b/assignments/hackyourtemperature/package.json new file mode 100644 index 000000000..91e66b214 --- /dev/null +++ b/assignments/hackyourtemperature/package.json @@ -0,0 +1,26 @@ +{ + "name": "hackyourtemperature", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "test": "jest", + "start": "node server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "express": "^4.21.1", + "express-handlebars": "^8.0.1", + "node-fetch": "^3.3.2" + }, + "type": "module", + "devDependencies": { + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "babel-jest": "^29.7.0", + "jest": "^29.7.0", + "supertest": "^7.0.0" + } +} \ No newline at end of file diff --git a/assignments/hackyourtemperature/server.js b/assignments/hackyourtemperature/server.js new file mode 100644 index 000000000..7f5935fc4 --- /dev/null +++ b/assignments/hackyourtemperature/server.js @@ -0,0 +1,7 @@ +import { app } from './app.js'; + +const port = 3001; + +app.listen(port, () => { + console.log(`App listening on port ${port}`); +}); \ No newline at end of file diff --git a/assignments/hackyourtemperature/sources/keys.js b/assignments/hackyourtemperature/sources/keys.js new file mode 100644 index 000000000..c3c279183 --- /dev/null +++ b/assignments/hackyourtemperature/sources/keys.js @@ -0,0 +1,4 @@ +export const keys = { + API_KEY: '7fd63ddde6d16bb3769e4d483716e57a' + }; + \ 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..22a2bdfb8 --- /dev/null +++ b/week2/practice-exercises/1-joke-api/package.json @@ -0,0 +1,17 @@ +{ + "name": "week2", + "version": "1.0.0", + "description": "## Agenda", + "main": "script.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "express": "^4.21.1", + "node-fetch": "^2.7.0" + } +} diff --git a/week2/practice-exercises/1-joke-api/script.js b/week2/practice-exercises/1-joke-api/script.js index be4d84612..8a8898a3c 100644 --- a/week2/practice-exercises/1-joke-api/script.js +++ b/week2/practice-exercises/1-joke-api/script.js @@ -1,18 +1,31 @@ /** * 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. + * (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() { +import fetch from "node-fetch"; +async function printChuckNorrisJoke() { // YOUR CODE GOES IN HERE - + try { + const url = "https://api.chucknorris.io/jokes/random"; + const response = await fetch(url); + if (!response.ok) { + throw new Error("Invalid URL"); + } + const data = await response.json(); + if (!data || !data.value) { + throw new Error("Error: Joke data not found "); + } + console.log("Joke: ", data.value); + } catch (error) { + throw new Error(`Fetching data failed: ${error.message}`); + } } -printChuckNorrisJoke(); \ No newline at end of file +printChuckNorrisJoke(); diff --git a/week2/practice-exercises/2-party-time/package.json b/week2/practice-exercises/2-party-time/package.json new file mode 100644 index 000000000..347bede1a --- /dev/null +++ b/week2/practice-exercises/2-party-time/package.json @@ -0,0 +1,15 @@ +{ + "name": "2-party-time", + "version": "1.0.0", + "description": "Are you excited for the biggest party on the planet? We are and we would like to invite everyone, but there is only a limited number of seats.", + "main": "script.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "node-fetch": "^3.3.2" + } +} diff --git a/week2/practice-exercises/2-party-time/script.js b/week2/practice-exercises/2-party-time/script.js index 31624dc52..7644ac049 100644 --- a/week2/practice-exercises/2-party-time/script.js +++ b/week2/practice-exercises/2-party-time/script.js @@ -8,9 +8,27 @@ * Hints: * - make sure to use the correct headers and http method in the request */ +import fetch from 'node-fetch'; -function makeReservation() { +async function makeReservation() { + try{ + const url = "https://reservation100-sandbox.mxapps.io/rest-doc/api"; + const response = await fetch(url); + if(!response.ok){ + throw new Error("URL: Not found"); + } +const data = await response.json(); +if(!data){ + throw new Error("Data: Not found"); + +} +console.log(data); + }catch(error){ + console.log(error); + + } // YOUR CODE GOES IN HERE + } makeReservation(); \ No newline at end of file diff --git a/week2/prep-exercises/1-blog-API/my-blog b/week2/prep-exercises/1-blog-API/my-blog new file mode 100644 index 000000000..2fea95d16 --- /dev/null +++ b/week2/prep-exercises/1-blog-API/my-blog @@ -0,0 +1 @@ +Lorem ipsu13311m \ No newline at end of file diff --git a/week2/prep-exercises/1-blog-API/server.js b/week2/prep-exercises/1-blog-API/server.js index 3f615e8f5..27dd9b96e 100644 --- a/week2/prep-exercises/1-blog-API/server.js +++ b/week2/prep-exercises/1-blog-API/server.js @@ -1,10 +1,70 @@ -const express = require('express') +const express = require("express"); 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 fs = require("fs"); + +app.get("/blogs/:title", (req, res) => { + const { title } = req.params; + + try { + if (fs.existsSync(title)) { + const content = fs.readFileSync(title, "utf-8"); + res.send(content); + } else { + res.status(404).send("Blog not found"); + } + } catch (err) { + res.status(500).send("Error reading file: " + err.message); + } +}); + +app.use(express.json()); +app.post("/blogs", function (req, res) { + const { title, content } = req.body; + if (!title || !content) { + return res.status(400).send("Title and content are required"); + } + try { + fs.writeFileSync(title, content); + res.status(201).send("Blog created successfully"); + } catch (err) { + res.status(500).send("Error writing file: " + err.message); + } +}); + +app.put("/posts/:title", function (req, res) { + const { title } = req.params; + const { content } = req.body; + if (!content) { + return res.status(400).send("Content is required to update the blog"); + } + + try { + if (fs.existsSync(title)) { + fs.writeFileSync(title, content); + res.send("Blog updated successfully"); + } else { + res.status(404).send("Blog not found"); + } + } catch (err) { + res.status(500).send("Error updating file: " + err.message); + } +}); + +app.delete("/blogs/:title", (req, res) => { + const { title } = req.params; + + try { + if (fs.existsSync(title)) { + fs.unlinkSync(title); + res.send("Blog deleted successfully"); + } else { + res.status(404).send("Blog not found"); + } + } catch (err) { + res.status(500).send("Error deleting file: " + err.message); + } +}); + +app.listen(3001, () => { + console.log("Server running on port 3001"); +}); diff --git a/week3/prep-exercise/package.json b/week3/prep-exercise/package.json index 6d4a60eeb..d86e45e58 100644 --- a/week3/prep-exercise/package.json +++ b/week3/prep-exercise/package.json @@ -16,12 +16,11 @@ "dependencies": { "bcrypt": "^5.1.1", "express": "^4.18.2", - "lokijs": "^1.5.12", "jsonwebtoken": "^9.0.2", + "lokijs": "^1.5.12", "uuid": "^9.0.1" }, "devDependencies": { - "nodemon": "^3.1.0" + "nodemon": "^3.1.9" } } - diff --git a/week3/prep-exercise/server/app.js b/week3/prep-exercise/server/app.js index a94f09af8..8316ba995 100644 --- a/week3/prep-exercise/server/app.js +++ b/week3/prep-exercise/server/app.js @@ -1,15 +1,21 @@ -import express from 'express'; -// TODO Use below import statement for importing middlewares from users.js for your routes -// TODO import { ....... } from "./users.js"; +import express from "express"; +import { register, login, getProfile, logout } from "./controllers.js"; -let app = express(); +const app = express(); -app.use(express.json()); -// TODO: Create routes here, e.g. app.post("/register", .......) +// Register Endpoint +app.post("/register", register); -// Serve the front-end application from the `client` folder -app.use(express.static('client')); +// Login Endpoint +app.post("/login", login); +// Get Profile Endpoint +app.get("/profile", getProfile); + +// Logout Endpoint +app.post("/logout", logout); + +// Start the server app.listen(3000, () => { - console.log('Server is running on port 3000'); + console.log("Server is running on port 3000"); }); diff --git a/week3/prep-exercise/server/users.js b/week3/prep-exercise/server/users.js index fbf91e6c2..73de55952 100644 --- a/week3/prep-exercise/server/users.js +++ b/week3/prep-exercise/server/users.js @@ -1,12 +1,141 @@ -import newDatabase from './database.js' +import jwt from "jsonwebtoken"; +import { v4 as generateUUID } from "uuid"; +import { hash, compare } from "bcrypt"; +import newDatabase from "./database.js"; -// Change this boolean to true if you wish to keep your -// users between restart of your application -const isPersistent = false -const database = newDatabase({isPersistent}) +const isPersistent = false; +const database = newDatabase({ isPersistent }); -// Create middlewares required for routes defined in app.js -// export const register = async (req, res) => {}; +const JWT_SECRET = "your_secret_key"; +const saltRounds = 10; -// You can also create helper functions in this file to help you implement logic -// inside middlewares +const usersDatabase = database.getUsers(); + +export const register = async (req, res) => { + // Check request body + if (!req.body.username || !req.body.password) { + res + .status(400) + .json({ message: "Please provide username and password" }) + .end(); + return; + } + + // Check if username already exists + const isUsernameExists = getUserByUsername(req.body.username) !== undefined; + if (isUsernameExists) { + res.status(400).json({ message: "Username already exists" }).end(); + return; + } + + // Hash the password and create new user + const hashedPassword = await hash(req.body.password, saltRounds); + const newUser = { + id: generateUUID(), + username: req.body.username, + password: hashedPassword, + }; + + // Save user to usersDatabase + database.addUser(newUser); + + // Return success and the new user to the client + res + .status(201) + .json({ + id: newUser.id, + username: newUser.username, + }) + .end(); +}; + +export const login = async (req, res) => { + // Check request body + if (!req.body.username || !req.body.password) { + res + .status(400) + .json({ message: "Please provide username and password" }) + .end(); + return; + } + + // Find user in the database + const user = getUserByUsername(req.body.username); + if (!user) { + res + .status(401) + .json({ message: "Invalid username / password combination" }) + .end(); + return; + } + + // Check if password is correct by using bcrypt compare + const isPasswordCorrect = await compare(req.body.password, user.password); + if (!isPasswordCorrect) { + res + .status(401) + .json({ message: "Invalid username / password combination" }) + .end(); + return; + } + + // Generate JWT token + const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: "1h" }); + + // Return the token to the client + res.status(200).json({ token }).end(); +}; + +export const getProfile = (req, res) => { + // Check if user is logged in + const authHeader = req.headers.authorization; + const token = extractBearerTokenFromAuth(authHeader); + + if (!token) { + res.status(401).json({ message: "You are not logged in" }).end(); + return; + } + + // Verify JWT token + jwt.verify(token, JWT_SECRET, (err, decoded) => { + if (err) { + res.status(401).json({ message: "Invalid token" }).end(); + return; + } + + const user = getUserById(decoded.userId); + if (!user) { + res.status(401).json({ message: "User not found" }).end(); + return; + } + + // Return user profile + res + .status(200) + .json({ + message: `Hello! You are currently logged in as ${user.username}!`, + }) + .end(); + }); +}; + +export const logout = (req, res) => { + // No need to track sessions with JWT + res.status(204).end(); +}; + +// Helper functions +const getUserByUsername = (username) => { + return usersDatabase.find((user) => user.username === username); +}; + +const getUserById = (userID) => { + return usersDatabase.find((user) => user.id === userID); +}; + +const extractBearerTokenFromAuth = (authorization) => { + if (!authorization || !authorization.startsWith("Bearer ")) { + return null; + } + return authorization.replace("Bearer ", ""); +};