1#![allow(non_snake_case)]
4
5use crate::{
25 DataError, MyError,
26 db::{
27 actor::find_agent_id_from_str,
28 agent_profile::{find, find_ids, remove, upsert},
29 },
30 eval_preconditions,
31 lrs::{
32 DB, Headers, User, WithDocumentOrIDs, emit_doc_response, etag_from_str, no_content,
33 resources::WithETag,
34 },
35};
36use chrono::{DateTime, Utc};
37use rocket::{State, delete, get, http::Status, post, put, routes};
38use serde_json::{Map, Value};
39use sqlx::PgPool;
40use std::mem;
41use tracing::{debug, info};
42
43#[doc(hidden)]
44pub fn routes() -> Vec<rocket::Route> {
45 routes![put, post, delete, get]
46}
47
48#[put("/?<agent>&<profileId>", data = "<doc>")]
51async fn put(
52 c: Headers,
53 agent: &str,
54 profileId: &str,
55 doc: &str,
56 db: &State<DB>,
57 user: User,
58) -> Result<WithETag, MyError> {
59 debug!("----- put ----- {}", user);
60 user.can_use_xapi()?;
61
62 if doc.is_empty() {
63 return Err(MyError::HTTP {
64 status: Status::BadRequest,
65 info: "Document must NOT be an empty string".into(),
66 });
67 }
68
69 if c.is_json_content() {
71 serde_json::from_str::<Map<String, Value>>(doc)
72 .map_err(|x| MyError::Data(DataError::JSON(x)).with_status(Status::BadRequest))?;
73 }
74
75 let conn = db.pool();
76 match find_agent_id_from_str(conn, agent).await {
77 Ok(agent_id) => {
78 debug!("agent_id = {}", agent_id);
79
80 let (x, _) = find(conn, agent_id, profileId).await?;
83 match x {
84 None => {
85 let etag = etag_from_str(doc);
87 upsert(conn, agent_id, profileId, doc).await?;
88 Ok(no_content(&etag))
89 }
90 Some(old_doc) => {
91 if c.has_no_conditionals() {
92 Err(MyError::HTTP {
93 status: Status::Conflict,
94 info: "PUT a known resource, w/ no pre-conditions, is NOT allowed"
95 .into(),
96 })
97 } else {
98 let etag = etag_from_str(&old_doc);
100 debug!("etag (old) = {}", etag);
101 match eval_preconditions!(&etag, c) {
102 s if s != Status::Ok => Err(MyError::HTTP {
103 status: s,
104 info: "Failed pre-condition(s)".into(),
105 }),
106 _ => {
107 if old_doc == doc {
108 info!("Old + new Agent Profile documents are identical");
109 Ok(no_content(&etag))
110 } else {
111 let etag = etag_from_str(doc);
112 upsert(conn, agent_id, profileId, doc).await?;
113 Ok(no_content(&etag))
114 }
115 }
116 }
117 }
118 }
119 }
120 }
121 Err(x) => match x {
122 MyError::Data(_) => Err(x.with_status(Status::BadRequest)),
123 x => Err(x),
124 },
125 }
126}
127
128#[post("/?<agent>&<profileId>", data = "<doc>")]
131async fn post(
132 c: Headers,
133 agent: &str,
134 profileId: &str,
135 doc: &str,
136 db: &State<DB>,
137 user: User,
138) -> Result<WithETag, MyError> {
139 debug!("----- post ----- {}", user);
140 user.can_use_xapi()?;
141
142 if doc.is_empty() {
143 return Err(MyError::HTTP {
144 status: Status::BadRequest,
145 info: "Document must NOT be an empty string".into(),
146 });
147 }
148
149 if c.is_json_content() {
151 serde_json::from_str::<Map<String, Value>>(doc)
152 .map_err(|x| MyError::Data(DataError::JSON(x)).with_status(Status::BadRequest))?;
153 }
154
155 let conn = db.pool();
156 match find_agent_id_from_str(conn, agent).await {
157 Ok(agent_id) => {
158 debug!("agent_id = {}", agent_id);
159
160 let (x, _) = find(conn, agent_id, profileId).await?;
161 match x {
162 None => {
163 upsert(conn, agent_id, profileId, doc).await?;
165 let etag = etag_from_str(doc);
166 Ok(no_content(&etag))
167 }
168 Some(old_doc) => {
169 let etag = etag_from_str(&old_doc);
170 debug!("etag (old) = {}", etag);
171 if c.has_conditionals() {
172 match eval_preconditions!(&etag, c) {
173 s if s != Status::Ok => {
174 return Err(MyError::HTTP {
175 status: s,
176 info: "Failed pre-condition(s)".into(),
177 });
178 }
179 _ => (),
180 }
181 }
182
183 let mut old: Map<String, Value> =
184 serde_json::from_str(&old_doc).map_err(|x| {
185 MyError::Data(DataError::JSON(x)).with_status(Status::BadRequest)
186 })?;
187
188 let mut new: Map<String, Value> = serde_json::from_str(doc).map_err(|x| {
189 MyError::Data(DataError::JSON(x)).with_status(Status::BadRequest)
190 })?;
191
192 if old == new {
193 info!("Old + new Agent Profile documents are identical");
194 return Ok(no_content(&etag));
195 }
196
197 debug!("document (before) = '{}'", old_doc);
198 for (k, v) in new.iter_mut() {
199 let new_v = mem::take(v);
200 old.insert(k.to_owned(), new_v);
201 }
202 let merged =
204 serde_json::to_string(&old).expect("Failed serialize merged document");
205 debug!("document ( after) = '{}'", merged);
206
207 upsert(conn, agent_id, profileId, &merged).await?;
208 let etag = etag_from_str(&merged);
209 Ok(no_content(&etag))
210 }
211 }
212 }
213 Err(x) => match x {
214 MyError::Data(_) => Err(x.with_status(Status::BadRequest)),
215 x => Err(x),
216 },
217 }
218}
219
220#[delete("/?<agent>&<profileId>")]
222async fn delete(
223 c: Headers,
224 agent: &str,
225 profileId: &str,
226 db: &State<DB>,
227 user: User,
228) -> Result<Status, MyError> {
229 debug!("----- delete ----- {}", user);
230 let _ = user.can_use_xapi();
231
232 let conn = db.pool();
233 match find_agent_id_from_str(conn, agent).await {
234 Ok(agent_id) => {
235 debug!("agent_id = {}", agent_id);
236 let (document, _) = get_profile(conn, agent_id, profileId).await?;
237 let etag = etag_from_str(&document);
238 debug!("etag (LaRS) = {}", etag);
239 match eval_preconditions!(&etag, c) {
240 s if s != Status::Ok => Err(MyError::HTTP {
241 status: s,
242 info: "Failed pre-condition(s)".into(),
243 }),
244 _ => {
245 remove(conn, agent_id, profileId).await?;
246 Ok(Status::NoContent)
247 }
248 }
249 }
250 Err(x) => match x {
251 MyError::Data(_) => Err(x.with_status(Status::BadRequest)),
252 x => Err(x),
253 },
254 }
255}
256
257#[get("/?<agent>&<profileId>&<since>")]
262async fn get(
263 agent: &str,
264 profileId: Option<&str>,
265 since: Option<&str>,
266 db: &State<DB>,
267 user: User,
268) -> Result<WithDocumentOrIDs, MyError> {
269 debug!("----- get ----- {}", user);
270 user.can_use_xapi()?;
271
272 let conn = db.pool();
273 match find_agent_id_from_str(conn, agent).await {
274 Ok(agent_id) => {
275 debug!("agent_id = {}", agent_id);
276 let resource = if let Some(z_profile_id) = profileId {
277 if since.is_some() {
278 return Err(MyError::HTTP {
279 status: Status::BadRequest,
280 info: "Either `profileId` or `since` should be specified; not both".into(),
281 });
282 } else {
283 get_profile(conn, agent_id, z_profile_id).await?
284 }
285 } else {
286 let (x, last_updated) = get_ids(conn, agent_id, since).await?;
287 (serde_json::to_string(&x).unwrap(), last_updated)
288 };
289
290 debug!("resource = {:?}", resource);
291 emit_doc_response(resource.0, Some(resource.1)).await
292 }
293 Err(x) => match x {
294 MyError::Data(_) => Err(x.with_status(Status::BadRequest)),
295 x => Err(x),
296 },
297 }
298}
299
300async fn get_profile(
301 conn: &PgPool,
302 actor_id: i32,
303 profile_id: &str,
304) -> Result<(String, DateTime<Utc>), MyError> {
305 let (x, updated) = find(conn, actor_id, profile_id).await?;
306 match x {
307 None => Err(MyError::HTTP {
308 status: Status::NotFound,
309 info: format!("Failed find Agent Profile ({profile_id}) for Actor #{actor_id}").into(),
310 }),
311 Some(doc) => Ok((doc, updated)),
312 }
313}
314
315async fn get_ids(
316 conn: &PgPool,
317 actor_id: i32,
318 since: Option<&str>,
319) -> Result<(Vec<String>, DateTime<Utc>), MyError> {
320 let since = if let Some(z_datetime) = since {
321 let x = DateTime::parse_from_rfc3339(z_datetime)
322 .map_err(|x| MyError::Data(DataError::Time(x)).with_status(Status::BadRequest))?;
323 Some(x.with_timezone(&Utc))
324 } else {
325 None
326 };
327
328 find_ids(conn, actor_id, since).await
329}