diff --git a/.eslintrc.js b/.eslintrc.js index c417de3..649d93b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,6 +18,7 @@ module.exports = { }, ignorePatterns: ['.eslintrc.js'], rules: { + '@typescript-eslint/no-invalid-void-type': ['error', { allowAsThisParameter: true }], '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off' diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts deleted file mode 100644 index 2552ec5..0000000 --- a/src/app.controller.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -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); - }); - - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); - }); - }); -}); diff --git a/src/app.controller.ts b/src/app.controller.ts deleted file mode 100644 index a325e8b..0000000 --- a/src/app.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -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(); - } -} diff --git a/src/app.module.ts b/src/app.module.ts index 30914da..fcc470a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,11 +1,36 @@ -import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; +import { createOsmClientWithCache } from './clients/geocoding'; +import { createOpenmeteoClientWithCache } from './clients/weather'; +import { + Module, + geocodingProviderDependency, + packagesRepositoryDependency, + packagesServiceDependency, + weatherProviderDependency, +} from './dependencies'; +import { PackagesController } from './packages.controller'; +import { PackagesService } from './packages.service'; +import { createSamplePackagesRepository } from './storage/samplePackagesRepository'; @Module({ - imports: [], - controllers: [AppController], - providers: [AppService], + controllers: [PackagesController], + providers: [ + { + provide: packagesServiceDependency, + useClass: PackagesService, + }, + { + provide: geocodingProviderDependency, + useValue: createOsmClientWithCache(), + }, + { + provide: weatherProviderDependency, + useValue: createOpenmeteoClientWithCache(), + }, + { + provide: packagesRepositoryDependency, + useFactory: createSamplePackagesRepository, + }, + ], }) // eslint-disable-next-line @typescript-eslint/no-extraneous-class diff --git a/src/clients/geocoding.ts b/src/clients/geocoding.ts new file mode 100644 index 0000000..00cfbce --- /dev/null +++ b/src/clients/geocoding.ts @@ -0,0 +1,14 @@ +import { createOsmClient } from '../integration/geocoding/osm'; +import { GeocodingProvider } from '../integration/geocoding/types'; +import { createCachedDataProvider } from '../storage/cache'; +import { createKeyValueStorage } from '../storage/inMemoryDB'; + +export const createOsmClientWithCache = (): GeocodingProvider => { + return { + geocode: createCachedDataProvider({ + cacheStorage: createKeyValueStorage(20), + getNewValue: createOsmClient().geocode, + ttlMs: 86_400_000, + }), + }; +}; diff --git a/src/clients/weather.ts b/src/clients/weather.ts new file mode 100644 index 0000000..6ce3566 --- /dev/null +++ b/src/clients/weather.ts @@ -0,0 +1,14 @@ +import { createOpenmeteoClient } from '../integration/weather/openmeteo'; +import { WeatherProvider } from '../integration/weather/types'; +import { createCachedDataProvider } from '../storage/cache'; +import { createKeyValueStorage } from '../storage/inMemoryDB'; + +export const createOpenmeteoClientWithCache = (): WeatherProvider => { + return { + getCurrentWeather: createCachedDataProvider({ + cacheStorage: createKeyValueStorage(20), + getNewValue: createOpenmeteoClient().getCurrentWeather, + ttlMs: 7_200_000, + }), + }; +}; diff --git a/src/clients/weather/types.ts b/src/clients/weather/types.ts deleted file mode 100644 index 871ea2b..0000000 --- a/src/clients/weather/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type WeatherProvider = { - getCurrentWeather(params: { - longitude: number; - latitude: number; - }): Promise<{ - temperature: string; - apparentTemperature: string; - relativeHumidity: string; - }>; -}; diff --git a/src/dependencies.ts b/src/dependencies.ts index 3f10440..88938d2 100644 --- a/src/dependencies.ts +++ b/src/dependencies.ts @@ -1,6 +1,7 @@ +import { Module as NestModule } from '@nestjs/common'; import { Test, TestingModuleBuilder } from '@nestjs/testing'; -import { GeocodingProvider } from './clients/geocoding/types'; -import { WeatherProvider } from './clients/weather/types'; +import { GeocodingProvider } from './integration/geocoding/types'; +import { WeatherProvider } from './integration/weather/types'; import { PackagesService } from './packages.service'; import { PackagesRepository } from './storage/types'; @@ -18,11 +19,27 @@ export type Dependencies = { export type Dependency = Dependencies[T]; +type ProviderImplementation = + | { + useValue: TProvider; + } + | { + // Quick workaround instead of extracting relevant types from @nestjs/types + // (which are not really very different from `any` anyway) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + useClass: new (...args: any[]) => TProvider; + } + | { + // Quick workaround instead of extracting relevant types from @nestjs/types + // (which are not really very different from `any` anyway) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + useFactory: (...args: any[]) => TProvider | Promise; + }; + type ProviderDictionary = { [TKey in keyof Dependencies]: { provide: TKey; - useValue: Dependencies[TKey]; - }; + } & ProviderImplementation; }; type Provider = ProviderDictionary[keyof ProviderDictionary]; @@ -38,3 +55,5 @@ type ModuleMetadata = { export const createTestingModule: ( metadata: ModuleMetadata, ) => TestingModuleBuilder = Test.createTestingModule.bind(Test); + +export const Module: (metadata: ModuleMetadata) => ClassDecorator = NestModule; diff --git a/src/clients/geocoding/osm.spec.ts b/src/integration/geocoding/osm.spec.ts similarity index 100% rename from src/clients/geocoding/osm.spec.ts rename to src/integration/geocoding/osm.spec.ts diff --git a/src/clients/geocoding/osm.ts b/src/integration/geocoding/osm.ts similarity index 100% rename from src/clients/geocoding/osm.ts rename to src/integration/geocoding/osm.ts diff --git a/src/clients/geocoding/types.ts b/src/integration/geocoding/types.ts similarity index 57% rename from src/clients/geocoding/types.ts rename to src/integration/geocoding/types.ts index 3acad6a..180489b 100644 --- a/src/clients/geocoding/types.ts +++ b/src/integration/geocoding/types.ts @@ -1,5 +1,8 @@ export type GeocodingProvider = { - geocode(query: string): Promise<{ + geocode( + this: void, + query: string, + ): Promise<{ longitude: number; latitude: number; }>; diff --git a/src/clients/weather/openmeteo.spec.ts b/src/integration/weather/openmeteo.spec.ts similarity index 100% rename from src/clients/weather/openmeteo.spec.ts rename to src/integration/weather/openmeteo.spec.ts diff --git a/src/clients/weather/openmeteo.ts b/src/integration/weather/openmeteo.ts similarity index 100% rename from src/clients/weather/openmeteo.ts rename to src/integration/weather/openmeteo.ts diff --git a/src/integration/weather/types.ts b/src/integration/weather/types.ts new file mode 100644 index 0000000..2bd2878 --- /dev/null +++ b/src/integration/weather/types.ts @@ -0,0 +1,13 @@ +export type WeatherProvider = { + getCurrentWeather( + this: void, + params: { + longitude: number; + latitude: number; + }, + ): Promise<{ + temperature: string; + apparentTemperature: string; + relativeHumidity: string; + }>; +}; diff --git a/src/packages.service.spec.ts b/src/packages.service.spec.ts index bfeee6e..ccd5f2d 100644 --- a/src/packages.service.spec.ts +++ b/src/packages.service.spec.ts @@ -1,5 +1,5 @@ -import { GeocodingProvider } from './clients/geocoding/types'; -import { WeatherProvider } from './clients/weather/types'; +import { GeocodingProvider } from './integration/geocoding/types'; +import { WeatherProvider } from './integration/weather/types'; import { PackagesService } from './packages.service'; import { PackageInfo, PackagesRepository } from './storage/types';