add: dedicated responses tab for profiles
This commit is contained in:
parent
9ba6320d46
commit
07a23f505b
24 changed files with 332 additions and 55 deletions
|
@ -1,6 +1,8 @@
|
|||
use super::common::NAME_REGEX;
|
||||
use oiseau::cache::Cache;
|
||||
use crate::model::auth::{Achievement, AchievementRarity, Notification, UserConnections};
|
||||
use crate::model::auth::{
|
||||
Achievement, AchievementName, AchievementRarity, Notification, UserConnections, ACHIEVEMENTS,
|
||||
};
|
||||
use crate::model::moderation::AuditLogEntry;
|
||||
use crate::model::oauth::AuthGrant;
|
||||
use crate::model::permissions::SecondaryPermission;
|
||||
|
@ -764,7 +766,13 @@ impl DataManager {
|
|||
/// Add an achievement to a user.
|
||||
///
|
||||
/// Still returns `Ok` if the user already has the achievement.
|
||||
pub async fn add_achievement(&self, user: &mut User, achievement: Achievement) -> Result<()> {
|
||||
#[async_recursion::async_recursion]
|
||||
pub async fn add_achievement(
|
||||
&self,
|
||||
user: &mut User,
|
||||
achievement: Achievement,
|
||||
check_for_final: bool,
|
||||
) -> Result<()> {
|
||||
if user.settings.disable_achievements {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -794,6 +802,15 @@ impl DataManager {
|
|||
self.update_user_achievements(user.id, user.achievements.to_owned())
|
||||
.await?;
|
||||
|
||||
// check for final
|
||||
if check_for_final {
|
||||
if user.achievements.len() + 1 == ACHIEVEMENTS {
|
||||
self.add_achievement(user, AchievementName::GetAllOtherAchievements.into(), false)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -242,8 +242,12 @@ impl DataManager {
|
|||
Ok(if data.role.check(CommunityPermission::REQUESTED) {
|
||||
"Join request sent".to_string()
|
||||
} else {
|
||||
self.add_achievement(&mut user.clone(), AchievementName::JoinCommunity.into())
|
||||
.await?;
|
||||
self.add_achievement(
|
||||
&mut user.clone(),
|
||||
AchievementName::JoinCommunity.into(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
"Community joined".to_string()
|
||||
})
|
||||
|
|
|
@ -758,6 +758,37 @@ impl DataManager {
|
|||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Get all posts (that are answering a question) from the given user (from most recent).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `id` - the ID of the user the requested posts belong to
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
pub async fn get_responses_by_user(
|
||||
&self,
|
||||
id: usize,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM posts WHERE owner = $1 AND replying_to = 0 AND NOT (context::json->>'is_profile_pinned')::boolean AND is_deleted = 0 AND NOT context::jsonb->>'answering' = '0' ORDER BY created DESC LIMIT $2 OFFSET $3",
|
||||
&[&(id as i64), &(batch as i64), &((page * batch) as i64)],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Calculate the GPA (great post average) of a given user.
|
||||
///
|
||||
/// To be considered a "great post", a post must have a score ((likes - dislikes) / (likes + dislikes))
|
||||
|
@ -1066,6 +1097,45 @@ impl DataManager {
|
|||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Get all posts (that are answering a question) from the given user
|
||||
/// with the given tag (from most recent).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `id` - the ID of the user the requested posts belong to
|
||||
/// * `tag` - the tag to filter by
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
pub async fn get_responses_by_user_tag(
|
||||
&self,
|
||||
id: usize,
|
||||
tag: &str,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM posts WHERE owner = $1 AND context::json->>'tags' LIKE $2 AND is_deleted = 0 AND NOT context::jsonb->>'answering' = '0' ORDER BY created DESC LIMIT $3 OFFSET $4",
|
||||
params![
|
||||
&(id as i64),
|
||||
&format!("%\"{tag}\"%"),
|
||||
&(batch as i64),
|
||||
&((page * batch) as i64)
|
||||
],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
}
|
||||
|
||||
/// Get all posts from the given community (from most recent).
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -1661,8 +1731,12 @@ impl DataManager {
|
|||
}
|
||||
|
||||
// award achievement
|
||||
self.add_achievement(&mut owner, AchievementName::CreatePostWithTitle.into())
|
||||
.await?;
|
||||
self.add_achievement(
|
||||
&mut owner,
|
||||
AchievementName::CreatePostWithTitle.into(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1803,7 +1877,7 @@ impl DataManager {
|
|||
}
|
||||
|
||||
// award achievement
|
||||
self.add_achievement(&mut owner, AchievementName::CreateRepost.into())
|
||||
self.add_achievement(&mut owner, AchievementName::CreateRepost.into(), true)
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
|
|
@ -162,26 +162,26 @@ impl DataManager {
|
|||
// achievements
|
||||
if user.id != post.owner {
|
||||
let mut owner = self.get_user_by_id(post.owner).await?;
|
||||
self.add_achievement(&mut owner, AchievementName::Get1Like.into())
|
||||
self.add_achievement(&mut owner, AchievementName::Get1Like.into(), true)
|
||||
.await?;
|
||||
|
||||
if post.likes >= 9 {
|
||||
self.add_achievement(&mut owner, AchievementName::Get10Likes.into())
|
||||
self.add_achievement(&mut owner, AchievementName::Get10Likes.into(), true)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if post.likes >= 49 {
|
||||
self.add_achievement(&mut owner, AchievementName::Get50Likes.into())
|
||||
self.add_achievement(&mut owner, AchievementName::Get50Likes.into(), true)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if post.likes >= 99 {
|
||||
self.add_achievement(&mut owner, AchievementName::Get100Likes.into())
|
||||
self.add_achievement(&mut owner, AchievementName::Get100Likes.into(), true)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if post.dislikes >= 24 {
|
||||
self.add_achievement(&mut owner, AchievementName::Get25Dislikes.into())
|
||||
self.add_achievement(&mut owner, AchievementName::Get25Dislikes.into(), true)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -262,33 +262,50 @@ impl DataManager {
|
|||
|
||||
// check if we're staff
|
||||
if initiator.permissions.check(FinePermission::STAFF_BADGE) {
|
||||
self.add_achievement(&mut other_user, AchievementName::FollowedByStaff.into())
|
||||
.await?;
|
||||
self.add_achievement(
|
||||
&mut other_user,
|
||||
AchievementName::FollowedByStaff.into(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// other achivements
|
||||
self.add_achievement(&mut other_user, AchievementName::Get1Follower.into())
|
||||
self.add_achievement(&mut other_user, AchievementName::Get1Follower.into(), true)
|
||||
.await?;
|
||||
|
||||
if other_user.follower_count >= 9 {
|
||||
self.add_achievement(&mut other_user, AchievementName::Get10Followers.into())
|
||||
.await?;
|
||||
self.add_achievement(
|
||||
&mut other_user,
|
||||
AchievementName::Get10Followers.into(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if other_user.follower_count >= 49 {
|
||||
self.add_achievement(&mut other_user, AchievementName::Get50Followers.into())
|
||||
.await?;
|
||||
self.add_achievement(
|
||||
&mut other_user,
|
||||
AchievementName::Get50Followers.into(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if other_user.follower_count >= 99 {
|
||||
self.add_achievement(&mut other_user, AchievementName::Get100Followers.into())
|
||||
.await?;
|
||||
self.add_achievement(
|
||||
&mut other_user,
|
||||
AchievementName::Get100Followers.into(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if initiator.following_count >= 9 {
|
||||
self.add_achievement(
|
||||
&mut initiator.clone(),
|
||||
AchievementName::Follow10Users.into(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
|
|
@ -124,6 +124,20 @@ impl DefaultTimelineChoice {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum DefaultProfileTabChoice {
|
||||
/// General posts (in any community) from the user.
|
||||
Posts,
|
||||
/// Responses to questions.
|
||||
Responses,
|
||||
}
|
||||
|
||||
impl Default for DefaultProfileTabChoice {
|
||||
fn default() -> Self {
|
||||
Self::Posts
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct UserSettings {
|
||||
#[serde(default)]
|
||||
|
@ -285,6 +299,9 @@ pub struct UserSettings {
|
|||
/// Automatically hide users that you've blocked on your other accounts from your timelines.
|
||||
#[serde(default)]
|
||||
pub hide_associated_blocked_users: bool,
|
||||
/// Which tab is shown by default on the user's profile.
|
||||
#[serde(default)]
|
||||
pub default_profile_tab: DefaultProfileTabChoice,
|
||||
}
|
||||
|
||||
fn mime_avif() -> String {
|
||||
|
@ -504,10 +521,15 @@ pub struct ExternalConnectionData {
|
|||
}
|
||||
|
||||
/// The total number of achievements needed to 100% Tetratto!
|
||||
pub const ACHIEVEMENTS: usize = 30;
|
||||
pub const ACHIEVEMENTS: usize = 34;
|
||||
/// "self-serve" achievements can be granted by the user through the API.
|
||||
pub const SELF_SERVE_ACHIEVEMENTS: &[AchievementName] =
|
||||
&[AchievementName::OpenTos, AchievementName::OpenPrivacyPolicy];
|
||||
pub const SELF_SERVE_ACHIEVEMENTS: &[AchievementName] = &[
|
||||
AchievementName::OpenReference,
|
||||
AchievementName::OpenTos,
|
||||
AchievementName::OpenPrivacyPolicy,
|
||||
AchievementName::AcceptProfileWarning,
|
||||
AchievementName::OpenSessionSettings,
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum AchievementName {
|
||||
|
@ -541,6 +563,10 @@ pub enum AchievementName {
|
|||
CreateRepost,
|
||||
OpenTos,
|
||||
OpenPrivacyPolicy,
|
||||
OpenReference,
|
||||
GetAllOtherAchievements,
|
||||
AcceptProfileWarning,
|
||||
OpenSessionSettings,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
|
@ -583,6 +609,10 @@ impl AchievementName {
|
|||
Self::CreateRepost => "More than a like or comment...",
|
||||
Self::OpenTos => "Well informed!",
|
||||
Self::OpenPrivacyPolicy => "Privacy conscious",
|
||||
Self::OpenReference => "What does this do?",
|
||||
Self::GetAllOtherAchievements => "The final performance",
|
||||
Self::AcceptProfileWarning => "I accept the risks!",
|
||||
Self::OpenSessionSettings => "Am I alone in here?",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -618,6 +648,10 @@ impl AchievementName {
|
|||
Self::CreateRepost => "Create a repost or quote.",
|
||||
Self::OpenTos => "Open the terms of service.",
|
||||
Self::OpenPrivacyPolicy => "Open the privacy policy.",
|
||||
Self::OpenReference => "Open the source code reference documentation.",
|
||||
Self::GetAllOtherAchievements => "Get every other achievement.",
|
||||
Self::AcceptProfileWarning => "Accept a profile warning.",
|
||||
Self::OpenSessionSettings => "Open your session settings.",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -655,6 +689,10 @@ impl AchievementName {
|
|||
Self::CreateRepost => Common,
|
||||
Self::OpenTos => Uncommon,
|
||||
Self::OpenPrivacyPolicy => Uncommon,
|
||||
Self::OpenReference => Uncommon,
|
||||
Self::GetAllOtherAchievements => Rare,
|
||||
Self::AcceptProfileWarning => Common,
|
||||
Self::OpenSessionSettings => Common,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue