diff --git a/Imagine Dragons - Believer/Imagine Dragons - Believer.mp3 b/Imagine Dragons - Believer/Imagine Dragons - Believer.mp3 new file mode 100644 index 000000000..02c2ea946 Binary files /dev/null and b/Imagine Dragons - Believer/Imagine Dragons - Believer.mp3 differ diff --git a/client/components/recommendations/RecommendButton.vue b/client/components/recommendations/RecommendButton.vue index 17520fea1..4e5400c67 100644 --- a/client/components/recommendations/RecommendButton.vue +++ b/client/components/recommendations/RecommendButton.vue @@ -27,11 +27,17 @@ +
+ + +

Enter the user’s username. We’ll resolve it on the server.

+
+
- {{ error }} @@ -56,7 +62,7 @@ export default { submitting: false, ok: false, error: '', - form: { tagId: '', visibility: 'public', note: '' } + form: { tagId: '', visibility: 'public', recipient: '', note: '' } }), mounted() { const flag = this.$store.getters['getServerSetting']?.('recommendationsEnabled') @@ -104,6 +110,10 @@ export default { }, async submit() { if (!this.form.tagId) return + if (this.form.visibility === 'recipient-only' && !this.form.recipient) { + this.error = 'Recipient is required' + return + } this.submitting = true this.error = '' this.ok = false @@ -112,7 +122,8 @@ export default { bookId: this.bookId, tagId: this.form.tagId, note: this.form.note || '', - visibility: this.form.visibility + visibility: this.form.visibility, + recipientUsername: this.form.visibility === 'recipient-only' ? this.form.recipient : null } const created = await this.$axios.$post('/api/recommendations', payload) this.$emit('recommended', created) diff --git a/server/controllers/recommendationsController.js b/server/controllers/recommendationsController.js index 31e153826..456f41808 100644 --- a/server/controllers/recommendationsController.js +++ b/server/controllers/recommendationsController.js @@ -14,7 +14,7 @@ module.exports = (injectedManager) => { const userId = req.user?.id if (!userId) return res.status(401).send('Unauthorized') - const { bookId, tagId, tagSlug, recipientUserId, note, visibility } = req.body || {} + const { bookId, tagId, tagSlug, recipientUserId, recipientUsername, note, visibility } = req.body || {} if (!bookId) return res.status(400).json({ message: 'bookId is required' }) if (!tagId && !tagSlug) return res.status(400).json({ message: 'tagId or tagSlug is required' }) @@ -24,6 +24,7 @@ module.exports = (injectedManager) => { tagId, tagSlug, recipientUserId, + recipientUsername, note, visibility }) diff --git a/server/managers/RecommendationManager.js b/server/managers/RecommendationManager.js index 0be609c91..9ef501eae 100644 --- a/server/managers/RecommendationManager.js +++ b/server/managers/RecommendationManager.js @@ -86,19 +86,30 @@ const RecommendationManager = { return null }, - async resolveRecipient(recipientUserId) { - if (!recipientUserId) return null - const user = await Database.userModel.findByPk(recipientUserId) - return user || null + async resolveRecipient({ recipientUserId, recipientUsername }) { + const User = Database.userModel + if (recipientUserId) { + const u = await User.findByPk(recipientUserId) + return u || null + } + if (recipientUsername) { + const u = await User.findOne({ where: { username: String(recipientUsername) } }) + return u || null + } + return null }, - async createRecommendation({ userId, bookId, tagId, tagSlug, recipientUserId, note, visibility }) { + async createRecommendation({ userId, bookId, tagId, tagSlug, recipientUserId, recipientUsername, note, visibility }) { const tag = await this.resolveTag({ tagId, tagSlug }) if (!tag) return { error: { status: 400, message: 'Invalid tag' } } - const recip = await this.resolveRecipient(recipientUserId) - if (recipientUserId && !recip) return { error: { status: 400, message: 'Recipient not found' } } - + if (visibility === 'recipient-only' && !recipientUserId && !recipientUsername) { + return { error: { status: 400, message: 'Recipient is required for recipient-only visibility' } } + } + const recip = await this.resolveRecipient({ recipientUserId, recipientUsername }) + if (visibility === 'recipient-only' && !recip) { + return { error: { status: 400, message: 'Recipient not found' } } + } const created = await Database.bookRecommendationModel.create({ bookId: String(bookId), tagId: tag.id,