use async_trait::async_trait;
use uuid::Uuid;
use super::event_error::EventResult;
use super::intake_service::{IntakePayload, IntakeService};
use super::my_tickets_service::MyTicketsService;
use super::registration_service::RegistrationCommandService;
use super::scheduler_service::{SchedulerRunSummary, SchedulerService};
use super::seat_service::{SeatAvailability, SeatService};
use crate::infrastructure::persistence::event_command_repository::EventCommandRepository;
use crate::infrastructure::persistence::seat_repository::RegistrationRow;
use crate::infrastructure::persistence::{scheduler_repository::SchedulerRepository, seat_repository::SeatRepository};
#[derive(Debug, Clone, serde::Serialize)]
pub struct PublicationState {
pub event_id: Uuid,
pub is_published: bool,
pub kanban_state: String,
pub date_publish: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct RegistrationStateView {
pub registration_id: Uuid,
pub state: String,
pub sale_order_id: Option<Uuid>,
pub sale_order_state: Option<String>,
pub sale_status: Option<String>,
pub active: bool,
}
#[derive(Debug, Clone)]
pub enum CapabilityScope {
Ics { event_id: Uuid, slot_id: Option<Uuid>, ttl_secs: i64 },
TicketReport { event_id: Uuid, registration_ids: Vec<Uuid> },
}
#[async_trait]
pub trait EventSurface: Send + Sync {
async fn seat_availability(
&self,
event_id: Uuid,
slot_id: Option<Uuid>,
) -> EventResult<SeatAvailability>;
async fn publication_state(&self, event_id: Uuid) -> EventResult<PublicationState>;
async fn register_intake(
&self,
event_id: Uuid,
payload: IntakePayload,
client_ip: &str,
) -> EventResult<RegistrationRow>;
async fn registration_state(&self, registration_id: Uuid) -> EventResult<RegistrationStateView>;
async fn mint_capability(&self, scope: CapabilityScope) -> EventResult<String>;
async fn run_due_schedulers(&self) -> EventResult<SchedulerRunSummary>;
}
pub struct DefaultEventSurface {
secret: String,
seats: SeatService,
events: EventCommandRepository,
registrations: RegistrationCommandService,
intake: IntakeService,
ics: IcsAlias,
tickets: MyTicketsService,
scheduler: SchedulerService,
}
type IcsAlias = super::ics_service::IcsService;
impl DefaultEventSurface {
#[allow(clippy::too_many_arguments)]
pub fn new(
secret: String,
seats: SeatService,
events: EventCommandRepository,
registrations: RegistrationCommandService,
intake: IntakeService,
ics: IcsAlias,
tickets: MyTicketsService,
scheduler: SchedulerService,
) -> Self {
Self {
secret,
seats,
events,
registrations,
intake,
ics,
tickets,
scheduler,
}
}
pub fn compose(
pool: sqlx::PgPool,
secret: String,
renderer: std::sync::Arc<dyn super::template_port::EventTemplateRenderer>,
queue: std::sync::Arc<dyn super::template_port::EventMailQueue>,
sms_queue: std::sync::Arc<dyn super::sms_port::EventSmsQueue>,
) -> Self {
let registrations = RegistrationCommandService::new(SeatRepository::new(pool.clone()));
let intake = IntakeService::new(
RegistrationCommandService::new(SeatRepository::new(pool.clone())),
Default::default(),
);
Self::new(
secret,
SeatService::new(
SeatRepository::new(pool.clone()),
EventCommandRepository::new(pool.clone()),
),
EventCommandRepository::new(pool.clone()),
registrations,
intake,
IcsAlias::new(EventCommandRepository::new(pool.clone())),
MyTicketsService::new(
EventCommandRepository::new(pool.clone()),
SeatRepository::new(pool.clone()),
),
SchedulerService::new(
SchedulerRepository::new(pool.clone()),
EventCommandRepository::new(pool.clone()),
renderer,
queue,
sms_queue,
),
)
}
}
#[async_trait]
impl EventSurface for DefaultEventSurface {
async fn seat_availability(
&self,
event_id: Uuid,
slot_id: Option<Uuid>,
) -> EventResult<SeatAvailability> {
self.seats.availability(event_id, slot_id).await
}
async fn publication_state(&self, event_id: Uuid) -> EventResult<PublicationState> {
let row = self.events.find(event_id).await?;
Ok(PublicationState {
event_id,
is_published: row.is_published,
kanban_state: row.kanban_state,
date_publish: row.date_publish,
})
}
async fn register_intake(
&self,
event_id: Uuid,
payload: IntakePayload,
client_ip: &str,
) -> EventResult<RegistrationRow> {
self.intake.intake(event_id, payload, client_ip).await
}
async fn registration_state(&self, registration_id: Uuid) -> EventResult<RegistrationStateView> {
let row = self.registrations.get(registration_id).await?;
Ok(RegistrationStateView {
registration_id: row.id,
state: row.state,
sale_order_id: row.sale_order_id,
sale_order_state: row.sale_order_state,
sale_status: row.sale_status,
active: row.active,
})
}
async fn mint_capability(&self, scope: CapabilityScope) -> EventResult<String> {
match scope {
CapabilityScope::Ics {
event_id,
slot_id,
ttl_secs,
} => self.ics.mint(&self.secret, event_id, slot_id, ttl_secs),
CapabilityScope::TicketReport {
event_id,
registration_ids,
} => self.tickets.mint(&self.secret, event_id, ®istration_ids),
}
}
async fn run_due_schedulers(&self) -> EventResult<SchedulerRunSummary> {
self.scheduler.run_due_schedulers().await
}
}