diff --git a/client/components/modals/ReviewModal.vue b/client/components/modals/ReviewModal.vue
index 5dfa953d6..c236b2c97 100644
--- a/client/components/modals/ReviewModal.vue
+++ b/client/components/modals/ReviewModal.vue
@@ -23,6 +23,10 @@
+
+ delete
+ {{ $strings.ButtonDelete }}
+
{{ $strings.ButtonCancel }}
{{ $strings.ButtonSubmit }}
@@ -36,13 +40,15 @@
* Managed via the 'globals' Vuex store.
*
* @emit review-updated - Emits the new/updated review object on the root event bus.
+ * @emit review-deleted - Emits the libraryItemId of the deleted review on the root event bus.
*/
export default {
data() {
return {
rating: 0,
reviewText: '',
- processing: false
+ processing: false,
+ processingDelete: false
}
},
watch: {
@@ -78,6 +84,22 @@ export default {
}
},
methods: {
+ async deleteReview() {
+ if (!confirm('Are you sure you want to delete this review?')) return
+
+ this.processingDelete = true
+ try {
+ await this.$axios.$delete(`/api/items/${this.libraryItem.id}/review`)
+ this.$root.$emit('review-deleted', { libraryItemId: this.libraryItem.id, reviewId: this.selectedReviewItem.review.id })
+ this.$toast.success('Review deleted')
+ this.show = false
+ } catch (error) {
+ console.error('Failed to delete review', error)
+ this.$toast.error('Failed to delete review')
+ } finally {
+ this.processingDelete = false
+ }
+ },
async submit() {
if (!this.rating) {
this.$toast.error('Please select a rating')
diff --git a/client/components/tables/ReviewsTable.vue b/client/components/tables/ReviewsTable.vue
index 533559402..a0111e73c 100644
--- a/client/components/tables/ReviewsTable.vue
+++ b/client/components/tables/ReviewsTable.vue
@@ -31,7 +31,12 @@
{{ review.user.username }}
- {{ $formatDate(review.createdAt, dateFormat) }}
+
+
{{ $formatDate(review.createdAt, dateFormat) }}
+
+
{{ review.reviewText }}
@@ -44,7 +49,7 @@
diff --git a/client/pages/library/_library/ratings.vue b/client/pages/library/_library/ratings.vue
index 760e580c9..635a9369a 100644
--- a/client/pages/library/_library/ratings.vue
+++ b/client/pages/library/_library/ratings.vue
@@ -101,10 +101,13 @@
-
@@ -121,7 +124,7 @@
-
+
@@ -160,6 +163,9 @@ export default {
currentUser() {
return this.$store.state.user.user
},
+ isAdmin() {
+ return this.currentUser.type === 'admin' || this.currentUser.type === 'root'
+ },
sortItems() {
return [
{ value: 'newest', text: this.$strings.LabelSortNewestFirst },
@@ -259,6 +265,18 @@ export default {
isReviewAuthor(review) {
return review.userId === this.currentUser.id
},
+ async deleteReviewAdmin(review) {
+ if (!confirm(`Are you sure you want to delete ${review.user?.username || 'this'}'s review?`)) return
+
+ try {
+ await this.$axios.$delete(`/api/reviews/${review.id}`)
+ this.fetchReviews()
+ this.$toast.success('Review deleted')
+ } catch (error) {
+ console.error('Failed to delete review', error)
+ this.$toast.error('Failed to delete review')
+ }
+ },
editReview(review) {
this.$store.commit('globals/setReviewModal', {
libraryItem: review.libraryItem,
diff --git a/server/controllers/ReviewController.js b/server/controllers/ReviewController.js
index 3d61fced7..78a88fe86 100644
--- a/server/controllers/ReviewController.js
+++ b/server/controllers/ReviewController.js
@@ -108,6 +108,40 @@ class ReviewController {
}
}
+ /**
+ * DELETE: /api/reviews/:id
+ * Delete a review by ID.
+ * Admin or the owner of the review can delete it.
+ *
+ * @param {import('express').Request} req
+ * @param {import('express').Response} res
+ */
+ async deleteById(req, res) {
+ if (!Database.serverSettings.enableReviews) {
+ return res.status(403).send('Review feature is disabled')
+ }
+
+ const { id } = req.params
+
+ try {
+ const review = await Database.reviewModel.findByPk(id)
+ if (!review) {
+ return res.sendStatus(404)
+ }
+
+ // Check if user is owner or admin
+ if (review.userId !== req.user.id && req.user.type !== 'admin' && req.user.type !== 'root') {
+ return res.status(403).send('Not authorized to delete this review')
+ }
+
+ await review.destroy()
+ res.sendStatus(200)
+ } catch (error) {
+ Logger.error(`[ReviewController] Failed to delete review ${id}`, error)
+ res.status(500).send('Failed to delete review')
+ }
+ }
+
/**
* GET: /api/me/reviews
* Get all reviews by the current user.
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index ae1ab85f3..d798e751a 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -132,6 +132,7 @@ class ApiRouter {
this.router.get('/items/:id/reviews', ReviewController.middleware.bind(this), ReviewController.findAllForItem.bind(this))
this.router.post('/items/:id/review', ReviewController.middleware.bind(this), ReviewController.createUpdate.bind(this))
this.router.delete('/items/:id/review', ReviewController.middleware.bind(this), ReviewController.delete.bind(this))
+ this.router.delete('/reviews/:id', ReviewController.deleteById.bind(this))
//
// User Routes