feat(entities): add person identity records
This commit is contained in:
parent
800dee9b76
commit
235c7e0540
10 changed files with 515 additions and 2 deletions
21
Makefile
21
Makefile
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
32
include/core/person_entity_service.h
Normal file
32
include/core/person_entity_service.h
Normal 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
|
||||||
33
include/views/create_person_dialog.h
Normal file
33
include/views/create_person_dialog.h
Normal 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 é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
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
86
src/core/person_entity_service.c
Normal file
86
src/core/person_entity_service.c
Normal 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;
|
||||||
|
}
|
||||||
204
src/views/create_person_dialog.c
Normal file
204
src/views/create_person_dialog.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
48
tests/test_person_entity_service.c
Normal file
48
tests/test_person_entity_service.c
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue