PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
viewer_state_record_repository.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
14
15#include <chrono>
16#include <cstdio>
17#include <sstream>
18
19#ifdef PACS_WITH_DATABASE_SYSTEM
20
21namespace kcenon::pacs::storage {
22
23namespace {
24
26[[nodiscard]] std::string to_timestamp_string(
27 std::chrono::system_clock::time_point tp) {
28 if (tp == std::chrono::system_clock::time_point{}) {
29 return "";
30 }
31 auto time = std::chrono::system_clock::to_time_t(tp);
32 std::tm tm{};
33#ifdef _WIN32
34 gmtime_s(&tm, &time);
35#else
36 gmtime_r(&time, &tm);
37#endif
38 auto since_epoch = tp.time_since_epoch();
39 auto secs = std::chrono::duration_cast<std::chrono::seconds>(since_epoch);
40 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
41 since_epoch - secs);
42
43 char buf[32];
44 std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
45 char result[40];
46 std::snprintf(
47 result, sizeof(result), "%s.%03d", buf, static_cast<int>(ms.count()));
48 return result;
49}
50
52[[nodiscard]] std::chrono::system_clock::time_point from_timestamp_string(
53 const std::string& str) {
54 if (str.empty()) {
55 return {};
56 }
57 std::tm tm{};
58 int ms = 0;
59 if (std::sscanf(str.c_str(),
60 "%d-%d-%d %d:%d:%d.%d",
61 &tm.tm_year,
62 &tm.tm_mon,
63 &tm.tm_mday,
64 &tm.tm_hour,
65 &tm.tm_min,
66 &tm.tm_sec,
67 &ms) < 6) {
68 return {};
69 }
70 tm.tm_year -= 1900;
71 tm.tm_mon -= 1;
72#ifdef _WIN32
73 auto time = _mkgmtime(&tm);
74#else
75 auto time = timegm(&tm);
76#endif
77 auto tp = std::chrono::system_clock::from_time_t(time);
78 return tp + std::chrono::milliseconds(ms);
79}
80
81} // namespace
82
83// =============================================================================
84// Constructor
85// =============================================================================
86
87viewer_state_record_repository::viewer_state_record_repository(
88 std::shared_ptr<pacs_database_adapter> db)
89 : base_repository(std::move(db), "viewer_states", "state_id") {}
90
91// =============================================================================
92// Domain-Specific Queries
93// =============================================================================
94
95auto viewer_state_record_repository::find_by_study(std::string_view study_uid)
96 -> list_result_type {
97 return find_where("study_uid", "=", std::string(study_uid));
98}
99
100auto viewer_state_record_repository::find_by_user(std::string_view user_id)
101 -> list_result_type {
102 return find_where("user_id", "=", std::string(user_id));
103}
104
105auto viewer_state_record_repository::find_by_study_and_user(
106 std::string_view study_uid,
107 std::string_view user_id) -> list_result_type {
108 if (!db() || !db()->is_connected()) {
109 return list_result_type(kcenon::common::error_info{
110 -1, "Database not connected", "viewer_state_record_repository"});
111 }
112
113 auto study_cond = database::query_condition(
114 "study_uid", "=", std::string(study_uid));
115 auto user_cond = database::query_condition(
116 "user_id", "=", std::string(user_id));
117
118 auto builder = storage_session().create_query_builder();
119 builder.select(select_columns())
120 .from(table_name())
121 .where(study_cond && user_cond)
122 .order_by("updated_at", database::sort_order::desc);
123
124 auto query = builder.build();
125 auto result = storage_session().select(query);
126
127 if (result.is_err()) {
128 return list_result_type(result.error());
129 }
130
131 std::vector<viewer_state_record> entities;
132 entities.reserve(result.value().size());
133
134 try {
135 for (const auto& row : result.value()) {
136 entities.push_back(map_row_to_entity(row));
137 }
138 return list_result_type(std::move(entities));
139 } catch (const std::exception& e) {
140 return list_result_type(kcenon::common::error_info{
141 -1,
142 std::string("Failed to map rows to entities: ") + e.what(),
143 "viewer_state_record_repository"});
144 }
145}
146
147auto viewer_state_record_repository::search(const viewer_state_query& query)
148 -> list_result_type {
149 if (!db() || !db()->is_connected()) {
150 return list_result_type(kcenon::common::error_info{
151 -1, "Database not connected", "viewer_state_record_repository"});
152 }
153
154 auto builder = storage_session().create_query_builder();
155 builder.select(select_columns()).from(table_name());
156
157 // Build condition based on query filters
158 std::optional<database::query_condition> condition;
159
160 if (query.study_uid.has_value()) {
161 condition = database::query_condition(
162 "study_uid", "=", query.study_uid.value());
163 }
164
165 if (query.user_id.has_value()) {
166 auto user_cond = database::query_condition(
167 "user_id", "=", query.user_id.value());
168 if (condition.has_value()) {
169 condition = condition.value() && user_cond;
170 } else {
171 condition = user_cond;
172 }
173 }
174
175 if (condition.has_value()) {
176 builder.where(condition.value());
177 }
178
179 builder.order_by("updated_at", database::sort_order::desc);
180
181 if (query.limit > 0) {
182 builder.limit(query.limit);
183 if (query.offset > 0) {
184 builder.offset(query.offset);
185 }
186 }
187
188 auto sql = builder.build();
189 auto result = storage_session().select(sql);
190
191 if (result.is_err()) {
192 return list_result_type(result.error());
193 }
194
195 std::vector<viewer_state_record> entities;
196 entities.reserve(result.value().size());
197
198 try {
199 for (const auto& row : result.value()) {
200 entities.push_back(map_row_to_entity(row));
201 }
202 return list_result_type(std::move(entities));
203 } catch (const std::exception& e) {
204 return list_result_type(kcenon::common::error_info{
205 -1,
206 std::string("Failed to map rows to entities: ") + e.what(),
207 "viewer_state_record_repository"});
208 }
209}
210
211// =============================================================================
212// base_repository Overrides
213// =============================================================================
214
215auto viewer_state_record_repository::map_row_to_entity(
216 const database_row& row) const -> viewer_state_record {
217 viewer_state_record record;
218
219 record.pk = std::stoll(row.at("pk"));
220 record.state_id = row.at("state_id");
221 record.study_uid = row.at("study_uid");
222 record.user_id = row.at("user_id");
223 record.state_json = row.at("state_json");
224 record.created_at = parse_timestamp(row.at("created_at"));
225 record.updated_at = parse_timestamp(row.at("updated_at"));
226
227 return record;
228}
229
230auto viewer_state_record_repository::entity_to_row(
231 const viewer_state_record& entity) const
232 -> std::map<std::string, database_value> {
233 auto now_str = format_timestamp(std::chrono::system_clock::now());
234
235 return {{"state_id", entity.state_id},
236 {"study_uid", entity.study_uid},
237 {"user_id", entity.user_id},
238 {"state_json", entity.state_json},
239 {"created_at", now_str},
240 {"updated_at", now_str}};
241}
242
243auto viewer_state_record_repository::get_pk(
244 const viewer_state_record& entity) const -> std::string {
245 return entity.state_id;
246}
247
248auto viewer_state_record_repository::has_pk(
249 const viewer_state_record& entity) const -> bool {
250 return !entity.state_id.empty();
251}
252
253auto viewer_state_record_repository::select_columns() const
254 -> std::vector<std::string> {
255 return {"pk",
256 "state_id",
257 "study_uid",
258 "user_id",
259 "state_json",
260 "created_at",
261 "updated_at"};
262}
263
264// =============================================================================
265// Private Helpers
266// =============================================================================
267
268auto viewer_state_record_repository::parse_timestamp(const std::string& str)
269 const -> std::chrono::system_clock::time_point {
270 return from_timestamp_string(str);
271}
272
273auto viewer_state_record_repository::format_timestamp(
274 std::chrono::system_clock::time_point tp) const -> std::string {
275 return to_timestamp_string(tp);
276}
277
278} // namespace kcenon::pacs::storage
279
280#endif // PACS_WITH_DATABASE_SYSTEM
@ move
C-MOVE move request/response.
const atna_coded_value query
Query (110112)
Repository for viewer state records using base_repository pattern.