feat: applied auth wirth session token on dart backend

This commit is contained in:
Jean Jacques Avril 2025-01-04 14:50:08 +00:00
parent 55edac6abe
commit e9221f36ef
No known key found for this signature in database
13 changed files with 146 additions and 236 deletions

View File

@ -1,11 +1,14 @@
import 'dart:convert'; import 'dart:convert';
import 'package:backend_dart/application/service/dto/auth_dto.dart'; import 'package:backend_dart/application/service/dto/auth_dto.dart';
import 'package:backend_dart/application/service/helper.dart';
import 'package:backend_dart/common/request_helper.dart'; import 'package:backend_dart/common/request_helper.dart';
import 'package:backend_dart/common/response_helpers.dart'; import 'package:backend_dart/common/response_helpers.dart';
import 'package:backend_dart/common/secure_hash.dart'; import 'package:backend_dart/common/secure_hash.dart';
import 'package:backend_dart/common/validation.dart'; import 'package:backend_dart/common/validation.dart';
import 'package:backend_dart/domain/entities/user.dart'; import 'package:backend_dart/domain/entities/user.dart';
import 'package:backend_dart/domain/errors/app_error.dart'; import 'package:backend_dart/domain/errors/app_error.dart';
import 'package:backend_dart/domain/errors/error.dart';
import 'package:backend_dart/domain/repository/auth_repository.dart'; import 'package:backend_dart/domain/repository/auth_repository.dart';
import 'package:backend_dart/domain/repository/user_repository.dart'; import 'package:backend_dart/domain/repository/user_repository.dart';
import 'package:fpdart/fpdart.dart'; import 'package:fpdart/fpdart.dart';
@ -37,20 +40,28 @@ class AuthService {
), ),
), ),
) )
.flatMap((user) => authRepository.generateToken(user.id).map((token) => .flatMap((user) => authRepository.generateToken(user.id).map(
TokenResponseDTO(token: token, userId: user.id).toJson())) (token) => Response.ok(
.map(jsonEncode) jsonEncode(
.toResponse() TokenResponseDTO(token: token, userId: user.id).toJson(),
),
headers: {
'Set-Cookie':
'session_token=$token; HttpOnly; Path=/, user_id=${user.id}; HttpOnly; Path=/',
'Content-Type': 'application/json',
},
),
))
.getOrElse(ResponseHelpers.fromError)
.run(); .run();
} }
/// Route to validate a token /// Route to validate a token
@Route.post('/validate') @Route.post('/validate')
Future<Response> validateToken(Request request) async { Future<Response> validateToken(Request request) async {
return requestToJson(request) return readCookie(request, 'session_token')
.flatMap(validateJsonKeys(['token'])) .toTaskEither<IError>(() => AppError.authenticationError(
.flatMap((json) => decodeJson(json, TokenRequestDTO.fromJson)) message: 'No token found in the request'))
.map((tokenRequest) => tokenRequest.token)
.flatMap((token) => authRepository.validateToken(token).map((userId) => .flatMap((token) => authRepository.validateToken(token).map((userId) =>
TokenResponseDTO(token: token, userId: userId).toJson())) TokenResponseDTO(token: token, userId: userId).toJson()))
.map(jsonEncode) .map(jsonEncode)
@ -61,14 +72,20 @@ class AuthService {
/// Route to revoke a token /// Route to revoke a token
@Route.post('/logout') @Route.post('/logout')
Future<Response> logout(Request request) async { Future<Response> logout(Request request) async {
return requestToJson(request) return readCookie(request, 'session_token')
.flatMap(validateJsonKeys(['token'])) .toTaskEither<IError>(() => AppError.authenticationError(
.flatMap((json) => decodeJson(json, TokenRequestDTO.fromJson)) message: 'No token found in the request'))
.map((tokenRequest) => tokenRequest.token)
.flatMap(authRepository.revokeToken) .flatMap(authRepository.revokeToken)
.map((_) => {'message': 'Token revoked successfully'}) .map((_) => Response.ok(
.map(jsonEncode) jsonEncode({'message': 'Token revoked successfully'}),
.toResponse() headers: {
// Clear cookies by setting Max-Age to 0
'Set-Cookie':
'session_token=; Max-Age=0; HttpOnly; Path=/, user_id=; Max-Age=0; HttpOnly; Path=/',
'Content-Type': 'application/json',
},
))
.getOrElse(ResponseHelpers.fromError)
.run(); .run();
} }

View File

@ -16,18 +16,6 @@ class TokenResponseDTO with _$TokenResponseDTO {
_$TokenResponseDTOFromJson(json); _$TokenResponseDTOFromJson(json);
} }
/// TokenRequestDTO represents a request for operations involving tokens.
@freezed
class TokenRequestDTO with _$TokenRequestDTO {
const factory TokenRequestDTO({
required String token,
}) = _TokenRequestDTO;
/// JSON serialization
factory TokenRequestDTO.fromJson(Map<String, dynamic> json) =>
_$TokenRequestDTOFromJson(json);
}
/// LoginRequestDTO represents the login request. /// LoginRequestDTO represents the login request.
@freezed @freezed
class LoginRequestDTO with _$LoginRequestDTO { class LoginRequestDTO with _$LoginRequestDTO {

View File

@ -182,157 +182,6 @@ abstract class _TokenResponseDTO implements TokenResponseDTO {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
TokenRequestDTO _$TokenRequestDTOFromJson(Map<String, dynamic> json) {
return _TokenRequestDTO.fromJson(json);
}
/// @nodoc
mixin _$TokenRequestDTO {
String get token => throw _privateConstructorUsedError;
/// Serializes this TokenRequestDTO to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of TokenRequestDTO
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TokenRequestDTOCopyWith<TokenRequestDTO> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TokenRequestDTOCopyWith<$Res> {
factory $TokenRequestDTOCopyWith(
TokenRequestDTO value, $Res Function(TokenRequestDTO) then) =
_$TokenRequestDTOCopyWithImpl<$Res, TokenRequestDTO>;
@useResult
$Res call({String token});
}
/// @nodoc
class _$TokenRequestDTOCopyWithImpl<$Res, $Val extends TokenRequestDTO>
implements $TokenRequestDTOCopyWith<$Res> {
_$TokenRequestDTOCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of TokenRequestDTO
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = null,
}) {
return _then(_value.copyWith(
token: null == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$TokenRequestDTOImplCopyWith<$Res>
implements $TokenRequestDTOCopyWith<$Res> {
factory _$$TokenRequestDTOImplCopyWith(_$TokenRequestDTOImpl value,
$Res Function(_$TokenRequestDTOImpl) then) =
__$$TokenRequestDTOImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String token});
}
/// @nodoc
class __$$TokenRequestDTOImplCopyWithImpl<$Res>
extends _$TokenRequestDTOCopyWithImpl<$Res, _$TokenRequestDTOImpl>
implements _$$TokenRequestDTOImplCopyWith<$Res> {
__$$TokenRequestDTOImplCopyWithImpl(
_$TokenRequestDTOImpl _value, $Res Function(_$TokenRequestDTOImpl) _then)
: super(_value, _then);
/// Create a copy of TokenRequestDTO
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = null,
}) {
return _then(_$TokenRequestDTOImpl(
token: null == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TokenRequestDTOImpl implements _TokenRequestDTO {
const _$TokenRequestDTOImpl({required this.token});
factory _$TokenRequestDTOImpl.fromJson(Map<String, dynamic> json) =>
_$$TokenRequestDTOImplFromJson(json);
@override
final String token;
@override
String toString() {
return 'TokenRequestDTO(token: $token)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TokenRequestDTOImpl &&
(identical(other.token, token) || other.token == token));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, token);
/// Create a copy of TokenRequestDTO
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$TokenRequestDTOImplCopyWith<_$TokenRequestDTOImpl> get copyWith =>
__$$TokenRequestDTOImplCopyWithImpl<_$TokenRequestDTOImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TokenRequestDTOImplToJson(
this,
);
}
}
abstract class _TokenRequestDTO implements TokenRequestDTO {
const factory _TokenRequestDTO({required final String token}) =
_$TokenRequestDTOImpl;
factory _TokenRequestDTO.fromJson(Map<String, dynamic> json) =
_$TokenRequestDTOImpl.fromJson;
@override
String get token;
/// Create a copy of TokenRequestDTO
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$TokenRequestDTOImplCopyWith<_$TokenRequestDTOImpl> get copyWith =>
throw _privateConstructorUsedError;
}
LoginRequestDTO _$LoginRequestDTOFromJson(Map<String, dynamic> json) { LoginRequestDTO _$LoginRequestDTOFromJson(Map<String, dynamic> json) {
return _LoginRequestDTO.fromJson(json); return _LoginRequestDTO.fromJson(json);
} }

View File

@ -20,18 +20,6 @@ Map<String, dynamic> _$$TokenResponseDTOImplToJson(
'userId': instance.userId, 'userId': instance.userId,
}; };
_$TokenRequestDTOImpl _$$TokenRequestDTOImplFromJson(
Map<String, dynamic> json) =>
_$TokenRequestDTOImpl(
token: json['token'] as String,
);
Map<String, dynamic> _$$TokenRequestDTOImplToJson(
_$TokenRequestDTOImpl instance) =>
<String, dynamic>{
'token': instance.token,
};
_$LoginRequestDTOImpl _$$LoginRequestDTOImplFromJson( _$LoginRequestDTOImpl _$$LoginRequestDTOImplFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$LoginRequestDTOImpl( _$LoginRequestDTOImpl(

View File

@ -0,0 +1,33 @@
import 'package:backend_dart/domain/errors/app_error.dart';
import 'package:backend_dart/domain/errors/error.dart';
import 'package:backend_dart/domain/repository/auth_repository.dart';
import 'package:fpdart/fpdart.dart';
import 'package:shelf/shelf.dart';
TaskOption<String> readCookie(Request request, String cookieName) {
// Retrieve the "Cookie" header
final cookieHeader = request.headers['cookie'];
if (cookieHeader == null) return TaskOption.none();
// No cookies found
// Split the cookies into individual key-value pairs
final cookies = cookieHeader.split(';');
// Find the cookie with the matching name
for (var cookie in cookies) {
final parts = cookie.trim().split('=');
if (parts.length == 2 && parts[0] == cookieName && parts[1].isNotEmpty) {
return TaskOption.some(parts[1]);
}
}
return TaskOption.none(); // Cookie not found
}
TaskEither<IError, String> checkAuth(
Request request, AuthRepository authRepository) {
return readCookie(request, 'session_token')
.toTaskEither<IError>(() => AppError.authenticationError(
message: 'No token found in the request'))
.flatMap(authRepository.validateToken);
}

View File

@ -1,10 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'package:backend_dart/application/service/dto/project_dto.dart'; import 'package:backend_dart/application/service/dto/project_dto.dart';
import 'package:backend_dart/application/service/helper.dart';
import 'package:backend_dart/application/service/mapper/project_dto_mapper.dart'; import 'package:backend_dart/application/service/mapper/project_dto_mapper.dart';
import 'package:backend_dart/common/request_helper.dart'; import 'package:backend_dart/common/request_helper.dart';
import 'package:backend_dart/common/response_helpers.dart'; import 'package:backend_dart/common/response_helpers.dart';
import 'package:backend_dart/common/validation.dart'; import 'package:backend_dart/common/validation.dart';
import 'package:backend_dart/domain/repository/auth_repository.dart';
import 'package:backend_dart/domain/repository/project_repository.dart'; import 'package:backend_dart/domain/repository/project_repository.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart'; import 'package:shelf_router/shelf_router.dart';
@ -13,13 +15,14 @@ part 'project_service.g.dart'; // generated with 'pub run build_runner build'
class ProjectService { class ProjectService {
final ProjectRepository projects; final ProjectRepository projects;
final AuthRepository authRepository;
final ProjectDtoMapper mapper = ProjectDtoMapper(); final ProjectDtoMapper mapper = ProjectDtoMapper();
ProjectService(this.projects); ProjectService(this.projects, this.authRepository);
@Route.get('/') @Route.get('/')
Future<Response> listProjects(Request request) async { Future<Response> listProjects(Request request) async {
return projects return checkAuth(request, authRepository)
.findAll() .flatMap((_) => projects.findAll())
.flatMap(mapper.listTo) .flatMap(mapper.listTo)
.map((projects) => projects.map((project) => project.toJson()).toList()) .map((projects) => projects.map((project) => project.toJson()).toList())
.map(jsonEncode) .map(jsonEncode)
@ -29,8 +32,8 @@ class ProjectService {
@Route.get('/<projectId>') @Route.get('/<projectId>')
Future<Response> fetchProject(Request request, String projectId) async { Future<Response> fetchProject(Request request, String projectId) async {
return projects return checkAuth(request, authRepository)
.findById(projectId) .flatMap((_) => projects.findById(projectId))
.flatMap(mapper.to) .flatMap(mapper.to)
.map((dto) => dto.toJson()) .map((dto) => dto.toJson())
.toResponse() .toResponse()
@ -39,7 +42,8 @@ class ProjectService {
@Route.post('/') @Route.post('/')
Future<Response> createProject(Request request) async { Future<Response> createProject(Request request) async {
return requestToJson(request) return checkAuth(request, authRepository)
.flatMap((_) => requestToJson(request))
.flatMap(validateJsonKeys(['name', 'userId'])) // Add required fields .flatMap(validateJsonKeys(['name', 'userId'])) // Add required fields
.flatMap((json) => decodeJson(json, ProjectCreateDto.fromJson)) .flatMap((json) => decodeJson(json, ProjectCreateDto.fromJson))
.flatMap(mapper.fromCreateTo) .flatMap(mapper.fromCreateTo)
@ -52,7 +56,8 @@ class ProjectService {
@Route.put('/<projectId>') @Route.put('/<projectId>')
Future<Response> updateProject(Request request, String projectId) async { Future<Response> updateProject(Request request, String projectId) async {
return requestToJson(request) return checkAuth(request, authRepository)
.flatMap((_) => requestToJson(request))
.flatMap(validateJsonKeys([])) .flatMap(validateJsonKeys([]))
.flatMap((json) => decodeJson(json, ProjectUpdateDto.fromJson)) .flatMap((json) => decodeJson(json, ProjectUpdateDto.fromJson))
.flatMap((dto) => mapper.fromUpdateTo(dto, projectId)) .flatMap((dto) => mapper.fromUpdateTo(dto, projectId))
@ -65,8 +70,8 @@ class ProjectService {
@Route.delete('/<projectId>') @Route.delete('/<projectId>')
Future<Response> deleteProject(Request request, String projectId) async { Future<Response> deleteProject(Request request, String projectId) async {
return projects return checkAuth(request, authRepository)
.delete(projectId) .flatMap((_) => projects.delete(projectId))
.flatMap(mapper.to) .flatMap(mapper.to)
.map((dto) => dto.toJson()) .map((dto) => dto.toJson())
.toResponse() .toResponse()

View File

@ -1,10 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'package:backend_dart/application/service/dto/project_task_dto.dart'; import 'package:backend_dart/application/service/dto/project_task_dto.dart';
import 'package:backend_dart/application/service/helper.dart';
import 'package:backend_dart/application/service/mapper/project_task_dto_mapper.dart'; import 'package:backend_dart/application/service/mapper/project_task_dto_mapper.dart';
import 'package:backend_dart/common/request_helper.dart'; import 'package:backend_dart/common/request_helper.dart';
import 'package:backend_dart/common/response_helpers.dart'; import 'package:backend_dart/common/response_helpers.dart';
import 'package:backend_dart/common/validation.dart'; import 'package:backend_dart/common/validation.dart';
import 'package:backend_dart/domain/repository/auth_repository.dart';
import 'package:backend_dart/domain/repository/project_task_repository.dart'; import 'package:backend_dart/domain/repository/project_task_repository.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart'; import 'package:shelf_router/shelf_router.dart';
@ -13,13 +15,15 @@ part 'project_task_service.g.dart'; // Generated with 'pub run build_runner buil
class ProjectTaskService { class ProjectTaskService {
final ProjectTaskRepository tasks; final ProjectTaskRepository tasks;
final AuthRepository authRepository;
final ProjectTaskDtoMapper mapper = ProjectTaskDtoMapper(); final ProjectTaskDtoMapper mapper = ProjectTaskDtoMapper();
ProjectTaskService(this.tasks); ProjectTaskService(this.tasks, this.authRepository);
@Route.get('/') @Route.get('/')
Future<Response> listTasks(Request request) async { Future<Response> listTasks(Request request) async {
return tasks return checkAuth(request, authRepository)
.findAll() .flatMap((_) => tasks.findAll())
.flatMap(mapper.listTo) .flatMap(mapper.listTo)
.map((tasks) => tasks.map((task) => task.toJson()).toList()) .map((tasks) => tasks.map((task) => task.toJson()).toList())
.map(jsonEncode) .map(jsonEncode)
@ -29,8 +33,8 @@ class ProjectTaskService {
@Route.get('/<taskId>') @Route.get('/<taskId>')
Future<Response> fetchTask(Request request, String taskId) async { Future<Response> fetchTask(Request request, String taskId) async {
return tasks return checkAuth(request, authRepository)
.findById(taskId) .flatMap((_) => tasks.findById(taskId))
.flatMap(mapper.to) .flatMap(mapper.to)
.map((dto) => dto.toJson()) .map((dto) => dto.toJson())
.toResponse() .toResponse()
@ -39,7 +43,8 @@ class ProjectTaskService {
@Route.post('/') @Route.post('/')
Future<Response> createTask(Request request) async { Future<Response> createTask(Request request) async {
return requestToJson(request) return checkAuth(request, authRepository)
.flatMap((_) => requestToJson(request))
.flatMap(validateJsonKeys(['name', 'projectId'])) // Add required fields .flatMap(validateJsonKeys(['name', 'projectId'])) // Add required fields
.flatMap((json) => decodeJson(json, ProjectTaskCreateDto.fromJson)) .flatMap((json) => decodeJson(json, ProjectTaskCreateDto.fromJson))
.flatMap(mapper.fromCreateTo) .flatMap(mapper.fromCreateTo)
@ -52,7 +57,8 @@ class ProjectTaskService {
@Route.put('/<taskId>') @Route.put('/<taskId>')
Future<Response> updateTask(Request request, String taskId) async { Future<Response> updateTask(Request request, String taskId) async {
return requestToJson(request) return checkAuth(request, authRepository)
.flatMap((_) => requestToJson(request))
.flatMap(validateJsonKeys([])) .flatMap(validateJsonKeys([]))
.flatMap((json) => decodeJson(json, ProjectTaskUpdateDto.fromJson)) .flatMap((json) => decodeJson(json, ProjectTaskUpdateDto.fromJson))
.flatMap((dto) => mapper.fromUpdateTo(dto, taskId)) .flatMap((dto) => mapper.fromUpdateTo(dto, taskId))
@ -65,8 +71,8 @@ class ProjectTaskService {
@Route.delete('/<taskId>') @Route.delete('/<taskId>')
Future<Response> deleteTask(Request request, String taskId) async { Future<Response> deleteTask(Request request, String taskId) async {
return tasks return checkAuth(request, authRepository)
.delete(taskId) .flatMap((_) => tasks.delete(taskId))
.flatMap(mapper.to) .flatMap(mapper.to)
.map((dto) => dto.toJson()) .map((dto) => dto.toJson())
.toResponse() .toResponse()

View File

@ -8,22 +8,26 @@ import 'package:riverpod/riverpod.dart';
final userServiceProvider = Provider<UserService>((ref) { final userServiceProvider = Provider<UserService>((ref) {
final database = ref.read(userRepoProvider); final database = ref.read(userRepoProvider);
return UserService(database); final authRepository = ref.read(authProvider);
return UserService(database, authRepository);
}); });
final projectServiceProvider = Provider<ProjectService>((ref) { final projectServiceProvider = Provider<ProjectService>((ref) {
final database = ref.read(projectProvider); final database = ref.read(projectProvider);
return ProjectService(database); final authRepository = ref.read(authProvider);
return ProjectService(database, authRepository);
}); });
final projectTaskServiceProvider = Provider<ProjectTaskService>((ref) { final projectTaskServiceProvider = Provider<ProjectTaskService>((ref) {
final database = ref.read(projectTaskProvider); final database = ref.read(projectTaskProvider);
return ProjectTaskService(database); final authRepository = ref.read(authProvider);
return ProjectTaskService(database, authRepository);
}); });
final timeEntryServiceProvider = Provider<TimeEntryService>((ref) { final timeEntryServiceProvider = Provider<TimeEntryService>((ref) {
final database = ref.read(timeEntryProvider); final database = ref.read(timeEntryProvider);
return TimeEntryService(database); final authRepository = ref.read(authProvider);
return TimeEntryService(database, authRepository);
}); });
final authServiceProvider = Provider<AuthService>((ref) { final authServiceProvider = Provider<AuthService>((ref) {

View File

@ -1,10 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'package:backend_dart/application/service/dto/time_entry_dto.dart'; import 'package:backend_dart/application/service/dto/time_entry_dto.dart';
import 'package:backend_dart/application/service/helper.dart';
import 'package:backend_dart/application/service/mapper/time_entry_dto_mapper.dart'; import 'package:backend_dart/application/service/mapper/time_entry_dto_mapper.dart';
import 'package:backend_dart/common/request_helper.dart'; import 'package:backend_dart/common/request_helper.dart';
import 'package:backend_dart/common/response_helpers.dart'; import 'package:backend_dart/common/response_helpers.dart';
import 'package:backend_dart/common/validation.dart'; import 'package:backend_dart/common/validation.dart';
import 'package:backend_dart/domain/repository/auth_repository.dart';
import 'package:backend_dart/domain/repository/time_entry_repository.dart'; import 'package:backend_dart/domain/repository/time_entry_repository.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart'; import 'package:shelf_router/shelf_router.dart';
@ -13,13 +15,14 @@ part 'time_entry_service.g.dart'; // Generated with 'pub run build_runner build'
class TimeEntryService { class TimeEntryService {
final TimeEntryRepository entries; final TimeEntryRepository entries;
final AuthRepository authRepository;
final TimeEntryDtoMapper mapper = TimeEntryDtoMapper(); final TimeEntryDtoMapper mapper = TimeEntryDtoMapper();
TimeEntryService(this.entries); TimeEntryService(this.entries, this.authRepository);
@Route.get('/') @Route.get('/')
Future<Response> listEntries(Request request) async { Future<Response> listEntries(Request request) async {
return entries return checkAuth(request, authRepository)
.findAll() .flatMap((_) => entries.findAll())
.flatMap(mapper.listTo) .flatMap(mapper.listTo)
.map((entries) => entries.map((entry) => entry.toJson()).toList()) .map((entries) => entries.map((entry) => entry.toJson()).toList())
.map(jsonEncode) .map(jsonEncode)
@ -29,8 +32,8 @@ class TimeEntryService {
@Route.get('/<entryId>') @Route.get('/<entryId>')
Future<Response> fetchEntry(Request request, String entryId) async { Future<Response> fetchEntry(Request request, String entryId) async {
return entries return checkAuth(request, authRepository)
.findById(entryId) .flatMap((_) => entries.findById(entryId))
.flatMap(mapper.to) .flatMap(mapper.to)
.map((dto) => dto.toJson()) .map((dto) => dto.toJson())
.toResponse() .toResponse()
@ -39,7 +42,8 @@ class TimeEntryService {
@Route.post('/') @Route.post('/')
Future<Response> createEntry(Request request) async { Future<Response> createEntry(Request request) async {
return requestToJson(request) return checkAuth(request, authRepository)
.flatMap((_) => requestToJson(request))
.flatMap( .flatMap(
validateJsonKeys(['startTime', 'endTime', 'userId', 'projectId'])) validateJsonKeys(['startTime', 'endTime', 'userId', 'projectId']))
.flatMap((json) => decodeJson(json, TimeEntryCreateDto.fromJson)) .flatMap((json) => decodeJson(json, TimeEntryCreateDto.fromJson))
@ -53,7 +57,8 @@ class TimeEntryService {
@Route.put('/<entryId>') @Route.put('/<entryId>')
Future<Response> updateEntry(Request request, String entryId) async { Future<Response> updateEntry(Request request, String entryId) async {
return requestToJson(request) return checkAuth(request, authRepository)
.flatMap((_) => requestToJson(request))
.flatMap(validateJsonKeys([])) .flatMap(validateJsonKeys([]))
.flatMap((json) => decodeJson(json, TimeEntryUpdateDto.fromJson)) .flatMap((json) => decodeJson(json, TimeEntryUpdateDto.fromJson))
.flatMap((dto) => mapper.fromUpdateTo(dto, entryId)) .flatMap((dto) => mapper.fromUpdateTo(dto, entryId))
@ -66,8 +71,8 @@ class TimeEntryService {
@Route.delete('/<entryId>') @Route.delete('/<entryId>')
Future<Response> deleteEntry(Request request, String entryId) async { Future<Response> deleteEntry(Request request, String entryId) async {
return entries return checkAuth(request, authRepository)
.delete(entryId) .flatMap((_) => entries.delete(entryId))
.map((dto) => dto.toJson()) .map((dto) => dto.toJson())
.toResponse() .toResponse()
.run(); .run();

View File

@ -1,10 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'package:backend_dart/application/service/dto/user_dto.dart'; import 'package:backend_dart/application/service/dto/user_dto.dart';
import 'package:backend_dart/application/service/helper.dart';
import 'package:backend_dart/application/service/mapper/user_dto_mapper.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/request_helper.dart';
import 'package:backend_dart/common/response_helpers.dart'; import 'package:backend_dart/common/response_helpers.dart';
import 'package:backend_dart/common/validation.dart'; import 'package:backend_dart/common/validation.dart';
import 'package:backend_dart/domain/repository/auth_repository.dart';
import 'package:backend_dart/domain/repository/user_repository.dart'; import 'package:backend_dart/domain/repository/user_repository.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart'; import 'package:shelf_router/shelf_router.dart';
@ -13,13 +15,15 @@ part 'user_service.g.dart'; // generated with 'pub run build_runner build'
class UserService { class UserService {
final UserRepository users; final UserRepository users;
final AuthRepository authRepository;
final UserDtoMapper mapper = UserDtoMapper(); final UserDtoMapper mapper = UserDtoMapper();
UserService(this.users); UserService(this.users, this.authRepository);
@Route.get('/') @Route.get('/')
Future<Response> listUsers(Request request) async { Future<Response> listUsers(Request request) async {
return users return checkAuth(request, authRepository)
.findAll() .flatMap((_) => users.findAll())
.flatMap(mapper.listTo) .flatMap(mapper.listTo)
.map((users) => users.map((user) => user.toJson()).toList()) .map((users) => users.map((user) => user.toJson()).toList())
.map(jsonEncode) .map(jsonEncode)
@ -29,8 +33,8 @@ class UserService {
@Route.get('/<userId>') @Route.get('/<userId>')
Future<Response> fetchUser(Request request, String userId) async { Future<Response> fetchUser(Request request, String userId) async {
return users return checkAuth(request, authRepository)
.findById(userId) .flatMap((_) => users.findById(userId))
.flatMap(mapper.to) .flatMap(mapper.to)
.map((dto) => dto.toJson()) .map((dto) => dto.toJson())
.toResponse() .toResponse()
@ -48,11 +52,22 @@ class UserService {
.map((dto) => dto.toJson()) .map((dto) => dto.toJson())
.toResponse() .toResponse()
.run(); .run();
//return checkAuth(request, authRepository) // ignore auth for initial user creation
// .flatMap((_) => requestToJson(request))
// .flatMap(validateJsonKeys(['name', 'email', 'password']))
// .flatMap((json) => decodeJson(json, UserCreateDto.fromJson))
// .flatMap(mapper.fromCreateTo)
// .flatMap(users.create)
// .flatMap(mapper.to)
// .map((dto) => dto.toJson())
// .toResponse()
// .run();
} }
@Route.put('/<userId>') @Route.put('/<userId>')
Future<Response> updateUser(Request request, String userId) async { Future<Response> updateUser(Request request, String userId) async {
return requestToJson(request) return checkAuth(request, authRepository)
.flatMap((_) => requestToJson(request))
.flatMap(validateJsonKeys([])) .flatMap(validateJsonKeys([]))
.flatMap((json) => decodeJson(json, UserUpdateDto.fromJson)) .flatMap((json) => decodeJson(json, UserUpdateDto.fromJson))
.flatMap((dto) => mapper.fromUpdateTo(dto, userId)) .flatMap((dto) => mapper.fromUpdateTo(dto, userId))
@ -65,8 +80,8 @@ class UserService {
@Route.delete('/<userId>') @Route.delete('/<userId>')
Future<Response> deleteUser(Request request, String userId) async { Future<Response> deleteUser(Request request, String userId) async {
return users return checkAuth(request, authRepository)
.delete(userId) .flatMap((_) => users.delete(userId))
.flatMap(mapper.to) .flatMap(mapper.to)
.map((dto) => dto.toJson()) .map((dto) => dto.toJson())
.toResponse() .toResponse()

View File

@ -14,8 +14,8 @@ extension TaskEitherResponseExtensions
extension TaskEitherResponseExtensionsFromString on TaskEither<IError, String> { extension TaskEitherResponseExtensionsFromString on TaskEither<IError, String> {
Task<Response> toResponse() => match( Task<Response> toResponse() => match(
(left) => ResponseHelpers.fromError(left), ResponseHelpers.fromError,
(right) => Response.ok(right), Response.ok,
); );
} }

View File

@ -106,15 +106,15 @@ func (s *AuthService) Logout(c *gin.Context) {
func setSessionTokenCookie(c *gin.Context, user entities.User) func(token string) string { func setSessionTokenCookie(c *gin.Context, user entities.User) func(token string) string {
return func(token string) string { return func(token string) string {
c.SetCookie("session_token", token, 3600, "/", "localhost", false, true) c.SetCookie("session_token", token, 3600, "/", "", false, true)
c.SetCookie("user_id", user.ID, 3600, "/", "localhost", false, true) c.SetCookie("user_id", user.ID, 3600, "/", "", false, true)
return token return token
} }
} }
func deleteSessionTokenCookie(c *gin.Context) { func deleteSessionTokenCookie(c *gin.Context) {
c.SetCookie("session_token", "", -1, "/", "localhost", false, true) c.SetCookie("session_token", "", -1, "/", "", false, true)
c.SetCookie("user_id", "", -1, "/", "localhost", false, true) c.SetCookie("user_id", "", -1, "/", "", false, true)
} }
func validatePassword(password string) func(user entities.User) E.Either[error, entities.User] { func validatePassword(password string) func(user entities.User) E.Either[error, entities.User] {

View File

@ -3,7 +3,7 @@ package dto
// TokenResponseDTO represents the response for a token generation or validation. // TokenResponseDTO represents the response for a token generation or validation.
type TokenResponseDTO struct { type TokenResponseDTO struct {
Token string `json:"token"` Token string `json:"token"`
UserID string `json:"user_id"` UserID string `json:"userId"`
} }
// LoginRequestDTO represents the login request. // LoginRequestDTO represents the login request.