Skip to content

Commit 9689798

Browse files
committed
feat(authentication): add pokemon fields to user
1 parent 433ba64 commit 9689798

File tree

15 files changed

+137
-23
lines changed

15 files changed

+137
-23
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ npm run prisma:generate
8383
Run migrations to create necessary tables in the DB
8484

8585
```bash
86-
npm run migrate:dev
86+
npm run prisma:migrate:dev
8787
```
8888

8989
Seed the database with the first user
9090

9191
```bash
92-
npm run seed
92+
npm run prisma:seed
9393
```
9494

9595
Start the application
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
meta {
2+
name: Catch Pokemon
3+
type: http
4+
seq: 7
5+
}
6+
7+
post {
8+
url: {{host}}/{{version}}/user/pokemon/catch
9+
body: json
10+
auth: none
11+
}
12+
13+
body:json {
14+
{
15+
"pokemonId": 1
16+
}
17+
}
18+
19+
script:pre-request {
20+
const accessToken = bru.getEnvVar('accessToken');
21+
if (accessToken) {
22+
req.headers.cookie = `accessToken=${accessToken}`;
23+
}
24+
}

bruno/NestJS Example app/User/Register.bru

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ headers {
1717
body:json {
1818
{
1919
"email": "[email protected]",
20-
"firstname": "Ismael",
20+
"name": "Ismael",
2121
"password": "Secret42",
2222
"favouritePokemonId": 25,
23-
"terms": false
23+
"terms": true
2424
}
2525
}
2626

bruno/NestJS Example app/User/Update.bru

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ patch {
1212

1313
body:json {
1414
{
15-
"firstname": "Ismael",
15+
"name": "Ismael",
1616
"language": "en-US"
1717
}
1818
}

package.json

+5-6
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,15 @@
1616
},
1717
"scripts": {
1818
"prepare": "husky install || true",
19-
"setup": "npm run docker:db && npm run prisma:generate && npm run migrate:dev && npm run seed && npm start",
19+
"setup": "npm run docker:db && npm run prisma:generate && npm run prisma:migrate:dev && npm run prisma:seed && npm start",
2020
"start": "nest start --watch",
2121
"start:prod": "node dist/main",
2222
"docker:db": "docker-compose -f docker-compose.db.yml up -d",
23-
"seed": "prisma db seed",
24-
"migrate:dev": "prisma migrate dev --preview-feature",
25-
"migrate:dev:create": "prisma migrate dev --create-only --preview-feature",
26-
"migrate:deploy": "npx prisma migrate deploy --preview-feature",
27-
"prisma:push": "prisma db push",
23+
"prisma:seed": "prisma db seed",
24+
"prisma:migrate:dev": "prisma migrate dev --preview-feature",
25+
"prisma:migrate:deploy": "npx prisma migrate deploy --preview-feature",
2826
"prisma:generate": "npx prisma generate",
27+
"prisma:push": "prisma db push",
2928
"lint": "eslint .",
3029
"test": "jest --coverage",
3130
"test:watch": "jest --watch",

prisma/migrations/20241216002534_init/migration.sql renamed to prisma/migrations/20241218145504_/migration.sql

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ CREATE TABLE "User" (
66
"id" TEXT NOT NULL,
77
"createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
88
"updatedAt" TIMESTAMPTZ(3) NOT NULL,
9+
"name" TEXT NOT NULL,
910
"email" TEXT NOT NULL,
1011
"password" TEXT NOT NULL,
11-
"firstname" TEXT NOT NULL,
12-
"language" "Language" NOT NULL,
1312
"favouritePokemonId" INTEGER NOT NULL,
13+
"language" "Language" NOT NULL,
1414
"terms" BOOLEAN NOT NULL DEFAULT false,
15+
"caughtPokemonIds" INTEGER[] DEFAULT ARRAY[]::INTEGER[],
1516

1617
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
1718
);

prisma/schema.prisma

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ model User {
1616
id String @id @default(uuid())
1717
createdAt DateTime @default(now()) @db.Timestamptz(3)
1818
updatedAt DateTime @updatedAt @db.Timestamptz(3)
19+
name String
1920
email String @unique
2021
password String
21-
firstname String
22-
language Language
2322
favouritePokemonId Int
23+
language Language
2424
terms Boolean @default(false)
25+
caughtPokemonIds Int[] @default([])
2526
}
2627

2728
enum Language {

prisma/seed.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ async function main() {
1111
await prisma.user.create({
1212
data: {
1313
14-
firstname: 'Pete',
14+
name: 'Pete',
1515
language: Language.EN_US,
1616
password: '$2b$10$yjh4MYBO/eNJKxcpODqbt.dQ/0u80wV.bR5uFRv7n27bmHI0glw1G', // Secret42
1717
favouritePokemonId: 25,

src/core/enums/app-error.enum.ts

+3
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ export enum AppError {
1414
REFRESH_TOKEN_NOT_FOUND = 3001,
1515
ACCESS_TOKEN_EXPIRED = 3002,
1616
REFRESH_TOKEN_EXPIRED = 3003,
17+
18+
// Pokemon Errors (4000–4999)
19+
POKEMON_ALREADY_CAUGHT = 4000,
1720
}

src/features/authentication/dto/register.request.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class RegisterRequest {
2121
password!: string;
2222

2323
@IsString()
24-
firstname!: string;
24+
name!: string;
2525

2626
@IsNumber()
2727
favouritePokemonId!: number;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { IsNumber } from 'class-validator';
2+
3+
export class CatchPokemonRequest {
4+
@IsNumber()
5+
pokemonId!: number;
6+
}

src/features/user/dto/update-user.request.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Language as AppLanguage } from '../../../core/enums/language.enum';
44
export class UpdateUserRequest {
55
@IsOptional()
66
@IsString()
7-
firstname?: string;
7+
name?: string;
88

99
@IsOptional()
1010
@IsEnum(AppLanguage)

src/features/user/user.controller.ts

+15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Get,
55
Logger,
66
Patch,
7+
Post,
78
Request,
89
UseGuards,
910
UseInterceptors,
@@ -15,6 +16,7 @@ import { ResponseInterceptor } from '../../core/interceptors/response.intercepto
1516
import { LanguageTransformInterceptor } from '../../core/interceptors/language.interceptor';
1617
import { UpdateUserRequest } from './dto/update-user.request';
1718
import { UpdateUserResponse } from './dto/update-user.response';
19+
import { CatchPokemonRequest } from './dto/catch-pokemon.request';
1820

1921
@Controller('user')
2022
@UseInterceptors(ResponseInterceptor)
@@ -42,4 +44,17 @@ export class UserController {
4244
this.logger.log(`[UpdateUser]: user with id "${userId}" updating itself`);
4345
return this.userService.updateUser(userId, updateUserRequest);
4446
}
47+
48+
@Post('pokemon/catch')
49+
@UseGuards(AuthenticationGuard)
50+
async catchPokemon(
51+
@Request() request: { userId: string },
52+
@Body() catchPokemonRequest: CatchPokemonRequest,
53+
): Promise<UpdateUserResponse> {
54+
const { userId } = request;
55+
this.logger.log(
56+
`[CatchPokemon]: user with id "${userId}" catching pokemon ${catchPokemonRequest.pokemonId}`,
57+
);
58+
return this.userService.catchPokemon(userId, catchPokemonRequest);
59+
}
4560
}

src/features/user/user.repository.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,27 @@ export class UserRepository {
3939
});
4040
}
4141

42-
async updateUserById(userId: string, userData: User): Promise<Omit<User, 'password'>> {
42+
async updateUserById(
43+
userId: string,
44+
userData: Partial<Omit<User, 'password'>>,
45+
): Promise<Omit<User, 'password'>> {
4346
return this.prisma.user.update({
44-
omit: {
45-
password: true,
46-
},
4747
data: userData,
4848
where: {
4949
id: userId,
5050
},
51+
select: {
52+
password: false,
53+
id: true,
54+
createdAt: true,
55+
updatedAt: true,
56+
email: true,
57+
name: true,
58+
language: true,
59+
favouritePokemonId: true,
60+
terms: true,
61+
caughtPokemonIds: true,
62+
},
5163
});
5264
}
5365
}

src/features/user/user.service.ts

+54-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
1+
import { BadRequestException, ConflictException, Injectable, Logger } from '@nestjs/common';
22
import { AppError } from '../../core/enums/app-error.enum';
33
import { LanguageService } from '../../core/services/language.service';
44
import { UserRepository } from './user.repository';
55
import { GetMeResponse } from './dto/get-me.response';
66
import { User } from '@prisma/client';
77
import { UpdateUserResponse } from './dto/update-user.response';
88
import { UpdateUserRequest } from './dto/update-user.request';
9+
import { CatchPokemonRequest } from './dto/catch-pokemon.request';
910

1011
@Injectable()
1112
export class UserService {
@@ -64,4 +65,56 @@ export class UserService {
6465
});
6566
}
6667
}
68+
69+
async catchPokemon(
70+
userId: string,
71+
catchPokemonRequest: CatchPokemonRequest,
72+
): Promise<UpdateUserResponse> {
73+
if (!userId) {
74+
throw new BadRequestException({
75+
code: AppError.USER_NOT_FOUND,
76+
message: `User not found`,
77+
});
78+
}
79+
80+
const userWithoutPokemon = await this.userRepository.getUserById(userId);
81+
if (!userWithoutPokemon) {
82+
throw new BadRequestException({
83+
code: AppError.USER_NOT_FOUND,
84+
message: `User with id ${userId} not found`,
85+
});
86+
}
87+
88+
return this.addPokemonToUser({ userId, userWithoutPokemon, catchPokemonRequest });
89+
}
90+
91+
private async addPokemonToUser(data: {
92+
userId: string;
93+
userWithoutPokemon: Omit<User, 'password'>;
94+
catchPokemonRequest: CatchPokemonRequest;
95+
}) {
96+
const pokemonsCaught = data.userWithoutPokemon.caughtPokemonIds;
97+
const { pokemonId } = data.catchPokemonRequest;
98+
if (pokemonsCaught.includes(pokemonId)) {
99+
throw new ConflictException({
100+
code: AppError.POKEMON_ALREADY_CAUGHT,
101+
message: `You already have caught pokemon with id: ${pokemonId}`,
102+
});
103+
}
104+
pokemonsCaught.push(pokemonId);
105+
106+
try {
107+
const userUpdated = await this.userRepository.updateUserById(data.userId, {
108+
...data.userWithoutPokemon,
109+
caughtPokemonIds: pokemonsCaught,
110+
});
111+
this.logger.log(`[CatchPokemon]: user updated successfully`);
112+
return { user: userUpdated };
113+
} catch {
114+
throw new BadRequestException({
115+
code: AppError.UPDATE_USER_FAILED,
116+
message: `Unable to update user`,
117+
});
118+
}
119+
}
67120
}

0 commit comments

Comments
 (0)