forked from kaverti/website
More Team Roles, and Admin stuff
This commit is contained in:
parent
8d9ce2ed14
commit
70fa581cff
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
port: process.env.PORT || 23981,
|
||||
sessionSecret: process.env.SESSION_SECRET || 'iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;',
|
||||
imageUploadTeams: process.env.TEAMUPLOADS || "C:\\Users\\matth\\Documents\\GitHub\\Kaverti-Team-Images"
|
||||
imageUploadTeams: process.env.TEAMUPLOADS || "C:\\Users\\matth\\Documents\\GitHub\\Kaverti-Team-Images",
|
||||
maintenance: process.env.MAINTENANCE || false
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"vue-matomo": "^3.14.0-0",
|
||||
"vue-router": "^2.7.0",
|
||||
"vue-router-sitemap": "^0.0.4",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuejs-paginator": "^2.0.2",
|
||||
"vuetify": "^2.3.8",
|
||||
"vuex": "^2.1.1"
|
||||
|
|
|
@ -3,7 +3,7 @@ module.exports = function(vuex) {
|
|||
let errors = []
|
||||
|
||||
if(res.response === undefined || res.response.data.errors === undefined) {
|
||||
errors.push('Something went wrong connecting to the Kaverti service, maybe try again later.')
|
||||
errors.push('Something went wrong connecting to the Kaverti service, please try again later.')
|
||||
} else {
|
||||
res.response.data.errors.forEach(error => {
|
||||
let path = error.path
|
||||
|
@ -15,7 +15,6 @@ module.exports = function(vuex) {
|
|||
errors.push(error.message[0].toUpperCase() + error.message.slice(1))
|
||||
})
|
||||
}
|
||||
|
||||
if(errors.length) {
|
||||
vuex.commit('setAjaxErrors', errors)
|
||||
vuex.commit('setAjaxErrorsModalState', true)
|
||||
|
|
|
@ -100,6 +100,37 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if='teams.length'>
|
||||
<div class='search_box__results__header search_box__results__header--divider'>Teams</div>
|
||||
<div
|
||||
class='search_box__results__search_all'
|
||||
:class='{
|
||||
"search_box__results--highlight": highlightIndex === getHighlightIndex("teams header")
|
||||
}'
|
||||
ref='teams header'
|
||||
@mouseover='highlightIndex = getHighlightIndex("teams header")'
|
||||
@click='goToSearch'
|
||||
>
|
||||
<div class='search_box__results__icon'><font-awesome-icon :icon='["fa", "search"]' /></div>
|
||||
<div>
|
||||
Search all teams containing '<strong>{{searchField}}</strong>'
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class='search_box__results__team'
|
||||
:class='{
|
||||
"search_box__results--highlight": highlightIndex === getHighlightIndex("teams", index)
|
||||
}'
|
||||
v-for='(team, index) in teams'
|
||||
:key='"team-result-" + index'
|
||||
ref='teams'
|
||||
@mouseover='highlightIndex = getHighlightIndex("teams", index)'
|
||||
@click='goToSearch'
|
||||
>
|
||||
<div class='search_box__results__title'>{{team.name}}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class='search_box__results__message' v-if='!threads.length && !users.length && !loading'>
|
||||
Uh oh! There were no results for the query: '<strong>{{searchField}}</strong>'
|
||||
</div>
|
||||
|
@ -132,7 +163,8 @@
|
|||
MinQueryLength: 2,
|
||||
|
||||
threads: [],
|
||||
users: []
|
||||
users: [],
|
||||
teams: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -193,7 +225,17 @@
|
|||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
} else if(group === 'teams' || group === 'teams header') {
|
||||
let ret = 0;
|
||||
if(this.threads.length) {
|
||||
ret += 1 + this.threads.length;
|
||||
}
|
||||
|
||||
if(group === 'teams') {
|
||||
ret += 1 + index;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
//Produces relative group and index
|
||||
//from overall highlight index
|
||||
|
@ -216,6 +258,12 @@
|
|||
} else {
|
||||
return { group: 'users', index: index-1 };
|
||||
}
|
||||
} else if(this.teams.length) {
|
||||
if(index === 0) {
|
||||
return { group: 'teams header', index: null };
|
||||
} else {
|
||||
return { group: 'teams', index: index-1 };
|
||||
}
|
||||
}
|
||||
},
|
||||
setKeyHighlight (e) {
|
||||
|
@ -279,7 +327,11 @@
|
|||
this.$router.push('/thread/' + thread.slug + '/' + thread.id);
|
||||
} else if (group === 'users header') {
|
||||
this.$router.push('/search/users/' + searchEncoded);
|
||||
} else {
|
||||
} else if(group === 'teams') {
|
||||
this.$router.push('/t/' + this.teams[index].username);
|
||||
} else if(group === 'teams header') {
|
||||
this.$router.push('/search/teams/' + searchEncoded);
|
||||
} else {
|
||||
this.$router.push('/search/threads/' + searchEncoded);
|
||||
}
|
||||
|
||||
|
@ -295,6 +347,7 @@
|
|||
this.loading = true;
|
||||
this.threads = [];
|
||||
this.users = [];
|
||||
this.teams = [];
|
||||
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'kaverti/search/thread?q=' + q)
|
||||
|
@ -311,6 +364,14 @@
|
|||
this.loading = false;
|
||||
})
|
||||
.catch(AjaxErrorHandler(this.$store));
|
||||
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'kaverti/search/team?q=' + q)
|
||||
.then(res => {
|
||||
this.teams = res.data.teams.slice(0, 5);
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(AjaxErrorHandler(this.$store));
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div
|
||||
class='user_display'
|
||||
@click='$router.push("/user/" + user.username)'
|
||||
>
|
||||
<team-icon :user='user' size='small'></team-icon>
|
||||
<div class='user_display__username'>
|
||||
{{user.username}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TeamIcon from './TeamIcon'
|
||||
export default {
|
||||
name: 'UserDisplay',
|
||||
props: ['user'],
|
||||
components: {TeamIcon
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '../assets/scss/variables.scss';
|
||||
|
||||
.user_display {
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border: thin solid $color__gray--darker;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
transition: box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
@extend .shadow_border--hover;
|
||||
}
|
||||
|
||||
@at-root #{&}__username {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 400;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,168 @@
|
|||
<template>
|
||||
<info-tooltip class='avatar_icon' :noEvents='user === null'>
|
||||
|
||||
<template slot='content'>
|
||||
|
||||
<template v-if='userData'>
|
||||
<div class='avatar_icon__header'>
|
||||
<figure class="avatar_icon__icon--small picture_circle">
|
||||
<img
|
||||
:src = userPicture
|
||||
>
|
||||
</figure>
|
||||
<div class='avatar_icon__header_info'>
|
||||
<span class='avatar_icon__username' @click.stop='goToUser'>
|
||||
{{proxyUser.username}}
|
||||
</span>
|
||||
<span class='avatar_icon__date'>Created: {{proxyUser.createdAt | formatDate('date') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='avatar_icon__description' v-if='proxyUser.description'>
|
||||
{{proxyUser.description | stripTags | truncate(30)}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>Loading...</template>
|
||||
</template>
|
||||
<figure
|
||||
slot="display"
|
||||
class='avatar_icon__icon picture_circle'
|
||||
:class='{
|
||||
"avatar_icon__icon--small": size === "small",
|
||||
"avatar_icon__icon--tiny": size === "tiny"
|
||||
}'
|
||||
@click.stop="goToUser">
|
||||
<img
|
||||
:src = userPicture
|
||||
>
|
||||
</figure>
|
||||
</info-tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InfoTooltip from './InfoTooltip'
|
||||
import AjaxErrorHandler from '../assets/js/errorHandler'
|
||||
|
||||
export default {
|
||||
name: 'AvatarIcon',
|
||||
props: ['user', 'size'],
|
||||
components: { InfoTooltip },
|
||||
data () {
|
||||
return {
|
||||
userData: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
//So that you never access a null variable
|
||||
proxyUser () {
|
||||
if(this.userData) {
|
||||
//Data loaded via api
|
||||
return this.userData;
|
||||
} else if (this.user) {
|
||||
//Data provided as a prop
|
||||
return this.user;
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
letter () {
|
||||
if(this.proxyUser.username && !this.proxyUser.picture) {
|
||||
return this.proxyUser.username[0].toUpperCase();
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
userPicture () {
|
||||
if(this.user && this.user.approved && !this.user.banned && this.user.picture !== 'default') {
|
||||
return this.user.picture
|
||||
} else if(this.user && this.user.banned) {
|
||||
return "https://cdn.kaverti.com/teams/unknown-light.png"
|
||||
} else if(this.user && !this.user.banned && !this.user.approved && this.$store.state.theme === 'light') {
|
||||
return "https://cdn.kaverti.com/teams/pending-light.png"
|
||||
} else if(this.user && !this.user.banned && !this.user.approved && this.$store.state.theme === 'dark') {
|
||||
return "https://cdn.kaverti.com/teams/pending-dark.png"
|
||||
} else if(this.user && !this.user.banned && !this.user.approved) {
|
||||
return "https://cdn.kaverti.com/teams/pending-light.png"
|
||||
} else {
|
||||
return "https://cdn.kaverti.com/teams/unknown-light.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadUser () {
|
||||
//If user is already loaded or no user provided as a prop
|
||||
if(this.userData || this.user === null) return;
|
||||
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'teams/view/' + this.proxyUser.username)
|
||||
.then((res) => {
|
||||
this.userData = res.data;
|
||||
})
|
||||
.catch(AjaxErrorHandler(this.$store));
|
||||
},
|
||||
goToUser () {
|
||||
if(this.user === null) return;
|
||||
|
||||
this.$router.push('/user/' + this.user.username)
|
||||
}
|
||||
},
|
||||
mounted() { this.loadUser(); }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '../assets/scss/variables.scss';
|
||||
|
||||
.avatar_icon {
|
||||
@at-root #{&}__icon {
|
||||
font-size: 0.7rem;
|
||||
margin-right: 0.25rem;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
@at-root #{&}__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@at-root #{&}__icon {
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
line-height: 3rem;
|
||||
cursor: pointer;
|
||||
@include text($font--role-emphasis, 2rem);
|
||||
text-align: center;
|
||||
border-radius: 100%;
|
||||
color: #fff;
|
||||
|
||||
@at-root #{&}--small {
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
font-size: 1.75rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
@at-root #{&}--tiny {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
}
|
||||
@at-root #{&}__header_info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 2.5rem;
|
||||
}
|
||||
@at-root #{&}__username {
|
||||
cursor: pointer;
|
||||
}
|
||||
@at-root #{&}__date {
|
||||
color: $color__darkgray--primary;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
@at-root #{&}__description {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<main>
|
||||
|
||||
</main>
|
||||
</template>
|
|
@ -57,6 +57,40 @@
|
|||
}}
|
||||
</div>
|
||||
</transition>
|
||||
<transition name='fade' mode='out-in'>
|
||||
<div class='search__results' key='results' v-if='teams && teams.length && !loadingTeams'>
|
||||
<h2>Teams</h2>
|
||||
<team-display v-for='user in teams.slice(0, 5)' :key='"search-user-" + user.id' :user='user'></team-display>
|
||||
|
||||
<div
|
||||
class='search__item search__more' v-if='users.length > 5'
|
||||
@click='$router.push("/search/users/" + $route.params.q)'
|
||||
>
|
||||
<font-awesome-icon :icon='["fa", "user"]' fixed-width />
|
||||
View all matching teams
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
key='loading'
|
||||
v-if='loadingTeams'
|
||||
>
|
||||
<h2>Teams</h2>
|
||||
<user-placeholder></user-placeholder>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class='overlay_message search__overlay_message'
|
||||
v-if='showNoResults || queryTooShort'
|
||||
key='no results'
|
||||
>
|
||||
<i class="far fa-exclamation-circle" />
|
||||
{{queryTooShort ?
|
||||
"Search term is too short." :
|
||||
"Uh oh! There were no results."
|
||||
}}
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
@ -66,8 +100,9 @@
|
|||
import UserPlaceholder from '../UserPlaceholder'
|
||||
import ThreadDisplay from '../ThreadDisplay'
|
||||
import ThreadDisplayPlaceholder from '../ThreadDisplayPlaceholder'
|
||||
import TeamDisplay from '../TeamDisplay'
|
||||
|
||||
import AjaxErrorHandler from '../../assets/js/errorHandler'
|
||||
import AjaxErrorHandler from '../../assets/js/errorHandler'
|
||||
import logger from '../../assets/js/logger'
|
||||
|
||||
export default {
|
||||
|
@ -76,7 +111,8 @@
|
|||
UserDisplay,
|
||||
UserPlaceholder,
|
||||
ThreadDisplay,
|
||||
ThreadDisplayPlaceholder
|
||||
ThreadDisplayPlaceholder,
|
||||
TeamDisplay
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -84,7 +120,10 @@
|
|||
loadingThreads: false,
|
||||
|
||||
users: [],
|
||||
loadingUsers: false
|
||||
loadingUsers: false,
|
||||
|
||||
teams: [],
|
||||
loadingTeams: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -121,6 +160,17 @@
|
|||
})
|
||||
.catch(AjaxErrorHandler(this.$store))
|
||||
},
|
||||
getTeams () {
|
||||
this.loadingTeams = true;
|
||||
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'kaverti/search/team?q=' + this.$route.params.q)
|
||||
.then(res => {
|
||||
this.loadingTeams = false;
|
||||
this.teams = res.data.teams;
|
||||
})
|
||||
.catch(AjaxErrorHandler(this.$store))
|
||||
},
|
||||
getResults () {
|
||||
if(this.queryTooShort) return;
|
||||
|
||||
|
@ -128,7 +178,8 @@
|
|||
|
||||
this.getThreads();
|
||||
this.getUsers();
|
||||
}
|
||||
this.getTeams();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.params': 'getResults'
|
||||
|
|
|
@ -56,18 +56,31 @@
|
|||
<div class="section">
|
||||
<div class="">
|
||||
<h1>Roles:</h1>
|
||||
<scroll-load
|
||||
key='user-row'
|
||||
v-if='roles.length'
|
||||
:loading='loading'
|
||||
@loadNext='fetchData'
|
||||
>
|
||||
<div class="column is-10" v-for='role in roles' :key='"user-row" + role.name' v-show="role">
|
||||
<b-tag class="is-large">{{role.name}}</b-tag>
|
||||
</div>
|
||||
<br>
|
||||
<b-button @click="toggleRoleCreate"><i class="fas fa-plus"></i> Add Role</b-button>
|
||||
</scroll-load>
|
||||
<ul class="list-group mb-2">
|
||||
<draggable
|
||||
class="list-group"
|
||||
tag="ul"
|
||||
v-model="roles"
|
||||
v-bind="dragOptions"
|
||||
>
|
||||
<transition-group type="transition">
|
||||
<li
|
||||
class="list-group-item"
|
||||
v-for="role in roles"
|
||||
:key="role.priority"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<b-tag class="is-large">{{ role.name }} <b-tag @click="toggleRoleCreate"><i @click="toggleRoleCreate" class="fas fa-pencil"></i></b-tag></b-tag>
|
||||
</li>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
</ul>
|
||||
|
||||
<b-button @click="saveRoles()">Save Role Order</b-button>
|
||||
|
||||
<b-button class="is-info" @click="toggleRoleCreate()"><i class="fas fa-plus"></i> Add Role</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<p name='fade' mode='out-in'>
|
||||
|
@ -81,15 +94,18 @@
|
|||
import LoadingMessage from '../LoadingMessage';
|
||||
import ScrollLoad from '../ScrollLoad';
|
||||
import ModalWindow from "@/components/ModalWindow";
|
||||
import draggable from 'vuedraggable'
|
||||
import throttle from 'lodash.throttle';
|
||||
import AjaxErrorHandler from '../../assets/js/errorHandler';
|
||||
|
||||
export default {
|
||||
name: 'TeamMembers',
|
||||
name: 'TeamRoles',
|
||||
components: {
|
||||
LoadingMessage,
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
ScrollLoad,
|
||||
ModalWindow
|
||||
ModalWindow,
|
||||
draggable
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -99,12 +115,13 @@ export default {
|
|||
roles: [],
|
||||
team: [],
|
||||
|
||||
loading: true,
|
||||
loading: false,
|
||||
offset: 0,
|
||||
limit: 15,
|
||||
showTeamTab: 0,
|
||||
showRoleModal: false,
|
||||
createTeamModal: false,
|
||||
drag: false,
|
||||
tRole: {
|
||||
name: '',
|
||||
|
||||
|
@ -132,6 +149,28 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
updateListSortOrder() {
|
||||
const newList = [...this.roles].map((item, index) => {
|
||||
const newSort = index + 1;
|
||||
item.priority = newSort;
|
||||
return item;
|
||||
});
|
||||
this.roles = newList;
|
||||
},
|
||||
saveRoles() {
|
||||
this.updateListSortOrder()
|
||||
this.axios.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/admin/roles/modify/` + this.$route.params.username, {
|
||||
roles: this.roles
|
||||
}).then(() => {
|
||||
this.tRole.loading = false
|
||||
this.closeAccountModal()
|
||||
}).catch(e => {
|
||||
this.tRole.loading = false
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
},
|
||||
|
||||
clearTeamErrors() {
|
||||
this.tcreateProd.errors.username = ''
|
||||
this.tcreateProd.errors.name = ''
|
||||
|
@ -140,7 +179,9 @@ export default {
|
|||
this.showRoleModal = false
|
||||
},
|
||||
addRole() {
|
||||
let postParams = {
|
||||
this.tRole.loading = true
|
||||
|
||||
this.axios.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/admin/roles/create/` + this.$route.params.username, {
|
||||
name: this.tRole.name,
|
||||
|
||||
administrator: this.tRole.administrator,
|
||||
|
@ -151,16 +192,14 @@ export default {
|
|||
changeTeamPrivacy: this.tRole.changeTeamPrivacy,
|
||||
submitTeamItems: this.tRole.submitTeamItems,
|
||||
priority: this.tRole.priority
|
||||
}
|
||||
this.tRole.loading = true
|
||||
|
||||
this.axios.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/admin/roles/create/` + this.$route.params.username, postParams).then(res => {
|
||||
res()
|
||||
}).then(() => {
|
||||
this.tRole.loading = false
|
||||
this.closeAccountModal()
|
||||
this.fetchData()
|
||||
}).catch(e => {
|
||||
this.tRole.loading = false
|
||||
AjaxErrorHandler(this.$store)(e);
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
this.fetchData()
|
||||
})
|
||||
},
|
||||
toggleRoleCreate() {
|
||||
|
@ -174,9 +213,12 @@ export default {
|
|||
this.axios
|
||||
.get( process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'teams/view/' + this.$route.params.username + "/roles")
|
||||
.then(res => {
|
||||
this.roles.push(...res.data);
|
||||
this.roles = res.data.filter(role => role.priority);
|
||||
//this.roles.push(...res.data);
|
||||
this.loading = /*loading =*/ false;
|
||||
|
||||
|
||||
|
||||
//If returned data is less than the limit
|
||||
//then there must be no more pages to paginate
|
||||
if(res.data.length < this.limit) {
|
||||
|
@ -230,6 +272,16 @@ export default {
|
|||
mounted () {
|
||||
this.fetchData();
|
||||
},
|
||||
computed: {
|
||||
dragOptions() {
|
||||
return {
|
||||
animation: 200,
|
||||
group: "description",
|
||||
disabled: false,
|
||||
ghostClass: "ghost"
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
tableSort: 'resetFetchData',
|
||||
roleSelected: 'resetFetchData',
|
||||
|
@ -239,3 +291,24 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.flip-list-move {
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
.no-move {
|
||||
transition: transform 0s;
|
||||
}
|
||||
.ghost {
|
||||
opacity: 0.5;
|
||||
background: #c8ebfb;
|
||||
}
|
||||
.list-group {
|
||||
min-height: 20px;
|
||||
}
|
||||
.list-group-item {
|
||||
cursor: move;
|
||||
}
|
||||
.list-group-item i {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -134,6 +134,7 @@ const AdminMarketplace = () => import('./components/routes/AdminMarketplace')
|
|||
|
||||
const Licenses = () => import('./components/routes/LICENSES')
|
||||
const ConnectionProblems = () => import('./components/routes/ConnectionProblems')
|
||||
const Maintenance = () => import('./components/routes/Maintenance')
|
||||
const Contributors = () => import('./components/routes/Contributors')
|
||||
|
||||
const Inventory = () => import('./components/routes/Inventory')
|
||||
|
@ -238,6 +239,7 @@ const router = new VueRouter({
|
|||
] },
|
||||
{ path: '/forum', redirect: '/category/all', component: Index },
|
||||
{ path: '/connection', component: ConnectionProblems },
|
||||
{ path: '/maintenance', component: Maintenance },
|
||||
{ path: '/contributors', component: Contributors },
|
||||
{ path: '/forums', redirect: '/category/all', component: Index },
|
||||
{ path: '/search/:q', component: Search },
|
||||
|
|
|
@ -9107,6 +9107,11 @@ sort-keys@^2.0.0:
|
|||
dependencies:
|
||||
is-plain-obj "^1.0.0"
|
||||
|
||||
sortablejs@1.10.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290"
|
||||
integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==
|
||||
|
||||
source-list-map@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://npm.open-registry.dev/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
||||
|
@ -10210,6 +10215,13 @@ vue@^2.6.11:
|
|||
resolved "https://npm.open-registry.dev/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
|
||||
integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
|
||||
|
||||
vuedraggable@^2.24.3:
|
||||
version "2.24.3"
|
||||
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.24.3.tgz#43c93849b746a24ce503e123d5b259c701ba0d19"
|
||||
integrity sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==
|
||||
dependencies:
|
||||
sortablejs "1.10.2"
|
||||
|
||||
vuejs-paginator@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/vuejs-paginator/-/vuejs-paginator-2.0.2.tgz#89792bb33cf73f4ddbd329485f174e23acf0f12c"
|
||||
|
|
|
@ -59,6 +59,10 @@ let Errors = {
|
|||
'This category has already been created',
|
||||
400
|
||||
],
|
||||
maintenance: [
|
||||
'Kaverti is currently under routine maintenance, website access has been restricted, please try again later.',
|
||||
400
|
||||
],
|
||||
accountDoesNotExist: [
|
||||
'This account does not exist',
|
||||
400
|
||||
|
|
|
@ -9,6 +9,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||
let TeamRoles = sequelize.define('TeamRoles', {
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
isString(val) {
|
||||
if (typeof val !== 'string') {
|
||||
|
@ -55,7 +56,8 @@ module.exports = (sequelize, DataTypes) => {
|
|||
},
|
||||
priority: {
|
||||
type: DataTypes.BIGINT,
|
||||
default: 1
|
||||
default: 1,
|
||||
allowNull: false,
|
||||
},
|
||||
teamId: {
|
||||
type: DataTypes.BIGINT
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@sendgrid/mail": "^7.2.5",
|
||||
"axios": "^0.19.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"compression": "^1.7.3",
|
||||
"connect-multiparty": "^2.2.0",
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
let express = require('express')
|
||||
let router = express.Router()
|
||||
const auth = require('../lib/authUserInfo')
|
||||
const Errors = require('../lib/errors')
|
||||
let { Settings, Sequelize } = require('../models')
|
||||
|
||||
router.get('*', async(req, res, next) => {
|
||||
try {
|
||||
throw Errors.maintenance
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('*', async(req, res, next) => {
|
||||
try {
|
||||
throw Errors.maintenance
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.put('*', async(req, res, next) => {
|
||||
try {
|
||||
throw Errors.maintenance
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.options('*', async(req, res, next) => {
|
||||
try {
|
||||
res.status(200)
|
||||
res.json({success: true})
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
module.exports = router
|
|
@ -2,7 +2,7 @@ let express = require('express')
|
|||
let router = express.Router()
|
||||
const auth = require('../lib/auth')
|
||||
|
||||
let { Post, Ban, Thread, User, Category, Sequelize } = require('../models')
|
||||
let { Post, Ban, Team, Thread, User, Category, Sequelize } = require('../models')
|
||||
const Errors = require('../lib/errors')
|
||||
const { setRandomFallback } = require('bcryptjs')
|
||||
|
||||
|
@ -159,4 +159,28 @@ router.get('/user', async(req, res, next) => {
|
|||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.get('/team', async(req, res, next) => {
|
||||
try {
|
||||
let searchString = req.query.q
|
||||
let offset = +req.query.offset || 0
|
||||
let limit = 10
|
||||
let teams = await Team.findAll({
|
||||
where: {
|
||||
username: { $like: '%' + searchString + '%' }
|
||||
},
|
||||
order: [ ['username', 'DESC'] ],
|
||||
attributes: {exclude: ['banReason']},
|
||||
limit,
|
||||
offset
|
||||
})
|
||||
|
||||
res.json({
|
||||
teams,
|
||||
offset: teams.length < limit ? null : offset + limit,
|
||||
next: teams.length < limit ? null : limit
|
||||
})
|
||||
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
let express = require('express')
|
||||
let router = express.Router()
|
||||
const auth = require('../lib/auth')
|
||||
|
||||
const Errors = require('../lib/errors')
|
||||
let { Settings, Ban, Sequelize } = require('../models')
|
||||
|
||||
router.get('/', async(req, res, next) => {
|
||||
try {
|
||||
let settings = await Settings.get()
|
||||
|
||||
if(!settings) throw Errors.noSettings
|
||||
|
||||
res.json(settings.toJSON())
|
||||
} catch (e) { next(e) }
|
||||
|
||||
})
|
||||
|
||||
router.all('*', auth, (req, res, next) => {
|
||||
if(req.userData.admin) {
|
||||
next()
|
||||
} else {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.requestNotAuthorized]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.put('/', auth, async(req, res, next) => {
|
||||
try {
|
||||
throw Errors.featureDisabledState
|
||||
await Ban.ReadOnlyMode(req.userData.username)
|
||||
let params = {}
|
||||
|
||||
if(req.body.siteName) {
|
||||
params.siteName = req.body.siteName
|
||||
}
|
||||
if(req.body.siteDesc !== undefined) {
|
||||
params.siteDesc = req.body.siteDesc
|
||||
}
|
||||
if(req.body.showDescription !== undefined) {
|
||||
params.showDescription = req.body.showDescription
|
||||
}
|
||||
if(req.body.bannerEnabled !== undefined) {
|
||||
params.bannerEnabled = req.body.bannerEnabled
|
||||
}
|
||||
if(req.body.bannerText !== undefined) {
|
||||
params.bannerText = req.body.bannerText
|
||||
}
|
||||
|
||||
let updatedSettings = await Settings.set(params)
|
||||
|
||||
res.json(params)
|
||||
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
module.exports = router
|
|
@ -0,0 +1,35 @@
|
|||
let express = require('express')
|
||||
let router = express.Router()
|
||||
const auth = require('../lib/auth')
|
||||
|
||||
const Errors = require('../lib/errors')
|
||||
let { Settings, Ban, Sequelize } = require('../models')
|
||||
|
||||
router.get('/', async(req, res, next) => {
|
||||
try {
|
||||
let settings = await Settings.findOne({
|
||||
where: {
|
||||
id: 1
|
||||
},
|
||||
attributes: { exclude: ['id', 'createdAt', 'updatedAt'] }
|
||||
})
|
||||
|
||||
if(!settings) throw Errors.noSettings
|
||||
|
||||
res.json(settings.toJSON())
|
||||
} catch (e) { next(e) }
|
||||
|
||||
})
|
||||
|
||||
router.all('*', auth, (req, res, next) => {
|
||||
if(req.userData.admin) {
|
||||
next()
|
||||
} else {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.requestNotAuthorized]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router
|
|
@ -48,6 +48,7 @@ const sgMail = require('@sendgrid/mail');
|
|||
const MailGen = require('mailgen')
|
||||
const crypto = require("crypto")
|
||||
const cryptoRandomString = require("crypto-random-string")
|
||||
let Promise = require('bluebird');
|
||||
const rateLimit = require("express-rate-limit");
|
||||
let upload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
|
@ -154,13 +155,92 @@ router.post('/roles/create/:username', auth, async(req, res, next) => {
|
|||
priority: req.body.priority,
|
||||
teamId: team.id
|
||||
}
|
||||
await TeamRoles.create(makeRole)
|
||||
let teamCreate = await TeamRoles.create(makeRole)
|
||||
res.status(200)
|
||||
res.json({success: true})
|
||||
res.json(teamCreate.toJSON())
|
||||
} else if (!teamJoinTest) {
|
||||
res.status(400)
|
||||
res.json({success: false})
|
||||
}
|
||||
} else {
|
||||
throw Errors.teamDoesNotExist
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.put('/roles/modify/:username/:id', auth, async(req, res, next) => {
|
||||
try {
|
||||
let team = await Team.findOne({
|
||||
where: {username: req.params.username}
|
||||
});
|
||||
if(team) {
|
||||
let queryObj3 = {
|
||||
where: {userId: req.userData.UserId, teamId: team.id},
|
||||
}
|
||||
if(team.banned) {
|
||||
res.status(200)
|
||||
res.json({success: false})
|
||||
}
|
||||
let teamJoinTest = await TeamMembers.findOne(queryObj3)
|
||||
if (teamJoinTest) {
|
||||
if(req.body.name) {
|
||||
let find = await TeamRoles.findOne({
|
||||
where: {
|
||||
id: req.params.id,
|
||||
teamId: team.id
|
||||
}
|
||||
})
|
||||
if(find) {
|
||||
let update = await TeamRoles.update({
|
||||
priority: req.body.priority,
|
||||
name: req.body.name,
|
||||
administrator: req.body.administrator,
|
||||
inviteUsers: req.body.inviteUsers,
|
||||
changeTeamMeta: req.body.changeTeamMeta,
|
||||
forumAdministrator: req.body.forumAdministrator,
|
||||
moderateForumThreads: req.body.moderateForumThreads,
|
||||
changeTeamPrivacy: req.body.changeTeamPrivacy,
|
||||
submitTeamItems: req.body.submitTeamItems,
|
||||
}, {
|
||||
where: {
|
||||
id: req.params.id,
|
||||
teamId: team.id
|
||||
}
|
||||
})
|
||||
res.status(200)
|
||||
res.json({success: true})
|
||||
} else {
|
||||
res.status(400)
|
||||
res.json({success: false})
|
||||
}
|
||||
} else if(req.body.priority && !req.body.name) {
|
||||
let find = await TeamRoles.findOne({
|
||||
where: {
|
||||
id: req.params.id,
|
||||
teamId: team.id
|
||||
}
|
||||
})
|
||||
if(find) {
|
||||
await TeamRoles.update({priority: req.body.priority}, {
|
||||
where: {
|
||||
id: req.params.id,
|
||||
teamId: team.id
|
||||
}
|
||||
})
|
||||
res.status(200)
|
||||
res.json({success:true})
|
||||
} else {
|
||||
res.status(400)
|
||||
res.json({success: false})
|
||||
}
|
||||
} else {
|
||||
res.status(400)
|
||||
res.json({success: false})
|
||||
}
|
||||
} else if (!teamJoinTest) {
|
||||
res.status(400)
|
||||
res.json({success: false})
|
||||
}
|
||||
} else {
|
||||
throw Errors.teamDoesNotExist
|
||||
}
|
||||
|
@ -168,46 +248,5 @@ router.post('/roles/create/:username', auth, async(req, res, next) => {
|
|||
})
|
||||
|
||||
|
||||
router.post('/admin/', auth, async(req, res, next) => {
|
||||
try {
|
||||
await Ban.ReadOnlyMode(req.userData.username)
|
||||
let team = await Team.findOne({
|
||||
where: { username: req.params.username }
|
||||
})
|
||||
if(team) {
|
||||
if(team.banned) {
|
||||
throw Errors.teamBanned
|
||||
}
|
||||
let queryObj3 = {
|
||||
where: {userId: req.userData.UserId, teamId: team.id},
|
||||
}
|
||||
let teamJoinTest = await TeamMembers.findOne(queryObj3)
|
||||
if(teamJoinTest) {
|
||||
throw Errors.joinedTeam
|
||||
}
|
||||
let role = await TeamRoles.findOne({
|
||||
where: {teamId: team.id, name: "Members"}
|
||||
})
|
||||
let join = {
|
||||
userId: req.userData.UserId,
|
||||
teamId: team.id,
|
||||
roles: {"deprecated": "deprecated"}
|
||||
}
|
||||
console.log(role)
|
||||
let roleUser = {
|
||||
UserId: req.userData.UserId,
|
||||
TeamId: team.id,
|
||||
RoleId: role.id
|
||||
}
|
||||
await TeamMembers.create(join)
|
||||
await TeamMemberRole.create(roleUser)
|
||||
res.status(200)
|
||||
res.json({success: true})
|
||||
} else {
|
||||
throw Errors.teamDoesNotExist
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
|
86
server.js
86
server.js
|
@ -66,7 +66,6 @@ const passport = require('passport');
|
|||
const specs = swaggerJsdoc(options);
|
||||
const csrf = require('csurf')
|
||||
const csrfProtection = csrf({ cookie: true })
|
||||
|
||||
if(process.env.NODE_ENV === 'production') {
|
||||
app.set('trust proxy', 1);
|
||||
}
|
||||
|
@ -80,45 +79,52 @@ app.use(expAutoSan.all);
|
|||
if(process.env.NODE_ENV !== 'test' && process.env.NODE_ENV !== 'production') {
|
||||
app.use(require('morgan')('dev'))
|
||||
}
|
||||
app.use('/api/v1/user/', require('./routes/user'))
|
||||
app.use('/api/v1/passkey', require('./routes/user_passkey'))
|
||||
app.use('/api/v1/admin/admin_token', require('./routes/admin_token'))
|
||||
app.use('/api/v1/admin/passkey', require('./routes/user_passkey'))
|
||||
app.use('/api/v1/forums/category', require('./routes/category'))
|
||||
app.use('/api/v1/forums/thread', require('./routes/thread'))
|
||||
app.use('/api/v1/users/notification', require('./routes/notification'))
|
||||
app.use('/api/v1/forums/post', require('./routes/post'))
|
||||
app.use('/api/v1/kaverti/state', require('./routes/settings'))
|
||||
app.use('/api/v1/users/report', require('./routes/report'))
|
||||
app.use('/api/v1/users/unban-request', require('./routes/UnbanRequest'))
|
||||
app.use('/api/v1/admin/ban', require('./routes/ban'))
|
||||
app.use('/api/v1/kaverti/search', require('./routes/search'))
|
||||
app.use('/api/v1/log', require('./routes/log'))
|
||||
app.use('/api/v1/forums/poll', require('./routes/poll'))
|
||||
app.use('/api/v1/forums/link_preview', require('./routes/link_preview'))
|
||||
app.use('/api/v1/kaverti/stats', require('./routes/stats'))
|
||||
app.use('/api/v1/users/login_status', require('./routes/login_status'))
|
||||
app.use('/api/v1/users/', require('./routes/userutils'))
|
||||
app.use('/api/v1/admin/killsession', require('./routes/admin_kill_session'))
|
||||
app.use('/api/v1/admin/ban', require('./routes/ban'))
|
||||
app.use('/api/v1/kaverti/job-apply', require('./routes/StaffApplications'))
|
||||
app.use('/api/v1/admin/', require('./routes/admin'))
|
||||
app.use('/api/v1/users/render', require('./routes/avatar'))
|
||||
app.use('/api/v1/users/render/', require('./routes/avatar'))
|
||||
app.use('/api/v1/userinfo', require('./routes/userinfo'))
|
||||
app.use('/api/v1/wall', require('./routes/user_wall'))
|
||||
app.use('/api/v1/chat/conversation', require('./routes/conversation'));
|
||||
app.use('/api/v1/chat/message', require('./routes/message'));
|
||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs, { explorer: true }));
|
||||
app.use('/api/v1/teams/', require('./routes/team'))
|
||||
app.use('/api/v1/teams/admin/', require('./routes/team_admin'))
|
||||
app.use('/api/v1/teams/wall/', require('./routes/team_wall'))
|
||||
app.use('/api/v1/marketplace', require('./routes/marketplace'))
|
||||
app.use('/api/v1/inventory', require('./routes/inventory'))
|
||||
app.use('/api/v1/transactions', require('./routes/transactions'))
|
||||
app.use(require('./lib/errorHandler'))
|
||||
app.use(profanity.init);
|
||||
app.set('trust proxy', true)
|
||||
if(!config.maintenance) {
|
||||
app.use('/api/v1/user/', require('./routes/user'))
|
||||
app.use('/api/v1/passkey', require('./routes/user_passkey'))
|
||||
app.use('/api/v1/admin/admin_token', require('./routes/admin_token'))
|
||||
app.use('/api/v1/admin/passkey', require('./routes/user_passkey'))
|
||||
app.use('/api/v1/forums/category', require('./routes/category'))
|
||||
app.use('/api/v1/forums/thread', require('./routes/thread'))
|
||||
app.use('/api/v1/users/notification', require('./routes/notification'))
|
||||
app.use('/api/v1/forums/post', require('./routes/post'))
|
||||
app.use('/api/v1/kaverti/state', require('./routes/state'))
|
||||
app.use('/api/v1/users/report', require('./routes/report'))
|
||||
app.use('/api/v1/users/unban-request', require('./routes/UnbanRequest'))
|
||||
app.use('/api/v1/admin/ban', require('./routes/ban'))
|
||||
app.use('/api/v1/kaverti/search', require('./routes/search'))
|
||||
app.use('/api/v1/log', require('./routes/log'))
|
||||
app.use('/api/v1/forums/poll', require('./routes/poll'))
|
||||
app.use('/api/v1/forums/link_preview', require('./routes/link_preview'))
|
||||
app.use('/api/v1/kaverti/stats', require('./routes/stats'))
|
||||
app.use('/api/v1/users/login_status', require('./routes/login_status'))
|
||||
app.use('/api/v1/users/', require('./routes/userutils'))
|
||||
app.use('/api/v1/admin/killsession', require('./routes/admin_kill_session'))
|
||||
app.use('/api/v1/admin/ban', require('./routes/ban'))
|
||||
app.use('/api/v1/kaverti/job-apply', require('./routes/StaffApplications'))
|
||||
app.use('/api/v1/admin/', require('./routes/admin'))
|
||||
app.use('/api/v1/users/render', require('./routes/avatar'))
|
||||
app.use('/api/v1/users/render/', require('./routes/avatar'))
|
||||
app.use('/api/v1/userinfo', require('./routes/userinfo'))
|
||||
app.use('/api/v1/wall', require('./routes/user_wall'))
|
||||
app.use('/api/v1/chat/conversation', require('./routes/conversation'));
|
||||
app.use('/api/v1/chat/message', require('./routes/message'));
|
||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs, {explorer: true}));
|
||||
app.use('/api/v1/teams/', require('./routes/team'))
|
||||
app.use('/api/v1/teams/admin/', require('./routes/team_admin'))
|
||||
app.use('/api/v1/teams/wall/', require('./routes/team_wall'))
|
||||
app.use('/api/v1/marketplace', require('./routes/marketplace'))
|
||||
app.use('/api/v1/inventory', require('./routes/inventory'))
|
||||
app.use('/api/v1/transactions', require('./routes/transactions'))
|
||||
app.use(require('./lib/errorHandler'))
|
||||
app.set('trust proxy', true)
|
||||
} else {
|
||||
app.use('/api/v1/userinfo', require('./routes/userinfo'))
|
||||
app.use('/api/v1/kaverti/state', require('./routes/state'))
|
||||
app.use('/api/v1/', require('./routes/maintenance'))
|
||||
app.use(require('./lib/errorHandler'))
|
||||
app.set('trust proxy', true)
|
||||
}
|
||||
function main () {
|
||||
let server = app.listen(config.port, () => {
|
||||
console.log('Initialized')
|
||||
|
|
Loading…
Reference in New Issue