diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml index 02385ce..73425c6 100644 --- a/.gitea/workflows/docker.yml +++ b/.gitea/workflows/docker.yml @@ -23,6 +23,5 @@ jobs: run: | docker buildx build \ --platform linux/amd64,linux/arm64 \ - --add-host=registry-1.docker.io:18.204.181.58 \ -t ${{ secrets.DOMAIN }}/${{ secrets.OWNER }}/${{ secrets.REPO }}/server:latest \ --push . diff --git a/server/cinema/.gitignore b/server/cinema/.gitignore index cd2de90..773a7ac 100644 --- a/server/cinema/.gitignore +++ b/server/cinema/.gitignore @@ -5,4 +5,4 @@ *.freezed.dart *.config.dart *.log -assets/cache +**/cache diff --git a/server/cinema/Dockerfile b/server/cinema/Dockerfile index 4e168a7..e67d250 100644 --- a/server/cinema/Dockerfile +++ b/server/cinema/Dockerfile @@ -1,5 +1,5 @@ # Use latest stable channel SDK. -FROM dart:3.10.1 AS build +FROM dart:stable AS build # Resolve app dependencies. WORKDIR /app @@ -17,7 +17,7 @@ RUN APP_VERSION=$(awk '/^version:/{print $2}' pubspec.yaml) && \ FROM scratch COPY --from=build /runtime/ / COPY --from=build /app/bin/server /app/bin/ -COPY assets / +COPY assets /assets # Start server. EXPOSE 3000 diff --git a/server/cinema/Makefile b/server/cinema/Makefile index 43900bb..bbedf76 100644 --- a/server/cinema/Makefile +++ b/server/cinema/Makefile @@ -1,3 +1,7 @@ +VERSION := $(shell grep 'version:' pubspec.yaml | sed 's/version: //') +MAJOR := $(shell echo $(VERSION) | cut -d. -f1) +MAJOR_MINOR := $(shell echo $(VERSION) | cut -d. -f1,2) + build: dart run build_runner build --delete-conflicting-outputs @@ -5,4 +9,18 @@ watch: dart run build_runner watch --delete-conflicting-outputs docker: build - docker build -t cinema-display . + docker build \ + -t cr.mars3142.io/model-railway/cinema-display:latest \ + -t cr.mars3142.io/model-railway/cinema-display:$(MAJOR) \ + -t cr.mars3142.io/model-railway/cinema-display:$(MAJOR_MINOR) \ + -t cr.mars3142.io/model-railway/cinema-display:$(VERSION) \ + . + +multi: build + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t cr.mars3142.io/model-railway/cinema-display:latest \ + -t cr.mars3142.io/model-railway/cinema-display:$(MAJOR) \ + -t cr.mars3142.io/model-railway/cinema-display:$(MAJOR_MINOR) \ + -t cr.mars3142.io/model-railway/cinema-display:$(VERSION) \ + . diff --git a/server/cinema/bin/server.dart b/server/cinema/bin/server.dart index 2b5c96f..84d5ba1 100644 --- a/server/cinema/bin/server.dart +++ b/server/cinema/bin/server.dart @@ -1,5 +1,7 @@ +import 'dart:async'; import 'dart:io'; +import 'package:cinema/common/env_not_found_exception.dart'; import 'package:cinema/feature/middlewares/cors.dart'; import 'package:cinema/feature/poster/data/repositories/image_loader.dart'; import 'package:cinema/feature/poster/data/services/poster.service.dart'; @@ -11,40 +13,50 @@ import 'package:shelf/shelf_io.dart' as io; import 'package:shelf_router/shelf_router.dart'; void main(List args) async { - final startTime = DateTime.now(); + runZonedGuarded( + () async { + final startTime = DateTime.now(); - configureDependencies(); + configureDependencies(); - final router = Router(); - router.mount("/poster", getIt().router.call); - router.mount("/", getIt().router.call); + final router = Router(); + router.mount("/poster", getIt().router.call); + router.mount("/", getIt().router.call); - /// add middlewares (Logging, CORS) - final handler = Pipeline().addMiddleware(logRequests()).addMiddleware(cors()).addHandler(router.call); + /// add middlewares (Logging, CORS) + final handler = Pipeline().addMiddleware(logRequests()).addMiddleware(cors()).addHandler(router.call); - // For running in containers, we respect the PORT environment variable. - final port = int.parse(Platform.environment['PORT'] ?? '3000'); - await io.serve(handler, InternetAddress.anyIPv4, port, poweredByHeader: null).then((server) async { - final bannerFile = File('banner.txt'); - if (await bannerFile.exists()) { - final banner = await bannerFile.readAsString(); - print(banner); - } + // For running in containers, we respect the PORT environment variable. + final port = int.parse(Platform.environment['PORT'] ?? '3000'); + await io.serve(handler, InternetAddress.anyIPv4, port, poweredByHeader: null).then((server) async { + final bannerFile = File('assets/banner.txt'); + if (await bannerFile.exists()) { + final banner = await bannerFile.readAsString(); + print(banner); + } - print('Caching current trending images...'); + print('Caching current trending images...'); - final ImageLoader loader = getIt(); - final movies = await loader.getPosterURIs(); - for (var movie in movies) { - await loader.downloadImages(movie); - } + final ImageLoader loader = getIt(); + final movies = await loader.getPosterURIs(); + for (var movie in movies) { + await loader.downloadImages(movie); + } - getIt().printVersion(); + getIt().printVersion(); - print('Serving at ${server.address.host}:${server.port}\n'); + print('Serving at ${server.address.host}:${server.port}\n'); - final time = DateTime.now().difference(startTime); - print('Server started at: $startTime'); - print('Startup time: $time\n'); - }); + final time = DateTime.now().difference(startTime); + print('Server started at: $startTime'); + print('Startup time: $time\n'); + }); + }, + (error, stackTrace) { + stderr.writeln(error); + if (error is EnvNotFoundException) { + exit(1); + } + }, + ); } diff --git a/server/cinema/lib/common/dio_module.dart b/server/cinema/lib/common/dio_module.dart index dce7314..8a7b4de 100644 --- a/server/cinema/lib/common/dio_module.dart +++ b/server/cinema/lib/common/dio_module.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:cinema/common/env_module.dart'; import 'package:cinema/feature/version/version.dart'; import 'package:dio/dio.dart'; import 'package:injectable/injectable.dart'; @@ -11,29 +12,27 @@ const dioIMAGES = 'images'; abstract class DioModule { @Named(dioAPI) @lazySingleton - Dio get apiDio => - Dio( // Der Getter selbst wird annotiert und gibt die Instanz zurück - BaseOptions( - baseUrl: 'https://api.themoviedb.org/3', - connectTimeout: const Duration(seconds: 10), - receiveTimeout: const Duration(seconds: 10), - headers: { - 'Authorization': 'Bearer ${Platform.environment['TMDB_API_KEY']}', - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'Cinema Service (v${Version().appVersion})' - }, - ), - ); + Dio apiDio(@Named(apiKey) String apiKey) => Dio( + BaseOptions( + baseUrl: 'https://api.themoviedb.org/3', + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 10), + headers: { + 'Authorization': 'Bearer $apiKey', + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'Cinema Service (v${Version().appVersion})', + }, + ), + ); @Named(dioIMAGES) @lazySingleton - Dio get imagesDio => - Dio( // Der Getter selbst wird annotiert und gibt die Instanz zurück - BaseOptions( - baseUrl: 'https://image.tmdb.org/t/p/original', - connectTimeout: const Duration(seconds: 10), - receiveTimeout: const Duration(seconds: 10), - ), - ); + Dio get imagesDio => Dio( + BaseOptions( + baseUrl: 'https://image.tmdb.org/t/p/original', + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 10), + ), + ); } diff --git a/server/cinema/lib/common/env_module.dart b/server/cinema/lib/common/env_module.dart new file mode 100644 index 0000000..7805e0d --- /dev/null +++ b/server/cinema/lib/common/env_module.dart @@ -0,0 +1,18 @@ +import 'dart:io'; +import 'package:injectable/injectable.dart'; +import 'package:cinema/common/env_not_found_exception.dart'; + +const apiKey = "apiKey"; + +@module +abstract class EnvModule { + @lazySingleton + @Named(apiKey) + String get api_key { + final key = Platform.environment['TMDB_API_KEY']; + if (key == null || key.isEmpty) { + throw EnvNotFoundException('TMDB_API_KEY environment variable is missing.'); + } + return key; + } +} diff --git a/server/cinema/lib/common/env_not_found_exception.dart b/server/cinema/lib/common/env_not_found_exception.dart new file mode 100644 index 0000000..b7b9069 --- /dev/null +++ b/server/cinema/lib/common/env_not_found_exception.dart @@ -0,0 +1,8 @@ +class EnvNotFoundException implements Exception { + final String message; + EnvNotFoundException([this.message = '']); + + @override + String toString() => 'EnvNotFoundException: $message'; +} + diff --git a/server/cinema/lib/feature/poster/data/repositories/tmdb_image_loader.dart b/server/cinema/lib/feature/poster/data/repositories/tmdb_image_loader.dart index 8ac84e2..c11c17f 100644 --- a/server/cinema/lib/feature/poster/data/repositories/tmdb_image_loader.dart +++ b/server/cinema/lib/feature/poster/data/repositories/tmdb_image_loader.dart @@ -26,8 +26,11 @@ class TmDBImageLoader implements ImageLoader { (movie) => Movie( (b) => b ..id = movie.id + ..title = movie.title ..poster = movie.posterPath - ..backdrop = movie.backdropPath, + ..backdrop = movie.backdropPath + ..release = movie.releaseDate + ..video = movie.video, ), ) .toList() ?? @@ -38,12 +41,12 @@ class TmDBImageLoader implements ImageLoader { @override Future downloadImages(Movie movie) async { - await downloadImage(movie.poster, "cache/movie/${movie.id}/poster.png"); - await downloadImage(movie.backdrop, "cache/movie/${movie.id}/backdrop.png"); + await _downloadImage(movie.poster, "cache/movie/${movie.id}/poster.png"); + await _downloadImage(movie.backdrop, "cache/movie/${movie.id}/backdrop.png"); return true; } - Future downloadImage(String url, String filename) async { + Future _downloadImage(String url, String filename) async { final file = File(filename); if (await file.exists()) { return false; diff --git a/server/cinema/lib/feature/poster/domain/movie.dart b/server/cinema/lib/feature/poster/domain/movie.dart index e145dce..6b90f88 100644 --- a/server/cinema/lib/feature/poster/domain/movie.dart +++ b/server/cinema/lib/feature/poster/domain/movie.dart @@ -8,6 +8,12 @@ abstract class Movie implements Built { int get id; + String get title; + + DateTime get release; + + bool get video; + String get poster; String get backdrop;