From 6bfd167122eea6ccc739e8f5583ceb3f5662c6be Mon Sep 17 00:00:00 2001 From: Chneemann Date: Thu, 1 May 2025 08:03:32 +0200 Subject: [PATCH] feat: create GenreService to load film genres from API --- backend/video_app/views.py | 10 ++++ backend/videoflix/urls.py | 3 +- .../home/categories/categories.component.ts | 49 ++++++++++--------- frontend/src/app/services/genre.service.ts | 33 +++++++++++++ 4 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 frontend/src/app/services/genre.service.ts diff --git a/backend/video_app/views.py b/backend/video_app/views.py index 3ce53f0..142b1c3 100644 --- a/backend/video_app/views.py +++ b/backend/video_app/views.py @@ -3,6 +3,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from .serializer import VideoSerializer from .models import Video +from .class_assets import FILM_GENRES from django.core.cache.backends.base import DEFAULT_TIMEOUT from django.views.decorators.cache import cache_page from django.conf import settings @@ -24,6 +25,15 @@ def video_list(request): serializer = VideoSerializer(videos, many=True) return Response(serializer.data) +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def genre_list(request): + """ + Return a list of film genres from static choices. + """ + genres = [{'code': code, 'name': name} for code, name in FILM_GENRES] + return Response(genres) + @api_view(['GET']) @permission_classes([IsAuthenticated]) def check_video_resolutions(request, id): diff --git a/backend/videoflix/urls.py b/backend/videoflix/urls.py index f880856..1574283 100644 --- a/backend/videoflix/urls.py +++ b/backend/videoflix/urls.py @@ -23,8 +23,9 @@ urlpatterns = [ # Content URLs path('videos/', video_views.video_list, name='video_list'), path('video/upload/', video_views.video_upload, name='video_upload'), + path('video/genres/', video_views.genre_list, name='genre_list'), path('video//', video_views.check_video_resolutions, name='check_video_resolutions'), - + # Users URLs path('users/', user_views.user_list, name='user_list'), path('users//', user_views.user_detail, name='user_detail'), diff --git a/frontend/src/app/components/home/categories/categories.component.ts b/frontend/src/app/components/home/categories/categories.component.ts index 3eafbf4..10c98c3 100644 --- a/frontend/src/app/components/home/categories/categories.component.ts +++ b/frontend/src/app/components/home/categories/categories.component.ts @@ -5,10 +5,13 @@ import { EventEmitter, HostListener, Input, + OnInit, Output, } from '@angular/core'; import { environment } from '../../../../environments/environment'; import { VideoListComponent } from './video-list/video-list.component'; +import { FilmGenre, GenreService } from '../../../services/genre.service'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-categories', @@ -17,7 +20,7 @@ import { VideoListComponent } from './video-list/video-list.component'; templateUrl: './categories.component.html', styleUrl: './categories.component.scss', }) -export class CategoriesComponent implements AfterViewInit { +export class CategoriesComponent implements OnInit, AfterViewInit { @Input() videos: any[] = []; @Input() currentVideo: number = 0; @Input() favoriteVideos: any[] = []; @@ -26,32 +29,34 @@ export class CategoriesComponent implements AfterViewInit { environmentBaseUrl: string = environment.baseUrl; isScrollable: boolean = false; + filmGenres: FilmGenre[] = []; - filmGenres = [ - { code: 'action', name: 'Action' }, - { code: 'adventure', name: 'Adventure' }, - { code: 'animation', name: 'Animation' }, - { code: 'anime', name: 'Anime' }, - { code: 'comedy', name: 'Comedy' }, - { code: 'crime', name: 'Crime' }, - { code: 'documentary', name: 'Documentary' }, - { code: 'drama', name: 'Drama' }, - { code: 'fantasy', name: 'Fantasy' }, - { code: 'horror', name: 'Horror' }, - { code: 'musical', name: 'Musical' }, - { code: 'mystery', name: 'Mystery' }, - { code: 'other', name: 'Miscellaneous' }, - { code: 'romance', name: 'Romance' }, - { code: 'science_fiction', name: 'Science Fiction' }, - { code: 'thriller', name: 'Thriller' }, - { code: 'war', name: 'War' }, - { code: 'western', name: 'Western' }, - ]; + private destroy$ = new Subject(); - ngAfterViewInit() { + constructor(private genreService: GenreService) {} + + ngOnInit(): void { + this.getFilmGenreNames(); + } + + ngAfterViewInit(): void { this.checkScroll(); } + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + getFilmGenreNames(): void { + this.genreService + .getGenres() + .pipe(takeUntil(this.destroy$)) + .subscribe((genres) => { + this.filmGenres = genres; + }); + } + openCurrentVideo(videoId: number) { this.currentVideo = videoId; this.currentVideoId.emit(videoId); diff --git a/frontend/src/app/services/genre.service.ts b/frontend/src/app/services/genre.service.ts new file mode 100644 index 0000000..626b53c --- /dev/null +++ b/frontend/src/app/services/genre.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { Observable, of, tap } from 'rxjs'; +import { ApiService } from './api.service'; + +export interface FilmGenre { + code: string; + name: string; +} + +@Injectable({ + providedIn: 'root', +}) +export class GenreService { + private genres: FilmGenre[] = []; + + constructor(private apiService: ApiService) {} + + /** + * Returns the genres either from the cache or from the API endpoint. + * @returns observable of movie genres + */ + getGenres(): Observable { + if (this.genres.length > 0) { + return of(this.genres); + } + + return this.apiService.get('/video/genres/', true).pipe( + tap((data) => { + this.genres = data; + }) + ); + } +}