feat(Moderation): enhance moderation reporting and user feedback

- 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 fd3eb34878
commit d53e2cc8a5
16 changed files with 417 additions and 20 deletions

View File

@@ -17,6 +17,11 @@
</li>
</ul>
<div class="tab-content" v-if="activeTab === 'general'">
<div class="profile-report-row">
<button type="button" class="report-btn" @click="reportProfile">
{{ $t('socialnetwork.profile.reportProfile') }}
</button>
</div>
<table>
<tr v-for="(value, key) in userProfile.params" :key="key">
<td>{{ $t(`socialnetwork.profile.${key}`) }}</td>
@@ -44,6 +49,9 @@
<li v-for="image in images" :key="image.id" @click="openImageDialog(image)">
<img :src="image.url || image.placeholder" :alt="$t('socialnetwork.gallery.imageLoadingAlt')" />
<p>{{ image.title }}</p>
<button type="button" class="report-btn" @click.stop="reportGalleryImage(image)">
{{ $t('socialnetwork.gallery.reportImage') }}
</button>
</li>
</ul>
</div>
@@ -82,6 +90,19 @@
<span @click="openProfile(entry.senderUsername)">{{ entry.sender }}</span>
</span>
</div>
<div class="entry-actions">
<button type="button" class="report-btn" @click="reportGuestbookEntry(entry)">
{{ $t('socialnetwork.profile.guestbook.reportEntry') }}
</button>
<button
v-if="canDeleteGuestbookEntry(entry)"
type="button"
class="delete-btn"
@click="deleteGuestbookEntry(entry)"
>
{{ $t('socialnetwork.profile.guestbook.deleteEntry') }}
</button>
</div>
</div>
<div class="pagination">
<button @click="loadGuestbookEntries(currentPage - 1)" :disabled="currentPage === 1">{{
@@ -357,6 +378,75 @@ export default {
console.error('Error fetching image:', error);
}
},
canDeleteGuestbookEntry(entry) {
const ownUsername = String(this.user?.username || '').toLowerCase();
const profileUsername = String(this.userProfile?.username || '').toLowerCase();
const sender = String(entry?.sender || '').toLowerCase();
return ownUsername && (ownUsername === sender || ownUsername === profileUsername);
},
async deleteGuestbookEntry(entry) {
const ok = window.confirm(this.$t('socialnetwork.profile.guestbook.confirmDeleteEntry'));
if (!ok) return;
try {
await apiClient.delete(`/api/socialnetwork/guestbook/entries/${entry.id}`);
await this.loadGuestbookEntries(this.currentPage);
} catch (error) {
console.error('Fehler beim Löschen des Gästebucheintrags:', error);
}
},
async reportModerationTarget(payload, successKey, errorKey) {
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('Error creating moderation report:', error);
this.$root.$refs.messageDialog?.open(this.$t(errorKey), this.$t('error.title'));
}
},
async reportProfile() {
await this.reportModerationTarget(
{
targetType: 'user_profile',
targetRef: this.userId,
details: `username=${this.userProfile?.username || ''}`
},
'socialnetwork.reporting.profileReported',
'socialnetwork.reporting.reportError'
);
},
async reportGalleryImage(image) {
await this.reportModerationTarget(
{
targetType: 'gallery_image',
targetId: image.id,
details: `profile=${this.userProfile?.username || ''}; imageTitle=${image?.title || ''}`
},
'socialnetwork.reporting.imageReported',
'socialnetwork.reporting.reportError'
);
},
async reportGuestbookEntry(entry) {
await this.reportModerationTarget(
{
targetType: 'guestbook_entry',
targetId: entry.id,
details: `profile=${this.userProfile?.username || ''}; sender=${entry?.sender || ''}`
},
'socialnetwork.reporting.guestbookReported',
'socialnetwork.reporting.reportError'
);
},
async handleFriendship() {
console.log(this.friendshipState);
if (['none', 'withdrawn'].includes(this.friendshipState)) {
@@ -502,6 +592,10 @@ export default {
text-align: center;
}
.image-list > li > .report-btn {
margin-top: 6px;
}
.folder-name-text {
cursor: pointer;
}
@@ -541,6 +635,26 @@ export default {
color: gray;
}
.entry-actions {
margin-top: 8px;
display: flex;
gap: 8px;
}
.report-btn,
.delete-btn {
min-height: auto;
padding: 4px 10px;
font-size: 0.75rem;
border-radius: 999px;
}
.profile-report-row {
display: flex;
justify-content: flex-end;
margin-bottom: 8px;
}
.pagination {
display: flex;
justify-content: center;