feat(entities): add person identity records

This commit is contained in:
grayTerminal-sh 2026-07-22 15:33:38 +02:00
parent 800dee9b76
commit 235c7e0540
10 changed files with 515 additions and 2 deletions

View file

@ -123,6 +123,7 @@ TEST_OSINT_EXECUTION_INTEGRITY := tests/test_osint_execution_integrity
TEST_EVIDENCE_RECLASSIFICATION := tests/test_evidence_reclassification TEST_EVIDENCE_RECLASSIFICATION := tests/test_evidence_reclassification
TEST_SOCIAL_ACCOUNT_SERVICE := tests/test_social_account_service TEST_SOCIAL_ACCOUNT_SERVICE := tests/test_social_account_service
TEST_SOCIAL_PLATFORM := tests/test_social_platform TEST_SOCIAL_PLATFORM := tests/test_social_platform
TEST_PERSON_ENTITY_SERVICE := tests/test_person_entity_service
all: $(TARGET) all: $(TARGET)
@ -629,6 +630,19 @@ $(TEST_SOCIAL_PLATFORM): \
src/models/social_platform.c src/models/social_platform.c
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) $(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): \ $(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \
tests/test_investigation_graph_load_task.c \ tests/test_investigation_graph_load_task.c \
src/core/investigation_graph_load_task.c \ src/core/investigation_graph_load_task.c \
@ -706,7 +720,8 @@ test: \
$(TEST_OSINT_EXECUTION_INTEGRITY) \ $(TEST_OSINT_EXECUTION_INTEGRITY) \
$(TEST_EVIDENCE_RECLASSIFICATION) \ $(TEST_EVIDENCE_RECLASSIFICATION) \
$(TEST_SOCIAL_ACCOUNT_SERVICE) \ $(TEST_SOCIAL_ACCOUNT_SERVICE) \
$(TEST_SOCIAL_PLATFORM) $(TEST_SOCIAL_PLATFORM) \
$(TEST_PERSON_ENTITY_SERVICE)
@echo "Exécution des tests..." @echo "Exécution des tests..."
@./$(TEST_NODE) @./$(TEST_NODE)
@./$(TEST_TREE_MODEL) @./$(TEST_TREE_MODEL)
@ -764,6 +779,7 @@ test: \
@$(TEST_EVIDENCE_RECLASSIFICATION) @$(TEST_EVIDENCE_RECLASSIFICATION)
@$(TEST_SOCIAL_ACCOUNT_SERVICE) @$(TEST_SOCIAL_ACCOUNT_SERVICE)
@$(TEST_SOCIAL_PLATFORM) @$(TEST_SOCIAL_PLATFORM)
@$(TEST_PERSON_ENTITY_SERVICE)
@echo "Tous les tests sont valides." @echo "Tous les tests sont valides."
%.o: %.c %.o: %.c
@ -827,7 +843,8 @@ clean:
$(TEST_OSINT_EXECUTION_INTEGRITY) \ $(TEST_OSINT_EXECUTION_INTEGRITY) \
$(TEST_EVIDENCE_RECLASSIFICATION) \ $(TEST_EVIDENCE_RECLASSIFICATION) \
$(TEST_SOCIAL_ACCOUNT_SERVICE) \ $(TEST_SOCIAL_ACCOUNT_SERVICE) \
$(TEST_SOCIAL_PLATFORM) $(TEST_SOCIAL_PLATFORM) \
$(TEST_PERSON_ENTITY_SERVICE)
-include $(DEP) -include $(DEP)

View file

@ -163,6 +163,8 @@ Le socle actuel comprend notamment :
première observation, état, notes et rattachement à une preuve ; première observation, état, notes et rattachement à une preuve ;
- pictogrammes vectoriels des plateformes sociales dans les nœuds du graphe, - pictogrammes vectoriels des plateformes sociales dans les nœuds du graphe,
conservant leur lisibilité pendant le zoom ; 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, - 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 ; 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 - vérification manuelle de l'intégrité des sorties OSINT enregistrées, sans

View file

@ -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 <glib.h>
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

View file

@ -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 <gtk/gtk.h>
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 é 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

View file

@ -54,6 +54,8 @@ typedef void (*MainWindowImportEvidenceCallback)(
typedef void (*MainWindowAddSocialAccountCallback)( typedef void (*MainWindowAddSocialAccountCallback)(
gpointer user_data 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. * @brief Callback appelé lorsque l'utilisateur revient au graphe.
@ -547,6 +549,9 @@ void main_window_set_add_social_account_callback(
MainWindowAddSocialAccountCallback callback, MainWindowAddSocialAccountCallback callback,
gpointer user_data 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 ». * @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, MainWindow *main_window,
gboolean enabled 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. * @brief Libère les ressources de la fenêtre.

View file

@ -48,8 +48,10 @@
#include "core/evidence_integrity_verifier.h" #include "core/evidence_integrity_verifier.h"
#include "core/relation_service.h" #include "core/relation_service.h"
#include "core/social_account_service.h" #include "core/social_account_service.h"
#include "core/person_entity_service.h"
#include "database/database.h" #include "database/database.h"
#include "views/create_social_account_dialog.h" #include "views/create_social_account_dialog.h"
#include "views/create_person_dialog.h"
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <errno.h> #include <errno.h>
@ -2273,6 +2275,7 @@ static gboolean application_install_session(
application->main_window, application->main_window,
TRUE TRUE
); );
main_window_set_add_person_enabled(application->main_window, TRUE);
application_start_graph_loading( application_start_graph_loading(
application, application,
@ -4172,6 +4175,55 @@ static void application_on_add_social_account_requested(gpointer user_data)
g_clear_error(&error); 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. * @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_on_add_social_account_requested,
application application
); );
main_window_set_add_person_callback(application->main_window,
application_on_add_person_requested, application);
main_window_set_import_evidence_enabled( main_window_set_import_evidence_enabled(
application->main_window, application->main_window,
@ -6243,6 +6297,8 @@ static void application_on_activate(
application->main_window, application->main_window,
application->session != NULL application->session != NULL
); );
main_window_set_add_person_enabled(application->main_window,
application->session != NULL);
main_window_set_quit_callback( main_window_set_quit_callback(
application->main_window, application->main_window,

View file

@ -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;
}

View file

@ -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;
}

View file

@ -60,6 +60,7 @@ struct MainWindow
GtkWidget *open_investigation_button; GtkWidget *open_investigation_button;
GtkWidget *import_evidence_button; GtkWidget *import_evidence_button;
GtkWidget *add_social_account_button; GtkWidget *add_social_account_button;
GtkWidget *add_person_button;
GtkWidget *show_graph_button; GtkWidget *show_graph_button;
GtkWidget *content_paned; GtkWidget *content_paned;
GtkWidget *main_paned; GtkWidget *main_paned;
@ -90,6 +91,8 @@ struct MainWindow
MainWindowAddSocialAccountCallback add_social_account_callback; MainWindowAddSocialAccountCallback add_social_account_callback;
gpointer add_social_account_user_data; gpointer add_social_account_user_data;
MainWindowAddPersonCallback add_person_callback;
gpointer add_person_user_data;
MainWindowShowGraphCallback MainWindowShowGraphCallback
show_graph_callback; 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_callback(
main_window->add_social_account_user_data); 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. * @brief Transmet la demande de fermeture au contrôleur.
@ -615,6 +625,8 @@ MainWindow *main_window_new(
"contact-new-symbolic", "contact-new-symbolic",
"Ajouter un compte social" "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->show_graph_button =
main_window_create_action_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_social_account_button, FALSE);
gtk_widget_set_sensitive(main_window->add_person_button, FALSE);
gtk_widget_set_sensitive( gtk_widget_set_sensitive(
main_window->show_graph_button, main_window->show_graph_button,
@ -671,6 +684,7 @@ MainWindow *main_window_new(
gtk_box_append(GTK_BOX(main_window->action_bar), gtk_box_append(GTK_BOX(main_window->action_bar),
main_window->add_social_account_button); 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_append(
GTK_BOX(main_window->action_bar), 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_signal_connect(main_window->add_social_account_button, "clicked",
G_CALLBACK(main_window_on_add_social_account_clicked), main_window); 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( g_signal_connect(
main_window->show_graph_button, 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_callback = callback;
main_window->add_social_account_user_data = user_data; 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( void main_window_set_show_graph_callback(
MainWindow *main_window, MainWindow *main_window,
@ -1493,6 +1516,11 @@ void main_window_set_add_social_account_enabled(
return; return;
gtk_widget_set_sensitive(main_window->add_social_account_button, enabled); 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( void main_window_set_verify_evidence_callback(
MainWindow *main_window, MainWindow *main_window,

View file

@ -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 <assert.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
/** @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;
}