Compare commits
11 Commits
92da4423b2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
187b42e465
|
|||
|
227311a39e
|
|||
|
0e46299ee0
|
|||
|
d7ce320702
|
|||
|
4ca90e327f
|
|||
|
4defe266eb
|
|||
|
8f51ac8b24
|
|||
|
4ce471599b
|
|||
|
4587901672
|
|||
|
ce980390df
|
|||
|
a5d9372806
|
42
.gitea/workflows/docker.yml
Normal file
42
.gitea/workflows/docker.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Build and Push Multi-Arch Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Login to Gitea Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ secrets.DOMAIN }}
|
||||||
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Extract Version
|
||||||
|
working-directory: ./server/cinema
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep 'version:' pubspec.yaml | sed 's/version: //')
|
||||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
echo "MAJOR=$(echo $VERSION | cut -d. -f1)" >> $GITHUB_ENV
|
||||||
|
echo "MAJOR_MINOR=$(echo $VERSION | cut -d. -f1,2)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Build and Push Multi-Arch Image
|
||||||
|
working-directory: ./server/cinema
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
-t ${{ secrets.DOMAIN }}/${{ secrets.OWNER }}/${{ secrets.REPO }}/server:latest \
|
||||||
|
-t ${{ secrets.DOMAIN }}/${{ secrets.OWNER }}/${{ secrets.REPO }}/server:${{ env.MAJOR }} \
|
||||||
|
-t ${{ secrets.DOMAIN }}/${{ secrets.OWNER }}/${{ secrets.REPO }}/server:${{ env.MAJOR_MINOR }} \
|
||||||
|
-t ${{ secrets.DOMAIN }}/${{ secrets.OWNER }}/${{ secrets.REPO }}/server:${{ env.VERSION }} \
|
||||||
|
--push .
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
**/.idea/libraries
|
**/.idea/libraries
|
||||||
**/.idea/copilot.*
|
**/.idea/copilot.*
|
||||||
|
*.log
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ body:json {
|
|||||||
"backgroundColor": "#000",
|
"backgroundColor": "#000",
|
||||||
"format": "png",
|
"format": "png",
|
||||||
"language": "de-DE",
|
"language": "de-DE",
|
||||||
"output": "lvgl_binary"
|
"output": "lvglBinary"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ build/
|
|||||||
.gitignore
|
.gitignore
|
||||||
.idea/
|
.idea/
|
||||||
.packages
|
.packages
|
||||||
|
*.g.dart
|
||||||
|
*.config.dart
|
||||||
|
assets/cache
|
||||||
|
|||||||
5
server/cinema/.gitignore
vendored
5
server/cinema/.gitignore
vendored
@@ -1,3 +1,8 @@
|
|||||||
# https://dart.dev/guides/libraries/private-files
|
# https://dart.dev/guides/libraries/private-files
|
||||||
# Created by `dart pub`
|
# Created by `dart pub`
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
|
*.g.dart
|
||||||
|
*.freezed.dart
|
||||||
|
*.config.dart
|
||||||
|
*.log
|
||||||
|
**/cache
|
||||||
|
|||||||
@@ -8,14 +8,18 @@ RUN dart pub get
|
|||||||
|
|
||||||
# Copy app source code (except anything in .dockerignore) and AOT compile app.
|
# Copy app source code (except anything in .dockerignore) and AOT compile app.
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN dart compile exe bin/server.dart -o bin/server
|
RUN dart run build_runner build --delete-conflicting-outputs && \
|
||||||
|
APP_VERSION=$(grep 'version:' pubspec.yaml | sed 's/version: //') && \
|
||||||
|
dart compile exe bin/server.dart -o bin/server \
|
||||||
|
-DAPP_VERSION=$APP_VERSION
|
||||||
|
|
||||||
# Build minimal serving image from AOT-compiled `/server`
|
# Build minimal serving image from AOT-compiled `/server`
|
||||||
# and the pre-built AOT-runtime in the `/runtime/` directory of the base image.
|
# and the pre-built AOT-runtime in the `/runtime/` directory of the base image.
|
||||||
FROM scratch
|
FROM scratch
|
||||||
COPY --from=build /runtime/ /
|
COPY --from=build /runtime/ /
|
||||||
COPY --from=build /app/bin/server /app/bin/
|
COPY --from=build /app/bin/server /app/bin/
|
||||||
|
COPY assets /assets
|
||||||
|
|
||||||
# Start server.
|
# Start server.
|
||||||
EXPOSE 8080
|
EXPOSE 3000
|
||||||
CMD ["/app/bin/server"]
|
CMD ["/app/bin/server"]
|
||||||
|
|||||||
26
server/cinema/Makefile
Normal file
26
server/cinema/Makefile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
watch:
|
||||||
|
dart run build_runner watch --delete-conflicting-outputs
|
||||||
|
|
||||||
|
docker: build
|
||||||
|
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) \
|
||||||
|
.
|
||||||
5
server/cinema/assets/banner.txt
Normal file
5
server/cinema/assets/banner.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
____ _ ____ _
|
||||||
|
/ ___(_)_ __ ___ _ __ ___ __ _ / ___| ___ _ ____ _(_) ___ ___
|
||||||
|
| | | | '_ \ / _ \ '_ ` _ \ / _` | \___ \ / _ \ '__\ \ / / |/ __/ _ \
|
||||||
|
| |___| | | | | __/ | | | | | (_| | ___) | __/ | \ V /| | (_| __/
|
||||||
|
\____|_|_| |_|\___|_| |_| |_|\__,_| |____/ \___|_| \_/ |_|\___\___|
|
||||||
@@ -1,71 +1,62 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
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';
|
||||||
|
import 'package:cinema/feature/root/data/service/root.service.dart';
|
||||||
|
import 'package:cinema/feature/version/version.dart';
|
||||||
|
import 'package:cinema/injectable.dart';
|
||||||
import 'package:shelf/shelf.dart';
|
import 'package:shelf/shelf.dart';
|
||||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
import 'package:shelf/shelf_io.dart' as io;
|
||||||
import 'package:shelf_router/shelf_router.dart';
|
import 'package:shelf_router/shelf_router.dart';
|
||||||
import 'package:shelf_web_socket/shelf_web_socket.dart';
|
|
||||||
|
|
||||||
final List<dynamic> clients = [];
|
|
||||||
|
|
||||||
// Configure routes.
|
|
||||||
final _router = Router()
|
|
||||||
..get('/', _rootHandler)
|
|
||||||
..get('/echo/<message>', _echoHandler);
|
|
||||||
|
|
||||||
Response _rootHandler(Request req) {
|
|
||||||
return Response.ok('Hello, World!\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
Response _echoHandler(Request request) {
|
|
||||||
final message = request.params['message'];
|
|
||||||
return Response.ok('$message\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
void main(List<String> args) async {
|
void main(List<String> args) async {
|
||||||
// Use any available host or container IP (usually `0.0.0.0`).
|
runZonedGuarded(
|
||||||
final ip = InternetAddress.anyIPv4;
|
() async {
|
||||||
|
final startTime = DateTime.now();
|
||||||
|
|
||||||
// Configure a pipeline that logs requests.
|
configureDependencies();
|
||||||
final handler = Pipeline().addMiddleware(logRequests()).addHandler(_router.call);
|
|
||||||
|
|
||||||
var wsHandler = webSocketHandler((webSocket, _) {
|
final router = Router();
|
||||||
clients.add(webSocket);
|
router.mount("/poster", getIt<PosterService>().router.call);
|
||||||
print('Client connected, total: ${clients.length}');
|
router.mount("/", getIt<RootService>().router.call);
|
||||||
|
|
||||||
webSocket.stream.listen(
|
/// add middlewares (Logging, CORS)
|
||||||
(message) {
|
final handler = Pipeline().addMiddleware(logRequests()).addMiddleware(cors()).addHandler(router.call);
|
||||||
webSocket.sink.add('echo $message');
|
|
||||||
},
|
|
||||||
onDone: () {
|
|
||||||
clients.remove(webSocket);
|
|
||||||
print('Client disconnected, total: ${clients.length}');
|
|
||||||
},
|
|
||||||
onError: (error) {
|
|
||||||
clients.remove(webSocket);
|
|
||||||
print('Client error: $error');
|
|
||||||
},
|
|
||||||
cancelOnError: true,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
FutureOr<Response> combinedHandler(Request request) {
|
|
||||||
if (request.url.path == 'ws') {
|
|
||||||
return wsHandler(request);
|
|
||||||
}
|
|
||||||
return handler(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For running in containers, we respect the PORT environment variable.
|
// For running in containers, we respect the PORT environment variable.
|
||||||
final port = int.parse(Platform.environment['PORT'] ?? '8080');
|
final port = int.parse(Platform.environment['PORT'] ?? '3000');
|
||||||
final server = await shelf_io.serve(combinedHandler, ip, port).then((server) {
|
await io.serve(handler, InternetAddress.anyIPv4, port, poweredByHeader: null).then((server) async {
|
||||||
print('Serving at http|ws://${server.address.host}:${server.port}');
|
final bannerFile = File('assets/banner.txt');
|
||||||
});
|
if (await bannerFile.exists()) {
|
||||||
|
final banner = await bannerFile.readAsString();
|
||||||
|
print(banner);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast to all clients
|
print('Caching current trending images...');
|
||||||
void broadcast(String message) {
|
|
||||||
for (final client in clients) {
|
final ImageLoader loader = getIt();
|
||||||
client.sink.add(message);
|
final movies = await loader.getPosterURIs();
|
||||||
|
for (var movie in movies) {
|
||||||
|
await loader.downloadImages(movie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getIt<Version>().printVersion();
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(error, stackTrace) {
|
||||||
|
stderr.writeln(error);
|
||||||
|
if (error is EnvNotFoundException) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
38
server/cinema/lib/common/dio_module.dart
Normal file
38
server/cinema/lib/common/dio_module.dart
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
const dioAPI = 'api';
|
||||||
|
const dioIMAGES = 'images';
|
||||||
|
|
||||||
|
@module
|
||||||
|
abstract class DioModule {
|
||||||
|
@Named(dioAPI)
|
||||||
|
@lazySingleton
|
||||||
|
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(
|
||||||
|
BaseOptions(
|
||||||
|
baseUrl: 'https://image.tmdb.org/t/p/original',
|
||||||
|
connectTimeout: const Duration(seconds: 10),
|
||||||
|
receiveTimeout: const Duration(seconds: 10),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:built_value/serializer.dart';
|
||||||
|
|
||||||
|
/// A custom serializer for [DateTime] objects that are represented as
|
||||||
|
/// a simple date string "yyyy-MM-dd" in JSON.
|
||||||
|
class CustomDateTimeSerializer implements PrimitiveSerializer<DateTime> {
|
||||||
|
@override
|
||||||
|
final Iterable<Type> types = const [DateTime];
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String wireName = 'DateTime';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object serialize(Serializers serializers, DateTime dateTime,
|
||||||
|
{FullType specifiedType = FullType.unspecified}) {
|
||||||
|
// On serialization, convert DateTime to a "yyyy-MM-dd" string.
|
||||||
|
return dateTime.toIso8601String().substring(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime deserialize(Serializers serializers, Object serialized,
|
||||||
|
{FullType specifiedType = FullType.unspecified}) {
|
||||||
|
// On deserialization, parse the string to a DateTime object.
|
||||||
|
// This handles formats like "2025-09-23".
|
||||||
|
final parts = (serialized as String).split('-');
|
||||||
|
final dateUtc = DateTime.utc(
|
||||||
|
int.parse(parts[0]),
|
||||||
|
int.parse(parts[1]),
|
||||||
|
int.parse(parts[2]),
|
||||||
|
);
|
||||||
|
return dateUtc;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
server/cinema/lib/common/domain/serializers.dart
Normal file
20
server/cinema/lib/common/domain/serializers.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:built_collection/built_collection.dart';
|
||||||
|
import 'package:built_value/serializer.dart';
|
||||||
|
import 'package:built_value/standard_json_plugin.dart';
|
||||||
|
import 'package:cinema/feature/poster/domain/movie.dart';
|
||||||
|
import 'package:cinema/feature/poster/domain/tmdb_trending_response.dart';
|
||||||
|
|
||||||
|
import 'custom_date_time_serializer.dart';
|
||||||
|
|
||||||
|
part 'serializers.g.dart';
|
||||||
|
|
||||||
|
@SerializersFor([TmdbTrendingResponse, TmdbMovieResult])
|
||||||
|
final Serializers serializers =
|
||||||
|
(_$serializers.toBuilder()
|
||||||
|
..add(CustomDateTimeSerializer())
|
||||||
|
..addPlugin(StandardJsonPlugin())
|
||||||
|
..addBuilderFactory(
|
||||||
|
const FullType(BuiltList, [FullType(TmdbMovieResult), FullType(Movie)]),
|
||||||
|
() => ListBuilder<TmdbMovieResult>(),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
18
server/cinema/lib/common/env_module.dart
Normal file
18
server/cinema/lib/common/env_module.dart
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
server/cinema/lib/common/env_not_found_exception.dart
Normal file
8
server/cinema/lib/common/env_not_found_exception.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
class EnvNotFoundException implements Exception {
|
||||||
|
final String message;
|
||||||
|
EnvNotFoundException([this.message = '']);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'EnvNotFoundException: $message';
|
||||||
|
}
|
||||||
|
|
||||||
24
server/cinema/lib/feature/middlewares/cors.dart
Normal file
24
server/cinema/lib/feature/middlewares/cors.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:shelf/shelf.dart';
|
||||||
|
|
||||||
|
Middleware cors() {
|
||||||
|
const corsHeaders = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
|
||||||
|
'Access-Control-Allow-Headers': 'Origin, Content-Type',
|
||||||
|
'Access-Control-Max-Age': "600",
|
||||||
|
};
|
||||||
|
|
||||||
|
return createMiddleware(
|
||||||
|
requestHandler: (Request request) {
|
||||||
|
if (request.method == "OPTIONS") {
|
||||||
|
return Response.ok('', headers: corsHeaders);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
responseHandler: (Response response) {
|
||||||
|
final headers = Map<String, String>.from(response.headers);
|
||||||
|
headers.addEntries(corsHeaders.entries.map((e) => MapEntry(e.key, e.value)));
|
||||||
|
return response.change(headers: headers);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import 'package:cinema/feature/poster/domain/movie.dart';
|
||||||
|
|
||||||
|
abstract class ImageLoader {
|
||||||
|
Future<List<Movie>> getPosterURIs({String? language = 'de'});
|
||||||
|
|
||||||
|
Future<bool> downloadImages(Movie movie);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cinema/common/dio_module.dart';
|
||||||
|
import 'package:cinema/common/domain/serializers.dart';
|
||||||
|
import 'package:cinema/feature/poster/data/repositories/image_loader.dart';
|
||||||
|
import 'package:cinema/feature/poster/domain/movie.dart';
|
||||||
|
import 'package:cinema/feature/poster/domain/tmdb_trending_response.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
@Injectable(as: ImageLoader)
|
||||||
|
class TmDBImageLoader implements ImageLoader {
|
||||||
|
final Dio api;
|
||||||
|
final Dio images;
|
||||||
|
final String imageBaseUrl = 'https://image.tmdb.org/t/p/w500';
|
||||||
|
|
||||||
|
TmDBImageLoader(@Named(dioAPI) this.api, @Named(dioIMAGES) this.images);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Movie>> getPosterURIs({String? language = 'de'}) async {
|
||||||
|
final response = await api.get('/trending/movie/week?language=$language');
|
||||||
|
if (response.data != null) {
|
||||||
|
final data = serializers.deserializeWith(TmdbTrendingResponse.serializer, response.data);
|
||||||
|
return data?.results
|
||||||
|
.map(
|
||||||
|
(movie) => Movie(
|
||||||
|
(b) => b
|
||||||
|
..id = movie.id
|
||||||
|
..title = movie.title
|
||||||
|
..poster = movie.posterPath
|
||||||
|
..backdrop = movie.backdropPath
|
||||||
|
..release = movie.releaseDate
|
||||||
|
..video = movie.video,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> downloadImages(Movie movie) async {
|
||||||
|
await _downloadImage(movie.poster, "cache/movie/${movie.id}/poster.png");
|
||||||
|
await _downloadImage(movie.backdrop, "cache/movie/${movie.id}/backdrop.png");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _downloadImage(String url, String filename) async {
|
||||||
|
final file = File(filename);
|
||||||
|
if (await file.exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await file.parent.create(recursive: true);
|
||||||
|
final response = await images.get(url, options: Options(responseType: ResponseType.bytes));
|
||||||
|
await file.writeAsBytes(response.data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cinema/feature/poster/domain/poster_request.schema.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:shelf/shelf.dart';
|
||||||
|
import 'package:shelf_router/shelf_router.dart';
|
||||||
|
|
||||||
|
part 'poster.service.g.dart';
|
||||||
|
|
||||||
|
@injectable
|
||||||
|
class PosterService {
|
||||||
|
@Route.get('/')
|
||||||
|
Future<Response> getRoot(Request request) async {
|
||||||
|
return Response.ok('deprecated poster endpoint. use POST /poster instead');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Route.post('/')
|
||||||
|
Future<Response> postRoot(Request request) async {
|
||||||
|
final payload = await request.readAsString();
|
||||||
|
final body = jsonDecode(payload);
|
||||||
|
final params = posterSchema.safeParse(body);
|
||||||
|
if (!params.success) {
|
||||||
|
return Response(
|
||||||
|
400,
|
||||||
|
body: jsonEncode({
|
||||||
|
'error': 'Invalid request',
|
||||||
|
'details': params.error?.messages,
|
||||||
|
}),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Response.ok(jsonEncode(params.data), headers: {'Content-Type': 'application/json'});
|
||||||
|
}
|
||||||
|
|
||||||
|
Router get router => _$PosterServiceRouter(this);
|
||||||
|
}
|
||||||
24
server/cinema/lib/feature/poster/domain/movie.dart
Normal file
24
server/cinema/lib/feature/poster/domain/movie.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:built_value/built_value.dart';
|
||||||
|
import 'package:built_value/serializer.dart';
|
||||||
|
|
||||||
|
part 'movie.g.dart';
|
||||||
|
|
||||||
|
abstract class Movie implements Built<Movie, MovieBuilder> {
|
||||||
|
static Serializer<Movie> get serializer => _$movieSerializer;
|
||||||
|
|
||||||
|
int get id;
|
||||||
|
|
||||||
|
String get title;
|
||||||
|
|
||||||
|
DateTime get release;
|
||||||
|
|
||||||
|
bool get video;
|
||||||
|
|
||||||
|
String get poster;
|
||||||
|
|
||||||
|
String get backdrop;
|
||||||
|
|
||||||
|
Movie._();
|
||||||
|
|
||||||
|
factory Movie([void Function(MovieBuilder) updates]) = _$Movie;
|
||||||
|
}
|
||||||
16
server/cinema/lib/feature/poster/domain/poster.enums.dart
Normal file
16
server/cinema/lib/feature/poster/domain/poster.enums.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
enum PosterOrientation {
|
||||||
|
horizontal,
|
||||||
|
vertical,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PosterFormat {
|
||||||
|
png,
|
||||||
|
jpeg,
|
||||||
|
bmp,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PosterOutput {
|
||||||
|
image,
|
||||||
|
lvgl,
|
||||||
|
lvglBinary,
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import 'package:cinema/feature/poster/domain/poster.enums.dart';
|
||||||
|
import 'package:zard/zard.dart';
|
||||||
|
|
||||||
|
final _orientations = PosterOrientation.values.map((e) => e.name);
|
||||||
|
final _formats = PosterFormat.values.map((e) => e.name);
|
||||||
|
final _outputs = PosterOutput.values.map((e) => e.name);
|
||||||
|
|
||||||
|
final posterSchema = z.map({
|
||||||
|
'width': z.int().min(1, message: "'width' must be at least 1").max(4000, message: "'width' must be at most 4000"),
|
||||||
|
'height': z.int().min(1, message: "'height' must be at least 1").max(4000, message: "'height' must be a most 4000"),
|
||||||
|
'count': z.int().min(1, message: "'count' must be at least 1").max(20, message: "'count' must be at most 20"),
|
||||||
|
'orientation': z.string().refine(
|
||||||
|
(value) => _orientations.contains(value),
|
||||||
|
message: "'orientation' must be either ${_orientations.join(', ')}",
|
||||||
|
),
|
||||||
|
'shuffle': z.bool(message: "'shuffle' must be a boolean value"),
|
||||||
|
'language': z.string(message: "'language' must be a string").transform((value) => value.trim()),
|
||||||
|
'backgroundColor': z.string().regex(
|
||||||
|
RegExp(r'^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$'),
|
||||||
|
message: "The 'backgroundColor' must be a valid hexadecimal color code (e.g., #000 or #FF0000)",
|
||||||
|
),
|
||||||
|
'format': z.string().refine(
|
||||||
|
(value) => _formats.contains(value),
|
||||||
|
message: "'format' must be either ${_formats.join(', ')}",
|
||||||
|
),
|
||||||
|
'output': z.string().refine(
|
||||||
|
(value) => _outputs.contains(value),
|
||||||
|
message: "'output' must be either ${_outputs.join(', ')}",
|
||||||
|
),
|
||||||
|
});
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:built_collection/built_collection.dart';
|
||||||
|
import 'package:built_value/built_value.dart';
|
||||||
|
import 'package:built_value/serializer.dart';
|
||||||
|
|
||||||
|
part 'tmdb_trending_response.g.dart';
|
||||||
|
|
||||||
|
abstract class TmdbTrendingResponse implements Built<TmdbTrendingResponse, TmdbTrendingResponseBuilder> {
|
||||||
|
static Serializer<TmdbTrendingResponse> get serializer => _$tmdbTrendingResponseSerializer;
|
||||||
|
|
||||||
|
int get page;
|
||||||
|
|
||||||
|
BuiltList<TmdbMovieResult> get results;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'total_pages')
|
||||||
|
int get totalPages;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'total_results')
|
||||||
|
int get totalResults;
|
||||||
|
|
||||||
|
TmdbTrendingResponse._();
|
||||||
|
|
||||||
|
factory TmdbTrendingResponse([void Function(TmdbTrendingResponseBuilder) updates]) = _$TmdbTrendingResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class TmdbMovieResult implements Built<TmdbMovieResult, TmdbMovieResultBuilder> {
|
||||||
|
static Serializer<TmdbMovieResult> get serializer => _$tmdbMovieResultSerializer;
|
||||||
|
|
||||||
|
bool get adult;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'backdrop_path')
|
||||||
|
String get backdropPath;
|
||||||
|
|
||||||
|
int get id;
|
||||||
|
|
||||||
|
String get title;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'original_title')
|
||||||
|
String get originalTitle;
|
||||||
|
|
||||||
|
String get overview;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'poster_path')
|
||||||
|
String get posterPath;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'media_type')
|
||||||
|
String get mediaType;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'genre_ids')
|
||||||
|
BuiltList<int> get genreIds;
|
||||||
|
|
||||||
|
double get popularity;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'release_date')
|
||||||
|
DateTime get releaseDate;
|
||||||
|
|
||||||
|
bool get video;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'vote_average')
|
||||||
|
double get voteAverage;
|
||||||
|
|
||||||
|
@BuiltValueField(wireName: 'vote_count')
|
||||||
|
int get voteCount;
|
||||||
|
|
||||||
|
TmdbMovieResult._();
|
||||||
|
|
||||||
|
factory TmdbMovieResult([void Function(TmdbMovieResultBuilder) updates]) = _$TmdbMovieResult;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:cinema/feature/websocket/websocket.service.dart';
|
||||||
|
import 'package:cinema/injectable.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:shelf/shelf.dart';
|
||||||
|
import 'package:shelf_router/shelf_router.dart';
|
||||||
|
|
||||||
|
part 'root.service.g.dart';
|
||||||
|
|
||||||
|
@injectable
|
||||||
|
class RootService {
|
||||||
|
@Route.get('/')
|
||||||
|
Future<Response> getRoot(Request request) async {
|
||||||
|
final isWebSocket =
|
||||||
|
request.headers['connection']?.toLowerCase() == 'upgrade' &&
|
||||||
|
request.headers['upgrade']?.toLowerCase() == 'websocket';
|
||||||
|
|
||||||
|
if (isWebSocket) {
|
||||||
|
return getIt<WebSocketService>().handler(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok('REST response');
|
||||||
|
}
|
||||||
|
|
||||||
|
Router get router => _$RootServiceRouter(this);
|
||||||
|
}
|
||||||
14
server/cinema/lib/feature/version/version.dart
Normal file
14
server/cinema/lib/feature/version/version.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
@injectable
|
||||||
|
class Version {
|
||||||
|
const Version();
|
||||||
|
|
||||||
|
String get appVersion => const String.fromEnvironment('APP_VERSION');
|
||||||
|
|
||||||
|
void printVersion() {
|
||||||
|
if (appVersion.isNotEmpty) {
|
||||||
|
print('App Version: $appVersion');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
server/cinema/lib/feature/websocket/websocket.service.dart
Normal file
28
server/cinema/lib/feature/websocket/websocket.service.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:shelf/shelf.dart';
|
||||||
|
import 'package:shelf_web_socket/shelf_web_socket.dart';
|
||||||
|
|
||||||
|
@LazySingleton()
|
||||||
|
class WebSocketService {
|
||||||
|
final List<dynamic> _clients = [];
|
||||||
|
|
||||||
|
Handler get handler => webSocketHandler((webSocket, _) {
|
||||||
|
_clients.add(webSocket);
|
||||||
|
print('Client connected, total: ${_clients.length}');
|
||||||
|
|
||||||
|
webSocket.stream.listen(
|
||||||
|
(message) {
|
||||||
|
webSocket.sink.add('echo $message');
|
||||||
|
},
|
||||||
|
onDone: () {
|
||||||
|
_clients.remove(webSocket);
|
||||||
|
print('Client disconnected, total: ${_clients.length}');
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
_clients.remove(webSocket);
|
||||||
|
print('Client error: $error');
|
||||||
|
},
|
||||||
|
cancelOnError: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
9
server/cinema/lib/injectable.dart
Normal file
9
server/cinema/lib/injectable.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
import 'injectable.config.dart';
|
||||||
|
|
||||||
|
final getIt = GetIt.instance;
|
||||||
|
|
||||||
|
@InjectableInit()
|
||||||
|
void configureDependencies() => getIt.init();
|
||||||
@@ -5,18 +5,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e"
|
sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "92.0.0"
|
version: "91.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e"
|
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.0"
|
version: "8.4.1"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -49,6 +49,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.2"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
build_daemon:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
|
build_runner:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
sha256: "7b5b569f3df370590a85029148d6fc66c7d0201fc6f1847c07dd85d365ae9fcd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.10.3"
|
||||||
|
built_collection:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1"
|
||||||
|
built_value:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.12.0"
|
||||||
|
built_value_generator:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: built_value_generator
|
||||||
|
sha256: "65f5823a2c4158384ebc845218e19286fdf5dd04f8ac2cf607b01a502be40b1b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.12.0"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.4"
|
||||||
cli_config:
|
cli_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -57,6 +121,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
|
code_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_builder
|
||||||
|
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.11.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -89,6 +161,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "3.0.7"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.9.0"
|
||||||
|
dio_web_adapter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio_web_adapter
|
||||||
|
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -105,6 +201,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -113,6 +217,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
get_it:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: get_it
|
||||||
|
sha256: "84792561b731b6463d053e9761a5236da967c369da10b134b8585a5e18429956"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.0.5"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -121,6 +233,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
graphs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
http:
|
http:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -161,6 +281,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.4"
|
version: "4.5.4"
|
||||||
|
injectable:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: injectable
|
||||||
|
sha256: "29559f7e3daebf0084597de86a825ae7f149d9e30264b7fbc71d1069ae82697d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.0"
|
||||||
|
injectable_generator:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: injectable_generator
|
||||||
|
sha256: "309c3f3546160dd00b575f16b341a6a3025479950441bcc7fcb2f8404a40d326"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.9.1"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -177,6 +313,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.2"
|
version: "0.7.2"
|
||||||
|
json_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.0"
|
||||||
lints:
|
lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -273,6 +417,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
|
recase:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: recase
|
||||||
|
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -297,6 +457,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.4"
|
version: "1.1.4"
|
||||||
|
shelf_router_generator:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: shelf_router_generator
|
||||||
|
sha256: "310416e0eb5a96c8b27f2586367f07b09dc480af06485c1f7951bbed1e8b8b08"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
shelf_static:
|
shelf_static:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -313,6 +481,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
sha256: "9098ab86015c4f1d8af6486b547b11100e73b193e1899015033cb3e14ad20243"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.2"
|
||||||
source_map_stack_trace:
|
source_map_stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -353,6 +529,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -465,5 +649,13 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
|
zard:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: zard
|
||||||
|
sha256: "772fc9ef6088123fefaaa88cb986253f0e838aec2af2c3b956a9a1c98ea2b049"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.23"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.9.0 <4.0.0"
|
dart: ">=3.9.0 <4.0.0"
|
||||||
|
|||||||
@@ -7,12 +7,22 @@ environment:
|
|||||||
sdk: ^3.9.0
|
sdk: ^3.9.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
built_collection: ^5.1.1
|
||||||
|
built_value: ^8.9.2
|
||||||
|
dio: ^5.9.0
|
||||||
|
get_it: ^9.0.5
|
||||||
image: ^4.5.4
|
image: ^4.5.4
|
||||||
|
injectable: ^2.6.0
|
||||||
shelf: ^1.4.2
|
shelf: ^1.4.2
|
||||||
shelf_router: ^1.1.2
|
shelf_router: ^1.1.2
|
||||||
shelf_web_socket: ^3.0.0
|
shelf_web_socket: ^3.0.0
|
||||||
|
zard: ^0.0.23
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
build_runner: ^2.10.3
|
||||||
|
built_value_generator: ^8.9.2
|
||||||
http: ^1.2.2
|
http: ^1.2.2
|
||||||
|
injectable_generator: ^2.9.1
|
||||||
lints: ^6.0.0
|
lints: ^6.0.0
|
||||||
|
shelf_router_generator: ^1.1.3
|
||||||
test: ^1.25.6
|
test: ^1.25.6
|
||||||
|
|||||||
Reference in New Issue
Block a user