From d4c623112b0cb7f39d40f37aa47aa33db813edc0 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani Date: Wed, 1 Oct 2025 14:17:50 +0200 Subject: Add post log and extract post log from admin tag --- .../6.2.2_add_forum_posting_logs_table.php | 25 +++++++ .../JsonApi/Routes/Forum/DiscussionPostings.php | 4 +- lib/classes/JsonApi/Routes/Forum/PostingShow.php | 4 +- lib/classes/JsonApi/Routes/Forum/PostingStore.php | 4 +- lib/classes/JsonApi/Routes/Forum/PostingUpdate.php | 4 +- lib/classes/JsonApi/SchemaMap.php | 1 + lib/classes/JsonApi/Schemas/Forum/Posting.php | 44 +++++++++++- lib/classes/JsonApi/Schemas/Forum/PostingLog.php | 78 ++++++++++++++++++++++ lib/models/Forum/Posting.php | 22 ++++++ lib/models/Forum/PostingLog.php | 37 ++++++++++ resources/vue/apps/forum/discussions/Show.vue | 2 +- resources/vue/components/forum/posts/Post.vue | 55 +++++++++++++-- .../vue/components/forum/posts/PostEditForm.vue | 2 +- 13 files changed, 268 insertions(+), 14 deletions(-) create mode 100644 db/migrations/6.2.2_add_forum_posting_logs_table.php create mode 100644 lib/classes/JsonApi/Schemas/Forum/PostingLog.php create mode 100644 lib/models/Forum/PostingLog.php diff --git a/db/migrations/6.2.2_add_forum_posting_logs_table.php b/db/migrations/6.2.2_add_forum_posting_logs_table.php new file mode 100644 index 0000000..14112b0 --- /dev/null +++ b/db/migrations/6.2.2_add_forum_posting_logs_table.php @@ -0,0 +1,25 @@ +exec(" + CREATE TABLE IF NOT EXISTS `forum_posting_logs` ( + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `posting_id` CHAR(32) COLLATE latin1_bin NOT NULL, + `user_id` CHAR(32) COLLATE latin1_bin NOT NULL, + `action` ENUM('create', 'edit', 'delete') NOT NULL DEFAULT 'create', + `mkdate` INT(11) UNSIGNED NOT NULL, + PRIMARY KEY (`id`) + ) + "); + } + + public function down() + { + \DBManager::get()->exec(" + DROP TABLE IF EXISTS `forum_posting_logs` + "); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionPostings.php b/lib/classes/JsonApi/Routes/Forum/DiscussionPostings.php index 0360aad..4cf39ae 100644 --- a/lib/classes/JsonApi/Routes/Forum/DiscussionPostings.php +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionPostings.php @@ -18,7 +18,9 @@ class DiscussionPostings extends JsonApiController \JsonApi\Schemas\Forum\Posting::REL_OPENGRAPH_URLS, \JsonApi\Schemas\Forum\Posting::REL_AUTHOR, \JsonApi\Schemas\Forum\Posting::REL_REACTIONS, - \JsonApi\Schemas\Forum\Posting::REL_REACTIONS_USER + \JsonApi\Schemas\Forum\Posting::REL_REACTIONS_USER, + \JsonApi\Schemas\Forum\Posting::REL_LOGS, + \JsonApi\Schemas\Forum\Posting::REL_LOGS_USER ]; public function __invoke(Request $request, Response $response, $args) diff --git a/lib/classes/JsonApi/Routes/Forum/PostingShow.php b/lib/classes/JsonApi/Routes/Forum/PostingShow.php index 730dc4f..2e5e9e4 100644 --- a/lib/classes/JsonApi/Routes/Forum/PostingShow.php +++ b/lib/classes/JsonApi/Routes/Forum/PostingShow.php @@ -16,7 +16,9 @@ class PostingShow extends JsonApiController \JsonApi\Schemas\Forum\Posting::REL_POSTING, \JsonApi\Schemas\Forum\Posting::REL_OPENGRAPH_URLS, \JsonApi\Schemas\Forum\Posting::REL_REACTIONS, - \JsonApi\Schemas\Forum\Posting::REL_REACTIONS_USER + \JsonApi\Schemas\Forum\Posting::REL_REACTIONS_USER, + \JsonApi\Schemas\Forum\Posting::REL_LOGS, + \JsonApi\Schemas\Forum\Posting::REL_LOGS_USER ]; public function __invoke(Request $request, Response $response, $args) diff --git a/lib/classes/JsonApi/Routes/Forum/PostingStore.php b/lib/classes/JsonApi/Routes/Forum/PostingStore.php index 1b563b9..0e28bdf 100644 --- a/lib/classes/JsonApi/Routes/Forum/PostingStore.php +++ b/lib/classes/JsonApi/Routes/Forum/PostingStore.php @@ -24,7 +24,9 @@ class PostingStore extends JsonApiController \JsonApi\Schemas\Forum\Posting::REL_OPENGRAPH_URLS, \JsonApi\Schemas\Forum\Posting::REL_AUTHOR, \JsonApi\Schemas\Forum\Posting::REL_REACTIONS, - \JsonApi\Schemas\Forum\Posting::REL_REACTIONS_USER + \JsonApi\Schemas\Forum\Posting::REL_REACTIONS_USER, + \JsonApi\Schemas\Forum\Posting::REL_LOGS, + \JsonApi\Schemas\Forum\Posting::REL_LOGS_USER ]; public function __invoke(Request $request, Response $response, $args) diff --git a/lib/classes/JsonApi/Routes/Forum/PostingUpdate.php b/lib/classes/JsonApi/Routes/Forum/PostingUpdate.php index 63b1ce5..75864b2 100644 --- a/lib/classes/JsonApi/Routes/Forum/PostingUpdate.php +++ b/lib/classes/JsonApi/Routes/Forum/PostingUpdate.php @@ -20,7 +20,9 @@ class PostingUpdate extends JsonApiController \JsonApi\Schemas\Forum\Posting::REL_OPENGRAPH_URLS, \JsonApi\Schemas\Forum\Posting::REL_AUTHOR, \JsonApi\Schemas\Forum\Posting::REL_REACTIONS, - \JsonApi\Schemas\Forum\Posting::REL_REACTIONS_USER + \JsonApi\Schemas\Forum\Posting::REL_REACTIONS_USER, + \JsonApi\Schemas\Forum\Posting::REL_LOGS, + \JsonApi\Schemas\Forum\Posting::REL_LOGS_USER ]; public function __invoke(Request $request, Response $response, $args) diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php index d45c6a7..2bc26de 100644 --- a/lib/classes/JsonApi/SchemaMap.php +++ b/lib/classes/JsonApi/SchemaMap.php @@ -44,6 +44,7 @@ class SchemaMap \Forum\Discussion::class => \JsonApi\Schemas\Forum\Discussion::class, \Forum\DiscussionType::class => \JsonApi\Schemas\Forum\DiscussionType::class, \Forum\Posting::class => \JsonApi\Schemas\Forum\Posting::class, + \Forum\PostingLog::class => \JsonApi\Schemas\Forum\PostingLog::class, \Forum\PostingReaction::class => \JsonApi\Schemas\Forum\PostingReaction::class, \Forum\Subscription::class => \JsonApi\Schemas\Forum\Subscription::class, \Forum\DTO\Member::class => \JsonApi\Schemas\Forum\Member::class, diff --git a/lib/classes/JsonApi/Schemas/Forum/Posting.php b/lib/classes/JsonApi/Schemas/Forum/Posting.php index e2323ec..5dac675 100644 --- a/lib/classes/JsonApi/Schemas/Forum/Posting.php +++ b/lib/classes/JsonApi/Schemas/Forum/Posting.php @@ -16,6 +16,8 @@ class Posting extends SchemaProvider const REL_REACTIONS = 'reactions'; const REL_REACTIONS_USER = 'reactions.user'; const REL_OPENGRAPH_URLS = 'opengraph-urls'; + const REL_LOGS = 'logs'; + const REL_LOGS_USER = 'logs.user'; /** * @param \Forum\Posting $resource @@ -59,7 +61,8 @@ class Posting extends SchemaProvider 'title' => $og['title'], 'description' => $og['description'], 'image' => $og['image'], - ], $resource->getOpenGraphURLs()) + ], $resource->getOpenGraphURLs()), + 'log' => $this->getPostLog($resource->content) ]; } @@ -73,6 +76,7 @@ class Posting extends SchemaProvider $relationships = $this->addDiscussionRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_DISCUSSION)); $relationships = $this->addPostingRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_POSTING)); $relationships = $this->addReactionsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_REACTIONS)); + $relationships = $this->addLogsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_LOGS)); return $relationships; } @@ -136,5 +140,41 @@ class Posting extends SchemaProvider return $relationships; } - + + private function addLogsRelationship(array $relationships, \Forum\Posting $posting, bool $withLogs = false) + { + if ($withLogs) { + $relationships[self::REL_LOGS] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->getRelationshipRelatedLink($posting, self::REL_REACTIONS) + ], + self::RELATIONSHIP_DATA => $posting->logs + ]; + } + + return $relationships; + } + + private function getPostLog($content): array + { + $attributes = []; + + if (preg_match('/^(.*)(id; + } + + /** + * @param \Forum\PostingLog $resource + */ + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'action' => $resource->action, + 'mkdate' => date('c', $resource->mkdate) + ]; + } + + /** + * @param \Forum\PostingLog $resource + */ + public function getRelationships($resource, ContextInterface $context): iterable + { + $relationships = []; + $relationships = $this->addPostingRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_POSTING)); + $relationships = $this->addUserRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_USER)); + + return $relationships; + } + + + private function addPostingRelationship(array $relationships, \Forum\PostingLog $postingLog, bool $withPosting = false) + { + $posting = $postingLog->posting; + + if ($withPosting && $posting) { + $relationships[self::REL_POSTING] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($posting) + ], + self::RELATIONSHIP_DATA => $posting + ]; + } + + return $relationships; + } + + private function addUserRelationship(array $relationships, \Forum\PostingLog $postingLog, bool $withUser = false) + { + $user = $postingLog->user; + + if ($withUser && $user) { + $relationships[self::REL_USER] = [ + self::RELATIONSHIP_LINKS => [ + Link::RELATED => $this->createLinkToResource($user) + ], + self::RELATIONSHIP_DATA => $user + ]; + } + + return $relationships; + } +} diff --git a/lib/models/Forum/Posting.php b/lib/models/Forum/Posting.php index 0bca814..f4a5d75 100644 --- a/lib/models/Forum/Posting.php +++ b/lib/models/Forum/Posting.php @@ -52,8 +52,16 @@ class Posting extends SimpleORMap 'assoc_foreign_key' => 'posting_id' ]; + $config['has_many']['logs'] = [ + 'class_name' => PostingLog::class, + 'foreign_key' => 'posting_id', + 'assoc_foreign_key' => 'posting_id', + 'order_by' => 'ORDER BY mkdate DESC LIMIT 1', + ]; + $config['additional_fields']['author']['get'] = 'getAuthor'; $config['registered_callbacks']['after_create'][] = 'onCreate'; + $config['registered_callbacks']['after_update'][] = 'onUpdate'; $config['registered_callbacks']['after_delete'][] = 'onDelete'; parent::configure($config); @@ -127,8 +135,22 @@ class Posting extends SimpleORMap $postingNotification->notifySubscribers(); } + public function onUpdate(): void + { + PostingLog::create([ + 'posting_id' => $this->posting_id, + 'user_id' => User::findCurrent()->user_id, + 'action' => 'edit' + ]); + } + public function onDelete(): void { PostingReaction::deleteBySQL("posting_id = ?", [$this->posting_id]); + PostingLog::create([ + 'posting_id' => $this->posting_id, + 'user_id' => User::findCurrent()->user_id, + 'action' => 'delete' + ]); } } diff --git a/lib/models/Forum/PostingLog.php b/lib/models/Forum/PostingLog.php new file mode 100644 index 0000000..75a05b9 --- /dev/null +++ b/lib/models/Forum/PostingLog.php @@ -0,0 +1,37 @@ + Posting::class, + 'foreign_key' => 'posting_id', + 'assoc_foreign_key' => 'posting_id' + ]; + + $config['belongs_to']['user'] = [ + 'class_name' => User::class, + 'foreign_key' => 'user_id', + 'assoc_foreign_key' => 'user_id' + ]; + + parent::configure($config); + } +} diff --git a/resources/vue/apps/forum/discussions/Show.vue b/resources/vue/apps/forum/discussions/Show.vue index 7e52669..222e451 100644 --- a/resources/vue/apps/forum/discussions/Show.vue +++ b/resources/vue/apps/forum/discussions/Show.vue @@ -90,7 +90,7 @@ const fetchPostings = async () => { `forum-discussions/${props.discussion.discussion_id}/postings`, { data: { - include: 'author,opengraph-urls,posting,reactions,reactions.user&fields[users]=id,username,formatted-name', + include: 'author,opengraph-urls,posting,reactions,reactions.user,logs,logs.user&fields[users]=id,username,formatted-name', page: { offset } } } diff --git a/resources/vue/components/forum/posts/Post.vue b/resources/vue/components/forum/posts/Post.vue index 48921aa..6528326 100644 --- a/resources/vue/components/forum/posts/Post.vue +++ b/resources/vue/components/forum/posts/Post.vue @@ -42,6 +42,23 @@ const selectedText = ref(''); const showPostEditForm = ref(false); const showPostCreateForm = ref(false); +const postRecentLog = computed(() => { + if (props.post.logs.length) { + return { + date: props.post.logs[0].mkdate, + author: props.post.logs[0].user.formatted_name, + username: props.post.logs[0].user.username, + } + } else if (props.post.meta.log.chdate) { + return { + date: props.post.meta.log.chdate, + author: props.post.meta.log.autor, + username: null, + } + } + + return null; +}); const isUnread = computed(() => (!props.post.author && props.is_unread) || (props.is_unread && props.post.author.id !== STUDIP.USER_ID)) const canEditPost = computed(() => forumConfig.isTutor || (props.post.author?.id === STUDIP.USER_ID && !props.discussion.closed_at)); const canDeletePost = computed(() => canEditPost.value); @@ -153,9 +170,22 @@ const removePostHighlight = id => { > {{ post.author.name }} - - {{ $gettext('Bearbeitet: ') }} - + + {{ $gettext('Zuletzt editiert von ') }} + + + – + @@ -177,9 +207,22 @@ const removePostHighlight = id => { > {{ post.author.name }} - - {{ $gettext('Bearbeitet: ') }} - + + {{ $gettext('Zuletzt editiert von ') }} + + + – + diff --git a/resources/vue/components/forum/posts/PostEditForm.vue b/resources/vue/components/forum/posts/PostEditForm.vue index 10821a1..20c665e 100644 --- a/resources/vue/components/forum/posts/PostEditForm.vue +++ b/resources/vue/components/forum/posts/PostEditForm.vue @@ -43,7 +43,7 @@ const updatePost = async () => { isLoading.value = true; const response = await STUDIP.jsonapi.withPromises().PATCH( - `forum-postings/${props.post.id}?include=opengraph-urls,posting`, + `forum-postings/${props.post.id}?include=opengraph-urls,posting,logs,logs.user&fields[users]=id,username,formatted-name`, { data: getPostJSONAPIObject } ); -- cgit v1.0