Module Content
Module de gestion de contenu pour tous les types de medias de la MyTelevision API.
Vue d'ensemble
Le module Content gere :
- Films (catalogue avec enrichissement TMDb)
- Series TV (avec saisons et episodes)
- Chaines TV en direct (Live TV)
- Stations Radio
- Podcasts (collections et episodes)
- Replays (rediffusions)
- Articles d'actualite (News)
- Evenements en direct (Live Events)
Architecture
Types de Contenu
Films (Movies)
model Movie {
id String @id @default(uuid())
tmdbId Int? @unique
title String
titleEn String?
overview String? @db.Text
overviewEn String? @db.Text
posterUrl String?
backdropUrl String?
releaseDate DateTime?
runtime Int?
voteAverage Float?
genres String[]
status ContentStatus @default(DRAFT)
accessType AccessType @default(FREE)
// Streaming
streamUrl String?
hlsUrl String?
qualities VideoQuality[]
// Engagement
viewCount Int @default(0)
likeCount Int @default(0)
dislikeCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Series TV
model Series {
id String @id @default(uuid())
tmdbId Int? @unique
title String
titleEn String?
overview String? @db.Text
overviewEn String? @db.Text
posterUrl String?
backdropUrl String?
firstAirDate DateTime?
status ContentStatus @default(DRAFT)
accessType AccessType @default(FREE)
seasons Season[]
viewCount Int @default(0)
likeCount Int @default(0)
dislikeCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Season {
id String @id @default(uuid())
seriesId String
series Series @relation(...)
seasonNumber Int
name String?
overview String? @db.Text
posterUrl String?
airDate DateTime?
episodes Episode[]
}
model Episode {
id String @id @default(uuid())
seasonId String
season Season @relation(...)
episodeNumber Int
name String
nameEn String?
overview String? @db.Text
stillUrl String?
airDate DateTime?
runtime Int?
streamUrl String?
hlsUrl String?
viewCount Int @default(0)
}
Chaines TV en Direct
model LiveTvChannel {
id String @id @default(uuid())
name String
nameEn String?
description String? @db.Text
logoUrl String?
streamUrl String
hlsUrl String?
category String?
country String?
language String?
status ContentStatus @default(PUBLISHED)
accessType AccessType @default(FREE)
isLive Boolean @default(true)
viewCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Structure des Fichiers
src/
├── application/
│ ├── dtos/
│ │ ├── movies/
│ │ ├── series/
│ │ ├── live-tv/
│ │ ├── radio/
│ │ ├── podcasts/
│ │ ├── replays/
│ │ ├── news/
│ │ └── events/
│ └── services/
│ ├── movies.service.ts
│ ├── series.service.ts
│ ├── live-tv.service.ts
│ ├── radio.service.ts
│ ├── podcasts.service.ts
│ ├── replays.service.ts
│ ├── news.service.ts
│ ├── events.service.ts
│ └── tmdb.service.ts
└── presentation/
└── controllers/
└── api/v2/
├── movies.controller.ts
├── series.controller.ts
├── live-tv.controller.ts
└── ...
Integration TMDb
Remplissage automatique des donnees films et series depuis TMDb :
@Injectable()
export class TmdbService {
async searchMovies(query: string, locale: string): Promise<TmdbMovie[]>;
async getMovieDetails(tmdbId: number, locale: string): Promise<TmdbMovie>;
async searchSeries(query: string, locale: string): Promise<TmdbSeries[]>;
async getSeriesDetails(tmdbId: number, locale: string): Promise<TmdbSeries>;
async getSeasonDetails(
tmdbId: number,
seasonNumber: number,
): Promise<TmdbSeason>;
}
Exemple d'utilisation
// Auto-fill d'un film depuis TMDb
@Post('auto-fill/:tmdbId')
async autoFillMovie(
@Param('tmdbId') tmdbId: number,
@Locale() locale: SupportedLocale,
) {
const tmdbData = await this.tmdbService.getMovieDetails(tmdbId, locale);
return this.moviesService.createFromTmdb(tmdbData);
}
Flux de Statut du Contenu
Types d'Acces
| Type | Description | Auth Requise |
|---|---|---|
| FREE | Disponible pour tous les utilisateurs | Non |
| PREMIUM | Necessite un abonnement premium | Oui |
| SUBSCRIPTION | Necessite un abonnement actif (tout plan) | Oui |
Strategie de Cache
| Type de Contenu | TTL du Cache | Strategie |
|---|---|---|
| Liste des films | 5 min | Cache aside |
| Details d'un film | 10 min | Cache aside |
| Liste TV en direct | 1 min | Cache aside |
| Liste des series | 5 min | Cache aside |
| Episode | 10 min | Cache aside |
Internationalisation
Le contenu supporte les champs bilingues (fr_FR et en_US) :
// Controller avec localisation
@Get(':id')
async findOne(
@Param('id') id: string,
@Locale() locale: SupportedLocale,
): Promise<MovieResponseDto> {
const movie = await this.moviesService.findById(id);
return deepLocalizeObject(movie, locale);
}
Pattern des champs bilingues
title/titleEnoverview/overviewEndescription/descriptionEnname/nameEn
Pagination
Tous les endpoints de liste supportent la pagination :
// Parametres de requete
interface PaginationQuery {
page?: number; // Default: 1
limit?: number; // Default: 20, Max: 100
sort?: string; // e.g., 'createdAt:desc'
}
// Format de reponse
interface PaginatedResponse<T> {
data: T[];
meta: {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
}
Recherche Full-Text
Recherche full-text PostgreSQL activee :
// Recherche de films
@Get('search')
async search(
@Query('q') query: string,
@Query() pagination: PaginationQuery,
): Promise<PaginatedResponse<Movie>> {
return this.moviesService.search(query, pagination);
}
Tests
describe('MoviesService', () => {
it('should return paginated movies', async () => {
const result = await moviesService.findAll({ page: 1, limit: 10 });
expect(result.data).toHaveLength(10);
expect(result.meta.page).toBe(1);
expect(result.meta.hasNext).toBe(true);
});
it('should filter by status', async () => {
const result = await moviesService.findAll({
status: ContentStatus.PUBLISHED,
});
result.data.forEach((movie) => {
expect(movie.status).toBe(ContentStatus.PUBLISHED);
});
});
});