added project repository

This commit is contained in:
Jean Jacques Avril 2025-01-01 20:32:01 +00:00
parent 4d52186d21
commit 6d980a980e
No known key found for this signature in database
7 changed files with 277 additions and 54 deletions

View File

@ -1,34 +1,36 @@
import 'package:backend_dart/domain/entities/project.dart'; 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. /// Interface for managing project data interactions.
abstract class ProjectDataSource { abstract class ProjectDataSource {
/// Creates a new project in the data source. /// Creates a new project in the data source.
/// ///
/// Throws an error if the creation fails. /// Throws an error if the creation fails.
Future<void> createProject(NewProject project); TaskEither<IError, Project> createProject(ProjectCreate project);
/// Retrieves a project by its unique ID. /// Retrieves a project by its unique ID.
/// ///
/// Returns `null` if no project is found. /// Returns `null` if no project is found.
Future<Project?> findProjectById(String id); TaskEither<IError, Project> findProjectById(String id);
/// Retrieves all projects associated with a specific user. /// Retrieves all projects associated with a specific user.
/// ///
/// Returns an empty list if no projects are found. /// Returns an empty list if no projects are found.
Future<List<Project>> findProjectsByUserId(String userId); TaskEither<IError,List<Project>> findProjectsByUserId(String userId);
/// Updates an existing project in the data source. /// Updates an existing project in the data source.
/// ///
/// Throws an error if the update fails. /// Throws an error if the update fails.
Future<void> updateProject(ProjectUpdate project); TaskEither<IError, Project> updateProject(ProjectUpdate project);
/// Deletes a project by its unique ID. /// Deletes a project by its unique ID.
/// ///
/// Throws an error if the deletion fails. /// Throws an error if the deletion fails.
Future<void> deleteProject(String id); TaskEither<IError, Project> deleteProject(String id);
/// Retrieves all projects in the data source. /// Retrieves all projects in the data source.
/// ///
/// Returns an empty list if no projects exist. /// Returns an empty list if no projects exist.
Future<List<Project>> findAllProjects(); TaskEither<IError, List<Project>> findAllProjects();
} }

View File

