Skip to content

Assignments week 2 Nikita #12

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions hackyourtemperature/__tests__/app.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import app from '../app.js'
import supertest from 'supertest'
import fetch from 'node-fetch'

jest.mock('node-fetch', () => jest.fn())

const request = supertest(app)

describe('POST /', () => {
it('returns temperature for a valid city', async () => {
const fakeGeoData = [{ lat: 52.37, lon: 4.89 }]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of mocking systems!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot! :)

const fakeWeatherData = {
main: {
temp: 283.15,
},
}

fetch
.mockResolvedValueOnce({
json: async () => fakeGeoData,
})
.mockResolvedValueOnce({
json: async () => fakeWeatherData,
})

const response = await request.post('/').send({ cityName: 'Amsterdam' })

expect(response.statusCode).toBe(200)
expect(response.body).toEqual({
cityName: 'Amsterdam',
temperature: '10.0',
})
})

it('returns error message for unknown city', async () => {
fetch.mockResolvedValueOnce({
json: async () => [],
})

const response = await request.post('/').send({ cityName: 'FakeCityXYZ' })

expect(response.statusCode).toBe(200)
expect(response.body).toBe('City is not found')
})
})
48 changes: 48 additions & 0 deletions hackyourtemperature/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import express from 'express'
import { engine } from 'express-handlebars'
import fetch from 'node-fetch'
import { API_KEY } from './sources/keys.js'


const app = express()

app.engine('handlebars', engine())
app.set('view engine', 'handlebars')
app.set('views', './views')

app.get('/', (req, res) => {
res.render('home')
})

app.use(express.json())

app.post('/', async (req, res) => {
try {
const cityName = req.body.cityName
const geoData = await fetch(
`http://api.openweathermap.org/geo/1.0/direct?q=${cityName}&limit=1&appid=${API_KEY}`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever you have to write domains multiple times, it's good to turn them into variables/constants so they can be switched out, and so you can make sure theirs no typos in the project. e.g.
openWeathMapApi = "http://api.openweathermap.org"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!

)
const geoResponse = await geoData.json()
console.log(geoResponse)
if (geoResponse.length == 0) {
throw new Error('City is not found')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is best not to use errors in "normal" functionality (a user typing a city that doesn't exist is usual behaviour). it is best to just write here:
res.status(404);
res.send({ weatherText: "City is not found!" });
return;
as this would also allow us to set specific messages/status based on the errors.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! Makes sense
I’ll change it

}

const lat = geoResponse[0].lat
const lon = geoResponse[0].lon
console.log(lat, lon)

const weatherData = await fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}`
)
const weatherResponse = await weatherData.json()
res.json({
cityName: cityName,
temperature: (weatherResponse.main.temp - 273.15).toFixed(1)
});
} catch (err) {
res.json(err.message)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best to null check this, as in theory you can "throw" anything, (even though it should be an Error by convention).

e.g the line "throw 0;" is valid, even if silly/should not be used.

Therfore, we should update this to res.json(err?.message ?? 'unknownError'). incase of null/undefined messages.

Also, when we return an error, it's good to set a different status (i.e. not 2XX) response codes, https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status A full list can be found here, but the common ones are 404 - not found, 403 - forbidden/ not logged in, 500 - server error, so 500 is a good catch all for unexpected errors like this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thnx, Edward! I’ll add the null check and set the status to 500 for unknown errors

}
})

export default app
13 changes: 13 additions & 0 deletions hackyourtemperature/babel.config.cjs
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",
},
},
],
],
};
8 changes: 8 additions & 0 deletions hackyourtemperature/jest.config.js
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: [],
};
25 changes: 25 additions & 0 deletions hackyourtemperature/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "hackyourtemperature",
"version": "1.0.0",
"main": "server.js",
"type": "module",
"scripts": {
"test": "jest",
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@babel/preset-env": "^7.27.2",
"babel-jest": "^29.7.0",
"express": "^5.1.0",
"express-handlebars": "^8.0.3",
"jest": "^29.7.0",
"node-fetch": "^3.3.2",
"nodemon": "^3.1.10",
"supertest": "^7.1.0"
}
}
5 changes: 5 additions & 0 deletions hackyourtemperature/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import app from './app.js'

app.listen(3000, () => {
console.log(`server started on http://localhost:3000`)
})
1 change: 1 addition & 0 deletions hackyourtemperature/sources/keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const API_KEY = '7d47d5f41dcdf3b026655cb15bf3ff0a'

Choose a reason for hiding this comment

The 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:
-> Ignoring this file in the .gitignore.
-> adding a .env file (and ignoring it) - This is better, as the code will still compile, and the .env file is a standard across a lot of software/programs, so loading them into the program at runtime is handleded well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're totally right — I’ll move the key to a .env file and add it to .gitignore.
even github itself was not satisfied with my pullrequest -_-

Empty file.
1 change: 1 addition & 0 deletions hackyourtemperature/views/layouts/main.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Hello from backend to frontend!</h1>
43 changes: 36 additions & 7 deletions week1/prep-exercises/1-web-server/server.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
/**
* Exercise 3: Create an HTTP web server
*/

const http = require('http');
const fs = require('fs').promises
const path = require('path')
const http = require('http')

//create a server
let server = http.createServer(function (req, res) {
let server = http.createServer(async function (req, res) {
// YOUR CODE GOES IN HERE
res.write('Hello World!'); // Sends a response back to the client
res.end(); // Ends the response
});
try {
if (req.url === '/') {
const data = await fs.readFile(path.join(__dirname, 'index.html'))
res.writeHead(200, {
'Content-Type': 'text/html',
})
res.end(data)
} else if (req.url === '/index.js') {
const data = await fs.readFile(path.join(__dirname, 'index.js'))
res.writeHead(200, {
'Content-Type': 'application/javascript',
})
res.end(data)
} else {
res.writeHead(404, {
'Content-Type': 'text/plain',
})
res.end('404 not found')
}
} catch (err) {
res.writeHead(500, {
'Content-Type': 'text/plain',
})
res.end('server error')
console.error(err)
}
// res.write('Hello World!') // Sends a response back to the client
// res.end() // Ends the response
})

server.listen(3000); // The server starts to listen on port 3000
server.listen(3000, () => {
console.log('server is running (listen on port 3000)')
}) // The server starts to listen on port 3000
16 changes: 16 additions & 0 deletions week2/practice-exercises/1-joke-api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "1-joke-api",
"type": "module",
"version": "1.0.0",
"description": "Did you know that there is an API for Chuck Norris jokes? That's incredible, right!?",
"main": "script.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"node-fetch": "^3.3.2"
}
}
23 changes: 15 additions & 8 deletions week2/practice-exercises/1-joke-api/script.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
/**
* 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.
*/
import fetch from 'node-fetch'

