Skip to content

Commit 0b81df2

Browse files
committed
Implement API for analytics of votes given by a person
1 parent 7aa5271 commit 0b81df2

10 files changed

Lines changed: 670 additions & 0 deletions

File tree

crates/api/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub mod private_message;
4242
pub mod private_message_report;
4343
pub mod site;
4444
pub mod sitemap;
45+
pub mod vote_analytics;
4546

4647
/// Converts the captcha to a base64 encoded wav audio file
4748
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult<String> {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use activitypub_federation::config::Data;
2+
use actix_web::web::{Json, Query};
3+
use chrono::{TimeZone, Utc};
4+
use lemmy_api_common::{context::LemmyContext, person::GetVoteAnalyticsByPerson, utils::is_admin};
5+
use lemmy_db_views::structs::LocalUserView;
6+
use lemmy_db_views_actor::structs::VoteAnalyticsGivenByPersonView;
7+
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
8+
9+
#[tracing::instrument(skip(context))]
10+
pub async fn get_vote_analytics_given_by_person(
11+
data: Query<GetVoteAnalyticsByPerson>,
12+
context: Data<LemmyContext>,
13+
local_user_view: LocalUserView,
14+
) -> LemmyResult<Json<VoteAnalyticsGivenByPersonView>> {
15+
is_admin(&local_user_view)?;
16+
17+
let since = match data.start_time {
18+
Some(t) => Some(
19+
Utc
20+
.timestamp_opt(t, 0)
21+
.single()
22+
.ok_or(LemmyErrorType::InvalidUnixTime)?,
23+
),
24+
_ => None,
25+
};
26+
let until = match data.end_time {
27+
Some(t) => Some(
28+
Utc
29+
.timestamp_opt(t, 0)
30+
.single()
31+
.ok_or(LemmyErrorType::InvalidUnixTime)?,
32+
),
33+
_ => None,
34+
};
35+
36+
let view = VoteAnalyticsGivenByPersonView::read(
37+
&mut context.pool(),
38+
data.person_id,
39+
data.exclude_votes_on_self.unwrap_or_default(),
40+
since,
41+
until,
42+
data.limit,
43+
)
44+
.await?;
45+
46+
Ok(Json(view))
47+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod given_by_person;

crates/api_common/src/person.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,14 @@ pub struct ListMedia {
441441
pub struct ListMediaResponse {
442442
pub images: Vec<LocalImageView>,
443443
}
444+
445+
#[derive(Debug, Deserialize)]
446+
#[cfg_attr(feature = "full", derive(TS))]
447+
#[cfg_attr(feature = "full", ts(export))]
448+
pub struct GetVoteAnalyticsByPerson {
449+
pub person_id: PersonId,
450+
pub exclude_votes_on_self: Option<bool>,
451+
pub start_time: Option<i64>,
452+
pub end_time: Option<i64>,
453+
pub limit: Option<i64>,
454+
}

crates/db_schema/src/impls/community.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ use diesel::{
5050
};
5151
use diesel_async::RunQueryDsl;
5252
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
53+
use std::collections::HashMap;
5354

5455
#[async_trait]
5556
impl Crud for Community {
@@ -147,6 +148,26 @@ impl Community {
147148
Ok(community_)
148149
}
149150

151+
pub async fn read_many(
152+
pool: &mut DbPool<'_>,
153+
community_ids: &[CommunityId],
154+
is_admin: bool,
155+
) -> Result<HashMap<CommunityId, Community>, Error> {
156+
let conn = &mut get_conn(pool).await?;
157+
let mut query = community::table
158+
.filter(community::id.eq_any(community_ids))
159+
.into_boxed();
160+
if !is_admin {
161+
query = query
162+
.filter(community::deleted.eq(false))
163+
.filter(community::removed.eq(false));
164+
}
165+
let communities: Vec<Community> = query.get_results(conn).await?;
166+
Ok(HashMap::from_iter(
167+
communities.iter().map(|c| (c.id, c.clone())),
168+
))
169+
}
170+
150171
/// Get the community which has a given moderators or featured url, also return the collection
151172
/// type
152173
pub async fn get_by_collection_url(

crates/db_schema/src/impls/person.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use diesel::{
2121
QueryDsl,
2222
};
2323
use diesel_async::RunQueryDsl;
24+
use std::collections::HashMap;
2425

2526
#[async_trait]
2627
impl Crud for Person {
@@ -97,6 +98,24 @@ impl Person {
9798
.await
9899
}
99100

101+
pub async fn read_many(
102+
pool: &mut DbPool<'_>,
103+
person_ids: &[PersonId],
104+
is_admin: bool,
105+
) -> Result<HashMap<PersonId, Person>, Error> {
106+
let conn = &mut get_conn(pool).await?;
107+
let mut query = person::table
108+
.filter(person::id.eq_any(person_ids))
109+
.into_boxed();
110+
if !is_admin {
111+
query = query.filter(person::deleted.eq(false));
112+
}
113+
let persons: Vec<Person> = query.get_results(conn).await?;
114+
Ok(HashMap::from_iter(
115+
persons.iter().map(|p| (p.id, p.clone())),
116+
))
117+
}
118+
100119
/// Lists local community ids for all posts and comments for a given creator.
101120
pub async fn list_local_community_ids(
102121
pool: &mut DbPool<'_>,

crates/db_views_actor/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ pub mod person_mention_view;
1919
#[cfg(feature = "full")]
2020
pub mod person_view;
2121
pub mod structs;
22+
#[cfg(feature = "full")]
23+
mod vote_analytics_given_by_view;

crates/db_views_actor/src/structs.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,43 @@ pub struct PersonView {
151151
pub counts: PersonAggregates,
152152
pub is_admin: bool,
153153
}
154+
155+
#[derive(Debug, Serialize)]
156+
#[cfg_attr(feature = "full", derive(TS))]
157+
#[cfg_attr(feature = "full", ts(export))]
158+
pub struct VoteAnalyticsByPerson {
159+
pub creator: Person,
160+
pub total_votes: i64,
161+
pub upvotes: i64,
162+
pub downvotes: i64,
163+
pub upvote_percentage: f64,
164+
}
165+
166+
#[derive(Debug, Serialize)]
167+
#[cfg_attr(feature = "full", derive(TS))]
168+
#[cfg_attr(feature = "full", ts(export))]
169+
pub struct VoteAnalyticsByCommunity {
170+
pub community: Community,
171+
pub total_votes: i64,
172+
pub upvotes: i64,
173+
pub downvotes: i64,
174+
pub upvote_percentage: f64,
175+
}
176+
177+
#[derive(Debug, Serialize)]
178+
#[cfg_attr(feature = "full", derive(TS))]
179+
#[cfg_attr(feature = "full", ts(export))]
180+
pub struct VoteAnalyticsGivenByPersonView {
181+
pub post_votes_total_votes: i64,
182+
pub post_votes_total_upvotes: i64,
183+
pub post_votes_total_downvotes: i64,
184+
pub post_votes_total_upvote_percentage: f64,
185+
pub post_votes_by_target_user: Vec<VoteAnalyticsByPerson>,
186+
pub post_votes_by_target_community: Vec<VoteAnalyticsByCommunity>,
187+
pub comment_votes_total_votes: i64,
188+
pub comment_votes_total_upvotes: i64,
189+
pub comment_votes_total_downvotes: i64,
190+
pub comment_votes_total_upvote_percentage: f64,
191+
pub comment_votes_by_target_user: Vec<VoteAnalyticsByPerson>,
192+
pub comment_votes_by_target_community: Vec<VoteAnalyticsByCommunity>,
193+
}

0 commit comments

Comments
 (0)