@ -3,13 +3,13 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'project.freezed.dart'; part 'project.freezed.dart';
@freezed @freezed
class NewProject with _$NewProject { class ProjectCreate with _$ProjectCreate {
const factory NewProject({ const factory ProjectCreate({
required String name, required String name,
String? description, String? description,
String? clientId, String? clientId,
required String userId, required String userId,
}) = _NewProject; }) = _ProjectCreate;
} }
@freezed @freezed
@ -28,6 +28,7 @@ class Project with _$Project {
@freezed @freezed
class ProjectUpdate with _$ProjectUpdate { class ProjectUpdate with _$ProjectUpdate {
const factory ProjectUpdate({ const factory ProjectUpdate({
required String id,
String? name, String? name,
String? description, String? description,
String? clientId, String? clientId,

View File

@ -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'); '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 /// @nodoc
mixin _$NewProject { mixin _$ProjectCreate {
String get name => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError;
String? get description => throw _privateConstructorUsedError; String? get description => throw _privateConstructorUsedError;
String? get clientId => throw _privateConstructorUsedError; String? get clientId => throw _privateConstructorUsedError;
String get userId => 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. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
$NewProjectCopyWith<NewProject> get copyWith => $ProjectCreateCopyWith<ProjectCreate> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
/// @nodoc /// @nodoc
abstract class $NewProjectCopyWith<$Res> { abstract class $ProjectCreateCopyWith<$Res> {
factory $NewProjectCopyWith( factory $ProjectCreateCopyWith(
NewProject value, $Res Function(NewProject) then) = ProjectCreate value, $Res Function(ProjectCreate) then) =
_$NewProjectCopyWithImpl<$Res, NewProject>; _$ProjectCreateCopyWithImpl<$Res, ProjectCreate>;
@useResult @useResult
$Res call( $Res call(
{String name, String? description, String? clientId, String userId}); {String name, String? description, String? clientId, String userId});
} }
/// @nodoc /// @nodoc
class _$NewProjectCopyWithImpl<$Res, $Val extends NewProject> class _$ProjectCreateCopyWithImpl<$Res, $Val extends ProjectCreate>
implements $NewProjectCopyWith<$Res> { implements $ProjectCreateCopyWith<$Res> {
_$NewProjectCopyWithImpl(this._value, this._then); _$ProjectCreateCopyWithImpl(this._value, this._then);
// ignore: unused_field // ignore: unused_field
final $Val _value; final $Val _value;
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; 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. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
@ -80,11 +80,11 @@ class _$NewProjectCopyWithImpl<$Res, $Val extends NewProject>
} }
/// @nodoc /// @nodoc
abstract class _$$NewProjectImplCopyWith<$Res> abstract class _$$ProjectCreateImplCopyWith<$Res>
implements $NewProjectCopyWith<$Res> { implements $ProjectCreateCopyWith<$Res> {
factory _$$NewProjectImplCopyWith( factory _$$ProjectCreateImplCopyWith(
_$NewProjectImpl value, $Res Function(_$NewProjectImpl) then) = _$ProjectCreateImpl value, $Res Function(_$ProjectCreateImpl) then) =
__$$NewProjectImplCopyWithImpl<$Res>; __$$ProjectCreateImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call( $Res call(
@ -92,14 +92,14 @@ abstract class _$$NewProjectImplCopyWith<$Res>
} }
/// @nodoc /// @nodoc
class __$$NewProjectImplCopyWithImpl<$Res> class __$$ProjectCreateImplCopyWithImpl<$Res>
extends _$NewProjectCopyWithImpl<$Res, _$NewProjectImpl> extends _$ProjectCreateCopyWithImpl<$Res, _$ProjectCreateImpl>
implements _$$NewProjectImplCopyWith<$Res> { implements _$$ProjectCreateImplCopyWith<$Res> {
__$$NewProjectImplCopyWithImpl( __$$ProjectCreateImplCopyWithImpl(
_$NewProjectImpl _value, $Res Function(_$NewProjectImpl) _then) _$ProjectCreateImpl _value, $Res Function(_$ProjectCreateImpl) _then)
: super(_value, _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. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
@ -109,7 +109,7 @@ class __$$NewProjectImplCopyWithImpl<$Res>
Object? clientId = freezed, Object? clientId = freezed,
Object? userId = null, Object? userId = null,
}) { }) {
return _then(_$NewProjectImpl( return _then(_$ProjectCreateImpl(
name: null == name name: null == name
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
@ -132,8 +132,8 @@ class __$$NewProjectImplCopyWithImpl<$Res>
/// @nodoc /// @nodoc
class _$NewProjectImpl implements _NewProject { class _$ProjectCreateImpl implements _ProjectCreate {
const _$NewProjectImpl( const _$ProjectCreateImpl(
{required this.name, {required this.name,
this.description, this.description,
this.clientId, this.clientId,
@ -150,14 +150,14 @@ class _$NewProjectImpl implements _NewProject {
@override @override
String toString() { String toString() {
return 'NewProject(name: $name, description: $description, clientId: $clientId, userId: $userId)'; return 'ProjectCreate(name: $name, description: $description, clientId: $clientId, userId: $userId)';
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$NewProjectImpl && other is _$ProjectCreateImpl &&
(identical(other.name, name) || other.name == name) && (identical(other.name, name) || other.name == name) &&
(identical(other.description, description) || (identical(other.description, description) ||
other.description == description) && other.description == description) &&
@ -170,21 +170,21 @@ class _$NewProjectImpl implements _NewProject {
int get hashCode => int get hashCode =>
Object.hash(runtimeType, name, description, clientId, userId); 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. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$NewProjectImplCopyWith<_$NewProjectImpl> get copyWith => _$$ProjectCreateImplCopyWith<_$ProjectCreateImpl> get copyWith =>
__$$NewProjectImplCopyWithImpl<_$NewProjectImpl>(this, _$identity); __$$ProjectCreateImplCopyWithImpl<_$ProjectCreateImpl>(this, _$identity);
} }
abstract class _NewProject implements NewProject { abstract class _ProjectCreate implements ProjectCreate {
const factory _NewProject( const factory _ProjectCreate(
{required final String name, {required final String name,
final String? description, final String? description,
final String? clientId, final String? clientId,
required final String userId}) = _$NewProjectImpl; required final String userId}) = _$ProjectCreateImpl;
@override @override
String get name; String get name;
@ -195,11 +195,11 @@ abstract class _NewProject implements NewProject {
@override @override
String get userId; String get userId;
/// Create a copy of NewProject /// Create a copy of ProjectCreate
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
_$$NewProjectImplCopyWith<_$NewProjectImpl> get copyWith => _$$ProjectCreateImplCopyWith<_$ProjectCreateImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -459,6 +459,7 @@ abstract class _Project implements Project {
/// @nodoc /// @nodoc
mixin _$ProjectUpdate { mixin _$ProjectUpdate {
String get id => throw _privateConstructorUsedError;
String? get name => throw _privateConstructorUsedError; String? get name => throw _privateConstructorUsedError;
String? get description => throw _privateConstructorUsedError; String? get description => throw _privateConstructorUsedError;
String? get clientId => throw _privateConstructorUsedError; String? get clientId => throw _privateConstructorUsedError;
@ -478,7 +479,11 @@ abstract class $ProjectUpdateCopyWith<$Res> {
_$ProjectUpdateCopyWithImpl<$Res, ProjectUpdate>; _$ProjectUpdateCopyWithImpl<$Res, ProjectUpdate>;
@useResult @useResult
$Res call( $Res call(
{String? name, String? description, String? clientId, String? userId}); {String id,
String? name,
String? description,
String? clientId,
String? userId});
} }
/// @nodoc /// @nodoc
@ -496,12 +501,17 @@ class _$ProjectUpdateCopyWithImpl<$Res, $Val extends ProjectUpdate>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? id = null,
Object? name = freezed, Object? name = freezed,
Object? description = freezed, Object? description = freezed,
Object? clientId = freezed, Object? clientId = freezed,
Object? userId = freezed, Object? userId = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
name: freezed == name name: freezed == name
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
@ -531,7 +541,11 @@ abstract class _$$ProjectUpdateImplCopyWith<$Res>
@override @override
@useResult @useResult
$Res call( $Res call(
{String? name, String? description, String? clientId, String? userId}); {String id,
String? name,
String? description,
String? clientId,
String? userId});
} }
/// @nodoc /// @nodoc
@ -547,12 +561,17 @@ class __$$ProjectUpdateImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? id = null,
Object? name = freezed, Object? name = freezed,
Object? description = freezed, Object? description = freezed,
Object? clientId = freezed, Object? clientId = freezed,
Object? userId = freezed, Object? userId = freezed,
}) { }) {
return _then(_$ProjectUpdateImpl( return _then(_$ProjectUpdateImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
name: freezed == name name: freezed == name
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
@ -577,8 +596,14 @@ class __$$ProjectUpdateImplCopyWithImpl<$Res>
class _$ProjectUpdateImpl implements _ProjectUpdate { class _$ProjectUpdateImpl implements _ProjectUpdate {
const _$ProjectUpdateImpl( 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 @override
final String? name; final String? name;
@override @override
@ -590,7 +615,7 @@ class _$ProjectUpdateImpl implements _ProjectUpdate {
@override @override
String toString() { 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 @override
@ -598,6 +623,7 @@ class _$ProjectUpdateImpl implements _ProjectUpdate {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$ProjectUpdateImpl && other is _$ProjectUpdateImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) && (identical(other.name, name) || other.name == name) &&
(identical(other.description, description) || (identical(other.description, description) ||
other.description == description) && other.description == description) &&
@ -608,7 +634,7 @@ class _$ProjectUpdateImpl implements _ProjectUpdate {
@override @override
int get hashCode => int get hashCode =>
Object.hash(runtimeType, name, description, clientId, userId); Object.hash(runtimeType, id, name, description, clientId, userId);
/// Create a copy of ProjectUpdate /// Create a copy of ProjectUpdate
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -621,11 +647,14 @@ class _$ProjectUpdateImpl implements _ProjectUpdate {
abstract class _ProjectUpdate implements ProjectUpdate { abstract class _ProjectUpdate implements ProjectUpdate {
const factory _ProjectUpdate( const factory _ProjectUpdate(
{final String? name, {required final String id,
final String? name,
final String? description, final String? description,
final String? clientId, final String? clientId,
final String? userId}) = _$ProjectUpdateImpl; final String? userId}) = _$ProjectUpdateImpl;
@override
String get id;
@override @override
String? get name; String? get name;
@override @override

View File

@ -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<IError, ProjectDbo> 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<IError, Project> 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<IError, ProjectDboCreateInput> 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<IError, ProjectDboUpdateInput> 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<IError, List<Project>> listFrom(Iterable<ProjectDbo> dbos) {
return TaskEither.traverseList(dbos.toList(), fromDbo);
}
}

View File

@ -21,9 +21,8 @@ class UserDboMapper {
return TaskEither.traverseList(targets.toList(), from); return TaskEither.traverseList(targets.toList(), from);
} }
TaskEither<IError, UserDboUncheckedUpdateInput> fromUpdateTo( TaskEither<IError, UserDboUpdateInput> fromUpdateTo(UserUpdate origin) =>
UserUpdate origin) => TaskEither.of(UserDboUpdateInput(
TaskEither.of(UserDboUncheckedUpdateInput(
id: PrismaUnion.$1(origin.id), id: PrismaUnion.$1(origin.id),
name: origin.name.let(PrismaUnion.$1), name: origin.name.let(PrismaUnion.$1),
email: origin.email.let(PrismaUnion.$1), email: origin.email.let(PrismaUnion.$1),

View File

@ -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<IError, Project> 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<IError, Project> findProjectById(String id) {
return TaskEither<IError, ProjectDbo?>.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<IError, List<Project>> findProjectsByUserId(String userId) {
return TaskEither<IError, Iterable<ProjectDbo>>.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<IError, Project> 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<IError, Project> deleteProject(String id) {
return TaskEither<IError, ProjectDbo?>.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<IError, List<Project>> findAllProjects() {
return TaskEither<IError, Iterable<ProjectDbo>>.tryCatch(
() async => await prisma.projectDbo.findMany(),
(error, _) => AppError.databaseError(
message: 'Failed to fetch all projects: ${error.toString()}',
),
).flatMap(mapper.listFrom);
}
}

View File

@ -82,7 +82,7 @@ class PrismaUserDataSource implements UserDataSource {
} }
final updatedUser = await prisma.userDbo.update( final updatedUser = await prisma.userDbo.update(
data: PrismaUnion.$2(userDbo), data: PrismaUnion.$1(userDbo),
where: UserDboWhereUniqueInput(id: user.id), where: UserDboWhereUniqueInput(id: user.id),
); );