Skip to content

Commit 2e20ff7

Browse files
committed
Add top countries endpoint
1 parent a895bc5 commit 2e20ff7

File tree

5 files changed

+173
-1
lines changed

5 files changed

+173
-1
lines changed

src/db/main/area/blocking_queries.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,69 @@ pub fn select_top_communities(conn: &Connection) -> Result<Vec<CommunityStats>>
407407
Ok(results)
408408
}
409409

410+
pub fn select_top_areas_by_type(conn: &Connection, area_type: &str) -> Result<Vec<CommunityStats>> {
411+
let year_ago = time::OffsetDateTime::now_utc()
412+
.saturating_sub(time::Duration::days(365))
413+
.date()
414+
.to_string();
415+
416+
let sql = format!(
417+
r#"
418+
WITH community_areas AS (
419+
SELECT id, alias, tags
420+
FROM area
421+
WHERE deleted_at IS NULL
422+
AND json_extract(tags, '$.type') = '{area_type}'
423+
),
424+
element_stats AS (
425+
SELECT
426+
ca.id as area_id,
427+
COUNT(e.id) as total_elements,
428+
SUM(CASE
429+
WHEN (COALESCE(json_extract(e.overpass_data, '$.tags.survey:date'), '2000-01-01') > '{year_ago}'
430+
OR COALESCE(json_extract(e.overpass_data, '$.tags.check_date'), '2000-01-01') > '{year_ago}'
431+
OR COALESCE(json_extract(e.overpass_data, '$.tags.check_date:currency:XBT'), '2000-01-01') > '{year_ago}')
432+
THEN 1 ELSE 0
433+
END) as verified_1y
434+
FROM community_areas ca
435+
INNER JOIN area_element ae ON ae.area_id = ca.id AND ae.deleted_at IS NULL
436+
INNER JOIN element e ON e.id = ae.element_id AND e.deleted_at IS NULL
437+
GROUP BY ca.id
438+
)
439+
SELECT
440+
ca.id,
441+
ca.alias,
442+
COALESCE(json_extract(ca.tags, '$.name'), '') as name,
443+
json_extract(ca.tags, '$.icon:square') as icon_url,
444+
COALESCE(es.total_elements, 0) as places_total,
445+
COALESCE(es.verified_1y, 0) as places_verified_1y
446+
FROM community_areas ca
447+
LEFT JOIN element_stats es ON es.area_id = ca.id
448+
ORDER BY
449+
(COALESCE(es.verified_1y, 0) - (COALESCE(es.total_elements, 0) - COALESCE(es.verified_1y, 0)) * 5) DESC,
450+
COALESCE(es.total_elements, 0) DESC
451+
"#
452+
);
453+
454+
let mut stmt = conn.prepare(&sql)?;
455+
let rows = stmt.query_map([], |row| {
456+
Ok(CommunityStats {
457+
id: row.get(0)?,
458+
alias: row.get(1)?,
459+
name: row.get(2)?,
460+
icon_url: row.get(3)?,
461+
places_total: row.get(4)?,
462+
places_verified_1y: row.get(5)?,
463+
})
464+
})?;
465+
466+
let mut results = Vec::new();
467+
for row in rows {
468+
results.push(row?);
469+
}
470+
Ok(results)
471+
}
472+
410473
#[cfg(test)]
411474
mod test {
412475
use super::schema::Area;

src/db/main/area/queries.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,11 @@ pub async fn select_top_communities(pool: &Pool) -> Result<Vec<CommunityStats>>
147147
.interact(|conn| blocking_queries::select_top_communities(conn))
148148
.await?
149149
}
150+
151+
pub async fn select_top_areas_by_type(pool: &Pool, area_type: &str) -> Result<Vec<CommunityStats>> {
152+
let area_type = area_type.to_string();
153+
pool.get()
154+
.await?
155+
.interact(move |conn| blocking_queries::select_top_areas_by_type(conn, &area_type))
156+
.await?
157+
}

