diff --git a/web/weather-api/.gitignore b/web/weather-api/.gitignore new file mode 100644 index 0000000..2d7ec5c --- /dev/null +++ b/web/weather-api/.gitignore @@ -0,0 +1,2 @@ +.env +node_modules/ diff --git a/web/weather-api/README.md b/web/weather-api/README.md new file mode 100644 index 0000000..853a082 --- /dev/null +++ b/web/weather-api/README.md @@ -0,0 +1,75 @@ +# 🌦 Weather CLI App + +A simple Node.js command-line application that fetches and displays real-time weather data using the [OpenWeatherMap API](https://openweathermap.org/api). + +--- + +## 📋 Features + +- Fetches live weather for any city +- Displays temperature, weather condition, humidity, and wind speed +- Handles errors (invalid city, bad API key, network issues) +- Uses environment variables to protect your API key +- Built with async/await — no callbacks + +--- + +## 🖥 Usage + +```bash +node index.js +``` + +**Examples:** + +```bash +node index.js London +node index.js "New York" +node index.js Tokyo +``` + +**Sample output:** + +``` + + 🌍 London, GB + + 🌡 Temperature : 18°C (feels like 16°C) + 🌤 Weather : Clear sky + 💧 Humidity : 60% + 💨 Wind Speed : 3.5 m/s +``` + +--- + +## ⚠️ Error Handling + +| Scenario | Message | +| ----------------- | -------------------------------------------------------------------- | +| No city provided | `Usage: node index.js ` | +| Invalid city name | `City "example" not found. Please check the city name.` | +| Invalid API key | `Invalid API key. Please check your OPENWEATHER_API_KEY.` | +| Network failure | `Network error: Unable to reach the weather API.` | +| Missing API key | `Missing API key. Please set OPENWEATHER_API_KEY in your .env file.` | + +--- + +## 📁 Project Structure + +``` +weather-cli/ +├── index.js # Main application logic +├── .env # The API key (hadi lazm tpushiha XD) +├── package.json # Project metadata and dependencies +└── README.md # This file +``` + +--- + +## 📦 Dependencies + +| Package | Purpose | +| -------- | --------------------------------------- | +| `dotenv` | Loads environment variables from `.env` | +| | | + diff --git a/web/weather-api/index.js b/web/weather-api/index.js new file mode 100644 index 0000000..e20894c --- /dev/null +++ b/web/weather-api/index.js @@ -0,0 +1,71 @@ +import "dotenv/config"; + +const API_KEY = process.env.OPENWEATHER_API_KEY; +const BASE_URL = "https://api.openweathermap.org/data/2.5/weather"; + +async function getWeather(city) { + if (!API_KEY) { + throw new Error( + "Missing API key." + ); + } + + const url = `${BASE_URL}?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=metric`; + + const response = await fetch(url); + const data = await response.json(); + + if (!response.ok) { + if (response.status === 401) { + throw new Error("Invalid API key. Please check your OPENWEATHER_API_KEY."); // 401 Unauthorized when the API key is invalid or missing + } + if (response.status === 404) { // 404 Not Found when the city does not exist + throw new Error(`City "${city}" not found. Please check the city name.`); + } + throw new Error(`API error (${response.status}): ${data.message}`); // Handle other errors like 500...ect + } + + return data; +} + +function displayWeather(data) { + const city = data.name; + const country = data.sys.country; + const temp = Math.round(data.main.temp); + const feelsLike = Math.round(data.main.feels_like); + const description = + data.weather[0].description.charAt(0).toUpperCase() + + data.weather[0].description.slice(1); + const humidity = data.main.humidity; + const windSpeed = data.wind.speed; + + console.log(` 🌍 ${city}, ${country} \n`); + console.log(` 🌡 Temperature : ${temp}°C (feels like ${feelsLike}°C)`); + console.log(` 🌤 Weather : ${description}`); + console.log(` 💧 Humidity : ${humidity}%`); + console.log(` 💨 Wind Speed : ${windSpeed} m/s`); +} + +async function main() { + const city = process.argv[2]; + + if (!city) { + console.error("Usage: node index.js "); + console.error("Example: node index.js London"); + process.exit(1); + } + + try { + const data = await getWeather(city); + displayWeather(data); + } catch (error) { + if (error.code === "ENOTFOUND" || error.code === "ECONNREFUSED") { + console.error("Network error: Unable to reach the weather API. Check your internet connection."); + } else { + console.error("Something went wrong:", error.message); + } + process.exit(1); + } +} + +main(); \ No newline at end of file diff --git a/web/weather-api/package-lock.json b/web/weather-api/package-lock.json new file mode 100644 index 0000000..1a4c50d --- /dev/null +++ b/web/weather-api/package-lock.json @@ -0,0 +1,120 @@ +{ + "name": "weather-cli", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "weather-cli", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^16.4.5", + "node-fetch": "^3.3.2" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/web/weather-api/package.json b/web/weather-api/package.json new file mode 100644 index 0000000..ee871a9 --- /dev/null +++ b/web/weather-api/package.json @@ -0,0 +1,22 @@ +{ + "name": "weather-cli", + "version": "1.0.0", + "description": "A Node.js CLI app that fetches and displays weather data.", + "main": "index.js", + "type": "module", + "scripts": { + "start": "node index.js" + }, + "keywords": [ + "weather", + "cli", + "openweathermap", + "nodejs" + ], + "author": "", + "license": "ISC", + "dependencies": { + "dotenv": "^16.4.5", + "node-fetch": "^3.3.2" + } +}