From a64258e7748134d4e0a114e0d893c139ad9c331b Mon Sep 17 00:00:00 2001 From: Fridon Date: Mon, 20 Jun 2022 17:47:11 +0400 Subject: refactor --- src/app.ts | 22 +++++++++ src/countries.ts | 107 +++++++++++++++++++++++++++++++++++++++++ src/environment/environment.ts | 19 ++++++++ src/models/country.model.ts | 20 ++++++++ src/util/cache.ts | 39 +++++++++++++++ 5 files changed, 207 insertions(+) create mode 100644 src/app.ts create mode 100644 src/countries.ts create mode 100644 src/environment/environment.ts create mode 100644 src/models/country.model.ts create mode 100644 src/util/cache.ts (limited to 'src') diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..87aa8bb --- /dev/null +++ b/src/app.ts @@ -0,0 +1,22 @@ +import { Countries } from "./countries.ts"; +import { Cache } from "./util/cache.ts"; + +export async function app() { + const countries = new Countries(new Cache()); + + await countries.sync(); + + const arg = Deno.args[0]; + + if (arg === "sync") { + await countries.sync({ force: true }); + } + + if (arg === "random") { + countries.print(countries.random()); + } + + if (arg === "find") { + countries.print(Deno.args[1]); + } +} diff --git a/src/countries.ts b/src/countries.ts new file mode 100644 index 0000000..895a93f --- /dev/null +++ b/src/countries.ts @@ -0,0 +1,107 @@ +import * as nano from "https://deno.land/x/nanocolors@0.1.12/mod.ts"; +import { environment } from "./environment/environment.ts"; +import { Country } from "./models/country.model.ts"; +import { Cache } from "./util/cache.ts"; + +export class Countries { + list: Country[] = []; + names: string[] = []; + query = environment.queries; + + constructor(private cache: Cache) {} + + async sync(config?: { force: boolean }): Promise { + const lastSynced = this.cache.readTxt("last-synced"); + const savedCountries = this.cache.readJson("countries") as + | Country[] + | undefined; + const week = environment.syncInterval * 23 * 60 * 60 * 1000; + + const shouldSync = + !savedCountries || + !lastSynced || + config?.force || + Date.now() - Number(lastSynced) > week; + + if (shouldSync) { + console.log( + nano.cyan("Syncronizing countries database..."), + config?.force + ? "" + : `\nThis will only happen every ${environment.syncInterval} days` + ); + + // Fetch and parse countries data from API + const response = await fetch(environment.baseUrl + this.query); + const countries = (await response.json()) as Country[]; + + this.list = countries; + this.cache.saveJson("countries", countries); + this.cache.saveTxt("last-synced", JSON.stringify(Date.now())); + + console.log("Synced successfully"); + } else { + this.list = savedCountries; + } + this.names = this.list.map((c) => c.name.common); + return this.list; + } + + find(name: string): Country { + // Replace snake case or kebab case with whitespaces + name = name.toLowerCase().replace(/-|_/g, " "); + + // Find exact match first, then fall back to fuzzy match + const country = this.list.find((c) => { + const countryName = c.name.common.toLocaleLowerCase(); + return countryName === name || countryName.includes(name); + }); + + if (!country) { + throw Error(`Cannot find country named ${name}`); + } + + return country; + } + + print(name: string) { + const country = this.find(name); + let currencies = []; + let iteration = 0; + for (const currencyAbbr in country.currencies) { + iteration++; + const currency = country.currencies[currencyAbbr]; + currencies.push( + `${iteration > 1 ? "\t\t " : ""}${currency.name} [${ + currency.symbol + }](${currencyAbbr})\n` + ); + } + + let languages = []; + for (const langAbbr in country.languages) { + languages.push(country.languages[langAbbr] + " "); + } + + console.log( + nano.cyan("\nCountry:\t"), + country.flag, + country.name.common, + nano.green("\nLanguages:\t"), + ...languages, + nano.green("\nCapital:\t"), + country.capital[0], + nano.green("\nRegion:\t\t"), + country.region, + nano.green("\nPopulation:\t"), + country.population.toLocaleString(), + nano.green("\nCurrencies:\t"), + ...currencies + ); + } + + random(): string { + const randomNum = Math.floor(Math.random() * this.names.length); + return this.names[randomNum]; + } +} diff --git a/src/environment/environment.ts b/src/environment/environment.ts new file mode 100644 index 0000000..0e29225 --- /dev/null +++ b/src/environment/environment.ts @@ -0,0 +1,19 @@ +import home_dir from "https://deno.land/x/dir/home_dir/mod.ts"; +import { join } from "https://deno.land/std@0.144.0/path/mod.ts"; + +export const environment = { + baseUrl: "https://restcountries.com/v3.1/", + syncInterval: 7, + cacheDir: join(home_dir() as string, ".cache", "countryfetch"), + queries: + "all?fields=" + + "name," + + "capital," + + "currencies," + + "population," + + "flag," + + "region," + + "continent," + + "languages," + + "region", +}; diff --git a/src/models/country.model.ts b/src/models/country.model.ts new file mode 100644 index 0000000..4765d5e --- /dev/null +++ b/src/models/country.model.ts @@ -0,0 +1,20 @@ +// For better currency types, this can be used later: +// https://github.com/freeall/currency-codes +// Same can be done for language abbreviations +export type CurrencyAbbr = string; +export type CurrencyInfo = { name: string; symbol: string }; +export type LangAbbr = string; + +export interface Country { + name: { + common: string; + official: string; + // Need type for nativeName later + }; + currencies: Record; + capital: string[]; + flag: string; + population: number; + languages: Record; + region: string; +} diff --git a/src/util/cache.ts b/src/util/cache.ts new file mode 100644 index 0000000..3a0c5d4 --- /dev/null +++ b/src/util/cache.ts @@ -0,0 +1,39 @@ +import { join } from "https://deno.land/std@0.144.0/path/mod.ts"; +import { ensureDirSync } from "https://deno.land/std@0.78.0/fs/mod.ts"; +import { environment } from "../environment/environment.ts"; + +export class Cache { + path = environment.cacheDir; + + public saveJson(name: string, data: {}) { + ensureDirSync(this.path); + + Deno.writeTextFileSync( + join(this.path, `${name}.json`), + JSON.stringify(data) + ); + } + + public saveTxt(name: string, value: string) { + ensureDirSync(this.path); + Deno.writeTextFileSync(join(this.path, `${name}.txt`), value); + } + + public readJson(name: string): {} | [] | undefined { + let data; + try { + data = Deno.readTextFileSync(join(this.path, `${name}.json`)); + } catch (err) { + return undefined; + } + return JSON.parse(data); + } + + public readTxt(name: string) { + try { + return Deno.readTextFileSync(join(this.path, `${name}.txt`)); + } catch (err) { + return undefined; + } + } +} -- cgit v1.2.3