Projektkezeléssel kapcsolatos hibajavítások, projekt törlése a szerveren, bejelentkezés
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s

This commit is contained in:
2026-08-09 23:18:06 +02:00
parent e1128700f0
commit fcd3971079
4 changed files with 317 additions and 37 deletions
+90 -2
View File
@@ -15,6 +15,7 @@ import 'package:terepi_seged/models/source_point.dart';
import 'package:terepi_seged/models/stakeout_point.dart';
import 'package:terepi_seged/models/track.dart';
import 'package:terepi_seged/models/vechicle_position_log.dart';
import 'package:terepi_seged/services/app_logger.dart';
import 'package:terepi_seged/services/device_identity_service.dart';
import 'package:uuid/uuid.dart';
import '../models/project.dart';
@@ -46,7 +47,7 @@ class AppDatabase {
final path = p.join(dbDir.path, 'terepi_seged.db');
return openDatabase(path,
version: 7,
version: 8,
onConfigure: (db) => db.execute('PRAGMA foreign_keys = ON'),
onCreate: _onCreate,
onUpgrade: _onUpgrade);
@@ -239,7 +240,7 @@ class AppDatabase {
vertical_error REAL,
description TEXT,
is_deleted INTEGER NOT NULL DEFAULT 0,
project_id INTEGER NOT NULL DEFAULT 2,
project_id INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
sync_status TEXT NOT NULL DEFAULT 'pending'
)
@@ -274,6 +275,7 @@ class AppDatabase {
await _addAppInstanceIdColumns(db);
await _addContactLocationColumns(db);
await _addProjectMissingStreakColumn(db);
await _createVibratorNavTables(db);
@@ -315,6 +317,7 @@ class AppDatabase {
await _addAppInstanceIdColumns(db);
await _createVibratorNavTables(db);
await _addContactLocationColumns(db);
await _addProjectMissingStreakColumn(db);
}
Future<void> _migrateToV4(Database db) async {
@@ -1406,6 +1409,11 @@ class AppDatabase {
}
}
Future<void> _addProjectMissingStreakColumn(Database db) async {
await _tryExec(db,
'ALTER TABLE projects ADD COLUMN missing_streak INTEGER NOT NULL DEFAULT 0');
}
// Future<void> testOnly() async {
// final db = await database;
// await db.execute(
@@ -1493,4 +1501,84 @@ class AppDatabase {
where: 'project_id = ?', whereArgs: [projectId]);
return rows.map(VehiclePositionLog.fromMap).toList();
}
/// Összeveti a szerver "aktív tagság" listáját a helyi, SZINKRONIZÁLT
/// (nem csak-lokális) projektekkel. Ami tartósan (több ciklusban)
/// hiányzik onnan, azt — a helyi gyerek-adatokkal EGYÜTT — törli.
/// Szándékosan NEM azonnal töröl egyetlen hiányzás után, hogy egy
/// átmeneti hálózati/RLS-hiba ne okozhasson véletlen adatvesztést.
Future<void> reconcileMissingProjects(Set<String> remoteUuids) async {
const missingThreshold = 3; // ennyi egymást követő ciklus után törlünk
final db = await database;
final localSynced = await db.query('projects',
where: 'is_local_only = 0',
columns: ['id', 'uuid', 'name', 'missing_streak']);
for (final row in localSynced) {
final localId = row['id'] as int;
final uuid = row['uuid'] as String;
final streak = (row['missing_streak'] as int?) ?? 0;
if (remoteUuids.contains(uuid)) {
if (streak != 0) {
await db.update('projects', {'missing_streak': 0},
where: 'id = ?', whereArgs: [localId]);
}
continue;
}
final newStreak = streak + 1;
if (newStreak >= missingThreshold) {
await _cascadeDeleteLocalProject(localId, uuid, row['name'] as String?);
} else {
await db.update('projects', {'missing_streak': newStreak},
where: 'id = ?', whereArgs: [localId]);
}
}
}
/// A projekt ÉS minden helyi, hozzá kötött adat törlése — a szerveren
/// már nem létező (törölt vagy tagságból kikerült) projekt helyi
/// árváinak eltávolítása.
///
/// A track_points (tracks-hoz) és a note_item_photos/note_item_audios
/// (note_items-hez) már ON DELETE CASCADE-del hivatkoznak a szülőre, és
/// a PRAGMA foreign_keys = ON aktív (onConfigure) — ezeket a SQLite
/// automatikusan törli, nem kell kézzel foglalkozni velük.
///
/// A contacts_outbox és a pending_points külön figyelmet igényel:
/// a contacts_outbox a projekt UUID-jével (nem a helyi int id-vel)
/// van kulcsolva, a pending_points viszont a szokásos int id-vel.
Future<void> _cascadeDeleteLocalProject(
int localProjectId, String projectUuid, String? name) async {
final db = await database;
await db.transaction((txn) async {
for (final table in [
'tracks', // → track_points automatikusan
'measured_points',
'note_items', // → note_item_photos/audios automatikusan
'stakeout_points',
'imported_layers',
'source_points',
'vehicle_position_logs',
'pending_points',
]) {
await txn.delete(table,
where: 'project_id = ?', whereArgs: [localProjectId]);
}
// A contacts_outbox a projekt UUID-jét használja kulcsként.
await txn.delete('contacts_outbox',
where: 'project_id = ?', whereArgs: [projectUuid]);
await txn
.delete('projects', where: 'id = ?', whereArgs: [localProjectId]);
});
AppLogger.e(
'AppDatabase',
'Projekt helyi törlése: "$name" (id=$localProjectId) — a szerveren '
'már nem szerepel a tagsági listában (törölve vagy kikerültünk).');
}
}