PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
system_endpoints.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
13// IMPORTANT: Include Crow FIRST before any PACS headers to avoid forward
14// declaration conflicts
15#include "crow.h"
16
22
23#ifdef PACS_WITH_MONITORING
27#endif
28
29#include <sstream>
30
32
33namespace {
34
38void add_cors_headers(crow::response &res, const rest_server_context &ctx) {
39 if (ctx.config && !ctx.config->cors_allowed_origins.empty()) {
40 res.add_header("Access-Control-Allow-Origin",
41 ctx.config->cors_allowed_origins);
42 }
43}
44
48std::string config_to_json(const rest_server_config &config) {
49 std::ostringstream oss;
50 oss << R"({"bind_address":")" << config.bind_address << R"(",)"
51 << R"("port":)" << config.port << R"(,)"
52 << R"("concurrency":)" << config.concurrency << R"(,)"
53 << R"("enable_cors":)" << (config.enable_cors ? "true" : "false")
54 << R"(,)"
55 << R"("cors_allowed_origins":")" << config.cors_allowed_origins << R"(",)"
56 << R"("enable_tls":)" << (config.enable_tls ? "true" : "false") << R"(,)"
57 << R"("request_timeout_seconds":)" << config.request_timeout_seconds
58 << R"(,)"
59 << R"("max_body_size":)" << config.max_body_size << "}";
60 return oss.str();
61}
62
63// Helper to check authentication and permission
64// Returns true if allowed, false if denied (and sets response)
65bool check_permission(const std::shared_ptr<rest_server_context> &ctx,
66 const crow::request &req, crow::response &res,
67 security::ResourceType resource, uint32_t action) {
68 if (!ctx->security_manager) {
69 // Fail open or closed? Closed is safer, but for dev maybe open if not
70 // configured? Let's fail closed.
71 res.code = 503;
72 res.body = make_error_json("SECURITY_UNAVAILABLE",
73 "Security manager not configured");
74 return false;
75 }
76
77 // Simple Auth via Header for now (Integration step)
78 auto user_id_header = req.get_header_value("X-User-ID");
79 if (user_id_header.empty()) {
80 res.code = 401;
81 res.body = make_error_json("UNAUTHORIZED", "Missing X-User-ID header");
82 return false;
83 }
84
85 std::string user_id = user_id_header;
86 auto user_res = ctx->security_manager->get_user(user_id);
87 if (user_res.is_err()) {
88 res.code = 401;
89 res.body = make_error_json("UNAUTHORIZED", "Invalid user");
90 return false;
91 }
92
93 auto user = user_res.unwrap();
94 if (!ctx->security_manager->check_permission(user, resource, action)) {
95 res.code = 403;
96 res.body = make_error_json("FORBIDDEN", "Insufficient permissions");
97 return false;
98 }
99
100 return true;
101}
102
103} // namespace
104
105// Internal implementation function called from rest_server.cpp
106void register_system_endpoints_impl(crow::SimpleApp &app,
107 std::shared_ptr<rest_server_context> ctx) {
108 // GET /api/v1/system/status - System health status
109 CROW_ROUTE(app, "/api/v1/system/status")
110 .methods(crow::HTTPMethod::GET)([ctx](const crow::request &req) {
111 crow::response res;
112 res.add_header("Content-Type", "application/json");
113 add_cors_headers(res, *ctx);
114
115 // Permission Check: System Read
116 if (!check_permission(ctx, req, res, security::ResourceType::System,
118 return res;
119 }
120
121 crow::json::wvalue status_json;
122 status_json["status"] = "unknown"; // Default
123 status_json["message"] = "Health checker not configured"; // Default
124 status_json["version"] = "1.0.0"; // Default
125
126#ifdef PACS_WITH_MONITORING
127 if (ctx->health_checker) {
128 auto status = ctx->health_checker->get_status();
129 // to_json is a free function in kcenon::pacs::monitoring namespace
130 status_json = crow::json::load(
131 monitoring::to_json(status)); // Parse existing JSON into wvalue
132 status_json["version"] = "1.0.0"; // Add version
133 } else {
134 status_json["status"] = "unknown";
135 status_json["message"] = "Health checker not configured";
136 }
137#else
138 // Basic status without monitoring module
139 res.code = 200;
140#endif
141 return res;
142 });
143
144 // GET /api/v1/system/metrics - Performance metrics
145 CROW_ROUTE(app, "/api/v1/system/metrics").methods(crow::HTTPMethod::GET)([ctx]() {
146 crow::response res;
147 res.add_header("Content-Type", "application/json");
148 add_cors_headers(res, *ctx);
149
150#ifdef PACS_WITH_MONITORING
151 if (ctx->metrics) {
152 // pacs_metrics provides individual counters, build simple JSON
153 std::ostringstream oss;
154 oss << R"({"message":"Metrics available via pacs_metrics API"})";
155 res.body = oss.str();
156 res.code = 200;
157 } else {
158 res.body =
159 R"({"error":{"code":"METRICS_UNAVAILABLE","message":"Metrics provider not configured"}})";
160 res.code = 503;
161 }
162#else
163 // Basic metrics without monitoring module
164 res.body =
165 R"({"uptime_seconds":0,"requests_total":0,"message":"Metrics module not available"})";
166 res.code = 200;
167#endif
168 return res;
169 });
170
171 // GET /api/v1/system/config - Current configuration
172 CROW_ROUTE(app, "/api/v1/system/config")
173 .methods(crow::HTTPMethod::GET)([ctx]() {
174 crow::response res;
175 res.add_header("Content-Type", "application/json");
176 add_cors_headers(res, *ctx);
177
178 if (ctx->config) {
179 res.body = config_to_json(*ctx->config);
180 res.code = 200;
181 } else {
182 res.body = make_error_json("CONFIG_UNAVAILABLE",
183 "Configuration not available");
184 res.code = 500;
185 }
186 return res;
187 });
188
189 // PUT /api/v1/system/config - Update configuration
190 CROW_ROUTE(app, "/api/v1/system/config")
191 .methods(crow::HTTPMethod::PUT)([ctx](const crow::request &req) {
192 crow::response res;
193 res.add_header("Content-Type", "application/json");
194 add_cors_headers(res, *ctx);
195
196 // Validate content type
197 auto content_type = req.get_header_value("Content-Type");
198 if (content_type.find("application/json") == std::string::npos) {
199 res.body = make_error_json("INVALID_CONTENT_TYPE",
200 "Content-Type must be application/json");
201 res.code = 415;
202 return res;
203 }
204
205 // Validate body is not empty
206 if (req.body.empty()) {
207 res.body = make_error_json("EMPTY_BODY", "Request body is required");
208 res.code = 400;
209 return res;
210 }
211
212 // Note: Full configuration update would require parsing JSON and
213 // updating the config. For now, we return a placeholder success
214 // response. Actual implementation would need a callback or direct
215 // config reference.
216 res.body = make_success_json("Configuration update acknowledged");
217 res.code = 200;
218 return res;
219 });
220
221 // GET /api/v1/system/version - API version info
222 CROW_ROUTE(app, "/api/v1/system/version").methods(crow::HTTPMethod::GET)([ctx]() {
223 crow::response res;
224 res.add_header("Content-Type", "application/json");
225 add_cors_headers(res, *ctx);
226
227 res.body =
228 R"({"api_version":"v1","pacs_version":"1.2.0","crow_version":"1.2.0"})";
229 res.code = 200;
230 return res;
231 });
232}
233
234} // namespace kcenon::pacs::web::endpoints
Core RBAC logic.
Health check service for PACS system components.
JSON serialization for health check data structures.
constexpr std::uint32_t Read
Definition permission.h:41
ResourceType
Categories of resources requiring protection.
Definition permission.h:25
@ System
System configuration and services.
void register_system_endpoints_impl(crow::SimpleApp &app, std::shared_ptr< rest_server_context > ctx)
std::string make_error_json(std::string_view code, std::string_view message)
Create JSON error response body with details.
Definition rest_types.h:79
std::string make_success_json(std::string_view message="OK")
Create success response with optional message.
Definition rest_types.h:91
Operation metrics collection for PACS DICOM services.
Configuration for REST API server.
Common types and utilities for REST API.
System API endpoints for REST server.
User definition for RBAC.