feat(relations): delete relations from details
This commit is contained in:
parent
a6ec67f38d
commit
67d8db15af
8 changed files with 275 additions and 0 deletions
|
|
@ -104,6 +104,24 @@ gboolean relation_dao_update(
|
|||
GError **error
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Supprime une relation par son UUID.
|
||||
*
|
||||
* Les associations dépendantes sont supprimées par les contraintes SQLite.
|
||||
* Les entités et les preuves ne sont jamais supprimées.
|
||||
*
|
||||
* @param relation_dao DAO valide.
|
||||
* @param identifier UUID de la relation existante.
|
||||
* @param error Emplacement facultatif recevant une erreur.
|
||||
*
|
||||
* @return TRUE si la suppression a été exécutée.
|
||||
*/
|
||||
gboolean relation_dao_delete(
|
||||
RelationDao *relation_dao,
|
||||
const char *identifier,
|
||||
GError **error
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Recherche une relation par son UUID.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -142,6 +142,9 @@ typedef void (*MainWindowEditRelationCallback)(
|
|||
const char *relation_identifier,
|
||||
gpointer user_data
|
||||
);
|
||||
/** @brief Callback appelé pour supprimer une relation. */
|
||||
typedef void (*MainWindowDeleteRelationCallback)(
|
||||
const char *relation_identifier, gpointer user_data);
|
||||
|
||||
/** @brief Callback appelé quand une relation est sélectionnée. */
|
||||
typedef void (*MainWindowRelationSelectedCallback)(
|
||||
|
|
@ -255,6 +258,9 @@ void main_window_set_edit_relation_callback(
|
|||
MainWindowEditRelationCallback callback,
|
||||
gpointer user_data
|
||||
);
|
||||
/** @brief Définit le callback de suppression d'une relation. */
|
||||
void main_window_set_delete_relation_callback(MainWindow *main_window,
|
||||
MainWindowDeleteRelationCallback callback, gpointer user_data);
|
||||
|
||||
/** @brief Définit le callback de sélection d'une relation. */
|
||||
void main_window_set_relation_selected_callback(
|
||||
|
|
|
|||
|
|
@ -84,6 +84,9 @@ typedef void (*WorkspaceEditRelationCallback)(
|
|||
const char *relation_identifier,
|
||||
gpointer user_data
|
||||
);
|
||||
/** @brief Callback appelé pour supprimer la relation sélectionnée. */
|
||||
typedef void (*WorkspaceDeleteRelationCallback)(
|
||||
const char *relation_identifier, gpointer user_data);
|
||||
|
||||
/** @brief Callback appelé quand une relation devient sélectionnée. */
|
||||
typedef void (*WorkspaceRelationSelectedCallback)(
|
||||
|
|
@ -326,6 +329,9 @@ void workspace_set_edit_relation_callback(
|
|||
WorkspaceEditRelationCallback callback,
|
||||
gpointer user_data
|
||||
);
|
||||
/** @brief Définit le callback de suppression d'une relation. */
|
||||
void workspace_set_delete_relation_callback(Workspace *workspace,
|
||||
WorkspaceDeleteRelationCallback callback, gpointer user_data);
|
||||
|
||||
/**
|
||||
* @brief Définit le callback de sélection d'une relation.
|
||||
|
|
|
|||
|
|
@ -4975,6 +4975,120 @@ typedef struct
|
|||
char *relation_identifier;
|
||||
} ApplicationEditRelationContext;
|
||||
|
||||
/** @brief Contexte possédé pendant la confirmation de suppression. */
|
||||
typedef struct
|
||||
{
|
||||
Application *application;
|
||||
char *relation_identifier;
|
||||
} ApplicationDeleteRelationContext;
|
||||
|
||||
/** @brief Libère un contexte de suppression de relation. */
|
||||
static void application_delete_relation_context_free(
|
||||
ApplicationDeleteRelationContext *context)
|
||||
{
|
||||
if (context == NULL) return;
|
||||
g_free(context->relation_identifier);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/** @brief Supprime la relation après confirmation explicite. */
|
||||
static void application_on_delete_relation_confirmed(gboolean confirmed,
|
||||
gpointer user_data)
|
||||
{
|
||||
ApplicationDeleteRelationContext *context = user_data;
|
||||
Application *application = context != NULL ? context->application : NULL;
|
||||
Database *database = NULL;
|
||||
RelationDao *relation_dao = NULL;
|
||||
const InvestigationProject *project = NULL;
|
||||
GError *error = NULL;
|
||||
gboolean transaction_active = FALSE;
|
||||
if (!confirmed || application == NULL || application->session == NULL)
|
||||
goto cleanup;
|
||||
database = investigation_session_get_database(application->session);
|
||||
project = investigation_session_get_project(application->session);
|
||||
relation_dao = relation_dao_new(database, &error);
|
||||
if (relation_dao == NULL || !database_transaction_begin(database))
|
||||
goto failure;
|
||||
transaction_active = TRUE;
|
||||
if (!relation_dao_delete(relation_dao, context->relation_identifier,
|
||||
&error) || !database_transaction_commit(database))
|
||||
goto failure;
|
||||
transaction_active = FALSE;
|
||||
main_window_set_status(application->main_window,
|
||||
"Relation supprimée. Actualisation du graphe…");
|
||||
application_start_graph_loading(application,
|
||||
investigation_project_get_database_path(project));
|
||||
goto cleanup;
|
||||
failure:
|
||||
if (transaction_active) database_transaction_rollback(database);
|
||||
application_present_error(application, "Suppression impossible",
|
||||
error != NULL ? error->message :
|
||||
"La relation n'a pas pu être supprimée.");
|
||||
cleanup:
|
||||
g_clear_error(&error);
|
||||
relation_dao_free(relation_dao);
|
||||
application_delete_relation_context_free(context);
|
||||
}
|
||||
|
||||
/** @brief Prépare la confirmation de suppression d'une relation. */
|
||||
static void application_on_delete_relation_requested(
|
||||
const char *relation_identifier, gpointer user_data)
|
||||
{
|
||||
Application *application = user_data;
|
||||
Database *database = NULL;
|
||||
RelationDao *relation_dao = NULL;
|
||||
EntityDao *entity_dao = NULL;
|
||||
RelationRecord *relation = NULL;
|
||||
EntityRecord *source = NULL;
|
||||
EntityRecord *target = NULL;
|
||||
ApplicationDeleteRelationContext *context = NULL;
|
||||
char *message = NULL;
|
||||
GError *error = NULL;
|
||||
if (application == NULL || application->session == NULL ||
|
||||
relation_identifier == NULL) return;
|
||||
database = investigation_session_get_database(application->session);
|
||||
relation_dao = relation_dao_new(database, &error);
|
||||
if (relation_dao != NULL) relation = relation_dao_find_by_identifier(
|
||||
relation_dao, relation_identifier, &error);
|
||||
entity_dao = entity_dao_new(database, &error);
|
||||
if (relation != NULL && entity_dao != NULL)
|
||||
{
|
||||
source = entity_dao_find_by_identifier(entity_dao,
|
||||
relation_record_get_source_entity_identifier(relation), &error);
|
||||
target = entity_dao_find_by_identifier(entity_dao,
|
||||
relation_record_get_target_entity_identifier(relation), &error);
|
||||
}
|
||||
if (relation == NULL || source == NULL || target == NULL) goto failure;
|
||||
context = g_new0(ApplicationDeleteRelationContext, 1);
|
||||
context->application = application;
|
||||
context->relation_identifier = g_strdup(relation_identifier);
|
||||
message = g_strdup_printf(
|
||||
"Supprimer définitivement la relation :\n\n%s → %s\n\n"
|
||||
"Les entités et les preuves resteront dans l’enquête.",
|
||||
entity_record_get_label(source) != NULL
|
||||
? entity_record_get_label(source) : entity_record_get_value(source),
|
||||
entity_record_get_label(target) != NULL
|
||||
? entity_record_get_label(target) : entity_record_get_value(target));
|
||||
if (context->relation_identifier == NULL || message == NULL) goto failure;
|
||||
application_message_dialog_present_confirmation(
|
||||
main_window_get_window(application->main_window),
|
||||
APPLICATION_MESSAGE_DIALOG_WARNING, "Supprimer la relation", message,
|
||||
"Supprimer", application_on_delete_relation_confirmed, context);
|
||||
context = NULL;
|
||||
goto cleanup;
|
||||
failure:
|
||||
application_delete_relation_context_free(context); context = NULL;
|
||||
application_present_error(application, "Suppression impossible",
|
||||
error != NULL ? error->message :
|
||||
"La relation sélectionnée n'a pas pu être chargée.");
|
||||
cleanup:
|
||||
g_free(message);
|
||||
g_clear_error(&error);
|
||||
entity_record_free(source); entity_record_free(target);
|
||||
relation_record_free(relation);
|
||||
entity_dao_free(entity_dao); relation_dao_free(relation_dao);
|
||||
}
|
||||
|
||||
/** @brief Enregistre la catégorie choisie depuis la fiche personne. */
|
||||
static void application_on_person_role_changed(const char *entity_identifier,
|
||||
PersonRole role, gpointer user_data)
|
||||
|
|
@ -6728,6 +6842,8 @@ static void application_on_activate(
|
|||
|
||||
main_window_set_edit_relation_callback(application->main_window,
|
||||
application_on_edit_relation_requested, application);
|
||||
main_window_set_delete_relation_callback(application->main_window,
|
||||
application_on_delete_relation_requested, application);
|
||||
main_window_set_relation_selected_callback(application->main_window,
|
||||
application_on_relation_selected, application);
|
||||
main_window_set_person_role_callback(application->main_window,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ static const char *const relation_dao_update_sql =
|
|||
"type_relation = ?, label = ?, justification = ?, confiance = ?, "
|
||||
"updated_at = ?, status = ? WHERE id = ?;";
|
||||
|
||||
/** @brief Requête de suppression d'une relation. */
|
||||
static const char *const relation_dao_delete_sql =
|
||||
"DELETE FROM relations WHERE id = ?;";
|
||||
|
||||
/**
|
||||
* @brief Requête de recherche par UUID.
|
||||
*/
|
||||
|
|
@ -1363,6 +1367,49 @@ cleanup:
|
|||
return success;
|
||||
}
|
||||
|
||||
gboolean relation_dao_delete(RelationDao *relation_dao,
|
||||
const char *identifier, GError **error)
|
||||
{
|
||||
DatabaseStatement *statement = NULL;
|
||||
gboolean success = FALSE;
|
||||
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||
if (relation_dao == NULL || relation_dao->database == NULL ||
|
||||
identifier == NULL || !g_uuid_string_is_valid(identifier))
|
||||
{
|
||||
relation_dao_set_error_literal(error,
|
||||
RELATION_DAO_ERROR_INVALID_ARGUMENT,
|
||||
"Les paramètres de suppression de la relation sont invalides.");
|
||||
return FALSE;
|
||||
}
|
||||
statement = database_statement_prepare(relation_dao->database,
|
||||
relation_dao_delete_sql);
|
||||
if (statement == NULL)
|
||||
{
|
||||
relation_dao_set_database_error(relation_dao, error,
|
||||
RELATION_DAO_ERROR_PREPARE,
|
||||
"Impossible de préparer la suppression de la relation");
|
||||
goto cleanup;
|
||||
}
|
||||
if (!database_statement_bind_text(statement, 1, identifier))
|
||||
{
|
||||
relation_dao_set_database_error(relation_dao, error,
|
||||
RELATION_DAO_ERROR_BIND,
|
||||
"Impossible de lier l'identifiant de la relation");
|
||||
goto cleanup;
|
||||
}
|
||||
if (database_statement_step(statement) != DATABASE_STATEMENT_STEP_DONE)
|
||||
{
|
||||
relation_dao_set_database_error(relation_dao, error,
|
||||
RELATION_DAO_ERROR_EXECUTE,
|
||||
"Impossible de supprimer la relation");
|
||||
goto cleanup;
|
||||
}
|
||||
success = TRUE;
|
||||
cleanup:
|
||||
database_statement_finalize(statement);
|
||||
return success;
|
||||
}
|
||||
|
||||
RelationRecord *relation_dao_find_by_identifier(
|
||||
RelationDao *relation_dao,
|
||||
const char *identifier,
|
||||
|
|
|
|||
|
|
@ -131,6 +131,8 @@ struct MainWindow
|
|||
|
||||
MainWindowEditRelationCallback edit_relation_callback;
|
||||
gpointer edit_relation_user_data;
|
||||
MainWindowDeleteRelationCallback delete_relation_callback;
|
||||
gpointer delete_relation_user_data;
|
||||
MainWindowPersonRoleCallback person_role_callback;
|
||||
gpointer person_role_user_data;
|
||||
MainWindowPersonConfidenceCallback person_confidence_callback;
|
||||
|
|
@ -532,6 +534,16 @@ static void main_window_on_edit_relation_requested(
|
|||
main_window->edit_relation_user_data);
|
||||
}
|
||||
|
||||
/** @brief Relaie la demande de suppression d'une relation. */
|
||||
static void main_window_on_delete_relation_requested(
|
||||
const char *relation_identifier, gpointer user_data)
|
||||
{
|
||||
MainWindow *main_window = user_data;
|
||||
if (main_window != NULL && main_window->delete_relation_callback != NULL)
|
||||
main_window->delete_relation_callback(relation_identifier,
|
||||
main_window->delete_relation_user_data);
|
||||
}
|
||||
|
||||
/** @brief Relaie la catégorisation d'une personne. */
|
||||
static void main_window_on_person_role_changed(const char *entity_identifier,
|
||||
PersonRole role, gpointer user_data)
|
||||
|
|
@ -903,6 +915,8 @@ MainWindow *main_window_new(
|
|||
|
||||
workspace_set_edit_relation_callback(main_window->workspace,
|
||||
main_window_on_edit_relation_requested, main_window);
|
||||
workspace_set_delete_relation_callback(main_window->workspace,
|
||||
main_window_on_delete_relation_requested, main_window);
|
||||
workspace_set_person_role_callback(main_window->workspace,
|
||||
main_window_on_person_role_changed, main_window);
|
||||
workspace_set_person_confidence_callback(main_window->workspace,
|
||||
|
|
@ -1701,6 +1715,14 @@ void main_window_set_edit_relation_callback(MainWindow *main_window,
|
|||
main_window->edit_relation_user_data = user_data;
|
||||
}
|
||||
|
||||
void main_window_set_delete_relation_callback(MainWindow *main_window,
|
||||
MainWindowDeleteRelationCallback callback, gpointer user_data)
|
||||
{
|
||||
if (main_window == NULL) return;
|
||||
main_window->delete_relation_callback = callback;
|
||||
main_window->delete_relation_user_data = user_data;
|
||||
}
|
||||
|
||||
void main_window_set_relation_selected_callback(MainWindow *main_window,
|
||||
MainWindowRelationSelectedCallback callback, gpointer user_data)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ struct Workspace
|
|||
GtkWidget *relation_details_summary_label;
|
||||
GtkWidget *relation_evidences_label;
|
||||
GtkWidget *edit_relation_button;
|
||||
GtkWidget *delete_relation_button;
|
||||
GtkWidget *close_relation_details_button;
|
||||
char *selected_relation_identifier;
|
||||
|
||||
|
|
@ -131,6 +132,8 @@ struct Workspace
|
|||
add_relation_user_data;
|
||||
|
||||
WorkspaceEditRelationCallback edit_relation_callback;
|
||||
WorkspaceDeleteRelationCallback delete_relation_callback;
|
||||
gpointer delete_relation_user_data;
|
||||
WorkspaceRelationSelectedCallback relation_selected_callback;
|
||||
gpointer relation_selected_user_data;
|
||||
gpointer edit_relation_user_data;
|
||||
|
|
@ -839,6 +842,18 @@ static void workspace_on_edit_relation_clicked(GtkButton *button,
|
|||
workspace->edit_relation_user_data);
|
||||
}
|
||||
|
||||
/** @brief Relaie la demande de suppression de la relation affichée. */
|
||||
static void workspace_on_delete_relation_clicked(GtkButton *button,
|
||||
gpointer user_data)
|
||||
{
|
||||
Workspace *workspace = user_data;
|
||||
(void) button;
|
||||
if (workspace == NULL || workspace->delete_relation_callback == NULL ||
|
||||
workspace->selected_relation_identifier == NULL) return;
|
||||
workspace->delete_relation_callback(workspace->selected_relation_identifier,
|
||||
workspace->delete_relation_user_data);
|
||||
}
|
||||
|
||||
/** @brief Ferme le volet de relation et désélectionne le graphe. */
|
||||
static void workspace_on_close_relation_details_clicked(GtkButton *button,
|
||||
gpointer user_data)
|
||||
|
|
@ -1712,6 +1727,8 @@ Workspace *workspace_new(void)
|
|||
workspace->relation_evidences_label = gtk_label_new(
|
||||
"Pièces jointes : chargement…");
|
||||
workspace->edit_relation_button = gtk_button_new_with_label("Modifier");
|
||||
workspace->delete_relation_button = gtk_button_new_with_label(
|
||||
"Supprimer la relation");
|
||||
workspace->close_relation_details_button =
|
||||
gtk_button_new_from_icon_name("window-close-symbolic");
|
||||
|
||||
|
|
@ -1722,6 +1739,7 @@ Workspace *workspace_new(void)
|
|||
workspace->relation_details_summary_label == NULL ||
|
||||
workspace->relation_evidences_label == NULL ||
|
||||
workspace->edit_relation_button == NULL ||
|
||||
workspace->delete_relation_button == NULL ||
|
||||
workspace->close_relation_details_button == NULL)
|
||||
{
|
||||
workspace_free(
|
||||
|
|
@ -1791,8 +1809,14 @@ Workspace *workspace_new(void)
|
|||
workspace->relation_evidences_label);
|
||||
gtk_box_append(GTK_BOX(workspace->relation_details_panel),
|
||||
workspace->edit_relation_button);
|
||||
gtk_widget_add_css_class(workspace->delete_relation_button,
|
||||
"destructive-action");
|
||||
gtk_box_append(GTK_BOX(workspace->relation_details_panel),
|
||||
workspace->delete_relation_button);
|
||||
g_signal_connect(workspace->edit_relation_button, "clicked",
|
||||
G_CALLBACK(workspace_on_edit_relation_clicked), workspace);
|
||||
g_signal_connect(workspace->delete_relation_button, "clicked",
|
||||
G_CALLBACK(workspace_on_delete_relation_clicked), workspace);
|
||||
g_signal_connect(workspace->close_relation_details_button, "clicked",
|
||||
G_CALLBACK(workspace_on_close_relation_details_clicked), workspace);
|
||||
gtk_widget_set_visible(workspace->relation_details_panel, FALSE);
|
||||
|
|
@ -3245,6 +3269,14 @@ void workspace_set_edit_relation_callback(Workspace *workspace,
|
|||
workspace->edit_relation_user_data = user_data;
|
||||
}
|
||||
|
||||
void workspace_set_delete_relation_callback(Workspace *workspace,
|
||||
WorkspaceDeleteRelationCallback callback, gpointer user_data)
|
||||
{
|
||||
if (workspace == NULL) return;
|
||||
workspace->delete_relation_callback = callback;
|
||||
workspace->delete_relation_user_data = user_data;
|
||||
}
|
||||
|
||||
void workspace_set_relation_selected_callback(Workspace *workspace,
|
||||
WorkspaceRelationSelectedCallback callback, gpointer user_data)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2266,6 +2266,33 @@ static void test_relation_dao_update_existing(void)
|
|||
test_relation_dao_fixture_clear(&fixture);
|
||||
}
|
||||
|
||||
/** @brief Vérifie la suppression ciblée sans supprimer les entités. */
|
||||
static void test_relation_dao_delete_existing(void)
|
||||
{
|
||||
static const char identifier[] =
|
||||
"90000000-0000-4000-8000-000000000002";
|
||||
TestRelationDaoFixture fixture = test_relation_dao_fixture_create();
|
||||
RelationRecord *relation = NULL;
|
||||
RelationRecord *stored = NULL;
|
||||
GError *error = NULL;
|
||||
guint64 count = 0;
|
||||
test_relation_dao_insert_standard_entities(&fixture);
|
||||
relation = test_relation_dao_create_relation(identifier,
|
||||
TEST_ENTITY_A_IDENTIFIER, TEST_ENTITY_B_IDENTIFIER, "uses",
|
||||
"Relation à supprimer", NULL, 80, "2026-07-20T20:00:00Z",
|
||||
"2026-07-20T20:00:00Z", RELATION_STATUS_ACTIVE);
|
||||
test_relation_dao_insert_relation(&fixture, relation);
|
||||
assert(relation_dao_delete(fixture.relation_dao, identifier, &error));
|
||||
assert(error == NULL);
|
||||
assert(relation_dao_count(fixture.relation_dao, &count, &error));
|
||||
assert(count == 0 && error == NULL);
|
||||
stored = relation_dao_find_by_identifier(fixture.relation_dao,
|
||||
identifier, &error);
|
||||
assert(stored == NULL && error == NULL);
|
||||
relation_record_free(relation);
|
||||
test_relation_dao_fixture_clear(&fixture);
|
||||
}
|
||||
|
||||
int main(
|
||||
int argc,
|
||||
char **argv
|
||||
|
|
@ -2298,6 +2325,7 @@ int main(
|
|||
test_relation_dao_reflexive_schema_constraint();
|
||||
test_relation_dao_foreign_key_restrict();
|
||||
test_relation_dao_update_existing();
|
||||
test_relation_dao_delete_existing();
|
||||
|
||||
printf(
|
||||
"RelationDao : tous les tests sont valides.\n"
|
||||
|
|
|
|||
Loading…
Reference in a new issue