uto_manager/lib/infrastructure/repositories/drift_unit_repository.dart

175 lines
5.4 KiB
Dart

import 'package:drift/drift.dart' show Value;
import 'package:uto_manager/domain/entities/doctrinal_branch.dart';
import 'package:uto_manager/domain/entities/echelon.dart';
import 'package:uto_manager/domain/entities/unit_node.dart';
import 'package:uto_manager/domain/repositories/unit_repository.dart';
import 'package:uto_manager/infrastructure/database/app_database.dart';
import 'package:uto_manager/infrastructure/database/daos/unit_dao.dart';
/// Drift-backed implementation of [UnitRepository].
///
/// Converts between Drift [Unit] data rows and domain [UnitNode] entities.
/// All persistence is handled via [UnitDao].
class DriftUnitRepository implements UnitRepository {
const DriftUnitRepository({required this.dao, required this.db});
final UnitDao dao;
final AppDatabase db;
// ---------------------------------------------------------------------------
// Mapping helpers
// ---------------------------------------------------------------------------
UnitNode _toDomain(Unit row) {
return UnitNode(
unitId: row.unitId,
echelon: Echelon.values.firstWhere((e) => e.name == row.echelon),
branch: DoctrinalBranch.values.firstWhere((b) => b.name == row.branch),
parentUnitId: row.parentUnitId,
name: row.name,
isKeyLeader: row.isKeyLeader,
);
}
UnitsCompanion _toCompanion(UnitNode unit) {
return UnitsCompanion.insert(
unitId: unit.unitId,
echelon: unit.echelon.name,
branch: unit.branch.name,
parentUnitId: Value(unit.parentUnitId),
name: unit.name,
isKeyLeader: unit.isKeyLeader,
);
}
// ---------------------------------------------------------------------------
// UnitRepository implementation
// ---------------------------------------------------------------------------
@override
Future<List<UnitNode>> getAll() async {
final rows = await dao.getAllUnits();
return rows.map(_toDomain).toList();
}
@override
Future<UnitNode?> getById(String unitId) async {
final row = await dao.getUnitById(unitId);
return row == null ? null : _toDomain(row);
}
@override
Future<List<UnitNode>> getChildren(String parentUnitId) async {
final rows = await dao.getChildren(parentUnitId);
return rows.map(_toDomain).toList();
}
@override
Future<List<UnitNode>> getRoots() async {
final rows = await dao.getRoots();
return rows.map(_toDomain).toList();
}
@override
Future<List<UnitNode>> getByEchelon(Echelon echelon) async {
final rows = await dao.getByEchelon(echelon.name);
return rows.map(_toDomain).toList();
}
@override
Future<List<UnitNode>> getSubtree(String unitId) async {
final all = await getAll();
return _collectSubtree(unitId, all);
}
@override
Future<void> insert(UnitNode unit) async {
final existing = await dao.getUnitById(unit.unitId);
if (existing != null) {
throw StateError('Unit ${unit.unitId} already exists.');
}
await dao.insertUnit(_toCompanion(unit));
}
@override
Future<void> insertAll(List<UnitNode> units) async {
await dao.insertAllUnits(units.map(_toCompanion).toList());
}
@override
Future<void> update(UnitNode unit) async {
final existing = await dao.getUnitById(unit.unitId);
if (existing == null) {
throw StateError('Unit ${unit.unitId} not found.');
}
await dao.updateUnit(_toCompanion(unit));
}
@override
Future<void> delete(String unitId) async {
await dao.deleteUnit(unitId);
}
@override
Future<void> deleteSubtree(String unitId) async {
final all = await getAll();
final subtree = _collectSubtree(unitId, all);
// Delete leaves first (bottom-up) to respect FK constraints.
for (final unit in subtree.reversed) {
await dao.deleteUnit(unit.unitId);
}
}
@override
Future<void> reparent(String unitId, String newParentId) async {
final unit = await getById(unitId);
if (unit == null) throw StateError('Unit $unitId not found.');
final newParent = await getById(newParentId);
if (newParent == null) throw StateError('Parent $newParentId not found.');
if (!unit.echelon.isValidParent(newParent.echelon)) {
throw ArgumentError(
'${unit.echelon.displayName} cannot be a child of '
'${newParent.echelon.displayName}.',
);
}
final updated = unit.copyWith(parentUnitId: newParentId);
await dao.updateUnit(_toCompanion(updated));
}
@override
Stream<List<UnitNode>> watchAll() {
return dao.watchAllUnits().map((rows) => rows.map(_toDomain).toList());
}
@override
Stream<List<UnitNode>> watchSubtree(String unitId) {
return watchAll().map((all) => _collectSubtree(unitId, all));
}
@override
Future<void> deleteAll() async {
await dao.deleteAllUnits();
}
// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------
List<UnitNode> _collectSubtree(String rootId, List<UnitNode> all) {
final result = <UnitNode>[];
final queue = <String>[rootId];
while (queue.isNotEmpty) {
final current = queue.removeAt(0);
final node = all.firstWhere(
(u) => u.unitId == current,
orElse: () => throw StateError('Unit $current not found'),
);
result.add(node);
queue.addAll(all.where((u) => u.parentUnitId == current).map((u) => u.unitId));
}
return result;
}
}