init tags tab in admin panel
This commit is contained in:
parent
854bd6538a
commit
06b35b0cd0
7 changed files with 154 additions and 5 deletions
|
@ -89,7 +89,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetch() {
|
async fetch() {
|
||||||
this.places = await this.$axios.$get('/place/all')
|
this.places = await this.$axios.$get('/places')
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['settings']),
|
...mapState(['settings']),
|
||||||
|
|
102
components/admin/Tags.vue
Normal file
102
components/admin/Tags.vue
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<template lang='pug'>
|
||||||
|
v-container
|
||||||
|
v-card-title {{ $t('common.tags') }}
|
||||||
|
v-spacer
|
||||||
|
v-text-field(v-model='search'
|
||||||
|
:append-icon='mdiMagnify' outlined rounded
|
||||||
|
:label="$t('common.search')"
|
||||||
|
single-line hide-details)
|
||||||
|
|
||||||
|
v-dialog(v-model='dialog' width='600' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||||
|
v-card
|
||||||
|
v-card-title {{ $t('admin.edit_tag') }}
|
||||||
|
v-card-text
|
||||||
|
v-form(v-model='valid' ref='form' lazy-validation)
|
||||||
|
v-text-field(
|
||||||
|
:rules="[$validators.required('common.name')]"
|
||||||
|
:label="$t('common.tag')"
|
||||||
|
v-model='tag.tag'
|
||||||
|
:placeholder='$t("common.tag")')
|
||||||
|
|
||||||
|
v-card-actions
|
||||||
|
v-spacer
|
||||||
|
v-btn(@click='dialog = false' outlined color='warning') {{ $t('common.cancel') }}
|
||||||
|
v-btn(@click='savePlace' color='primary' outlined :loading='loading'
|
||||||
|
:disable='!valid || loading') {{ $t('common.save') }}
|
||||||
|
|
||||||
|
v-card-text
|
||||||
|
v-data-table(
|
||||||
|
:headers='headers'
|
||||||
|
:items='tags'
|
||||||
|
:hide-default-footer='tags.length < 5'
|
||||||
|
:header-props='{ sortIcon: mdiChevronDown }'
|
||||||
|
:footer-props='{ prevIcon: mdiChevronLeft, nextIcon: mdiChevronRight }'
|
||||||
|
:search='search')
|
||||||
|
template(v-slot:item.map='{ item }')
|
||||||
|
span {{item.latitude && item.longitude && 'YEP' }}
|
||||||
|
template(v-slot:item.actions='{ item }')
|
||||||
|
v-btn(@click='editTag(item)' color='primary' icon)
|
||||||
|
v-icon(v-text='mdiPencil')
|
||||||
|
nuxt-link(:to='`/tag/${item.tag}`')
|
||||||
|
v-icon(v-text='mdiEye')
|
||||||
|
v-btn(@click='removeTag(item)' color='primary' icon)
|
||||||
|
v-icon(v-text='mdiDeleteForever')
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever } from '@mdi/js'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
|
import get from 'lodash/get'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data( {$store} ) {
|
||||||
|
return {
|
||||||
|
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever,
|
||||||
|
loading: false,
|
||||||
|
dialog: false,
|
||||||
|
valid: false,
|
||||||
|
tag: {},
|
||||||
|
tags: [],
|
||||||
|
search: '',
|
||||||
|
headers: [
|
||||||
|
{ value: 'tag', text: this.$t('common.tag') },
|
||||||
|
{ value: 'count', text: 'N.' },
|
||||||
|
{ value: 'actions', text: this.$t('common.actions'), align: 'right' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetch() {
|
||||||
|
this.tags = await this.$axios.$get('/tags')
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['settings']),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
editTag(item) {
|
||||||
|
this.tag.tag = item.tag
|
||||||
|
this.dialog = true
|
||||||
|
},
|
||||||
|
async saveTag() {
|
||||||
|
if (!this.$refs.form.validate()) return
|
||||||
|
this.loading = true
|
||||||
|
await this.$axios.$put('/tag', this.tag)
|
||||||
|
await this.$fetch()
|
||||||
|
this.loading = false
|
||||||
|
this.dialog = false
|
||||||
|
},
|
||||||
|
async removeTag(tag) {
|
||||||
|
const ret = await this.$root.$confirm('admin.delete_tag_confirm', { tag: tag.tag })
|
||||||
|
if (!ret) { return }
|
||||||
|
try {
|
||||||
|
await this.$axios.$delete(`/tag/${encodeURIComponent(tag.tag)}`)
|
||||||
|
await this.$fetch()
|
||||||
|
} catch (e) {
|
||||||
|
const err = get(e, 'response.data.errors[0].message', e)
|
||||||
|
this.$root.$message(this.$t(err), { color: 'error' })
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -82,6 +82,7 @@
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"place": "Place",
|
"place": "Place",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
|
"tag": "Tag",
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"import": "Import",
|
"import": "Import",
|
||||||
|
|
|
@ -26,6 +26,11 @@ v-container.container.pa-0.pa-md-3
|
||||||
v-tab-item(value='places')
|
v-tab-item(value='places')
|
||||||
Places
|
Places
|
||||||
|
|
||||||
|
//- TAGS
|
||||||
|
v-tab(href='#tags') {{$t('common.tags')}}
|
||||||
|
v-tab-item(value='tags')
|
||||||
|
Tags
|
||||||
|
|
||||||
//- GEOCODING / MAPS
|
//- GEOCODING / MAPS
|
||||||
v-tab(href='#geolocation' v-if='settings.allow_geolocation') {{$t('admin.geolocation')}}
|
v-tab(href='#geolocation' v-if='settings.allow_geolocation') {{$t('admin.geolocation')}}
|
||||||
v-tab-item(value='geolocation')
|
v-tab-item(value='geolocation')
|
||||||
|
@ -77,6 +82,7 @@ export default {
|
||||||
Users: () => import(/* webpackChunkName: "admin" */'../components/admin/Users'),
|
Users: () => import(/* webpackChunkName: "admin" */'../components/admin/Users'),
|
||||||
Events: () => import(/* webpackChunkName: "admin" */'../components/admin/Events'),
|
Events: () => import(/* webpackChunkName: "admin" */'../components/admin/Events'),
|
||||||
Places: () => import(/* webpackChunkName: "admin" */'../components/admin/Places'),
|
Places: () => import(/* webpackChunkName: "admin" */'../components/admin/Places'),
|
||||||
|
Tags: () => import(/* webpackChunkName: "admin" */'../components/admin/Tags'),
|
||||||
Collections: () => import(/* webpackChunkName: "admin" */'../components/admin/Collections'),
|
Collections: () => import(/* webpackChunkName: "admin" */'../components/admin/Collections'),
|
||||||
[process.client && 'Geolocation']: () => import(/* webpackChunkName: "admin" */'../components/admin/Geolocation.vue'),
|
[process.client && 'Geolocation']: () => import(/* webpackChunkName: "admin" */'../components/admin/Geolocation.vue'),
|
||||||
Federation: () => import(/* webpackChunkName: "admin" */'../components/admin/Federation.vue'),
|
Federation: () => import(/* webpackChunkName: "admin" */'../components/admin/Federation.vue'),
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
const Tag = require('../models/tag')
|
const Tag = require('../models/tag')
|
||||||
const Event = require('../models/event')
|
const Event = require('../models/event')
|
||||||
const uniq = require('lodash/uniq')
|
const uniq = require('lodash/uniq')
|
||||||
|
const log = require('../../log')
|
||||||
|
|
||||||
|
|
||||||
const { where, fn, col, Op } = require('sequelize')
|
const { where, fn, col, Op } = require('sequelize')
|
||||||
const exportController = require('./export')
|
const exportController = require('./export')
|
||||||
|
@ -45,6 +47,20 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async getAll (_req, res) {
|
||||||
|
const tags = await Tag.findAll({
|
||||||
|
order: [[fn('COUNT', col('tag.tag')), 'DESC']],
|
||||||
|
attributes: ['tag', [fn('COUNT', col('tag.tag')), 'count']],
|
||||||
|
include: [{ model: Event, where: { is_visible: true }, attributes: [], through: { attributes: [] }, required: true }],
|
||||||
|
group: ['tag.tag'],
|
||||||
|
raw: true,
|
||||||
|
})
|
||||||
|
return res.json(tags)
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* search for tags by query string
|
* search for tags by query string
|
||||||
* sorted by usage
|
* sorted by usage
|
||||||
|
@ -64,5 +80,26 @@ module.exports = {
|
||||||
})
|
})
|
||||||
|
|
||||||
return res.json(tags.map(t => t.tag))
|
return res.json(tags.map(t => t.tag))
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateTag (req, res) {
|
||||||
|
const tag = await Tag.findByPk(req.body.tag)
|
||||||
|
await tag.update(req.body)
|
||||||
|
res.json(place)
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async remove (req, res) {
|
||||||
|
log.info('Remove tag', req.params.tag)
|
||||||
|
const tagName = req.params.tag
|
||||||
|
try {
|
||||||
|
const tag = await Tag.findByPk(tagName)
|
||||||
|
await tag.destroy()
|
||||||
|
res.sendStatus(200)
|
||||||
|
} catch (e) {
|
||||||
|
log.error('Tag removal failed:', e)
|
||||||
|
res.sendStatus(404)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -162,15 +162,18 @@ if (config.status !== 'READY') {
|
||||||
api.get('/export/:type', cors, exportController.export)
|
api.get('/export/:type', cors, exportController.export)
|
||||||
|
|
||||||
|
|
||||||
api.get('/place/all', isAdmin, placeController.getAll)
|
api.get('/places', isAdmin, placeController.getAll)
|
||||||
api.get('/place/:placeName', cors, placeController.getEvents)
|
api.get('/place/:placeName', cors, placeController.getEvents)
|
||||||
api.get('/place', cors, placeController.search)
|
api.get('/place', cors, placeController.search)
|
||||||
api.get('/placeOSM/Nominatim/:place_details', cors, placeController._nominatim)
|
api.get('/placeOSM/Nominatim/:place_details', cors, placeController._nominatim)
|
||||||
api.get('/placeOSM/Photon/:place_details', cors, placeController._photon)
|
api.get('/placeOSM/Photon/:place_details', cors, placeController._photon)
|
||||||
api.put('/place', isAdmin, placeController.updatePlace)
|
api.put('/place', isAdmin, placeController.updatePlace)
|
||||||
|
|
||||||
|
api.get('/tags', isAdmin, tagController.getAll)
|
||||||
api.get('/tag', cors, tagController.search)
|
api.get('/tag', cors, tagController.search)
|
||||||
api.get('/tag/:tag', cors, tagController.getEvents)
|
api.get('/tag/:tag', cors, tagController.getEvents)
|
||||||
|
api.delete('/tag/:tag', isAdmin, tagController.remove)
|
||||||
|
|
||||||
|
|
||||||
// - FEDIVERSE INSTANCES, MODERATION, RESOURCES
|
// - FEDIVERSE INSTANCES, MODERATION, RESOURCES
|
||||||
api.get('/instances', isAdmin, instanceController.getAll)
|
api.get('/instances', isAdmin, instanceController.getAll)
|
||||||
|
|
|
@ -285,10 +285,10 @@ describe('Place', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('admin should get all places', async () => {
|
test('admin should get all places', async () => {
|
||||||
await request(app).get('/api/place/all')
|
await request(app).get('/api/places')
|
||||||
.expect(403)
|
.expect(403)
|
||||||
|
|
||||||
const response = await request(app).get('/api/place/all')
|
const response = await request(app).get('/api/places')
|
||||||
.auth(token.access_token, { type: 'bearer' })
|
.auth(token.access_token, { type: 'bearer' })
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue