added frontend

This commit is contained in:
verboomp
2026-01-21 16:08:09 +01:00
parent d2e6f5164a
commit b3de3eec8c
74 changed files with 4938 additions and 26 deletions

View File

@@ -0,0 +1,174 @@
import 'dart:convert' show jsonDecode, jsonEncode;
import 'package:fotodocumentation/dto/base_dto.dart';
import 'package:fotodocumentation/utils/di_container.dart';
import 'package:fotodocumentation/utils/http_client_utils.dart';
import 'package:fotodocumentation/utils/jwt_token_storage.dart';
import 'package:fotodocumentation/utils/url_utils.dart';
import 'package:http/http.dart' as http;
import 'package:http/http.dart' show Response;
import 'package:fotodocumentation/main.dart' show logger;
abstract class BaseController {
UrlUtils get uriUtils => DiContainer.get();
JwtTokenStorage get _jwtTokenStorage => DiContainer.get();
HttpClientUtils get httpClientUtils => DiContainer.get();
Future<Header> getAuthHeader() async {
final accessToken = await _jwtTokenStorage.getAccessToken();
if (accessToken != null && accessToken.isNotEmpty) {
// Use JWT Bearer token
return Header('Authorization', 'Bearer $accessToken');
} else {
return const Header("Accept-Language", "en-US");
}
}
Exception getServerError(Response response) {
return Exception("Error receiving data from server");
}
Future<List<T>> runGetListWithAuth<T>(String uriStr, List<T> Function(dynamic) convert) async {
http.Client client = httpClientUtils.client;
try {
Header cred = await getAuthHeader();
Uri uri = Uri.parse(uriStr);
var response = await client.get(uri, headers: {cred.name: cred.value});
if (response.statusCode == 200) {
String text = response.body;
var jsonArray = jsonDecode(text);
return convert(jsonArray);
} else {
throw ServerError(response.statusCode); // Exception("Failed to get server data ${response.statusCode}");
}
} catch (e) {
logger.e("exception $e");
rethrow;
}
}
Future<T?> runGetWithAuth<T>(String uriStr, T Function(dynamic) convert) async {
http.Client client = httpClientUtils.client;
try {
Header cred = await getAuthHeader();
Uri uri = Uri.parse(uriStr);
var response = await client.get(uri, headers: {cred.name: cred.value});
if (response.statusCode == 200) {
String text = response.body;
var jsonArray = jsonDecode(text);
return convert(jsonArray);
} else {
throw ServerError(response.statusCode); // Exception("Failed to get server data ${response.statusCode}");
}
} catch (e) {
logger.e("exception $e");
rethrow;
}
}
Future<bool> runDeleteWithAuth(String uriStr) async {
http.Client client = httpClientUtils.client;
Header cred = await getAuthHeader();
Uri uri = Uri.parse(uriStr);
var response = await client.delete(uri, headers: {cred.name: cred.value});
return response.statusCode == 200;
}
Future<bool> runPutWithAuth(String uriStr) async {
http.Client client = httpClientUtils.client;
Header cred = await getAuthHeader();
Uri uri = Uri.parse(uriStr);
var response = await client.put(uri, headers: {cred.name: cred.value});
return response.statusCode == 200;
}
Future<ServerReply<T>> runSaveNew<T extends DtoMapAble>(String uriStr, T dtoObj, Function(http.Response response, T dto) processReply) async {
http.Client client = httpClientUtils.client;
try {
Header cred = await getAuthHeader();
String body = jsonEncode(dtoObj.toMap());
Uri uri = Uri.parse(uriStr);
var response = await client.post(uri, headers: {cred.name: cred.value, "Accept": "application/json", "Content-Type": "application/json"}, body: body);
return processReply(response, dtoObj);
} catch (e) {
logger.e("exception $e");
}
return ServerReply(ServerState.error, dtoObj);
}
Future<ServerReply<T>> runSaveUpdate<T extends DtoMapAble>(String uriStr, T dtoObj, Function(http.Response response, T dto) processReply) async {
http.Client client = httpClientUtils.client;
try {
Header cred = await getAuthHeader();
String body = jsonEncode(dtoObj.toMap());
Uri uri = Uri.parse(uriStr);
var response = await client.put(uri, headers: {cred.name: cred.value, "Accept": "application/json", "Content-Type": "application/json"}, body: body);
return processReply(response, dtoObj);
} catch (e) {
logger.e("exception $e");
}
return ServerReply(ServerState.error, dtoObj);
}
ServerReply<T> processServerResponse<T>(Response response, T dto, T Function(Map<String, dynamic> json) fromJson) {
if (response.statusCode == 200) {
String text = response.body;
var json = jsonDecode(text);
var dto = fromJson(json);
return ServerReply(ServerState.ok, dto);
} else if (response.statusCode == 400 || response.statusCode == 409) {
String text = response.body;
try {
var json = jsonDecode(text);
var error = ErrorDto.fromJson(json);
return ServerReply(ServerState.duplicate, dto, error: error);
} catch (e) {
return ServerReply(ServerState.error, dto, error: ErrorDto(response.statusCode, text));
}
} else if (response.statusCode == 403) {
var error = ErrorDto(403, "Not allowed.");
return ServerReply(ServerState.error, dto, error: error);
}
return ServerReply(ServerState.error, dto, error: ErrorDto(response.statusCode, "Internal server error"));
}
}
class Header {
final String name;
final String value;
const Header(this.name, this.value);
@override
String toString() => '$runtimeType: $name, $value';
}
class ServerReply<T> {
ServerState state;
T entity;
ErrorDto? error;
ServerReply(this.state, this.entity, {this.error});
@override
String toString() => '$runtimeType: $state, $entity, $error';
}
class ServerError {
int statusCode;
ServerError(this.statusCode);
}
enum ServerState {
ok,
duplicate,
error,
;
}

