feat(Moderation): enhance moderation reporting and user feedback
All checks were successful
Deploy to production / deploy (push) Successful in 1m55s

- Added user blocking checks in authentication and reporting processes, returning appropriate error responses.
- Expanded moderation report functionality to include new target types and optional fields for reports.
- Implemented a new API endpoint to retrieve the count of open moderation reports.
- Enhanced frontend components to allow users to report profiles, images, and guestbook entries, with corresponding UI updates.
- Updated internationalization files to include new strings for reporting features in both German and English.
This commit is contained in:
Torsten Schulz (local)
2026-04-27 15:57:02 +02:00
parent e94ae4350d
commit 530855e26e
16 changed files with 417 additions and 20 deletions

View File

@@ -10,6 +10,7 @@
</div>
<div v-if="$store.getters.isLoggedIn" class="actions">
<router-link class="editbutton" v-if="isOwner" :to="{ name: 'BlogEdit', params: { id: blog.id } }">{{ $t('blog.view.edit') }}</router-link>
<button type="button" class="report-btn" @click="reportBlog">{{ $t('blog.view.reportBlog') }}</button>
</div>
</section>
<div class="blog-content">
@@ -22,6 +23,9 @@
<article v-for="p in items" :key="p.id" class="post">
<h3>{{ p.title }}</h3>
<div class="content" v-html="sanitize(p.content)" />
<div class="post-actions">
<button type="button" class="report-btn" @click="reportPost(p)">{{ $t('blog.view.reportPost') }}</button>
</div>
</article>
<div class="pagination" v-if="total > pageSize">
<button :disabled="page===1" @click="go(page-1)">«</button>
@@ -44,6 +48,7 @@
<script>
import { getBlog, listPosts, createPost } from '@/api/blogApi.js';
import apiClient from '@/utils/axios.js';
import DOMPurify from 'dompurify';
import RichTextEditor from './components/RichTextEditor.vue';
import {
@@ -185,6 +190,41 @@ export default {
this.applyBlogSeo();
},
async go(p) { if (p>=1 && p<=this.pages) await this.fetchPage(p); },
async submitReport(payload, successKey) {
const reason = window.prompt(this.$t('socialnetwork.reporting.reasonPrompt'));
if (reason == null) return;
const trimmed = String(reason || '').trim();
if (trimmed.length < 3) {
this.$root.$refs.messageDialog?.open(this.$t('socialnetwork.reporting.reasonTooShort'), this.$t('error.title'));
return;
}
try {
await apiClient.post('/api/moderation/reports', {
...payload,
reason: trimmed,
details: payload.details || ''
});
this.$root.$refs.messageDialog?.open(this.$t(successKey), this.$t('message.title'));
} catch (error) {
console.error('Blog report failed:', error);
this.$root.$refs.messageDialog?.open(this.$t('socialnetwork.reporting.reportError'), this.$t('error.title'));
}
},
async reportBlog() {
if (!this.blog) return;
await this.submitReport({
targetType: 'blog',
targetId: this.blog.id,
details: `title=${this.blog.title || ''}; owner=${this.blog.owner?.username || ''}`
}, 'blog.view.reportBlogSent');
},
async reportPost(post) {
await this.submitReport({
targetType: 'blog_post',
targetId: post.id,
details: `blogId=${this.blog?.id || ''}; postTitle=${post?.title || ''}`
}, 'blog.view.reportPostSent');
},
async addPost() {
if (!this.newPost.title || !this.newPost.content) return;
const id = this.$route.params.id || this.resolvedId;
@@ -263,6 +303,17 @@ export default {
border-top: 1px solid var(--color-border);
}
.post-actions {
margin-top: 10px;
}
.report-btn {
min-height: auto;
padding: 4px 10px;
font-size: 0.78rem;
border-radius: 999px;
}
.content {
color: var(--color-text-secondary);
}