├── .gitignore ├── draft.txt ├── package.json ├── helpers └── args.js ├── services ├── log.service.js ├── storage.service.js └── api.service.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /draft.txt: -------------------------------------------------------------------------------- 1 | const getIcon = (icon) => { 2 | switch (icon.slice(0, -1)) { 3 | case '01': 4 | return '☀️'; 5 | case '02': 6 | return '🌤️'; 7 | case '03': 8 | return '☁️'; 9 | case '04': 10 | return '☁️'; 11 | case '09': 12 | return '🌧️'; 13 | case '10': 14 | return '🌦️'; 15 | case '11': 16 | return '🌩️'; 17 | case '13': 18 | return '❄️'; 19 | case '50': 20 | return '🌫️'; 21 | } 22 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "weather-cli", 3 | "version": "1.0.0", 4 | "description": "weather-cli project", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node index.js", 9 | "dev": "nodemon index.js" 10 | }, 11 | "author": "Samar Badriddinov", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "nodemon": "^2.0.20" 15 | }, 16 | "dependencies": { 17 | "axios": "^1.2.1", 18 | "chalk": "^5.2.0", 19 | "dedent-js": "^1.0.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /helpers/args.js: -------------------------------------------------------------------------------- 1 | const getArgs = args => { 2 | const res = {} 3 | const [executer, file, ...rest] = args 4 | rest.forEach((value, index, array) => { 5 | if (value.charAt(0) == '-') { 6 | if (index == array.length - 1) { 7 | res[value.substring(1)] = true 8 | } else if (array[index + 1].charAt(0) != '-') { 9 | res[value.substring(1)] = array[index + 1] 10 | } else { 11 | res[value.substring(1)] = true 12 | } 13 | } 14 | }) 15 | 16 | return res 17 | } 18 | 19 | export default getArgs 20 | -------------------------------------------------------------------------------- /services/log.service.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import dedent from 'dedent-js' 3 | 4 | const printError = error => { 5 | console.log(chalk.bgRed('ERROR') + ' ' + error) 6 | } 7 | 8 | const printSuccess = message => { 9 | console.log(chalk.bgGreen('SUCCESS') + ' ' + message) 10 | } 11 | 12 | const printHelp = () => { 13 | console.log(dedent` 14 | ${chalk.bgCyan('HELP')} 15 | -s [CITY] for install city 16 | -h for help 17 | -t [API_KEY] for saving token 18 | `) 19 | } 20 | 21 | const printWeather = (response, icon) => { 22 | console.log(dedent` 23 | ${chalk.bgYellowBright('WEATHER')} City weather ${response.name} 24 | ${icon} ${response.weather[0].description} 25 | Temperature: ${response.main.temp} (feels like ${response.main.feels_like}) 26 | Humidity: ${response.main.humidity}% 27 | Wind speed: ${response.wind.speed} 28 | `) 29 | } 30 | 31 | export {printError, printSuccess, printHelp, printWeather} 32 | -------------------------------------------------------------------------------- /services/storage.service.js: -------------------------------------------------------------------------------- 1 | import os from 'os' 2 | import path from 'path' 3 | import fs from 'fs' 4 | 5 | const filePath = path.join(os.homedir(), 'weather-data.json') 6 | 7 | const TOKEN_DICTIONARY = { 8 | token: 'token', 9 | city: 'city', 10 | } 11 | 12 | const saveKeyValue = async (key, value) => { 13 | let data = {} 14 | 15 | if (await isExist(filePath)) { 16 | const file = await fs.promises.readFile(filePath) 17 | data = JSON.parse(file) 18 | } 19 | 20 | data[key] = value 21 | await fs.promises.writeFile(filePath, JSON.stringify(data)) 22 | } 23 | 24 | const getKeyValue = async key => { 25 | if (await isExist(filePath)) { 26 | const file = await fs.promises.readFile(filePath) 27 | const data = JSON.parse(file) 28 | return data[key] 29 | } 30 | 31 | return undefined 32 | } 33 | 34 | const isExist = async path => { 35 | try { 36 | await fs.promises.stat(path) 37 | return true 38 | } catch (error) { 39 | return false 40 | } 41 | } 42 | 43 | export {saveKeyValue, getKeyValue, TOKEN_DICTIONARY} 44 | -------------------------------------------------------------------------------- /services/api.service.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import {getKeyValue, TOKEN_DICTIONARY} from './storage.service.js' 3 | 4 | const getIcon = icon => { 5 | switch (icon.slice(0, -1)) { 6 | case '01': 7 | return '☀️' 8 | case '02': 9 | return '🌤️' 10 | case '03': 11 | return '☁️' 12 | case '04': 13 | return '☁️' 14 | case '09': 15 | return '🌧️' 16 | case '10': 17 | return '🌦️' 18 | case '11': 19 | return '🌩️' 20 | case '13': 21 | return '❄️' 22 | case '50': 23 | return '🌫️' 24 | } 25 | } 26 | 27 | const getWeather = async city => { 28 | const token = process.env.TOKEN ?? (await getKeyValue(TOKEN_DICTIONARY.token)) 29 | if (!token) { 30 | throw new Error("API doesn't exist, -t [API_KEY] for saving token") 31 | } 32 | 33 | const {data} = await axios.get('https://api.openweathermap.org/data/2.5/weather', { 34 | params: { 35 | q: city, 36 | appid: token, 37 | lang: 'en', 38 | units: 'metric', 39 | }, 40 | }) 41 | 42 | return data 43 | } 44 | 45 | export {getWeather, getIcon} 46 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import getArgs from './helpers/args.js' 2 | import {getIcon, getWeather} from './services/api.service.js' 3 | import {printError, printSuccess, printHelp, printWeather} from './services/log.service.js' 4 | import {getKeyValue, saveKeyValue, TOKEN_DICTIONARY} from './services/storage.service.js' 5 | 6 | const saveToken = async token => { 7 | if (!token.length) { 8 | printError("Token doesn't exist") 9 | return 10 | } 11 | try { 12 | await saveKeyValue(TOKEN_DICTIONARY.token, token) 13 | printSuccess('Token was saved') 14 | } catch (error) { 15 | printError(error.message) 16 | } 17 | } 18 | 19 | const saveCity = async city => { 20 | if (!city.length) { 21 | printError("City doesn't exist") 22 | return 23 | } 24 | try { 25 | await saveKeyValue(TOKEN_DICTIONARY.city, city) 26 | printSuccess('City was saved') 27 | } catch (error) { 28 | printError(error.message) 29 | } 30 | } 31 | 32 | const getForcast = async () => { 33 | try { 34 | const city = process.env.CITY ?? (await getKeyValue(TOKEN_DICTIONARY.city)) 35 | const response = await getWeather(city) 36 | printWeather(response, getIcon(response.weather[0].icon)) 37 | } catch (error) { 38 | if (error?.response?.status == 404) { 39 | printError('City not found') 40 | } else if (error?.response?.status == 401) { 41 | printError('Invalid token') 42 | } else { 43 | printError(error.message) 44 | } 45 | } 46 | } 47 | 48 | const startCLI = () => { 49 | const args = getArgs(process.argv) 50 | if (args.h) { 51 | return printHelp() 52 | } 53 | if (args.s) { 54 | return saveCity(args.s) 55 | } 56 | if (args.t) { 57 | return saveToken(args.t) 58 | } 59 | return getForcast() 60 | } 61 | 62 | startCLI() 63 | --------------------------------------------------------------------------------