Skip to content

Commit

Permalink
Community post tags (part 1) (LemmyNet#4997)
Browse files Browse the repository at this point in the history
* partial post tags implementation

* fixes

* fix lints

* schema fix

* chore: restructure / rename tag tables

* chore: fix post view tests

* format

* lint

* expect used

* chore: update code to maybe final version

* add ts-rs optionals

* remove error context

* clippy
  • Loading branch information
phiresky authored Dec 18, 2024
1 parent d346890 commit a2a5cb0
Show file tree
Hide file tree
Showing 16 changed files with 647 additions and 235 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion crates/api_common/src/post.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use lemmy_db_schema::{
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId, TagId},
ListingType,
PostFeatureType,
PostSortType,
Expand Down Expand Up @@ -37,6 +37,8 @@ pub struct CreatePost {
/// Instead of fetching a thumbnail, use a custom one.
#[cfg_attr(feature = "full", ts(optional))]
pub custom_thumbnail: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub tags: Option<Vec<TagId>>,
/// Time when this post should be scheduled. Null means publish immediately.
#[cfg_attr(feature = "full", ts(optional))]
pub scheduled_publish_time: Option<i64>,
Expand Down Expand Up @@ -164,6 +166,8 @@ pub struct EditPost {
/// Instead of fetching a thumbnail, use a custom one.
#[cfg_attr(feature = "full", ts(optional))]
pub custom_thumbnail: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub tags: Option<Vec<TagId>>,
/// Time when this post should be scheduled. Null means publish immediately.
#[cfg_attr(feature = "full", ts(optional))]
pub scheduled_publish_time: Option<i64>,
Expand Down
1 change: 1 addition & 0 deletions crates/db_schema/src/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ pub mod private_message_report;
pub mod registration_application;
pub mod secret;
pub mod site;
pub mod tag;
pub mod tagline;
53 changes: 53 additions & 0 deletions crates/db_schema/src/impls/tag.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate::{
newtypes::TagId,
schema::{post_tag, tag},
source::tag::{PostTagInsertForm, Tag, TagInsertForm},
traits::Crud,
utils::{get_conn, DbPool},
};
use diesel::{insert_into, result::Error, QueryDsl};
use diesel_async::RunQueryDsl;
use lemmy_utils::error::LemmyResult;

#[async_trait]
impl Crud for Tag {
type InsertForm = TagInsertForm;

type UpdateForm = TagInsertForm;

type IdType = TagId;

async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(tag::table)
.values(form)
.get_result::<Self>(conn)
.await
}

async fn update(
pool: &mut DbPool<'_>,
pid: TagId,
form: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(tag::table.find(pid))
.set(form)
.get_result::<Self>(conn)
.await
}
}

impl PostTagInsertForm {
pub async fn insert_tag_associations(
pool: &mut DbPool<'_>,
tags: &[PostTagInsertForm],
) -> LemmyResult<()> {
let conn = &mut get_conn(pool).await?;
insert_into(post_tag::table)
.values(tags)
.execute(conn)
.await?;
Ok(())
}
}
6 changes: 6 additions & 0 deletions crates/db_schema/src/newtypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,9 @@ impl InstanceId {
self.0
}
}

#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))]
/// The internal tag id.
pub struct TagId(pub i32);
25 changes: 25 additions & 0 deletions crates/db_schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,14 @@ diesel::table! {
}
}

diesel::table! {
post_tag (post_id, tag_id) {
post_id -> Int4,
tag_id -> Int4,
published -> Timestamptz,
}
}

