From 235c7e0540082178f1f55f88324b57b4b520dcb5 Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Wed, 22 Jul 2026 15:33:38 +0200 Subject: [PATCH] feat(entities): add person identity records --- Makefile | 21 ++- README.md | 2 + include/core/person_entity_service.h | 32 +++++ include/views/create_person_dialog.h | 33 +++++ include/views/main_window.h | 7 + src/core/application.c | 56 ++++++++ src/core/person_entity_service.c | 86 +++++++++++ src/views/create_person_dialog.c | 204 +++++++++++++++++++++++++++ src/views/main_window.c | 28 ++++ tests/test_person_entity_service.c | 48 +++++++ 10 files changed, 515 insertions(+), 2 deletions(-) create mode 100644 include/core/person_entity_service.h create mode 100644 include/views/create_person_dialog.h create mode 100644 src/core/person_entity_service.c create mode 100644 src/views/create_person_dialog.c create mode 100644 tests/test_person_entity_service.c diff --git a/Makefile b/Makefile index 48c2ecf..fc02f71 100644 --- a/Makefile +++ b/Makefile @@ -123,6 +123,7 @@ TEST_OSINT_EXECUTION_INTEGRITY := tests/test_osint_execution_integrity TEST_EVIDENCE_RECLASSIFICATION := tests/test_evidence_reclassification TEST_SOCIAL_ACCOUNT_SERVICE := tests/test_social_account_service TEST_SOCIAL_PLATFORM := tests/test_social_platform +TEST_PERSON_ENTITY_SERVICE := tests/test_person_entity_service all: $(TARGET) @@ -629,6 +630,19 @@ $(TEST_SOCIAL_PLATFORM): \ src/models/social_platform.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) +$(TEST_PERSON_ENTITY_SERVICE): \ + tests/test_person_entity_service.c \ + src/core/person_entity_service.c \ + src/dao/entity_dao.c \ + src/dao/evidence_entity_dao.c \ + src/models/entity_record.c \ + src/database/database.c \ + src/database/schema.c \ + src/database/statement.c \ + src/database/transaction.c \ + src/database/error.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 + $(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \ tests/test_investigation_graph_load_task.c \ src/core/investigation_graph_load_task.c \ @@ -706,7 +720,8 @@ test: \ $(TEST_OSINT_EXECUTION_INTEGRITY) \ $(TEST_EVIDENCE_RECLASSIFICATION) \ $(TEST_SOCIAL_ACCOUNT_SERVICE) \ - $(TEST_SOCIAL_PLATFORM) + $(TEST_SOCIAL_PLATFORM) \ + $(TEST_PERSON_ENTITY_SERVICE) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @@ -764,6 +779,7 @@ test: \ @$(TEST_EVIDENCE_RECLASSIFICATION) @$(TEST_SOCIAL_ACCOUNT_SERVICE) @$(TEST_SOCIAL_PLATFORM) + @$(TEST_PERSON_ENTITY_SERVICE) @echo "Tous les tests sont valides." %.o: %.c @@ -827,7 +843,8 @@ clean: $(TEST_OSINT_EXECUTION_INTEGRITY) \ $(TEST_EVIDENCE_RECLASSIFICATION) \ $(TEST_SOCIAL_ACCOUNT_SERVICE) \ - $(TEST_SOCIAL_PLATFORM) + $(TEST_SOCIAL_PLATFORM) \ + $(TEST_PERSON_ENTITY_SERVICE) -include $(DEP) diff --git a/README.md b/README.md index 90c41c1..1351bb2 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,8 @@ Le socle actuel comprend notamment : première observation, état, notes et rattachement à une preuve ; - pictogrammes vectoriels des plateformes sociales dans les nœuds du graphe, conservant leur lisibilité pendant le zoom ; +- création de personnes observées avec statut d'identification, confiance, + notes factuelles et rattachement facultatif à une preuve ; - historique OSINT contextuel en lecture seule avec détail des exécutions, sorties standard et d'erreur, et objets créés ou réutilisés ; - vérification manuelle de l'intégrité des sorties OSINT enregistrées, sans diff --git a/include/core/person_entity_service.h b/include/core/person_entity_service.h new file mode 100644 index 0000000..cf7ded0 --- /dev/null +++ b/include/core/person_entity_service.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * @file person_entity_service.h + * @brief Création transactionnelle d'une personne observée. + ******************************************************************************/ +#ifndef LABFY_INVESTIGATION_PERSON_ENTITY_SERVICE_H +#define LABFY_INVESTIGATION_PERSON_ENTITY_SERVICE_H +#include "database/database.h" +#include +G_BEGIN_DECLS +/** @brief Données factuelles utilisées pour créer une personne. */ +typedef struct +{ + const char *designation; + const char *declared_name; + const char *pseudonym; + const char *identification_status; + const char *notes; + gint confidence; + const char *evidence_identifier; +} PersonEntityInput; +/** + * @brief Crée une personne et son éventuelle liaison à une preuve. + * @param database Connexion ouverte empruntée. + * @param input Données à enregistrer. + * @param out_identifier Destination facultative de l'UUID créé. + * @param error Destination facultative d'une erreur. + * @return TRUE après validation complète de la transaction. + */ +gboolean person_entity_service_create(Database *database, + const PersonEntityInput *input, char **out_identifier, GError **error); +G_END_DECLS +#endif diff --git a/include/views/create_person_dialog.h b/include/views/create_person_dialog.h new file mode 100644 index 0000000..f62857d --- /dev/null +++ b/include/views/create_person_dialog.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * @file create_person_dialog.h + * @brief Formulaire GTK de création d'une personne observée. + ******************************************************************************/ +#ifndef LABFY_INVESTIGATION_CREATE_PERSON_DIALOG_H +#define LABFY_INVESTIGATION_CREATE_PERSON_DIALOG_H +#include "core/person_entity_service.h" +#include "models/evidence_record.h" +#include +G_BEGIN_DECLS +/** @brief Résultat opaque possédé par le callback. */ +typedef struct CreatePersonDialogResult CreatePersonDialogResult; +/** @brief Callback appelé après validation ou annulation. */ +typedef void (*CreatePersonDialogCallback)(CreatePersonDialogResult *result, + gpointer user_data); +/** + * @brief Présente le formulaire de création d'une personne. + * @param parent Fenêtre parente. + * @param evidence_records Preuves disponibles empruntées. + * @param callback Callback de fin. + * @param user_data Données privées du callback. + * @return TRUE si le dialogue a été présenté. + */ +gboolean create_person_dialog_present(GtkWindow *parent, + const GPtrArray *evidence_records, CreatePersonDialogCallback callback, + gpointer user_data); +/** @brief Libère un résultat. */ +void create_person_dialog_result_free(CreatePersonDialogResult *result); +/** @brief Retourne les données empruntées du résultat. */ +const PersonEntityInput *create_person_dialog_result_get_input( + const CreatePersonDialogResult *result); +G_END_DECLS +#endif diff --git a/include/views/main_window.h b/include/views/main_window.h index 15d817a..19d441b 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -54,6 +54,8 @@ typedef void (*MainWindowImportEvidenceCallback)( typedef void (*MainWindowAddSocialAccountCallback)( gpointer user_data ); +/** @brief Callback appelé pour ajouter une personne. */ +typedef void (*MainWindowAddPersonCallback)(gpointer user_data); /** * @brief Callback appelé lorsque l'utilisateur revient au graphe. @@ -547,6 +549,9 @@ void main_window_set_add_social_account_callback( MainWindowAddSocialAccountCallback callback, gpointer user_data ); +/** @brief Définit le callback d'ajout d'une personne. */ +void main_window_set_add_person_callback(MainWindow *main_window, + MainWindowAddPersonCallback callback, gpointer user_data); /** * @brief Définit le callback du bouton « Revenir au graphe ». @@ -583,6 +588,8 @@ void main_window_set_add_social_account_enabled( MainWindow *main_window, gboolean enabled ); +/** @brief Active ou désactive l'ajout d'une personne. */ +void main_window_set_add_person_enabled(MainWindow *main_window, gboolean enabled); /** * @brief Libère les ressources de la fenêtre. diff --git a/src/core/application.c b/src/core/application.c index 3e422c5..4f988be 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -48,8 +48,10 @@ #include "core/evidence_integrity_verifier.h" #include "core/relation_service.h" #include "core/social_account_service.h" +#include "core/person_entity_service.h" #include "database/database.h" #include "views/create_social_account_dialog.h" +#include "views/create_person_dialog.h" #include #include @@ -2273,6 +2275,7 @@ static gboolean application_install_session( application->main_window, TRUE ); + main_window_set_add_person_enabled(application->main_window, TRUE); application_start_graph_loading( application, @@ -4172,6 +4175,55 @@ static void application_on_add_social_account_requested(gpointer user_data) g_clear_error(&error); } +/** @brief Persiste une personne validée puis recharge le graphe. */ +static void application_on_person_completed( + CreatePersonDialogResult *result, gpointer user_data) +{ + Application *application = user_data; + const PersonEntityInput *input = create_person_dialog_result_get_input(result); + const InvestigationProject *project = NULL; + GError *error = NULL; + char *identifier = NULL; + if (result == NULL || application == NULL || application->session == NULL) + { create_person_dialog_result_free(result); return; } + if (!person_entity_service_create( + investigation_session_get_database(application->session), + input, &identifier, &error)) + application_present_error(application, "Personne non créée", + error != NULL ? error->message : "L'écriture a échoué."); + else + { + project = investigation_session_get_project(application->session); + main_window_set_status(application->main_window, + "Personne ajoutée. Actualisation du graphe…"); + application_start_graph_loading(application, + investigation_project_get_database_path(project)); + } + g_clear_error(&error); g_free(identifier); + create_person_dialog_result_free(result); +} + +/** @brief Charge les preuves puis ouvre le formulaire d'une personne. */ +static void application_on_add_person_requested(gpointer user_data) +{ + Application *application = user_data; + EvidenceDao *dao = NULL; + GPtrArray *records = NULL; + GError *error = NULL; + if (application == NULL || application->session == NULL) return; + dao = evidence_dao_new( + investigation_session_get_database(application->session), &error); + if (dao != NULL) records = evidence_dao_list_all(dao, &error); + if (records == NULL) + application_present_error(application, "Formulaire indisponible", + error != NULL ? error->message : "Impossible de charger les preuves."); + else + create_person_dialog_present(main_window_get_window(application->main_window), + records, application_on_person_completed, application); + g_clear_pointer(&records, g_ptr_array_unref); + evidence_dao_free(dao); g_clear_error(&error); +} + /** * @brief Traite la sélection d'un nœud dans l'arborescence. * @@ -6233,6 +6285,8 @@ static void application_on_activate( application_on_add_social_account_requested, application ); + main_window_set_add_person_callback(application->main_window, + application_on_add_person_requested, application); main_window_set_import_evidence_enabled( application->main_window, @@ -6243,6 +6297,8 @@ static void application_on_activate( application->main_window, application->session != NULL ); + main_window_set_add_person_enabled(application->main_window, + application->session != NULL); main_window_set_quit_callback( application->main_window, diff --git a/src/core/person_entity_service.c b/src/core/person_entity_service.c new file mode 100644 index 0000000..e17c75a --- /dev/null +++ b/src/core/person_entity_service.c @@ -0,0 +1,86 @@ +/****************************************************************************** + * @file person_entity_service.c + * @brief Création transactionnelle d'une personne observée. + ******************************************************************************/ +#include "core/person_entity_service.h" +#include "dao/entity_dao.h" +#include "dao/evidence_entity_dao.h" +#include "database/transaction.h" +#include "models/entity_record.h" + +/** @brief Indique si le niveau d'identification est reconnu. */ +static gboolean person_entity_service_status_valid(const char *status) +{ + return g_strcmp0(status, "unknown") == 0 || + g_strcmp0(status, "suspected") == 0 || + g_strcmp0(status, "confirmed") == 0; +} + +gboolean person_entity_service_create(Database *database, + const PersonEntityInput *input, char **out_identifier, GError **error) +{ + EntityDao *entity_dao = NULL; + EvidenceEntityDao *link_dao = NULL; + EntityRecord *record = NULL; + GDateTime *now = NULL; + char *timestamp = NULL; + char *identifier = NULL; + char *description = NULL; + const char *value = NULL; + gboolean active = FALSE; + gboolean success = FALSE; + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + if (out_identifier != NULL) *out_identifier = NULL; + if (database == NULL || input == NULL || input->designation == NULL || + input->designation[0] == '\0' || + !person_entity_service_status_valid(input->identification_status) || + input->confidence < 0 || input->confidence > 100) + { + g_set_error_literal(error, g_quark_from_static_string( + "person-entity-service-error"), 1, + "Les informations de la personne sont invalides."); + return FALSE; + } + value = input->declared_name != NULL && input->declared_name[0] != '\0' + ? input->declared_name + : input->pseudonym != NULL && input->pseudonym[0] != '\0' + ? input->pseudonym : input->designation; + description = g_strdup_printf( + "Statut d'identification : %s.%s%s%s%s", + input->identification_status, + input->pseudonym != NULL ? "\nPseudonyme déclaré : " : "", + input->pseudonym != NULL ? input->pseudonym : "", + input->notes != NULL ? "\nNotes factuelles : " : "", + input->notes != NULL ? input->notes : ""); + identifier = g_uuid_string_random(); + now = g_date_time_new_now_utc(); + timestamp = now != NULL ? g_date_time_format(now, + "%Y-%m-%dT%H:%M:%SZ") : NULL; + if (identifier == NULL || timestamp == NULL || description == NULL || + !database_transaction_begin(database)) goto cleanup; + active = TRUE; + entity_dao = entity_dao_new(database, error); + record = entity_record_new(identifier, "person", value, + input->designation, description, input->confidence, + timestamp, timestamp, ENTITY_STATUS_ACTIVE, error); + if (entity_dao == NULL || record == NULL || + !entity_dao_insert(entity_dao, record, error)) goto cleanup; + if (input->evidence_identifier != NULL && input->evidence_identifier[0] != '\0') + { + link_dao = evidence_entity_dao_new(database, error); + if (link_dao == NULL || !evidence_entity_dao_link(link_dao, + input->evidence_identifier, identifier, error)) goto cleanup; + } + if (!database_transaction_commit(database)) goto cleanup; + active = FALSE; + success = TRUE; + if (out_identifier != NULL) *out_identifier = g_strdup(identifier); +cleanup: + if (!success && active) database_transaction_rollback(database); + evidence_entity_dao_free(link_dao); + entity_record_free(record); + entity_dao_free(entity_dao); + g_clear_pointer(&now, g_date_time_unref); + g_free(description); g_free(timestamp); g_free(identifier); + return success; +} diff --git a/src/views/create_person_dialog.c b/src/views/create_person_dialog.c new file mode 100644 index 0000000..7fa1ca4 --- /dev/null +++ b/src/views/create_person_dialog.c @@ -0,0 +1,204 @@ +/****************************************************************************** + * @file create_person_dialog.c + * @brief Formulaire GTK de création d'une personne observée. + ******************************************************************************/ +#include "views/create_person_dialog.h" + +struct CreatePersonDialogResult +{ + PersonEntityInput input; + char *designation; + char *declared_name; + char *pseudonym; + char *status; + char *notes; + char *evidence_identifier; +}; +typedef struct +{ + GtkWindow *window; + GtkEntry *designation; + GtkEntry *name; + GtkEntry *pseudonym; + GtkDropDown *status; + GtkSpinButton *confidence; + GtkDropDown *evidence; + GtkTextView *notes; + GtkLabel *error; + GPtrArray *evidence_identifiers; + CreatePersonDialogCallback callback; + gpointer user_data; + gboolean completed; +} CreatePersonDialogState; +static const char *const status_codes[] = {"unknown", "suspected", "confirmed"}; + +/** @brief Copie et nettoie un texte facultatif. */ +static char *create_person_dialog_copy(const char *text) +{ + char *copy = text != NULL ? g_strdup(text) : NULL; + if (copy == NULL) return NULL; + g_strstrip(copy); + if (copy[0] == '\0') { g_free(copy); return NULL; } + return copy; +} +/** @brief Extrait les notes de la zone de texte. */ +static char *create_person_dialog_get_notes(CreatePersonDialogState *state) +{ + GtkTextBuffer *buffer = gtk_text_view_get_buffer(state->notes); + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buffer, &start, &end); + return gtk_text_buffer_get_text(buffer, &start, &end, FALSE); +} +/** @brief Libère l'état attaché à la fenêtre. */ +static void create_person_dialog_state_free(gpointer data) +{ + CreatePersonDialogState *state = data; + if (state == NULL) return; + g_clear_pointer(&state->evidence_identifiers, g_ptr_array_unref); + g_free(state); +} +/** @brief Termine le dialogue comme une annulation. */ +static void create_person_dialog_cancel(CreatePersonDialogState *state) +{ + if (state == NULL || state->completed) return; + state->completed = TRUE; + if (state->callback != NULL) state->callback(NULL, state->user_data); +} +/** @brief Traite la fermeture native. */ +static gboolean create_person_dialog_on_close(GtkWindow *window, gpointer data) +{ + (void) window; create_person_dialog_cancel(data); return FALSE; +} +/** @brief Traite le bouton Annuler. */ +static void create_person_dialog_on_cancel(GtkButton *button, gpointer data) +{ + CreatePersonDialogState *state = data; + (void) button; create_person_dialog_cancel(state); gtk_window_close(state->window); +} +/** @brief Valide puis transmet les valeurs du formulaire. */ +static void create_person_dialog_on_create(GtkButton *button, gpointer data) +{ + CreatePersonDialogState *state = data; + CreatePersonDialogResult *result = NULL; + char *notes = NULL; + guint status = gtk_drop_down_get_selected(state->status); + guint evidence = gtk_drop_down_get_selected(state->evidence); + (void) button; + if (status >= G_N_ELEMENTS(status_codes) || + gtk_editable_get_text(GTK_EDITABLE(state->designation))[0] == '\0') + { + gtk_label_set_text(state->error, "La désignation de la personne est obligatoire."); + gtk_widget_set_visible(GTK_WIDGET(state->error), TRUE); return; + } + notes = create_person_dialog_get_notes(state); + result = g_new0(CreatePersonDialogResult, 1); + result->designation = create_person_dialog_copy( + gtk_editable_get_text(GTK_EDITABLE(state->designation))); + result->declared_name = create_person_dialog_copy( + gtk_editable_get_text(GTK_EDITABLE(state->name))); + result->pseudonym = create_person_dialog_copy( + gtk_editable_get_text(GTK_EDITABLE(state->pseudonym))); + result->status = g_strdup(status_codes[status]); + result->notes = create_person_dialog_copy(notes); + if (evidence > 0 && evidence - 1 < state->evidence_identifiers->len) + result->evidence_identifier = g_strdup(g_ptr_array_index( + state->evidence_identifiers, evidence - 1)); + result->input.designation = result->designation; + result->input.declared_name = result->declared_name; + result->input.pseudonym = result->pseudonym; + result->input.identification_status = result->status; + result->input.notes = result->notes; + result->input.confidence = gtk_spin_button_get_value_as_int(state->confidence); + result->input.evidence_identifier = result->evidence_identifier; + state->completed = TRUE; + if (state->callback != NULL) state->callback(result, state->user_data); + else create_person_dialog_result_free(result); + g_free(notes); gtk_window_close(state->window); +} +/** @brief Ajoute une ligne libellée au formulaire. */ +static void create_person_dialog_add_row(GtkGrid *grid, int row, + const char *label, GtkWidget *widget) +{ + GtkWidget *caption = gtk_label_new(label); + gtk_label_set_xalign(GTK_LABEL(caption), 0.0f); + gtk_widget_set_hexpand(widget, TRUE); + gtk_grid_attach(grid, caption, 0, row, 1, 1); + gtk_grid_attach(grid, widget, 1, row, 1, 1); +} +gboolean create_person_dialog_present(GtkWindow *parent, + const GPtrArray *records, CreatePersonDialogCallback callback, gpointer data) +{ + static const char *const statuses[] = {"Inconnu", "Présumé", "Confirmé", NULL}; + CreatePersonDialogState *state = NULL; + GtkWidget *box = NULL, *grid = NULL, *actions = NULL, *cancel = NULL, *create = NULL; + GtkStringList *labels = NULL; + if (parent == NULL) return FALSE; + state = g_new0(CreatePersonDialogState, 1); + state->callback = callback; state->user_data = data; + state->evidence_identifiers = g_ptr_array_new_with_free_func(g_free); + state->window = GTK_WINDOW(gtk_window_new()); + gtk_window_set_title(state->window, "Ajouter une personne"); + gtk_window_set_transient_for(state->window, parent); + gtk_window_set_modal(state->window, TRUE); + gtk_window_set_default_size(state->window, 580, 500); + box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + gtk_widget_set_margin_start(box, 16); gtk_widget_set_margin_end(box, 16); + gtk_widget_set_margin_top(box, 16); gtk_widget_set_margin_bottom(box, 16); + grid = gtk_grid_new(); gtk_grid_set_row_spacing(GTK_GRID(grid), 8); + gtk_grid_set_column_spacing(GTK_GRID(grid), 12); + state->designation = GTK_ENTRY(gtk_entry_new()); + gtk_entry_set_placeholder_text(state->designation, "Personne présumée liée aux comptes"); + state->name = GTK_ENTRY(gtk_entry_new()); + state->pseudonym = GTK_ENTRY(gtk_entry_new()); + state->status = GTK_DROP_DOWN(gtk_drop_down_new_from_strings(statuses)); + gtk_drop_down_set_selected(state->status, 1); + state->confidence = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(0, 100, 5)); + gtk_spin_button_set_value(state->confidence, 30); + labels = gtk_string_list_new(NULL); gtk_string_list_append(labels, "Aucune preuve associée"); + for (guint i = 0; records != NULL && i < records->len; i++) + { + EvidenceRecord *record = g_ptr_array_index((GPtrArray *) records, i); + const char *id = evidence_record_get_identifier(record); + const char *name = evidence_record_get_original_name(record); + if (id == NULL || name == NULL) continue; + gtk_string_list_append(labels, name); + g_ptr_array_add(state->evidence_identifiers, g_strdup(id)); + } + state->evidence = GTK_DROP_DOWN(gtk_drop_down_new(G_LIST_MODEL(labels), NULL)); + labels = NULL; + state->notes = GTK_TEXT_VIEW(gtk_text_view_new()); + gtk_text_view_set_wrap_mode(state->notes, GTK_WRAP_WORD_CHAR); + gtk_widget_set_size_request(GTK_WIDGET(state->notes), -1, 90); + create_person_dialog_add_row(GTK_GRID(grid), 0, "Désignation", GTK_WIDGET(state->designation)); + create_person_dialog_add_row(GTK_GRID(grid), 1, "Nom déclaré", GTK_WIDGET(state->name)); + create_person_dialog_add_row(GTK_GRID(grid), 2, "Pseudonyme", GTK_WIDGET(state->pseudonym)); + create_person_dialog_add_row(GTK_GRID(grid), 3, "Identification", GTK_WIDGET(state->status)); + create_person_dialog_add_row(GTK_GRID(grid), 4, "Confiance (%)", GTK_WIDGET(state->confidence)); + create_person_dialog_add_row(GTK_GRID(grid), 5, "Preuve associée", GTK_WIDGET(state->evidence)); + create_person_dialog_add_row(GTK_GRID(grid), 6, "Notes factuelles", GTK_WIDGET(state->notes)); + state->error = GTK_LABEL(gtk_label_new(NULL)); gtk_widget_set_visible(GTK_WIDGET(state->error), FALSE); + actions = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); gtk_widget_set_halign(actions, GTK_ALIGN_END); + cancel = gtk_button_new_with_label("Annuler"); create = gtk_button_new_with_label("Ajouter au graphe"); + gtk_widget_add_css_class(create, "suggested-action"); + gtk_box_append(GTK_BOX(actions), cancel); gtk_box_append(GTK_BOX(actions), create); + gtk_box_append(GTK_BOX(box), grid); gtk_box_append(GTK_BOX(box), GTK_WIDGET(state->error)); + gtk_box_append(GTK_BOX(box), actions); gtk_window_set_child(state->window, box); + g_signal_connect(state->window, "close-request", G_CALLBACK(create_person_dialog_on_close), state); + g_signal_connect(cancel, "clicked", G_CALLBACK(create_person_dialog_on_cancel), state); + g_signal_connect(create, "clicked", G_CALLBACK(create_person_dialog_on_create), state); + g_object_set_data_full(G_OBJECT(state->window), "person-dialog-state", state, + create_person_dialog_state_free); + gtk_window_present(state->window); return TRUE; +} +void create_person_dialog_result_free(CreatePersonDialogResult *result) +{ + if (result == NULL) return; + g_free(result->designation); g_free(result->declared_name); + g_free(result->pseudonym); g_free(result->status); g_free(result->notes); + g_free(result->evidence_identifier); g_free(result); +} +const PersonEntityInput *create_person_dialog_result_get_input( + const CreatePersonDialogResult *result) +{ + return result != NULL ? &result->input : NULL; +} diff --git a/src/views/main_window.c b/src/views/main_window.c index e8e8845..55b9638 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -60,6 +60,7 @@ struct MainWindow GtkWidget *open_investigation_button; GtkWidget *import_evidence_button; GtkWidget *add_social_account_button; + GtkWidget *add_person_button; GtkWidget *show_graph_button; GtkWidget *content_paned; GtkWidget *main_paned; @@ -90,6 +91,8 @@ struct MainWindow MainWindowAddSocialAccountCallback add_social_account_callback; gpointer add_social_account_user_data; + MainWindowAddPersonCallback add_person_callback; + gpointer add_person_user_data; MainWindowShowGraphCallback show_graph_callback; @@ -309,6 +312,13 @@ static void main_window_on_add_social_account_clicked( main_window->add_social_account_callback( main_window->add_social_account_user_data); } +/** @brief Relaie la demande d'ajout d'une personne. */ +static void main_window_on_add_person_clicked(GtkButton *button, gpointer data) +{ + MainWindow *main_window = data; (void) button; + if (main_window != NULL && main_window->add_person_callback != NULL) + main_window->add_person_callback(main_window->add_person_user_data); +} /** * @brief Transmet la demande de fermeture au contrôleur. @@ -615,6 +625,8 @@ MainWindow *main_window_new( "contact-new-symbolic", "Ajouter un compte social" ); + main_window->add_person_button = main_window_create_action_button( + "avatar-default-symbolic", "Ajouter une personne"); main_window->show_graph_button = main_window_create_action_button( @@ -648,6 +660,7 @@ MainWindow *main_window_new( ); gtk_widget_set_sensitive(main_window->add_social_account_button, FALSE); + gtk_widget_set_sensitive(main_window->add_person_button, FALSE); gtk_widget_set_sensitive( main_window->show_graph_button, @@ -671,6 +684,7 @@ MainWindow *main_window_new( gtk_box_append(GTK_BOX(main_window->action_bar), main_window->add_social_account_button); + gtk_box_append(GTK_BOX(main_window->action_bar), main_window->add_person_button); gtk_box_append( GTK_BOX(main_window->action_bar), @@ -716,6 +730,8 @@ MainWindow *main_window_new( g_signal_connect(main_window->add_social_account_button, "clicked", G_CALLBACK(main_window_on_add_social_account_clicked), main_window); + g_signal_connect(main_window->add_person_button, "clicked", + G_CALLBACK(main_window_on_add_person_clicked), main_window); g_signal_connect( main_window->show_graph_button, @@ -1450,6 +1466,13 @@ void main_window_set_add_social_account_callback( main_window->add_social_account_callback = callback; main_window->add_social_account_user_data = user_data; } +void main_window_set_add_person_callback(MainWindow *main_window, + MainWindowAddPersonCallback callback, gpointer user_data) +{ + if (main_window == NULL) return; + main_window->add_person_callback = callback; + main_window->add_person_user_data = user_data; +} void main_window_set_show_graph_callback( MainWindow *main_window, @@ -1493,6 +1516,11 @@ void main_window_set_add_social_account_enabled( return; gtk_widget_set_sensitive(main_window->add_social_account_button, enabled); } +void main_window_set_add_person_enabled(MainWindow *main_window, gboolean enabled) +{ + if (main_window == NULL || main_window->add_person_button == NULL) return; + gtk_widget_set_sensitive(main_window->add_person_button, enabled); +} void main_window_set_verify_evidence_callback( MainWindow *main_window, diff --git a/tests/test_person_entity_service.c b/tests/test_person_entity_service.c new file mode 100644 index 0000000..fd6ebfc --- /dev/null +++ b/tests/test_person_entity_service.c @@ -0,0 +1,48 @@ +/****************************************************************************** + * @file test_person_entity_service.c + * @brief Tests transactionnels de création d'une personne. + ******************************************************************************/ +#include "core/person_entity_service.h" +#include "database/database.h" +#include "dao/entity_dao.h" +#include "models/entity_record.h" +#include +#include +#include +#include +#include +/** @brief Vérifie la création d'une personne présumée. */ +static void test_person_entity_create(void) +{ + char *directory = NULL, *path = NULL, *identifier = NULL; + Database *database = NULL; + EntityDao *dao = NULL; + EntityRecord *record = NULL; + GError *error = NULL; + PersonEntityInput input = { + .designation = "Personne présumée liée aux comptes", + .declared_name = NULL, .pseudonym = "vendeur_test", + .identification_status = "suspected", + .notes = "Identité non confirmée.", .confidence = 30, + .evidence_identifier = NULL}; + directory = g_dir_make_tmp("labfy-person-service-test-XXXXXX", &error); + assert(directory != NULL && error == NULL); + path = g_build_filename(directory, "Enquete.sqlite", NULL); + assert(database_initialize(path, "Enquete_Personne", directory)); + database = database_open(path); assert(database != NULL); + assert(database_migrate_to_latest(database)); + assert(person_entity_service_create(database, &input, &identifier, &error)); + dao = entity_dao_new(database, &error); + record = entity_dao_find_by_identifier(dao, identifier, &error); + assert(record != NULL && error == NULL); + assert(strcmp(entity_record_get_type_identifier(record), "person") == 0); + assert(entity_record_get_confidence(record) == 30); + entity_record_free(record); entity_dao_free(dao); database_close(database); + assert(g_remove(path) == 0); assert(g_rmdir(directory) == 0); + g_free(identifier); g_free(path); g_free(directory); +} +int main(void) +{ + test_person_entity_create(); + puts("PersonEntityService : tous les tests sont valides."); return 0; +}