dart user service, dependency injection with riverpod

This commit is contained in:
Jean Jacques Avril 2025-01-01 12:05:39 +00:00
parent cf3c34fb2f
commit bdd4042cef
No known key found for this signature in database
22 changed files with 502 additions and 115 deletions

View File

@ -1,3 +1 @@
int calculate() {
return 6 * 7;
}

View File

@ -1,8 +0,0 @@
import 'package:db_client/db_client.dart';
import 'package:test/test.dart';
void main() {
test('calculate', () {
expect(calculate(), 42);
});
}

7
backend-dart/.env Executable file
View File

@ -0,0 +1,7 @@
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="postgresql://user:secret@localhost:5432/time_tracking_db?schema=public"

View File

@ -22,7 +22,9 @@ include: package:lints/recommended.yaml
# analyzer: # analyzer:
# exclude: # exclude:
# - path/to/excluded/files/** # - path/to/excluded/files/**
analyzer:
plugins:
- custom_lint
# For more information about the core and recommended set of lints, see # For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints # https://dart.dev/go/core-lints

View File

@ -0,0 +1,11 @@
import 'package:backend_dart/infrastructure/config/config.dart';
import 'package:backend_dart/interfaces/http/server.dart';
import 'package:shelf_hotreload/shelf_hotreload.dart';
void main() async {
final config = await Config.load();
final server = Server(config);
print('(DEV) Starting ActaTempus server on port ${config.port}...');
withHotreload(() => server.start());
}

View File

@ -0,0 +1 @@
dart run build_runner build

2
backend-dart/dev.sh Normal file
View File

@ -0,0 +1,2 @@
#!/bin/sh
dart run --enable-vm-service bin/backend_dart_dev.dart # Backend on port 8080

View File

@ -1,19 +0,0 @@
import 'package:backend_dart/domain/entities/user.dart';
import 'package:backend_dart/domain/repositories/user_repository.dart';
class RegisterUserUseCase {
final UserRepository userRepository;
RegisterUserUseCase(this.userRepository);
Future<void> execute(String name, String email, String password) async {
final user = User(
id: 'generated-id', // Eine Methode zur ID-Erzeugung einfügen
name: name,
email: email,
password: password, // In der Realität: Passwörter hashen
);
await userRepository.create(user);
}
}

View File

@ -0,0 +1,27 @@
import 'package:backend_dart/infrastructure/persistence/database.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
part 'user_service.g.dart'; // generated with 'pub run build_runner build'
class UserService {
final Database database;
UserService(this.database);
@Route.get('/')
Future<Response> listUsers(Request request) async {
return Response.ok('["user1"]');
}
@Route.get('/<userId>')
Future<Response> fetchUser(Request request, String userId) async {
final result = await database.users.findById(userId);
if (result != null) {
return Response.ok(result);
}
return Response.notFound('no such user');
}
// Create router using the generate function defined in 'userservice.g.dart'.
Router get router => _$UserServiceRouter(this);
}

View File

@ -0,0 +1,22 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_service.dart';
// **************************************************************************
// ShelfRouterGenerator
// **************************************************************************
Router _$UserServiceRouter(UserService service) {
final router = Router();
router.add(
'GET',
r'/',
service.listUsers,
);
router.add(
'GET',
r'/<userId>',
service.fetchUser,
);
return router;
}

View File

@ -0,0 +1,8 @@
import 'package:backend_dart/application/user_service/user_service.dart';
import 'package:backend_dart/infrastructure/persistence/database_provider.dart';
import 'package:riverpod/riverpod.dart';
final userServiceProvider = Provider<UserService>((ref) {
final database = ref.read(databaseProvider);
return UserService(database);
});

View File

@ -1,3 +0,0 @@
int calculate() {
return 6 * 7;
}

View File