diesel::table! {
private_message (id) {
id -> Int4,
Expand Down Expand Up @@ -951,6 +959,18 @@ diesel::table! {
}
}

diesel::table! {
tag (id) {
id -> Int4,
ap_id -> Text,
name -> Text,
community_id -> Int4,
published -> Timestamptz,
updated -> Nullable<Timestamptz>,
deleted -> Bool,
}
}

diesel::table! {
tagline (id) {
id -> Int4,
Expand Down Expand Up @@ -1032,13 +1052,16 @@ diesel::joinable!(post_aggregates -> instance (instance_id));
diesel::joinable!(post_aggregates -> person (creator_id));
diesel::joinable!(post_aggregates -> post (post_id));
diesel::joinable!(post_report -> post (post_id));
diesel::joinable!(post_tag -> post (post_id));
diesel::joinable!(post_tag -> tag (tag_id));
diesel::joinable!(private_message_report -> private_message (private_message_id));
diesel::joinable!(registration_application -> local_user (local_user_id));
diesel::joinable!(registration_application -> person (admin_id));
diesel::joinable!(site -> instance (instance_id));
diesel::joinable!(site_aggregates -> site (site_id));
diesel::joinable!(site_language -> language (language_id));
diesel::joinable!(site_language -> site (site_id));
diesel::joinable!(tag -> community (community_id));

diesel::allow_tables_to_appear_in_same_query!(
admin_allow_instance,
Expand Down Expand Up @@ -1098,6 +1121,7 @@ diesel::allow_tables_to_appear_in_same_query!(
post_actions,
post_aggregates,
post_report,
post_tag,
private_message,
private_message_report,
received_activity,
Expand All @@ -1108,5 +1132,6 @@ diesel::allow_tables_to_appear_in_same_query!(
site,
site_aggregates,
site_language,
tag,
tagline,
);
1 change: 1 addition & 0 deletions crates/db_schema/src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub mod private_message_report;
pub mod registration_application;
pub mod secret;
pub mod site;
pub mod tag;
pub mod tagline;

/// Default value for columns like [community::Community.inbox_url] which are marked as serde(skip).
Expand Down
57 changes: 57 additions & 0 deletions crates/db_schema/src/source/tag.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::newtypes::{CommunityId, DbUrl, PostId, TagId};
#[cfg(feature = "full")]
use crate::schema::{post_tag, tag};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;

/// A tag that can be assigned to a post within a community.
/// The tag object is created by the community moderators.
/// The assignment happens by the post creator and can be updated by the community moderators.
///
/// A tag is a federatable object that gives additional context to another object, which can be
/// displayed and filtered on currently, we only have community post tags, which is a tag that is
/// created by post authors as well as mods of a community, to categorize a post. in the future we
/// may add more tag types, depending on the requirements, this will lead to either expansion of
/// this table (community_id optional, addition of tag_type enum) or split of this table / creation
/// of new tables.
#[skip_serializing_none]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable, Identifiable))]
#[cfg_attr(feature = "full", diesel(table_name = tag))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
pub struct Tag {
pub id: TagId,
pub ap_id: DbUrl,
pub name: String,
/// the community that owns this tag
pub community_id: CommunityId,
pub published: DateTime<Utc>,
#[cfg_attr(feature = "full", ts(optional))]
pub updated: Option<DateTime<Utc>>,
pub deleted: bool,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = tag))]
pub struct TagInsertForm {
pub ap_id: DbUrl,
pub name: String,
pub community_id: CommunityId,
// default now
pub published: Option<DateTime<Utc>>,
pub updated: Option<DateTime<Utc>>,
pub deleted: bool,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = post_tag))]
pub struct PostTagInsertForm {
pub post_id: PostId,
pub tag_id: TagId,
}
5 changes: 5 additions & 0 deletions crates/db_schema/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,11 @@ pub mod functions {

// really this function is variadic, this just adds the two-argument version
define_sql_function!(fn coalesce<T: diesel::sql_types::SqlType + diesel::sql_types::SingleValue>(x: diesel::sql_types::Nullable<T>, y: T) -> T);

define_sql_function! {
#[aggregate]
fn json_agg<T: diesel::sql_types::SqlType + diesel::sql_types::SingleValue>(obj: T) -> Json
}
}

pub const DELETED_REPLACEMENT_TEXT: &str = "*Permanently Deleted*";
Expand Down
2 changes: 2 additions & 0 deletions crates/db_views/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ diesel-async = { workspace = true, optional = true }
diesel_ltree = { workspace = true, optional = true }
serde = { workspace = true }
serde_with = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true, optional = true }
ts-rs = { workspace = true, optional = true }
actix-web = { workspace = true, optional = true }
Expand All @@ -46,3 +47,4 @@ serial_test = { workspace = true }
tokio = { workspace = true }
pretty_assertions = { workspace = true }
url = { workspace = true }
test-context = "0.3.0"
2 changes: 2 additions & 0 deletions crates/db_views/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub mod local_user_view;
#[cfg(feature = "full")]
pub mod post_report_view;
#[cfg(feature = "full")]
pub mod post_tags_view;
#[cfg(feature = "full")]
pub mod post_view;
#[cfg(feature = "full")]
pub mod private_message_report_view;
Expand Down
30 changes: 30 additions & 0 deletions crates/db_views/src/post_tags_view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! see post_view.rs for the reason for this json decoding
use crate::structs::PostTags;
use diesel::{
deserialize::FromSql,
pg::{Pg, PgValue},
serialize::ToSql,
sql_types::{self, Nullable},
};

impl FromSql<Nullable<sql_types::Json>, Pg> for PostTags {
fn from_sql(bytes: PgValue) -> diesel::deserialize::Result<Self> {
let value = <serde_json::Value as FromSql<sql_types::Json, Pg>>::from_sql(bytes)?;
Ok(serde_json::from_value::<PostTags>(value)?)
}
fn from_nullable_sql(
bytes: Option<<Pg as diesel::backend::Backend>::RawValue<'_>>,
) -> diesel::deserialize::Result<Self> {
match bytes {
Some(bytes) => Self::from_sql(bytes),
None => Ok(Self { tags: vec![] }),
}
}
}

impl ToSql<Nullable<sql_types::Json>, Pg> for PostTags {
fn to_sql(&self, out: &mut diesel::serialize::Output<Pg>) -> diesel::serialize::Result {
let value = serde_json::to_value(self)?;
<serde_json::Value as ToSql<sql_types::Json, Pg>>::to_sql(&value, &mut out.reborrow())
}
}
Loading

0 comments on commit a2a5cb0

Please sign in to comment.