-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from mathiasberggren/feature/search-by-movie-ba…
…ckend [Backend] Add feature: search by movie title using IMDB API
- Loading branch information
Showing
26 changed files
with
440 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
node_modules | ||
# Keep environment variables out of version control | ||
.env | ||
|
||
.nestjs_repl_history |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { z } from 'nestjs-zod/z' | ||
|
||
export function validate (config: Record<string, unknown>) { | ||
const schema = z.object({ | ||
DATABASE_URL: z.string().url(), | ||
STREAMING_AVAILABILITY_API_HOST: z.string().url().optional(), | ||
IMDB_API_HOST: z.string().url().optional(), | ||
RAPID_API_KEY: z.string().optional() | ||
|
||
}) | ||
|
||
schema.parse(config) | ||
|
||
return config | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { HttpModule, HttpService } from '@nestjs/axios' | ||
import { Module } from '@nestjs/common' | ||
import { ImdbApiBuilder, ImdbApiHttpService } from './imdb-api.service' | ||
|
||
@Module({ | ||
imports: [HttpModule.registerAsync({ | ||
useClass: ImdbApiBuilder | ||
})], | ||
providers: [ | ||
{ | ||
provide: ImdbApiHttpService, | ||
useExisting: HttpService | ||
} | ||
], | ||
exports: [ImdbApiHttpService] | ||
}) | ||
export class ImdbApiModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { HttpModuleOptions, HttpModuleOptionsFactory, HttpService } from '@nestjs/axios' | ||
import { Injectable } from '@nestjs/common' | ||
import { ConfigService } from '@nestjs/config' | ||
|
||
@Injectable() | ||
export class ImdbApiBuilder implements HttpModuleOptionsFactory { | ||
constructor (private readonly config: ConfigService) { | ||
} | ||
|
||
createHttpOptions (): HttpModuleOptions { | ||
return { | ||
baseURL: this.config.get('IMDB_API_HOST'), | ||
headers: { | ||
'X-RapidAPI-Key': this.config.get('RAPID_API_KEY') | ||
} | ||
} | ||
} | ||
} | ||
|
||
export abstract class ImdbApiHttpService extends HttpService {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { HttpModule, HttpService } from '@nestjs/axios' | ||
import { Module } from '@nestjs/common' | ||
import { StreamingApiBuilder, StreamingApiHttpService } from './streaming-api.service' | ||
|
||
@Module({ | ||
imports: [HttpModule.registerAsync({ | ||
useClass: StreamingApiBuilder | ||
})], | ||
providers: [ | ||
{ | ||
provide: StreamingApiHttpService, | ||
useExisting: HttpService | ||
} | ||
], | ||
exports: [StreamingApiHttpService] | ||
}) | ||
export class StreamingApiModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { HttpModuleOptions, HttpModuleOptionsFactory, HttpService } from '@nestjs/axios' | ||
import { Injectable } from '@nestjs/common' | ||
import { ConfigService } from '@nestjs/config' | ||
|
||
@Injectable() | ||
export class StreamingApiBuilder implements HttpModuleOptionsFactory { | ||
constructor (private readonly config: ConfigService) { | ||
} | ||
|
||
createHttpOptions (): HttpModuleOptions { | ||
return { | ||
baseURL: this.config.get('STREAMING_AVAILABILITY_API_HOST'), | ||
headers: { | ||
'X-RapidAPI-Key': this.config.get('RAPID_API_KEY') | ||
} | ||
} | ||
} | ||
} | ||
|
||
export abstract class StreamingApiHttpService extends HttpService {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/* eslint-disable @typescript-eslint/no-extraneous-class */ | ||
export class CreateMovieDto {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { PartialType } from '@nestjs/swagger' | ||
import { CreateMovieDto } from './create-movie.dto' | ||
|
||
export class UpdateMovieDto extends PartialType(CreateMovieDto) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/* eslint-disable @typescript-eslint/no-extraneous-class */ | ||
export class Movie {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export interface IMDBSearchResponse { | ||
status: boolean | ||
message: string | ||
timestamp: number | ||
data: IMDBMovie[] | ||
} | ||
|
||
export interface IMDBMovie { | ||
id: string | ||
qid: string | ||
title: string | ||
year: number | ||
stars: string | ||
q: string | ||
image: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export interface MoviesSearch { | ||
// TODO: Implement Movie response interface | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
findByTitle: (title: string) => Promise<any> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { Test, TestingModule } from '@nestjs/testing' | ||
import { MoviesSearchApiService } from './movies-search.api.service' | ||
import { ImdbApiHttpService } from './clients/imdb-api.service' | ||
import { HttpService } from '@nestjs/axios' | ||
import { createMock } from '@golevelup/ts-jest' | ||
import { IMDBSearchResponse } from './interfaces/imdb-api.interface' | ||
import { AxiosResponse } from 'axios' | ||
import { of } from 'rxjs' | ||
|
||
describe('MoviesSearchApiService', () => { | ||
let service: MoviesSearchApiService | ||
let httpService: ImdbApiHttpService | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [ | ||
MoviesSearchApiService, | ||
{ | ||
provide: ImdbApiHttpService, | ||
useExisting: HttpService | ||
} | ||
] | ||
}).useMocker(createMock).compile() | ||
|
||
service = module.get<MoviesSearchApiService>(MoviesSearchApiService) | ||
httpService = module.get<ImdbApiHttpService>(ImdbApiHttpService) | ||
}) | ||
|
||
it('should be defined', () => { | ||
expect(service).toBeDefined() | ||
}) | ||
|
||
it('should return all movies with given title', async () => { | ||
const data: IMDBSearchResponse = { | ||
status: true, | ||
message: 'Success', | ||
timestamp: 1689187551887, | ||
data: [ | ||
{ | ||
id: 'tt9603212', | ||
qid: 'movie', | ||
title: 'Mission: Impossible - Dead Reckoning Part One', | ||
year: 2023, | ||
stars: 'Tom Cruise, Hayley Atwell', | ||
q: 'feature', | ||
image: 'https://m.media-amazon.com/images/M/MV5BY2VmZDhhNjgtNDcxYi00M2I3LThlMTQtMWRiNWI2Y2I4ZjRmXkEyXkFqcGdeQXVyMTMxMTIwMTE0._V1_.jpg' | ||
}, | ||
{ | ||
id: 'tt0117060', | ||
qid: 'movie', | ||
title: 'Mission: Impossible', | ||
year: 1996, | ||
stars: 'Tom Cruise, Jon Voight', | ||
q: 'feature', | ||
image: 'https://m.media-amazon.com/images/M/MV5BMTc3NjI2MjU0Nl5BMl5BanBnXkFtZTgwNDk3ODYxMTE@._V1_.jpg' | ||
} | ||
] | ||
} | ||
|
||
const response: AxiosResponse<IMDBSearchResponse> = { | ||
data, | ||
status: 200, | ||
statusText: 'OK', | ||
headers: {}, | ||
config: { url: 'http://localhost:3000/mockUrl' } | ||
} | ||
|
||
jest.spyOn(httpService, 'get').mockReturnValue(of(response)) | ||
|
||
const movies = await service.findByTitle('Mission') | ||
expect(movies).toEqual(data.data) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Injectable } from '@nestjs/common' | ||
|
||
import { firstValueFrom } from 'rxjs' | ||
import { MoviesSearch } from './interfaces/movies-search.interface' | ||
import { ImdbApiHttpService } from './clients/imdb-api.service' | ||
import { IMDBMovie } from './interfaces/imdb-api.interface' | ||
|
||
@Injectable() | ||
export class MoviesSearchApiService implements MoviesSearch { | ||
constructor (private readonly imdbClient: ImdbApiHttpService) {} | ||
|
||
async findByTitle (title: string): Promise<IMDBMovie[]> { | ||
const { data } = await firstValueFrom(this.imdbClient.get('/api/v1/searchIMDB', { params: { query: title } })) | ||
const { data: movies } = data | ||
return movies | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Test, TestingModule } from '@nestjs/testing' | ||
import { MoviesSearchService } from './movies-search.service' | ||
|
||
describe('MoviesSearchService', () => { | ||
let service: MoviesSearchService | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [MoviesSearchService] | ||
}).compile() | ||
|
||
service = module.get<MoviesSearchService>(MoviesSearchService) | ||
}) | ||
|
||
it('should be defined', () => { | ||
expect(service).toBeDefined() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Injectable } from '@nestjs/common' | ||
import { MoviesSearch } from './interfaces/movies-search.interface' | ||
|
||
// Only used for NestJS to find the correct provider to inject into MoviesController | ||
|
||
@Injectable() | ||
export abstract class MoviesSearchService implements MoviesSearch { | ||
// TODO: Implement Movie response interface | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
abstract findByTitle (title: string): Promise<any> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { Test, TestingModule } from '@nestjs/testing' | ||
import { MoviesController } from './movies.controller' | ||
import { MoviesService } from './movies.service' | ||
import { MoviesSearchService } from './movies-search.service' | ||
import { createMock } from '@golevelup/ts-jest' | ||
import { MoviesSearchApiService } from './movies-search.api.service' | ||
|
||
const movies = [ | ||
{ | ||
id: 'tt9603212', | ||
qid: 'movie', | ||
title: 'Mission: Impossible - Dead Reckoning Part One', | ||
year: 2023, | ||
stars: 'Tom Cruise, Hayley Atwell', | ||
q: 'feature', | ||
image: 'https://m.media-amazon.com/images/M/MV5BY2VmZDhhNjgtNDcxYi00M2I3LThlMTQtMWRiNWI2Y2I4ZjRmXkEyXkFqcGdeQXVyMTMxMTIwMTE0._V1_.jpg' | ||
}, | ||
{ | ||
id: 'tt0117060', | ||
qid: 'movie', | ||
title: 'Mission: Impossible', | ||
year: 1996, | ||
stars: 'Tom Cruise, Jon Voight', | ||
q: 'feature', | ||
image: 'https://m.media-amazon.com/images/M/MV5BMTc3NjI2MjU0Nl5BMl5BanBnXkFtZTgwNDk3ODYxMTE@._V1_.jpg' | ||
} | ||
] | ||
|
||
describe('MoviesController', () => { | ||
let controller: MoviesController | ||
let movieSearchService: MoviesSearchService | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
controllers: [MoviesController], | ||
providers: [ | ||
MoviesService, | ||
{ | ||
provide: MoviesSearchService, | ||
useClass: MoviesSearchApiService | ||
} | ||
] | ||
}).useMocker(createMock).compile() | ||
|
||
controller = module.get<MoviesController>(MoviesController) | ||
movieSearchService = module.get<MoviesSearchService>(MoviesSearchService) | ||
}) | ||
|
||
it('should be defined', () => { | ||
expect(controller).toBeDefined() | ||
}) | ||
|
||
it('should find a movie by title', async () => { | ||
movieSearchService.findByTitle = jest.fn().mockResolvedValueOnce(movies) | ||
|
||
const response = await controller.findByTitle('Mission: Impossible') | ||
|
||
expect(response).toEqual(movies) | ||
}) | ||
}) |
Oops, something went wrong.