1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::net::IpAddr;
7use uuid::Uuid;
8
9#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
11#[serde(rename_all = "snake_case")]
12#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
13pub enum TenantStatus {
14 #[default]
16 Active,
17 Suspended,
19 Pending,
21}
22
23#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct Tenant {
26 pub id: Uuid,
28 pub name: String,
30 pub slug: String,
32 pub email: String,
34 pub status: TenantStatus,
36 pub plan: String,
38 pub max_domains: i32,
40 pub max_routes_per_domain: i32,
42 pub settings: serde_json::Value,
44 pub created_at: DateTime<Utc>,
46 pub updated_at: DateTime<Utc>,
48}
49
50#[derive(Clone, Debug, sqlx::FromRow)]
57pub(crate) struct TenantRow {
58 pub(crate) id: Uuid,
59 pub(crate) name: String,
60 pub(crate) slug: String,
61 pub(crate) email: String,
62 pub(crate) status: String,
63 pub(crate) plan: String,
64 pub(crate) max_domains: i32,
65 pub(crate) max_routes_per_domain: i32,
66 pub(crate) settings: serde_json::Value,
67 pub(crate) created_at: DateTime<Utc>,
68 pub(crate) updated_at: DateTime<Utc>,
69}
70
71impl From<TenantRow> for Tenant {
72 fn from(row: TenantRow) -> Self {
73 Self {
74 id: row.id,
75 name: row.name,
76 slug: row.slug,
77 email: row.email,
78 status: match row.status.as_str() {
79 "suspended" => TenantStatus::Suspended,
80 "pending" => TenantStatus::Pending,
81 _ => TenantStatus::Active,
82 },
83 plan: row.plan,
84 max_domains: row.max_domains,
85 max_routes_per_domain: row.max_routes_per_domain,
86 settings: row.settings,
87 created_at: row.created_at,
88 updated_at: row.updated_at,
89 }
90 }
91}
92
93#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
95#[serde(rename_all = "snake_case")]
96#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
97pub enum VerificationStatus {
98 #[default]
100 Pending,
101 Verified,
103 Failed,
105 Expired,
107}
108
109#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
111#[serde(rename_all = "snake_case")]
112#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
113pub enum VerificationMethod {
114 #[default]
116 Dns,
117 Http,
119}
120
121#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
123#[serde(rename_all = "snake_case")]
124#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
125pub enum SslStatus {
126 #[default]
128 Pending,
129 Provisioning,
131 Active,
133 Failed,
135 Expired,
137}
138
139#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
141#[serde(rename_all = "snake_case")]
142#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
143pub enum SslProvider {
144 #[default]
146 Acme,
147 Manual,
149 None,
151}
152
153#[derive(Clone, Debug, Serialize, Deserialize)]
155pub struct Domain {
156 pub id: Uuid,
158 pub tenant_id: Uuid,
160 pub domain: String,
163 pub verification_status: VerificationStatus,
165 pub verification_method: VerificationMethod,
167 pub verification_token: String,
169 pub verification_attempts: i32,
171 pub verified_at: Option<DateTime<Utc>>,
173 pub last_verification_attempt: Option<DateTime<Utc>>,
175 pub ssl_status: SslStatus,
178 pub ssl_provider: SslProvider,
180 pub ssl_expires_at: Option<DateTime<Utc>>,
182 pub enabled: bool,
185 pub created_at: DateTime<Utc>,
187 pub updated_at: DateTime<Utc>,
189}
190
191#[derive(Clone, Debug, sqlx::FromRow)]
193pub(crate) struct DomainRow {
194 pub(crate) id: Uuid,
195 pub(crate) tenant_id: Uuid,
196 pub(crate) domain: String,
197 pub(crate) verification_status: String,
198 pub(crate) verification_method: String,
199 pub(crate) verification_token: String,
200 pub(crate) verification_attempts: i32,
201 pub(crate) verified_at: Option<DateTime<Utc>>,
202 pub(crate) last_verification_attempt: Option<DateTime<Utc>>,
203 pub(crate) ssl_status: String,
204 pub(crate) ssl_provider: String,
205 pub(crate) ssl_expires_at: Option<DateTime<Utc>>,
206 pub(crate) enabled: bool,
207 pub(crate) created_at: DateTime<Utc>,
208 pub(crate) updated_at: DateTime<Utc>,
209}
210
211impl From<DomainRow> for Domain {
212 fn from(row: DomainRow) -> Self {
213 Self {
214 id: row.id,
215 tenant_id: row.tenant_id,
216 domain: row.domain,
217 verification_status: match row.verification_status.as_str() {
218 "verified" => VerificationStatus::Verified,
219 "failed" => VerificationStatus::Failed,
220 "expired" => VerificationStatus::Expired,
221 _ => VerificationStatus::Pending,
222 },
223 verification_method: match row.verification_method.as_str() {
224 "http" => VerificationMethod::Http,
225 _ => VerificationMethod::Dns,
226 },
227 verification_token: row.verification_token,
228 verification_attempts: row.verification_attempts,
229 verified_at: row.verified_at,
230 last_verification_attempt: row.last_verification_attempt,
231 ssl_status: match row.ssl_status.as_str() {
232 "provisioning" => SslStatus::Provisioning,
233 "active" => SslStatus::Active,
234 "failed" => SslStatus::Failed,
235 "expired" => SslStatus::Expired,
236 _ => SslStatus::Pending,
237 },
238 ssl_provider: match row.ssl_provider.as_str() {
239 "manual" => SslProvider::Manual,
240 "none" => SslProvider::None,
241 _ => SslProvider::Acme,
242 },
243 ssl_expires_at: row.ssl_expires_at,
244 enabled: row.enabled,
245 created_at: row.created_at,
246 updated_at: row.updated_at,
247 }
248 }
249}
250
251#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
253#[serde(rename_all = "snake_case")]
254#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
255pub enum DomainPathMatchType {
256 #[default]
258 Prefix,
259 Exact,
261 Regex,
263}
264
265#[derive(Clone, Debug, Serialize, Deserialize)]
267pub struct DomainRoute {
268 pub id: Uuid,
270 pub domain_id: Uuid,
272 pub tenant_id: Uuid,
275 pub name: String,
277 pub path_pattern: String,
279 pub path_type: DomainPathMatchType,
281 pub methods: Option<Vec<String>>,
283 pub priority: i32,
285 pub upstream_id: Option<Uuid>,
287 pub strip_path: bool,
289 pub add_headers: HashMap<String, String>,
291 pub remove_headers: Vec<String>,
293 pub rate_limit_requests: Option<i32>,
296 pub rate_limit_window_secs: Option<i32>,
298 pub timeout_secs: i32,
300 pub enabled: bool,
302 pub created_at: DateTime<Utc>,
304 pub updated_at: DateTime<Utc>,
306}
307
308#[derive(Clone, Debug, sqlx::FromRow)]
310pub(crate) struct DomainRouteRow {
311 pub(crate) id: Uuid,
312 pub(crate) domain_id: Uuid,
313 pub(crate) tenant_id: Uuid,
314 pub(crate) name: String,
315 pub(crate) path_pattern: String,
316 pub(crate) path_type: String,
317 pub(crate) methods: Option<Vec<String>>,
318 pub(crate) priority: i32,
319 pub(crate) upstream_id: Option<Uuid>,
320 pub(crate) strip_path: bool,
321 pub(crate) add_headers: serde_json::Value,
322 pub(crate) remove_headers: Vec<String>,
323 pub(crate) rate_limit_requests: Option<i32>,
324 pub(crate) rate_limit_window_secs: Option<i32>,
325 pub(crate) timeout_secs: i32,
326 pub(crate) enabled: bool,
327 pub(crate) created_at: DateTime<Utc>,
328 pub(crate) updated_at: DateTime<Utc>,
329}
330
331impl From<DomainRouteRow> for DomainRoute {
332 fn from(row: DomainRouteRow) -> Self {
333 let add_headers: HashMap<String, String> =
334 serde_json::from_value(row.add_headers).unwrap_or_default();
335
336 Self {
337 id: row.id,
338 domain_id: row.domain_id,
339 tenant_id: row.tenant_id,
340 name: row.name,
341 path_pattern: row.path_pattern,
342 path_type: match row.path_type.as_str() {
343 "exact" => DomainPathMatchType::Exact,
344 "regex" => DomainPathMatchType::Regex,
345 _ => DomainPathMatchType::Prefix,
346 },
347 methods: row.methods,
348 priority: row.priority,
349 upstream_id: row.upstream_id,
350 strip_path: row.strip_path,
351 add_headers,
352 remove_headers: row.remove_headers,
353 rate_limit_requests: row.rate_limit_requests,
354 rate_limit_window_secs: row.rate_limit_window_secs,
355 timeout_secs: row.timeout_secs,
356 enabled: row.enabled,
357 created_at: row.created_at,
358 updated_at: row.updated_at,
359 }
360 }
361}
362
363#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
365#[serde(rename_all = "snake_case")]
366#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
367pub enum DomainLoadBalanceStrategy {
368 #[default]
370 RoundRobin,
371 LeastConnections,
373 Weighted,
375 Random,
377 Sticky,
379}
380
381#[derive(Clone, Debug, Serialize, Deserialize)]
383pub struct DomainUpstream {
384 pub id: Uuid,
386 pub tenant_id: Uuid,
388 pub name: String,
390 pub lb_strategy: DomainLoadBalanceStrategy,
392 pub health_check_enabled: bool,
394 pub health_check_path: String,
396 pub health_check_interval_secs: i32,
398 pub health_check_timeout_secs: i32,
400 pub healthy_threshold: i32,
402 pub unhealthy_threshold: i32,
404 pub enabled: bool,
406 pub created_at: DateTime<Utc>,
408 pub updated_at: DateTime<Utc>,
410 #[serde(default)]
412 pub backends: Vec<DomainBackend>,
413}
414
415#[derive(Clone, Debug, sqlx::FromRow)]
417pub(crate) struct DomainUpstreamRow {
418 pub(crate) id: Uuid,
419 pub(crate) tenant_id: Uuid,
420 pub(crate) name: String,
421 pub(crate) lb_strategy: String,
422 pub(crate) health_check_enabled: bool,
423 pub(crate) health_check_path: String,
424 pub(crate) health_check_interval_secs: i32,
425 pub(crate) health_check_timeout_secs: i32,
426 pub(crate) healthy_threshold: i32,
427 pub(crate) unhealthy_threshold: i32,
428 pub(crate) enabled: bool,
429 pub(crate) created_at: DateTime<Utc>,
430 pub(crate) updated_at: DateTime<Utc>,
431}
432
433impl From<DomainUpstreamRow> for DomainUpstream {
434 fn from(row: DomainUpstreamRow) -> Self {
435 Self {
436 id: row.id,
437 tenant_id: row.tenant_id,
438 name: row.name,
439 lb_strategy: match row.lb_strategy.as_str() {
440 "least_connections" => DomainLoadBalanceStrategy::LeastConnections,
441 "weighted" => DomainLoadBalanceStrategy::Weighted,
442 "random" => DomainLoadBalanceStrategy::Random,
443 "sticky" => DomainLoadBalanceStrategy::Sticky,
444 _ => DomainLoadBalanceStrategy::RoundRobin,
445 },
446 health_check_enabled: row.health_check_enabled,
447 health_check_path: row.health_check_path,
448 health_check_interval_secs: row.health_check_interval_secs,
449 health_check_timeout_secs: row.health_check_timeout_secs,
450 healthy_threshold: row.healthy_threshold,
451 unhealthy_threshold: row.unhealthy_threshold,
452 enabled: row.enabled,
453 created_at: row.created_at,
454 updated_at: row.updated_at,
455 backends: Vec::new(),
456 }
457 }
458}
459
460#[derive(Clone, Debug, Serialize, Deserialize, sqlx::FromRow)]
462pub struct DomainBackend {
463 pub id: Uuid,
465 pub upstream_id: Uuid,
467 pub address: String,
469 pub scheme: String,
471 pub weight: i32,
473 pub enabled: bool,
475 pub created_at: DateTime<Utc>,
477}
478
479#[derive(Clone, Debug, Serialize, Deserialize)]
481pub struct ApiKey {
482 pub id: Uuid,
484 pub tenant_id: Uuid,
486 pub name: String,
488 #[serde(skip_serializing_if = "Option::is_none")]
493 pub key: Option<String>,
494 pub key_prefix: String,
497 pub scopes: Vec<String>,
499 pub last_used_at: Option<DateTime<Utc>>,
501 pub expires_at: Option<DateTime<Utc>>,
503 pub enabled: bool,
506 pub created_at: DateTime<Utc>,
508}
509
510#[derive(Clone, Debug, sqlx::FromRow)]
512pub(crate) struct ApiKeyRow {
513 pub(crate) id: Uuid,
514 pub(crate) tenant_id: Uuid,
515 pub(crate) name: String,
516 pub(crate) key_prefix: String,
517 pub(crate) scopes: Vec<String>,
518 pub(crate) last_used_at: Option<DateTime<Utc>>,
519 pub(crate) expires_at: Option<DateTime<Utc>>,
520 pub(crate) enabled: bool,
521 pub(crate) created_at: DateTime<Utc>,
522}
523
524impl From<ApiKeyRow> for ApiKey {
525 fn from(row: ApiKeyRow) -> Self {
526 Self {
527 id: row.id,
528 tenant_id: row.tenant_id,
529 name: row.name,
530 key: None, key_prefix: row.key_prefix,
532 scopes: row.scopes,
533 last_used_at: row.last_used_at,
534 expires_at: row.expires_at,
535 enabled: row.enabled,
536 created_at: row.created_at,
537 }
538 }
539}
540
541#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
543#[serde(rename_all = "snake_case")]
544pub enum ChallengeStatus {
545 #[default]
547 Pending,
548 Checking,
550 Verified,
552 Failed,
554}
555
556#[derive(Clone, Debug, Serialize, Deserialize, sqlx::FromRow)]
558pub struct VerificationChallenge {
559 pub id: Uuid,
561 pub domain_id: Uuid,
563 pub challenge_type: String,
565 pub token: String,
567 pub expected_value: String,
571 pub status: String,
573 pub error_message: Option<String>,
575 pub created_at: DateTime<Utc>,
577 pub expires_at: DateTime<Utc>,
579 pub verified_at: Option<DateTime<Utc>>,
581}
582
583#[derive(Clone, Debug, Serialize, Deserialize, sqlx::FromRow)]
585pub struct AuditLogEntry {
586 pub id: Uuid,
588 pub tenant_id: Option<Uuid>,
590 pub domain_id: Option<Uuid>,
592 pub action: String,
594 pub actor_type: String,
596 pub actor_id: Option<String>,
598 pub details: serde_json::Value,
600 pub ip_address: Option<IpAddr>,
602 pub created_at: DateTime<Utc>,
604}
605
606#[derive(Clone, Debug, Deserialize)]
612pub struct CreateTenantRequest {
613 pub name: String,
615 pub slug: String,
617 pub email: String,
619 pub plan: Option<String>,
621}
622
623#[derive(Clone, Debug, Deserialize)]
625pub struct UpdateTenantRequest {
626 pub name: Option<String>,
628 pub email: Option<String>,
630 pub plan: Option<String>,
632 pub settings: Option<serde_json::Value>,
634}
635
636#[derive(Clone, Debug, Deserialize)]
638pub struct CreateDomainRequest {
639 pub domain: String,
641 #[serde(default)]
646 pub verification_method: VerificationMethod,
647 #[serde(default)]
649 pub ssl_provider: SslProvider,
650}
651
652#[derive(Clone, Debug, Deserialize, Default)]
659pub struct UpdateDomainRequest {
660 #[serde(default)]
665 pub verification_method: Option<VerificationMethod>,
666
667 #[serde(default)]
671 pub ssl_provider: Option<SslProvider>,
672}
673
674#[derive(Clone, Debug, Serialize)]
676pub struct DomainResponse {
677 #[serde(flatten)]
680 pub domain: Domain,
681 pub verification_instructions: VerificationInstructions,
683}
684
685#[derive(Clone, Debug, Serialize)]
687pub struct VerificationInstructions {
688 pub method: VerificationMethod,
691 #[serde(skip_serializing_if = "Option::is_none")]
693 pub dns_record_type: Option<String>,
694 #[serde(skip_serializing_if = "Option::is_none")]
696 pub dns_record_name: Option<String>,
697 #[serde(skip_serializing_if = "Option::is_none")]
699 pub dns_record_value: Option<String>,
700 #[serde(skip_serializing_if = "Option::is_none")]
702 pub http_url: Option<String>,
703 #[serde(skip_serializing_if = "Option::is_none")]
705 pub http_expected_content: Option<String>,
706}
707
708impl VerificationInstructions {
709 pub fn dns(domain: &str, token: &str) -> Self {
711 Self {
712 method: VerificationMethod::Dns,
713 dns_record_type: Some("TXT".to_string()),
714 dns_record_name: Some(format!("_postrust-verification.{}", domain)),
715 dns_record_value: Some(format!("postrust-verify={}", token)),
716 http_url: None,
717 http_expected_content: None,
718 }
719 }
720
721 pub fn http(domain: &str, token: &str) -> Self {
723 Self {
724 method: VerificationMethod::Http,
725 dns_record_type: None,
726 dns_record_name: None,
727 dns_record_value: None,
728 http_url: Some(format!(
729 "https://{}/.well-known/postrust-verification/{}",
730 domain, token
731 )),
732 http_expected_content: Some(format!("postrust-verify={}", token)),
733 }
734 }
735}
736
737#[derive(Clone, Debug, Serialize)]
739#[serde(rename_all = "snake_case")]
740pub enum VerificationResult {
741 Verified,
743 Pending,
745 Failed {
747 reason: String,
749 },
750}
751
752#[derive(Clone, Debug, Deserialize)]
754pub struct CreateDomainRouteRequest {
755 pub name: String,
757 #[serde(default = "default_path_pattern")]
759 pub path_pattern: String,
760 #[serde(default)]
762 pub path_type: DomainPathMatchType,
763 pub methods: Option<Vec<String>>,
765 pub upstream_id: Uuid,
767 #[serde(default)]
769 pub strip_path: bool,
770 #[serde(default = "default_priority")]
772 pub priority: i32,
773 #[serde(default)]
775 pub add_headers: HashMap<String, String>,
776 #[serde(default)]
778 pub remove_headers: Vec<String>,
779 pub rate_limit_requests: Option<i32>,
781 pub rate_limit_window_secs: Option<i32>,
783 #[serde(default = "default_timeout")]
785 pub timeout_secs: i32,
786}
787
788fn default_path_pattern() -> String {
789 "/".to_string()
790}
791
792fn default_priority() -> i32 {
793 100
794}
795
796fn default_timeout() -> i32 {
797 30
798}
799
800#[derive(Clone, Debug, Deserialize)]
802pub struct UpdateDomainRouteRequest {
803 pub name: Option<String>,
805 pub path_pattern: Option<String>,
807 pub path_type: Option<DomainPathMatchType>,
809 pub methods: Option<Vec<String>>,
811 pub upstream_id: Option<Uuid>,
813 pub strip_path: Option<bool>,
815 pub priority: Option<i32>,
817 pub add_headers: Option<HashMap<String, String>>,
819 pub remove_headers: Option<Vec<String>>,
821 pub rate_limit_requests: Option<i32>,
823 pub rate_limit_window_secs: Option<i32>,
825 pub timeout_secs: Option<i32>,
827 pub enabled: Option<bool>,
829}
830
831#[derive(Clone, Debug, Deserialize)]
833pub struct CreateUpstreamRequest {
834 pub name: String,
836 #[serde(default)]
838 pub lb_strategy: DomainLoadBalanceStrategy,
839 #[serde(default = "default_true")]
841 pub health_check_enabled: bool,
842 #[serde(default = "default_health_path")]
844 pub health_check_path: String,
845 #[serde(default = "default_health_interval")]
847 pub health_check_interval_secs: i32,
848 #[serde(default = "default_health_timeout")]
850 pub health_check_timeout_secs: i32,
851 #[serde(default = "default_healthy_threshold")]
853 pub healthy_threshold: i32,
854 #[serde(default = "default_unhealthy_threshold")]
856 pub unhealthy_threshold: i32,
857 #[serde(default)]
859 pub backends: Vec<CreateBackendRequest>,
860}
861
862fn default_true() -> bool {
863 true
864}
865
866fn default_health_path() -> String {
867 "/health".to_string()
868}
869
870fn default_health_interval() -> i32 {
871 30
872}
873
874fn default_health_timeout() -> i32 {
875 5
876}
877
878fn default_healthy_threshold() -> i32 {
879 2
880}
881
882fn default_unhealthy_threshold() -> i32 {
883 3
884}
885
886#[derive(Clone, Debug, Deserialize)]
888pub struct UpdateUpstreamRequest {
889 pub name: Option<String>,
891 pub lb_strategy: Option<DomainLoadBalanceStrategy>,
893 pub health_check_enabled: Option<bool>,
895 pub health_check_path: Option<String>,
897 pub health_check_interval_secs: Option<i32>,
899 pub health_check_timeout_secs: Option<i32>,
901 pub healthy_threshold: Option<i32>,
903 pub unhealthy_threshold: Option<i32>,
905 pub enabled: Option<bool>,
911}
912
913#[derive(Clone, Debug, Deserialize)]
915pub struct CreateBackendRequest {
916 pub address: String,
918 #[serde(default = "default_scheme")]
920 pub scheme: String,
921 #[serde(default = "default_weight")]
923 pub weight: i32,
924}
925
926fn default_scheme() -> String {
927 "http".to_string()
928}
929
930fn default_weight() -> i32 {
931 100
932}
933
934#[derive(Clone, Debug, Deserialize)]
936pub struct CreateApiKeyRequest {
937 pub name: String,
939 #[serde(default = "default_scopes")]
942 pub scopes: Vec<String>,
943 pub expires_at: Option<DateTime<Utc>>,
945}
946
947fn default_scopes() -> Vec<String> {
948 vec!["domains:read".to_string(), "domains:write".to_string()]
949}
950
951#[derive(Clone, Debug, Deserialize)]
953pub struct UploadCertificateRequest {
954 pub cert_pem: String,
956 pub key_pem: String,
960}
961
962#[derive(Clone, Debug, Serialize)]
964pub struct TenantUsage {
965 pub domains_count: i64,
967 pub domains_limit: i32,
969 pub verified_domains: i64,
971 pub routes_count: i64,
973 pub upstreams_count: i64,
975 pub api_keys_count: i64,
977}