src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ async fn main() -> Result<()> {
177177
.service(scope("areas").service(rest::v4::areas::get))
178178
.service(scope("dashboard").service(rest::v4::dashboard::get))
179179
.service(scope("top-editors").service(rest::v4::top_editors::get))
180-
.service(scope("communities").service(rest::v4::communities::get_top)),
180+
.service(scope("communities").service(rest::v4::communities::get_top))
181+
.service(scope("countries").service(rest::v4::countries::get_top)),
181182
)
182183
})
183184
.client_request_timeout(Duration::from_millis(0))

src/rest/v4/countries.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use crate::db::main::area::blocking_queries::CommunityStats;
2+
use crate::db::main::MainPool;
3+
use crate::rest::error::RestApiError;
4+
use crate::rest::error::RestResult as Res;
5+
use actix_web::get;
6+
use actix_web::web::Data;
7+
use actix_web::web::Json;
8+
use serde::Serialize;
9+
10+
#[derive(Serialize)]
11+
pub struct Country {
12+
pub id: i64,
13+
pub alias: String,
14+
pub name: String,
15+
pub icon: Option<String>,
16+
pub places_total: i64,
17+
pub places_verified_1y: i64,
18+
pub grade: i32,
19+
}
20+
21+
fn calculate_grade(places_total: i64, places_verified_1y: i64) -> i32 {
22+
if places_total == 0 {
23+
return 1;
24+
}
25+
let percentage = (places_verified_1y as f64 / places_total as f64) * 100.0;
26+
if percentage >= 95.0 {
27+
5
28+
} else if percentage >= 75.0 {
29+
4
30+
} else if percentage >= 50.0 {
31+
3
32+
} else if percentage >= 25.0 {
33+
2
34+
} else {
35+
1
36+
}
37+
}
38+
39+
#[get("/top")]
40+
pub async fn get_top(pool: Data<MainPool>) -> Res<Vec<Country>> {
41+
let countries = crate::db::main::area::queries::select_top_areas_by_type(&pool, "country")
42+
.await
43+
.map_err(|_| RestApiError::database())?;
44+
45+
let result: Vec<Country> = countries
46+
.into_iter()
47+
.map(|c: CommunityStats| Country {
48+
id: c.id,
49+
alias: c.alias,
50+
name: c.name,
51+
icon: c.icon_url,
52+
places_total: c.places_total,
53+
places_verified_1y: c.places_verified_1y,
54+
grade: calculate_grade(c.places_total, c.places_verified_1y),
55+
})
56+
.collect();
57+
58+
Ok(Json(result))
59+
}
60+
61+
#[cfg(test)]
62+
mod test {
63+
use super::*;
64+
65+
#[test]
66+
fn test_calculate_grade_zero_places() {
67+
assert_eq!(calculate_grade(0, 0), 1);
68+
}
69+
70+
#[test]
71+
fn test_calculate_grade_0_to_25_percent() {
72+
assert_eq!(calculate_grade(100, 0), 1);
73+
assert_eq!(calculate_grade(100, 24), 1);
74+
}
75+
76+
#[test]
77+
fn test_calculate_grade_25_to_50_percent() {
78+
assert_eq!(calculate_grade(100, 25), 2);
79+
assert_eq!(calculate_grade(100, 49), 2);
80+
}
81+
82+
#[test]
83+
fn test_calculate_grade_50_to_75_percent() {
84+
assert_eq!(calculate_grade(100, 50), 3);
85+
assert_eq!(calculate_grade(100, 74), 3);
86+
}
87+
88+
#[test]
89+
fn test_calculate_grade_75_to_95_percent() {
90+
assert_eq!(calculate_grade(100, 75), 4);
91+
assert_eq!(calculate_grade(100, 94), 4);
92+
}
93+
94+
#[test]
95+
fn test_calculate_grade_95_plus_percent() {
96+
assert_eq!(calculate_grade(100, 95), 5);
97+
assert_eq!(calculate_grade(100, 100), 5);
98+
}
99+
}

src/rest/v4/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod areas;
22
pub mod communities;
3+
pub mod countries;
34
pub mod dashboard;
45
pub mod element_issues;
56
pub mod events;

0 commit comments

Comments
 (0)