View File

@@ -0,0 +1,102 @@
import 'dart:convert' show base64, utf8;
import 'dart:convert' show jsonDecode, jsonEncode;
import 'package:fotodocumentation/controller/base_controller.dart';
import 'package:fotodocumentation/dto/jwt_token_pair_dto.dart';
import 'package:fotodocumentation/main.dart' show logger;
import 'package:fotodocumentation/utils/di_container.dart';
import 'package:fotodocumentation/utils/jwt_token_storage.dart';
import 'package:http/http.dart' as http;
typedef AuthenticateReply = ({JwtTokenPairDto? jwtTokenPairDto});
abstract interface class LoginController {
Future<AuthenticateReply> authenticate(String username, String password);
Future<bool> refreshAccessToken();
Future<bool> isUsingJwtAuth();
}
class LoginControllerImpl extends BaseController implements LoginController {
final String path = "login";
JwtTokenStorage get _jwtTokenStorage => DiContainer.get();
@override
Future<AuthenticateReply> authenticate(String username, String password) async {
http.Client client = httpClientUtils.client;
try {
Header cred = _getLoginHeader(username, password);
String uriStr = '${uriUtils.getBaseUrl()}$path';
Uri uri = Uri.parse(uriStr);
var response = await client.get(uri, headers: {cred.name: cred.value});
if (response.statusCode == 200) {
final Map<String, dynamic> data = Map.castFrom(jsonDecode(response.body));
final tokenPair = JwtTokenPairDto.fromJson(data);
// Store tokens securely
await _jwtTokenStorage.saveTokens(tokenPair.accessToken, tokenPair.refreshToken);
// Load user data using the new token
return (jwtTokenPairDto: tokenPair);
} else {
logger.e('Authentication failed: ${response.statusCode} ${response.body}');
return (jwtTokenPairDto: null);
}
} catch (e) {
logger.e("Authentication error: $e");
return (jwtTokenPairDto: null);
}
}
@override
Future<bool> refreshAccessToken() async {
try {
final refreshToken = await _jwtTokenStorage.getRefreshToken();
if (refreshToken == null) {
logger.i('No refresh token available');
return false;
}
String uriStr = '${uriUtils.getBaseUrl()}$path/login/refresh';
Uri uri = Uri.parse(uriStr);
final response = await http.post(
uri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'refreshToken': refreshToken,
}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
final newAccessToken = data['accessToken'] as String;
// Update only the access token (keep same refresh token)
await _jwtTokenStorage.updateAccessToken(newAccessToken);
logger.d('Access token refreshed successfully');
return true;
} else {
logger.d('Token refresh failed: ${response.statusCode} ${response.body}');
return false;
}
} catch (e) {
logger.e('Token refresh error: $e');
return false;
}
}
@override
Future<bool> isUsingJwtAuth() async {
return await _jwtTokenStorage.hasTokens();
}
Header _getLoginHeader(String username, String password) {
String combined = "$username:$password";
final bytes = utf8.encode(combined);
String asBase64 = base64.encode(bytes);
return Header("Authorization", "Basic $asBase64");
}
}