koprogo_api/infrastructure/
openapi.rs1use utoipa::{
5 openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme},
6 Modify, OpenApi,
7};
8use utoipa_swagger_ui::SwaggerUi;
9
10#[derive(OpenApi)]
16#[openapi(
17 info(
18 title = "KoproGo API",
19 version = "1.0.0",
20 description = "Belgian Property Management SaaS Platform\n\n\
21 # Features\n\
22 - ๐ข Building & Unit Management\n\
23 - ๐ฅ Multi-owner & Multi-role Support\n\
24 - ๐ฐ Financial Management (Belgian PCMN)\n\
25 - ๐ณ๏ธ Meeting & Voting System\n\
26 - ๐ Document Management\n\
27 - ๐ Budget & รtat Datรฉ Generation\n\
28 - ๐ Notifications & Payment Recovery\n\
29 - ๐ค Community Features (SEL, Notices, Skills)\n\
30 - ๐ฎ Gamification & Achievements\n\
31 - ๐ GDPR Compliant\n\n\
32 # Authentication\n\
33 All endpoints (except /health and /public/*) require JWT Bearer token.\n\
34 Get token via POST /api/v1/auth/login\n\n\
35 # Complete API Documentation\n\
36 This OpenAPI spec is incrementally being built. Currently includes:\n\
37 - Health check endpoint\n\
38 - ~400 additional endpoints available (see routes.rs)\n\n\
39 To add endpoints to this spec, annotate handlers with #[utoipa::path(...)]\n\
40 See health.rs for example implementation.",
41 contact(
42 name = "KoproGo Support",
43 email = "support@koprogo.com"
44 ),
45 license(
46 name = "AGPL-3.0-or-later",
47 url = "https://www.gnu.org/licenses/agpl-3.0.en.html"
48 ),
49 ),
50 servers(
51 (url = "http://localhost:8080", description = "Local development"),
52 (url = "https://api.koprogo.com", description = "Production"),
53 ),
54 paths(
55 ),
59 components(
60 schemas(
61 )
63 ),
64 modifiers(&SecurityAddon),
65 tags(
66 (name = "Health", description = "System health and monitoring"),
67 (name = "Auth", description = "Authentication and authorization"),
68 (name = "Buildings", description = "Building management"),
69 (name = "Units", description = "Unit management"),
70 (name = "Owners", description = "Owner management"),
71 (name = "Expenses", description = "Expense and invoice management"),
72 (name = "Meetings", description = "General assembly management"),
73 (name = "Budgets", description = "Annual budget management"),
74 (name = "Documents", description = "Document upload/download"),
75 (name = "GDPR", description = "Data privacy compliance"),
76 (name = "Payments", description = "Payment processing"),
77 (name = "PaymentMethods", description = "Stored payment methods"),
78 (name = "LocalExchanges", description = "SEL time-based exchange system"),
79 (name = "Notifications", description = "Multi-channel notifications"),
80 (name = "Tickets", description = "Maintenance request system"),
81 (name = "Resolutions", description = "Meeting voting system"),
82 (name = "BoardMembers", description = "Board of directors management"),
83 (name = "Quotes", description = "Contractor quote management"),
84 (name = "EtatsDates", description = "Property sale documentation"),
85 (name = "PaymentRecovery", description = "Automated payment reminders"),
86 )
87)]
88pub struct ApiDoc;
89
90struct SecurityAddon;
92
93impl Modify for SecurityAddon {
94 fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
95 if let Some(components) = openapi.components.as_mut() {
96 components.add_security_scheme(
97 "bearer_auth",
98 SecurityScheme::Http(
99 HttpBuilder::new()
100 .scheme(HttpAuthScheme::Bearer)
101 .bearer_format("JWT")
102 .description(Some(
103 "JWT token obtained from /api/v1/auth/login.\n\n\
104 Example: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`\n\n\
105 To authenticate:\n\
106 1. Click 'Authorize' button above\n\
107 2. Enter token (with or without 'Bearer ' prefix)\n\
108 3. Click 'Authorize' in dialog\n\
109 4. Try endpoints",
110 ))
111 .build(),
112 ),
113 )
114 }
115 }
116}
117
118pub fn configure_swagger_ui() -> SwaggerUi {
122 SwaggerUi::new("/swagger-ui/{_:.*}")
123 .url("/api-docs/openapi.json", ApiDoc::openapi())
124 .config(
125 utoipa_swagger_ui::Config::default()
126 .try_it_out_enabled(true)
127 .persist_authorization(true)
128 .display_request_duration(true)
129 .deep_linking(true)
130 .display_operation_id(true)
131 .default_models_expand_depth(1)
132 .default_model_expand_depth(1), )
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_openapi_spec_generation() {
142 let spec = ApiDoc::openapi();
143
144 assert_eq!(spec.info.title, "KoproGo API");
146 assert_eq!(spec.info.version, "1.0.0");
147
148 assert!(spec.servers.is_some());
150 let servers = spec.servers.unwrap();
151 assert_eq!(servers.len(), 2);
152
153 assert!(spec.components.is_some());
155 let components = spec.components.unwrap();
156 assert!(components.security_schemes.contains_key("bearer_auth"));
157
158 assert!(spec.tags.is_some());
160 let tags = spec.tags.unwrap();
161 assert!(tags.len() >= 15);
162 }
163
164 #[test]
165 fn test_swagger_ui_configuration() {
166 let _swagger = configure_swagger_ui();
167 }
169
170 #[test]
171 fn test_openapi_json_is_valid() {
172 let spec = ApiDoc::openapi();
173
174 let json = serde_json::to_string(&spec).expect("Should serialize to JSON");
176 assert!(json.contains("\"title\":\"KoproGo API\""));
177 assert!(json.contains("\"version\":\"1.0.0\""));
178 }
179}