From ef2b7eb928079164e77a52e540953a841063a26f Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 28 May 2025 23:26:18 +0200 Subject: [PATCH] Add UI for managing blockers --- habitica-images | 2 +- .../src/components/blocker/blocker_form.vue | 51 +++++++ .../client/src/components/blocker/index.vue | 132 ++++++++++++++---- website/client/src/store/actions/blockers.js | 14 +- website/server/controllers/api-v4/admin.js | 59 +++++++- website/server/models/blocker.js | 11 -- 6 files changed, 224 insertions(+), 45 deletions(-) create mode 100644 website/client/src/components/blocker/blocker_form.vue diff --git a/habitica-images b/habitica-images index 992d838120..aa72332019 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit 992d8381200857d0962814f3435c4c7ce8fcbe30 +Subproject commit aa723320199d7f03ce749d431b46e8d7f95cc8de diff --git a/website/client/src/components/blocker/blocker_form.vue b/website/client/src/components/blocker/blocker_form.vue new file mode 100644 index 0000000000..1413dc25b9 --- /dev/null +++ b/website/client/src/components/blocker/blocker_form.vue @@ -0,0 +1,51 @@ + + + diff --git a/website/client/src/components/blocker/index.vue b/website/client/src/components/blocker/index.vue index 4db7122469..0787aff19f 100644 --- a/website/client/src/components/blocker/index.vue +++ b/website/client/src/components/blocker/index.vue @@ -1,7 +1,10 @@ @@ -72,11 +90,23 @@ import { mapState } from '@/libs/store'; import editIcon from '@/assets/svg/edit.svg'; import deleteIcon from '@/assets/svg/delete.svg'; +import BlockerForm from './blocker_form.vue'; export default { + components: { + BlockerForm, + }, data () { return { + showCreateForm: false, + newBlocker: { + type: '', + area: '', + value: '', + reason: '', + }, blockers: [], + editedBlockerId: null, icons: Object.freeze({ editIcon, deleteIcon, @@ -95,7 +125,47 @@ export default { methods: { async loadBlockers () { this.blockers = await this.$store.dispatch('blockers:getBlockers'); - console.log(this.blockers); + }, + editBlocker (id) { + this.editedBlockerId = id; + }, + async saveBlocker (blocker) { + await this.$store.dispatch('blockers:updateBlocker', { blocker }); + this.editedBlockerId = null; + this.loadBlockers(); + }, + async deleteBlocker (blockerId) { + if (!window.confirm('Are you sure you want to delete this blocker?')) { + return; + } + await this.$store.dispatch('blockers:deleteBlocker', { blockerId }); + this.loadBlockers(); + }, + async createBlocker (blocker) { + await this.$store.dispatch('blockers:createBlocker', { blocker }); + this.showCreateForm = false; + this.loadBlockers(); + }, + + getTypeName (type) { + switch (type) { + case 'ipaddress': + return 'IP Address'; + case 'email': + return 'E-Mail'; + default: + return type; + } + }, + getAreaName (area) { + switch (area) { + case 'full': + return 'Full'; + case 'payments': + return 'Payments'; + default: + return area; + } }, }, }; diff --git a/website/client/src/store/actions/blockers.js b/website/client/src/store/actions/blockers.js index b87bffd739..4473474d6b 100644 --- a/website/client/src/store/actions/blockers.js +++ b/website/client/src/store/actions/blockers.js @@ -2,6 +2,18 @@ import axios from 'axios'; export async function getBlockers () { const response = await axios.get('/api/v4/admin/blockers'); - console.log(response); + return response.data.data; +} +export async function createBlocker (store, payload) { + const response = await axios.post('/api/v4/admin/blockers', payload.blocker); + return response.data.data; +} +export async function updateBlocker (store, payload) { + const response = await axios.put(`/api/v4/admin/blockers/${payload.blocker._id}`, payload.blocker); + return response.data.data; +} + +export async function deleteBlocker (store, payload) { + const response = await axios.delete(`/api/v4/admin/blockers/${payload.blockerId}`); return response.data.data; } diff --git a/website/server/controllers/api-v4/admin.js b/website/server/controllers/api-v4/admin.js index 2cb5d38456..c9b67fbec3 100644 --- a/website/server/controllers/api-v4/admin.js +++ b/website/server/controllers/api-v4/admin.js @@ -1,4 +1,6 @@ import validator from 'validator'; +import _ from 'lodash'; +import { v4 as uuid } from 'uuid'; import { authWithHeaders } from '../../middlewares/auth'; import { ensurePermission } from '../../middlewares/ensureAccessRight'; import { model as User } from '../../models/user'; @@ -123,7 +125,7 @@ api.getBlockers = { middlewares: [authWithHeaders(), ensurePermission('userSupport')], async handler (req, res) { const blockers = await Blocker - .find() + .find({ disabled: false }) .lean() .exec(); @@ -131,4 +133,59 @@ api.getBlockers = { }, }; +api.createBlocker = { + method: 'POST', + url: '/admin/blockers', + middlewares: [authWithHeaders(), ensurePermission('userSupport')], + async handler (req, res) { + const id = uuid(); + const blocker = await Blocker({ + _id: id, + ...Blocker.sanitize(req.body), + }).save(); + + res.respond(200, blocker); + }, +}; + +api.updateBlocker = { + method: 'PUT', + url: '/admin/blockers/:blockerId', + middlewares: [authWithHeaders(), ensurePermission('userSupport')], + async handler (req, res) { + req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID(); + + const validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + const blocker = await Blocker.findById(req.params.blockerId).exec(); + if (!blocker) throw new NotFound(res.t('blockerNotFound')); + + _.merge(blocker, Blocker.sanitize(req.body)); + const savedBlocker = await blocker.save(); + + res.respond(200, savedBlocker); + }, +}; + +api.deleteBlocker = { + method: 'DELETE', + url: '/admin/blockers/:blockerId', + middlewares: [authWithHeaders(), ensurePermission('userSupport')], + async handler (req, res) { + req.checkParams('blockerId', res.t('blockerIdRequired')).notEmpty().isUUID(); + + const validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + const blocker = await Blocker.findById(req.params.blockerId).exec(); + if (!blocker) throw new NotFound(res.t('blockerNotFound')); + + blocker.disabled = true; + const savedBlocker = await blocker.save(); + + res.respond(200, savedBlocker); + }, +}; + export default api; diff --git a/website/server/models/blocker.js b/website/server/models/blocker.js index 9d2e7c2f3a..19c99105e3 100644 --- a/website/server/models/blocker.js +++ b/website/server/models/blocker.js @@ -2,8 +2,6 @@ import mongoose from 'mongoose'; import EventEmitter from 'events'; -import { v4 as uuid } from 'uuid'; -import validator from 'validator'; import baseModel from '../libs/baseModel'; export const blockTypes = [ @@ -17,12 +15,6 @@ export const blockArea = [ ]; export const schema = new mongoose.Schema({ - id: { - $type: String, - default: uuid, - validate: [v => validator.isUUID(v), 'Invalid uuid for tag.'], - required: true, - }, disabled: { $type: Boolean, default: false, // If true, the block is disabled }, @@ -44,14 +36,11 @@ export const schema = new mongoose.Schema({ }, { strict: true, minimize: false, // So empty objects are returned - _id: false, // use id instead of _id typeKey: '$type', // So that we can use fields named `type` }); schema.plugin(baseModel, { timestamps: true, - noSet: ['_id'], - _id: false, // use id instead of _id }); schema.statics.watchBlockers = function watchBlockers (query, options) {