diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml
new file mode 100644
index 0000000..7008872
--- /dev/null
+++ b/.forgejo/workflows/deploy.yml
@@ -0,0 +1,18 @@
+on:
+ release:
+ types: [published]
+jobs:
+ test:
+ runs-on: docker
+ steps:
+ - run: apt update && apt install -y sshpass jq
+ - uses: actions/checkout@v4
+ - name: Set up Flutter
+ uses: https://github.com/subosito/flutter-action@v2
+ with:
+ channel: stable
+ flutter-version: 3.35.1
+ cache: true
+ - run: git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.35.1-x64
+ - run: flutter build web --release --wasm --base-href /app/
+ - run: sshpass -p "${{ secrets.DEPLOY_PASSWORD }}" scp -o StrictHostKeyChecking=accept-new -rp build/web/* ${{ secrets.DEPLOY_USERNAME }}@${{ secrets.DEPLOY_ADDRESS }}:${{ secrets.DEPLOY_PATH }}
diff --git a/README.md b/README.md
index 304117b..a5a46b7 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
+> [!WARNING]
+> This repo has been moved to [illes](https://git.illes.fr/UEAuvergne/Seshat).
+
# seshat
Client android/iOS/web, écrit en dart x flutter, pour Alexandria.
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index d2ea4a2..80cd4ad 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -24,7 +24,7 @@ android {
applicationId = "fr.ueauvergne.seshat"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
- minSdk = flutter.minSdkVersion
+ minSdkVersion(24)
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html
index f7fc0b1..d098403 100644
--- a/android/build/reports/problems/problems-report.html
+++ b/android/build/reports/problems/problems-report.html
@@ -650,7 +650,7 @@ code + .copy-button {
diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart
index 2b56ddb..c55d564 100644
--- a/lib/data/repositories/auth_repository.dart
+++ b/lib/data/repositories/auth_repository.dart
@@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:seshat/data/services/auth_client.dart';
import 'package:seshat/utils/result.dart';
+/// Repository to manage authentification
class AuthRepository extends ChangeNotifier {
AuthRepository({required AuthClient authClient}) : _authClient = authClient;
@@ -9,6 +10,7 @@ class AuthRepository extends ChangeNotifier {
bool? _isAuthenticated;
+ /// Checks the validity of the token if not already checked
Future get isLoggedIn async {
if (_isAuthenticated != null) {
return _isAuthenticated!;
@@ -25,6 +27,7 @@ class AuthRepository extends ChangeNotifier {
}
}
+ /// Logs in the user
Future> login(String username, String password) async {
try {
final result = await _authClient.login(username, password);
@@ -33,13 +36,14 @@ class AuthRepository extends ChangeNotifier {
_isAuthenticated = true;
return Result.ok(());
case Error():
- return Result.error(result.error);
+ return result;
}
} catch (e) {
return Result.error(Exception(e));
}
}
+ /// Gets the API's remote version
Future> getRemoteApiVersion() async {
return await _authClient.getRemoteApiVersion();
}
diff --git a/lib/data/repositories/bal_repository.dart b/lib/data/repositories/bal_repository.dart
index ccce2e4..97bc023 100644
--- a/lib/data/repositories/bal_repository.dart
+++ b/lib/data/repositories/bal_repository.dart
@@ -5,13 +5,19 @@ import 'package:seshat/domain/models/bal_stats.dart';
import 'package:seshat/domain/models/enums.dart';
import 'package:seshat/utils/result.dart';
+/// Repository to manage [Bal]
class BalRepository {
BalRepository({required ApiClient apiClient}) : _apiClient = apiClient;
final ApiClient _apiClient;
- List? _bals;
- Accounting? accounting;
+ /// [List] of all the user's [Bal]
+ List? _bals;
+
+ /// [Accounting] of [Bal], mapped by [Bal] id
+ final Map _accountingMap = {};
+
+ /// Gets a list of all [Bal] from cache or remote
Future>> getBals() async {
if (_bals != null) {
return Result.ok(_bals!);
@@ -26,6 +32,7 @@ class BalRepository {
}
}
+ /// Gets a list of all [Bal] from remote only
Future>> _getBalsNoCache() async {
final result = await _apiClient.getBals();
switch (result) {
@@ -37,15 +44,16 @@ class BalRepository {
}
}
- Future> balById(int id) async {
+ /// Gets a [Bal] by [balId], either from cache or remote
+ Future> balById(int balId) async {
if (_bals == null) {
await getBals();
}
- Bal? bal = _bals!.where((bal) => bal.id == id).firstOrNull;
+ Bal? bal = _bals!.where((bal) => bal.id == balId).firstOrNull;
if (bal != null) {
return Result.ok(bal);
}
- final result = await _apiClient.getBalById(id);
+ final result = await _apiClient.getBalById(balId);
switch (result) {
case Ok():
return Result.ok(result.value);
@@ -54,11 +62,13 @@ class BalRepository {
}
}
+ /// Return wether or not a [Bal] is currently [BalState.ongoing]
bool isABalOngoing() {
return _bals?.where((bal) => bal.state == BalState.ongoing).isNotEmpty ??
false;
}
+ /// Gets the [Bal] that is [BalState.ongoing]
Future ongoingBal() async {
if (_bals == null) {
await _getBalsNoCache();
@@ -66,12 +76,14 @@ class BalRepository {
return _bals!.where((bal) => bal.state == BalState.ongoing).firstOrNull;
}
+ /// Stops a [Bal] and refresh cache
Future> stopBal(int id) async {
final result = await _apiClient.stopBal(id);
_getBalsNoCache();
return result;
}
+ /// Starts a [Bal] and refresh cache
Future> startBal(int id) async {
if (isABalOngoing()) {
return Result.error(
@@ -83,52 +95,62 @@ class BalRepository {
return result;
}
+ /// Changes a [Bal]'s [name], [startTime] or [endTime]
Future> editBal(
int id,
String name,
- DateTime start,
- DateTime end,
+ DateTime startTime,
+ DateTime endTime,
) async {
- final result = await _apiClient.editBal(id, name, start, end);
+ final result = await _apiClient.editBal(id, name, startTime, endTime);
await _getBalsNoCache();
return result;
}
- Future> addBal(String name, DateTime start, DateTime end) async {
- final result = await _apiClient.addBal(name, start, end);
+ /// Creates a [Bal] from its [name], [startTime] and [endTime]
+ Future> addBal(
+ String name,
+ DateTime startTime,
+ DateTime endTime,
+ ) async {
+ final result = await _apiClient.addBal(name, startTime, endTime);
await _getBalsNoCache();
return result;
}
- Future> getBalStats(int id) async {
- return _apiClient.getBalStats(id);
+ /// Gets a [BalStats] from its [balId]
+ Future> getBalStats(int balId) async {
+ return _apiClient.getBalStats(balId);
}
+ /// Get [Accounting] of a [Bal] from remote only
Future> getAccountingNoCache(int balId) async {
final result = await _apiClient.getAccounting(balId);
switch (result) {
case Ok():
- accounting = result.value;
+ _accountingMap[balId] = result.value;
break;
default:
}
return result;
}
+ /// Get [Accounting] of a [Bal] from cache or remote
Future> getAccounting(int balId) async {
- if (accounting != null) {
- return Result.ok(accounting!);
+ if (_accountingMap[balId] != null) {
+ return Result.ok(_accountingMap[balId]!);
}
final result = await _apiClient.getAccounting(balId);
switch (result) {
case Ok():
- accounting = result.value;
+ _accountingMap[balId] = result.value;
break;
default:
}
return result;
}
+ /// Manages what returning (of type [ReturnType]) does to cache and notifies remote
Future> returnToId(
int balId,
int ownerId,
@@ -139,26 +161,32 @@ class BalRepository {
case Ok():
switch (type) {
case ReturnType.books:
- final owner = accounting?.owners
+ final owner = _accountingMap[balId]?.owners
.where((el) => el.ownerId == ownerId)
.firstOrNull;
if (owner?.owedMoney == 0) {
- accounting?.owners.removeWhere((el) => el.ownerId == ownerId);
+ _accountingMap[balId]?.owners.removeWhere(
+ (el) => el.ownerId == ownerId,
+ );
}
owner?.owed = [];
owner?.owedInstances = [];
break;
case ReturnType.money:
- final owner = accounting?.owners
+ final owner = _accountingMap[balId]?.owners
.where((el) => el.ownerId == ownerId)
.firstOrNull;
if (owner?.owed == null || owner!.owed.isEmpty) {
- accounting?.owners.removeWhere((el) => el.ownerId == ownerId);
+ _accountingMap[balId]?.owners.removeWhere(
+ (el) => el.ownerId == ownerId,
+ );
}
owner?.owedMoney = 0;
break;
case ReturnType.all:
- accounting?.owners.removeWhere((el) => el.ownerId == ownerId);
+ _accountingMap[balId]?.owners.removeWhere(
+ (el) => el.ownerId == ownerId,
+ );
break;
}
break;
diff --git a/lib/data/repositories/book_instance_repository.dart b/lib/data/repositories/book_instance_repository.dart
index 4a07167..7a60d19 100644
--- a/lib/data/repositories/book_instance_repository.dart
+++ b/lib/data/repositories/book_instance_repository.dart
@@ -6,16 +6,19 @@ import 'package:seshat/domain/models/owner.dart';
import 'package:seshat/domain/models/search_result.dart';
import 'package:seshat/utils/result.dart';
+/// Repository to manage [BookInstance]
class BookInstanceRepository {
BookInstanceRepository({required ApiClient apiClient})
: _apiClient = apiClient;
final ApiClient _apiClient;
+ /// Gets a [List] from an [ean]
Future>> getByEan(int balId, int ean) async {
return await _apiClient.getBookInstanceByEAN(balId, ean);
}
+ /// Gets a [List] from a [title] and [author]
Future>> getBySearch(
int balId,
String title,
@@ -24,15 +27,17 @@ class BookInstanceRepository {
return await _apiClient.getBookInstanceBySearch(balId, title, author);
}
- Future> sendBook(
+ /// Sends a new [BookInstance]'s [book], [owner], [bal] and [price]
+ Future> sendNewBookInstance(
Book book,
Owner owner,
Bal bal,
double price,
) async {
- return await _apiClient.sendBook(book, owner, bal, price);
+ return await _apiClient.sendNewBookInstance(book, owner, bal, price);
}
+ /// Sells a [List]
Future> sellBooks(List books) async {
Map res = {};
for (BookInstance instance in books) {
diff --git a/lib/data/repositories/book_repository.dart b/lib/data/repositories/book_repository.dart
index 984d85b..5d38be1 100644
--- a/lib/data/repositories/book_repository.dart
+++ b/lib/data/repositories/book_repository.dart
@@ -2,16 +2,19 @@ import 'package:seshat/data/services/api_client.dart';
import 'package:seshat/domain/models/book.dart';
import 'package:seshat/utils/result.dart';
+/// Repository to manage [Book]
class BookRepository {
BookRepository({required ApiClient apiClient}) : _apiClient = apiClient;
final ApiClient _apiClient;
+ /// Gets a [Book] by its [ean]
Future> getBookByEAN(String ean) async {
return await _apiClient.getBookByEAN(ean);
}
- Future> getBookById(int id) async {
- return await _apiClient.getBookById(id);
+ /// Gets a [Book] by its [bookId]
+ Future> getBookById(int bookId) async {
+ return await _apiClient.getBookById(bookId);
}
}
diff --git a/lib/data/repositories/owner_repository.dart b/lib/data/repositories/owner_repository.dart
index 5020123..817bea0 100644
--- a/lib/data/repositories/owner_repository.dart
+++ b/lib/data/repositories/owner_repository.dart
@@ -5,6 +5,7 @@ import 'package:seshat/data/services/websocket_client.dart';
import 'package:seshat/domain/models/owner.dart';
import 'package:seshat/utils/result.dart';
+/// Repository to manage [Owner]
class OwnerRepository {
OwnerRepository({
required ApiClient apiClient,
@@ -14,18 +15,25 @@ class OwnerRepository {
final ApiClient _apiClient;
final WebsocketClient _wsClient;
- late final StreamSubscription sub;
- List? _cachedOwners;
- Owner? _sectionOwner;
- Future> get sectionOwner async {
- if (_sectionOwner != null) {
- return Result.ok(_sectionOwner!);
+ /// [StreamSubscription] to the [Stream] for [_wsClient]
+ late final StreamSubscription sub;
+
+ /// [List] of owners, updated by [_wsClient]
+ List? _cachedOwners;
+
+ /// [Owner] of the current user
+ Owner? _ownerOfUser;
+
+ /// [Owner] of the current user
+ Future> get ownerOfUser async {
+ if (_ownerOfUser != null) {
+ return Result.ok(_ownerOfUser!);
}
- final result = await _apiClient.getSectionOwner();
+ final result = await _apiClient.getOwnerOfUser();
switch (result) {
case Ok():
- _sectionOwner = result.value;
+ _ownerOfUser = result.value;
break;
default:
break;
@@ -33,16 +41,17 @@ class OwnerRepository {
return result;
}
- Future> getOwnerById(int id) async {
+ /// Gets an [Owner] from its [ownerId]
+ Future> getOwnerById(int ownerId) async {
if (_cachedOwners != null) {
final result1 = _cachedOwners!
- .where((owner) => owner.id == id)
+ .where((owner) => owner.id == ownerId)
.firstOrNull;
if (result1 != null) {
return Result.ok(result1);
}
}
- return await _apiClient.getOwnerById(id);
+ return await _apiClient.getOwnerById(ownerId);
}
/// Adds an [Owner] to the database, and gets the resulting [Owner].
diff --git a/lib/data/services/api_client.dart b/lib/data/services/api_client.dart
index 383f269..c638d4c 100644
--- a/lib/data/services/api_client.dart
+++ b/lib/data/services/api_client.dart
@@ -1,19 +1,17 @@
import 'dart:convert';
-import 'dart:math';
-import 'package:flutter/rendering.dart';
-import 'package:flutter/widgets.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart';
+import 'package:logger/logger.dart';
import 'package:seshat/config/constants.dart';
import 'package:seshat/domain/models/accounting.dart';
import 'package:seshat/domain/models/bal.dart';
import 'package:seshat/domain/models/bal_stats.dart';
import 'package:seshat/domain/models/book.dart';
import 'package:seshat/domain/models/book_instance.dart';
+import 'package:seshat/domain/models/enums.dart';
import 'package:seshat/domain/models/owner.dart';
import 'package:seshat/domain/models/search_result.dart';
-import 'package:seshat/utils/command.dart';
import 'package:seshat/utils/result.dart';
extension StringExtension on String {
@@ -22,22 +20,33 @@ extension StringExtension on String {
}
}
-typedef AuthHeaderProvider = String? Function();
-
+/// API Client to manage all authenticated REST routes
class ApiClient {
- ApiClient({String? host, int? port});
+ ApiClient();
- late final Command0 load;
+ /// JWT for registration
String? token;
- bool isReady = false;
- FlutterSecureStorage? _secureStorage;
+ /// Readiness of the API Client
+ bool isReady = false;
+
+ /// Storage to access JWT
+ FlutterSecureStorage? _secureStorage;
+ Logger log = Logger(
+ printer: PrettyPrinter(
+ colors: true,
+ lineLength: 100,
+ methodCount: 0,
+ dateTimeFormat: DateTimeFormat.dateAndTime,
+ ),
+ );
+
+ /// Initializes connection to the [_secureStorage]
Future _initStore() async {
- _secureStorage ??= const FlutterSecureStorage(
- aOptions: AndroidOptions(encryptedSharedPreferences: true),
- );
+ _secureStorage ??= const FlutterSecureStorage();
}
+ /// Generates authorization headers and option [additionalHeaders]
Future