1use crate::core::{FinitePeriod, Handler, Instant, Listing};
2use crate::digest::{DigestSha2_256, DigestSha3_256};
3use crate::email::EmailAddress;
4use crate::mailer::store::acquire_outbound_email_request::{
5 AcquireOutboundEmailRequest, AcquireOutboundEmailRequestError,
6};
7use crate::mailer::store::create_outbound_email::{CreateOutboundEmail, CreateOutboundEmailError};
8use crate::mailer::store::get_outbound_email_requests::{GetOutboundEmailRequests, GetOutboundEmailRequestsError};
9use crate::mailer::store::get_outbound_emails::{GetOutboundEmails, GetOutboundEmailsError};
10use crate::mailer::store::release_outbound_email_request::{
11 ReleaseOutboundEmailRequest, ReleaseOutboundEmailRequestError,
12};
13use crate::user::UserIdRef;
14use async_trait::async_trait;
15#[cfg(feature = "serde")]
16use eternaltwin_serde_tools::{Deserialize, Serialize};
17use std::ops::Deref;
18
19pub mod acquire_outbound_email_request;
20pub mod create_outbound_email;
21pub mod get_outbound_email_requests;
22pub mod get_outbound_emails;
23pub mod release_outbound_email_request;
24
25declare_new_uuid! {
26 pub struct OutboundEmailId(Uuid);
27 pub type ParseError = OuboundEmailIdParseError;
28 const SQL_NAME = "outbound_email_id";
29}
30
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
33#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub struct OutboundEmailIdRef {
35 pub id: OutboundEmailId,
36}
37
38impl OutboundEmailIdRef {
39 pub const fn new(id: OutboundEmailId) -> Self {
40 Self { id }
41 }
42}
43
44impl From<OutboundEmailId> for OutboundEmailIdRef {
45 fn from(id: OutboundEmailId) -> Self {
46 Self::new(id)
47 }
48}
49
50declare_new_uuid! {
51 pub struct OutboundEmailRequestId(Uuid);
52 pub type ParseError = OutboundEmailRequestIdParseError;
53 const SQL_NAME = "outbound_email_request_id";
54}
55
56#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
57#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
58#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub struct OutboundEmailRequestIdRef {
60 pub id: OutboundEmailRequestId,
61}
62
63impl OutboundEmailRequestIdRef {
64 pub const fn new(id: OutboundEmailRequestId) -> Self {
65 Self { id }
66 }
67}
68
69impl From<OutboundEmailRequestId> for OutboundEmailRequestIdRef {
70 fn from(id: OutboundEmailRequestId) -> Self {
71 Self::new(id)
72 }
73}
74
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub struct OutboundEmail<Opaque> {
78 pub id: OutboundEmailId,
79 pub submitted_at: Instant,
80 pub created_by: UserIdRef,
81 pub deadline: Instant,
82 pub sender: EmailAddress,
83 pub recipient: EmailAddress,
84 pub payload: EmailContentPayload<Opaque>,
85 pub read_at: Option<Instant>,
86}
87
88declare_new_enum!(
89 pub enum EmailDeliveryStatus {
90 #[str("Pending")]
91 Pending,
92 #[str("Cancelled")]
93 Cancelled,
94 #[str("Sent")]
95 Sent,
96 #[str("Acknowledged")]
97 Acknowledged,
98 }
99 pub type ParseError = EmailDeliveryStatusParseError;
100 const SQL_NAME = "email_delivery_status";
101);
102
103declare_new_enum!(
104 pub enum EmailPayloadKind {
105 #[str("Marktwin")]
106 Marktwin,
107 #[str("ResetPassword")]
108 ResetPassword,
109 #[str("VerifyEmail")]
110 VerifyEmail,
111 }
112 pub type ParseError = EmailPayloadKindParseError;
113 const SQL_NAME = "email_payload_kind";
114);
115
116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
117#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
118pub struct EmailContentPayload<Opaque> {
119 pub kind: EmailPayloadKind,
120 pub version: u8,
121 pub data: Opaque,
122 pub text: EmailContentSummary,
123 pub html: EmailContentSummary,
124}
125
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
128pub struct EmailContentSummary {
129 pub size: u16,
130 pub sha2_256: DigestSha2_256,
131 pub sha3_256: DigestSha3_256,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
135pub enum EmailContentSummaryError {
136 #[error("the provided data is too large: actual size = {0}, max size = {1}")]
137 Size(usize, usize),
138}
139
140impl EmailContentSummary {
141 pub const EMPTY: Self = Self {
149 size: 0,
150 #[rustfmt::skip]
151 sha2_256: DigestSha2_256::from_array([
152 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
153 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
154 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
155 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
156 ]),
157 #[rustfmt::skip]
158 sha3_256: DigestSha3_256::from_array([
159 0xa7, 0xff, 0xc6, 0xf8, 0xbf, 0x1e, 0xd7, 0x66,
160 0x51, 0xc1, 0x47, 0x56, 0xa0, 0x61, 0xd6, 0x62,
161 0xf5, 0x80, 0xff, 0x4d, 0xe4, 0x3b, 0x49, 0xfa,
162 0x82, 0xd8, 0x0a, 0x4b, 0x80, 0xf8, 0x43, 0x4a,
163 ]),
164 };
165
166 pub fn new(data: &[u8]) -> Result<Self, EmailContentSummaryError> {
167 let size =
168 u16::try_from(data.len()).map_err(|_| EmailContentSummaryError::Size(data.len(), usize::from(u16::MAX)))?;
169 let sha2_256 = DigestSha2_256::digest(data);
170 let sha3_256 = DigestSha3_256::digest(data);
171 Ok(Self {
172 size,
173 sha2_256,
174 sha3_256,
175 })
176 }
177}
178
179declare_new_enum!(
180 pub enum EmailRequestStatus {
181 #[str("Pending")]
182 Pending,
183 #[str("Cancelled")]
184 Cancelled,
185 #[str("Timeout")]
186 Timeout,
187 #[str("Ok")]
188 Ok,
189 #[str("Error")]
190 Error,
191 }
192 pub type ParseError = EmailRequestStatusParseError;
193 const SQL_NAME = "email_request_status";
194);
195
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
198pub struct OutboundEmailRequest {
199 pub id: OutboundEmailRequestId,
200 pub email: OutboundEmailIdRef,
201 pub period: FinitePeriod,
202 pub status: EmailRequestStatus,
203}
204
205#[async_trait]
206pub trait MailerStore<Opaque>:
207 Send
208 + Sync
209 + Handler<CreateOutboundEmail<Opaque>>
210 + Handler<AcquireOutboundEmailRequest>
211 + Handler<ReleaseOutboundEmailRequest>
212 + Handler<GetOutboundEmails<Opaque>>
213 + Handler<GetOutboundEmailRequests>
214where
215 Opaque: Send + Sync + 'static,
216{
217 async fn create_outbound_email(
218 &self,
219 cmd: CreateOutboundEmail<Opaque>,
220 ) -> Result<OutboundEmail<Opaque>, CreateOutboundEmailError> {
221 Handler::<CreateOutboundEmail<Opaque>>::handle(self, cmd).await
222 }
223
224 async fn acquire_outbound_email_request(
225 &self,
226 cmd: AcquireOutboundEmailRequest,
227 ) -> Result<OutboundEmailRequest, AcquireOutboundEmailRequestError> {
228 Handler::<AcquireOutboundEmailRequest>::handle(self, cmd).await
229 }
230
231 async fn release_outbound_email_request(
232 &self,
233 cmd: ReleaseOutboundEmailRequest,
234 ) -> Result<OutboundEmailRequest, ReleaseOutboundEmailRequestError> {
235 Handler::<ReleaseOutboundEmailRequest>::handle(self, cmd).await
236 }
237
238 async fn get_outbound_emails(
239 &self,
240 query: GetOutboundEmails<Opaque>,
241 ) -> Result<Listing<OutboundEmail<Opaque>>, GetOutboundEmailsError> {
242 Handler::<GetOutboundEmails<Opaque>>::handle(self, query).await
243 }
244
245 async fn get_outbound_email_requests(
246 &self,
247 query: GetOutboundEmailRequests,
248 ) -> Result<Listing<OutboundEmailRequest>, GetOutboundEmailRequestsError> {
249 Handler::<GetOutboundEmailRequests>::handle(self, query).await
250 }
251}
252
253impl<T, Opaque> MailerStore<Opaque> for T
254where
255 T: Send
256 + Sync
257 + Handler<CreateOutboundEmail<Opaque>>
258 + Handler<AcquireOutboundEmailRequest>
259 + Handler<ReleaseOutboundEmailRequest>
260 + Handler<GetOutboundEmails<Opaque>>
261 + Handler<GetOutboundEmailRequests>,
262 Opaque: Send + Sync + 'static,
263{
264}
265
266pub trait MailerStoreRef<Opaque>: Send + Sync
268where
269 Opaque: Send + Sync + 'static,
270{
271 type MailerStore: MailerStore<Opaque> + ?Sized;
272
273 fn mailer_store(&self) -> &Self::MailerStore;
274}
275
276impl<TyRef, Opaque> MailerStoreRef<Opaque> for TyRef
277where
278 TyRef: Deref + Send + Sync,
279 TyRef::Target: MailerStore<Opaque>,
280 Opaque: Send + Sync + 'static,
281{
282 type MailerStore = TyRef::Target;
283
284 fn mailer_store(&self) -> &Self::MailerStore {
285 self.deref()
286 }
287}