1use crate::NoteType;
2use actix_web::{delete, get, post, web, HttpResponse, Responder};
3use notegraf::errors::NoteStoreError;
4use notegraf::notemetadata::NoteMetadataEditable;
5use notegraf::notestore::BoxedNoteStore;
6use notegraf::{NoteLocator, NoteSerializable};
7use serde::Deserialize;
8use std::collections::HashSet;
9
10fn notestore_error_handler(e: &NoteStoreError) -> HttpResponse {
11 match e {
12 NoteStoreError::NoteNotExist(_) => HttpResponse::NotFound().body(e.to_string()),
13 NoteStoreError::NoteIDConflict(_) => HttpResponse::Conflict().body(e.to_string()),
14 NoteStoreError::RevisionNotExist(_, _) => HttpResponse::NotFound().body(e.to_string()),
15 NoteStoreError::IOError(_) => {
16 error!("Note store internal error {:?}", e);
17 HttpResponse::InternalServerError().finish()
18 }
19 NoteStoreError::SerdeError(_) => HttpResponse::BadRequest().body(e.to_string()),
20 NoteStoreError::UpdateOldRevision(_, _) => HttpResponse::Conflict().body(e.to_string()),
21 NoteStoreError::DeleteOldRevision(_, _) => HttpResponse::Conflict().body(e.to_string()),
22 NoteStoreError::NotAChild(_, _) => HttpResponse::Conflict().body(e.to_string()),
23 NoteStoreError::ExistingNext(_, _) => HttpResponse::Conflict().body(e.to_string()),
24 NoteStoreError::HasBranches(_) => HttpResponse::Conflict().body(e.to_string()),
25 NoteStoreError::HasReferences(_) => HttpResponse::Conflict().body(e.to_string()),
26 NoteStoreError::ParseError(_) => HttpResponse::BadRequest().body(e.to_string()),
27 NoteStoreError::PostgreSQLError(_) => {
28 error!("Note store internal error {:?}", e);
29 HttpResponse::InternalServerError().finish()
30 }
31 NoteStoreError::NoteInnerError(_) => HttpResponse::BadRequest().body(e.to_string()),
32 NoteStoreError::NotUuid(_) => HttpResponse::BadRequest().body(e.to_string()),
33 }
34}
35
36async fn get_note_by_locator(
37 store: web::Data<BoxedNoteStore<NoteType>>,
38 loc: &NoteLocator,
39) -> impl Responder {
40 let result = store.as_ref().get_note(loc).await;
41 match result {
42 Ok(note) => HttpResponse::Ok().json(NoteSerializable::all_fields(note)),
43 Err(e) => notestore_error_handler(&e),
44 }
45}
46
47#[delete("/note/{note_id}")]
48#[instrument(
49 skip(store, params),
50 fields(
51 note_id = %params.0
52 )
53)]
54async fn delete_note_current(
55 store: web::Data<BoxedNoteStore<NoteType>>,
56 params: web::Path<(String,)>,
57) -> impl Responder {
58 let (note_id,) = params.into_inner();
59 let loc = NoteLocator::Current(note_id.into());
60 let res = store.delete_note(&loc).await;
61 match res {
62 Ok(_) => HttpResponse::Ok().finish(),
63 Err(e) => notestore_error_handler(&e),
64 }
65}
66
67#[get("/note/{note_id}/revision/{revision_id}")]
68#[instrument(
69 skip(store, params),
70 fields(
71 note_id = %params.0,
72 revision_id = %params.1
73 )
74)]
75async fn get_note_specific(
76 store: web::Data<BoxedNoteStore<NoteType>>,
77 params: web::Path<(String, String)>,
78) -> impl Responder {
79 let (note_id, revision_id) = params.into_inner();
80 let loc = NoteLocator::Specific(note_id.into(), revision_id.into());
81 get_note_by_locator(store, &loc).await
82}
83
84#[derive(Deserialize)]
85struct NotePostData {
86 title: String,
87 note_inner: String,
88 metadata_tags: String,
89 metadata_custom_metadata: String,
90}
91
92struct NoteStoreEditArgument {
93 title: String,
94 note_inner: NoteType,
95 metadata: NoteMetadataEditable,
96}
97
98impl TryFrom<NotePostData> for NoteStoreEditArgument {
99 type Error = String;
100
101 fn try_from(note: NotePostData) -> Result<Self, Self::Error> {
102 let custom_metadata =
103 serde_json::from_str(¬e.metadata_custom_metadata).map_err(|e| e.to_string())?;
104 let tags: HashSet<String> = HashSet::from_iter(
105 note.metadata_tags
106 .split(',')
107 .into_iter()
108 .map(|tag| tag.trim().to_owned())
109 .filter(|tag| !tag.is_empty()),
110 );
111 Ok(NoteStoreEditArgument {
112 title: note.title,
113 note_inner: NoteType::from(note.note_inner),
114 metadata: NoteMetadataEditable {
115 tags: Some(tags),
116 custom_metadata: Some(custom_metadata),
117 },
118 })
119 }
120}
121
122#[post("/note")]
123#[instrument(skip(store, note))]
124async fn new_note(
125 store: web::Data<BoxedNoteStore<NoteType>>,
126 note: web::Json<NotePostData>,
127) -> impl Responder {
128 let note: Result<NoteStoreEditArgument, String> = note.into_inner().try_into();
129 if let Err(e) = note {
130 return HttpResponse::BadRequest().body(e);
131 }
132 let note = note.unwrap();
133 let res = store
134 .new_note(note.title, note.note_inner, note.metadata)
135 .await;
136 match res {
137 Ok(loc) => HttpResponse::Ok().json(loc),
138 Err(e) => notestore_error_handler(&e),
139 }
140}
141
142#[get("/note/{note_id}/revision")]
143#[instrument(
144 skip(store, params),
145 fields(
146 note_id = %params.0
147 )
148)]
149async fn get_revisions(
150 store: web::Data<BoxedNoteStore<NoteType>>,
151 params: web::Path<(String,)>,
152) -> impl Responder {
153 let (note_id,) = params.into_inner();
154 let loc = NoteLocator::Current(note_id.into());
155 let res = store.get_revisions(&loc).await;
156 if let Err(e) = res {
157 return notestore_error_handler(&e);
158 }
159 let revisions: Vec<NoteSerializable<NoteType>> = res
160 .unwrap()
161 .into_iter()
162 .map(|x| NoteSerializable::all_fields(x))
163 .collect();
164 HttpResponse::Ok().json(revisions)
165}
166
167#[post("/note/{note_id}/revision")]
168#[instrument(
169 skip(store, params, note),
170 fields(
171 note_id = %params.0
172 )
173)]
174async fn update_note(
175 store: web::Data<BoxedNoteStore<NoteType>>,
176 params: web::Path<(String,)>,
177 note: web::Json<NotePostData>,
178) -> impl Responder {
179 let (note_id,) = params.into_inner();
180 let loc = NoteLocator::Current(note_id.into());
181 let note: Result<NoteStoreEditArgument, String> = note.into_inner().try_into();
182 if let Err(e) = note {
183 return HttpResponse::BadRequest().body(e);
184 }
185 let note = note.unwrap();
186 let res = store
187 .update_note(&loc, Some(note.title), Some(note.note_inner), note.metadata)
188 .await;
189 match res {
190 Ok(loc) => HttpResponse::Ok().json(loc),
191 Err(e) => notestore_error_handler(&e),
192 }
193}
194
195#[post("/note/{note_id}/branch")]
196#[instrument(
197 skip(store, params, note),
198 fields(
199 note_id = %params.0
200 )
201)]
202async fn new_branch(
203 store: web::Data<BoxedNoteStore<NoteType>>,
204 params: web::Path<(String,)>,
205 note: web::Json<NotePostData>,
206) -> impl Responder {
207 let (note_id,) = params.into_inner();
208 let loc = NoteLocator::Current(note_id.into());
209 let note: Result<NoteStoreEditArgument, String> = note.into_inner().try_into();
210 if let Err(e) = note {
211 return HttpResponse::BadRequest().body(e);
212 }
213 let note = note.unwrap();
214 let res = store
215 .add_branch(loc.get_id(), note.title, note.note_inner, note.metadata)
216 .await;
217 match res {
218 Ok(loc_child) => HttpResponse::Ok().json(loc_child),
219 Err(e) => notestore_error_handler(&e),
220 }
221}
222
223#[post("/note/{note_id}/next")]
224#[instrument(
225 skip(store, params, note),
226 fields(
227 note_id = %params.0
228 )
229)]
230async fn new_next(
231 store: web::Data<BoxedNoteStore<NoteType>>,
232 params: web::Path<(String,)>,
233 note: web::Json<NotePostData>,
234) -> impl Responder {
235 let (note_id,) = params.into_inner();
236 let loc = NoteLocator::Current(note_id.into());
237 let note: Result<NoteStoreEditArgument, String> = note.into_inner().try_into();
238 if let Err(e) = note {
239 return HttpResponse::BadRequest().body(e);
240 }
241 let note = note.unwrap();
242 let res = store
243 .append_note(loc.get_id(), note.title, note.note_inner, note.metadata)
244 .await;
245 match res {
246 Ok(loc_next) => HttpResponse::Ok().json(loc_next),
247 Err(e) => notestore_error_handler(&e),
248 }
249}
250
251#[get("/note/{note_id}")]
252#[instrument(
253 skip(store, params),
254 fields(
255 note_id = %params.0
256 )
257)]
258async fn get_note_current(
259 store: web::Data<BoxedNoteStore<NoteType>>,
260 params: web::Path<(String,)>,
261) -> impl Responder {
262 let (note_id,) = params.into_inner();
263 let loc = NoteLocator::Current(note_id.into());
264 get_note_by_locator(store, &loc).await
265}
266
267#[derive(Deserialize, Debug)]
268struct SearchQuery {
269 query: Option<String>,
270}
271
272#[get("/note")]
273#[instrument(skip(store, search))]
274async fn search(
275 store: web::Data<BoxedNoteStore<NoteType>>,
276 search: web::Query<SearchQuery>,
277) -> impl Responder {
278 let search = search.into_inner();
279 let query = search.query;
280 let res = if let Some(q) = query {
281 store.search(&q.into()).await
282 } else {
283 store.search(&"".to_owned().into()).await
284 };
285 if let Err(e) = res {
286 return notestore_error_handler(&e);
287 }
288 let revisions: Vec<NoteSerializable<NoteType>> = res
289 .unwrap()
290 .into_iter()
291 .map(|x| NoteSerializable::all_fields(x))
292 .collect();
293 HttpResponse::Ok().json(revisions)
294}
295
296#[get("/tags")]
297#[instrument(skip(store))]
298async fn get_tags(store: web::Data<BoxedNoteStore<NoteType>>) -> impl Responder {
299 let res = store.tags().await;
300
301 if let Err(e) = res {
302 return notestore_error_handler(&e);
303 }
304 HttpResponse::Ok().json(res.unwrap())
305}
306
307pub fn config(cfg: &mut web::ServiceConfig) {
308 cfg.service(get_note_current)
309 .service(get_note_specific)
310 .service(new_note)
311 .service(delete_note_current)
312 .service(update_note)
313 .service(get_revisions)
314 .service(search)
315 .service(new_branch)
316 .service(new_next)
317 .service(get_tags);
318}