feat(person): add investigation roles and graph colors
This commit is contained in:
parent
8d5a5910db
commit
205c65f275
22 changed files with 478 additions and 8 deletions
|
|
@ -165,6 +165,8 @@ Le socle actuel comprend notamment :
|
||||||
conservant leur lisibilité pendant le zoom ;
|
conservant leur lisibilité pendant le zoom ;
|
||||||
- création de personnes observées avec statut d'identification, confiance,
|
- création de personnes observées avec statut d'identification, confiance,
|
||||||
notes factuelles et rattachement facultatif à une preuve ;
|
notes factuelles et rattachement facultatif à une preuve ;
|
||||||
|
- catégories d'enquête des personnes en SQLite V5, modifiables depuis leur
|
||||||
|
fiche et représentées par une couleur et un libellé dans le graphe ;
|
||||||
- relations représentées par des flèches directes à libellé cliquable, sans
|
- relations représentées par des flèches directes à libellé cliquable, sans
|
||||||
ajouter de faux nœud visuel entre les entités ;
|
ajouter de faux nœud visuel entre les entités ;
|
||||||
- historique OSINT contextuel en lecture seule avec détail des exécutions,
|
- historique OSINT contextuel en lecture seule avec détail des exécutions,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Labfy Investigation
|
* Labfy Investigation
|
||||||
*
|
*
|
||||||
* Extensions idempotentes du schéma SQLite courant V4
|
* Extensions idempotentes du schéma SQLite courant V5
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
30
database/schema_v5.sql
Normal file
30
database/schema_v5.sql
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* Labfy Investigation
|
||||||
|
*
|
||||||
|
* Migration du schéma SQLite V4 vers V5 : rôles d'enquête des personnes
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
CREATE TABLE person_roles
|
||||||
|
(
|
||||||
|
entity_id TEXT PRIMARY KEY,
|
||||||
|
role TEXT NOT NULL DEFAULT 'uncategorized',
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (entity_id)
|
||||||
|
REFERENCES entites(id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
|
||||||
|
CHECK (role IN
|
||||||
|
(
|
||||||
|
'uncategorized',
|
||||||
|
'alleged_scammer',
|
||||||
|
'victim',
|
||||||
|
'witness',
|
||||||
|
'suspect',
|
||||||
|
'related_person'
|
||||||
|
)),
|
||||||
|
CHECK (length(updated_at) = 20)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_person_roles_role ON person_roles(role);
|
||||||
|
|
@ -1161,6 +1161,17 @@ compte via `preuve_entites`. La création des deux lignes et de cette liaison
|
||||||
est transactionnelle : aucun nœud incomplet n'est conservé si une étape
|
est transactionnelle : aucun nœud incomplet n'est conservé si une étape
|
||||||
échoue.
|
échoue.
|
||||||
|
|
||||||
|
### Rôles d'enquête des personnes — schéma V5
|
||||||
|
|
||||||
|
La table `person_roles` associe une entité de type personne à une catégorie
|
||||||
|
d'enquête contrôlée : scammer présumé, victime, témoin, suspect, personne liée
|
||||||
|
ou non catégorisée. La clé étrangère avec suppression en cascade évite les
|
||||||
|
rôles orphelins. Le code stable est persisté ; le libellé et la couleur restent
|
||||||
|
des choix de présentation.
|
||||||
|
|
||||||
|
Une personne sans ligne dans cette table est toujours interprétée comme non
|
||||||
|
catégorisée, ce qui garantit la compatibilité avec les enquêtes antérieures.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 6. Tables de liaison
|
# 6. Tables de liaison
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#ifndef LABFY_INVESTIGATION_PERSON_ENTITY_SERVICE_H
|
#ifndef LABFY_INVESTIGATION_PERSON_ENTITY_SERVICE_H
|
||||||
#define LABFY_INVESTIGATION_PERSON_ENTITY_SERVICE_H
|
#define LABFY_INVESTIGATION_PERSON_ENTITY_SERVICE_H
|
||||||
#include "database/database.h"
|
#include "database/database.h"
|
||||||
|
#include "models/entity_record.h"
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
/** @brief Données factuelles utilisées pour créer une personne. */
|
/** @brief Données factuelles utilisées pour créer une personne. */
|
||||||
|
|
@ -28,5 +29,8 @@ typedef struct
|
||||||
*/
|
*/
|
||||||
gboolean person_entity_service_create(Database *database,
|
gboolean person_entity_service_create(Database *database,
|
||||||
const PersonEntityInput *input, char **out_identifier, GError **error);
|
const PersonEntityInput *input, char **out_identifier, GError **error);
|
||||||
|
/** @brief Enregistre le rôle d'une personne existante. */
|
||||||
|
gboolean person_entity_service_update_role(Database *database,
|
||||||
|
const char *entity_identifier, PersonRole role, GError **error);
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,9 @@ bool schema_install_v4(
|
||||||
Database *database
|
Database *database
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** @brief Installe la migration des rôles de personnes du schéma V5. */
|
||||||
|
bool schema_install_v5(Database *database);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Garantit la présence des extensions du schéma courant V2.
|
* @brief Garantit la présence des extensions du schéma courant V2.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,17 @@ typedef enum
|
||||||
ENTITY_STATUS_DELETED
|
ENTITY_STATUS_DELETED
|
||||||
} EntityStatus;
|
} EntityStatus;
|
||||||
|
|
||||||
|
/** @brief Rôle d'une personne dans l'enquête. */
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
PERSON_ROLE_UNCATEGORIZED,
|
||||||
|
PERSON_ROLE_ALLEGED_SCAMMER,
|
||||||
|
PERSON_ROLE_VICTIM,
|
||||||
|
PERSON_ROLE_WITNESS,
|
||||||
|
PERSON_ROLE_SUSPECT,
|
||||||
|
PERSON_ROLE_RELATED_PERSON
|
||||||
|
} PersonRole;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Codes d'erreur produits par EntityRecord.
|
* @brief Codes d'erreur produits par EntityRecord.
|
||||||
*/
|
*/
|
||||||
|
|
@ -170,6 +181,18 @@ EntityStatus entity_record_get_status(
|
||||||
const EntityRecord *entity_record
|
const EntityRecord *entity_record
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** @brief Affecte un rôle d'enquête à une entité de type personne. */
|
||||||
|
gboolean entity_record_set_person_role(EntityRecord *entity_record,
|
||||||
|
PersonRole role);
|
||||||
|
/** @brief Retourne le rôle d'enquête, non catégorisé par défaut. */
|
||||||
|
PersonRole entity_record_get_person_role(const EntityRecord *entity_record);
|
||||||
|
/** @brief Convertit un rôle en code stable pour SQLite. */
|
||||||
|
const char *person_role_to_code(PersonRole role);
|
||||||
|
/** @brief Convertit un code SQLite en rôle. */
|
||||||
|
PersonRole person_role_from_code(const char *code);
|
||||||
|
/** @brief Retourne le libellé français d'un rôle. */
|
||||||
|
const char *person_role_get_label(PersonRole role);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,10 @@ typedef void (*MainWindowEditRelationCallback)(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** @brief Callback appelé pour catégoriser une personne. */
|
||||||
|
typedef void (*MainWindowPersonRoleCallback)(const char *entity_identifier,
|
||||||
|
PersonRole role, gpointer user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Callback appelé lors du déclenchement d'une action OSINT.
|
* @brief Callback appelé lors du déclenchement d'une action OSINT.
|
||||||
*
|
*
|
||||||
|
|
@ -240,6 +244,10 @@ void main_window_set_edit_relation_callback(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** @brief Définit le callback de catégorisation d'une personne. */
|
||||||
|
void main_window_set_person_role_callback(MainWindow *main_window,
|
||||||
|
MainWindowPersonRoleCallback callback, gpointer user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Définit le callback de déclenchement des actions OSINT.
|
* @brief Définit le callback de déclenchement des actions OSINT.
|
||||||
*
|
*
|
||||||
|
|
@ -437,6 +445,9 @@ gboolean main_window_select_graph_relation(
|
||||||
MainWindow *main_window,
|
MainWindow *main_window,
|
||||||
const char *relation_identifier
|
const char *relation_identifier
|
||||||
);
|
);
|
||||||
|
/** @brief Sélectionne une entité dans le graphe affiché. */
|
||||||
|
gboolean main_window_select_graph_entity(MainWindow *main_window,
|
||||||
|
const char *entity_identifier);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Affiche une erreur de chargement du graphe.
|
* @brief Affiche une erreur de chargement du graphe.
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#define LABFY_INVESTIGATION_ENTITY_DETAILS_PANEL_H
|
#define LABFY_INVESTIGATION_ENTITY_DETAILS_PANEL_H
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
#include "models/entity_record.h"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
|
@ -45,6 +46,13 @@ typedef void (*EntityDetailsPanelAddRelationCallback)(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** @brief Callback appelé lors du changement de catégorie d'une personne. */
|
||||||
|
typedef void (*EntityDetailsPanelPersonRoleCallback)(
|
||||||
|
const char *entity_identifier,
|
||||||
|
PersonRole role,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Crée un volet de détails fermé.
|
* @brief Crée un volet de détails fermé.
|
||||||
*
|
*
|
||||||
|
|
@ -120,6 +128,13 @@ void entity_details_panel_set_add_relation_callback(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** @brief Définit le callback de changement de catégorie d'une personne. */
|
||||||
|
void entity_details_panel_set_person_role_callback(
|
||||||
|
EntityDetailsPanel *details_panel,
|
||||||
|
EntityDetailsPanelPersonRoleCallback callback,
|
||||||
|
gpointer user_data
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Indique si le volet est actuellement ouvert.
|
* @brief Indique si le volet est actuellement ouvert.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include "core/investigation_node.h"
|
#include "core/investigation_node.h"
|
||||||
#include "models/evidence_record.h"
|
#include "models/evidence_record.h"
|
||||||
|
#include "models/entity_record.h"
|
||||||
#include "models/osint_action_catalog.h"
|
#include "models/osint_action_catalog.h"
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|
@ -84,6 +85,10 @@ typedef void (*WorkspaceEditRelationCallback)(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** @brief Callback appelé pour catégoriser une personne. */
|
||||||
|
typedef void (*WorkspacePersonRoleCallback)(const char *entity_identifier,
|
||||||
|
PersonRole role, gpointer user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Callback appelé lors du déclenchement d'une action OSINT.
|
* @brief Callback appelé lors du déclenchement d'une action OSINT.
|
||||||
*
|
*
|
||||||
|
|
@ -301,6 +306,10 @@ void workspace_set_edit_relation_callback(
|
||||||
gpointer user_data
|
gpointer user_data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** @brief Définit le callback de catégorisation d'une personne. */
|
||||||
|
void workspace_set_person_role_callback(Workspace *workspace,
|
||||||
|
WorkspacePersonRoleCallback callback, gpointer user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Affiche l'état de chargement du graphe.
|
* @brief Affiche l'état de chargement du graphe.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ struct Application
|
||||||
|
|
||||||
char *selected_evidence_identifier;
|
char *selected_evidence_identifier;
|
||||||
char *pending_relation_selection_identifier;
|
char *pending_relation_selection_identifier;
|
||||||
|
char *pending_entity_selection_identifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -747,6 +748,13 @@ static void application_on_graph_loaded(
|
||||||
g_clear_pointer(&application->pending_relation_selection_identifier,
|
g_clear_pointer(&application->pending_relation_selection_identifier,
|
||||||
g_free);
|
g_free);
|
||||||
}
|
}
|
||||||
|
if (application->pending_entity_selection_identifier != NULL)
|
||||||
|
{
|
||||||
|
main_window_select_graph_entity(application->main_window,
|
||||||
|
application->pending_entity_selection_identifier);
|
||||||
|
g_clear_pointer(&application->pending_entity_selection_identifier,
|
||||||
|
g_free);
|
||||||
|
}
|
||||||
|
|
||||||
status_message =
|
status_message =
|
||||||
g_strdup_printf(
|
g_strdup_printf(
|
||||||
|
|
@ -4935,6 +4943,35 @@ typedef struct
|
||||||
char *relation_identifier;
|
char *relation_identifier;
|
||||||
} ApplicationEditRelationContext;
|
} ApplicationEditRelationContext;
|
||||||
|
|
||||||
|
/** @brief Enregistre la catégorie choisie depuis la fiche personne. */
|
||||||
|
static void application_on_person_role_changed(const char *entity_identifier,
|
||||||
|
PersonRole role, gpointer user_data)
|
||||||
|
{
|
||||||
|
Application *application = user_data;
|
||||||
|
Database *database = NULL;
|
||||||
|
const InvestigationProject *project = NULL;
|
||||||
|
GError *error = NULL;
|
||||||
|
if (application == NULL || application->session == NULL) return;
|
||||||
|
database = investigation_session_get_database(application->session);
|
||||||
|
project = investigation_session_get_project(application->session);
|
||||||
|
if (!person_entity_service_update_role(database, entity_identifier,
|
||||||
|
role, &error))
|
||||||
|
{
|
||||||
|
application_present_error(application, "Catégorie non enregistrée",
|
||||||
|
error != NULL ? error->message :
|
||||||
|
"Impossible de catégoriser cette personne.");
|
||||||
|
g_clear_error(&error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_free(application->pending_entity_selection_identifier);
|
||||||
|
application->pending_entity_selection_identifier =
|
||||||
|
g_strdup(entity_identifier);
|
||||||
|
main_window_set_status(application->main_window,
|
||||||
|
"Catégorie enregistrée. Actualisation du graphe…");
|
||||||
|
application_start_graph_loading(application,
|
||||||
|
investigation_project_get_database_path(project));
|
||||||
|
}
|
||||||
|
|
||||||
/** @brief Libère le contexte d'édition d'une relation. */
|
/** @brief Libère le contexte d'édition d'une relation. */
|
||||||
static void application_edit_relation_context_free(
|
static void application_edit_relation_context_free(
|
||||||
ApplicationEditRelationContext *context)
|
ApplicationEditRelationContext *context)
|
||||||
|
|
@ -6476,6 +6513,8 @@ static void application_on_activate(
|
||||||
|
|
||||||
main_window_set_edit_relation_callback(application->main_window,
|
main_window_set_edit_relation_callback(application->main_window,
|
||||||
application_on_edit_relation_requested, application);
|
application_on_edit_relation_requested, application);
|
||||||
|
main_window_set_person_role_callback(application->main_window,
|
||||||
|
application_on_person_role_changed, application);
|
||||||
|
|
||||||
main_window_set_osint_action_callback(
|
main_window_set_osint_action_callback(
|
||||||
application->main_window,
|
application->main_window,
|
||||||
|
|
@ -6752,6 +6791,7 @@ void application_free(
|
||||||
);
|
);
|
||||||
g_clear_pointer(&application->pending_relation_selection_identifier,
|
g_clear_pointer(&application->pending_relation_selection_identifier,
|
||||||
g_free);
|
g_free);
|
||||||
|
g_clear_pointer(&application->pending_entity_selection_identifier, g_free);
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
application
|
application
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "dao/entity_dao.h"
|
#include "dao/entity_dao.h"
|
||||||
#include "dao/evidence_entity_dao.h"
|
#include "dao/evidence_entity_dao.h"
|
||||||
#include "database/transaction.h"
|
#include "database/transaction.h"
|
||||||
|
#include "database/statement.h"
|
||||||
#include "models/entity_record.h"
|
#include "models/entity_record.h"
|
||||||
|
|
||||||
/** @brief Indique si le niveau d'identification est reconnu. */
|
/** @brief Indique si le niveau d'identification est reconnu. */
|
||||||
|
|
@ -16,6 +17,58 @@ static gboolean person_entity_service_status_valid(const char *status)
|
||||||
g_strcmp0(status, "confirmed") == 0;
|
g_strcmp0(status, "confirmed") == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean person_entity_service_update_role(Database *database,
|
||||||
|
const char *entity_identifier, PersonRole role, GError **error)
|
||||||
|
{
|
||||||
|
static const char validate_sql[] =
|
||||||
|
"SELECT 1 FROM entites e JOIN types_entite t ON t.id=e.type_id "
|
||||||
|
"WHERE e.id=? AND t.code='person';";
|
||||||
|
static const char upsert_sql[] =
|
||||||
|
"INSERT INTO person_roles(entity_id, role, updated_at) VALUES(?,?,?) "
|
||||||
|
"ON CONFLICT(entity_id) DO UPDATE SET role=excluded.role, "
|
||||||
|
"updated_at=excluded.updated_at;";
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
GDateTime *now = NULL;
|
||||||
|
char *timestamp = NULL;
|
||||||
|
const char *role_code = person_role_to_code(role);
|
||||||
|
gboolean active = FALSE, success = FALSE;
|
||||||
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
||||||
|
if (database == NULL || entity_identifier == NULL ||
|
||||||
|
!g_uuid_string_is_valid(entity_identifier) || role_code == NULL)
|
||||||
|
goto invalid;
|
||||||
|
statement = database_statement_prepare(database, validate_sql);
|
||||||
|
if (statement == NULL || !database_statement_bind_text(statement, 1,
|
||||||
|
entity_identifier) || database_statement_step(statement) !=
|
||||||
|
DATABASE_STATEMENT_STEP_ROW)
|
||||||
|
goto invalid;
|
||||||
|
database_statement_finalize(statement); statement = NULL;
|
||||||
|
now = g_date_time_new_now_utc();
|
||||||
|
timestamp = now != NULL ? g_date_time_format(now,
|
||||||
|
"%Y-%m-%dT%H:%M:%SZ") : NULL;
|
||||||
|
if (timestamp == NULL || !database_transaction_begin(database))
|
||||||
|
goto cleanup;
|
||||||
|
active = TRUE;
|
||||||
|
statement = database_statement_prepare(database, upsert_sql);
|
||||||
|
if (statement == NULL ||
|
||||||
|
!database_statement_bind_text(statement, 1, entity_identifier) ||
|
||||||
|
!database_statement_bind_text(statement, 2, role_code) ||
|
||||||
|
!database_statement_bind_text(statement, 3, timestamp) ||
|
||||||
|
database_statement_step(statement) != DATABASE_STATEMENT_STEP_DONE)
|
||||||
|
goto cleanup;
|
||||||
|
database_statement_finalize(statement); statement = NULL;
|
||||||
|
if (!database_transaction_commit(database)) goto cleanup;
|
||||||
|
active = FALSE; success = TRUE; goto cleanup;
|
||||||
|
invalid:
|
||||||
|
g_set_error_literal(error, g_quark_from_static_string(
|
||||||
|
"person-entity-service-error"), 3,
|
||||||
|
"La personne ou la catégorie sélectionnée est invalide.");
|
||||||
|
cleanup:
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
if (!success && active) database_transaction_rollback(database);
|
||||||
|
g_free(timestamp); g_clear_pointer(&now, g_date_time_unref);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean person_entity_service_create(Database *database,
|
gboolean person_entity_service_create(Database *database,
|
||||||
const PersonEntityInput *input, char **out_identifier, GError **error)
|
const PersonEntityInput *input, char **out_identifier, GError **error)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,12 @@ static const char *const entity_dao_find_by_identifier_sql =
|
||||||
" entites.confiance,"
|
" entites.confiance,"
|
||||||
" entites.created_at,"
|
" entites.created_at,"
|
||||||
" entites.updated_at,"
|
" entites.updated_at,"
|
||||||
" entites.status "
|
" entites.status,"
|
||||||
|
" person_roles.role "
|
||||||
"FROM entites "
|
"FROM entites "
|
||||||
"LEFT JOIN types_entite "
|
"LEFT JOIN types_entite "
|
||||||
"ON types_entite.id = entites.type_id "
|
"ON types_entite.id = entites.type_id "
|
||||||
|
"LEFT JOIN person_roles ON person_roles.entity_id = entites.id "
|
||||||
"WHERE entites.id = ?;";
|
"WHERE entites.id = ?;";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -87,10 +89,12 @@ static const char *const entity_dao_list_all_sql =
|
||||||
" entites.confiance,"
|
" entites.confiance,"
|
||||||
" entites.created_at,"
|
" entites.created_at,"
|
||||||
" entites.updated_at,"
|
" entites.updated_at,"
|
||||||
" entites.status "
|
" entites.status,"
|
||||||
|
" person_roles.role "
|
||||||
"FROM entites "
|
"FROM entites "
|
||||||
"LEFT JOIN types_entite "
|
"LEFT JOIN types_entite "
|
||||||
"ON types_entite.id = entites.type_id "
|
"ON types_entite.id = entites.type_id "
|
||||||
|
"LEFT JOIN person_roles ON person_roles.entity_id = entites.id "
|
||||||
"ORDER BY "
|
"ORDER BY "
|
||||||
" entites.created_at ASC,"
|
" entites.created_at ASC,"
|
||||||
" entites.id ASC;";
|
" entites.id ASC;";
|
||||||
|
|
@ -674,6 +678,9 @@ static EntityRecord *entity_dao_read_current_record(
|
||||||
char *status_text =
|
char *status_text =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
char *person_role_text =
|
||||||
|
NULL;
|
||||||
|
|
||||||
int64_t confidence_value =
|
int64_t confidence_value =
|
||||||
-1;
|
-1;
|
||||||
|
|
||||||
|
|
@ -740,6 +747,11 @@ static EntityRecord *entity_dao_read_current_record(
|
||||||
statement,
|
statement,
|
||||||
8,
|
8,
|
||||||
&status_text
|
&status_text
|
||||||
|
) ||
|
||||||
|
!database_statement_column_text(
|
||||||
|
statement,
|
||||||
|
9,
|
||||||
|
&person_role_text
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
entity_dao_set_error_literal(
|
entity_dao_set_error_literal(
|
||||||
|
|
@ -812,6 +824,11 @@ static EntityRecord *entity_dao_read_current_record(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (person_role_text != NULL)
|
||||||
|
{
|
||||||
|
entity_record_set_person_role(entity_record,
|
||||||
|
person_role_from_code(person_role_text));
|
||||||
|
}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
|
||||||
|
|
@ -823,6 +840,8 @@ cleanup:
|
||||||
status_text
|
status_text
|
||||||
);
|
);
|
||||||
|
|
||||||
|
g_free(person_role_text);
|
||||||
|
|
||||||
g_free(
|
g_free(
|
||||||
updated_at
|
updated_at
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@
|
||||||
/**
|
/**
|
||||||
* @brief Version actuelle du schéma SQLite.
|
* @brief Version actuelle du schéma SQLite.
|
||||||
*/
|
*/
|
||||||
#define DATABASE_SCHEMA_VERSION_CURRENT 4
|
#define DATABASE_SCHEMA_VERSION_CURRENT 5
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Version actuelle sous forme textuelle pour metadata.
|
* @brief Version actuelle sous forme textuelle pour metadata.
|
||||||
*/
|
*/
|
||||||
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "4"
|
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "5"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Nom de l'application enregistré dans les métadonnées.
|
* @brief Nom de l'application enregistré dans les métadonnées.
|
||||||
|
|
@ -712,6 +712,24 @@ rollback:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Applique atomiquement la migration du schéma V4 vers V5. */
|
||||||
|
static bool database_migrate_v4_to_v5(Database *database)
|
||||||
|
{
|
||||||
|
bool transaction_started = false;
|
||||||
|
if (database == NULL || !database_transaction_begin(database))
|
||||||
|
return false;
|
||||||
|
transaction_started = true;
|
||||||
|
if (!schema_install_v5(database) ||
|
||||||
|
!database_update_schema_version(database, "5") ||
|
||||||
|
!database_transaction_commit(database))
|
||||||
|
goto rollback;
|
||||||
|
return true;
|
||||||
|
rollback:
|
||||||
|
if (transaction_started && !database_transaction_rollback(database))
|
||||||
|
g_warning("Impossible d’annuler la migration SQLite V4 vers V5.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Garantit atomiquement la présence des extensions du schéma courant.
|
* @brief Garantit atomiquement la présence des extensions du schéma courant.
|
||||||
*/
|
*/
|
||||||
|
|
@ -910,6 +928,12 @@ bool database_migrate_to_latest(
|
||||||
schema_version = 4;
|
schema_version = 4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
if (!database_migrate_v4_to_v5(database))
|
||||||
|
return false;
|
||||||
|
schema_version = 5;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
database_set_error(
|
database_set_error(
|
||||||
database,
|
database,
|
||||||
|
|
@ -1031,6 +1055,11 @@ bool database_initialize(
|
||||||
goto rollback;
|
goto rollback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!schema_install_v5(database))
|
||||||
|
{
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
if (!schema_ensure_current(
|
if (!schema_ensure_current(
|
||||||
database
|
database
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,12 @@ bool schema_install_v4(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool schema_install_v5(Database *database)
|
||||||
|
{
|
||||||
|
return schema_execute_file(database, "database/schema_v5.sql",
|
||||||
|
"la migration SQLite V5");
|
||||||
|
}
|
||||||
|
|
||||||
bool schema_ensure_current(
|
bool schema_ensure_current(
|
||||||
Database *database
|
Database *database
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ struct EntityRecord
|
||||||
char *updated_at;
|
char *updated_at;
|
||||||
|
|
||||||
EntityStatus status;
|
EntityStatus status;
|
||||||
|
PersonRole person_role;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -582,3 +583,62 @@ EntityStatus entity_record_get_status(
|
||||||
? entity_record->status
|
? entity_record->status
|
||||||
: ENTITY_STATUS_UNKNOWN;
|
: ENTITY_STATUS_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean entity_record_set_person_role(EntityRecord *entity_record,
|
||||||
|
PersonRole role)
|
||||||
|
{
|
||||||
|
if (entity_record == NULL ||
|
||||||
|
g_strcmp0(entity_record->type_identifier, "person") != 0 ||
|
||||||
|
person_role_to_code(role) == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
entity_record->person_role = role;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PersonRole entity_record_get_person_role(const EntityRecord *entity_record)
|
||||||
|
{
|
||||||
|
return entity_record != NULL ? entity_record->person_role :
|
||||||
|
PERSON_ROLE_UNCATEGORIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *person_role_to_code(PersonRole role)
|
||||||
|
{
|
||||||
|
switch (role)
|
||||||
|
{
|
||||||
|
case PERSON_ROLE_UNCATEGORIZED: return "uncategorized";
|
||||||
|
case PERSON_ROLE_ALLEGED_SCAMMER: return "alleged_scammer";
|
||||||
|
case PERSON_ROLE_VICTIM: return "victim";
|
||||||
|
case PERSON_ROLE_WITNESS: return "witness";
|
||||||
|
case PERSON_ROLE_SUSPECT: return "suspect";
|
||||||
|
case PERSON_ROLE_RELATED_PERSON: return "related_person";
|
||||||
|
default: return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PersonRole person_role_from_code(const char *code)
|
||||||
|
{
|
||||||
|
if (g_strcmp0(code, "alleged_scammer") == 0)
|
||||||
|
return PERSON_ROLE_ALLEGED_SCAMMER;
|
||||||
|
if (g_strcmp0(code, "victim") == 0) return PERSON_ROLE_VICTIM;
|
||||||
|
if (g_strcmp0(code, "witness") == 0) return PERSON_ROLE_WITNESS;
|
||||||
|
if (g_strcmp0(code, "suspect") == 0) return PERSON_ROLE_SUSPECT;
|
||||||
|
if (g_strcmp0(code, "related_person") == 0)
|
||||||
|
return PERSON_ROLE_RELATED_PERSON;
|
||||||
|
return PERSON_ROLE_UNCATEGORIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *person_role_get_label(PersonRole role)
|
||||||
|
{
|
||||||
|
switch (role)
|
||||||
|
{
|
||||||
|
case PERSON_ROLE_ALLEGED_SCAMMER: return "Scammer présumé";
|
||||||
|
case PERSON_ROLE_VICTIM: return "Victime";
|
||||||
|
case PERSON_ROLE_WITNESS: return "Témoin";
|
||||||
|
case PERSON_ROLE_SUSPECT: return "Suspect";
|
||||||
|
case PERSON_ROLE_RELATED_PERSON: return "Personne liée";
|
||||||
|
case PERSON_ROLE_UNCATEGORIZED:
|
||||||
|
default: return "Non catégorisé";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,8 @@ struct MainWindow
|
||||||
|
|
||||||
MainWindowEditRelationCallback edit_relation_callback;
|
MainWindowEditRelationCallback edit_relation_callback;
|
||||||
gpointer edit_relation_user_data;
|
gpointer edit_relation_user_data;
|
||||||
|
MainWindowPersonRoleCallback person_role_callback;
|
||||||
|
gpointer person_role_user_data;
|
||||||
|
|
||||||
MainWindowOsintActionCallback
|
MainWindowOsintActionCallback
|
||||||
osint_action_callback;
|
osint_action_callback;
|
||||||
|
|
@ -526,6 +528,16 @@ static void main_window_on_edit_relation_requested(
|
||||||
main_window->edit_relation_user_data);
|
main_window->edit_relation_user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Relaie la catégorisation d'une personne. */
|
||||||
|
static void main_window_on_person_role_changed(const char *entity_identifier,
|
||||||
|
PersonRole role, gpointer user_data)
|
||||||
|
{
|
||||||
|
MainWindow *main_window = user_data;
|
||||||
|
if (main_window != NULL && main_window->person_role_callback != NULL)
|
||||||
|
main_window->person_role_callback(entity_identifier, role,
|
||||||
|
main_window->person_role_user_data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Relaie la demande de vérification provenant du Workspace.
|
* @brief Relaie la demande de vérification provenant du Workspace.
|
||||||
*/
|
*/
|
||||||
|
|
@ -866,6 +878,8 @@ MainWindow *main_window_new(
|
||||||
|
|
||||||
workspace_set_edit_relation_callback(main_window->workspace,
|
workspace_set_edit_relation_callback(main_window->workspace,
|
||||||
main_window_on_edit_relation_requested, main_window);
|
main_window_on_edit_relation_requested, main_window);
|
||||||
|
workspace_set_person_role_callback(main_window->workspace,
|
||||||
|
main_window_on_person_role_changed, main_window);
|
||||||
|
|
||||||
workspace_set_osint_action_callback(
|
workspace_set_osint_action_callback(
|
||||||
main_window->workspace,
|
main_window->workspace,
|
||||||
|
|
@ -1280,6 +1294,14 @@ gboolean main_window_select_graph_relation(MainWindow *main_window,
|
||||||
relation_identifier);
|
relation_identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean main_window_select_graph_entity(MainWindow *main_window,
|
||||||
|
const char *entity_identifier)
|
||||||
|
{
|
||||||
|
if (main_window == NULL) return FALSE;
|
||||||
|
return workspace_select_graph_entity(main_window->workspace,
|
||||||
|
entity_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
void main_window_set_graph_error(
|
void main_window_set_graph_error(
|
||||||
MainWindow *main_window,
|
MainWindow *main_window,
|
||||||
const char *message
|
const char *message
|
||||||
|
|
@ -1650,6 +1672,14 @@ void main_window_set_edit_relation_callback(MainWindow *main_window,
|
||||||
main_window->edit_relation_user_data = user_data;
|
main_window->edit_relation_user_data = user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void main_window_set_person_role_callback(MainWindow *main_window,
|
||||||
|
MainWindowPersonRoleCallback callback, gpointer user_data)
|
||||||
|
{
|
||||||
|
if (main_window == NULL) return;
|
||||||
|
main_window->person_role_callback = callback;
|
||||||
|
main_window->person_role_user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
void main_window_set_quit_callback(
|
void main_window_set_quit_callback(
|
||||||
MainWindow *main_window,
|
MainWindow *main_window,
|
||||||
MainWindowQuitCallback callback,
|
MainWindowQuitCallback callback,
|
||||||
|
|
@ -1803,6 +1833,7 @@ void main_window_free(
|
||||||
|
|
||||||
workspace_set_edit_relation_callback(main_window->workspace,
|
workspace_set_edit_relation_callback(main_window->workspace,
|
||||||
NULL, NULL);
|
NULL, NULL);
|
||||||
|
workspace_set_person_role_callback(main_window->workspace, NULL, NULL);
|
||||||
|
|
||||||
main_window->graph_node_moved_callback =
|
main_window->graph_node_moved_callback =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
@ -1824,6 +1855,8 @@ void main_window_free(
|
||||||
|
|
||||||
main_window->edit_relation_callback = NULL;
|
main_window->edit_relation_callback = NULL;
|
||||||
main_window->edit_relation_user_data = NULL;
|
main_window->edit_relation_user_data = NULL;
|
||||||
|
main_window->person_role_callback = NULL;
|
||||||
|
main_window->person_role_user_data = NULL;
|
||||||
|
|
||||||
main_window->show_graph_callback =
|
main_window->show_graph_callback =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ struct EntityDetailsPanel
|
||||||
GtkWidget *entity_created_at_label;
|
GtkWidget *entity_created_at_label;
|
||||||
GtkWidget *entity_updated_at_label;
|
GtkWidget *entity_updated_at_label;
|
||||||
GtkWidget *entity_identifier_label;
|
GtkWidget *entity_identifier_label;
|
||||||
|
GtkWidget *person_role_box;
|
||||||
|
GtkDropDown *person_role_dropdown;
|
||||||
|
|
||||||
char *selected_entity_identifier;
|
char *selected_entity_identifier;
|
||||||
|
|
||||||
|
|
@ -40,8 +42,30 @@ struct EntityDetailsPanel
|
||||||
|
|
||||||
gpointer
|
gpointer
|
||||||
add_relation_user_data;
|
add_relation_user_data;
|
||||||
|
|
||||||
|
EntityDetailsPanelPersonRoleCallback person_role_callback;
|
||||||
|
gpointer person_role_user_data;
|
||||||
|
gboolean updating_person_role;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief Relaie une catégorie choisie explicitement par l'utilisateur. */
|
||||||
|
static void entity_details_panel_on_person_role_changed(GObject *object,
|
||||||
|
GParamSpec *parameter, gpointer user_data)
|
||||||
|
{
|
||||||
|
EntityDetailsPanel *details_panel = user_data;
|
||||||
|
guint selected = GTK_INVALID_LIST_POSITION;
|
||||||
|
(void) object; (void) parameter;
|
||||||
|
if (details_panel == NULL || details_panel->updating_person_role ||
|
||||||
|
details_panel->person_role_callback == NULL ||
|
||||||
|
details_panel->selected_entity_identifier == NULL)
|
||||||
|
return;
|
||||||
|
selected = gtk_drop_down_get_selected(details_panel->person_role_dropdown);
|
||||||
|
if (selected > PERSON_ROLE_RELATED_PERSON) return;
|
||||||
|
details_panel->person_role_callback(
|
||||||
|
details_panel->selected_entity_identifier, (PersonRole) selected,
|
||||||
|
details_panel->person_role_user_data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Définit une valeur avec un texte de remplacement.
|
* @brief Définit une valeur avec un texte de remplacement.
|
||||||
*/
|
*/
|
||||||
|
|
@ -314,6 +338,8 @@ EntityDetailsPanel *entity_details_panel_new(void)
|
||||||
GtkWidget *details_grid =
|
GtkWidget *details_grid =
|
||||||
NULL;
|
NULL;
|
||||||
|
|
||||||
|
GtkStringList *person_role_labels = NULL;
|
||||||
|
|
||||||
details_panel =
|
details_panel =
|
||||||
g_try_new0(
|
g_try_new0(
|
||||||
EntityDetailsPanel,
|
EntityDetailsPanel,
|
||||||
|
|
@ -695,6 +721,23 @@ EntityDetailsPanel *entity_details_panel_new(void)
|
||||||
"Identifiant"
|
"Identifiant"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
details_panel->person_role_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||||
|
person_role_labels = gtk_string_list_new(NULL);
|
||||||
|
for (guint role = PERSON_ROLE_UNCATEGORIZED;
|
||||||
|
role <= PERSON_ROLE_RELATED_PERSON; role++)
|
||||||
|
gtk_string_list_append(person_role_labels,
|
||||||
|
person_role_get_label((PersonRole) role));
|
||||||
|
details_panel->person_role_dropdown = GTK_DROP_DOWN(
|
||||||
|
gtk_drop_down_new(G_LIST_MODEL(person_role_labels), NULL));
|
||||||
|
person_role_labels = NULL;
|
||||||
|
gtk_box_append(GTK_BOX(details_panel->person_role_box),
|
||||||
|
gtk_label_new("Catégorie dans l’enquête"));
|
||||||
|
gtk_box_append(GTK_BOX(details_panel->person_role_box),
|
||||||
|
GTK_WIDGET(details_panel->person_role_dropdown));
|
||||||
|
gtk_box_append(GTK_BOX(details_box), details_panel->person_role_box);
|
||||||
|
g_signal_connect(details_panel->person_role_dropdown, "notify::selected",
|
||||||
|
G_CALLBACK(entity_details_panel_on_person_role_changed), details_panel);
|
||||||
|
|
||||||
if (details_panel->entity_value_label == NULL ||
|
if (details_panel->entity_value_label == NULL ||
|
||||||
details_panel->entity_type_label == NULL ||
|
details_panel->entity_type_label == NULL ||
|
||||||
details_panel->entity_description_label == NULL ||
|
details_panel->entity_description_label == NULL ||
|
||||||
|
|
@ -812,6 +855,14 @@ void entity_details_panel_set_entity(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
gboolean is_person = g_strcmp0(entity_record_get_type_identifier(
|
||||||
|
entity_record), "person") == 0;
|
||||||
|
gtk_widget_set_visible(details_panel->person_role_box, is_person);
|
||||||
|
details_panel->updating_person_role = TRUE;
|
||||||
|
gtk_drop_down_set_selected(details_panel->person_role_dropdown,
|
||||||
|
(guint) entity_record_get_person_role(entity_record));
|
||||||
|
details_panel->updating_person_role = FALSE;
|
||||||
|
|
||||||
entity_details_panel_update_add_relation_button(
|
entity_details_panel_update_add_relation_button(
|
||||||
details_panel
|
details_panel
|
||||||
);
|
);
|
||||||
|
|
@ -1013,6 +1064,8 @@ void entity_details_panel_clear(
|
||||||
/* Le revealer fermé ne doit pas bloquer le canvas sous l'overlay. */
|
/* Le revealer fermé ne doit pas bloquer le canvas sous l'overlay. */
|
||||||
gtk_widget_set_can_target(details_panel->root_revealer, FALSE);
|
gtk_widget_set_can_target(details_panel->root_revealer, FALSE);
|
||||||
}
|
}
|
||||||
|
if (details_panel->person_role_box != NULL)
|
||||||
|
gtk_widget_set_visible(details_panel->person_role_box, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void entity_details_panel_set_close_callback(
|
void entity_details_panel_set_close_callback(
|
||||||
|
|
@ -1055,6 +1108,15 @@ void entity_details_panel_set_add_relation_callback(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void entity_details_panel_set_person_role_callback(
|
||||||
|
EntityDetailsPanel *details_panel,
|
||||||
|
EntityDetailsPanelPersonRoleCallback callback, gpointer user_data)
|
||||||
|
{
|
||||||
|
if (details_panel == NULL) return;
|
||||||
|
details_panel->person_role_callback = callback;
|
||||||
|
details_panel->person_role_user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean entity_details_panel_is_open(
|
gboolean entity_details_panel_is_open(
|
||||||
const EntityDetailsPanel *details_panel
|
const EntityDetailsPanel *details_panel
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3304,6 +3304,8 @@ static void investigation_graph_view_draw_entity(
|
||||||
SocialPlatform social_platform =
|
SocialPlatform social_platform =
|
||||||
SOCIAL_PLATFORM_NONE;
|
SOCIAL_PLATFORM_NONE;
|
||||||
|
|
||||||
|
PersonRole person_role = PERSON_ROLE_UNCATEGORIZED;
|
||||||
|
|
||||||
double text_x =
|
double text_x =
|
||||||
x + INVESTIGATION_GRAPH_VIEW_NODE_PADDING;
|
x + INVESTIGATION_GRAPH_VIEW_NODE_PADDING;
|
||||||
|
|
||||||
|
|
@ -3325,7 +3327,28 @@ static void investigation_graph_view_draw_entity(
|
||||||
INVESTIGATION_GRAPH_VIEW_NODE_RADIUS
|
INVESTIGATION_GRAPH_VIEW_NODE_RADIUS
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selected)
|
if (g_strcmp0(entity_record_get_type_identifier(entity_record),
|
||||||
|
"person") == 0)
|
||||||
|
{
|
||||||
|
person_role = entity_record_get_person_role(entity_record);
|
||||||
|
switch (person_role)
|
||||||
|
{
|
||||||
|
case PERSON_ROLE_ALLEGED_SCAMMER:
|
||||||
|
cairo_set_source_rgb(cairo_context, 0.55, 0.12, 0.14); break;
|
||||||
|
case PERSON_ROLE_VICTIM:
|
||||||
|
cairo_set_source_rgb(cairo_context, 0.12, 0.42, 0.22); break;
|
||||||
|
case PERSON_ROLE_WITNESS:
|
||||||
|
cairo_set_source_rgb(cairo_context, 0.12, 0.30, 0.56); break;
|
||||||
|
case PERSON_ROLE_SUSPECT:
|
||||||
|
cairo_set_source_rgb(cairo_context, 0.62, 0.32, 0.08); break;
|
||||||
|
case PERSON_ROLE_RELATED_PERSON:
|
||||||
|
cairo_set_source_rgb(cairo_context, 0.38, 0.20, 0.52); break;
|
||||||
|
case PERSON_ROLE_UNCATEGORIZED:
|
||||||
|
default:
|
||||||
|
cairo_set_source_rgb(cairo_context, 0.25, 0.27, 0.31); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (selected)
|
||||||
{
|
{
|
||||||
cairo_set_source_rgb(
|
cairo_set_source_rgb(
|
||||||
cairo_context,
|
cairo_context,
|
||||||
|
|
@ -3393,6 +3416,9 @@ static void investigation_graph_view_draw_entity(
|
||||||
|
|
||||||
social_platform = social_platform_from_entity_type(type_identifier);
|
social_platform = social_platform_from_entity_type(type_identifier);
|
||||||
|
|
||||||
|
if (g_strcmp0(type_identifier, "person") == 0)
|
||||||
|
type_label = person_role_get_label(person_role);
|
||||||
|
|
||||||
if (social_platform != SOCIAL_PLATFORM_NONE)
|
if (social_platform != SOCIAL_PLATFORM_NONE)
|
||||||
{
|
{
|
||||||
investigation_graph_view_draw_social_icon(
|
investigation_graph_view_draw_social_icon(
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,8 @@ struct Workspace
|
||||||
|
|
||||||
WorkspaceEditRelationCallback edit_relation_callback;
|
WorkspaceEditRelationCallback edit_relation_callback;
|
||||||
gpointer edit_relation_user_data;
|
gpointer edit_relation_user_data;
|
||||||
|
WorkspacePersonRoleCallback person_role_callback;
|
||||||
|
gpointer person_role_user_data;
|
||||||
|
|
||||||
WorkspaceOsintActionCallback
|
WorkspaceOsintActionCallback
|
||||||
osint_action_callback;
|
osint_action_callback;
|
||||||
|
|
@ -782,6 +784,16 @@ static void workspace_on_add_relation_requested(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Relaie le changement de catégorie d'une personne. */
|
||||||
|
static void workspace_on_person_role_changed(const char *entity_identifier,
|
||||||
|
PersonRole role, gpointer user_data)
|
||||||
|
{
|
||||||
|
Workspace *workspace = user_data;
|
||||||
|
if (workspace != NULL && workspace->person_role_callback != NULL)
|
||||||
|
workspace->person_role_callback(entity_identifier, role,
|
||||||
|
workspace->person_role_user_data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Désélectionne le nœud lorsque le volet est fermé manuellement.
|
* @brief Désélectionne le nœud lorsque le volet est fermé manuellement.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1654,6 +1666,10 @@ Workspace *workspace_new(void)
|
||||||
workspace
|
workspace
|
||||||
);
|
);
|
||||||
|
|
||||||
|
entity_details_panel_set_person_role_callback(
|
||||||
|
workspace->entity_details_panel, workspace_on_person_role_changed,
|
||||||
|
workspace);
|
||||||
|
|
||||||
gtk_overlay_set_child(
|
gtk_overlay_set_child(
|
||||||
GTK_OVERLAY(
|
GTK_OVERLAY(
|
||||||
workspace->graph_view_page
|
workspace->graph_view_page
|
||||||
|
|
@ -2993,6 +3009,14 @@ void workspace_set_edit_relation_callback(Workspace *workspace,
|
||||||
workspace->edit_relation_user_data = user_data;
|
workspace->edit_relation_user_data = user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void workspace_set_person_role_callback(Workspace *workspace,
|
||||||
|
WorkspacePersonRoleCallback callback, gpointer user_data)
|
||||||
|
{
|
||||||
|
if (workspace == NULL) return;
|
||||||
|
workspace->person_role_callback = callback;
|
||||||
|
workspace->person_role_user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
void workspace_reset_graph_layout(
|
void workspace_reset_graph_layout(
|
||||||
Workspace *workspace
|
Workspace *workspace
|
||||||
)
|
)
|
||||||
|
|
@ -3059,6 +3083,8 @@ void workspace_free(Workspace *workspace)
|
||||||
NULL,
|
NULL,
|
||||||
NULL
|
NULL
|
||||||
);
|
);
|
||||||
|
entity_details_panel_set_person_role_callback(
|
||||||
|
workspace->entity_details_panel, NULL, NULL);
|
||||||
|
|
||||||
entity_details_panel_clear(
|
entity_details_panel_clear(
|
||||||
workspace->entity_details_panel
|
workspace->entity_details_panel
|
||||||
|
|
|
||||||
|
|
@ -525,11 +525,12 @@ static void test_database_initialize_valid_database(void)
|
||||||
"FROM investigation;"
|
"FROM investigation;"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert(strcmp(schema_version, "4") == 0);
|
assert(strcmp(schema_version, "5") == 0);
|
||||||
test_database_assert_table_exists(database, "osint_executions");
|
test_database_assert_table_exists(database, "osint_executions");
|
||||||
test_database_assert_table_exists(database, "osint_execution_entities");
|
test_database_assert_table_exists(database, "osint_execution_entities");
|
||||||
test_database_assert_table_exists(database, "osint_execution_relations");
|
test_database_assert_table_exists(database, "osint_execution_relations");
|
||||||
test_database_assert_table_exists(database, "comptes_sociaux");
|
test_database_assert_table_exists(database, "comptes_sociaux");
|
||||||
|
test_database_assert_table_exists(database, "person_roles");
|
||||||
assert(strcmp(application_name, "Labfy Investigation") == 0);
|
assert(strcmp(application_name, "Labfy Investigation") == 0);
|
||||||
|
|
||||||
assert(created_at[0] != '\0');
|
assert(created_at[0] != '\0');
|
||||||
|
|
@ -988,7 +989,7 @@ static void test_database_migrate_v1_to_v2(void)
|
||||||
assert(
|
assert(
|
||||||
strcmp(
|
strcmp(
|
||||||
schema_version,
|
schema_version,
|
||||||
"4"
|
"5"
|
||||||
) == 0
|
) == 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,13 @@ static void test_person_entity_create(void)
|
||||||
assert(record != NULL && error == NULL);
|
assert(record != NULL && error == NULL);
|
||||||
assert(strcmp(entity_record_get_type_identifier(record), "person") == 0);
|
assert(strcmp(entity_record_get_type_identifier(record), "person") == 0);
|
||||||
assert(entity_record_get_confidence(record) == 30);
|
assert(entity_record_get_confidence(record) == 30);
|
||||||
|
assert(entity_record_get_person_role(record) == PERSON_ROLE_UNCATEGORIZED);
|
||||||
|
entity_record_free(record); record = NULL;
|
||||||
|
assert(person_entity_service_update_role(database, identifier,
|
||||||
|
PERSON_ROLE_VICTIM, &error));
|
||||||
|
record = entity_dao_find_by_identifier(dao, identifier, &error);
|
||||||
|
assert(record != NULL && error == NULL);
|
||||||
|
assert(entity_record_get_person_role(record) == PERSON_ROLE_VICTIM);
|
||||||
entity_record_free(record); entity_dao_free(dao); database_close(database);
|
entity_record_free(record); entity_dao_free(dao); database_close(database);
|
||||||
assert(g_remove(path) == 0); assert(g_rmdir(directory) == 0);
|
assert(g_remove(path) == 0); assert(g_rmdir(directory) == 0);
|
||||||
g_free(identifier); g_free(path); g_free(directory);
|
g_free(identifier); g_free(path); g_free(directory);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue