feat: add screening service
This commit is contained in:
commit
13c322653a
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
/build
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# temp directory
|
||||
.temp
|
||||
.tmp
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
98
README.md
Normal file
98
README.md
Normal file
@ -0,0 +1,98 @@
|
||||
<p align="center">
|
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
||||
</p>
|
||||
|
||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
|
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
|
||||
</p>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](https://opencollective.com/nest#sponsor)-->
|
||||
|
||||
## Description
|
||||
|
||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||
|
||||
## Project setup
|
||||
|
||||
```bash
|
||||
$ yarn install
|
||||
```
|
||||
|
||||
## Compile and run the project
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ yarn run start
|
||||
|
||||
# watch mode
|
||||
$ yarn run start:dev
|
||||
|
||||
# production mode
|
||||
$ yarn run start:prod
|
||||
```
|
||||
|
||||
## Run tests
|
||||
|
||||
```bash
|
||||
# unit tests
|
||||
$ yarn run test
|
||||
|
||||
# e2e tests
|
||||
$ yarn run test:e2e
|
||||
|
||||
# test coverage
|
||||
$ yarn run test:cov
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
||||
|
||||
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
||||
|
||||
```bash
|
||||
$ yarn install -g @nestjs/mau
|
||||
$ mau deploy
|
||||
```
|
||||
|
||||
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
||||
|
||||
## Resources
|
||||
|
||||
Check out a few resources that may come in handy when working with NestJS:
|
||||
|
||||
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
|
||||
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
|
||||
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
|
||||
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
|
||||
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
|
||||
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
|
||||
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
|
||||
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
|
||||
|
||||
## Support
|
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||
|
||||
## Stay in touch
|
||||
|
||||
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
|
||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||
|
||||
## License
|
||||
|
||||
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
|
||||
35
eslint.config.mjs
Normal file
35
eslint.config.mjs
Normal file
@ -0,0 +1,35 @@
|
||||
// @ts-check
|
||||
import eslint from '@eslint/js';
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['eslint.config.mjs'],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
sourceType: 'commonjs',
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||
"prettier/prettier": ["error", { endOfLine: "auto" }],
|
||||
},
|
||||
},
|
||||
);
|
||||
8
nest-cli.json
Normal file
8
nest-cli.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
80
package.json
Normal file
80
package.json
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "screening-service",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.3",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.4",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/microservices": "^11.1.19",
|
||||
"@nestjs/mongoose": "^11.0.4",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@teacinema/common": "^1.6.0",
|
||||
"@teacinema/contracts": "^1.5.0",
|
||||
"@teacinema/core": "^1.0.9",
|
||||
"mongoose": "^9.5.0",
|
||||
"nanoid": "^5.1.9",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"globals": "^16.0.0",
|
||||
"jest": "^30.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
7
prettier.config.mjs
Normal file
7
prettier.config.mjs
Normal file
@ -0,0 +1,7 @@
|
||||
import config from '@teacinema/core/prettier'
|
||||
|
||||
export default {
|
||||
...config,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
};
|
||||
14
src/app.module.ts
Normal file
14
src/app.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ConfigModule } from '@nestjs/config'
|
||||
|
||||
import { DatabaseModule } from './infra/database/database.module'
|
||||
import { ScreeningModule } from './modules/screening/infra/screening.module'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
DatabaseModule,
|
||||
ScreeningModule
|
||||
]
|
||||
})
|
||||
export class AppModule {}
|
||||
11
src/config/mongoose.config.ts
Normal file
11
src/config/mongoose.config.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { MongooseModuleOptions } from '@nestjs/mongoose'
|
||||
|
||||
export function getMongooseConfig(
|
||||
config: ConfigService
|
||||
): MongooseModuleOptions {
|
||||
return {
|
||||
uri: config.getOrThrow<string>('MONGO_URI'),
|
||||
serverSelectionTimeoutMS: 5000
|
||||
}
|
||||
}
|
||||
15
src/infra/database/database.module.ts
Normal file
15
src/infra/database/database.module.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { MongooseModule } from '@nestjs/mongoose'
|
||||
|
||||
import { getMongooseConfig } from '../../config/mongoose.config'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forRootAsync({
|
||||
useFactory: getMongooseConfig,
|
||||
inject: [ConfigService]
|
||||
})
|
||||
]
|
||||
})
|
||||
export class DatabaseModule {}
|
||||
34
src/main.ts
Normal file
34
src/main.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { NestFactory } from '@nestjs/core'
|
||||
import { MicroserviceOptions, Transport } from '@nestjs/microservices'
|
||||
import { PROTO_PATHS } from '@teacinema/contracts'
|
||||
|
||||
import { AppModule } from './app.module'
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule)
|
||||
const config = app.get(ConfigService)
|
||||
const host = config.getOrThrow<string>('GRPC_HOST')
|
||||
const port = config.getOrThrow<string>('GRPC_PORT')
|
||||
const url = `${host}:${port}`
|
||||
|
||||
app.connectMicroservice<MicroserviceOptions>({
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url,
|
||||
package: ['screening.v1'],
|
||||
protoPath: [PROTO_PATHS.SCREENING],
|
||||
loader: {
|
||||
keepCase: false,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await app.startAllMicroservices()
|
||||
await app.init()
|
||||
}
|
||||
bootstrap()
|
||||
@ -0,0 +1,61 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { RpcException } from '@nestjs/microservices'
|
||||
import { RpcStatus } from '@teacinema/common'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
import { ScreeningEntity } from '../../domain/entities/screening.entity'
|
||||
import { HallPort } from '../../domain/ports/hall.port'
|
||||
import { ScreeningRepositoryPort } from '../../domain/ports/screening.repository.port'
|
||||
|
||||
@Injectable()
|
||||
export class CreateScreeningUsecase {
|
||||
public constructor(
|
||||
private readonly repository: ScreeningRepositoryPort,
|
||||
private readonly hallPort: HallPort
|
||||
) {}
|
||||
|
||||
public async execute(input: {
|
||||
movieId: string
|
||||
hallId: string
|
||||
startAt: string | Date
|
||||
endAt: string | Date
|
||||
}) {
|
||||
const start = new Date(input.startAt)
|
||||
const end = new Date(input.endAt)
|
||||
|
||||
if (!(start < end)) {
|
||||
throw new RpcException({
|
||||
code: RpcStatus.INVALID_ARGUMENTS,
|
||||
details: 'Invalid time range'
|
||||
})
|
||||
}
|
||||
|
||||
const hall = await this.hallPort.findById(input.hallId)
|
||||
if (!hall) {
|
||||
throw new RpcException({
|
||||
code: RpcStatus.NOT_FOUND,
|
||||
details: 'Hall not found'
|
||||
})
|
||||
}
|
||||
|
||||
const overlap = await this.repository.findOverlap(hall.id, start, end)
|
||||
if (overlap) {
|
||||
throw new RpcException({
|
||||
code: RpcStatus.ALREADY_EXISTS,
|
||||
details: 'Screening overlap with existing screening'
|
||||
})
|
||||
}
|
||||
|
||||
const screening = new ScreeningEntity(
|
||||
nanoid(),
|
||||
hall.id,
|
||||
input.movieId,
|
||||
start,
|
||||
end
|
||||
)
|
||||
|
||||
await this.repository.create(screening)
|
||||
|
||||
return { ok: true }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { RpcException } from '@nestjs/microservices'
|
||||
import { RpcStatus } from '@teacinema/common'
|
||||
|
||||
import { HallPort } from '../../domain/ports/hall.port'
|
||||
import { MoviePort } from '../../domain/ports/movie.port'
|
||||
import { ScreeningRepositoryPort } from '../../domain/ports/screening.repository.port'
|
||||
import { SeatPort } from '../../domain/ports/seat.port'
|
||||
import { TheaterPort } from '../../domain/ports/theater.port'
|
||||
|
||||
@Injectable()
|
||||
export class GetScreeningUsecase {
|
||||
public constructor(
|
||||
private readonly repository: ScreeningRepositoryPort,
|
||||
private readonly hallPort: HallPort,
|
||||
private readonly theaterPort: TheaterPort,
|
||||
private readonly moviePort: MoviePort,
|
||||
private readonly seatPort: SeatPort
|
||||
) {}
|
||||
|
||||
public async execute(id: string) {
|
||||
const screening = await this.repository.findById(id)
|
||||
|
||||
if (!screening) {
|
||||
throw new RpcException({
|
||||
code: RpcStatus.NOT_FOUND,
|
||||
datails: 'Screening not found'
|
||||
})
|
||||
}
|
||||
|
||||
const [hall, movie] = await Promise.all([
|
||||
this.hallPort.findById(screening.hallId),
|
||||
this.moviePort.findById(screening.movieId)
|
||||
])
|
||||
|
||||
if (!hall || !movie) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [theater, seatTypes] = await Promise.all([
|
||||
this.theaterPort.findById(hall.theaterId),
|
||||
this.seatPort.listSeatTypes(screening.hallId, screening.id)
|
||||
])
|
||||
if (!theater) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: screening.id,
|
||||
startAt: screening.startAt,
|
||||
endAt: screening.endAt,
|
||||
hall,
|
||||
theater,
|
||||
movie,
|
||||
seatTypes
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
|
||||
import { resolveDayRange } from '../../../../shared/utils/resolve-day-range'
|
||||
import { Hall, HallPort } from '../../domain/ports/hall.port'
|
||||
import { Movie, MoviePort } from '../../domain/ports/movie.port'
|
||||
import { ScreeningRepositoryPort } from '../../domain/ports/screening.repository.port'
|
||||
import { SeatPort } from '../../domain/ports/seat.port'
|
||||
import { Theater, TheaterPort } from '../../domain/ports/theater.port'
|
||||
|
||||
@Injectable()
|
||||
export class GetScreeningsUsecase {
|
||||
public constructor(
|
||||
private readonly repository: ScreeningRepositoryPort,
|
||||
private readonly hallPort: HallPort,
|
||||
private readonly theaterPort: TheaterPort,
|
||||
private readonly moviePort: MoviePort,
|
||||
private readonly seatPort: SeatPort
|
||||
) {}
|
||||
|
||||
public async execute(input: { date?: string; theaterId?: string }) {
|
||||
const { start, end } = resolveDayRange(input.date)
|
||||
|
||||
let hallIds: string[] | undefined
|
||||
|
||||
if (input.theaterId) {
|
||||
const halls = await this.hallPort.listByTheater(input.theaterId)
|
||||
if (halls.length === 0) {
|
||||
return []
|
||||
}
|
||||
hallIds = halls.map(hall => hall.id)
|
||||
}
|
||||
|
||||
const screenings = await this.repository.findByDateRange(
|
||||
start,
|
||||
end,
|
||||
hallIds
|
||||
)
|
||||
|
||||
if (screenings.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const theaterCache = new Map<string, Promise<Theater | null>>()
|
||||
const hallCache = new Map<string, Promise<Hall | null>>()
|
||||
const movieCache = new Map<string, Promise<Movie | null>>()
|
||||
|
||||
const enriched = await Promise.all(
|
||||
screenings.map(async screening => {
|
||||
const hall = await this.getHallCached(screening.hallId, hallCache)
|
||||
if (!hall) {
|
||||
return null
|
||||
}
|
||||
|
||||
const theater = await this.getTheaterCached(
|
||||
hall.theaterId,
|
||||
theaterCache
|
||||
)
|
||||
if (!theater) {
|
||||
return null
|
||||
}
|
||||
|
||||
const movie = await this.getMovieCached(screening.movieId, movieCache)
|
||||
if (!movie) {
|
||||
return null
|
||||
}
|
||||
|
||||
const seatTypes = await this.seatPort.listSeatTypes(
|
||||
screening.hallId,
|
||||
screening.id
|
||||
)
|
||||
|
||||
return {
|
||||
id: screening.id,
|
||||
startAt: screening.startAt,
|
||||
endAt: screening.endAt,
|
||||
hall,
|
||||
theater,
|
||||
movie,
|
||||
seatTypes
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return enriched.filter(Boolean)
|
||||
}
|
||||
|
||||
private getTheaterCached(
|
||||
theaterId: string,
|
||||
cache: Map<string, Promise<Theater | null>>
|
||||
): Promise<Theater | null> {
|
||||
let promise = cache.get(theaterId)
|
||||
|
||||
if (!promise) {
|
||||
promise = this.theaterPort.findById(theaterId)
|
||||
cache.set(theaterId, promise)
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
private getHallCached(
|
||||
hallId: string,
|
||||
cache: Map<string, Promise<Hall | null>>
|
||||
): Promise<Hall | null> {
|
||||
let promise = cache.get(hallId)
|
||||
|
||||
if (!promise) {
|
||||
promise = this.hallPort.findById(hallId)
|
||||
cache.set(hallId, promise)
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
private getMovieCached(
|
||||
movieId: string,
|
||||
cache: Map<string, Promise<Movie | null>>
|
||||
): Promise<Movie | null> {
|
||||
let promise = cache.get(movieId)
|
||||
|
||||
if (!promise) {
|
||||
promise = this.moviePort.findById(movieId)
|
||||
cache.set(movieId, promise)
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
|
||||
import { resolveDayRange } from '../../../../shared/utils/resolve-day-range'
|
||||
import { Hall, HallPort } from '../../domain/ports/hall.port'
|
||||
import { ScreeningRepositoryPort } from '../../domain/ports/screening.repository.port'
|
||||
import { SeatPort } from '../../domain/ports/seat.port'
|
||||
import { Theater, TheaterPort } from '../../domain/ports/theater.port'
|
||||
|
||||
@Injectable()
|
||||
export class GetScreeningsByMovieUsecase {
|
||||
public constructor(
|
||||
private readonly repository: ScreeningRepositoryPort,
|
||||
private readonly theaterPort: TheaterPort,
|
||||
private readonly hallPort: HallPort,
|
||||
private readonly seatPort: SeatPort
|
||||
) {}
|
||||
|
||||
public async execute(input: { movieId: string; date?: string }) {
|
||||
const { start, end } = resolveDayRange(input.date)
|
||||
|
||||
const screenings = await this.repository.findManyByMovie(
|
||||
input.movieId,
|
||||
start,
|
||||
end
|
||||
)
|
||||
|
||||
if (screenings.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const theaterCache = new Map<string, Promise<Theater | null>>()
|
||||
const hallCache = new Map<string, Promise<Hall | null>>()
|
||||
|
||||
const enriched = await Promise.all(
|
||||
screenings.map(async screening => {
|
||||
const hall = await this.getHallCached(screening.hallId, hallCache)
|
||||
if (!hall) {
|
||||
return null
|
||||
}
|
||||
|
||||
const theater = await this.getTheaterCached(
|
||||
hall.theaterId,
|
||||
theaterCache
|
||||
)
|
||||
if (!theater) {
|
||||
return null
|
||||
}
|
||||
|
||||
const seatTypes = await this.seatPort.listSeatTypes(
|
||||
screening.hallId,
|
||||
screening.id
|
||||
)
|
||||
|
||||
return {
|
||||
id: screening.id,
|
||||
startAt: screening.startAt,
|
||||
endAt: screening.endAt,
|
||||
hall,
|
||||
theater,
|
||||
seatTypes
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return enriched.filter(Boolean)
|
||||
}
|
||||
|
||||
private getTheaterCached(
|
||||
theaterId: string,
|
||||
cache: Map<string, Promise<Theater | null>>
|
||||
): Promise<Theater | null> {
|
||||
let promise = cache.get(theaterId)
|
||||
|
||||
if (!promise) {
|
||||
promise = this.theaterPort.findById(theaterId)
|
||||
cache.set(theaterId, promise)
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
private getHallCached(
|
||||
hallId: string,
|
||||
cache: Map<string, Promise<Hall | null>>
|
||||
): Promise<Hall | null> {
|
||||
let promise = cache.get(hallId)
|
||||
|
||||
if (!promise) {
|
||||
promise = this.hallPort.findById(hallId)
|
||||
cache.set(hallId, promise)
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
export class ScreeningEntity {
|
||||
public constructor(
|
||||
public readonly id: string,
|
||||
public readonly hallId: string,
|
||||
public readonly movieId: string,
|
||||
public readonly startAt: Date,
|
||||
public readonly endAt: Date
|
||||
) {}
|
||||
}
|
||||
11
src/modules/screening/domain/ports/hall.port.ts
Normal file
11
src/modules/screening/domain/ports/hall.port.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface Hall {
|
||||
id: string
|
||||
name: string
|
||||
theaterId: string
|
||||
}
|
||||
|
||||
export abstract class HallPort {
|
||||
public abstract findById(id: string): Promise<Hall | null>
|
||||
|
||||
public abstract listByTheater(theaterId: string): Promise<Hall[]>
|
||||
}
|
||||
12
src/modules/screening/domain/ports/movie.port.ts
Normal file
12
src/modules/screening/domain/ports/movie.port.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export interface Movie {
|
||||
id: string
|
||||
title: string
|
||||
slug: string
|
||||
poster: string
|
||||
banner: string
|
||||
duration: string
|
||||
}
|
||||
|
||||
export abstract class MoviePort {
|
||||
public abstract findById(id: string): Promise<Movie | null>
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import { ScreeningEntity } from '../entities/screening.entity'
|
||||
|
||||
export abstract class ScreeningRepositoryPort {
|
||||
public abstract findById(id: string): Promise<ScreeningEntity | null>
|
||||
public abstract findOverlap(
|
||||
hallId: string,
|
||||
startAt: Date,
|
||||
endAt: Date
|
||||
): Promise<ScreeningEntity | null>
|
||||
|
||||
public abstract findByDateRange(
|
||||
dayStart: Date,
|
||||
dayEnd: Date,
|
||||
hallIds?: string[]
|
||||
): Promise<ScreeningEntity[]>
|
||||
|
||||
public abstract findManyByMovie(
|
||||
movieId: string,
|
||||
dateStart?: Date,
|
||||
dateEnd?: Date
|
||||
): Promise<ScreeningEntity[]>
|
||||
|
||||
public abstract create(screening: ScreeningEntity): Promise<ScreeningEntity>
|
||||
}
|
||||
11
src/modules/screening/domain/ports/seat.port.ts
Normal file
11
src/modules/screening/domain/ports/seat.port.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface SeatType {
|
||||
type: string
|
||||
price: number
|
||||
}
|
||||
|
||||
export abstract class SeatPort {
|
||||
public abstract listSeatTypes(
|
||||
hallId: string,
|
||||
screeningId: string
|
||||
): Promise<SeatType[]>
|
||||
}
|
||||
9
src/modules/screening/domain/ports/theater.port.ts
Normal file
9
src/modules/screening/domain/ports/theater.port.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface Theater {
|
||||
id: string
|
||||
name: string
|
||||
address: string
|
||||
}
|
||||
|
||||
export abstract class TheaterPort {
|
||||
public abstract findById(id: string): Promise<Theater | null>
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
import { InjectModel } from '@nestjs/mongoose'
|
||||
import { ClientSession, Model, QueryFilter } from 'mongoose'
|
||||
|
||||
import { ScreeningEntity } from '../../../domain/entities/screening.entity'
|
||||
import { ScreeningRepositoryPort } from '../../../domain/ports/screening.repository.port'
|
||||
import { ScreeningMapper } from '../../mapper/screening.mapper'
|
||||
import { ScreeningDocument, ScreeningModel } from '../schemas/screening.schema'
|
||||
|
||||
export class ScreeningMongoRepository implements ScreeningRepositoryPort {
|
||||
public constructor(
|
||||
@InjectModel(ScreeningModel.name)
|
||||
private readonly screening: Model<ScreeningDocument>
|
||||
) {}
|
||||
|
||||
public async findOverlap(
|
||||
hallId: string,
|
||||
startAt: Date,
|
||||
endAt: Date
|
||||
): Promise<ScreeningEntity | null> {
|
||||
const doc = await this.screening
|
||||
.findOne({
|
||||
hallId,
|
||||
startAt: { $lt: endAt },
|
||||
endAt: { $gt: startAt }
|
||||
})
|
||||
.lean()
|
||||
.exec()
|
||||
|
||||
return doc ? ScreeningMapper.toEntity(doc) : null
|
||||
}
|
||||
|
||||
public async create(
|
||||
screening: ScreeningEntity,
|
||||
session?: ClientSession
|
||||
): Promise<ScreeningEntity> {
|
||||
const persistence = ScreeningMapper.toPersistence(screening)
|
||||
const doc = new this.screening(persistence)
|
||||
await doc.save({ session })
|
||||
|
||||
return ScreeningMapper.toEntity(doc.toObject())
|
||||
}
|
||||
|
||||
public async findById(id: string): Promise<ScreeningEntity | null> {
|
||||
const doc = await this.screening.findById(id).lean().exec()
|
||||
|
||||
return doc ? ScreeningMapper.toEntity(doc) : null
|
||||
}
|
||||
|
||||
public async findByDateRange(
|
||||
dayStart: Date,
|
||||
dayEnd: Date,
|
||||
hallIds?: string[]
|
||||
): Promise<ScreeningEntity[]> {
|
||||
const query: QueryFilter<ScreeningDocument> = {
|
||||
startAt: { $gte: dayStart, $lte: dayEnd },
|
||||
...(hallIds?.length ? { hallId: { $in: hallIds } } : {})
|
||||
}
|
||||
|
||||
const docs = await this.screening
|
||||
.find(query)
|
||||
.sort({ startAt: 1 })
|
||||
.lean()
|
||||
.exec()
|
||||
|
||||
return docs.map(doc => ScreeningMapper.toEntity(doc))
|
||||
}
|
||||
|
||||
public async findManyByMovie(
|
||||
movieId: string,
|
||||
dateStart?: Date,
|
||||
dateEnd?: Date
|
||||
): Promise<ScreeningEntity[]> {
|
||||
const query: QueryFilter<ScreeningDocument> = {
|
||||
movieId,
|
||||
...(dateStart && dateEnd
|
||||
? { startAt: { $gte: dateStart, $lte: dateEnd } }
|
||||
: {})
|
||||
}
|
||||
|
||||
const docs = await this.screening
|
||||
.find(query)
|
||||
.sort({ startAt: 1 })
|
||||
.lean()
|
||||
.exec()
|
||||
return docs.map(doc => ScreeningMapper.toEntity(doc))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
|
||||
import { HydratedDocument } from 'mongoose'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
export type ScreeningDocument = HydratedDocument<ScreeningModel>
|
||||
|
||||
@Schema({
|
||||
_id: false,
|
||||
collection: 'screenings',
|
||||
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
|
||||
})
|
||||
export class ScreeningModel {
|
||||
@Prop({
|
||||
type: String,
|
||||
default: nanoid,
|
||||
required: true
|
||||
})
|
||||
public _id: string
|
||||
|
||||
@Prop({
|
||||
type: String,
|
||||
required: true,
|
||||
alias: 'hall_id'
|
||||
})
|
||||
public hallId: string
|
||||
|
||||
@Prop({
|
||||
type: String,
|
||||
required: true,
|
||||
alias: 'movie_id'
|
||||
})
|
||||
public movieId: string
|
||||
|
||||
@Prop({
|
||||
type: Date,
|
||||
required: true,
|
||||
alias: 'start_at'
|
||||
})
|
||||
public startAt: Date
|
||||
|
||||
@Prop({
|
||||
type: Date,
|
||||
required: true,
|
||||
alias: 'end_at'
|
||||
})
|
||||
public endAt: Date
|
||||
}
|
||||
|
||||
export const ScreeningSchema = SchemaFactory.createForClass(ScreeningModel)
|
||||
|
||||
ScreeningSchema.index({ hallId: 1 })
|
||||
ScreeningSchema.index({ movieId: 1 })
|
||||
30
src/modules/screening/infra/grpc/hall.grpc.adapter.ts
Normal file
30
src/modules/screening/infra/grpc/hall.grpc.adapter.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Inject, OnModuleInit } from '@nestjs/common'
|
||||
import type { ClientGrpc } from '@nestjs/microservices'
|
||||
import { HallServiceClient } from '@teacinema/contracts/gen/ts/Hall'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { Hall, HallPort } from '../../domain/ports/hall.port'
|
||||
|
||||
export class HallGrpcAdapter implements HallPort, OnModuleInit {
|
||||
public service: HallServiceClient
|
||||
|
||||
public constructor(
|
||||
@Inject('HALL_PACKAGE') private readonly client: ClientGrpc
|
||||
) {}
|
||||
|
||||
public onModuleInit() {
|
||||
this.service = this.client.getService<HallServiceClient>('HallService')
|
||||
}
|
||||
|
||||
public async findById(id: string): Promise<Hall | null> {
|
||||
const { hall } = await lastValueFrom(this.service.getHall({ id }))
|
||||
return hall ?? null
|
||||
}
|
||||
|
||||
public async listByTheater(id: string): Promise<Hall[]> {
|
||||
const { halls } = await lastValueFrom(
|
||||
this.service.listHallsByTheater({ theaterId: id })
|
||||
)
|
||||
return halls ?? []
|
||||
}
|
||||
}
|
||||
23
src/modules/screening/infra/grpc/movie.grpc.adapter.ts
Normal file
23
src/modules/screening/infra/grpc/movie.grpc.adapter.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Inject, OnModuleInit } from '@nestjs/common'
|
||||
import type { ClientGrpc } from '@nestjs/microservices'
|
||||
import { MovieServiceClient } from '@teacinema/contracts/gen/ts/Movie'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { Movie, MoviePort } from '../../domain/ports/movie.port'
|
||||
|
||||
export class MovieGrpcAdapter implements MoviePort, OnModuleInit {
|
||||
public service: MovieServiceClient
|
||||
|
||||
public constructor(
|
||||
@Inject('MOVIE_PACKAGE') private readonly client: ClientGrpc
|
||||
) {}
|
||||
|
||||
public onModuleInit() {
|
||||
this.service = this.client.getService<MovieServiceClient>('MovieService')
|
||||
}
|
||||
|
||||
public async findById(id: string): Promise<Movie | null> {
|
||||
const { movie } = await lastValueFrom(this.service.getMovie({ id }))
|
||||
return (movie as unknown as Movie) ?? null
|
||||
}
|
||||
}
|
||||
41
src/modules/screening/infra/grpc/seat.grpc.adapter.ts
Normal file
41
src/modules/screening/infra/grpc/seat.grpc.adapter.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { Inject, OnModuleInit } from '@nestjs/common'
|
||||
import type { ClientGrpc } from '@nestjs/microservices'
|
||||
import { SeatServiceClient } from '@teacinema/contracts/gen/ts/Seat'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { SeatPort, SeatType } from '../../domain/ports/seat.port'
|
||||
|
||||
export class SeatGrpcAdapter implements SeatPort, OnModuleInit {
|
||||
public service: SeatServiceClient
|
||||
|
||||
public constructor(
|
||||
@Inject('SEAT_PACKAGE') private readonly client: ClientGrpc
|
||||
) {}
|
||||
|
||||
public onModuleInit() {
|
||||
this.service = this.client.getService<SeatServiceClient>('SeatService')
|
||||
}
|
||||
|
||||
public async listSeatTypes(
|
||||
hallId: string,
|
||||
screeningId: string
|
||||
): Promise<SeatType[]> {
|
||||
const res = await lastValueFrom(
|
||||
this.service.listSeatsByHall({ hallId, screeningId })
|
||||
)
|
||||
const map = new Map<string, SeatType>()
|
||||
|
||||
for (const seat of res.seats) {
|
||||
const key = `${seat.type}-${seat.price}`
|
||||
|
||||
if (!map.has(key)) {
|
||||
map.set(key, {
|
||||
type: seat.type,
|
||||
price: seat.price
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(map.values())
|
||||
}
|
||||
}
|
||||
24
src/modules/screening/infra/grpc/theater.grpc.adapter.ts
Normal file
24
src/modules/screening/infra/grpc/theater.grpc.adapter.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Inject, OnModuleInit } from '@nestjs/common'
|
||||
import type { ClientGrpc } from '@nestjs/microservices'
|
||||
import { TheaterServiceClient } from '@teacinema/contracts/gen/ts/theater'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { Theater, TheaterPort } from '../../domain/ports/theater.port'
|
||||
|
||||
export class TheaterGrpcAdapter implements TheaterPort, OnModuleInit {
|
||||
public service: TheaterServiceClient
|
||||
|
||||
public constructor(
|
||||
@Inject('THEATER_PACKAGE') private readonly client: ClientGrpc
|
||||
) {}
|
||||
|
||||
public onModuleInit() {
|
||||
this.service =
|
||||
this.client.getService<TheaterServiceClient>('TheaterService')
|
||||
}
|
||||
|
||||
public async findById(id: string): Promise<Theater | null> {
|
||||
const { theater } = await lastValueFrom(this.service.getTheater({ id }))
|
||||
return theater ?? null
|
||||
}
|
||||
}
|
||||
31
src/modules/screening/infra/mapper/screening.mapper.ts
Normal file
31
src/modules/screening/infra/mapper/screening.mapper.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { ScreeningEntity } from '../../domain/entities/screening.entity'
|
||||
|
||||
interface ScreeningPersistence {
|
||||
_id: string
|
||||
hallId: string
|
||||
movieId: string
|
||||
startAt: Date
|
||||
endAt: Date
|
||||
}
|
||||
|
||||
export class ScreeningMapper {
|
||||
public static toEntity(raw: ScreeningPersistence): ScreeningEntity {
|
||||
return new ScreeningEntity(
|
||||
raw._id.toString(),
|
||||
raw.hallId,
|
||||
raw.movieId,
|
||||
raw.startAt,
|
||||
raw.endAt
|
||||
)
|
||||
}
|
||||
|
||||
public static toPersistence(entity: ScreeningEntity): ScreeningPersistence {
|
||||
return {
|
||||
_id: entity.id,
|
||||
hallId: entity.hallId,
|
||||
movieId: entity.movieId,
|
||||
startAt: entity.startAt,
|
||||
endAt: entity.endAt
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/modules/screening/infra/screening.module.ts
Normal file
102
src/modules/screening/infra/screening.module.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ClientsModule, Transport } from '@nestjs/microservices'
|
||||
import { MongooseModule } from '@nestjs/mongoose'
|
||||
import { PROTO_PATHS } from '@teacinema/contracts'
|
||||
|
||||
import { CreateScreeningUsecase } from '../application/commands/create-screening.usecase'
|
||||
import { GetScreeningUsecase } from '../application/queries/get-screening.usecase'
|
||||
import { GetScreeningsUsecase } from '../application/queries/get-screenings.usecase'
|
||||
import { GetScreeningsByMovieUsecase } from '../application/queries/get=screenings-by-movie.usecase'
|
||||
import { HallPort } from '../domain/ports/hall.port'
|
||||
import { MoviePort } from '../domain/ports/movie.port'
|
||||
import { ScreeningRepositoryPort } from '../domain/ports/screening.repository.port'
|
||||
import { SeatPort } from '../domain/ports/seat.port'
|
||||
import { TheaterPort } from '../domain/ports/theater.port'
|
||||
import { ScreeningGrpcController } from '../interfaces/grpc/screening.grpc.controller'
|
||||
|
||||
import { ScreeningMongoRepository } from './dataabse/repositories/screening.mongo.repository'
|
||||
import {
|
||||
ScreeningModel,
|
||||
ScreeningSchema
|
||||
} from './dataabse/schemas/screening.schema'
|
||||
import { HallGrpcAdapter } from './grpc/hall.grpc.adapter'
|
||||
import { MovieGrpcAdapter } from './grpc/movie.grpc.adapter'
|
||||
import { SeatGrpcAdapter } from './grpc/seat.grpc.adapter'
|
||||
import { TheaterGrpcAdapter } from './grpc/theater.grpc.adapter'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([
|
||||
{
|
||||
name: ScreeningModel.name,
|
||||
schema: ScreeningSchema
|
||||
}
|
||||
]),
|
||||
ClientsModule.register([
|
||||
{
|
||||
name: 'THEATER_PACKAGE',
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
package: 'theater.v1',
|
||||
protoPath: PROTO_PATHS.THEATER,
|
||||
url: 'localhost:50054'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'HALL_PACKAGE',
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
package: 'hall.v1',
|
||||
protoPath: PROTO_PATHS.HALL,
|
||||
url: 'localhost:50054'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'SEAT_PACKAGE',
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
package: 'seat.v1',
|
||||
protoPath: PROTO_PATHS.SEAT,
|
||||
url: 'localhost:50054'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MOVIE_PACKAGE',
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
package: 'movie.v1',
|
||||
protoPath: PROTO_PATHS.MOVIE,
|
||||
url: 'localhost:50053'
|
||||
}
|
||||
}
|
||||
])
|
||||
],
|
||||
controllers: [ScreeningGrpcController],
|
||||
providers: [
|
||||
{
|
||||
provide: ScreeningRepositoryPort,
|
||||
useClass: ScreeningMongoRepository
|
||||
},
|
||||
{
|
||||
provide: HallPort,
|
||||
useClass: HallGrpcAdapter
|
||||
},
|
||||
{
|
||||
provide: SeatPort,
|
||||
useClass: SeatGrpcAdapter
|
||||
},
|
||||
{
|
||||
provide: MoviePort,
|
||||
useClass: MovieGrpcAdapter
|
||||
},
|
||||
{
|
||||
provide: TheaterPort,
|
||||
useClass: TheaterGrpcAdapter
|
||||
},
|
||||
CreateScreeningUsecase,
|
||||
GetScreeningsByMovieUsecase,
|
||||
GetScreeningUsecase,
|
||||
GetScreeningsUsecase
|
||||
]
|
||||
})
|
||||
export class ScreeningModule {}
|
||||
@ -0,0 +1,61 @@
|
||||
import { Controller } from '@nestjs/common'
|
||||
import { GrpcMethod } from '@nestjs/microservices'
|
||||
import type {
|
||||
CreateScreeningRequest,
|
||||
CreateScreeningResponse,
|
||||
GetScreeningRequest,
|
||||
GetScreeningResponse,
|
||||
GetScreeningsByMovieRequest,
|
||||
GetScreeningsByMovieResponse,
|
||||
GetScreeningsRequest,
|
||||
GetScreeningsResponse
|
||||
} from '@teacinema/contracts/gen/ts/screening'
|
||||
|
||||
import { CreateScreeningUsecase } from '../../application/commands/create-screening.usecase'
|
||||
import { GetScreeningUsecase } from '../../application/queries/get-screening.usecase'
|
||||
import { GetScreeningsUsecase } from '../../application/queries/get-screenings.usecase'
|
||||
import { GetScreeningsByMovieUsecase } from '../../application/queries/get=screenings-by-movie.usecase'
|
||||
|
||||
@Controller()
|
||||
export class ScreeningGrpcController {
|
||||
public constructor(
|
||||
private readonly getAllByMovieUC: GetScreeningsByMovieUsecase,
|
||||
private readonly getByIdUC: GetScreeningUsecase,
|
||||
private readonly getAllUC: GetScreeningsUsecase,
|
||||
private readonly createUC: CreateScreeningUsecase
|
||||
) {}
|
||||
|
||||
@GrpcMethod('ScreeningService', 'CreateScreening')
|
||||
public async create(
|
||||
data: CreateScreeningRequest
|
||||
): Promise<CreateScreeningResponse> {
|
||||
return this.createUC.execute(data)
|
||||
}
|
||||
|
||||
@GrpcMethod('ScreeningService', 'GetScreening')
|
||||
public async getById(
|
||||
data: GetScreeningRequest
|
||||
): Promise<GetScreeningResponse> {
|
||||
const screening = await this.getByIdUC.execute(data.id)
|
||||
// @ts-expect-error -- todo
|
||||
return { screening }
|
||||
}
|
||||
|
||||
@GrpcMethod('ScreeningService', 'GetScreenings')
|
||||
public async getAll(
|
||||
data: GetScreeningsRequest
|
||||
): Promise<GetScreeningsResponse> {
|
||||
const screenings = await this.getAllUC.execute(data)
|
||||
// @ts-expect-error -- todo
|
||||
return { screenings }
|
||||
}
|
||||
|
||||
@GrpcMethod('ScreeningService', 'GetScreeningsByMovie')
|
||||
public async getAllByMovie(
|
||||
data: GetScreeningsByMovieRequest
|
||||
): Promise<GetScreeningsByMovieResponse> {
|
||||
const screenings = await this.getAllByMovieUC.execute(data)
|
||||
// @ts-expect-error -- todo
|
||||
return { screenings }
|
||||
}
|
||||
}
|
||||
11
src/shared/utils/resolve-day-range.ts
Normal file
11
src/shared/utils/resolve-day-range.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export function resolveDayRange(dateString?: string) {
|
||||
const date = dateString ? new Date(dateString) : new Date()
|
||||
|
||||
const start = new Date(date)
|
||||
start.setHours(0, 0, 0, 0)
|
||||
|
||||
const end = new Date(start)
|
||||
end.setHours(23, 59, 59, 999)
|
||||
|
||||
return { start, end }
|
||||
}
|
||||
4
tsconfig.build.json
Normal file
4
tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
25
tsconfig.json
Normal file
25
tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"resolvePackageJsonExports": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2023",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user