|
|
|
@ -0,0 +1,180 @@
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Supabase; // Make sure SupabaseManager initializes this correctly
|
|
|
|
|
using Postgrest.Models;
|
|
|
|
|
using Postgrest.Attributes;
|
|
|
|
|
|
|
|
|
|
public class SupabaseEventLogger : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
public static SupabaseEventLogger Instance;
|
|
|
|
|
|
|
|
|
|
private DateTime sessionStartTime;
|
|
|
|
|
|
|
|
|
|
private void Awake()
|
|
|
|
|
{
|
|
|
|
|
if (Instance == null)
|
|
|
|
|
Instance = this;
|
|
|
|
|
else
|
|
|
|
|
Destroy(gameObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Call this at the start of the game session.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public async void StartSession()
|
|
|
|
|
{
|
|
|
|
|
sessionStartTime = DateTime.UtcNow;
|
|
|
|
|
|
|
|
|
|
var gameEvent = new GameEvent
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.NewGuid(), // <== Ensure this is explicitly set
|
|
|
|
|
EventKey = "game_session_started",
|
|
|
|
|
Timestamp = sessionStartTime,
|
|
|
|
|
UserId = "user123"
|
|
|
|
|
};
|
|
|
|
|
await Client.Instance.From<GameEvent>().Insert(gameEvent);
|
|
|
|
|
Debug.Log("✅ Supabase Event: game_session_started");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Logs optimal/suboptimal decisions at runtime.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public async void LogDecisionEvent(bool isOptimal)
|
|
|
|
|
{
|
|
|
|
|
string eventKey = isOptimal ? "game_optimal_decision_made" : "game_suboptimal_decision_made";
|
|
|
|
|
|
|
|
|
|
var gameEvent = new GameEvent
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
EventKey = eventKey,
|
|
|
|
|
Timestamp = DateTime.UtcNow,
|
|
|
|
|
UserId = "user123"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await Client.Instance.From<GameEvent>().Insert(gameEvent);
|
|
|
|
|
Debug.Log($"✅ Supabase Event: {eventKey}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Completes the session and submits full results to phishing_game_attempts table.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public async void CompleteSessionAndSubmitResult(string userId, bool passed, int optimal, int suboptimal, string scenarioId, List<Decision> decisionLog = null)
|
|
|
|
|
{
|
|
|
|
|
var endTime = DateTime.UtcNow;
|
|
|
|
|
int duration = (int)(endTime - sessionStartTime).TotalSeconds;
|
|
|
|
|
|
|
|
|
|
// Log completion events
|
|
|
|
|
await Client.Instance.From<GameEvent>().Insert(new GameEvent
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
EventKey = "game_session_completed",
|
|
|
|
|
Timestamp = endTime,
|
|
|
|
|
UserId = userId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Client.Instance.From<GameEvent>().Insert(new GameEvent
|
|
|
|
|
{Id = Guid.NewGuid(),
|
|
|
|
|
EventKey = "game_score_recorded",
|
|
|
|
|
Timestamp = endTime,
|
|
|
|
|
UserId = userId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Insert session result
|
|
|
|
|
var gameAttempt = new GameAttempt
|
|
|
|
|
{
|
|
|
|
|
GameId = "phishing-awareness-1",
|
|
|
|
|
ScenarioId = scenarioId,
|
|
|
|
|
UserId = userId,
|
|
|
|
|
AttemptNumber = 1,
|
|
|
|
|
StartTime = sessionStartTime,
|
|
|
|
|
EndTime = endTime,
|
|
|
|
|
DurationSeconds = duration,
|
|
|
|
|
Score = passed ? 100 : 50,
|
|
|
|
|
Passed = passed,
|
|
|
|
|
Optimal = optimal,
|
|
|
|
|
Suboptimal = suboptimal,
|
|
|
|
|
KeyDecisionsLogJson = decisionLog != null ? JsonUtility.ToJson(new DecisionLogWrapper { decisions = decisionLog }) : "[]"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await Client.Instance.From<GameAttempt>().Insert(gameAttempt);
|
|
|
|
|
Debug.Log("✅ Supabase Game Result Submitted");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
public class Decision
|
|
|
|
|
{
|
|
|
|
|
public string decisionId;
|
|
|
|
|
public string timestamp;
|
|
|
|
|
public bool optimal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
public class DecisionLogWrapper
|
|
|
|
|
{
|
|
|
|
|
public List<Decision> decisions;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Existing SupabaseEventLogger class here...
|
|
|
|
|
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
|
|
|
|
|
|
|
|
|
[Table("game_events")]
|
|
|
|
|
public class GameEvent : BaseModel
|
|
|
|
|
{
|
|
|
|
|
[PrimaryKey("id", false)]
|
|
|
|
|
public Guid Id { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("event_key")]
|
|
|
|
|
public string EventKey { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("timestamp")]
|
|
|
|
|
public DateTime Timestamp { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("user_id")]
|
|
|
|
|
public string UserId { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Table("phishing_game_attempts")]
|
|
|
|
|
public class GameAttempt : BaseModel
|
|
|
|
|
{
|
|
|
|
|
[PrimaryKey("id", false)]
|
|
|
|
|
public Guid Id { get; set; } = Guid.NewGuid();
|
|
|
|
|
|
|
|
|
|
[Column("game_id")]
|
|
|
|
|
public string GameId { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("scenario_id")]
|
|
|
|
|
public string ScenarioId { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("user_id")]
|
|
|
|
|
public string UserId { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("attempt_number")]
|
|
|
|
|
public int AttemptNumber { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("start_timestamp")]
|
|
|
|
|
public DateTime StartTime { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("end_timestamp")]
|
|
|
|
|
public DateTime EndTime { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("duration_seconds")]
|
|
|
|
|
public int DurationSeconds { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("final_score_percentage")]
|
|
|
|
|
public float Score { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("pass_fail_status")]
|
|
|
|
|
public bool Passed { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("optimal_decisions_count")]
|
|
|
|
|
public int Optimal { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("suboptimal_decisions_count")]
|
|
|
|
|
public int Suboptimal { get; set; }
|
|
|
|
|
|
|
|
|
|
[Column("key_decisions_log")]
|
|
|
|
|
public string KeyDecisionsLogJson { get; set; }
|
|
|
|
|
}
|