Skip to content

Commit 791c053

Browse files
authored
Merge pull request #48 from Quantus-Network/feat/add-filter-to-raid-leaderboard
Support filter by referral code to raid leaderboard endpoint
2 parents d12ef5f + 165f7a2 commit 791c053

2 files changed

Lines changed: 100 additions & 33 deletions

File tree

src/handlers/raid_quest.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,18 @@ pub async fn handle_get_raid_leaderboard(
118118
) -> Result<Json<PaginatedResponse<RaidLeaderboard>>, AppError> {
119119
validate_pagination_query(params.page, params.page_size)?;
120120

121-
let total_items = state.db.raid_leaderboards.get_total_items(raid_id).await? as u32;
121+
let total_items = state
122+
.db
123+
.raid_leaderboards
124+
.get_total_items(raid_id, params.referral_code.clone())
125+
.await? as u32;
122126
let total_pages = calculate_total_pages(params.page_size, total_items);
123127

124128
let offset = (params.page - 1) * params.page_size;
125129
let entries = state
126130
.db
127131
.raid_leaderboards
128-
.get_entries(raid_id, params.page_size as i64, offset as i64)
132+
.get_entries(raid_id, params.page_size as i64, offset as i64, params.referral_code)
129133
.await?;
130134

131135
let response = PaginatedResponse {

src/repositories/raid_leaderboard.rs

Lines changed: 94 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{models::raid_leaderboard::RaidLeaderboard, repositories::DbResult};
2-
use sqlx::PgPool;
2+
use sqlx::{PgPool, QueryBuilder};
33

44
#[derive(Clone, Debug)]
55
pub struct RaidLeaderboardRepository {
@@ -19,33 +19,60 @@ impl RaidLeaderboardRepository {
1919
}
2020

2121
/// Retrieves the top raiders with their Rank and Address info.
22-
pub async fn get_entries(&self, raid_id: i32, limit: i64, offset: i64) -> DbResult<Vec<RaidLeaderboard>> {
23-
let query = "
24-
SELECT
25-
l.raid_id,
26-
l.total_submissions,
27-
l.total_impressions,
28-
l.total_replies,
29-
l.total_retweets,
30-
l.total_likes,
31-
l.last_activity,
32-
RANK() OVER (ORDER BY l.total_impressions DESC) as rank,
22+
pub async fn get_entries(
23+
&self,
24+
raid_id: i32,
25+
limit: i64,
26+
offset: i64,
27+
referral_code: Option<String>,
28+
) -> DbResult<Vec<RaidLeaderboard>> {
29+
let mut qb = QueryBuilder::new(
30+
"WITH ranked_entries AS (
31+
SELECT
32+
l.raid_id,
33+
l.total_submissions,
34+
l.total_impressions,
35+
l.total_replies,
36+
l.total_retweets,
37+
l.total_likes,
38+
l.last_activity,
39+
l.raider_id,
40+
RANK() OVER (ORDER BY l.total_impressions DESC) as rank
41+
FROM raid_leaderboards l
42+
WHERE l.raid_id = ",
43+
);
44+
qb.push_bind(raid_id);
45+
qb.push(" ) ");
46+
47+
qb.push(
48+
"SELECT
49+
r.raid_id,
50+
r.total_submissions,
51+
r.total_impressions,
52+
r.total_replies,
53+
r.total_retweets,
54+
r.total_likes,
55+
r.last_activity,
56+
r.rank,
3357
a.quan_address,
3458
a.referral_code,
3559
a.referrals_count
36-
FROM raid_leaderboards l
37-
JOIN addresses a ON l.raider_id = a.quan_address
38-
WHERE l.raid_id = $1
39-
ORDER BY l.total_impressions DESC
40-
LIMIT $2 OFFSET $3
41-
";
60+
FROM ranked_entries r
61+
JOIN addresses a ON r.raider_id = a.quan_address
62+
WHERE 1=1 ",
63+
);
4264

43-
let leaderboard = sqlx::query_as::<_, RaidLeaderboard>(query)
44-
.bind(raid_id)
45-
.bind(limit)
46-
.bind(offset)
47-
.fetch_all(&self.pool)
48-
.await?;
65+
if let Some(code) = referral_code {
66+
qb.push(" AND a.referral_code ILIKE ");
67+
qb.push_bind(format!("{}%", code));
68+
}
69+
70+
qb.push(" ORDER BY r.total_impressions DESC LIMIT ");
71+
qb.push_bind(limit);
72+
qb.push(" OFFSET ");
73+
qb.push_bind(offset);
74+
75+
let leaderboard = qb.build_query_as::<RaidLeaderboard>().fetch_all(&self.pool).await?;
4976

5077
Ok(leaderboard)
5178
}
@@ -88,10 +115,22 @@ impl RaidLeaderboardRepository {
88115
Ok(entry)
89116
}
90117

91-
pub async fn get_total_items(&self, raid_id: i32) -> DbResult<i64> {
92-
let query = "SELECT COUNT(*) FROM raid_leaderboards WHERE raid_id = $1";
118+
pub async fn get_total_items(&self, raid_id: i32, referral_code: Option<String>) -> DbResult<i64> {
119+
let mut qb = QueryBuilder::new("SELECT COUNT(*) FROM raid_leaderboards l ");
120+
121+
if referral_code.is_some() {
122+
qb.push(" JOIN addresses a ON l.raider_id = a.quan_address ");
123+
}
124+
125+
qb.push(" WHERE l.raid_id = ");
126+
qb.push_bind(raid_id);
127+
128+
if let Some(code) = referral_code {
129+
qb.push(" AND a.referral_code ILIKE ");
130+
qb.push_bind(format!("{}%", code));
131+
}
93132

94-
let total_items = sqlx::query_scalar(query).bind(raid_id).fetch_one(&self.pool).await?;
133+
let total_items = qb.build_query_scalar().fetch_one(&self.pool).await?;
95134

96135
Ok(total_items)
97136
}
@@ -209,7 +248,7 @@ mod tests {
209248
repo.refresh().await.expect("Failed to refresh view");
210249

211250
// 1. Get Top 3
212-
let entries = repo.get_entries(data.raid_id, 10, 0).await.unwrap();
251+
let entries = repo.get_entries(data.raid_id, 10, 0, None).await.unwrap();
213252

214253
assert_eq!(entries.len(), 3);
215254

@@ -253,7 +292,7 @@ mod tests {
253292
repo.refresh().await.unwrap();
254293

255294
// 1. Limit 1, Offset 1 (Should skip Rank 1, return Rank 2)
256-
let page = repo.get_entries(data.raid_id, 1, 1).await.unwrap();
295+
let page = repo.get_entries(data.raid_id, 1, 1, None).await.unwrap();
257296

258297
assert_eq!(page.len(), 1);
259298
assert_eq!(page[0].raider.address, data.user_mid);
@@ -282,11 +321,35 @@ mod tests {
282321
repo.refresh().await.unwrap();
283322

284323
// Query the empty/old raid
285-
let entries = repo.get_entries(other_raid_id, 10, 0).await.unwrap();
324+
let entries = repo.get_entries(other_raid_id, 10, 0, None).await.unwrap();
286325
assert!(entries.is_empty(), "Should not return entries from a different raid");
287326

288327
// Query the populated raid
289-
let entries_main = repo.get_entries(data.raid_id, 10, 0).await.unwrap();
328+
let entries_main = repo.get_entries(data.raid_id, 10, 0, None).await.unwrap();
290329
assert_eq!(entries_main.len(), 3);
291330
}
331+
332+
#[tokio::test]
333+
async fn test_get_entries_filtered_by_referral_code() {
334+
let repo = setup_test_repository().await;
335+
let data = seed_leaderboard_scenario(&repo.pool).await;
336+
repo.refresh().await.unwrap();
337+
338+
// user_mid has REF_MID
339+
let entries = repo
340+
.get_entries(data.raid_id, 10, 0, Some("REF_MID".to_string()))
341+
.await
342+
.unwrap();
343+
344+
assert_eq!(entries.len(), 1);
345+
assert_eq!(entries[0].raider.address, data.user_mid);
346+
// Rank should be preserved from global ranking (Rank 2)
347+
assert_eq!(entries[0].rank, Some(2));
348+
349+
let total = repo
350+
.get_total_items(data.raid_id, Some("REF_MID".to_string()))
351+
.await
352+
.unwrap();
353+
assert_eq!(total, 1);
354+
}
292355
}

0 commit comments

Comments
 (0)