@ -2,7 +2,7 @@ class User {
final String id; final String id;
final String name; final String name;
final String email; final String email;
final String password; final String? password;
User({ User({
required this.id, required this.id,

View File

@ -0,0 +1,13 @@
import 'package:backend_dart/infrastructure/persistence/db/client.dart';
import 'package:backend_dart/infrastructure/persistence/prisma_user_repository.dart';
class Database {
final prisma = PrismaClient();
late final PrismaUserRepository users;
Database() {
users = PrismaUserRepository(prisma);
print('Database initialized');
}
}

View File

@ -0,0 +1,8 @@
import 'package:backend_dart/infrastructure/persistence/database.dart';
import 'package:riverpod/riverpod.dart';
final databaseProvider = Provider<Database>((ref) {
// Hier die Datenbankverbindung initialisieren
final database = Database();
return database;
});

View File

@ -1,60 +0,0 @@
import 'package:backend_dart/domain/entities/user.dart';
import 'package:backend_dart/domain/repositories/user_repository.dart';
import 'package:postgres/postgres.dart';
class PostgresUserRepository implements UserRepository {
final PostgreSQLConnection connection;
PostgresUserRepository(this.connection);
@override
Future<void> create(User user) async {
await connection.query(
'INSERT INTO users (id, name, email, password) VALUES (@id, @name, @mail, @pwd)',
substitutionValues: {
'id': user.id,
'name': user.name,
'mail': user.email,
'pwd': user.password,
},
);
}
@override
Future<User?> findByEmail(String email) async {
final results = await connection.query(
'SELECT id, name, email, password FROM users WHERE email = @mail',
substitutionValues: {'mail': email},
);
if (results.isNotEmpty) {
final row = results.first;
return User(
id: row[0],
name: row[1],
email: row[2],
password: row[3],
);
}
return null;
}
@override
Future<User?> findById(String id) async {
final results = await connection.query(
'SELECT id, name, email, password FROM users WHERE id = @id',
substitutionValues: {'id': id},
);
if (results.isNotEmpty) {
final row = results.first;
return User(
id: row[0],
name: row[1],
email: row[2],
password: row[3],
);
}
return null;
}
}

View File

@ -0,0 +1,57 @@
import 'package:backend_dart/domain/entities/user.dart';
import 'package:backend_dart/domain/repositories/user_repository.dart';
import 'package:backend_dart/infrastructure/persistence/db/prisma.dart';
import 'package:orm/orm.dart';
import 'db/client.dart';
class PrismaUserRepository implements UserRepository {
final PrismaClient prisma;
PrismaUserRepository(this.prisma);
@override
Future<void> create(User user) async {
if (user.password == null) {
throw Exception('Password is required');
}
prisma.user.create(
data: PrismaUnion.$1(UserCreateInput(
id: user.id,
name: user.name,
email: user.email,
password: user.password!,
)));
}
@override
Future<User?> findByEmail(String email) async {
final user =
await prisma.user.findUnique(where: UserWhereUniqueInput(email: email));
if (user == null) {
return null;
}
return User(
id: user.id!,
name: user.name!,
email: user.email!,
password: user.password,
);
}
@override
Future<User?> findById(String id) async {
final user =
await prisma.user.findUnique(where: UserWhereUniqueInput(id: id));
if (user == null) {
return null;
}
return User(
id: user.id!,
name: user.name!,
email: user.email!,
password: user.password,
);
}
}

View File

@ -0,0 +1,25 @@
import 'package:backend_dart/application/user_service/user_service_provider.dart';
import 'package:riverpod/riverpod.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
Router getRouter(ProviderContainer container) {
final router = Router();
// Welcome and health routes
router.get('/', (Request request) {
return Response.ok('Welcome to ActaTempus!');
});
router.get('/health', (Request request) {
return Response.ok('Server is running');
});
// UserService instanzieren
final userService = container.read(userServiceProvider);
// UserService-Router an Haupt-Router binden
router.mount('/users/', userService.router.call);
return router;
}

View File

@ -1,21 +1,36 @@
import 'dart:io'; import 'dart:io';
import 'package:riverpod/riverpod.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:backend_dart/infrastructure/config/config.dart'; import 'package:backend_dart/infrastructure/config/config.dart';
import 'router.dart';
class Server { class Server {
final Config config; final Config config;
Server(this.config); Server(this.config);
Future<void> start() async { Future<HttpServer> start() async {
final server = final container = ProviderContainer();
await HttpServer.bind(InternetAddress.anyIPv4, int.parse(config.port)); // Load the router from the router file
print('Listening on port ${config.port}'); final router = getRouter(container);
await for (HttpRequest request in server) { // Define the pipeline and attach the router
request.response final handler = Pipeline()
..write('Welcome to ActaTempus!') .addMiddleware(logRequests()) // Logs all incoming requests
..close(); .addHandler(router.call);
}
// Start the server
final server = await shelf_io.serve(
handler,
InternetAddress.anyIPv4,
int.parse(config.port),
);
print('Server listening on port ${server.port}');
return server;
} }
} }

View File

@ -22,6 +22,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.11.0" version: "6.11.0"
analyzer_plugin:
dependency: transitive
description:
name: analyzer_plugin
sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161"
url: "https://pub.dev"
source: hosted
version: "0.11.3"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -54,6 +62,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.3" version: "1.2.3"
build:
dependency: transitive
description:
name: build
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
url: "https://pub.dev"
source: hosted
version: "2.4.2"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948"
url: "https://pub.dev"
source: hosted
version: "4.0.3"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573"
url: "https://pub.dev"
source: hosted
version: "2.4.14"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -78,6 +134,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
ci:
dependency: transitive
description:
name: ci
sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -126,6 +206,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.6" version: "3.0.6"
custom_lint:
dependency: transitive
description:
name: custom_lint
sha256: "3486c470bb93313a9417f926c7dd694a2e349220992d7b9d14534dc49c15bba9"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
custom_lint_builder:
dependency: transitive
description:
name: custom_lint_builder
sha256: "42cdc41994eeeddab0d7a722c7093ec52bd0761921eeb2cbdbf33d192a234759"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
custom_lint_core:
dependency: transitive
description:
name: custom_lint_core
sha256: "02450c3e45e2a6e8b26c4d16687596ab3c4644dd5792e3313aa9ceba5a49b7f5"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
custom_lint_visitor:
dependency: transitive
description:
name: custom_lint_visitor
sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9
url: "https://pub.dev"
source: hosted
version: "1.0.0+6.11.0"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
@ -166,6 +278,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
url: "https://pub.dev"
source: hosted
version: "2.4.4"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -182,6 +302,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
graphs:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
hotreloader:
dependency: transitive
description:
name: hotreloader
sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e
url: "https://pub.dev"
source: hosted
version: "4.2.0"
http_methods:
dependency: transitive
description:
name: http_methods
sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -222,6 +366,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.1" version: "0.7.1"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
json_rpc_2: json_rpc_2:
dependency: transitive dependency: transitive
description: description:
@ -334,6 +486,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
rational: rational:
dependency: transitive dependency: transitive
description: description:
@ -358,6 +518,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.1.0"
riverpod:
dependency: "direct main"
description:
name: riverpod
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
url: "https://pub.dev"
source: hosted
version: "2.6.1"
riverpod_analyzer_utils:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: c6b8222b2b483cb87ae77ad147d6408f400c64f060df7a225b127f4afef4f8c8
url: "https://pub.dev"
source: hosted
version: "0.5.8"
riverpod_annotation:
dependency: "direct main"
description:
name: riverpod_annotation
sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8
url: "https://pub.dev"
source: hosted
version: "2.6.1"
riverpod_generator:
dependency: "direct dev"
description:
name: riverpod_generator
sha256: "63546d70952015f0981361636bf8f356d9cfd9d7f6f0815e3c07789a41233188"
url: "https://pub.dev"
source: hosted
version: "2.6.3"
riverpod_lint:
dependency: "direct dev"
description:
name: riverpod_lint
sha256: "83e4caa337a9840469b7b9bd8c2351ce85abad80f570d84146911b32086fbd99"
url: "https://pub.dev"
source: hosted
version: "2.6.3"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
sasl_scram: sasl_scram:
dependency: transitive dependency: transitive
description: description:
@ -375,13 +583,21 @@ packages:
source: hosted source: hosted
version: "1.0.3" version: "1.0.3"
shelf: shelf:
dependency: transitive dependency: "direct main"
description: description:
name: shelf name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.2" version: "1.4.2"
shelf_hotreload:
dependency: "direct main"
description:
name: shelf_hotreload
sha256: d7099618b18d3c63ba5272491c1812c306629495129ef9996115f0417902f963
url: "https://pub.dev"
source: hosted
version: "1.5.0"
shelf_packages_handler: shelf_packages_handler:
dependency: transitive dependency: transitive
description: description:
@ -390,6 +606,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.2"
shelf_router:
dependency: "direct main"
description:
name: shelf_router
sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864
url: "https://pub.dev"
source: hosted
version: "1.1.4"
shelf_router_generator:
dependency: "direct dev"
description:
name: shelf_router_generator
sha256: dce5ad77c5db0271ffcc282ced6b106145c8f953e83f5074a13a07811c818793
url: "https://pub.dev"
source: hosted
version: "1.1.0"
shelf_static: shelf_static:
dependency: transitive dependency: transitive
description: description:
@ -406,6 +638,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
source_map_stack_trace: source_map_stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -430,6 +670,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -438,6 +686,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.12.0"
state_notifier:
dependency: transitive
description:
name: state_notifier
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
url: "https://pub.dev"
source: hosted
version: "1.0.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
@ -446,6 +702,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
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:
@ -486,6 +750,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.5" version: "0.6.5"
timing:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -502,6 +774,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.0" version: "0.3.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
@ -567,4 +847,4 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.6.0 <4.0.0"

View File

@ -12,8 +12,17 @@ dependencies:
fpdart: ^1.1.1 fpdart: ^1.1.1
orm: ^5.2.1 orm: ^5.2.1
postgres: ^2.4.1 postgres: ^2.4.1
shelf: ^1.4.2
shelf_hotreload: ^1.5.0
shelf_router: ^1.1.4
yaml: ^3.1.0 yaml: ^3.1.0
riverpod: ^2.6.1
riverpod_annotation: ^2.6.1
dev_dependencies: dev_dependencies:
lints: ^3.0.0 lints: ^3.0.0
test: ^1.24.0 test: ^1.24.0
shelf_router_generator: ^1.1.0
build_runner: ^2.4.14
riverpod_generator: ^2.6.3
riverpod_lint: ^2.6.3

View File

@ -1,8 +0,0 @@
import 'package:backend_dart/backend_dart.dart';
import 'package:test/test.dart';
void main() {
test('calculate', () {
expect(calculate(), 42);
});
}