feat: add optional mood to thoughts with custom moods support
Mood is an optional label+emoji string (e.g. "relaxed 😌") on thoughts.
Users can define up to 8 custom moods in profile settings.
Mood federates via AP Note JSON and displays on thought cards.
This commit is contained in:
@@ -100,6 +100,7 @@ fn local_thought(author_id: UserId) -> Thought {
|
||||
visibility: Visibility::Public,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -283,6 +284,7 @@ async fn direct_thought_created_does_not_broadcast() {
|
||||
visibility: Visibility::Direct,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
});
|
||||
store.users.lock().unwrap().push(alice.clone());
|
||||
store.thoughts.lock().unwrap().push(thought.clone());
|
||||
@@ -312,6 +314,7 @@ async fn followers_only_thought_does_not_broadcast_publicly() {
|
||||
visibility: Visibility::Followers,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
});
|
||||
store.users.lock().unwrap().push(alice.clone());
|
||||
store.thoughts.lock().unwrap().push(thought.clone());
|
||||
|
||||
@@ -32,6 +32,7 @@ async fn like_creates_notification_for_thought_author() {
|
||||
visibility: Visibility::Public,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
});
|
||||
store.thoughts.lock().unwrap().push(thought.clone());
|
||||
let svc = NotificationEventService {
|
||||
@@ -62,6 +63,7 @@ async fn self_like_creates_no_notification() {
|
||||
visibility: Visibility::Public,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
});
|
||||
store.thoughts.lock().unwrap().push(thought.clone());
|
||||
let svc = NotificationEventService {
|
||||
@@ -111,6 +113,7 @@ async fn reply_creates_notification_for_original_author() {
|
||||
visibility: Visibility::Public,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
});
|
||||
store.thoughts.lock().unwrap().push(original.clone());
|
||||
let svc = NotificationEventService {
|
||||
@@ -141,6 +144,7 @@ async fn self_reply_creates_no_notification() {
|
||||
visibility: Visibility::Public,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
});
|
||||
store.thoughts.lock().unwrap().push(original.clone());
|
||||
let svc = NotificationEventService {
|
||||
@@ -169,6 +173,7 @@ async fn self_boost_creates_no_notification() {
|
||||
visibility: Visibility::Public,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
});
|
||||
store.thoughts.lock().unwrap().push(thought.clone());
|
||||
let svc = NotificationEventService {
|
||||
|
||||
@@ -2,6 +2,9 @@ const MAX_TOP_FRIENDS: usize = 8;
|
||||
const MAX_PROFILE_FIELDS: usize = 4;
|
||||
const MAX_FIELD_NAME_LEN: usize = 64;
|
||||
const MAX_FIELD_VALUE_LEN: usize = 256;
|
||||
const MAX_CUSTOM_MOODS: usize = 8;
|
||||
const MAX_MOOD_LABEL_LEN: usize = 32;
|
||||
const MAX_MOOD_EMOJI_LEN: usize = 8;
|
||||
|
||||
use bytes::Bytes;
|
||||
use domain::{
|
||||
@@ -72,6 +75,20 @@ pub async fn update_profile(
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(ref moods) = input.custom_moods {
|
||||
if moods.len() > MAX_CUSTOM_MOODS {
|
||||
return Err(DomainError::InvalidInput(format!(
|
||||
"custom moods: max {MAX_CUSTOM_MOODS}"
|
||||
)));
|
||||
}
|
||||
for (label, emoji) in moods {
|
||||
if label.len() > MAX_MOOD_LABEL_LEN || emoji.len() > MAX_MOOD_EMOJI_LEN {
|
||||
return Err(DomainError::InvalidInput(
|
||||
"custom mood label or emoji too long".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
users.update_profile(user_id, input).await?;
|
||||
events
|
||||
.publish(&DomainEvent::ProfileUpdated {
|
||||
|
||||
@@ -34,6 +34,7 @@ async fn like_and_unlike() {
|
||||
visibility: Visibility::Public,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
}));
|
||||
like_thought(&store, &store, &alice.id, &tid).await.unwrap();
|
||||
assert_eq!(store.likes.lock().unwrap().len(), 1);
|
||||
|
||||
@@ -26,6 +26,7 @@ pub struct CreateThoughtInput {
|
||||
pub visibility: Option<String>,
|
||||
pub content_warning: Option<String>,
|
||||
pub sensitive: bool,
|
||||
pub mood: Option<String>,
|
||||
}
|
||||
pub struct CreateThoughtOutput {
|
||||
pub thought: Thought,
|
||||
@@ -39,6 +40,11 @@ pub async fn create_thought(
|
||||
outbox: &dyn OutboxWriter,
|
||||
input: CreateThoughtInput,
|
||||
) -> Result<CreateThoughtOutput, DomainError> {
|
||||
if let Some(ref m) = input.mood {
|
||||
if m.len() > 64 {
|
||||
return Err(DomainError::InvalidInput("mood: max 64 chars".into()));
|
||||
}
|
||||
}
|
||||
let content = Content::new_local(input.content)?;
|
||||
let visibility = match input.visibility.as_deref() {
|
||||
Some("followers") => Visibility::Followers,
|
||||
@@ -54,6 +60,7 @@ pub async fn create_thought(
|
||||
visibility,
|
||||
content_warning: input.content_warning,
|
||||
sensitive: input.sensitive,
|
||||
mood: input.mood,
|
||||
});
|
||||
thoughts.save(&thought).await?;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ fn input(uid: UserId) -> CreateThoughtInput {
|
||||
visibility: None,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +208,7 @@ async fn create_reply_sets_in_reply_to_id() {
|
||||
visibility: None,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -243,6 +245,7 @@ fn make_thought(user_id: UserId) -> Thought {
|
||||
visibility: Visibility::Public,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -295,6 +298,7 @@ async fn get_thread_views_batches_correctly() {
|
||||
visibility: Visibility::Public,
|
||||
content_warning: None,
|
||||
sensitive: false,
|
||||
mood: None,
|
||||
});
|
||||
<TestStore as ThoughtRepository>::save(&store, &reply)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user