Adding community description in addition to sidebar, like site. (#5120)

* Adding community description in addition to sidebar, like site.

- Also made changes to lemmy's group apub to be similar to its site,
  which uses content for the sidebar, and summary for the short
  description.
- Fixes #5078

* Fixing tests.

* Remove comment.

* Fix name for description checker.
This commit is contained in:
Dessalines 2024-10-28 11:54:36 -04:00 committed by GitHub
parent d6d01a3b62
commit cdc1cf3bf7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 100 additions and 43 deletions

View file

@ -48,7 +48,9 @@ pub struct CreateCommunity {
pub name: String, pub name: String,
/// A longer title. /// A longer title.
pub title: String, pub title: String,
/// A longer sidebar, or description of your community, in markdown. /// A sidebar for the community in markdown.
pub sidebar: Option<String>,
/// A shorter, one line description of your community.
pub description: Option<String>, pub description: Option<String>,
/// An icon URL. /// An icon URL.
pub icon: Option<String>, pub icon: Option<String>,
@ -147,7 +149,9 @@ pub struct EditCommunity {
pub community_id: CommunityId, pub community_id: CommunityId,
/// A longer title. /// A longer title.
pub title: Option<String>, pub title: Option<String>,
/// A longer sidebar, or description of your community, in markdown. /// A sidebar for the community in markdown.
pub sidebar: Option<String>,
/// A shorter, one line description of your community.
pub description: Option<String>, pub description: Option<String>,
/// An icon URL. /// An icon URL.
pub icon: Option<String>, pub icon: Option<String>,

View file

@ -221,6 +221,7 @@ pub struct CreateSite {
/// Edits a site. /// Edits a site.
pub struct EditSite { pub struct EditSite {
pub name: Option<String>, pub name: Option<String>,
/// A sidebar for the site, in markdown.
pub sidebar: Option<String>, pub sidebar: Option<String>,
/// A shorter, one line description of your site. /// A shorter, one line description of your site.
pub description: Option<String>, pub description: Option<String>,

View file

@ -36,7 +36,11 @@ use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::{ utils::{
slurs::check_slurs, slurs::check_slurs,
validation::{is_valid_actor_name, is_valid_body_field}, validation::{
is_valid_actor_name,
is_valid_body_field,
site_or_community_description_length_check,
},
}, },
}; };
@ -57,8 +61,18 @@ pub async fn create_community(
let url_blocklist = get_url_blocklist(&context).await?; let url_blocklist = get_url_blocklist(&context).await?;
check_slurs(&data.name, &slur_regex)?; check_slurs(&data.name, &slur_regex)?;
check_slurs(&data.title, &slur_regex)?; check_slurs(&data.title, &slur_regex)?;
let description = let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
// Ensure that the sidebar has fewer than the max num characters...
if let Some(sidebar) = &sidebar {
is_valid_body_field(sidebar, false)?;
}
let description = data.description.clone();
if let Some(desc) = &description {
site_or_community_description_length_check(desc)?;
check_slurs(desc, &slur_regex)?;
}
let icon = diesel_url_create(data.icon.as_deref())?; let icon = diesel_url_create(data.icon.as_deref())?;
let icon = proxy_image_link_api(icon, &context).await?; let icon = proxy_image_link_api(icon, &context).await?;
@ -68,10 +82,6 @@ pub async fn create_community(
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?; is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
if let Some(desc) = &data.description {
is_valid_body_field(desc, false)?;
}
// Double check for duplicate community actor_ids // Double check for duplicate community actor_ids
let community_actor_id = generate_local_apub_endpoint( let community_actor_id = generate_local_apub_endpoint(
EndpointType::Community, EndpointType::Community,
@ -88,6 +98,7 @@ pub async fn create_community(
let keypair = generate_actor_keypair()?; let keypair = generate_actor_keypair()?;
let community_form = CommunityInsertForm { let community_form = CommunityInsertForm {
sidebar,
description, description,
icon, icon,
banner, banner,

View file

@ -41,16 +41,18 @@ pub async fn update_community(
let url_blocklist = get_url_blocklist(&context).await?; let url_blocklist = get_url_blocklist(&context).await?;
check_slurs_opt(&data.title, &slur_regex)?; check_slurs_opt(&data.title, &slur_regex)?;
let description = diesel_string_update( let sidebar = diesel_string_update(
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context) process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context)
.await? .await?
.as_deref(), .as_deref(),
); );
if let Some(Some(desc)) = &description { if let Some(Some(sidebar)) = &sidebar {
is_valid_body_field(desc, false)?; is_valid_body_field(sidebar, false)?;
} }
let description = diesel_string_update(data.description.as_deref());
let old_community = Community::read(&mut context.pool(), data.community_id).await?; let old_community = Community::read(&mut context.pool(), data.community_id).await?;
let icon = diesel_url_update(data.icon.as_deref())?; let icon = diesel_url_update(data.icon.as_deref())?;
@ -84,6 +86,7 @@ pub async fn update_community(
let community_form = CommunityUpdateForm { let community_form = CommunityUpdateForm {
title: data.title.clone(), title: data.title.clone(),
sidebar,
description, description,
icon, icon,
banner, banner,

View file

@ -29,13 +29,13 @@ use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyErrorType, LemmyResult}, error::{LemmyErrorType, LemmyResult},
utils::{ utils::{
slurs::{check_slurs, check_slurs_opt}, slurs::check_slurs,
validation::{ validation::{
build_and_check_regex, build_and_check_regex,
check_site_visibility_valid, check_site_visibility_valid,
is_valid_body_field, is_valid_body_field,
site_description_length_check,
site_name_length_check, site_name_length_check,
site_or_community_description_length_check,
}, },
}, },
}; };
@ -167,8 +167,8 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
check_slurs(&create_site.name, &slur_regex)?; check_slurs(&create_site.name, &slur_regex)?;
if let Some(desc) = &create_site.description { if let Some(desc) = &create_site.description {
site_description_length_check(desc)?; site_or_community_description_length_check(desc)?;
check_slurs_opt(&create_site.description, &slur_regex)?; check_slurs(desc, &slur_regex)?;
} }
site_default_post_listing_type_check(&create_site.default_post_listing_type)?; site_default_post_listing_type_check(&create_site.default_post_listing_type)?;

View file

@ -40,8 +40,8 @@ use lemmy_utils::{
check_site_visibility_valid, check_site_visibility_valid,
check_urls_are_valid, check_urls_are_valid,
is_valid_body_field, is_valid_body_field,
site_description_length_check,
site_name_length_check, site_name_length_check,
site_or_community_description_length_check,
}, },
}, },
}; };
@ -219,7 +219,7 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
} }
if let Some(desc) = &edit_site.description { if let Some(desc) = &edit_site.description {
site_description_length_check(desc)?; site_or_community_description_length_check(desc)?;
check_slurs_opt(&edit_site.description, &slur_regex)?; check_slurs_opt(&edit_site.description, &slur_regex)?;
} }

View file

@ -3,11 +3,13 @@
"type": "Group", "type": "Group",
"preferredUsername": "tenforward", "preferredUsername": "tenforward",
"name": "Ten Forward", "name": "Ten Forward",
"summary": "<p>Lounge and recreation facility</p>\n<hr />\n<p>Welcome to the Enterprise!.</p>\n", "summary": "A description of ten forward.",
"content": "<p>Lounge and recreation facility</p>\n<hr />\n<p>Welcome to the Enterprise!.</p>\n",
"source": { "source": {
"content": "Lounge and recreation facility\n\n---\n\nWelcome to the Enterprise!", "content": "Lounge and recreation facility\n\n---\n\nWelcome to the Enterprise!",
"mediaType": "text/markdown" "mediaType": "text/markdown"
}, },
"mediaType": "text/html",
"sensitive": false, "sensitive": false,
"icon": { "icon": {
"type": "Image", "type": "Image",

View file

@ -13,6 +13,7 @@ use crate::{
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
kinds::actor::GroupType, kinds::actor::GroupType,
protocol::values::MediaTypeHtml,
traits::{Actor, Object}, traits::{Actor, Object},
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -107,8 +108,10 @@ impl Object for ApubCommunity {
id: self.id().into(), id: self.id().into(),
preferred_username: self.name.clone(), preferred_username: self.name.clone(),
name: Some(self.title.clone()), name: Some(self.title.clone()),
summary: self.description.as_ref().map(|b| markdown_to_html(b)), content: self.sidebar.as_ref().map(|d| markdown_to_html(d)),
source: self.description.clone().map(Source::new), source: self.sidebar.clone().map(Source::new),
summary: self.description.clone(),
media_type: self.sidebar.as_ref().map(|_| MediaTypeHtml::Html),
icon: self.icon.clone().map(ImageObject::new), icon: self.icon.clone().map(ImageObject::new),
image: self.banner.clone().map(ImageObject::new), image: self.banner.clone().map(ImageObject::new),
sensitive: Some(self.nsfw), sensitive: Some(self.nsfw),
@ -144,10 +147,9 @@ impl Object for ApubCommunity {
let local_site = LocalSite::read(&mut context.pool()).await.ok(); let local_site = LocalSite::read(&mut context.pool()).await.ok();
let slur_regex = &local_site_opt_to_slur_regex(&local_site); let slur_regex = &local_site_opt_to_slur_regex(&local_site);
let url_blocklist = get_url_blocklist(context).await?; let url_blocklist = get_url_blocklist(context).await?;
let description = read_from_string_or_source_opt(&group.summary, &None, &group.source); let sidebar = read_from_string_or_source_opt(&group.content, &None, &group.source);
let description = let sidebar = process_markdown_opt(&sidebar, slur_regex, &url_blocklist, context).await?;
process_markdown_opt(&description, slur_regex, &url_blocklist, context).await?; let sidebar = markdown_rewrite_remote_links_opt(sidebar, context).await;
let description = markdown_rewrite_remote_links_opt(description, context).await;
let icon = proxy_image_link_opt_apub(group.icon.map(|i| i.url), context).await?; let icon = proxy_image_link_opt_apub(group.icon.map(|i| i.url), context).await?;
let banner = proxy_image_link_opt_apub(group.image.map(|i| i.url), context).await?; let banner = proxy_image_link_opt_apub(group.image.map(|i| i.url), context).await?;
@ -161,7 +163,8 @@ impl Object for ApubCommunity {
last_refreshed_at: Some(naive_now()), last_refreshed_at: Some(naive_now()),
icon, icon,
banner, banner,
description, sidebar,
description: group.summary,
followers_url: group.followers.clone().map(Into::into), followers_url: group.followers.clone().map(Into::into),
inbox_url: Some( inbox_url: Some(
group group
@ -299,10 +302,16 @@ pub(crate) mod tests {
assert_eq!(community.title, "Ten Forward"); assert_eq!(community.title, "Ten Forward");
assert!(!community.local); assert!(!community.local);
// Test the sidebar and description
assert_eq!( assert_eq!(
community.description.as_ref().map(std::string::String::len), community.sidebar.as_ref().map(std::string::String::len),
Some(63) Some(63)
); );
assert_eq!(
community.description,
Some("A description of ten forward.".into())
);
Community::delete(&mut context.pool(), community.id).await?; Community::delete(&mut context.pool(), community.id).await?;
Site::delete(&mut context.pool(), site.id).await?; Site::delete(&mut context.pool(), site.id).await?;

View file

@ -7,7 +7,7 @@ use crate::{
community_outbox::ApubCommunityOutbox, community_outbox::ApubCommunityOutbox,
}, },
local_site_data_cached, local_site_data_cached,
objects::{community::ApubCommunity, read_from_string_or_source_opt}, objects::community::ApubCommunity,
protocol::{ protocol::{
objects::{Endpoints, LanguageTag}, objects::{Endpoints, LanguageTag},
ImageObject, ImageObject,
@ -21,6 +21,7 @@ use activitypub_federation::{
protocol::{ protocol::{
helpers::deserialize_skip_error, helpers::deserialize_skip_error,
public_key::PublicKey, public_key::PublicKey,
values::MediaTypeHtml,
verification::verify_domains_match, verification::verify_domains_match,
}, },
}; };
@ -50,9 +51,13 @@ pub struct Group {
/// title /// title
pub(crate) name: Option<String>, pub(crate) name: Option<String>,
pub(crate) summary: Option<String>, // sidebar
pub(crate) content: Option<String>,
#[serde(deserialize_with = "deserialize_skip_error", default)] #[serde(deserialize_with = "deserialize_skip_error", default)]
pub(crate) source: Option<Source>, pub(crate) source: Option<Source>,
pub(crate) media_type: Option<MediaTypeHtml>,
// short instance description
pub(crate) summary: Option<String>,
#[serde(deserialize_with = "deserialize_skip_error", default)] #[serde(deserialize_with = "deserialize_skip_error", default)]
pub(crate) icon: Option<ImageObject>, pub(crate) icon: Option<ImageObject>,
/// banner /// banner
@ -86,8 +91,7 @@ impl Group {
check_slurs(&self.preferred_username, slur_regex)?; check_slurs(&self.preferred_username, slur_regex)?;
check_slurs_opt(&self.name, slur_regex)?; check_slurs_opt(&self.name, slur_regex)?;
let description = read_from_string_or_source_opt(&self.summary, &None, &self.source); check_slurs_opt(&self.summary, slur_regex)?;
check_slurs_opt(&description, slur_regex)?;
Ok(()) Ok(())
} }
} }

View file

@ -32,9 +32,9 @@ pub struct Instance {
pub(crate) content: Option<String>, pub(crate) content: Option<String>,
#[serde(deserialize_with = "deserialize_skip_error", default)] #[serde(deserialize_with = "deserialize_skip_error", default)]
pub(crate) source: Option<Source>, pub(crate) source: Option<Source>,
pub(crate) media_type: Option<MediaTypeHtml>,
// short instance description // short instance description
pub(crate) summary: Option<String>, pub(crate) summary: Option<String>,
pub(crate) media_type: Option<MediaTypeHtml>,
/// instance icon /// instance icon
pub(crate) icon: Option<ImageObject>, pub(crate) icon: Option<ImageObject>,
/// instance banner /// instance banner

View file

@ -508,6 +508,7 @@ mod tests {
id: inserted_community.id, id: inserted_community.id,
name: "TIL".into(), name: "TIL".into(),
title: "nada".to_owned(), title: "nada".to_owned(),
sidebar: None,
description: None, description: None,
nsfw: false, nsfw: false,
removed: false, removed: false,

View file

@ -170,7 +170,7 @@ diesel::table! {
name -> Varchar, name -> Varchar,
#[max_length = 255] #[max_length = 255]
title -> Varchar, title -> Varchar,
description -> Nullable<Text>, sidebar -> Nullable<Text>,
removed -> Bool, removed -> Bool,
published -> Timestamptz, published -> Timestamptz,
updated -> Nullable<Timestamptz>, updated -> Nullable<Timestamptz>,
@ -196,6 +196,8 @@ diesel::table! {
#[max_length = 255] #[max_length = 255]
featured_url -> Nullable<Varchar>, featured_url -> Nullable<Varchar>,
visibility -> CommunityVisibility, visibility -> CommunityVisibility,
#[max_length = 150]
description -> Nullable<Varchar>,
} }
} }

View file

@ -24,8 +24,8 @@ pub struct Community {
pub name: String, pub name: String,
/// A longer title, that can contain other characters, and doesn't have to be unique. /// A longer title, that can contain other characters, and doesn't have to be unique.
pub title: String, pub title: String,
/// A sidebar / markdown description. /// A sidebar for the community in markdown.
pub description: Option<String>, pub sidebar: Option<String>,
/// Whether the community is removed by a mod. /// Whether the community is removed by a mod.
pub removed: bool, pub removed: bool,
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
@ -66,6 +66,8 @@ pub struct Community {
#[serde(skip)] #[serde(skip)]
pub featured_url: Option<DbUrl>, pub featured_url: Option<DbUrl>,
pub visibility: CommunityVisibility, pub visibility: CommunityVisibility,
/// A shorter, one-line description of the site.
pub description: Option<String>,
} }
#[derive(Debug, Clone, derive_new::new)] #[derive(Debug, Clone, derive_new::new)]
@ -77,7 +79,7 @@ pub struct CommunityInsertForm {
pub title: String, pub title: String,
pub public_key: String, pub public_key: String,
#[new(default)] #[new(default)]
pub description: Option<String>, pub sidebar: Option<String>,
#[new(default)] #[new(default)]
pub removed: Option<bool>, pub removed: Option<bool>,
#[new(default)] #[new(default)]
@ -114,6 +116,8 @@ pub struct CommunityInsertForm {
pub posting_restricted_to_mods: Option<bool>, pub posting_restricted_to_mods: Option<bool>,
#[new(default)] #[new(default)]
pub visibility: Option<CommunityVisibility>, pub visibility: Option<CommunityVisibility>,
#[new(default)]
pub description: Option<String>,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -121,7 +125,7 @@ pub struct CommunityInsertForm {
#[cfg_attr(feature = "full", diesel(table_name = community))] #[cfg_attr(feature = "full", diesel(table_name = community))]
pub struct CommunityUpdateForm { pub struct CommunityUpdateForm {
pub title: Option<String>, pub title: Option<String>,
pub description: Option<Option<String>>, pub sidebar: Option<Option<String>>,
pub removed: Option<bool>, pub removed: Option<bool>,
pub published: Option<DateTime<Utc>>, pub published: Option<DateTime<Utc>>,
pub updated: Option<Option<DateTime<Utc>>>, pub updated: Option<Option<DateTime<Utc>>>,
@ -141,6 +145,7 @@ pub struct CommunityUpdateForm {
pub hidden: Option<bool>, pub hidden: Option<bool>,
pub posting_restricted_to_mods: Option<bool>, pub posting_restricted_to_mods: Option<bool>,
pub visibility: Option<CommunityVisibility>, pub visibility: Option<CommunityVisibility>,
pub description: Option<Option<String>>,
} }
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]

View file

@ -391,6 +391,7 @@ mod tests {
actor_id: inserted_community.actor_id.clone(), actor_id: inserted_community.actor_id.clone(),
local: true, local: true,
title: inserted_community.title, title: inserted_community.title,
sidebar: None,
description: None, description: None,
updated: None, updated: None,
banner: None, banner: None,

View file

@ -1092,6 +1092,7 @@ mod tests {
actor_id: data.inserted_community.actor_id.clone(), actor_id: data.inserted_community.actor_id.clone(),
local: true, local: true,
title: "nada".to_owned(), title: "nada".to_owned(),
sidebar: None,
description: None, description: None,
updated: None, updated: None,
banner: None, banner: None,

View file

@ -1834,6 +1834,7 @@ mod tests {
actor_id: inserted_community.actor_id.clone(), actor_id: inserted_community.actor_id.clone(),
local: true, local: true,
title: "nada".to_owned(), title: "nada".to_owned(),
sidebar: None,
description: None, description: None,
updated: None, updated: None,
banner: None, banner: None,

View file

@ -191,8 +191,8 @@ pub fn site_name_length_check(name: &str) -> LemmyResult<()> {
) )
} }
/// Checks the site description length, the limit as defined in the DB. /// Checks the site / community description length, the limit as defined in the DB.
pub fn site_description_length_check(description: &str) -> LemmyResult<()> { pub fn site_or_community_description_length_check(description: &str) -> LemmyResult<()> {
max_length_check( max_length_check(
description, description,
SITE_DESCRIPTION_MAX_LENGTH, SITE_DESCRIPTION_MAX_LENGTH,
@ -368,8 +368,8 @@ mod tests {
is_valid_matrix_id, is_valid_matrix_id,
is_valid_post_title, is_valid_post_title,
is_valid_url, is_valid_url,
site_description_length_check,
site_name_length_check, site_name_length_check,
site_or_community_description_length_check,
BIO_MAX_LENGTH, BIO_MAX_LENGTH,
SITE_DESCRIPTION_MAX_LENGTH, SITE_DESCRIPTION_MAX_LENGTH,
SITE_NAME_MAX_LENGTH, SITE_NAME_MAX_LENGTH,
@ -537,14 +537,14 @@ mod tests {
#[test] #[test]
fn test_valid_site_description() { fn test_valid_site_description() {
assert!(site_description_length_check( assert!(site_or_community_description_length_check(
&(0..SITE_DESCRIPTION_MAX_LENGTH) &(0..SITE_DESCRIPTION_MAX_LENGTH)
.map(|_| 'A') .map(|_| 'A')
.collect::<String>() .collect::<String>()
) )
.is_ok()); .is_ok());
let invalid_result = site_description_length_check( let invalid_result = site_or_community_description_length_check(
&(0..SITE_DESCRIPTION_MAX_LENGTH + 1) &(0..SITE_DESCRIPTION_MAX_LENGTH + 1)
.map(|_| 'A') .map(|_| 'A')
.collect::<String>(), .collect::<String>(),

View file

@ -0,0 +1,5 @@
ALTER TABLE community
DROP COLUMN description;
ALTER TABLE community RENAME COLUMN sidebar TO description;

View file

@ -0,0 +1,7 @@
-- Renaming description to sidebar
ALTER TABLE community RENAME COLUMN description TO sidebar;
-- Adding a short description column
ALTER TABLE community
ADD COLUMN description varchar(150);