Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/pokemon/dtos/get-pokemon-by-name.query.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
import { IsNotEmpty, IsString } from 'class-validator';

export class GetPokemonByNameQuery {
@ApiProperty({ type: 'string' })
@ApiProperty({
type: 'string',
description:
'pokemon name, must be exact and must be the name of a first-generation pokemon',
})
@IsString()
@IsNotEmpty()
name: string;
}
64 changes: 64 additions & 0 deletions src/pokemon/dtos/get-pokemon-by-name.response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsArray, IsNumber, IsString } from 'class-validator';

class PokemonSpeciesResponse {
@ApiProperty({
type: 'string',
description: 'species name',
})
@IsString()
name: string;

@ApiProperty({
type: 'string',
description: 'species URL',
})
@IsString()
url: string;
}

export class GetPokemonByNameResponse {
@ApiProperty({
type: 'string',
description: 'pokemon name',
})
@IsString()
name: string;

@ApiProperty({
type: 'number',
description: 'pokemon ID (from 1 to 151)',
})
@IsNumber()
id: number;

@ApiProperty({
type: 'number',
description: 'height',
})
@IsNumber()
height: number;

@ApiProperty({
type: 'number',
description: 'weight',
})
@IsNumber()
weight: number;

@ApiProperty({
type: [String],
description: 'pokemon ID (from 1 to 151)',
})
@IsArray()
@IsString({ each: true })
types: string[];

@ApiProperty({
type: () => PokemonSpeciesResponse,
description: 'pokemon species',
})
@Type(() => PokemonSpeciesResponse)
species: PokemonSpeciesResponse;
}
68 changes: 34 additions & 34 deletions src/pokemon/pokemon.controller.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,64 @@
export var pokemonApiUrl = 'http://pokeapi.co/api/v2';

import {
Controller,
Get,
HttpException,
Inject,
InternalServerErrorException,
NotFoundException,
Param,
Post,
Query,
UseInterceptors,
} from '@nestjs/common';
import { PokemonService } from './pokemon.service';
import { GetPokemonByNameQuery } from './dtos/get-pokemon-by-name.query';
import { Pokemon } from './types/pokemon';
import {
ApiBadRequestResponse,
ApiInternalServerErrorResponse,
ApiNotFoundResponse,
ApiQuery,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { GetPokemonByNameQuery } from './dtos/get-pokemon-by-name.query';
import { GetPokemonByNameResponse } from './dtos/get-pokemon-by-name.response';
import { PokemonService } from './pokemon.service';
import { PokemonNotFoundError } from './types/error';
import { Pokemon } from './types/pokemon';

@Controller('pokemon')
@ApiTags('pokemon')
export class PokemonController {
constructor(private pokemonService: PokemonService) {}
constructor(private readonly pokemonService: PokemonService) {}

@Post('pokemon')
@Get('pokemon')
@ApiQuery({
type: GetPokemonByNameQuery,
description: 'Lookup string for pokemons (match against pokemon name)',
})
@ApiResponse({
status: 200,
description: 'Returns pokemons whose name matches a string query param',
type: GetPokemonByNameResponse,
description:
'Returns a pokemon whose name exactly matches the string query param',
})
@ApiBadRequestResponse({
description: 'Returned if query param "name" is empty',
})
@ApiNotFoundResponse({
description:
'Returned if no first-generation pokemon was found for this name',
})
@ApiInternalServerErrorResponse({
description: 'This will never happen, trust me',
description: 'Returned if any other error is encountered',
})
async GetPokemonByNameController(
@Query() name: any,
): Promise<Pokemon | null> {
if (name === null) return;

name == null
? name.trim() != ''
? ((name = name),
(pokemonApiUrl = pokemonApiUrl + '/'),
(pokemonApiUrl = pokemonApiUrl + name))
: ((pokemonApiUrl = pokemonApiUrl + '"?offset=20"'),
(pokemonApiUrl = pokemonApiUrl + '&limit=20'))
: ((pokemonApiUrl = pokemonApiUrl + '"?offset=20"'),
(pokemonApiUrl = pokemonApiUrl + '&limit=20'));

console.log('Printing name for debug : ', name);

const myPokemon = await this.pokemonService.findPokemonByNameOrFail(name);

console.log('Printing name for debug : ', myPokemon);

return myPokemon;
@Query() { name }: GetPokemonByNameQuery,
): Promise<Pokemon> {
try {
return await this.pokemonService.findPokemonByNameOrFail(name);
} catch (error) {
if (error instanceof PokemonNotFoundError) {
throw new NotFoundException(error);
} else if (error instanceof HttpException) {
throw error;
} else {
throw new InternalServerErrorException(error);
}
}
}
}
53 changes: 33 additions & 20 deletions src/pokemon/pokemon.service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { Injectable } from '@nestjs/common';
import got, { Got } from 'got';
import got from 'got';
import { Pokemon } from './types/pokemon';
import { pokemonApiUrl } from './pokemon.controller';
import { PokemonSpecies } from './types/species';

import { PokemonNotFoundError, UnexpectedError } from './types/error';

const POKEMON_API_URL = 'http://pokeapi.co/api/v2';
const FIRST_GENERATION_POKEMON_LAST_ID = 151;
@Injectable()
export class PokemonService {
private httpClient: Got;
constructor() {
this.httpClient = got.extend({
prefixUrl: pokemonApiUrl,
responseType: 'json',
throwHttpErrors: false,
});
}
private readonly httpClient = got.extend({
prefixUrl: POKEMON_API_URL,
responseType: 'json',
throwHttpErrors: false,
});

async findPokemonByNameOrFail(pokemonName: string): Promise<Pokemon> {
try {
return this.findPokemon(pokemonName);
} catch (error) {
if (error instanceof PokemonNotFoundError) {
throw error;
}
throw new UnexpectedError(error);
}
}

async findPokemon(pokemonName: string): Promise<Pokemon> {
type GetPokemonResponse = {
name: string;
id: number;
Expand All @@ -25,32 +36,34 @@ export class PokemonService {
species: PokemonSpecies;
};

return await this.httpClient
return this.httpClient
.get(`pokemon/${pokemonName}`)
.then((response) => response.body as unknown as GetPokemonResponse)
.then((body) => {
.then(async (body) => {
if (!body.id) {
return null;
throw new PokemonNotFoundError(
`No pokemon found for name '${pokemonName}'`,
);
}

const types = body.types
.map((type) => type.type)
.map((type) => type.name);
if (body.id > FIRST_GENERATION_POKEMON_LAST_ID) {
throw new PokemonNotFoundError(
`Pokemon '${pokemonName}' is not first-generation: ID ${body.id} > ${FIRST_GENERATION_POKEMON_LAST_ID}`,
);
}

// remap PokeAPI stats to a 'PokemonSpecs' object
return {
name: body.name,
id: body.id,
height: body.height,
weight: body.weight,
types,
types: body.types.map((type) => type.type.name),
species: {
name: body.species.name,
url: body.species.url,
},
};
})
.catch((error) => {
throw error;
});
}
}
3 changes: 3 additions & 0 deletions src/pokemon/types/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class PokemonNotFoundError extends Error {}

export class UnexpectedError extends Error {}