Aller au contenu principal

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

TypeDescriptionAuth Requise
FREEDisponible pour tous les utilisateursNon
PREMIUMNecessite un abonnement premiumOui
SUBSCRIPTIONNecessite un abonnement actif (tout plan)Oui

Strategie de Cache

Type de ContenuTTL du CacheStrategie
Liste des films5 minCache aside
Details d'un film10 minCache aside
Liste TV en direct1 minCache aside
Liste des series5 minCache aside
Episode10 minCache 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 / titleEn
  • overview / overviewEn
  • description / descriptionEn
  • name / 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);
});
});
});

Documentation associee