refactor user repo with helpers. better api validation etc.

This commit is contained in:
Jean Jacques Avril 2025-01-01 19:58:19 +00:00
parent 8559b1c44e
commit 4d52186d21
No known key found for this signature in database
23 changed files with 1802 additions and 237 deletions

View File

@ -1,5 +1,5 @@
import 'package:backend_dart/domain/entities/user.dart';
import 'package:backend_dart/domain/interface/database.dart';
import 'package:backend_dart/domain/data/database.dart';
import 'package:backend_dart/domain/interface/error.dart';
import 'package:backend_dart/domain/repository/user_repository.dart';
import 'package:fpdart/fpdart.dart';
@ -9,7 +9,7 @@ class UserRepositoryImpl implements UserRepository {
UserRepositoryImpl(this.database);
@override
TaskEither<IError, User> create(User user) {
TaskEither<IError, User> create(UserCreate user) {
return database.users
.generateId()
.map(
@ -29,7 +29,7 @@ class UserRepositoryImpl implements UserRepository {
}
@override
TaskEither<IError, User> update(User user) {
TaskEither<IError, User> update(UserUpdate user) {
return database.users.update(user);
}

View File

@ -6,15 +6,39 @@ part 'user_dto.g.dart';
@freezed
class UserDto with _$UserDto {
const factory UserDto({
String? id,
String? name,
String? email,
String? password,
required String id,
required String name,
required String email,
DateTime? createdAt,
DateTime? updatedAt,
}) = _UserDto;
/// JSON-Serialisierung
factory UserDto.fromJson(Map<String, dynamic> json) =>
_$UserDtoFromJson(json);
factory UserDto.fromJson(Map<String, dynamic> json) => _$UserDtoFromJson(json);
}
@freezed
class UserCreateDto with _$UserCreateDto {
const factory UserCreateDto({
required String name,
required String email,
required String password,
}) = _UserCreateDto;
/// JSON-Serialisierung
factory UserCreateDto.fromJson(Map<String, dynamic> json) =>
_$UserCreateDtoFromJson(json);
}
@freezed
class UserUpdateDto with _$UserUpdateDto {
const factory UserUpdateDto({
String? name,
String? email,
String? password,
}) = _UserUpdateDto;
/// JSON-Serialisierung
factory UserUpdateDto.fromJson(Map<String, dynamic> json) =>
_$UserUpdateDtoFromJson(json);
}

View File

@ -20,10 +20,9 @@ UserDto _$UserDtoFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$UserDto {
String? get id => throw _privateConstructorUsedError;
String? get name => throw _privateConstructorUsedError;
String? get email => throw _privateConstructorUsedError;
String? get password => throw _privateConstructorUsedError;
String get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get email => throw _privateConstructorUsedError;
DateTime? get createdAt => throw _privateConstructorUsedError;
DateTime? get updatedAt => throw _privateConstructorUsedError;
@ -42,10 +41,9 @@ abstract class $UserDtoCopyWith<$Res> {
_$UserDtoCopyWithImpl<$Res, UserDto>;
@useResult
$Res call(
{String? id,
String? name,
String? email,
String? password,
{String id,
String name,
String email,
DateTime? createdAt,
DateTime? updatedAt});
}
@ -65,30 +63,25 @@ class _$UserDtoCopyWithImpl<$Res, $Val extends UserDto>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? name = freezed,
Object? email = freezed,
Object? password = freezed,
Object? id = null,
Object? name = null,
Object? email = null,
Object? createdAt = freezed,
Object? updatedAt = freezed,
}) {
return _then(_value.copyWith(
id: freezed == id
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String?,
name: freezed == name
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
email: freezed == email
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
password: freezed == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String?,
as String,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
@ -109,10 +102,9 @@ abstract class _$$UserDtoImplCopyWith<$Res> implements $UserDtoCopyWith<$Res> {
@override
@useResult
$Res call(
{String? id,
String? name,
String? email,
String? password,
{String id,
String name,
String email,
DateTime? createdAt,
DateTime? updatedAt});
}
@ -130,30 +122,25 @@ class __$$UserDtoImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? name = freezed,
Object? email = freezed,
Object? password = freezed,
Object? id = null,
Object? name = null,
Object? email = null,
Object? createdAt = freezed,
Object? updatedAt = freezed,
}) {
return _then(_$UserDtoImpl(
id: freezed == id
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String?,
name: freezed == name
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
email: freezed == email
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
password: freezed == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String?,
as String,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
@ -170,10 +157,9 @@ class __$$UserDtoImplCopyWithImpl<$Res>
@JsonSerializable()
class _$UserDtoImpl implements _UserDto {
const _$UserDtoImpl(
{this.id,
this.name,
this.email,
this.password,
{required this.id,
required this.name,
required this.email,
this.createdAt,
this.updatedAt});
@ -181,13 +167,11 @@ class _$UserDtoImpl implements _UserDto {
_$$UserDtoImplFromJson(json);
@override
final String? id;
final String id;
@override
final String? name;
final String name;
@override
final String? email;
@override
final String? password;
final String email;
@override
final DateTime? createdAt;
@override
@ -195,7 +179,7 @@ class _$UserDtoImpl implements _UserDto {
@override
String toString() {
return 'UserDto(id: $id, name: $name, email: $email, password: $password, createdAt: $createdAt, updatedAt: $updatedAt)';
return 'UserDto(id: $id, name: $name, email: $email, createdAt: $createdAt, updatedAt: $updatedAt)';
}
@override
@ -206,8 +190,6 @@ class _$UserDtoImpl implements _UserDto {
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.password, password) ||
other.password == password) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
@ -217,7 +199,7 @@ class _$UserDtoImpl implements _UserDto {
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, id, name, email, password, createdAt, updatedAt);
Object.hash(runtimeType, id, name, email, createdAt, updatedAt);
/// Create a copy of UserDto
/// with the given fields replaced by the non-null parameter values.
@ -237,23 +219,20 @@ class _$UserDtoImpl implements _UserDto {
abstract class _UserDto implements UserDto {
const factory _UserDto(
{final String? id,
final String? name,
final String? email,
final String? password,
{required final String id,
required final String name,
required final String email,
final DateTime? createdAt,
final DateTime? updatedAt}) = _$UserDtoImpl;
factory _UserDto.fromJson(Map<String, dynamic> json) = _$UserDtoImpl.fromJson;
@override
String? get id;
String get id;
@override
String? get name;
String get name;
@override
String? get email;
@override
String? get password;
String get email;
@override
DateTime? get createdAt;
@override
@ -266,3 +245,374 @@ abstract class _UserDto implements UserDto {
_$$UserDtoImplCopyWith<_$UserDtoImpl> get copyWith =>
throw _privateConstructorUsedError;
}
UserCreateDto _$UserCreateDtoFromJson(Map<String, dynamic> json) {
return _UserCreateDto.fromJson(json);
}
/// @nodoc
mixin _$UserCreateDto {
String get name => throw _privateConstructorUsedError;
String get email => throw _privateConstructorUsedError;
String get password => throw _privateConstructorUsedError;
/// Serializes this UserCreateDto to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of UserCreateDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$UserCreateDtoCopyWith<UserCreateDto> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $UserCreateDtoCopyWith<$Res> {
factory $UserCreateDtoCopyWith(
UserCreateDto value, $Res Function(UserCreateDto) then) =
_$UserCreateDtoCopyWithImpl<$Res, UserCreateDto>;
@useResult
$Res call({String name, String email, String password});
}
/// @nodoc
class _$UserCreateDtoCopyWithImpl<$Res, $Val extends UserCreateDto>
implements $UserCreateDtoCopyWith<$Res> {
_$UserCreateDtoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of UserCreateDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? email = null,
Object? password = null,
}) {
return _then(_value.copyWith(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$UserCreateDtoImplCopyWith<$Res>
implements $UserCreateDtoCopyWith<$Res> {
factory _$$UserCreateDtoImplCopyWith(
_$UserCreateDtoImpl value, $Res Function(_$UserCreateDtoImpl) then) =
__$$UserCreateDtoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String name, String email, String password});
}
/// @nodoc
class __$$UserCreateDtoImplCopyWithImpl<$Res>
extends _$UserCreateDtoCopyWithImpl<$Res, _$UserCreateDtoImpl>
implements _$$UserCreateDtoImplCopyWith<$Res> {
__$$UserCreateDtoImplCopyWithImpl(
_$UserCreateDtoImpl _value, $Res Function(_$UserCreateDtoImpl) _then)
: super(_value, _then);
/// Create a copy of UserCreateDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? email = null,
Object? password = null,
}) {
return _then(_$UserCreateDtoImpl(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$UserCreateDtoImpl implements _UserCreateDto {
const _$UserCreateDtoImpl(
{required this.name, required this.email, required this.password});
factory _$UserCreateDtoImpl.fromJson(Map<String, dynamic> json) =>
_$$UserCreateDtoImplFromJson(json);
@override
final String name;
@override
final String email;
@override
final String password;
@override
String toString() {
return 'UserCreateDto(name: $name, email: $email, password: $password)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$UserCreateDtoImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.password, password) ||
other.password == password));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, name, email, password);
/// Create a copy of UserCreateDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$UserCreateDtoImplCopyWith<_$UserCreateDtoImpl> get copyWith =>
__$$UserCreateDtoImplCopyWithImpl<_$UserCreateDtoImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$UserCreateDtoImplToJson(
this,
);
}
}
abstract class _UserCreateDto implements UserCreateDto {
const factory _UserCreateDto(
{required final String name,
required final String email,
required final String password}) = _$UserCreateDtoImpl;
factory _UserCreateDto.fromJson(Map<String, dynamic> json) =
_$UserCreateDtoImpl.fromJson;
@override
String get name;
@override
String get email;
@override
String get password;
/// Create a copy of UserCreateDto
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$UserCreateDtoImplCopyWith<_$UserCreateDtoImpl> get copyWith =>
throw _privateConstructorUsedError;
}
UserUpdateDto _$UserUpdateDtoFromJson(Map<String, dynamic> json) {
return _UserUpdateDto.fromJson(json);
}
/// @nodoc
mixin _$UserUpdateDto {
String? get name => throw _privateConstructorUsedError;
String? get email => throw _privateConstructorUsedError;
String? get password => throw _privateConstructorUsedError;
/// Serializes this UserUpdateDto to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of UserUpdateDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$UserUpdateDtoCopyWith<UserUpdateDto> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $UserUpdateDtoCopyWith<$Res> {
factory $UserUpdateDtoCopyWith(
UserUpdateDto value, $Res Function(UserUpdateDto) then) =
_$UserUpdateDtoCopyWithImpl<$Res, UserUpdateDto>;
@useResult
$Res call({String? name, String? email, String? password});
}
/// @nodoc
class _$UserUpdateDtoCopyWithImpl<$Res, $Val extends UserUpdateDto>
implements $UserUpdateDtoCopyWith<$Res> {
_$UserUpdateDtoCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of UserUpdateDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = freezed,
Object? email = freezed,
Object? password = freezed,
}) {
return _then(_value.copyWith(
name: freezed == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
email: freezed == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
password: freezed == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$UserUpdateDtoImplCopyWith<$Res>
implements $UserUpdateDtoCopyWith<$Res> {
factory _$$UserUpdateDtoImplCopyWith(
_$UserUpdateDtoImpl value, $Res Function(_$UserUpdateDtoImpl) then) =
__$$UserUpdateDtoImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String? name, String? email, String? password});
}
/// @nodoc
class __$$UserUpdateDtoImplCopyWithImpl<$Res>
extends _$UserUpdateDtoCopyWithImpl<$Res, _$UserUpdateDtoImpl>
implements _$$UserUpdateDtoImplCopyWith<$Res> {
__$$UserUpdateDtoImplCopyWithImpl(
_$UserUpdateDtoImpl _value, $Res Function(_$UserUpdateDtoImpl) _then)
: super(_value, _then);
/// Create a copy of UserUpdateDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = freezed,
Object? email = freezed,
Object? password = freezed,
}) {
return _then(_$UserUpdateDtoImpl(
name: freezed == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
email: freezed == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
password: freezed == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$UserUpdateDtoImpl implements _UserUpdateDto {
const _$UserUpdateDtoImpl({this.name, this.email, this.password});
factory _$UserUpdateDtoImpl.fromJson(Map<String, dynamic> json) =>
_$$UserUpdateDtoImplFromJson(json);
@override
final String? name;
@override
final String? email;
@override
final String? password;
@override
String toString() {
return 'UserUpdateDto(name: $name, email: $email, password: $password)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$UserUpdateDtoImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.password, password) ||
other.password == password));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, name, email, password);
/// Create a copy of UserUpdateDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$UserUpdateDtoImplCopyWith<_$UserUpdateDtoImpl> get copyWith =>
__$$UserUpdateDtoImplCopyWithImpl<_$UserUpdateDtoImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$UserUpdateDtoImplToJson(
this,
);
}
}
abstract class _UserUpdateDto implements UserUpdateDto {
const factory _UserUpdateDto(
{final String? name,
final String? email,
final String? password}) = _$UserUpdateDtoImpl;
factory _UserUpdateDto.fromJson(Map<String, dynamic> json) =
_$UserUpdateDtoImpl.fromJson;
@override
String? get name;
@override
String? get email;
@override
String? get password;
/// Create a copy of UserUpdateDto
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$UserUpdateDtoImplCopyWith<_$UserUpdateDtoImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -8,10 +8,9 @@ part of 'user_dto.dart';
_$UserDtoImpl _$$UserDtoImplFromJson(Map<String, dynamic> json) =>
_$UserDtoImpl(
id: json['id'] as String?,
name: json['name'] as String?,
email: json['email'] as String?,
password: json['password'] as String?,
id: json['id'] as String,
name: json['name'] as String,
email: json['email'] as String,
createdAt: json['createdAt'] == null
? null
: DateTime.parse(json['createdAt'] as String),
@ -25,7 +24,34 @@ Map<String, dynamic> _$$UserDtoImplToJson(_$UserDtoImpl instance) =>
'id': instance.id,
'name': instance.name,
'email': instance.email,
'password': instance.password,
'createdAt': instance.createdAt?.toIso8601String(),
'updatedAt': instance.updatedAt?.toIso8601String(),
};
_$UserCreateDtoImpl _$$UserCreateDtoImplFromJson(Map<String, dynamic> json) =>
_$UserCreateDtoImpl(
name: json['name'] as String,
email: json['email'] as String,
password: json['password'] as String,
);
Map<String, dynamic> _$$UserCreateDtoImplToJson(_$UserCreateDtoImpl instance) =>
<String, dynamic>{
'name': instance.name,
'email': instance.email,
'password': instance.password,
};
_$UserUpdateDtoImpl _$$UserUpdateDtoImplFromJson(Map<String, dynamic> json) =>
_$UserUpdateDtoImpl(
name: json['name'] as String?,
email: json['email'] as String?,
password: json['password'] as String?,
);
Map<String, dynamic> _$$UserUpdateDtoImplToJson(_$UserUpdateDtoImpl instance) =>
<String, dynamic>{
'name': instance.name,
'email': instance.email,
'password': instance.password,
};

View File

@ -18,7 +18,6 @@ class UserDtoMapper implements IMapper<User, UserDto> {
id: dto.id,
name: dto.name,
email: dto.email,
password: dto.password,
createdAt: dto.createdAt,
updatedAt: dto.updatedAt,
));
@ -32,4 +31,20 @@ class UserDtoMapper implements IMapper<User, UserDto> {
TaskEither<IError, List<UserDto>> listTo(Iterable<User> origins) {
return TaskEither.traverseList(origins.toList(), to);
}
TaskEither<IError, UserCreate> fromCreateTo(UserCreateDto origin) =>
TaskEither.of(UserCreate(
name: origin.name,
email: origin.email,
password: origin.password,
));
TaskEither<IError, UserUpdate> fromUpdateTo(
UserUpdateDto origin, String id) =>
TaskEither.of(UserUpdate(
id: id,
name: origin.name,
email: origin.email,
password: origin.password,
));
}

View File

@ -2,11 +2,10 @@ import 'dart:convert';
import 'package:backend_dart/application/service/dto/user_dto.dart';
import 'package:backend_dart/application/service/mapper/user_dto_mapper.dart';
import 'package:backend_dart/common/request_helper.dart';
import 'package:backend_dart/common/response_helpers.dart';
import 'package:backend_dart/domain/errors/app_error.dart';
import 'package:backend_dart/domain/interface/error.dart';
import 'package:backend_dart/common/validation.dart';
import 'package:backend_dart/domain/repository/user_repository.dart';
import 'package:fpdart/fpdart.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
@ -40,15 +39,11 @@ class UserService {
@Route.post('/')
Future<Response> createUser(Request request) async {
return TaskEither<IError, UserDto>.tryCatch(() async {
final json = jsonDecode(await request.readAsString());
final user = UserDto.fromJson(json);
return user;
}, (error, stack) {
return AppError.inputError(
message: 'Failed to create user: ${error.toString()}');
})
.flatMap(mapper.from)
return requestToJson(request)
.flatMap(
(json) => validateJsonKeys(json, ['name', 'email', 'password']))
.flatMap((json) => decodeJson(json, UserCreateDto.fromJson))
.flatMap(mapper.fromCreateTo)
.flatMap(users.create)
.flatMap(mapper.to)
.match((left) => ResponseHelpers.fromError(left),
@ -58,15 +53,10 @@ class UserService {
@Route.put('/<userId>')
Future<Response> updateUser(Request request, String userId) async {
return TaskEither<IError, UserDto>.tryCatch(() async {
final json = jsonDecode(await request.readAsString());
final user = UserDto.fromJson(json);
return user;
}, (error, stack) {
return AppError.inputError(
message: 'Failed to update user: ${error.toString()}');
})
.flatMap(mapper.from)
return requestToJson(request)
.flatMap((json) => validateJsonKeys(json, []))
.flatMap((json) => decodeJson(json, UserUpdateDto.fromJson))
.flatMap((dto) => mapper.fromUpdateTo(dto, userId))
.flatMap(users.update)
.flatMap(mapper.to)
.match((left) => ResponseHelpers.fromError(left),

View File

@ -0,0 +1,8 @@
extension Let<T> on T? {
R? let<R>(R Function(T it) action) {
if (this != null) {
return action(this!);
}
return null;
}
}

View File

@ -0,0 +1,31 @@
import 'dart:convert';
import 'dart:core';
import 'package:backend_dart/domain/errors/app_error.dart';
import 'package:backend_dart/domain/interface/error.dart';
import 'package:fpdart/fpdart.dart';
import 'package:shelf/shelf.dart';
TaskEither<IError, Map<String, dynamic>> requestToJson(Request request) {
return TaskEither.tryCatch(
() async {
final body = await request.readAsString();
return jsonDecode(body);
},
(error, stack) => AppError.inputError(
message: 'Failed to decode JSON: ${error.toString()}',
),
);
}
TaskEither<IError, T> decodeJson<T>(
Map<String, dynamic> json, T Function(Map<String, dynamic>) fromJson) {
return TaskEither.tryCatch(
() async {
return fromJson(json);
},
(error, stack) => AppError.inputError(
message: 'Failed to decode JSON: ${error.toString()}',
),
);
}

View File

@ -0,0 +1,10 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
String generateSecureHash(String input) {
final bytes = utf8.encode(input);
final hash = sha256.convert(bytes);
print(hash);
return hash.toString();
}

View File

@ -0,0 +1,22 @@
import 'package:backend_dart/domain/errors/app_error.dart';
import 'package:backend_dart/domain/interface/error.dart';
import 'package:fpdart/fpdart.dart';
TaskEither<IError, Map<String, dynamic>> validateJsonKeys(
Map<String, dynamic> json, List<String> requiredKeys) {
return TaskEither.tryCatch(
() async {
final missingKeys =
requiredKeys.where((key) => !json.containsKey(key)).toList();
if (missingKeys.isNotEmpty) {
throw Exception('Missing required keys: ${missingKeys.join(', ')}');
}
return json;
},
(error, _) => AppError.validationError(
message: 'Failed to validate JSON keys: ${error.toString()}',
),
);
}

View File

@ -1,7 +1,6 @@
import 'package:backend_dart/domain/data/user_data_source.dart';
import 'package:backend_dart/domain/entities/user.dart';
abstract class IDatabase {
UserDataSource<User> get users;
UserDataSource get users;
Future<void> close();
}

View File

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

View File

@ -1,18 +1,19 @@
import 'package:backend_dart/domain/entities/user.dart';
import 'package:backend_dart/domain/interface/error.dart';
import 'package:fpdart/fpdart.dart';
abstract class UserDataSource<T> {
TaskEither<IError, T> create(T user);
abstract class UserDataSource {
TaskEither<IError, User> create(UserCreate user);
TaskEither<IError, T> findByEmail(String email);
TaskEither<IError, User> findByEmail(String email);
TaskEither<IError, T> findById(String id);
TaskEither<IError, User> findById(String id);
TaskEither<IError, T> update(T user);
TaskEither<IError, User> update(UserUpdate user);
TaskEither<IError, void> delete(String id);
TaskEither<IError, List<T>> findAll();
TaskEither<IError, List<User>> findAll();
TaskEither<IError, String> generateId();
}

View File

@ -0,0 +1,36 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'project.freezed.dart';
@freezed
class NewProject with _$NewProject {
const factory NewProject({
required String name,
String? description,
String? clientId,
required String userId,
}) = _NewProject;
}
@freezed
class Project with _$Project {
const factory Project({
required String id,
required String name,
String? description,
String? clientId,
required String userId,
required DateTime createdAt,
required DateTime updatedAt,
}) = _Project;
}
@freezed
class ProjectUpdate with _$ProjectUpdate {
const factory ProjectUpdate({
String? name,
String? description,
String? clientId,
String? userId,
}) = _ProjectUpdate;
}

View File

@ -0,0 +1,644 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'project.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
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 {
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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$NewProjectCopyWith<NewProject> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $NewProjectCopyWith<$Res> {
factory $NewProjectCopyWith(
NewProject value, $Res Function(NewProject) then) =
_$NewProjectCopyWithImpl<$Res, NewProject>;
@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);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of NewProject
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? description = freezed,
Object? clientId = freezed,
Object? userId = null,
}) {
return _then(_value.copyWith(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
clientId: freezed == clientId
? _value.clientId
: clientId // ignore: cast_nullable_to_non_nullable
as String?,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$NewProjectImplCopyWith<$Res>
implements $NewProjectCopyWith<$Res> {
factory _$$NewProjectImplCopyWith(
_$NewProjectImpl value, $Res Function(_$NewProjectImpl) then) =
__$$NewProjectImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String name, String? description, String? clientId, String userId});
}
/// @nodoc
class __$$NewProjectImplCopyWithImpl<$Res>
extends _$NewProjectCopyWithImpl<$Res, _$NewProjectImpl>
implements _$$NewProjectImplCopyWith<$Res> {
__$$NewProjectImplCopyWithImpl(
_$NewProjectImpl _value, $Res Function(_$NewProjectImpl) _then)
: super(_value, _then);
/// Create a copy of NewProject
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? description = freezed,
Object? clientId = freezed,
Object? userId = null,
}) {
return _then(_$NewProjectImpl(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
clientId: freezed == clientId
? _value.clientId
: clientId // ignore: cast_nullable_to_non_nullable
as String?,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$NewProjectImpl implements _NewProject {
const _$NewProjectImpl(
{required this.name,
this.description,
this.clientId,
required this.userId});
@override
final String name;
@override
final String? description;
@override
final String? clientId;
@override
final String userId;
@override
String toString() {
return 'NewProject(name: $name, description: $description, clientId: $clientId, userId: $userId)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$NewProjectImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.clientId, clientId) ||
other.clientId == clientId) &&
(identical(other.userId, userId) || other.userId == userId));
}
@override
int get hashCode =>
Object.hash(runtimeType, name, description, clientId, userId);
/// Create a copy of NewProject
/// 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);
}
abstract class _NewProject implements NewProject {
const factory _NewProject(
{required final String name,
final String? description,
final String? clientId,
required final String userId}) = _$NewProjectImpl;
@override
String get name;
@override
String? get description;
@override
String? get clientId;
@override
String get userId;
/// Create a copy of NewProject
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$NewProjectImplCopyWith<_$NewProjectImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$Project {
String get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String? get description => throw _privateConstructorUsedError;
String? get clientId => throw _privateConstructorUsedError;
String get userId => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError;
/// Create a copy of Project
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProjectCopyWith<Project> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProjectCopyWith<$Res> {
factory $ProjectCopyWith(Project value, $Res Function(Project) then) =
_$ProjectCopyWithImpl<$Res, Project>;
@useResult
$Res call(
{String id,
String name,
String? description,
String? clientId,
String userId,
DateTime createdAt,
DateTime updatedAt});
}
/// @nodoc
class _$ProjectCopyWithImpl<$Res, $Val extends Project>
implements $ProjectCopyWith<$Res> {
_$ProjectCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Project
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? name = null,
Object? description = freezed,
Object? clientId = freezed,
Object? userId = null,
Object? createdAt = null,
Object? updatedAt = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
clientId: freezed == clientId
? _value.clientId
: clientId // ignore: cast_nullable_to_non_nullable
as String?,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProjectImplCopyWith<$Res> implements $ProjectCopyWith<$Res> {
factory _$$ProjectImplCopyWith(
_$ProjectImpl value, $Res Function(_$ProjectImpl) then) =
__$$ProjectImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String id,
String name,
String? description,
String? clientId,
String userId,
DateTime createdAt,
DateTime updatedAt});
}
/// @nodoc
class __$$ProjectImplCopyWithImpl<$Res>
extends _$ProjectCopyWithImpl<$Res, _$ProjectImpl>
implements _$$ProjectImplCopyWith<$Res> {
__$$ProjectImplCopyWithImpl(
_$ProjectImpl _value, $Res Function(_$ProjectImpl) _then)
: super(_value, _then);
/// Create a copy of Project
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? name = null,
Object? description = freezed,
Object? clientId = freezed,
Object? userId = null,
Object? createdAt = null,
Object? updatedAt = null,
}) {
return _then(_$ProjectImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
clientId: freezed == clientId
? _value.clientId
: clientId // ignore: cast_nullable_to_non_nullable
as String?,
userId: null == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
class _$ProjectImpl implements _Project {
const _$ProjectImpl(
{required this.id,
required this.name,
this.description,
this.clientId,
required this.userId,
required this.createdAt,
required this.updatedAt});
@override
final String id;
@override
final String name;
@override
final String? description;
@override
final String? clientId;
@override
final String userId;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
String toString() {
return 'Project(id: $id, name: $name, description: $description, clientId: $clientId, userId: $userId, createdAt: $createdAt, updatedAt: $updatedAt)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProjectImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.clientId, clientId) ||
other.clientId == clientId) &&
(identical(other.userId, userId) || other.userId == userId) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt));
}
@override
int get hashCode => Object.hash(runtimeType, id, name, description, clientId,
userId, createdAt, updatedAt);
/// Create a copy of Project
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProjectImplCopyWith<_$ProjectImpl> get copyWith =>
__$$ProjectImplCopyWithImpl<_$ProjectImpl>(this, _$identity);
}
abstract class _Project implements Project {
const factory _Project(
{required final String id,
required final String name,
final String? description,
final String? clientId,
required final String userId,
required final DateTime createdAt,
required final DateTime updatedAt}) = _$ProjectImpl;
@override
String get id;
@override
String get name;
@override
String? get description;
@override
String? get clientId;
@override
String get userId;
@override
DateTime get createdAt;
@override
DateTime get updatedAt;
/// Create a copy of Project
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProjectImplCopyWith<_$ProjectImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ProjectUpdate {
String? get name => throw _privateConstructorUsedError;
String? get description => throw _privateConstructorUsedError;
String? get clientId => throw _privateConstructorUsedError;
String? get userId => throw _privateConstructorUsedError;
/// Create a copy of ProjectUpdate
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ProjectUpdateCopyWith<ProjectUpdate> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProjectUpdateCopyWith<$Res> {
factory $ProjectUpdateCopyWith(
ProjectUpdate value, $Res Function(ProjectUpdate) then) =
_$ProjectUpdateCopyWithImpl<$Res, ProjectUpdate>;
@useResult
$Res call(
{String? name, String? description, String? clientId, String? userId});
}
/// @nodoc
class _$ProjectUpdateCopyWithImpl<$Res, $Val extends ProjectUpdate>
implements $ProjectUpdateCopyWith<$Res> {
_$ProjectUpdateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProjectUpdate
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = freezed,
Object? description = freezed,
Object? clientId = freezed,
Object? userId = freezed,
}) {
return _then(_value.copyWith(
name: freezed == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
clientId: freezed == clientId
? _value.clientId
: clientId // ignore: cast_nullable_to_non_nullable
as String?,
userId: freezed == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProjectUpdateImplCopyWith<$Res>
implements $ProjectUpdateCopyWith<$Res> {
factory _$$ProjectUpdateImplCopyWith(
_$ProjectUpdateImpl value, $Res Function(_$ProjectUpdateImpl) then) =
__$$ProjectUpdateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String? name, String? description, String? clientId, String? userId});
}
/// @nodoc
class __$$ProjectUpdateImplCopyWithImpl<$Res>
extends _$ProjectUpdateCopyWithImpl<$Res, _$ProjectUpdateImpl>
implements _$$ProjectUpdateImplCopyWith<$Res> {
__$$ProjectUpdateImplCopyWithImpl(
_$ProjectUpdateImpl _value, $Res Function(_$ProjectUpdateImpl) _then)
: super(_value, _then);
/// Create a copy of ProjectUpdate
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = freezed,
Object? description = freezed,
Object? clientId = freezed,
Object? userId = freezed,
}) {
return _then(_$ProjectUpdateImpl(
name: freezed == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
description: freezed == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String?,
clientId: freezed == clientId
? _value.clientId
: clientId // ignore: cast_nullable_to_non_nullable
as String?,
userId: freezed == userId
? _value.userId
: userId // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
class _$ProjectUpdateImpl implements _ProjectUpdate {
const _$ProjectUpdateImpl(
{this.name, this.description, this.clientId, this.userId});
@override
final String? name;
@override
final String? description;
@override
final String? clientId;
@override
final String? userId;
@override
String toString() {
return 'ProjectUpdate(name: $name, description: $description, clientId: $clientId, userId: $userId)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProjectUpdateImpl &&
(identical(other.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.clientId, clientId) ||
other.clientId == clientId) &&
(identical(other.userId, userId) || other.userId == userId));
}
@override
int get hashCode =>
Object.hash(runtimeType, name, description, clientId, userId);
/// Create a copy of ProjectUpdate
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ProjectUpdateImplCopyWith<_$ProjectUpdateImpl> get copyWith =>
__$$ProjectUpdateImplCopyWithImpl<_$ProjectUpdateImpl>(this, _$identity);
}
abstract class _ProjectUpdate implements ProjectUpdate {
const factory _ProjectUpdate(
{final String? name,
final String? description,
final String? clientId,
final String? userId}) = _$ProjectUpdateImpl;
@override
String? get name;
@override
String? get description;
@override
String? get clientId;
@override
String? get userId;
/// Create a copy of ProjectUpdate
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ProjectUpdateImplCopyWith<_$ProjectUpdateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -5,11 +5,31 @@ part 'user.freezed.dart';
@freezed
class User with _$User {
const factory User({
String? id,
String? name,
String? email,
String? password,
required String id,
required String name,
required String email,
String? passwordHash,
DateTime? createdAt,
DateTime? updatedAt,
}) = _User;
}
@freezed
class UserCreate with _$UserCreate {
const factory UserCreate({
String? id,
required String name,
required String email,
required String password,
}) = _UserCreate;
}
@freezed
class UserUpdate with _$UserUpdate {
const factory UserUpdate({
required String id,
String? name,
String? email,
String? password,
}) = _UserUpdate;
}

View File

@ -16,10 +16,10 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$User {
String? get id => throw _privateConstructorUsedError;
String? get name => throw _privateConstructorUsedError;
String? get email => throw _privateConstructorUsedError;
String? get password => throw _privateConstructorUsedError;
String get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get email => throw _privateConstructorUsedError;
String? get passwordHash => throw _privateConstructorUsedError;
DateTime? get createdAt => throw _privateConstructorUsedError;
DateTime? get updatedAt => throw _privateConstructorUsedError;
@ -35,10 +35,10 @@ abstract class $UserCopyWith<$Res> {
_$UserCopyWithImpl<$Res, User>;
@useResult
$Res call(
{String? id,
String? name,
String? email,
String? password,
{String id,
String name,
String email,
String? passwordHash,
DateTime? createdAt,
DateTime? updatedAt});
}
@ -58,29 +58,29 @@ class _$UserCopyWithImpl<$Res, $Val extends User>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? name = freezed,
Object? email = freezed,
Object? password = freezed,
Object? id = null,
Object? name = null,
Object? email = null,
Object? passwordHash = freezed,
Object? createdAt = freezed,
Object? updatedAt = freezed,
}) {
return _then(_value.copyWith(
id: freezed == id
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String?,
name: freezed == name
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
email: freezed == email
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
password: freezed == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
passwordHash: freezed == passwordHash
? _value.passwordHash
: passwordHash // ignore: cast_nullable_to_non_nullable
as String?,
createdAt: freezed == createdAt
? _value.createdAt
@ -102,10 +102,10 @@ abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> {
@override
@useResult
$Res call(
{String? id,
String? name,
String? email,
String? password,
{String id,
String name,
String email,
String? passwordHash,
DateTime? createdAt,
DateTime? updatedAt});
}
@ -122,29 +122,29 @@ class __$$UserImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? name = freezed,
Object? email = freezed,
Object? password = freezed,
Object? id = null,
Object? name = null,
Object? email = null,
Object? passwordHash = freezed,
Object? createdAt = freezed,
Object? updatedAt = freezed,
}) {
return _then(_$UserImpl(
id: freezed == id
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String?,
name: freezed == name
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
email: freezed == email
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
password: freezed == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
passwordHash: freezed == passwordHash
? _value.passwordHash
: passwordHash // ignore: cast_nullable_to_non_nullable
as String?,
createdAt: freezed == createdAt
? _value.createdAt
@ -162,21 +162,21 @@ class __$$UserImplCopyWithImpl<$Res>
class _$UserImpl implements _User {
const _$UserImpl(
{this.id,
this.name,
this.email,
this.password,
{required this.id,
required this.name,
required this.email,
this.passwordHash,
this.createdAt,
this.updatedAt});
@override
final String? id;
final String id;
@override
final String? name;
final String name;
@override
final String? email;
final String email;
@override
final String? password;
final String? passwordHash;
@override
final DateTime? createdAt;
@override
@ -184,7 +184,7 @@ class _$UserImpl implements _User {
@override
String toString() {
return 'User(id: $id, name: $name, email: $email, password: $password, createdAt: $createdAt, updatedAt: $updatedAt)';
return 'User(id: $id, name: $name, email: $email, passwordHash: $passwordHash, createdAt: $createdAt, updatedAt: $updatedAt)';
}
@override
@ -195,8 +195,8 @@ class _$UserImpl implements _User {
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.password, password) ||
other.password == password) &&
(identical(other.passwordHash, passwordHash) ||
other.passwordHash == passwordHash) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
@ -204,8 +204,8 @@ class _$UserImpl implements _User {
}
@override
int get hashCode =>
Object.hash(runtimeType, id, name, email, password, createdAt, updatedAt);
int get hashCode => Object.hash(
runtimeType, id, name, email, passwordHash, createdAt, updatedAt);
/// Create a copy of User
/// with the given fields replaced by the non-null parameter values.
@ -218,21 +218,21 @@ class _$UserImpl implements _User {
abstract class _User implements User {
const factory _User(
{final String? id,
final String? name,
final String? email,
final String? password,
{required final String id,
required final String name,
required final String email,
final String? passwordHash,
final DateTime? createdAt,
final DateTime? updatedAt}) = _$UserImpl;
@override
String? get id;
String get id;
@override
String? get name;
String get name;
@override
String? get email;
String get email;
@override
String? get password;
String? get passwordHash;
@override
DateTime? get createdAt;
@override
@ -245,3 +245,370 @@ abstract class _User implements User {
_$$UserImplCopyWith<_$UserImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$UserCreate {
String? get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get email => throw _privateConstructorUsedError;
String get password => throw _privateConstructorUsedError;
/// Create a copy of UserCreate
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$UserCreateCopyWith<UserCreate> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $UserCreateCopyWith<$Res> {
factory $UserCreateCopyWith(
UserCreate value, $Res Function(UserCreate) then) =
_$UserCreateCopyWithImpl<$Res, UserCreate>;
@useResult
$Res call({String? id, String name, String email, String password});
}
/// @nodoc
class _$UserCreateCopyWithImpl<$Res, $Val extends UserCreate>
implements $UserCreateCopyWith<$Res> {
_$UserCreateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of UserCreate
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? name = null,
Object? email = null,
Object? password = null,
}) {
return _then(_value.copyWith(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String?,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$UserCreateImplCopyWith<$Res>
implements $UserCreateCopyWith<$Res> {
factory _$$UserCreateImplCopyWith(
_$UserCreateImpl value, $Res Function(_$UserCreateImpl) then) =
__$$UserCreateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String? id, String name, String email, String password});
}
/// @nodoc
class __$$UserCreateImplCopyWithImpl<$Res>
extends _$UserCreateCopyWithImpl<$Res, _$UserCreateImpl>
implements _$$UserCreateImplCopyWith<$Res> {
__$$UserCreateImplCopyWithImpl(
_$UserCreateImpl _value, $Res Function(_$UserCreateImpl) _then)
: super(_value, _then);
/// Create a copy of UserCreate
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? name = null,
Object? email = null,
Object? password = null,
}) {
return _then(_$UserCreateImpl(
id: freezed == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String?,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$UserCreateImpl implements _UserCreate {
const _$UserCreateImpl(
{this.id,
required this.name,
required this.email,
required this.password});
@override
final String? id;
@override
final String name;
@override
final String email;
@override
final String password;
@override
String toString() {
return 'UserCreate(id: $id, name: $name, email: $email, password: $password)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$UserCreateImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.password, password) ||
other.password == password));
}
@override
int get hashCode => Object.hash(runtimeType, id, name, email, password);
/// Create a copy of UserCreate
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$UserCreateImplCopyWith<_$UserCreateImpl> get copyWith =>
__$$UserCreateImplCopyWithImpl<_$UserCreateImpl>(this, _$identity);
}
abstract class _UserCreate implements UserCreate {
const factory _UserCreate(
{final String? id,
required final String name,
required final String email,
required final String password}) = _$UserCreateImpl;
@override
String? get id;
@override
String get name;
@override
String get email;
@override
String get password;
/// Create a copy of UserCreate
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$UserCreateImplCopyWith<_$UserCreateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$UserUpdate {
String get id => throw _privateConstructorUsedError;
String? get name => throw _privateConstructorUsedError;
String? get email => throw _privateConstructorUsedError;
String? get password => throw _privateConstructorUsedError;
/// Create a copy of UserUpdate
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$UserUpdateCopyWith<UserUpdate> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $UserUpdateCopyWith<$Res> {
factory $UserUpdateCopyWith(
UserUpdate value, $Res Function(UserUpdate) then) =
_$UserUpdateCopyWithImpl<$Res, UserUpdate>;
@useResult
$Res call({String id, String? name, String? email, String? password});
}
/// @nodoc
class _$UserUpdateCopyWithImpl<$Res, $Val extends UserUpdate>
implements $UserUpdateCopyWith<$Res> {
_$UserUpdateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of UserUpdate
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? name = freezed,
Object? email = freezed,
Object? password = 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
as String?,
email: freezed == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
password: freezed == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$UserUpdateImplCopyWith<$Res>
implements $UserUpdateCopyWith<$Res> {
factory _$$UserUpdateImplCopyWith(
_$UserUpdateImpl value, $Res Function(_$UserUpdateImpl) then) =
__$$UserUpdateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String id, String? name, String? email, String? password});
}
/// @nodoc
class __$$UserUpdateImplCopyWithImpl<$Res>
extends _$UserUpdateCopyWithImpl<$Res, _$UserUpdateImpl>
implements _$$UserUpdateImplCopyWith<$Res> {
__$$UserUpdateImplCopyWithImpl(
_$UserUpdateImpl _value, $Res Function(_$UserUpdateImpl) _then)
: super(_value, _then);
/// Create a copy of UserUpdate
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? name = freezed,
Object? email = freezed,
Object? password = freezed,
}) {
return _then(_$UserUpdateImpl(
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
as String?,
email: freezed == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
password: freezed == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
class _$UserUpdateImpl implements _UserUpdate {
const _$UserUpdateImpl(
{required this.id, this.name, this.email, this.password});
@override
final String id;
@override
final String? name;
@override
final String? email;
@override
final String? password;
@override
String toString() {
return 'UserUpdate(id: $id, name: $name, email: $email, password: $password)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$UserUpdateImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.password, password) ||
other.password == password));
}
@override
int get hashCode => Object.hash(runtimeType, id, name, email, password);
/// Create a copy of UserUpdate
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$UserUpdateImplCopyWith<_$UserUpdateImpl> get copyWith =>
__$$UserUpdateImplCopyWithImpl<_$UserUpdateImpl>(this, _$identity);
}
abstract class _UserUpdate implements UserUpdate {
const factory _UserUpdate(
{required final String id,
final String? name,
final String? email,
final String? password}) = _$UserUpdateImpl;
@override
String get id;
@override
String? get name;
@override
String? get email;
@override
String? get password;
/// Create a copy of UserUpdate
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$UserUpdateImplCopyWith<_$UserUpdateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -3,13 +3,13 @@ import 'package:backend_dart/domain/interface/error.dart';
import 'package:fpdart/fpdart.dart';
abstract class UserRepository {
TaskEither<IError, User> create(User user);
TaskEither<IError, User> create(UserCreate user);
TaskEither<IError, User> findByEmail(String email);
TaskEither<IError, User> findById(String id);
TaskEither<IError, User> update(User user);
TaskEither<IError, User> update(UserUpdate user);
TaskEither<IError, void> delete(String id);

View File

@ -1,37 +1,40 @@
import 'package:backend_dart/common/extensions.dart';
import 'package:backend_dart/common/secure_hash.dart';
import 'package:backend_dart/domain/entities/user.dart';
import 'package:backend_dart/domain/interface/error.dart';
import 'package:backend_dart/domain/interface/mapper.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 UserDboMapper implements IMapper<User, UserDbo> {
@override
class UserDboMapper {
TaskEither<IError, User> from(UserDbo target) => TaskEither.of(User(
id: target.id!,
name: target.name!,
email: target.email!,
password: target.password,
passwordHash: target.password,
createdAt: target.createdAt,
updatedAt: target.updatedAt,
));
@override
TaskEither<IError, List<User>> listFrom(Iterable<UserDbo> targets) {
return TaskEither.traverseList(targets.toList(), from);
}
@override
TaskEither<IError, List<UserDbo>> listTo(Iterable<User> origins) {
return TaskEither.traverseList(origins.toList(), to);
}
TaskEither<IError, UserDboUncheckedUpdateInput> fromUpdateTo(
UserUpdate origin) =>
TaskEither.of(UserDboUncheckedUpdateInput(
id: PrismaUnion.$1(origin.id),
name: origin.name.let(PrismaUnion.$1),
email: origin.email.let(PrismaUnion.$1),
password: origin.password.let(generateSecureHash).let(PrismaUnion.$1),
));
@override
TaskEither<IError, UserDbo> to(User origin) => TaskEither.of(UserDbo(
TaskEither<IError, UserDboCreateInput> fromCreateTo(UserCreate origin) =>
TaskEither.of(UserDboCreateInput(
id: origin.id,
name: origin.name,
email: origin.email,
password: origin.password,
createdAt: origin.createdAt,
updatedAt: origin.updatedAt,
password: generateSecureHash(origin.password),
));
}

View File

@ -1,4 +1,4 @@
import 'package:backend_dart/domain/interface/database.dart';
import 'package:backend_dart/domain/data/database.dart';
import 'package:backend_dart/infrastructure/persistence/db/client.dart';
import 'package:backend_dart/infrastructure/persistence/prisma_user_data_source.dart';

View File

@ -12,36 +12,25 @@ import 'package:uuid/uuid.dart';
import 'db/client.dart';
class PrismaUserDataSource implements UserDataSource<User> {
class PrismaUserDataSource implements UserDataSource {
final PrismaClient prisma;
final UserDboMapper mapper = UserDboMapper();
PrismaUserDataSource(this.prisma);
@override
TaskEither<IError, User> create(User user) {
return TaskEither<IError, UserDbo>.tryCatch(
TaskEither<IError, User> create(UserCreate user) => mapper
.fromCreateTo(user)
.flatMap((userDbo) => TaskEither<IError, UserDbo>.tryCatch(
() async {
if (user.password == null) {
throw Exception('Password is required');
}
if (user.email == null) {
throw Exception('Email is required');
}
final createdUser = await prisma.userDbo.create(
data: PrismaUnion.$1(UserDboCreateInput(
id: user.id,
name: user.name!,
email: user.email!,
password: user.password!,
)),
data: PrismaUnion.$1(userDbo),
);
return createdUser;
},
(error, _) => AppError.databaseError(
message: 'Failed to create user: ${error.toString()}',
),
).flatMap(mapper.from);
}
).flatMap(mapper.from));
@override
TaskEither<IError, User> findByEmail(String email) {
@ -81,41 +70,36 @@ class PrismaUserDataSource implements UserDataSource<User> {
}
@override
TaskEither<IError, User> update(User user) {
TaskEither<IError, User> update(UserUpdate user) {
return mapper
.to(user)
.flatMap((userDbo) => TaskEither.tryCatch(() async {
.fromUpdateTo(user)
.flatMap(
(userDbo) => TaskEither.tryCatch(
() async {
// Führe das Update durch
if (userDbo.id == null) {
throw Exception('User ID is required');
}
final updatedUser = await prisma.userDbo.update(
data: PrismaUnion.$2(
UserDboUncheckedUpdateInput(
id: PrismaUnion.$1(userDbo.id!),
name: userDbo.name != null
? PrismaUnion.$1(userDbo.name!)
: null,
email: userDbo.email != null
? PrismaUnion.$1(userDbo.email!)
: null,
password: userDbo.password != null
? PrismaUnion.$1(userDbo.password!)
: null,
),
),
where: UserDboWhereUniqueInput(id: userDbo.id),
data: PrismaUnion.$2(userDbo),
where: UserDboWhereUniqueInput(id: user.id),
);
return updatedUser;
},
(error, _) => AppError.databaseError(
message: 'Failed to update user: ${error.toString()}',
)))
.flatMap(errorOnNull(AppError.notFound(
),
),
)
.flatMap(
errorOnNull(
AppError.notFound(
'User not found',
)))
),
),
)
.flatMap(mapper.from);
}

View File

@ -199,7 +199,7 @@ packages:
source: hosted
version: "1.11.0"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"

View File

@ -21,6 +21,7 @@ dependencies:
json_annotation: ^4.9.0
freezed_annotation: ^2.4.4
uuid: ^4.5.1
crypto: ^3.0.6
dev_dependencies:
lints: ^3.0.0