first commit
This commit is contained in:
commit
942b698dd0
25
.eslintrc.js
Normal file
25
.eslintrc.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir : __dirname,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
ignorePatterns: ['.eslintrc.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
},
|
||||||
|
};
|
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
.gntmp*
|
||||||
|
/geobase
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
}
|
77
README.md
Normal file
77
README.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||||
|
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||||
|
|
||||||
|
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||||
|
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||||
|
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||||
|
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||||
|
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||||
|
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||||
|
</p>
|
||||||
|
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
|
||||||
|
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the app
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker run --rm --name geobase-db -p 22336:3306 -v $PWD/geobase:/var/lib/mysql --env MARIADB_USER=geobase --env MARIADB_PASSWORD=geobase --env MARIADB_ROOT_PASSWORD=geobase --env MARIADB_DATABASE=geobase mariadb:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# development
|
||||||
|
$ npm run start
|
||||||
|
|
||||||
|
# watch mode
|
||||||
|
$ npm run start:dev
|
||||||
|
|
||||||
|
# production mode
|
||||||
|
$ npm run start:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# unit tests
|
||||||
|
$ npm run test
|
||||||
|
|
||||||
|
# e2e tests
|
||||||
|
$ npm run test:e2e
|
||||||
|
|
||||||
|
# test coverage
|
||||||
|
$ npm run test:cov
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||||
|
|
||||||
|
## Stay in touch
|
||||||
|
|
||||||
|
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
||||||
|
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||||
|
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Nest is [MIT licensed](LICENSE).
|
31
example.mjs
Normal file
31
example.mjs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import { pipeline } from 'stream/promises';
|
||||||
|
import { join } from 'path';
|
||||||
|
import unzipper from 'unzipper';
|
||||||
|
|
||||||
|
const GEONAMES_DUMP =
|
||||||
|
'https://download.geonames.org/export/dump/allCountries.zip';
|
||||||
|
|
||||||
|
async function workDirectory() {
|
||||||
|
const path = await fs.promises.mkdtemp(join(process.cwd(), '.gntmp-'));
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
remove: async () => {
|
||||||
|
await fs.promises.rm(path, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadGeodump({ path }) {
|
||||||
|
const outfile = join(path, 'out.txt');
|
||||||
|
const httpStream = await fetch(GEONAMES_DUMP);
|
||||||
|
pipeline(httpStream.body, unzipper.Extract({ path }));
|
||||||
|
return outfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function test() {
|
||||||
|
const wd = await workDirectory();
|
||||||
|
await downloadGeodump(wd);
|
||||||
|
}
|
||||||
|
|
||||||
|
test().catch(console.error);
|
5
nest-cli.json
Normal file
5
nest-cli.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src"
|
||||||
|
}
|
17027
package-lock.json
generated
Normal file
17027
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
79
package.json
Normal file
79
package.json
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"name": "evert-earth-utils",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"prebuild": "rimraf dist",
|
||||||
|
"build": "nest build",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"start": "nest start",
|
||||||
|
"start:dev": "nest start --watch",
|
||||||
|
"start:debug": "nest start --debug --watch",
|
||||||
|
"start:prod": "node dist/main",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:cov": "jest --coverage",
|
||||||
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/common": "^9.0.0",
|
||||||
|
"@nestjs/core": "^9.0.0",
|
||||||
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
|
"@nestjs/typeorm": "^9.0.1",
|
||||||
|
"mysql2": "^2.3.3",
|
||||||
|
"nestjs-command": "^3.1.2",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"rxjs": "^7.2.0",
|
||||||
|
"sqlite": "^4.1.2",
|
||||||
|
"sqlite3": "^5.1.4",
|
||||||
|
"typeorm": "^0.3.11",
|
||||||
|
"unzipper": "^0.10.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^9.0.0",
|
||||||
|
"@nestjs/schematics": "^9.0.0",
|
||||||
|
"@nestjs/testing": "^9.0.0",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
|
"@types/jest": "28.1.4",
|
||||||
|
"@types/node": "^16.0.0",
|
||||||
|
"@types/supertest": "^2.0.11",
|
||||||
|
"@types/unzipper": "^0.10.5",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
|
"@typescript-eslint/parser": "^5.0.0",
|
||||||
|
"eslint": "^8.0.1",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
"jest": "28.1.2",
|
||||||
|
"prettier": "^2.3.2",
|
||||||
|
"source-map-support": "^0.5.20",
|
||||||
|
"supertest": "^6.1.3",
|
||||||
|
"ts-jest": "28.0.5",
|
||||||
|
"ts-loader": "^9.2.3",
|
||||||
|
"ts-node": "^10.0.0",
|
||||||
|
"tsconfig-paths": "4.0.0",
|
||||||
|
"typescript": "^4.3.5"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
}
|
22
src/app.controller.spec.ts
Normal file
22
src/app.controller.spec.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
|
describe('AppController', () => {
|
||||||
|
let appController: AppController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const app: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
appController = app.get<AppController>(AppController);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('root', () => {
|
||||||
|
it('should return "Hello World!"', () => {
|
||||||
|
expect(appController.getHello()).toBe('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
12
src/app.controller.ts
Normal file
12
src/app.controller.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class AppController {
|
||||||
|
constructor(private readonly appService: AppService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
getHello(): string {
|
||||||
|
return this.appService.getHello();
|
||||||
|
}
|
||||||
|
}
|
31
src/app.module.ts
Normal file
31
src/app.module.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
import { CommandModule } from 'nestjs-command';
|
||||||
|
import { GeonamesModule } from './modules/geonames/geonames.module';
|
||||||
|
import { Geoname } from './modules/geonames/geonames.entity';
|
||||||
|
import { Country } from './modules/countries/countries.entity';
|
||||||
|
import { CountriesModule } from './modules/countries/countries.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
type: 'mysql',
|
||||||
|
host: 'localhost',
|
||||||
|
port: 22336,
|
||||||
|
username: 'geobase',
|
||||||
|
password: 'geobase',
|
||||||
|
database: 'geobase',
|
||||||
|
name: 'geobase',
|
||||||
|
entities: [Geoname, Country],
|
||||||
|
synchronize: true,
|
||||||
|
}),
|
||||||
|
CommandModule,
|
||||||
|
GeonamesModule,
|
||||||
|
CountriesModule,
|
||||||
|
],
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
8
src/app.service.ts
Normal file
8
src/app.service.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AppService {
|
||||||
|
getHello(): string {
|
||||||
|
return 'Hello World!';
|
||||||
|
}
|
||||||
|
}
|
18
src/cli.ts
Normal file
18
src/cli.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { CommandModule, CommandService } from 'nestjs-command';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.createApplicationContext(AppModule);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await app.select(CommandModule).get(CommandService).exec();
|
||||||
|
await app.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
await app.close();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap();
|
8
src/main.ts
Normal file
8
src/main.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.create(AppModule);
|
||||||
|
await app.listen(3000);
|
||||||
|
}
|
||||||
|
bootstrap();
|
16
src/modules/countries/countries.command.ts
Normal file
16
src/modules/countries/countries.command.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Command } from 'nestjs-command';
|
||||||
|
import { CountriesService } from './countries.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CountriesCommand {
|
||||||
|
constructor(private readonly service: CountriesService) {}
|
||||||
|
|
||||||
|
@Command({
|
||||||
|
command: 'countries:import',
|
||||||
|
describe: 'Import Countries database',
|
||||||
|
})
|
||||||
|
async import() {
|
||||||
|
await this.service.pullCountries();
|
||||||
|
}
|
||||||
|
}
|
23
src/modules/countries/countries.controller.ts
Normal file
23
src/modules/countries/countries.controller.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Controller, Get, Param, Query } from '@nestjs/common';
|
||||||
|
import { CountriesQueryDto } from './countries.interfaces';
|
||||||
|
import { CountriesService } from './countries.service';
|
||||||
|
|
||||||
|
@Controller({
|
||||||
|
path: '/countries',
|
||||||
|
})
|
||||||
|
export class CountriesController {
|
||||||
|
constructor(private readonly service: CountriesService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async getAllCountries(@Query() { q, fields }: CountriesQueryDto) {
|
||||||
|
return this.service.search(q, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':iso')
|
||||||
|
async getByISO(
|
||||||
|
@Param('iso') iso: string,
|
||||||
|
@Query() { fields }: CountriesQueryDto,
|
||||||
|
) {
|
||||||
|
return this.service.getByISO(iso.toUpperCase(), fields);
|
||||||
|
}
|
||||||
|
}
|
66
src/modules/countries/countries.entity.ts
Normal file
66
src/modules/countries/countries.entity.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Country {
|
||||||
|
@PrimaryColumn({ length: 2 })
|
||||||
|
iso: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ length: 3, nullable: true })
|
||||||
|
iso3: string;
|
||||||
|
|
||||||
|
@Column({ length: 3, nullable: true })
|
||||||
|
isoNumeric: string;
|
||||||
|
|
||||||
|
@Column({ length: 2, nullable: true })
|
||||||
|
fips: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column()
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column()
|
||||||
|
capital: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
area: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
population: number;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
continent: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
tld: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ nullable: true })
|
||||||
|
currencyCode: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
currencyName: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
phone: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
postalCodeFormat: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
postalCodeRegex: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
languages: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column()
|
||||||
|
geonameid: number;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
neighbours: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
equivalentFipsCode: string;
|
||||||
|
}
|
4
src/modules/countries/countries.interfaces.ts
Normal file
4
src/modules/countries/countries.interfaces.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface CountriesQueryDto {
|
||||||
|
fields?: string[];
|
||||||
|
q?: string;
|
||||||
|
}
|
15
src/modules/countries/countries.module.ts
Normal file
15
src/modules/countries/countries.module.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { CommandModule } from 'nestjs-command';
|
||||||
|
import { CountriesCommand } from './countries.command';
|
||||||
|
import { CountriesController } from './countries.controller';
|
||||||
|
import { Country } from './countries.entity';
|
||||||
|
import { CountriesService } from './countries.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [CommandModule, TypeOrmModule.forFeature([Country], 'geobase')],
|
||||||
|
controllers: [CountriesController],
|
||||||
|
providers: [CountriesService, CountriesCommand],
|
||||||
|
exports: [CountriesService, TypeOrmModule],
|
||||||
|
})
|
||||||
|
export class CountriesModule {}
|
116
src/modules/countries/countries.service.ts
Normal file
116
src/modules/countries/countries.service.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { ILike, Repository } from 'typeorm';
|
||||||
|
import { Country } from './countries.entity';
|
||||||
|
import { intOrNull } from 'src/utils/int-or-null';
|
||||||
|
|
||||||
|
const COUNTRIES_URL =
|
||||||
|
'https://download.geonames.org/export/dump/countryInfo.txt';
|
||||||
|
|
||||||
|
const ACCEPT_FIELDS = [
|
||||||
|
'iso',
|
||||||
|
'iso3',
|
||||||
|
'isoNumeric',
|
||||||
|
'fips',
|
||||||
|
'country',
|
||||||
|
'capital',
|
||||||
|
'area',
|
||||||
|
'population',
|
||||||
|
'continent',
|
||||||
|
'tld',
|
||||||
|
'currencyCode',
|
||||||
|
'currencyName',
|
||||||
|
'phone',
|
||||||
|
'postalCodeFormat',
|
||||||
|
'postalCodeRegex',
|
||||||
|
'languages',
|
||||||
|
'geonameid',
|
||||||
|
'neighbours',
|
||||||
|
'equivalentFipsCode',
|
||||||
|
];
|
||||||
|
@Injectable()
|
||||||
|
export class CountriesService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Country, 'geobase')
|
||||||
|
private countryRepository: Repository<Country>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private mapAllowedQuery(select: string[]) {
|
||||||
|
return (Array.isArray(select) ? select : [select]).filter((field) =>
|
||||||
|
ACCEPT_FIELDS.includes(field),
|
||||||
|
) as unknown as (keyof Country)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async pullCountries() {
|
||||||
|
const countryList = await fetch(COUNTRIES_URL).then((x) => x.text());
|
||||||
|
const entries = countryList
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => line && !line.startsWith('#'))
|
||||||
|
.map((line) => line.replace('\r', '').split('\t'))
|
||||||
|
.map((entry) => {
|
||||||
|
const country = new Country();
|
||||||
|
Object.assign(country, {
|
||||||
|
iso: entry[0],
|
||||||
|
iso3: entry[1],
|
||||||
|
isoNumeric: entry[2],
|
||||||
|
fips: entry[3],
|
||||||
|
country: entry[4],
|
||||||
|
capital: entry[5],
|
||||||
|
area: parseFloat(entry[6] || '0'),
|
||||||
|
population: intOrNull(entry[7]),
|
||||||
|
continent: entry[8],
|
||||||
|
tld: entry[9],
|
||||||
|
currencyCode: entry[10],
|
||||||
|
currencyName: entry[11],
|
||||||
|
phone: entry[12],
|
||||||
|
postalCodeFormat: entry[13],
|
||||||
|
postalCodeRegex: entry[14],
|
||||||
|
languages: entry[15],
|
||||||
|
geonameid: intOrNull(entry[16]),
|
||||||
|
neighbours: entry[17],
|
||||||
|
equivalentFipsCode: entry[18],
|
||||||
|
});
|
||||||
|
return country;
|
||||||
|
});
|
||||||
|
await this.countryRepository.save(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(fields = ACCEPT_FIELDS) {
|
||||||
|
const select = this.mapAllowedQuery(fields);
|
||||||
|
|
||||||
|
return this.countryRepository.find({
|
||||||
|
select,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(query?: string, fields = ACCEPT_FIELDS) {
|
||||||
|
const select = this.mapAllowedQuery(fields);
|
||||||
|
const filter = {};
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
filter['country'] = ILike(`%${query}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.countryRepository.find({
|
||||||
|
where: filter,
|
||||||
|
select,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByISO(iso: string, fields = ACCEPT_FIELDS) {
|
||||||
|
const select = this.mapAllowedQuery(fields);
|
||||||
|
|
||||||
|
const find = await this.countryRepository.findOne({
|
||||||
|
where: {
|
||||||
|
[iso.length === 2 ? 'iso' : 'iso3']: iso,
|
||||||
|
},
|
||||||
|
select,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!find) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return find;
|
||||||
|
}
|
||||||
|
}
|
2508
src/modules/geonames/feature.dictionary.ts
Normal file
2508
src/modules/geonames/feature.dictionary.ts
Normal file
File diff suppressed because it is too large
Load Diff
16
src/modules/geonames/geonames.command.ts
Normal file
16
src/modules/geonames/geonames.command.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Command } from 'nestjs-command';
|
||||||
|
import { GeonamesService } from './geonames.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GeonamesCommand {
|
||||||
|
constructor(private readonly service: GeonamesService) {}
|
||||||
|
|
||||||
|
@Command({
|
||||||
|
command: 'geonames:import',
|
||||||
|
describe: 'Import Geonames database',
|
||||||
|
})
|
||||||
|
async import() {
|
||||||
|
await this.service.runUpdateCycle();
|
||||||
|
}
|
||||||
|
}
|
15
src/modules/geonames/geonames.controller.ts
Normal file
15
src/modules/geonames/geonames.controller.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Controller, Get, Query } from '@nestjs/common';
|
||||||
|
import { GeonameQuery } from './geonames.interfaces';
|
||||||
|
import { GeonamesService } from './geonames.service';
|
||||||
|
|
||||||
|
@Controller({
|
||||||
|
path: 'geonames',
|
||||||
|
})
|
||||||
|
export class GeonamesController {
|
||||||
|
constructor(private readonly service: GeonamesService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async search(@Query() query: GeonameQuery) {
|
||||||
|
return this.service.search(query);
|
||||||
|
}
|
||||||
|
}
|
68
src/modules/geonames/geonames.entity.ts
Normal file
68
src/modules/geonames/geonames.entity.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Entity, Column, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Geoname {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
geonameid: number;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ length: 200 })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, length: 200 })
|
||||||
|
asciiname: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'text' })
|
||||||
|
alternatenames: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'real' })
|
||||||
|
latitude: number;
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'real' })
|
||||||
|
longitude: number;
|
||||||
|
|
||||||
|
// http://www.geonames.org/export/codes.html
|
||||||
|
@Index()
|
||||||
|
@Column({ type: 'char', length: 1 })
|
||||||
|
featureclass: string;
|
||||||
|
|
||||||
|
// http://www.geonames.org/export/codes.html
|
||||||
|
@Index()
|
||||||
|
@Column({ length: 10 })
|
||||||
|
featurecode: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ length: 2 })
|
||||||
|
countrycode: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, length: 200 })
|
||||||
|
cc2: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, length: 20 })
|
||||||
|
admin1code: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, length: 80 })
|
||||||
|
admin2code: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, length: 20 })
|
||||||
|
admin3code: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, length: 20 })
|
||||||
|
admin4code: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'bigint' })
|
||||||
|
population: number;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
elevation: number;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
dem: number;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ nullable: true })
|
||||||
|
timezone: string;
|
||||||
|
|
||||||
|
@Column({ type: 'date' })
|
||||||
|
moddate: string;
|
||||||
|
}
|
13
src/modules/geonames/geonames.interfaces.ts
Normal file
13
src/modules/geonames/geonames.interfaces.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Geoname } from './geonames.entity';
|
||||||
|
|
||||||
|
export interface WorkDirectory {
|
||||||
|
path: string;
|
||||||
|
remove(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GeonameQuery = Record<keyof Geoname, unknown> & {
|
||||||
|
fields?: (keyof Geoname)[];
|
||||||
|
limit?: string;
|
||||||
|
offset?: string;
|
||||||
|
q?: string;
|
||||||
|
};
|
15
src/modules/geonames/geonames.module.ts
Normal file
15
src/modules/geonames/geonames.module.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { CommandModule } from 'nestjs-command';
|
||||||
|
import { GeonamesCommand } from './geonames.command';
|
||||||
|
import { GeonamesController } from './geonames.controller';
|
||||||
|
import { Geoname } from './geonames.entity';
|
||||||
|
import { GeonamesService } from './geonames.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [CommandModule, TypeOrmModule.forFeature([Geoname], 'geobase')],
|
||||||
|
controllers: [GeonamesController],
|
||||||
|
providers: [GeonamesService, GeonamesCommand],
|
||||||
|
exports: [GeonamesService],
|
||||||
|
})
|
||||||
|
export class GeonamesModule {}
|
219
src/modules/geonames/geonames.service.ts
Normal file
219
src/modules/geonames/geonames.service.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { DataSource, ILike, In, Repository } from 'typeorm';
|
||||||
|
import { pipeline } from 'stream/promises';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { GeonameQuery, WorkDirectory } from './geonames.interfaces';
|
||||||
|
import { createInterface } from 'readline';
|
||||||
|
import { Geoname } from './geonames.entity';
|
||||||
|
import { intOrNull } from 'src/utils/int-or-null';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as unzipper from 'unzipper';
|
||||||
|
|
||||||
|
const GEONAMES_DUMP =
|
||||||
|
'https://download.geonames.org/export/dump/allCountries.zip';
|
||||||
|
|
||||||
|
const context = 'GeonamesImport';
|
||||||
|
|
||||||
|
const ACCEPT_FIELDS = [
|
||||||
|
'geonameid',
|
||||||
|
'name',
|
||||||
|
'asciiname',
|
||||||
|
'alternatenames',
|
||||||
|
'latitude',
|
||||||
|
'longitude',
|
||||||
|
'featureclass',
|
||||||
|
'featurecode',
|
||||||
|
'countrycode',
|
||||||
|
'cc2',
|
||||||
|
'admin1code',
|
||||||
|
'admin2code',
|
||||||
|
'admin3code',
|
||||||
|
'admin4code',
|
||||||
|
'population',
|
||||||
|
'elevation',
|
||||||
|
'dem',
|
||||||
|
'timezone',
|
||||||
|
'moddate',
|
||||||
|
];
|
||||||
|
|
||||||
|
type ReduceType = Partial<Record<keyof Geoname, unknown>>;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GeonamesService {
|
||||||
|
constructor(
|
||||||
|
@InjectDataSource('geobase')
|
||||||
|
private dataSource: DataSource,
|
||||||
|
@InjectRepository(Geoname, 'geobase')
|
||||||
|
private geonameRepository: Repository<Geoname>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private mapAllowedQuery(select: string[]) {
|
||||||
|
return (Array.isArray(select) ? select : [select]).filter((field) =>
|
||||||
|
ACCEPT_FIELDS.includes(field),
|
||||||
|
) as unknown as (keyof Geoname)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(params: GeonameQuery) {
|
||||||
|
const select = this.mapAllowedQuery(params.fields || ACCEPT_FIELDS);
|
||||||
|
let where = Object.keys(params)
|
||||||
|
.filter((key) => !['fields', 'limit', 'q', 'offset'].includes(key))
|
||||||
|
.reduce<ReduceType>(
|
||||||
|
(obj, key) =>
|
||||||
|
ACCEPT_FIELDS.includes(key)
|
||||||
|
? {
|
||||||
|
...obj,
|
||||||
|
[key]: Array.isArray(params[key])
|
||||||
|
? In(params[key])
|
||||||
|
: params[key],
|
||||||
|
}
|
||||||
|
: obj,
|
||||||
|
{},
|
||||||
|
) as ReduceType | ReduceType[];
|
||||||
|
|
||||||
|
if (params.q) {
|
||||||
|
let searchTerm = params.q;
|
||||||
|
if (searchTerm.startsWith('ext:')) {
|
||||||
|
searchTerm = searchTerm.substring(4);
|
||||||
|
where = ['name', 'asciiname', 'alternatenames'].map((field) => ({
|
||||||
|
...(where as ReduceType),
|
||||||
|
[field as keyof Geoname]: ILike(`%${searchTerm}%`),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
where['name'] = ILike(`%${searchTerm}%`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const take = Math.max(
|
||||||
|
Math.min(intOrNull(params.limit as string) || 50, 1000),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
const skip = intOrNull(params.offset as string) || 0;
|
||||||
|
return this.geonameRepository.find({
|
||||||
|
select,
|
||||||
|
where: where as unknown,
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async workDirectory(): Promise<WorkDirectory> {
|
||||||
|
const path = await fs.promises.mkdtemp(join(process.cwd(), '.gntmp-'));
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
remove: async () => {
|
||||||
|
await fs.promises.rm(path, { recursive: true, force: true });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadGeodump({ path }: WorkDirectory): Promise<string> {
|
||||||
|
const outfile = join(path, 'out.txt');
|
||||||
|
const output = fs.createWriteStream(outfile);
|
||||||
|
const httpStream = await fetch(GEONAMES_DUMP);
|
||||||
|
|
||||||
|
await pipeline(
|
||||||
|
httpStream.body as unknown as NodeJS.ReadableStream,
|
||||||
|
unzipper.Parse().on('entry', (entry) => {
|
||||||
|
if (entry.path === 'allCountries.txt') {
|
||||||
|
entry.pipe(output);
|
||||||
|
} else {
|
||||||
|
entry.autodrain();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return outfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseGeodump(path: string) {
|
||||||
|
const read = fs.createReadStream(path);
|
||||||
|
const rl = createInterface({
|
||||||
|
terminal: false,
|
||||||
|
input: read,
|
||||||
|
crlfDelay: Infinity,
|
||||||
|
});
|
||||||
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
|
await queryRunner.connect();
|
||||||
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
|
let entities = 0;
|
||||||
|
let totalBatches = 0;
|
||||||
|
let totalEntities = 0;
|
||||||
|
|
||||||
|
for await (const line of rl) {
|
||||||
|
const split = line.split('\t');
|
||||||
|
const model = new Geoname();
|
||||||
|
Object.assign(model, {
|
||||||
|
geonameid: parseInt(split[0], 10),
|
||||||
|
name: split[1],
|
||||||
|
asciiname: split[2],
|
||||||
|
alternatenames: split[3],
|
||||||
|
latitude: parseFloat(split[4]) || null,
|
||||||
|
longitude: parseFloat(split[5]) || null,
|
||||||
|
featureclass: split[6],
|
||||||
|
featurecode: split[7],
|
||||||
|
countrycode: split[8],
|
||||||
|
cc2: split[9],
|
||||||
|
admin1code: split[10] || null,
|
||||||
|
admin2code: split[11] || null,
|
||||||
|
admin3code: split[12] || null,
|
||||||
|
admin4code: split[13] || null,
|
||||||
|
population: intOrNull(split[14]),
|
||||||
|
elevation: intOrNull(split[15]),
|
||||||
|
dem: intOrNull(split[16]),
|
||||||
|
timezone: split[17],
|
||||||
|
moddate: split[18],
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryRunner.manager.save(model);
|
||||||
|
entities += 1;
|
||||||
|
totalEntities += 1;
|
||||||
|
|
||||||
|
if (entities >= 100) {
|
||||||
|
totalBatches += 1;
|
||||||
|
try {
|
||||||
|
await queryRunner.commitTransaction();
|
||||||
|
} catch (err) {
|
||||||
|
Logger.error(`Some fields failed to insert: ${err}`, context);
|
||||||
|
await queryRunner.rollbackTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalBatches % 10 === 0) {
|
||||||
|
Logger.log(
|
||||||
|
`Batch ${totalBatches} committed, ${totalEntities} entities so far`,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await queryRunner.startTransaction();
|
||||||
|
entities = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await queryRunner.commitTransaction();
|
||||||
|
await queryRunner.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
async runUpdateCycle() {
|
||||||
|
Logger.log('Starting geonames importer', context);
|
||||||
|
const workDirectory = await this.workDirectory();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Logger.log('Downloading dump...', context);
|
||||||
|
// const file = await this.downloadGeodump(workDirectory);
|
||||||
|
|
||||||
|
Logger.log('Creating database...', context);
|
||||||
|
await this.parseGeodump(
|
||||||
|
'/home/evert/Projects/evert-earth-utils/.gntmp-sof1ES/out.txt',
|
||||||
|
); //file);
|
||||||
|
} catch (e) {
|
||||||
|
await workDirectory.remove();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.log('Cleaning up', context);
|
||||||
|
await workDirectory.remove();
|
||||||
|
|
||||||
|
Logger.log('Done!', context);
|
||||||
|
}
|
||||||
|
}
|
6
src/utils/int-or-null.ts
Normal file
6
src/utils/int-or-null.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const intOrNull = (ref: string) => {
|
||||||
|
if (!ref) return null;
|
||||||
|
const parse = parseInt(ref, 10);
|
||||||
|
if (isNaN(parse)) return null;
|
||||||
|
return parse;
|
||||||
|
};
|
24
test/app.e2e-spec.ts
Normal file
24
test/app.e2e-spec.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import * as request from 'supertest';
|
||||||
|
import { AppModule } from './../src/app.module';
|
||||||
|
|
||||||
|
describe('AppController (e2e)', () => {
|
||||||
|
let app: INestApplication;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [AppModule],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
app = moduleFixture.createNestApplication();
|
||||||
|
await app.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('/ (GET)', () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get('/')
|
||||||
|
.expect(200)
|
||||||
|
.expect('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
9
test/jest-e2e.json
Normal file
9
test/jest-e2e.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"moduleFileExtensions": ["js", "json", "ts"],
|
||||||
|
"rootDir": ".",
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testRegex": ".e2e-spec.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
}
|
||||||
|
}
|
4
tsconfig.build.json
Normal file
4
tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||||
|
}
|
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "es2017",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"incremental": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"strictBindCallApply": false,
|
||||||
|
"forceConsistentCasingInFileNames": false,
|
||||||
|
"noFallthroughCasesInSwitch": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user