Skip to main content

allsource_core/application/dto/
event_dto.rs

1use crate::domain::entities::Event;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6/// DTO for ingesting a new event
7#[derive(Debug, Deserialize)]
8pub struct IngestEventRequest {
9    pub event_type: String,
10    pub entity_id: String,
11    pub tenant_id: Option<String>, // Optional, defaults to "default"
12    pub payload: serde_json::Value,
13    pub metadata: Option<serde_json::Value>,
14    /// Optional optimistic concurrency control: if set, the write is rejected
15    /// with 409 Conflict unless the entity's current version matches this value.
16    pub expected_version: Option<u64>,
17}
18
19/// DTO for event ingestion response
20#[derive(Debug, Serialize)]
21pub struct IngestEventResponse {
22    pub event_id: Uuid,
23    pub timestamp: DateTime<Utc>,
24    /// The entity's version after this event was appended
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub version: Option<u64>,
27}
28
29impl IngestEventResponse {
30    pub fn from_event(event: &Event) -> Self {
31        Self {
32            event_id: event.id(),
33            timestamp: event.timestamp(),
34            version: None,
35        }
36    }
37}
38
39/// DTO for querying events
40#[derive(Debug, Default, Deserialize)]
41pub struct QueryEventsRequest {
42    /// Filter by entity ID
43    pub entity_id: Option<String>,
44
45    /// Filter by event type
46    pub event_type: Option<String>,
47
48    /// Tenant ID (required for multi-tenancy)
49    pub tenant_id: Option<String>,
50
51    /// Time-travel: get events as of this timestamp
52    pub as_of: Option<DateTime<Utc>>,
53
54    /// Get events since this timestamp
55    pub since: Option<DateTime<Utc>>,
56
57    /// Get events until this timestamp
58    pub until: Option<DateTime<Utc>>,
59
60    /// Limit number of results
61    pub limit: Option<usize>,
62
63    /// Filter by event type prefix (e.g., "index." matches "index.created", "index.updated")
64    pub event_type_prefix: Option<String>,
65
66    /// Filter by payload fields (JSON string, e.g., `{"user_id":"abc-123"}`).
67    /// Matches events where payload contains ALL specified key-value pairs.
68    pub payload_filter: Option<String>,
69}
70
71/// DTO for query response
72#[derive(Debug, Serialize)]
73pub struct QueryEventsResponse {
74    pub events: Vec<EventDto>,
75    pub count: usize,
76    pub total_count: usize,
77    pub has_more: bool,
78    /// Current version of the entity (present only when query filters by entity_id)
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub entity_version: Option<u64>,
81}
82
83/// DTO for a single event in responses
84#[derive(Debug, Serialize, Deserialize, Clone)]
85pub struct EventDto {
86    pub id: Uuid,
87    pub event_type: String,
88    pub entity_id: String,
89    pub tenant_id: String,
90    pub payload: serde_json::Value,
91    pub timestamp: DateTime<Utc>,
92    pub metadata: Option<serde_json::Value>,
93    pub version: i64,
94}
95
96impl From<&Event> for EventDto {
97    fn from(event: &Event) -> Self {
98        Self {
99            id: event.id(),
100            event_type: event.event_type().to_string(),
101            entity_id: event.entity_id().to_string(),
102            tenant_id: event.tenant_id().to_string(),
103            payload: event.payload().clone(),
104            timestamp: event.timestamp(),
105            metadata: event.metadata().cloned(),
106            version: event.version(),
107        }
108    }
109}
110
111impl From<Event> for EventDto {
112    fn from(event: Event) -> Self {
113        EventDto::from(&event)
114    }
115}
116
117/// Request parameters for listing entities
118#[derive(Debug, Deserialize)]
119pub struct ListEntitiesRequest {
120    /// Filter entities by event type prefix
121    pub event_type_prefix: Option<String>,
122    /// Filter by payload fields (JSON string)
123    pub payload_filter: Option<String>,
124    /// Limit number of entities returned
125    pub limit: Option<usize>,
126    /// Offset for pagination
127    pub offset: Option<usize>,
128}
129
130/// A single entity summary in the list response
131#[derive(Debug, Serialize)]
132pub struct EntitySummary {
133    pub entity_id: String,
134    pub event_count: usize,
135    pub last_event_type: String,
136    pub last_event_at: DateTime<Utc>,
137}
138
139/// Response from listing entities
140#[derive(Debug, Serialize)]
141pub struct ListEntitiesResponse {
142    pub entities: Vec<EntitySummary>,
143    pub total: usize,
144    pub has_more: bool,
145}
146
147/// Request parameters for detecting duplicate entities
148#[derive(Debug, Deserialize)]
149pub struct DetectDuplicatesRequest {
150    /// Required: event type prefix to scope the search (no full-store scans)
151    pub event_type_prefix: String,
152    /// Comma-separated list of payload field names to group by (e.g., "name,user_id")
153    pub group_by: String,
154    /// Limit number of duplicate groups returned
155    pub limit: Option<usize>,
156    /// Offset for pagination
157    pub offset: Option<usize>,
158}
159
160/// A group of entities that share the same payload field values
161#[derive(Debug, Serialize)]
162pub struct DuplicateGroup {
163    /// The shared field values that define this group
164    pub key: serde_json::Value,
165    /// Entity IDs in this group
166    pub entity_ids: Vec<String>,
167    /// Number of entities in this group
168    pub count: usize,
169}
170
171/// Response from duplicate entity detection
172#[derive(Debug, Serialize)]
173pub struct DetectDuplicatesResponse {
174    /// Groups where count > 1
175    pub duplicates: Vec<DuplicateGroup>,
176    /// Total number of duplicate groups found
177    pub total: usize,
178    /// Whether more results are available
179    pub has_more: bool,
180}
181
182/// DTO for batch ingesting multiple events
183#[derive(Debug, Deserialize)]
184pub struct IngestEventsBatchRequest {
185    pub events: Vec<IngestEventRequest>,
186}
187
188/// DTO for batch ingestion response
189#[derive(Debug, Serialize)]
190pub struct IngestEventsBatchResponse {
191    /// Total number of events submitted
192    pub total: usize,
193    /// Number of events successfully ingested
194    pub ingested: usize,
195    /// Individual results for each event
196    pub events: Vec<IngestEventResponse>,
197}