-
Notifications
You must be signed in to change notification settings - Fork 6
Assignments week 2 Ilia Bubnov #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,5 +6,6 @@ npm-debug.log | |
package-lock.json | ||
yarn-error.log | ||
*.bkp | ||
.idea | ||
|
||
week3/prep-exercise/server-demo/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { app } from "../app.js"; | ||
import supertest from "supertest"; | ||
|
||
const request = supertest(app); | ||
|
||
describe("POST /weather", () => { | ||
it("Should return an error when cityName is not passed", async () => { | ||
const response = await request.post("/weather").send({ cityName: "" }); | ||
expect(response.statusCode).toBe(400); | ||
expect(response.text).toBe( | ||
'{"weatherText":"You have to fill in city name"}', | ||
); | ||
}); | ||
it("Should return an error when cityName is not a city", async () => { | ||
const response = await request | ||
.post("/weather") | ||
.send({ cityName: "jkasdhgjkjasdgf" }); | ||
expect(response.statusCode).toBe(404); | ||
expect(response.text).toBe( | ||
'{"weatherText":"Error occurred while fetching data, please check your request"}', | ||
); | ||
}); | ||
it("Should successfully fetch data from external API", async () => { | ||
const response = await request | ||
.post("/weather") | ||
.send({ cityName: "Amsterdam" }); | ||
expect(response.statusCode).toBe(200); | ||
expect(response.text).toContain("Amsterdam"); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import express from "express"; | ||
import { engine } from "express-handlebars"; | ||
import { getWeather } from "./functions.js"; | ||
|
||
export const app = express(); | ||
|
||
app.engine("handlebars", engine()); | ||
app.set("view engine", "handlebars"); | ||
app.set("views", "./views"); | ||
|
||
app.use(express.json()); | ||
|
||
app.get("/", (req, res) => { | ||
//res.render("index"); | ||
res.send("hello from backend to frontend!"); | ||
}); | ||
app.post("/weather", (req, res) => { | ||
getWeather(req, res); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really good seperation of logic, placing functions/controllers in a different class/method. Good Job! |
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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", | ||
}, | ||
}, | ||
], | ||
], | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { keys } from "./sources/keys.js"; | ||
|
||
export async function fetcher(url) { | ||
const response = await fetch(url); | ||
if (!response.ok) { | ||
throw response; | ||
} | ||
return response.json(); | ||
} | ||
|
||
export async function getWeather(req, res) { | ||
const message = { weatherText: "" }; | ||
if (!req.body.cityName) { | ||
message.weatherText = "You have to fill in city name"; | ||
return res.status(400).json(message); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should use a different model for errors than we do for valid responses, as the client may not check the status code, and just if their is a weatherText value. A good practise is all errors get returned in an error object like : This allows you to build standard error wrappers/handlers in both the back-end and front-end. |
||
} | ||
|
||
const { cityName } = req.body; | ||
const url = `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${keys.API_KEY}&units=metric`; | ||
|
||
try { | ||
const data = await fetcher(url); | ||
const temperature = data.main.temp.toFixed(1); | ||
message.weatherText = `${cityName} : ${temperature}°C`; | ||
res.json(message); | ||
} catch (e) { | ||
console.error( | ||
`An error occurred while fetching data: ${e.status + e.statusText}`, | ||
); | ||
if (e.status >= 400 && e.status < 500) { | ||
message.weatherText = | ||
"Error occurred while fetching data, please check your request"; | ||
return res.status(e.status).json(message); | ||
} | ||
if (e.status >= 500) { | ||
message.weatherText = | ||
"Something went wrong on server side, try again later"; | ||
return res.status(e.status).json(message); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the errors status is not set (e.g. the error was thrown from something other that the API request, then this API will not respond properly, good to write at the bottom a single "unknown error". response. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: [], | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "hackyourtemperature", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "server.js", | ||
"scripts": { | ||
"test": "jest", | ||
"start": "node server.js", | ||
"dev": "node --watch server.js", | ||
"prettier": "npx prettier . --write" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC", | ||
"type": "module", | ||
"dependencies": { | ||
"express": "^5.1.0", | ||
"express-handlebars": "^8.0.3" | ||
}, | ||
"devDependencies": { | ||
"@babel/preset-env": "^7.27.1", | ||
"babel-jest": "^29.7.0", | ||
"jest": "^29.7.0", | ||
"prettier": "^3.5.3", | ||
"supertest": "^7.1.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { app } from "./app.js"; | ||
|
||
const PORT = process.env.PORT || 3000; | ||
app.listen(PORT, () => console.log(`Listening on port ${PORT}`)); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const keys = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's good practice to not push up API keys to repo's to keep them safe. two ways to do this are: |
||
API_KEY: "532e0f8f80398903f64483df5c48bdc0", | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
[ | ||
{ | ||
"id": "asdkjhflaksdhf-asdjkfhglajks", | ||
"title": "The Whispering Shadows", | ||
"author": "Elena Marquez" | ||
}, | ||
{ | ||
"id": "iuy23io5u34-43lkj5h234589", | ||
"title": "Beneath the Crimson Sky", | ||
"author": "Liam O'Connor" | ||
}, | ||
{ | ||
"id": "jghghruh89ehrgeu9-g34y", | ||
"title": "Echoes of Tomorrow", | ||
"author": "Priya Desai" | ||
}, | ||
{ | ||
"id": "4g5u2345837g-hleriuthw", | ||
"title": "The Forgotten Orchard", | ||
"author": "Sophie Laurent" | ||
}, | ||
{ | ||
"id": "h75485gkhjgughiu-34783", | ||
"title": "Midnight in Avalon", | ||
"author": "Jonas Berg" | ||
}, | ||
{ | ||
"id": "hg4u3gh23io457-g452jh34g5o", | ||
"title": "Fragments of Infinity", | ||
"author": "Aisha Khan" | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "library-api", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC", | ||
"type": "module", | ||
"dependencies": { | ||
"express": "^5.1.0" | ||
}, | ||
"devDependencies": { | ||
"uuid": "^11.1.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import express from 'express'; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
import data from './books.json' with { type: "json" }; | ||
const app = express(); | ||
const PORT = process.env.PORT || 3000; | ||
|
||
app.use(express.json()); | ||
app.post('/books', (req, res) => { | ||
createBook(req, res); | ||
}) | ||
app.get('/books', (req, res) => { | ||
readBooks(req, res); | ||
}) | ||
app.put('/books/:id', (req, res) => { | ||
updateBook(req, res); | ||
}) | ||
app.delete('/books/:id', (req, res) => { | ||
deleteBook(req, res); | ||
}) | ||
|
||
const isInvalid = (req) => { | ||
return !req.body || !req.body.title || !req.body.author; | ||
} | ||
|
||
const createBook = (req, res) => { | ||
if ( isInvalid(req) ) return res.status(400).send("Invalid request"); | ||
const id = uuidv4(); | ||
let newBook = { | ||
id: id, | ||
title: req.body.title, | ||
author: req.body.author, | ||
} | ||
data.push(newBook); | ||
res.status(200).send(id); | ||
} | ||
|
||
const readBooks = (req, res) => { | ||
res.setHeader('Content-Type', 'application/json'); | ||
res.send(data); | ||
} | ||
|
||
const updateBook = (req, res) => { | ||
if ( isInvalid(req) ) return res.status(400).send("Invalid request"); | ||
const id = req.params.id; | ||
const bookToUpdate = data.find(book => book.id === id); | ||
if (!bookToUpdate) return res.status(404).send("Book Not Found"); | ||
bookToUpdate.title = req.body.title; | ||
bookToUpdate.author = req.body.author; | ||
res.send('ok') | ||
} | ||
|
||
const deleteBook = (req, res) => { | ||
const id = req.params.id; | ||
const bookToDelete = data.find(book => book.id === id); | ||
if (!bookToDelete) return res.status(404).send("Book Not Found"); | ||
data.splice(data.indexOf(bookToDelete), 1); | ||
} | ||
|
||
app.listen(PORT, () => console.log(`Listening on port ${PORT}`)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good checking of edge cases.