aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFridon <[email protected]>2022-06-20 17:47:11 +0400
committerFridon <[email protected]>2022-06-20 17:47:11 +0400
commita64258e7748134d4e0a114e0d893c139ad9c331b (patch)
treeb81edb45f8aca4d79530a52891ae9f5a998b0098 /src
parent82a2f864b8f676b575d075151d7a2db0fe663b51 (diff)
downloadcountryfetch-a64258e7748134d4e0a114e0d893c139ad9c331b.tar.xz
countryfetch-a64258e7748134d4e0a114e0d893c139ad9c331b.zip
refactor
Diffstat (limited to 'src')
-rw-r--r--src/app.ts22
-rw-r--r--src/countries.ts107
-rw-r--r--src/environment/environment.ts19
-rw-r--r--src/models/country.model.ts20
-rw-r--r--src/util/cache.ts39
5 files changed, 207 insertions, 0 deletions
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/[email protected]/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<Country[]> {
+ 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/[email protected]/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<CurrencyAbbr, CurrencyInfo>;
+ capital: string[];
+ flag: string;
+ population: number;
+ languages: Record<LangAbbr, string>;
+ 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/[email protected]/path/mod.ts";
+import { ensureDirSync } from "https://deno.land/[email protected]/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;
+ }
+ }
+}