function printChuckNorrisJoke() {
// YOUR CODE GOES IN HERE

async function printChuckNorrisJoke() {
try {
const response = await fetch('https://api.chucknorris.io/jokes/random')
// const body = await response.text()
const data = await response.json()
// console.log(response);
console.log(data.value)
} catch (err) {
console.error(err)
}
}

printChuckNorrisJoke();
printChuckNorrisJoke()
1 change: 1 addition & 0 deletions week2/prep-exercises/1-blog-API/My first blog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Lorem ipsum
5 changes: 3 additions & 2 deletions week2/prep-exercises/1-blog-API/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
"start": "nodemon server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
"express": "^4.17.1",
"nodemon": "^3.1.10"
}
}
62 changes: 58 additions & 4 deletions week2/prep-exercises/1-blog-API/server.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,64 @@
const express = require('express')
const app = express();

const app = express()
const fs = require('fs')

app.use(express.json()) // to read requests format in JSON

app.post('/blogs', (req, res) => {
// const content = req.body.content
// const title = req.body.title
const { title, content } = req.body
fs.writeFileSync(title, content)
res.end('ok')
})

app.put('/posts/:title', (req, res) => {
// console.log(req.query)
const { title, content } = req.body
if (title || content) {
if (fs.existsSync(title)) {
fs.writeFileSync(title, content)
res.end('ok')
} else {
res.end('This post does not exist!')
}
} else {
res.end('the request does not have a title and/or content')
}
})

app.delete('/blogs/:title', (req, res) => {
// console.log(req.params.title)
const title = req.params.title
if (fs.existsSync(title)) {
fs.unlinkSync(title);
res.end('ok');
} else {
res.end('This blog does not exist!')
}
})

app.get('/blogs/:title', (req, res) => {

// How to get the title from the url parameters?
const title = req.params.title
// check if post exists
if (fs.existsSync(title)) {
const post = fs.readFileSync(title);
res.end(`Your post content is: ${post}`)
} else {
res.end('This post does not exist!')
}


// send response
})

// YOUR CODE GOES IN HERE
app.get('/', function (req, res) {
res.send('Hello World')
})

app.listen(3000)

app.listen(3000, () => {
console.log(`server started on http://localhost:3000`)
})