From f064dd30b9b5e3569e657bc55c4b2138f092c3d2 Mon Sep 17 00:00:00 2001 From: samuel-asleep Date: Fri, 3 Apr 2026 16:03:53 +0000 Subject: [PATCH 1/3] Implement edit posts and sticky/pinned posts for Posted boards --- src/retroshare/rsposted.h | 17 ++++++++-- src/rsitems/rsposteditems.cc | 61 +++++++++++++++++++++++++++--------- src/rsitems/rsposteditems.h | 2 ++ src/services/p3posted.cc | 35 ++++++++++++++++++++- src/services/p3posted.h | 3 +- 5 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/retroshare/rsposted.h b/src/retroshare/rsposted.h index 9829d973b6..27bf44fa7a 100644 --- a/src/retroshare/rsposted.h +++ b/src/retroshare/rsposted.h @@ -32,6 +32,7 @@ #include "retroshare/rsgxscommon.h" #include "retroshare/rsgxscircles.h" #include "serialiser/rsserializable.h" +#include "serialiser/rstlvidset.h" class RsPosted; @@ -46,6 +47,11 @@ struct RsPostedGroup: public RsSerializable, RsGxsGenericGroupData std::string mDescription; RsGxsImage mGroupImage; + /** @brief List of board pinned posts, those are displayed on top + * @todo run away from TLV old serializables as those types are opaque to + * JSON API! */ + RsTlvGxsMsgIdSet mPinnedPosts; + /// @see RsSerializable virtual void serial_process( RsGenericSerializer::SerializeJob j, RsGenericSerializer::SerializeContext& ctx ) override @@ -53,6 +59,11 @@ struct RsPostedGroup: public RsSerializable, RsGxsGenericGroupData RS_SERIAL_PROCESS(mMeta); RS_SERIAL_PROCESS(mDescription); RS_SERIAL_PROCESS(mGroupImage); + + if( mPinnedPosts.ids.empty() && j == RsGenericSerializer::DESERIALIZE + && ctx.mOffset == ctx.mSize ) + return; + RS_SERIAL_PROCESS(mPinnedPosts); } }; @@ -280,7 +291,7 @@ class RsPosted : public RsGxsIfaceHelper, public RsGxsCommentService virtual bool createPost(const RsPostedPost& post,RsGxsMessageId& post_id) =0; /** - * @brief createPostV2. Create post. Blocking API + * @brief createPostV2. Create or edit a post. Blocking API * @jsonapi{development} * @param[in] boardId Id of the board where to post * @param[in] title title of the post @@ -290,6 +301,7 @@ class RsPosted : public RsGxsIfaceHelper, public RsGxsCommentService * @param[in] image optional post image. * @param[out] postId id of the post after it's been generated * @param[out] error_message possible error message if the method returns false + * @param[in] origPostId if non-empty, this post replaces the original post with this Id. * @return true if ok, false if an error occured (see error_message) */ virtual bool createPostV2(const RsGxsGroupId& boardId, @@ -299,7 +311,8 @@ class RsPosted : public RsGxsIfaceHelper, public RsGxsCommentService const RsGxsId& authorId, const RsGxsImage& image, RsGxsMessageId& postId, - std::string& error_message) =0; + std::string& error_message, + const RsGxsMessageId& origPostId = RsGxsMessageId()) =0; /** @brief Add a comment on a post or on another comment. Blocking API. * @jsonapi{development} diff --git a/src/rsitems/rsposteditems.cc b/src/rsitems/rsposteditems.cc index 3c16ff5bd9..d5de5e4b36 100644 --- a/src/rsitems/rsposteditems.cc +++ b/src/rsitems/rsposteditems.cc @@ -19,8 +19,9 @@ * along with this program. If not, see . * * * *******************************************************************************/ -#include "rsitems/rsposteditems.h" -#include "serialiser/rstypeserializer.h" +#include "rsitems/rsposteditems.h" +#include "serialiser/rstypeserializer.h" +#include "serialiser/rstlvbase.h" @@ -42,18 +43,47 @@ void RsGxsPostedPostItem::serial_process(RsGenericSerializer::SerializeJob j,RsG RsTypeSerializer::serial_process(j,ctx,mImage,"mImage") ; } -void RsGxsPostedGroupItem::serial_process(RsGenericSerializer::SerializeJob j,RsGenericSerializer::SerializeContext& ctx) -{ - RsTypeSerializer::serial_process(j,ctx,TLV_TYPE_STR_DESCR ,mDescription,"mDescription") ; - - if(j == RsGenericSerializer::DESERIALIZE && ctx.mOffset == ctx.mSize) - return ; - - if((j == RsGenericSerializer::SIZE_ESTIMATE || j == RsGenericSerializer::SERIALIZE) && mGroupImage.empty()) - return ; - - RsTypeSerializer::serial_process(j,ctx,mGroupImage,"mGroupImage") ; -} +void RsGxsPostedGroupItem::serial_process(RsGenericSerializer::SerializeJob j,RsGenericSerializer::SerializeContext& ctx) +{ + RsTypeSerializer::serial_process(j,ctx,TLV_TYPE_STR_DESCR ,mDescription,"mDescription") ; + + if(j == RsGenericSerializer::DESERIALIZE) + { + if(ctx.mOffset == ctx.mSize) + return; + + if(ctx.mSize >= ctx.mOffset + TLV_HEADER_SIZE) + { + const uint16_t nextTlvType = GetTlvType( + ctx.mData + ctx.mOffset ); + if(nextTlvType == mGroupImage.TlvType()) + RsTypeSerializer::serial_process( + j,ctx,mGroupImage,"mGroupImage" ); + } + + if(ctx.mOffset == ctx.mSize) + return; + + if(ctx.mSize >= ctx.mOffset + TLV_HEADER_SIZE) + { + const uint16_t nextTlvType = GetTlvType( + ctx.mData + ctx.mOffset ); + if(nextTlvType == mPinnedPosts.TlvType()) + RsTypeSerializer::serial_process( + j,ctx,mPinnedPosts,"mPinnedPosts" ); + } + return; + } + + if(!mGroupImage.empty()) + RsTypeSerializer::serial_process(j,ctx,mGroupImage,"mGroupImage") ; + + if((j == RsGenericSerializer::SIZE_ESTIMATE + || j == RsGenericSerializer::SERIALIZE) + && mPinnedPosts.ids.empty()) return; + + RsTypeSerializer::serial_process(j,ctx,mPinnedPosts,"mPinnedPosts") ; +} RsItem *RsGxsPostedSerialiser::create_item(uint16_t service_id,uint8_t item_subtype) const { @@ -119,6 +149,7 @@ void RsGxsPostedGroupItem::clear() { mDescription.clear(); mGroupImage.TlvClear(); + mPinnedPosts.TlvClear(); } bool RsGxsPostedGroupItem::fromPostedGroup(RsPostedGroup &group, bool moveImage) @@ -126,6 +157,7 @@ bool RsGxsPostedGroupItem::fromPostedGroup(RsPostedGroup &group, bool moveImage) clear(); meta = group.mMeta; mDescription = group.mDescription; + mPinnedPosts = group.mPinnedPosts; if (moveImage) { @@ -144,6 +176,7 @@ bool RsGxsPostedGroupItem::toPostedGroup(RsPostedGroup &group, bool moveImage) { group.mMeta = meta; group.mDescription = mDescription; + group.mPinnedPosts = mPinnedPosts; if (moveImage) { group.mGroupImage.take((uint8_t *) mGroupImage.binData.bin_data, mGroupImage.binData.bin_len); diff --git a/src/rsitems/rsposteditems.h b/src/rsitems/rsposteditems.h index 910471c19f..323bf16af4 100644 --- a/src/rsitems/rsposteditems.h +++ b/src/rsitems/rsposteditems.h @@ -26,6 +26,7 @@ #include "rsitems/rsgxscommentitems.h" #include "rsitems/rsgxsitems.h" #include "serialiser/rstlvimage.h" +#include "serialiser/rstlvidset.h" #include "retroshare/rsposted.h" @@ -48,6 +49,7 @@ class RsGxsPostedGroupItem : public RsGxsGrpItem std::string mDescription; RsTlvImage mGroupImage; + RsTlvGxsMsgIdSet mPinnedPosts; }; diff --git a/src/services/p3posted.cc b/src/services/p3posted.cc index 2bb4f08471..b7ee98f287 100644 --- a/src/services/p3posted.cc +++ b/src/services/p3posted.cc @@ -21,6 +21,7 @@ *******************************************************************************/ #include "services/p3posted.h" #include "retroshare/rsgxscircles.h" +#include "retroshare/rsgxsflags.h" #include "retroshare/rspeers.h" #include "rsitems/rsposteditems.h" @@ -730,7 +731,8 @@ bool p3Posted::createPostV2(const RsGxsGroupId& boardId, const RsGxsId& authorId, const RsGxsImage& image, RsGxsMessageId& postId, - std::string& error_message) + std::string& error_message, + const RsGxsMessageId& origPostId) { // check boardId @@ -752,6 +754,36 @@ bool p3Posted::createPostV2(const RsGxsGroupId& boardId, return false; } + // If editing an existing post, validate it exists and the author matches (unless admin) + + if(!origPostId.isNull()) + { + std::vector orig_posts; + std::vector orig_comments; + std::vector orig_votes; + + if(!getBoardContent(boardId, { origPostId }, orig_posts, orig_comments, orig_votes) + || orig_posts.size() != 1) + { + error_message = "Original post " + origPostId.toStdString() + " not found in board " + boardId.toStdString(); + RsErr() << error_message; + return false; + } + + const RsGxsId& origAuthor = orig_posts[0].mMeta.mAuthorId; + bool isAdmin = IS_GROUP_ADMIN(groupsInfo[0].mMeta.mSubscribeFlags); + + if(origAuthor != authorId && !isAdmin) + { + error_message = "You do not have permission to edit this post. " + "Only the post author or a board admin can edit posts."; + RsErr() << __PRETTY_FUNCTION__ << " Editor " << authorId + << " is neither the original author " << origAuthor + << " nor a board admin." << std::endl; + return false; + } + } + RsPostedPost post; post.mMeta.mGroupId = boardId; post.mLink = link.toString(); @@ -759,6 +791,7 @@ bool p3Posted::createPostV2(const RsGxsGroupId& boardId, post.mNotes = notes; post.mMeta.mAuthorId = authorId; post.mMeta.mMsgName = title; + post.mMeta.mOrigMsgId = origPostId; // check size diff --git a/src/services/p3posted.h b/src/services/p3posted.h index 4adc2cf299..2a0d392efb 100644 --- a/src/services/p3posted.h +++ b/src/services/p3posted.h @@ -97,7 +97,8 @@ virtual void receiveHelperChanges(std::vector& changes) const RsGxsId& authorId, const RsGxsImage& image, RsGxsMessageId& postId, - std::string& error_message) override; + std::string& error_message, + const RsGxsMessageId& origPostId = RsGxsMessageId()) override; bool voteForPost(const RsGxsGroupId& boardId, const RsGxsMessageId& postMsgId, From 64285d916f4864551c82590a700c0d31f9b813ae Mon Sep 17 00:00:00 2001 From: samuel-asleep Date: Fri, 3 Apr 2026 16:39:56 +0000 Subject: [PATCH 2/3] Fix strlen undeclared error in librnp mem.cpp on MinGW/Windows builds --- src/libretroshare.pro | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libretroshare.pro b/src/libretroshare.pro index 812c593856..f3e08ffe03 100644 --- a/src/libretroshare.pro +++ b/src/libretroshare.pro @@ -1193,6 +1193,8 @@ message("In rnp_rnplib precompilation code") win32-g++ { LIBRNP_OUTPUT_LIBRARY = librnp.dll.a + # mem.cpp uses strlen which requires ; force-include it for all librnp TUs + LIBRNP_CMAKE_CXXFLAGS *= -include cstring } else { LIBRNP_OUTPUT_LIBRARY = librnp.a } From 8ef2c91541544c5503d03aaf3ed445184d7e67cd Mon Sep 17 00:00:00 2001 From: samuel-asleep Date: Sat, 4 Apr 2026 07:05:00 +0000 Subject: [PATCH 3/3] fix: use TLV_TYPE_GXSMSGIDSET constant instead of non-existent TlvType() method --- src/rsitems/rsposteditems.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rsitems/rsposteditems.cc b/src/rsitems/rsposteditems.cc index d5de5e4b36..c58f41e56d 100644 --- a/src/rsitems/rsposteditems.cc +++ b/src/rsitems/rsposteditems.cc @@ -56,7 +56,7 @@ void RsGxsPostedGroupItem::serial_process(RsGenericSerializer::SerializeJob j,Rs { const uint16_t nextTlvType = GetTlvType( ctx.mData + ctx.mOffset ); - if(nextTlvType == mGroupImage.TlvType()) + if(nextTlvType == TLV_TYPE_IMAGE) RsTypeSerializer::serial_process( j,ctx,mGroupImage,"mGroupImage" ); } @@ -68,7 +68,7 @@ void RsGxsPostedGroupItem::serial_process(RsGenericSerializer::SerializeJob j,Rs { const uint16_t nextTlvType = GetTlvType( ctx.mData + ctx.mOffset ); - if(nextTlvType == mPinnedPosts.TlvType()) + if(nextTlvType == TLV_TYPE_GXSMSGIDSET) RsTypeSerializer::serial_process( j,ctx,mPinnedPosts,"mPinnedPosts" ); }