Skip to content

Commit dadfc56

Browse files
authored
Feat/use in memory repo to save url
Use an in-memory repository to save shortened url
1 parent b9d5461 commit dadfc56

File tree

17 files changed

+191
-43
lines changed

17 files changed

+191
-43
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ jobs:
3838
- name: Create .env file
3939
run: |
4040
echo "MACHINE_ID=1" > .env
41+
echo "PORT=3000" >> .env
42+
echo "SHORTENED_BASE_URL=http://localhost:3000" >> .env
4143
working-directory: api # Ensure it is created inside `api/`
4244

4345
- name: Run Tests (API)

api/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
"@nestjs/config": "4.0.0",
2525
"@nestjs/core": "10.0.0",
2626
"@nestjs/platform-express": "10.0.0",
27+
"class-transformer": "0.5.1",
28+
"class-validator": "0.14.1",
2729
"reflect-metadata": "0.2.0",
2830
"rxjs": "7.8.1"
2931
},

api/src/app.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Module } from '@nestjs/common';
22
import { ConfigModule } from '@nestjs/config';
33
import { ShortenUrlModule } from './shorten-url/shorten-url.module';
4+
import { InfrastructureModule } from './infrastructure/infrastructure.module';
45

56
@Module({
6-
imports: [ConfigModule.forRoot(), ShortenUrlModule], // Load environment variables
7+
imports: [ConfigModule.forRoot(), InfrastructureModule, ShortenUrlModule], // Load environment variables
78
controllers: [],
89
providers: [],
910
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Module } from '@nestjs/common';
2+
import { ConfigModule } from '@nestjs/config';
3+
import { InMemoryUrlRepository } from '../infrastructure/repository/in-memory-url.repository';
4+
5+
@Module({
6+
imports: [ConfigModule.forRoot()], // Load environment variables
7+
controllers: [],
8+
providers: [InMemoryUrlRepository],
9+
})
10+
export class InfrastructureModule {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ShortenUrlRepository } from '../../shorten-url/shorten-url.repository';
2+
import { Injectable } from '@nestjs/common';
3+
4+
@Injectable()
5+
export class InMemoryUrlRepository implements ShortenUrlRepository {
6+
private urls: Map<string, string> = new Map();
7+
8+
create(url: string, shortenedUrl: string): Promise<void> {
9+
this.urls.set(url, shortenedUrl);
10+
return Promise.resolve(undefined);
11+
}
12+
13+
findURL(url: string): Promise<string | null> {
14+
const shortenedUrl = this.urls.get(url);
15+
return Promise.resolve(shortenedUrl);
16+
}
17+
}

api/src/main.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,42 @@
11
import { NestFactory } from '@nestjs/core';
22
import { AppModule } from './app.module';
3+
import {
4+
BadRequestException,
5+
Logger,
6+
ValidationError,
7+
ValidationPipe,
8+
} from '@nestjs/common';
39

410
async function bootstrap() {
511
const app = await NestFactory.create(AppModule);
6-
await app.listen(process.env.PORT ?? 3001);
12+
13+
app.useGlobalPipes(
14+
new ValidationPipe({
15+
transform: true,
16+
exceptionFactory: (validationErrors: ValidationError[] = []) => {
17+
Logger.log('error', validationErrors);
18+
const formatError = (error: ValidationError) => {
19+
if (error.children?.length) {
20+
return {
21+
field: error.property,
22+
errors: error.children.map(formatError),
23+
};
24+
}
25+
return {
26+
field: error.property,
27+
errors: Object.values(error.constraints ?? {}),
28+
};
29+
};
30+
31+
return new BadRequestException(
32+
validationErrors.map((error) => formatError(error)),
33+
);
34+
},
35+
}),
36+
);
37+
38+
const port = process.env.PORT || 3000;
39+
await app.listen(port).then(() => Logger.log(`Server started on ${port}`));
740
}
41+
842
bootstrap();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { IsString, IsUrl } from 'class-validator';
2+
3+
export class CreateShortenUrlDto {
4+
@IsUrl()
5+
url: string;
6+
}

api/src/shorten-url/shorten-url.controller.spec.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22
import { ShortenUrlController } from './shorten-url.controller';
3-
import { ShortenUrlUsecase } from './shorten-url.usecase';
4-
import { ShortenUrlIdGeneratorService } from './shorten-url.id-generator.service';
53
import { ConfigModule } from '@nestjs/config';
4+
import { ShortenUrlModule } from './shorten-url.module';
65

76
describe('ShortenUrl controller', () => {
87
let underTest: ShortenUrlController;
98

109
beforeEach(async () => {
1110
const app: TestingModule = await Test.createTestingModule({
12-
imports: [await ConfigModule.forRoot()],
13-
controllers: [ShortenUrlController],
14-
providers: [ShortenUrlIdGeneratorService, ShortenUrlUsecase],
11+
imports: [await ConfigModule.forRoot(), ShortenUrlModule],
1512
}).compile();
1613

1714
underTest = app.get<ShortenUrlController>(ShortenUrlController);
@@ -21,13 +18,13 @@ describe('ShortenUrl controller', () => {
2118
it('should return a valid Base62 short URL with 10 chars length', async () => {
2219
const longUrl =
2320
'https://zapper.xyz/very-long-url/very-long-url/very-long-url';
24-
const shortenedUrl = await underTest.shortenUrl(longUrl); // Assuming it's async
21+
const response = await underTest.shortenUrl({ url: longUrl });
2522

2623
// Extract the unique ID part of the URL
27-
const urlPattern = /^https:\/\/zapper\.xyz\/([A-Za-z0-9]{10})$/;
28-
const match = shortenedUrl.match(urlPattern);
24+
const urlPattern = /^http:\/\/localhost:3000\/([A-Za-z0-9]{10})$/;
25+
const match = response?.shortenedUrl.match(urlPattern);
2926

30-
expect(shortenedUrl).not.toBeNull();
27+
expect(response).not.toBeNull();
3128
expect(match).not.toBeNull(); // Ensure it matches the pattern
3229
expect(match![1].length).toBe(10); // Validate the ID part has 10 characters
3330
});
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
import { Controller, Post } from '@nestjs/common';
1+
import { Body, Controller, Logger, Post } from '@nestjs/common';
22
import { ShortenUrlUsecase } from './shorten-url.usecase';
3+
import { CreateShortenUrlDto } from './create-shorten-url.dto';
34

45
@Controller('/shorten-url')
56
export class ShortenUrlController {
67
constructor(private readonly shortenUrlUsecase: ShortenUrlUsecase) {}
78

89
@Post()
9-
shortenUrl(url: string): string {
10-
return this.shortenUrlUsecase.createTinyURL('https://zapper.xyz');
10+
async shortenUrl(
11+
@Body() request: CreateShortenUrlDto,
12+
): Promise<{ shortenedUrl: string }> {
13+
Logger.log(`Received url ${request.url} to shorten`);
14+
const shortenedUrl = await this.shortenUrlUsecase.createTinyURL(
15+
request.url,
16+
);
17+
return { shortenedUrl };
1118
}
1219
}

api/src/shorten-url/shorten-url.module.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import { ConfigModule } from '@nestjs/config';
33
import { ShortenUrlController } from './shorten-url.controller';
44
import { ShortenUrlUsecase } from './shorten-url.usecase';
55
import { ShortenUrlIdGeneratorService } from './shorten-url.id-generator.service';
6+
import { InMemoryUrlRepository } from '../infrastructure/repository/in-memory-url.repository';
7+
import { InfrastructureModule } from '../infrastructure/infrastructure.module';
68

79
@Module({
8-
imports: [ConfigModule.forRoot()], // Load environment variables
10+
imports: [ConfigModule.forRoot(), InfrastructureModule], // Load environment variables
911
controllers: [ShortenUrlController],
10-
providers: [ShortenUrlIdGeneratorService, ShortenUrlUsecase],
12+
providers: [
13+
{ provide: 'ShortenUrlRepository', useClass: InMemoryUrlRepository },
14+
ShortenUrlIdGeneratorService,
15+
ShortenUrlUsecase,
16+
],
1117
})
1218
export class ShortenUrlModule {}

0 commit comments

Comments
 (0)