From 2d95c867229a45c90c524ab2c1abb59d0b2cdff8 Mon Sep 17 00:00:00 2001 From: selimyanat Date: Fri, 14 Mar 2025 13:10:50 +0100 Subject: [PATCH] Use an in-memory repository to save shortened url --- .github/workflows/ci.yml | 4 +-- api/.env-dev | 5 +++ api/eslint.config.mjs | 34 +++++++++++++++++++ .../infrastructure/infrastructure.module.ts | 21 ++++++++---- .../repository/redis-url.repository.ts | 17 ++++++++++ .../repository/repository.provider.ts | 26 ++++++++++++++ api/src/shorten-url/shorten-url.module.ts | 12 +++++-- api/src/shorten-url/shorten-url.repository.ts | 6 ++-- 8 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 api/.env-dev create mode 100644 api/eslint.config.mjs create mode 100644 api/src/infrastructure/repository/redis-url.repository.ts create mode 100644 api/src/infrastructure/repository/repository.provider.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7aa89b..bbaa45e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,9 +37,7 @@ jobs: - name: Create .env file run: | - echo "MACHINE_ID=1" > .env - echo "PORT=3000" >> .env - echo "SHORTENED_BASE_URL=http://localhost:3000" >> .env + cp .env-dev .env working-directory: api # Ensure it is created inside `api/` - name: Run Tests (API) diff --git a/api/.env-dev b/api/.env-dev new file mode 100644 index 0000000..2e964d6 --- /dev/null +++ b/api/.env-dev @@ -0,0 +1,5 @@ +CACHE_PERSISTENCE= memory +MACHINE_ID=1 +PORT = 3000 +# Perhpas we should only use a domain name here instead of a full URL (with the port as it is error prone) +SHORTENED_BASE_URL = http://localhost:3000 \ No newline at end of file diff --git a/api/eslint.config.mjs b/api/eslint.config.mjs new file mode 100644 index 0000000..0207646 --- /dev/null +++ b/api/eslint.config.mjs @@ -0,0 +1,34 @@ +import js from '@eslint/js'; +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import prettier from 'eslint-plugin-prettier'; // ✅ Explicitly import the plugin +import prettierConfig from 'eslint-config-prettier'; + +export default [ + js.configs.recommended, + { + files: ['src/**/*.ts', 'test/**/*.ts'], + languageOptions: { + parser: tsParser, + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: import.meta.dirname, + sourceType: 'module', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + prettier, // ✅ Register Prettier plugin + }, + rules: { + ...tseslint.configs.recommended.rules, + ...prettierConfig.rules, // ✅ Apply Prettier rules + 'prettier/prettier': 'error', // ✅ Ensure Prettier rules are enforced + '@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', + }, + ignores: ['**/*.config.js', '**/node_modules/**'], + }, +]; diff --git a/api/src/infrastructure/infrastructure.module.ts b/api/src/infrastructure/infrastructure.module.ts index f9e7cc5..c1742dd 100644 --- a/api/src/infrastructure/infrastructure.module.ts +++ b/api/src/infrastructure/infrastructure.module.ts @@ -1,10 +1,17 @@ -import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; -import { InMemoryUrlRepository } from '../infrastructure/repository/in-memory-url.repository'; +import { Module, DynamicModule } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ShortenUrlRepository } from '../shorten-url/shorten-url.repository'; +import { RepositoryProvider } from './repository/repository.provider'; @Module({ - imports: [ConfigModule.forRoot()], // Load environment variables - controllers: [], - providers: [InMemoryUrlRepository], + imports: [ConfigModule], // ✅ Ensure ConfigModule is loaded }) -export class InfrastructureModule {} +export class InfrastructureModule { + static register(): DynamicModule { + return { + module: InfrastructureModule, + providers: [RepositoryProvider], + exports: [ShortenUrlRepository], // ✅ Export repository so other modules can use it + }; + } +} diff --git a/api/src/infrastructure/repository/redis-url.repository.ts b/api/src/infrastructure/repository/redis-url.repository.ts new file mode 100644 index 0000000..d284af7 --- /dev/null +++ b/api/src/infrastructure/repository/redis-url.repository.ts @@ -0,0 +1,17 @@ +import { ShortenUrlRepository } from '../../shorten-url/shorten-url.repository'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class RedisUrlRepository implements ShortenUrlRepository { + private urls: Map = new Map(); + + create(url: string, shortenedUrl: string): Promise { + this.urls.set(url, shortenedUrl); + return Promise.resolve(undefined); + } + + findURL(url: string): Promise { + const shortenedUrl = this.urls.get(url); + return Promise.resolve(shortenedUrl); + } +} diff --git a/api/src/infrastructure/repository/repository.provider.ts b/api/src/infrastructure/repository/repository.provider.ts new file mode 100644 index 0000000..c18c032 --- /dev/null +++ b/api/src/infrastructure/repository/repository.provider.ts @@ -0,0 +1,26 @@ +import { ConfigService } from '@nestjs/config'; +import { Provider } from '@nestjs/common'; +import { InMemoryUrlRepository } from './in-memory-url.repository'; +import { ShortenUrlRepository } from '../../shorten-url/shorten-url.repository'; + +/** + * Factory provider to select the appropriate repository implementation based on configuration. + */ +export const RepositoryProvider: Provider = { + // TODO: Use a constant for the key + //provide: 'ShortenUrlRepository', + provide: ShortenUrlRepository, + useFactory: (configService: ConfigService) => { + const persistenceType = configService.get( + 'CACHE_PERSISTENCE', + 'memory', + ); + + if (persistenceType === 'redis') { + throw new Error('Redis persistence is not yet implemented'); + } + + return new InMemoryUrlRepository(); + }, + inject: [ConfigService], // ✅ Ensure ConfigService is available +}; diff --git a/api/src/shorten-url/shorten-url.module.ts b/api/src/shorten-url/shorten-url.module.ts index 4d1c2d7..28167d8 100644 --- a/api/src/shorten-url/shorten-url.module.ts +++ b/api/src/shorten-url/shorten-url.module.ts @@ -3,14 +3,20 @@ import { ConfigModule } from '@nestjs/config'; import { ShortenUrlController } from './shorten-url.controller'; import { ShortenUrlUsecase } from './shorten-url.usecase'; import { ShortenUrlIdGeneratorService } from './shorten-url.id-generator.service'; -import { InMemoryUrlRepository } from '../infrastructure/repository/in-memory-url.repository'; import { InfrastructureModule } from '../infrastructure/infrastructure.module'; +import { ShortenUrlRepository } from './shorten-url.repository'; @Module({ - imports: [ConfigModule.forRoot(), InfrastructureModule], // Load environment variables + imports: [ + ConfigModule, // ✅ Ensure ConfigModule is available + InfrastructureModule.register(), // ✅ Register InfrastructureModule dynamically + ], controllers: [ShortenUrlController], providers: [ - { provide: 'ShortenUrlRepository', useClass: InMemoryUrlRepository }, + { + provide: 'ShortenUrlRepository', + useExisting: ShortenUrlRepository, // ✅ Use injected provider + }, ShortenUrlIdGeneratorService, ShortenUrlUsecase, ], diff --git a/api/src/shorten-url/shorten-url.repository.ts b/api/src/shorten-url/shorten-url.repository.ts index 3c5814e..efa6921 100644 --- a/api/src/shorten-url/shorten-url.repository.ts +++ b/api/src/shorten-url/shorten-url.repository.ts @@ -1,5 +1,5 @@ -export interface ShortenUrlRepository { - findURL(url: string): Promise; +export abstract class ShortenUrlRepository { + abstract findURL(url: string): Promise; - create(url: string, shortenedUrl: string): Promise; + abstract create(url: string, shortenedUrl: string): Promise; }