Skip to content

Commit def1c70

Browse files
committed
Implement v3 areas endpoints
1 parent 2eb84c8 commit def1c70

3 files changed

Lines changed: 203 additions & 0 deletions

File tree

src/area/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub use model::Area;
33
pub use model::AreaRepo;
44
pub mod admin;
55
pub mod v2;
6+
pub mod v3;

src/area/v3.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
use crate::area::Area;
2+
use crate::area::AreaRepo;
3+
use crate::Error;
4+
use actix_web::get;
5+
use actix_web::web::Data;
6+
use actix_web::web::Json;
7+
use actix_web::web::Query;
8+
use serde::Deserialize;
9+
use serde::Serialize;
10+
use serde_json::Map;
11+
use serde_json::Value;
12+
use time::OffsetDateTime;
13+
14+
#[derive(Deserialize)]
15+
pub struct GetArgs {
16+
#[serde(with = "time::serde::rfc3339")]
17+
updated_since: OffsetDateTime,
18+
limit: i64,
19+
}
20+
21+
#[derive(Serialize, Deserialize, PartialEq, Debug)]
22+
pub struct GetItem {
23+
pub id: i64,
24+
#[serde(skip_serializing_if = "Option::is_none")]
25+
pub tags: Option<Map<String, Value>>,
26+
#[serde(with = "time::serde::rfc3339")]
27+
pub updated_at: OffsetDateTime,
28+
#[serde(default)]
29+
#[serde(skip_serializing_if = "Option::is_none")]
30+
#[serde(with = "time::serde::rfc3339::option")]
31+
pub deleted_at: Option<OffsetDateTime>,
32+
}
33+
34+
impl Into<GetItem> for Area {
35+
fn into(self) -> GetItem {
36+
let tags = if self.deleted_at.is_none() && !self.tags.is_empty() {
37+
Some(self.tags)
38+
} else {
39+
None
40+
};
41+
GetItem {
42+
id: self.id,
43+
tags,
44+
updated_at: self.updated_at,
45+
deleted_at: self.deleted_at,
46+
}
47+
}
48+
}
49+
50+
impl Into<Json<GetItem>> for Area {
51+
fn into(self) -> Json<GetItem> {
52+
Json(self.into())
53+
}
54+
}
55+
56+
#[get("")]
57+
async fn get(args: Query<GetArgs>, repo: Data<AreaRepo>) -> Result<Json<Vec<GetItem>>, Error> {
58+
Ok(Json(
59+
repo.select_updated_since(&args.updated_since, Some(args.limit))
60+
.await?
61+
.into_iter()
62+
.map(|it| it.into())
63+
.collect(),
64+
))
65+
}
66+
67+
#[cfg(test)]
68+
mod test {
69+
use crate::element::ElementRepo;
70+
use crate::error::{self, ApiError};
71+
use crate::test::mock_state;
72+
use crate::Result;
73+
use actix_web::test::TestRequest;
74+
use actix_web::web::{scope, Data, QueryConfig};
75+
use actix_web::{test, App};
76+
use http::StatusCode;
77+
use serde_json::Map;
78+
use time::macros::datetime;
79+
80+
#[test]
81+
async fn get_no_updated_since() -> Result<()> {
82+
let app = test::init_service(
83+
App::new()
84+
.app_data(QueryConfig::default().error_handler(error::query_error_handler))
85+
.app_data(Data::new(ElementRepo::mock()))
86+
.service(scope("/").service(super::get)),
87+
)
88+
.await;
89+
let req = TestRequest::get().uri("/?limit=1").to_request();
90+
let res: ApiError = test::try_call_and_read_body_json(&app, req).await.unwrap();
91+
assert_eq!(StatusCode::BAD_REQUEST.as_u16(), res.http_code);
92+
assert!(res.message.contains("missing field `updated_since`"));
93+
Ok(())
94+
}
95+
96+
#[test]
97+
async fn get_no_limit() -> Result<()> {
98+
let app = test::init_service(
99+
App::new()
100+
.app_data(QueryConfig::default().error_handler(error::query_error_handler))
101+
.app_data(Data::new(ElementRepo::mock()))
102+
.service(scope("/").service(super::get)),
103+
)
104+
.await;
105+
let req = TestRequest::get()
106+
.uri("/?updated_since=2020-01-01T00:00:00Z")
107+
.to_request();
108+
let res: ApiError = test::try_call_and_read_body_json(&app, req).await.unwrap();
109+
assert_eq!(StatusCode::BAD_REQUEST.as_u16(), res.http_code);
110+
assert!(res.message.contains("missing field `limit`"));
111+
Ok(())
112+
}
113+
114+
#[test]
115+
async fn get_empty_array() -> Result<()> {
116+
let state = mock_state().await;
117+
let app = test::init_service(
118+
App::new()
119+
.app_data(Data::new(state.area_repo))
120+
.service(scope("/").service(super::get)),
121+
)
122+
.await;
123+
let req = TestRequest::get()
124+
.uri("/?updated_since=2020-01-01T00:00:00Z&limit=1")
125+
.to_request();
126+
let res: Vec<super::GetItem> = test::call_and_read_body_json(&app, req).await;
127+
assert_eq!(res.len(), 0);
128+
Ok(())
129+
}
130+
131+
#[test]
132+
async fn get_not_empty_array() -> Result<()> {
133+
let state = mock_state().await;
134+
let area = state.area_repo.insert(&Map::new()).await?;
135+
let app = test::init_service(
136+
App::new()
137+
.app_data(Data::new(state.area_repo))
138+
.service(scope("/").service(super::get)),
139+
)
140+
.await;
141+
let req = TestRequest::get()
142+
.uri("/?updated_since=2020-01-01T00:00:00Z&limit=1")
143+
.to_request();
144+
let res: Vec<super::GetItem> = test::call_and_read_body_json(&app, req).await;
145+
assert_eq!(res, vec![area.into()]);
146+
Ok(())
147+
}
148+
149+
#[test]
150+
async fn get_with_limit() -> Result<()> {
151+
let state = mock_state().await;
152+
let area_1 = state.area_repo.insert(&Map::new()).await?;
153+
let area_2 = state.area_repo.insert(&Map::new()).await?;
154+
let _area_3 = state.area_repo.insert(&Map::new()).await?;
155+
let app = test::init_service(
156+
App::new()
157+
.app_data(Data::new(state.area_repo))
158+
.service(scope("/").service(super::get)),
159+
)
160+
.await;
161+
let req = TestRequest::get()
162+
.uri("/?updated_since=2020-01-01T00:00:00Z&limit=2")
163+
.to_request();
164+
let res: Vec<super::GetItem> = test::call_and_read_body_json(&app, req).await;
165+
assert_eq!(res, vec![area_1.into(), area_2.into()]);
166+
Ok(())
167+
}
168+
169+
#[test]
170+
async fn get_updated_since() -> Result<()> {
171+
let state = mock_state().await;
172+
let area_1 = state.area_repo.insert(&Map::new()).await?;
173+
state
174+
.area_repo
175+
.set_updated_at(area_1.id, &datetime!(2022-01-05 00:00 UTC))
176+
.await?;
177+
let area_2 = state.area_repo.insert(&Map::new()).await?;
178+
let area_2 = state
179+
.area_repo
180+
.set_updated_at(area_2.id, &datetime!(2022-02-05 00:00 UTC))
181+
.await?;
182+
let app = test::init_service(
183+
App::new()
184+
.app_data(Data::new(state.area_repo))
185+
.service(scope("/").service(super::get)),
186+
)
187+
.await;
188+
let req = TestRequest::get()
189+
.uri("/?updated_since=2022-01-10T00:00:00Z&limit=100")
190+
.to_request();
191+
let res: Vec<super::GetItem> = test::call_and_read_body_json(&app, req).await;
192+
assert_eq!(res, vec![area_2.into()]);
193+
Ok(())
194+
}
195+
}

src/server/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ pub async fn run() -> Result<()> {
143143
scope("events")
144144
.service(event::v3::get)
145145
.service(event::v3::get_by_id),
146+
)
147+
.service(
148+
scope("areas")
149+
.service(area::admin::post)
150+
.service(area::admin::patch)
151+
.service(area::admin::delete)
152+
.service(area::v3::get),
146153
),
147154
)
148155
.service(

0 commit comments

Comments
 (0)