From 6d980a980e053aec8cf069b6e147952883f37ebc Mon Sep 17 00:00:00 2001 From: Jean Jacques Avril Date: Wed, 1 Jan 2025 20:32:01 +0000 Subject: [PATCH] added project repository --- .../lib/domain/data/project_data_source.dart | 14 +- backend-dart/lib/domain/entities/project.dart | 7 +- .../lib/domain/entities/project.freezed.dart | 111 +++++++++------ .../mapper/project_dbo_mapper.dart | 64 +++++++++ .../persistence/mapper/user_dbo_mapper.dart | 5 +- .../prisma_project_data_source.dart | 128 ++++++++++++++++++ .../persistence/prisma_user_data_source.dart | 2 +- 7 files changed, 277 insertions(+), 54 deletions(-) create mode 100644 backend-dart/lib/infrastructure/persistence/mapper/project_dbo_mapper.dart create mode 100644 backend-dart/lib/infrastructure/persistence/prisma_project_data_source.dart diff --git a/backend-dart/lib/domain/data/project_data_source.dart b/backend-dart/lib/domain/data/project_data_source.dart index c20da8e..b5e084a 100644 --- a/backend-dart/lib/domain/data/project_data_source.dart +++ b/backend-dart/lib/domain/data/project_data_source.dart @@ -1,34 +1,36 @@ import 'package:backend_dart/domain/entities/project.dart'; +import 'package:backend_dart/domain/interface/error.dart'; +import 'package:fpdart/fpdart.dart'; /// Interface for managing project data interactions. abstract class ProjectDataSource { /// Creates a new project in the data source. /// /// Throws an error if the creation fails. - Future createProject(NewProject project); + TaskEither createProject(ProjectCreate project); /// Retrieves a project by its unique ID. /// /// Returns `null` if no project is found. - Future findProjectById(String id); + TaskEither findProjectById(String id); /// Retrieves all projects associated with a specific user. /// /// Returns an empty list if no projects are found. - Future> findProjectsByUserId(String userId); + TaskEither> findProjectsByUserId(String userId); /// Updates an existing project in the data source. /// /// Throws an error if the update fails. - Future updateProject(ProjectUpdate project); + TaskEither updateProject(ProjectUpdate project); /// Deletes a project by its unique ID. /// /// Throws an error if the deletion fails. - Future deleteProject(String id); + TaskEither deleteProject(String id); /// Retrieves all projects in the data source. /// /// Returns an empty list if no projects exist. - Future> findAllProjects(); + TaskEither> findAllProjects(); } diff --git a/backend-dart/lib/domain/entities/project.dart b/backend-dart/lib/domain/entities/project.dart index 167f33a..d5040da 100644 --- a/backend-dart/lib/domain/entities/project.dart +++ b/backend-dart/lib/domain/entities/project.dart @@ -3,13 +3,13 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'project.freezed.dart'; @freezed -class NewProject with _$NewProject { - const factory NewProject({ +class ProjectCreate with _$ProjectCreate { + const factory ProjectCreate({ required String name, String? description, String? clientId, required String userId, - }) = _NewProject; + }) = _ProjectCreate; } @freezed @@ -28,6 +28,7 @@ class Project with _$Project { @freezed class ProjectUpdate with _$ProjectUpdate { const factory ProjectUpdate({ + required String id, String? name, String? description, String? clientId, diff --git a/backend-dart/lib/domain/entities/project.freezed.dart b/backend-dart/lib/domain/entities/project.freezed.dart index 9a412de..6b67f3e 100644 --- a/backend-dart/lib/domain/entities/project.freezed.dart +++ b/backend-dart/lib/domain/entities/project.freezed.dart @@ -15,40 +15,40 @@ final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc -mixin _$NewProject { +mixin _$ProjectCreate { String get name => throw _privateConstructorUsedError; String? get description => throw _privateConstructorUsedError; String? get clientId => throw _privateConstructorUsedError; String get userId => throw _privateConstructorUsedError; - /// Create a copy of NewProject + /// Create a copy of ProjectCreate /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $NewProjectCopyWith get copyWith => + $ProjectCreateCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $NewProjectCopyWith<$Res> { - factory $NewProjectCopyWith( - NewProject value, $Res Function(NewProject) then) = - _$NewProjectCopyWithImpl<$Res, NewProject>; +abstract class $ProjectCreateCopyWith<$Res> { + factory $ProjectCreateCopyWith( + ProjectCreate value, $Res Function(ProjectCreate) then) = + _$ProjectCreateCopyWithImpl<$Res, ProjectCreate>; @useResult $Res call( {String name, String? description, String? clientId, String userId}); } /// @nodoc -class _$NewProjectCopyWithImpl<$Res, $Val extends NewProject> - implements $NewProjectCopyWith<$Res> { - _$NewProjectCopyWithImpl(this._value, this._then); +class _$ProjectCreateCopyWithImpl<$Res, $Val extends ProjectCreate> + implements $ProjectCreateCopyWith<$Res> { + _$ProjectCreateCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of NewProject + /// Create a copy of ProjectCreate /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -80,11 +80,11 @@ class _$NewProjectCopyWithImpl<$Res, $Val extends NewProject> } /// @nodoc -abstract class _$$NewProjectImplCopyWith<$Res> - implements $NewProjectCopyWith<$Res> { - factory _$$NewProjectImplCopyWith( - _$NewProjectImpl value, $Res Function(_$NewProjectImpl) then) = - __$$NewProjectImplCopyWithImpl<$Res>; +abstract class _$$ProjectCreateImplCopyWith<$Res> + implements $ProjectCreateCopyWith<$Res> { + factory _$$ProjectCreateImplCopyWith( + _$ProjectCreateImpl value, $Res Function(_$ProjectCreateImpl) then) = + __$$ProjectCreateImplCopyWithImpl<$Res>; @override @useResult $Res call( @@ -92,14 +92,14 @@ abstract class _$$NewProjectImplCopyWith<$Res> } /// @nodoc -class __$$NewProjectImplCopyWithImpl<$Res> - extends _$NewProjectCopyWithImpl<$Res, _$NewProjectImpl> - implements _$$NewProjectImplCopyWith<$Res> { - __$$NewProjectImplCopyWithImpl( - _$NewProjectImpl _value, $Res Function(_$NewProjectImpl) _then) +class __$$ProjectCreateImplCopyWithImpl<$Res> + extends _$ProjectCreateCopyWithImpl<$Res, _$ProjectCreateImpl> + implements _$$ProjectCreateImplCopyWith<$Res> { + __$$ProjectCreateImplCopyWithImpl( + _$ProjectCreateImpl _value, $Res Function(_$ProjectCreateImpl) _then) : super(_value, _then); - /// Create a copy of NewProject + /// Create a copy of ProjectCreate /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -109,7 +109,7 @@ class __$$NewProjectImplCopyWithImpl<$Res> Object? clientId = freezed, Object? userId = null, }) { - return _then(_$NewProjectImpl( + return _then(_$ProjectCreateImpl( name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -132,8 +132,8 @@ class __$$NewProjectImplCopyWithImpl<$Res> /// @nodoc -class _$NewProjectImpl implements _NewProject { - const _$NewProjectImpl( +class _$ProjectCreateImpl implements _ProjectCreate { + const _$ProjectCreateImpl( {required this.name, this.description, this.clientId, @@ -150,14 +150,14 @@ class _$NewProjectImpl implements _NewProject { @override String toString() { - return 'NewProject(name: $name, description: $description, clientId: $clientId, userId: $userId)'; + return 'ProjectCreate(name: $name, description: $description, clientId: $clientId, userId: $userId)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$NewProjectImpl && + other is _$ProjectCreateImpl && (identical(other.name, name) || other.name == name) && (identical(other.description, description) || other.description == description) && @@ -170,21 +170,21 @@ class _$NewProjectImpl implements _NewProject { int get hashCode => Object.hash(runtimeType, name, description, clientId, userId); - /// Create a copy of NewProject + /// Create a copy of ProjectCreate /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$NewProjectImplCopyWith<_$NewProjectImpl> get copyWith => - __$$NewProjectImplCopyWithImpl<_$NewProjectImpl>(this, _$identity); + _$$ProjectCreateImplCopyWith<_$ProjectCreateImpl> get copyWith => + __$$ProjectCreateImplCopyWithImpl<_$ProjectCreateImpl>(this, _$identity); } -abstract class _NewProject implements NewProject { - const factory _NewProject( +abstract class _ProjectCreate implements ProjectCreate { + const factory _ProjectCreate( {required final String name, final String? description, final String? clientId, - required final String userId}) = _$NewProjectImpl; + required final String userId}) = _$ProjectCreateImpl; @override String get name; @@ -195,11 +195,11 @@ abstract class _NewProject implements NewProject { @override String get userId; - /// Create a copy of NewProject + /// Create a copy of ProjectCreate /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$NewProjectImplCopyWith<_$NewProjectImpl> get copyWith => + _$$ProjectCreateImplCopyWith<_$ProjectCreateImpl> get copyWith => throw _privateConstructorUsedError; } @@ -459,6 +459,7 @@ abstract class _Project implements Project { /// @nodoc mixin _$ProjectUpdate { + String get id => throw _privateConstructorUsedError; String? get name => throw _privateConstructorUsedError; String? get description => throw _privateConstructorUsedError; String? get clientId => throw _privateConstructorUsedError; @@ -478,7 +479,11 @@ abstract class $ProjectUpdateCopyWith<$Res> { _$ProjectUpdateCopyWithImpl<$Res, ProjectUpdate>; @useResult $Res call( - {String? name, String? description, String? clientId, String? userId}); + {String id, + String? name, + String? description, + String? clientId, + String? userId}); } /// @nodoc @@ -496,12 +501,17 @@ class _$ProjectUpdateCopyWithImpl<$Res, $Val extends ProjectUpdate> @pragma('vm:prefer-inline') @override $Res call({ + Object? id = null, Object? name = freezed, Object? description = freezed, Object? clientId = freezed, Object? userId = freezed, }) { return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -531,7 +541,11 @@ abstract class _$$ProjectUpdateImplCopyWith<$Res> @override @useResult $Res call( - {String? name, String? description, String? clientId, String? userId}); + {String id, + String? name, + String? description, + String? clientId, + String? userId}); } /// @nodoc @@ -547,12 +561,17 @@ class __$$ProjectUpdateImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ + Object? id = null, Object? name = freezed, Object? description = freezed, Object? clientId = freezed, Object? userId = freezed, }) { return _then(_$ProjectUpdateImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -577,8 +596,14 @@ class __$$ProjectUpdateImplCopyWithImpl<$Res> class _$ProjectUpdateImpl implements _ProjectUpdate { const _$ProjectUpdateImpl( - {this.name, this.description, this.clientId, this.userId}); + {required this.id, + this.name, + this.description, + this.clientId, + this.userId}); + @override + final String id; @override final String? name; @override @@ -590,7 +615,7 @@ class _$ProjectUpdateImpl implements _ProjectUpdate { @override String toString() { - return 'ProjectUpdate(name: $name, description: $description, clientId: $clientId, userId: $userId)'; + return 'ProjectUpdate(id: $id, name: $name, description: $description, clientId: $clientId, userId: $userId)'; } @override @@ -598,6 +623,7 @@ class _$ProjectUpdateImpl implements _ProjectUpdate { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ProjectUpdateImpl && + (identical(other.id, id) || other.id == id) && (identical(other.name, name) || other.name == name) && (identical(other.description, description) || other.description == description) && @@ -608,7 +634,7 @@ class _$ProjectUpdateImpl implements _ProjectUpdate { @override int get hashCode => - Object.hash(runtimeType, name, description, clientId, userId); + Object.hash(runtimeType, id, name, description, clientId, userId); /// Create a copy of ProjectUpdate /// with the given fields replaced by the non-null parameter values. @@ -621,11 +647,14 @@ class _$ProjectUpdateImpl implements _ProjectUpdate { abstract class _ProjectUpdate implements ProjectUpdate { const factory _ProjectUpdate( - {final String? name, + {required final String id, + final String? name, final String? description, final String? clientId, final String? userId}) = _$ProjectUpdateImpl; + @override + String get id; @override String? get name; @override diff --git a/backend-dart/lib/infrastructure/persistence/mapper/project_dbo_mapper.dart b/backend-dart/lib/infrastructure/persistence/mapper/project_dbo_mapper.dart new file mode 100644 index 0000000..e6691e8 --- /dev/null +++ b/backend-dart/lib/infrastructure/persistence/mapper/project_dbo_mapper.dart @@ -0,0 +1,64 @@ +import 'package:backend_dart/common/extensions.dart'; +import 'package:backend_dart/domain/entities/project.dart'; +import 'package:backend_dart/domain/interface/error.dart'; +import 'package:backend_dart/infrastructure/persistence/db/model.dart'; +import 'package:backend_dart/infrastructure/persistence/db/prisma.dart'; +import 'package:fpdart/fpdart.dart'; +import 'package:orm/orm.dart'; + +class ProjectDboMapper { + TaskEither toDbo(Project project) { + return TaskEither.of(ProjectDbo( + id: project.id, + name: project.name, + description: project.description, + clientId: project.clientId, + userId: project.userId, + createdAt: project.createdAt, + updatedAt: project.updatedAt, + )); + } + + TaskEither fromDbo(ProjectDbo dbo) { + return TaskEither.of(Project( + id: dbo.id!, + name: dbo.name!, + description: dbo.description, + clientId: dbo.clientId, + userId: dbo.userId!, + createdAt: dbo.createdAt!, + updatedAt: dbo.updatedAt!, + )); + } + + TaskEither fromCreatetoDbo( + ProjectCreate project) { + return TaskEither.of(ProjectDboCreateInput( + name: project.name, + description: project.description.let(PrismaUnion.$1), + clientId: project.clientId.let(PrismaUnion.$1), + user: UserDboCreateNestedOneWithoutProjectsInput( + connect: UserDboWhereUniqueInput(id: project.userId), + ), + )); + } + + TaskEither fromUpdateToDbo( + ProjectUpdate project) { + return TaskEither.of(ProjectDboUpdateInput( + id: PrismaUnion.$1(project.id), + name: project.name.let(PrismaUnion.$1), + description: project.description.let(PrismaUnion.$1), + clientId: project.clientId.let(PrismaUnion.$1), + user: project.userId.let( + (userId) => UserDboUpdateOneRequiredWithoutProjectsNestedInput( + connect: UserDboWhereUniqueInput(id: userId), + ), + ), + )); + } + + TaskEither> listFrom(Iterable dbos) { + return TaskEither.traverseList(dbos.toList(), fromDbo); + } +} diff --git a/backend-dart/lib/infrastructure/persistence/mapper/user_dbo_mapper.dart b/backend-dart/lib/infrastructure/persistence/mapper/user_dbo_mapper.dart index bbc87a2..6f6a8cf 100644 --- a/backend-dart/lib/infrastructure/persistence/mapper/user_dbo_mapper.dart +++ b/backend-dart/lib/infrastructure/persistence/mapper/user_dbo_mapper.dart @@ -21,9 +21,8 @@ class UserDboMapper { return TaskEither.traverseList(targets.toList(), from); } - TaskEither fromUpdateTo( - UserUpdate origin) => - TaskEither.of(UserDboUncheckedUpdateInput( + TaskEither fromUpdateTo(UserUpdate origin) => + TaskEither.of(UserDboUpdateInput( id: PrismaUnion.$1(origin.id), name: origin.name.let(PrismaUnion.$1), email: origin.email.let(PrismaUnion.$1), diff --git a/backend-dart/lib/infrastructure/persistence/prisma_project_data_source.dart b/backend-dart/lib/infrastructure/persistence/prisma_project_data_source.dart new file mode 100644 index 0000000..7130b5d --- /dev/null +++ b/backend-dart/lib/infrastructure/persistence/prisma_project_data_source.dart @@ -0,0 +1,128 @@ +import 'package:backend_dart/common/error_on_null.dart'; +import 'package:backend_dart/domain/data/project_data_source.dart'; +import 'package:backend_dart/domain/entities/project.dart'; +import 'package:backend_dart/domain/errors/app_error.dart'; +import 'package:backend_dart/domain/interface/error.dart'; +import 'package:backend_dart/infrastructure/persistence/db/client.dart'; +import 'package:backend_dart/infrastructure/persistence/db/model.dart'; +import 'package:backend_dart/infrastructure/persistence/db/prisma.dart'; +import 'package:backend_dart/infrastructure/persistence/mapper/project_dbo_mapper.dart'; +import 'package:fpdart/fpdart.dart'; +import 'package:orm/orm.dart'; + +class PrismaProjectDataSource implements ProjectDataSource { + final PrismaClient prisma; + final ProjectDboMapper mapper = ProjectDboMapper(); + + PrismaProjectDataSource(this.prisma); + + @override + TaskEither createProject(ProjectCreate project) { + return mapper + .fromCreatetoDbo(project) + .flatMap((projectDbo) => TaskEither.tryCatch( + () async { + return await prisma.projectDbo.create( + data: PrismaUnion.$1(projectDbo), + ); + }, + (error, _) => AppError.databaseError( + message: 'Failed to create project: ${error.toString()}', + ), + )) + .flatMap(mapper.fromDbo); + } + + @override + TaskEither findProjectById(String id) { + return TaskEither.tryCatch( + () async { + final project = await prisma.projectDbo + .findUnique(where: ProjectDboWhereUniqueInput(id: id)); + return project; + }, + (error, _) => AppError.databaseError( + message: 'Failed to find project by ID: ${error.toString()}', + ), + ) + .flatMap( + errorOnNull(AppError.notFound('Project with ID $id not found'))) + .flatMap(mapper.fromDbo); + } + + @override + TaskEither> findProjectsByUserId(String userId) { + return TaskEither>.tryCatch( + () async { + final projects = await prisma.projectDbo.findMany( + where: ProjectDboWhereInput( + userId: PrismaUnion.$1(StringFilter( + equals: PrismaUnion.$1(userId), + ))), + ); + return projects; + }, + (error, _) => AppError.databaseError( + message: 'Failed to find projects by user ID: ${error.toString()}', + ), + ).flatMap(mapper.listFrom); + } + + @override + TaskEither updateProject(ProjectUpdate project) { + return mapper + .fromUpdateToDbo(project) + .flatMap( + (projectDbo) => TaskEither.tryCatch( + () async { + return await prisma.projectDbo.update( + data: PrismaUnion.$1(projectDbo), + where: ProjectDboWhereUniqueInput(id: project.id), + ); + }, + (error, _) => AppError.databaseError( + message: 'Failed to update project: ${error.toString()}', + ), + ), + ) + .flatMap( + errorOnNull( + AppError.notFound( + 'Project with ID ${project.id} not found', + ), + ), + ) + .flatMap(mapper.fromDbo); + } + + @override + TaskEither deleteProject(String id) { + return TaskEither.tryCatch( + () async { + return await prisma.projectDbo + .delete(where: ProjectDboWhereUniqueInput(id: id)); + }, + (error, _) => AppError.databaseError( + message: 'Failed to delete project: ${error.toString()}', + ), + ) + .flatMap( + errorOnNull( + AppError.notFound( + 'Project with ID $id not found', + ), + ), + ) + .flatMap(mapper.fromDbo); + } + + @override + TaskEither> findAllProjects() { + return TaskEither>.tryCatch( + () async => await prisma.projectDbo.findMany(), + (error, _) => AppError.databaseError( + message: 'Failed to fetch all projects: ${error.toString()}', + ), + ).flatMap(mapper.listFrom); + } +} diff --git a/backend-dart/lib/infrastructure/persistence/prisma_user_data_source.dart b/backend-dart/lib/infrastructure/persistence/prisma_user_data_source.dart index 7b3604c..37c6423 100755 --- a/backend-dart/lib/infrastructure/persistence/prisma_user_data_source.dart +++ b/backend-dart/lib/infrastructure/persistence/prisma_user_data_source.dart @@ -82,7 +82,7 @@ class PrismaUserDataSource implements UserDataSource { } final updatedUser = await prisma.userDbo.update( - data: PrismaUnion.$2(userDbo), + data: PrismaUnion.$1(userDbo), where: UserDboWhereUniqueInput(id: user.id), );