feat: add movie and category services

This commit is contained in:
Sergey Krylov 2026-04-12 09:32:09 +03:00
parent b2e02e0ae2
commit 471ac1ba44
12 changed files with 202 additions and 30 deletions

View File

@ -4,7 +4,9 @@ import { PassportModule } from '@teacinema/passport'
import { AccountModule } from '../modules/account/account.module'
import { AuthModule } from '../modules/auth/auth.module'
import { CategoryModule } from '../modules/category/category.module'
import { MediaModule } from '../modules/media/media.module'
import { MovieModule } from '../modules/movie/movie.module'
import { UsersModule } from '../modules/users/users.module'
import { ObservabilityModule } from '../observability/observability.module'
@ -27,6 +29,8 @@ import { getPassportConfig } from './config'
AccountModule,
MediaModule,
UsersModule,
MovieModule,
CategoryModule,
PassportModule.registerAsync({
useFactory: getPassportConfig,
inject: [ConfigService]

View File

@ -0,0 +1,20 @@
import {
Controller,
Get,
HttpCode,
HttpStatus,
} from '@nestjs/common'
import { CategoryClientGrpc } from './category.grpc'
@Controller('categories')
export class CategoryController {
constructor(private readonly client: CategoryClientGrpc) {}
@Get()
@HttpCode(HttpStatus.OK)
public async getAll() {
const { categories } = await this.client.call('getAllCategories', {})
return categories
}
}

View File

@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common'
import type { ClientGrpc } from '@nestjs/microservices'
import { InjectGrpc } from '@teacinema/common'
import { CategoryServiceClient } from '@teacinema/contracts/gen/ts/category'
import { AbstractGrpcClient } from '../../shared/grpc/abstract-grpc.client'
@Injectable()
export class CategoryClientGrpc extends AbstractGrpcClient<CategoryServiceClient> {
constructor(@InjectGrpc('CATEGORY_PACKAGE') client: ClientGrpc) {
super(client, 'CategoryService')
}
}

View File

@ -0,0 +1,13 @@
import { Module } from '@nestjs/common'
import { GrpcModule } from '@teacinema/common'
import { CategoryController } from './category.controller'
import { CategoryClientGrpc } from './category.grpc'
@Module({
imports: [GrpcModule.register(['CATEGORY_PACKAGE'])],
controllers: [CategoryController],
providers: [CategoryClientGrpc],
exports: [CategoryClientGrpc]
})
export class CategoryModule {}

View File

@ -0,0 +1 @@
export * from './rq'

View File

@ -0,0 +1,46 @@
import { ApiPropertyOptional } from '@nestjs/swagger'
import { Transform } from 'class-transformer'
import {
IsBoolean,
IsInt,
IsOptional,
IsString,
Max,
Min
} from 'class-validator'
export class GetMoviesRequestDto {
@ApiPropertyOptional({
example: 'classic'
})
@IsOptional()
@IsString()
// eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
@Transform(({ value }) => value.toString().trim())
public category: string
@ApiPropertyOptional({
example: true
})
@IsOptional()
@IsBoolean()
@Transform(({ value }) => {
if (value === 'true') return true
if (value === 'false') return false
return value as boolean
})
public random: boolean
@ApiPropertyOptional({
example: 10
})
@IsInt()
@IsOptional()
@Min(1)
@Max(100)
@Transform(({ value }) => {
if (value !== undefined) return Number(value)
return undefined
})
public limit: number
}

View File

@ -0,0 +1 @@
export * from './get-movies.request.dto'

View File

@ -0,0 +1,35 @@
import {
Controller,
Get,
HttpCode,
HttpStatus,
Param,
Query
} from '@nestjs/common'
import { Movie, MovieDetails } from '@teacinema/contracts/gen/ts/movie'
import { GetMoviesRequestDto } from './dto'
import { MovieClientGrpc } from './movie.grpc'
import { MovieMapper } from './movie.mapper'
@Controller('movies')
export class MovieController {
constructor(private readonly client: MovieClientGrpc) {}
@Get()
@HttpCode(HttpStatus.OK)
public async getAll(@Query() dto: GetMoviesRequestDto) {
const { movies } = await this.client.call('listMovies', dto)
// eslint-disable-next-line @typescript-eslint/unbound-method
return movies.map(MovieMapper.toMovie<Movie>)
}
@Get(':slug')
@HttpCode(HttpStatus.OK)
public async getBySlug(@Param('slug') slug: string) {
const { movie } = await this.client.call('getMovie', { slug })
// @ts-expect-error -- ok
return MovieMapper.toMovie<MovieDetails>(movie)
}
}

View File

@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common'
import type { ClientGrpc } from '@nestjs/microservices'
import { InjectGrpc } from '@teacinema/common'
import { MovieServiceClient } from '@teacinema/contracts/gen/ts/movie'
import { AbstractGrpcClient } from '../../shared/grpc/abstract-grpc.client'
@Injectable()
export class MovieClientGrpc extends AbstractGrpcClient<MovieServiceClient> {
constructor(@InjectGrpc('MOVIE_PACKAGE') client: ClientGrpc) {
super(client, 'MovieService')
}
}

View File

@ -0,0 +1,13 @@
import { timestampToISO } from '@teacinema/common'
import { Movie, MovieDetails } from '@teacinema/contracts/gen/ts/movie'
export class MovieMapper {
public static toMovie<T extends Movie | MovieDetails>(entity: T) {
if (!entity.releaseDate) return entity
return {
...entity,
releaseDate: timestampToISO(entity.releaseDate)
}
}
}

View File

@ -0,0 +1,13 @@
import { Module } from '@nestjs/common'
import { GrpcModule } from '@teacinema/common'
import { MovieController } from './movie.controller'
import { MovieClientGrpc } from './movie.grpc'
@Module({
imports: [GrpcModule.register(['MOVIE_PACKAGE'])],
controllers: [MovieController],
providers: [MovieClientGrpc],
exports: [MovieClientGrpc]
})
export class MovieModule {}

View File

@ -1028,12 +1028,12 @@
tslib "2.8.1"
"@nestjs/common@^11.1.16":
version "11.1.17"
resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.1.17.tgz#58a330f3f6748cf92b0a244a2a232cfddba1bfd2"
integrity sha512-hLODw5Abp8OQgA+mUO4tHou4krKgDtUcM9j5Ihxncst9XeyxYBTt2bwZm4e4EQr5E352S4Fyy6V3iFx9ggxKAg==
version "11.1.18"
resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.1.18.tgz#329b214aadea5a4a1b12b4eee61c32b9c5b5082c"
integrity sha512-0sLq8Z+TIjLnz1Tqp0C/x9BpLbqpt1qEu0VcH4/fkE0y3F5JxhfK1AdKQ/SPbKhKgwqVDoY4gS8GQr2G6ujaWg==
dependencies:
uid "2.0.2"
file-type "21.3.2"
file-type "21.3.4"
iterare "1.2.1"
load-esm "1.0.3"
tslib "2.8.1"
@ -1048,13 +1048,13 @@
lodash "4.17.21"
"@nestjs/config@^4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-4.0.3.tgz#bb24a5f64e4e3ccf6fbf9e02f63303d14d7f308e"
integrity sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==
version "4.0.4"
resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-4.0.4.tgz#311d806fdc32a8bf29cf9ced903b97cbdb7e064b"
integrity sha512-CJPjNitr0bAufSEnRe2N+JbnVmMmDoo6hvKCPzXgZoGwJSmp/dZPk9f/RMbuD/+Q1ZJPjwsRpq0vxna++Knwow==
dependencies:
dotenv "17.2.3"
dotenv "17.4.1"
dotenv-expand "12.0.3"
lodash "4.17.23"
lodash "4.18.1"
"@nestjs/core@^11.0.1":
version "11.1.12"
@ -1094,9 +1094,9 @@
tslib "2.8.1"
"@nestjs/microservices@^11.1.16":
version "11.1.17"
resolved "https://registry.yarnpkg.com/@nestjs/microservices/-/microservices-11.1.17.tgz#f2b1aa25e24532377477a437583f1af002118602"
integrity sha512-rubBPEEXS9PwA3LwdNN/ytjEUmON+d8+4fbiUCIFGW8i+rR/g36vrpwuvoAzcGpxlu45/TpZPfHTb0nJciG9Yw==
version "11.1.18"
resolved "https://registry.yarnpkg.com/@nestjs/microservices/-/microservices-11.1.18.tgz#3f5edd756e32f7c85c1c67c65f8ba3b62a736ebf"
integrity sha512-sodzBqqKAcOv5GctTbb0j3vzgx+X3/IxmJ8pBKL9hEmsarsYPkig6dcOcVFmM2Pknm+o6Cxaqv1YZZETISNi+w==
dependencies:
iterare "1.2.1"
tslib "2.8.1"
@ -1997,9 +1997,9 @@
"@sinonjs/commons" "^3.0.1"
"@teacinema/common@^1.0.0":
version "1.1.6"
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcommon/-/1.1.6/common-1.1.6.tgz#4b43fc7c1f19cef0ba138a3a7988918f54f52ebb"
integrity sha512-a0wU0EaJ92PdMlwFQ5pA0EZ9G1/f9Gca8CCN2qUy75q0HldNTGKKYUEyDHx4Y0z16N1e3/RO12gagebOrV6Nbg==
version "1.3.2"
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcommon/-/1.3.2/common-1.3.2.tgz#51ef68a7a92bb9fbd604052953a10b84846ddd77"
integrity sha512-uD83OWCPZrjDknQNvDGz90WyHDHsRvzn/kE3ofK5oToOOG5f0WXrq0vr1UGi7+DZzTbRfsUe06mgMyahuJ/j4w==
dependencies:
"@nestjs/common" "^11.1.16"
"@nestjs/config" "^4.0.3"
@ -2007,9 +2007,9 @@
"@teacinema/contracts" "^1.1.3"
"@teacinema/contracts@^1.0.0", "@teacinema/contracts@^1.1.3":
version "1.1.8"
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.1.8/contracts-1.1.8.tgz#e96c23a957d2682503c157250672eb4c46bf4ef9"
integrity sha512-wSFTntA0IoQrC/iYz3rylJW6qK6KGWa25oQrl/n55aSOcMk1ACmSBt0aqAPML86SQWAsn7yikpr/foaTgJH3zg==
version "1.2.3"
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.2.3/contracts-1.2.3.tgz#d356a9dc7395b304a9d5ab639565fa9bcebb9c74"
integrity sha512-Rvw8DVmIpRD/I2Yk1gQyO8ZmxyOIY3FW+purxwiLBIi22gHQtVAXV27a9xK6sO/XXHx70KhrY/8fU8JH/+ehkw==
dependencies:
"@nestjs/microservices" "^11.1.12"
protoc "33.4.0"
@ -3455,10 +3455,10 @@ dotenv@16.4.7:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26"
integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==
dotenv@17.2.3:
version "17.2.3"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2"
integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==
dotenv@17.4.1:
version "17.4.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.4.1.tgz#d8e2179fe287365ef3aecb9459668454168eda88"
integrity sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==
dotenv@^16.4.5:
version "16.6.1"
@ -3853,10 +3853,10 @@ file-type@21.3.0:
token-types "^6.1.1"
uint8array-extras "^1.4.0"
file-type@21.3.2:
version "21.3.2"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-21.3.2.tgz#b28bb3a19ea4b03f59af31546b9d95ee8a3623eb"
integrity sha512-DLkUvGwep3poOV2wpzbHCOnSKGk1LzyXTv+aHFgN2VFl96wnp8YA9YjO2qPzg5PuL8q/SW9Pdi6WTkYOIh995w==
file-type@21.3.4:
version "21.3.4"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-21.3.4.tgz#e3f902faee8ec4aa152909fc902a7a77f9c06725"
integrity sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==
dependencies:
"@tokenizer/inflate" "^0.4.1"
strtok3 "^10.3.4"
@ -4909,10 +4909,10 @@ lodash@4.17.21, lodash@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lodash@4.17.23:
version "4.17.23"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a"
integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==
lodash@4.18.1:
version "4.18.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c"
integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==
log-symbols@^4.1.0:
version "4.1.0"