forked from kaverti/website
Team stuff
This commit is contained in:
parent
50fb35ea17
commit
4fa794f133
|
@ -1,4 +1,5 @@
|
|||
module.exports = {
|
||||
port: process.env.PORT || 23981,
|
||||
sessionSecret: process.env.SESSION_SECRET || 'iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;'
|
||||
sessionSecret: process.env.SESSION_SECRET || 'iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;',
|
||||
imageUploadTeams: process.env.TEAMUPLOADS || "C:\\Users\\matth\\Documents\\GitHub\\Kaverti-Team-Images"
|
||||
}
|
||||
|
|
|
@ -44,6 +44,9 @@
|
|||
border-left: 8px solid #bab7b7;
|
||||
padding: 1rem;
|
||||
}
|
||||
body {
|
||||
background: #fdf9f9
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div id='app'>
|
||||
|
@ -519,7 +522,7 @@
|
|||
}
|
||||
},
|
||||
input: '',
|
||||
experimentsTemp: [],
|
||||
experimentsTemp: null,
|
||||
loadingLogout: false,
|
||||
showMenu: false,
|
||||
connModal: true,
|
||||
|
@ -621,8 +624,7 @@
|
|||
setExperimentsStore() {
|
||||
if (localStorage.getItem('experimentsStore')) {
|
||||
this.experimentsTemp = JSON.parse(localStorage.getItem('experimentsStore'))
|
||||
this.$store.commit('setUserWallExperiment', this.experimentsTemp.wall)
|
||||
this.$store.commit('setRelationshipsExperiment', this.experimentsTemp.relationships)
|
||||
this.$store.commit('setExperimentsStore', this.experimentsTemp)
|
||||
} else {
|
||||
console.log("No experimentsStore, not storing experiments into state (Kaverti Experiments)")
|
||||
}
|
||||
|
|
|
@ -24,6 +24,14 @@
|
|||
Toggle
|
||||
</b-switch>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h2>Teams (Local)</h2>
|
||||
<b-switch
|
||||
v-model="experiments.teams"
|
||||
v-if="$store.state.developerMode">
|
||||
Toggle
|
||||
</b-switch>
|
||||
</div>
|
||||
<b-button
|
||||
class='button is-info'
|
||||
:loading='experiments.loading'
|
||||
|
@ -61,6 +69,7 @@
|
|||
error: '',
|
||||
relationships: false,
|
||||
wall: true,
|
||||
teams: false,
|
||||
local: []
|
||||
},
|
||||
}
|
||||
|
@ -75,7 +84,7 @@
|
|||
this.localExperiments()
|
||||
},
|
||||
localExperiments() {
|
||||
localStorage.setItem('experimentsStore', JSON.stringify({wall: this.experiments.wall, theme: this.experiments.theme, relationships: this.experiments.relationships}));
|
||||
localStorage.setItem('experimentsStore', JSON.stringify({wall: this.experiments.wall, theme: this.experiments.theme, relationships: this.experiments.relationships, teams: this.experiments.teams}));
|
||||
this.setExperimentsStore()
|
||||
},
|
||||
savePreferences() {
|
||||
|
@ -107,8 +116,9 @@
|
|||
created () {
|
||||
if (localStorage.getItem('experimentsStore')) {
|
||||
this.experiments = JSON.parse(localStorage.getItem('experimentsStore'))
|
||||
this.experiments(this.experiments.wall)
|
||||
this.experiments(this.experiments.relationships)
|
||||
this.experiments.wall(this.experiments.wall)
|
||||
this.experiments.relationships(this.experiments.relationships)
|
||||
this.experiments.teams(this.experiments.teams)
|
||||
} else {
|
||||
console.log("No experimentsStore, not storing experiments into state (Kaverti Experiments)")
|
||||
}
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
.team-img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
body {
|
||||
background: #F5F7FA
|
||||
}
|
||||
.vertical {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
|
@ -27,23 +24,12 @@ body {
|
|||
<div class="section profile-heading card">
|
||||
<div class="columns is-mobile is-multiline">
|
||||
<div class="column is-2">
|
||||
<figure class="image is-256 team-img" v-if="user.approved && user.picture !== 'default'">
|
||||
<img class="team-img" alt="Kaverti Team profile picture (not a character avatar)" src="https://placehold.it/256x256">
|
||||
</figure>
|
||||
<figure class="image is-256 team-img">
|
||||
<div
|
||||
v-if="user.approved && user.picture === 'default'"
|
||||
class='user_header__icon team-img'
|
||||
style="background: #249acb;"
|
||||
>
|
||||
<h1 class="vertical default-img">{{username[0].toUpperCase()}}</h1>
|
||||
</div>
|
||||
</figure>
|
||||
<figure v-if="!user.approved && $store.state.theme === 'light'" class="image is-256 is-centered">
|
||||
<img class="team-img" alt="Kaverti Team profile picture (not a character avatar)" src="https://cdn.kaverti.com/teams/pending-light.png">
|
||||
</figure>
|
||||
<figure v-if="!user.approved && $store.state.theme === 'dark'" class="image is-256 is-centered">
|
||||
<img class="team-img" alt="Kaverti Team profile picture (not a character avatar)" src="https://cdn.kaverti.com/teams/pending-dark.png">
|
||||
<figure class="image is-256 is-centered">
|
||||
<img class="team-img" v-if="user.picture !== 'default' && user.approved" width="256px" height="256px" :src="user.picture">
|
||||
<img class="team-img" v-if="user.picture === 'default' && $store.state.theme === 'light' && user.approved" width="256px" height="256px" src="https://cdn.kaverti.com/teams/unknown-light.png">
|
||||
<img class="team-img" v-if="user.picture === 'default' && $store.state.theme === 'dark' && user.approved" width="256px" height="256px" src="https://cdn.kaverti.com/teams/unknown-dark.png">
|
||||
<img class="team-img" v-if="$store.state.theme === 'light' && !user.approved" width="256px" height="256px" src="https://cdn.kaverti.com/teams/pending-light.png">
|
||||
<img class="team-img" v-if="$store.state.theme === 'dark' && !user.approved" width="256px" height="256px" src="https://cdn.kaverti.com/teams/pending-dark.png">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="column is-4-tablet is-10-mobile name">
|
||||
|
@ -68,20 +54,20 @@ body {
|
|||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="column is-8-fullhd">
|
||||
<b-button v-if="!user.userWallOptOut && $store.state.username" class="menu_button" :key='"user-menu-item-wall"' @click='$router.push(`/user/${username}/wall`)'>
|
||||
User Wall
|
||||
<div class="column">
|
||||
<b-button v-if="!user.userWallOptOut && $store.state.username" class="menu_button" :key='"user-menu-item-wall"' @click='$router.push(`/t/${username}/wall`)'>
|
||||
Team Wall
|
||||
</b-button>
|
||||
<b-tooltip v-if="!user.userWallOptOut && !$store.state.username" label="You can only view a user's wall when logged in">
|
||||
<b-tooltip v-if="!user.userWallOptOut && !$store.state.username" label="You can only view a team's wall when logged in">
|
||||
<b-button v-if="!user.userWallOptOut && !$store.state.username" disabled="disabled" class="menu_button disabled" :key='"user-menu-item-wall"'>
|
||||
User Wall
|
||||
Team Wall
|
||||
</b-button>
|
||||
</b-tooltip>
|
||||
<b-button class="menu_button" :key='"user-menu-item-posts"' @click='$router.push(`/user/${username}/posts`)'>
|
||||
Posts
|
||||
<b-button class="menu_button" :key='"user-menu-item-posts"' @click='$router.push(`/t/${username}/items`)'>
|
||||
Items
|
||||
</b-button>
|
||||
<b-button class="menu_button" :key='"user-menu-item-threads"' @click='$router.push(`/user/${username}/threads`)'>
|
||||
Threads
|
||||
<b-button class="menu_button" :key='"user-menu-item-posts"' @click='$router.push(`/t/${username}/members`)'>
|
||||
Members and Roles
|
||||
</b-button>
|
||||
<br/> <br/>
|
||||
<div class="column box">
|
||||
|
@ -209,9 +195,6 @@ export default {
|
|||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/view/${this.$route.params.username}`)
|
||||
.then(res => this.user = res.data)
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `relationships/${this.$route.params.username}`)
|
||||
.then(res => this.relationship = res.data)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
<style>
|
||||
mini-br {
|
||||
border-width: 1px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class='route_container'>
|
||||
<modal-window
|
||||
v-model='picture.showProfilePictureModal'
|
||||
:loading='picture.loading'
|
||||
width='25rem'
|
||||
style="z-index: 99;"
|
||||
@input='hideProflePictureModal'
|
||||
>
|
||||
<div slot="header">
|
||||
Add a team profile picture
|
||||
</div>
|
||||
<div
|
||||
slot='main'
|
||||
class='card-content'
|
||||
:class='{ "profile_picture_modal--picture.dragging": picture.dragging }'
|
||||
@dragover='handleDragOver'
|
||||
@drkagend='picture.dragging = false'
|
||||
@drkgleave='picture.dragging = false'
|
||||
@drop='handleFileDrop'
|
||||
>
|
||||
<div class='h3'>Add a profile picture</div>
|
||||
<p class='p--condensed'>
|
||||
Drag and drop an image or<br>
|
||||
<label class='button profile_picture_modal__upload_button'>
|
||||
<input type='file' accept='image/*' @change='processImage($event.target.files[0])'>
|
||||
Upload image
|
||||
</label>
|
||||
</p>
|
||||
<div class='profile_picture_modal__drag_area'>
|
||||
<span
|
||||
v-if='!picture.dataURL'
|
||||
class='profile_picture_modal__drag_area__icon'
|
||||
:class='{ "profile_picture_modal__drag_area__icon--picture.dragging": picture.dragging }'
|
||||
>
|
||||
<font-awesome-icon :icon='["fa", "cloud-upload-alt"]' />
|
||||
</span>
|
||||
<div
|
||||
class='profile_picture_modal__drag_area__image picture_circle'
|
||||
:style='{ "background-image": "url(" + picture.dataURL + ")" }'
|
||||
v-else
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='profile_picture_modal__buttons' slot='footer'>
|
||||
<button
|
||||
class='button button--modal button--green'
|
||||
:class='{ "button--disabled": !picture.dataURL }'
|
||||
@click='uploadProfilePicture'
|
||||
>
|
||||
Upload picture
|
||||
</button>
|
||||
<button class='button button--modal' @click='hideProflePictureModal'>Cancel</button>
|
||||
</div>
|
||||
</modal-window>
|
||||
<h1>{{team.name}} General</h1>
|
||||
<div>
|
||||
<h3>Username (200 Koins):</h3>
|
||||
<b-input
|
||||
:placeholder='team.username'
|
||||
maxlength="30"
|
||||
:value="team.username"
|
||||
v-model='teamGeneral.username'
|
||||
:error='teamGeneral.usernameError'
|
||||
></b-input>
|
||||
<h3>Name:</h3>
|
||||
<b-input
|
||||
:placeholder='team.name'
|
||||
maxlength="30"
|
||||
:value="teamGeneral.name"
|
||||
v-model='teamGeneral.name'
|
||||
:error='teamGeneral.nameError'
|
||||
></b-input>
|
||||
<h3>Description:</h3>
|
||||
<b-input
|
||||
:placeholder='team.description'
|
||||
maxlength="256"
|
||||
:value="team.description"
|
||||
v-model='teamGeneral.description'
|
||||
:error='teamGeneral.descriptionError'
|
||||
></b-input>
|
||||
<b-button
|
||||
class='button is-info'
|
||||
:loading='teamGeneral.loading'
|
||||
@click='saveTeam'
|
||||
>
|
||||
Save team
|
||||
</b-button>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Add/change Team profile picture:</h2>
|
||||
<p
|
||||
class='p--condensed profile_picture_preview picture_circle'
|
||||
:style='{ "background-image": "url(" + picture.current + ")" }'
|
||||
v-if='picture.current'
|
||||
></p>
|
||||
<button class='button' @click='picture.showProfilePictureModal = true'>
|
||||
{{picture.current ? "Change" : "Add" }} profile picture
|
||||
</button>
|
||||
<button
|
||||
v-if='picture.current'
|
||||
class='button'
|
||||
style='margin-left: 0.5rem;'
|
||||
@click='picture.showRemoveProfilePictureModal = true'
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FancyTextarea from '../FancyTextarea'
|
||||
import ModalWindow from '../ModalWindow'
|
||||
|
||||
import AjaxErrorHandler from '../../assets/js/errorHandler'
|
||||
import logger from '../../assets/js/logger'
|
||||
|
||||
export default {
|
||||
name: 'settingsGeneral',
|
||||
components: {
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
FancyTextarea,
|
||||
ModalWindow
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
teamGeneral: {
|
||||
name: '',
|
||||
loading: false,
|
||||
nameError: '',
|
||||
description: '',
|
||||
descriptionError: '',
|
||||
username: '',
|
||||
usernameError: ''
|
||||
},
|
||||
picture: {
|
||||
current: null,
|
||||
showProfilePictureModal: false,
|
||||
showRemoveProfilePictureModal: false,
|
||||
dragging: false,
|
||||
dataURL: null,
|
||||
file: null,
|
||||
loading: false
|
||||
},
|
||||
team: null
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
uploadProfilePicture() {
|
||||
this.picture.loading = true
|
||||
let formData = new FormData()
|
||||
formData.append('picture', this.picture.file)
|
||||
this.axios
|
||||
.post('/api/v1/teams/admin/' + this.team.username + '/picture', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
this.hideProflePictureModal()
|
||||
this.picture.current = res.data.picture
|
||||
})
|
||||
.catch(e => {
|
||||
this.picture.loading = false
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
},
|
||||
removeProfilePicture() {
|
||||
this.axios
|
||||
.delete('/api/v1/user/' + this.$store.state.username + '/picture')
|
||||
.then(() => {
|
||||
this.picture.current = null
|
||||
})
|
||||
.catch(AjaxErrorHandler(this.$store))
|
||||
},
|
||||
hideProflePictureModal() {
|
||||
this.picture.showProfilePictureModal = false
|
||||
|
||||
//Wait for transition to complete
|
||||
setTimeout(() => {
|
||||
this.picture.dataURL = null
|
||||
this.picture.loading = false
|
||||
}, 200)
|
||||
},
|
||||
handleDragOver(e) {
|
||||
e.preventDefault()
|
||||
this.picture.dragging = true
|
||||
},
|
||||
handleFileDrop(e) {
|
||||
e.preventDefault()
|
||||
this.picture.dragging = false
|
||||
|
||||
if (e.dataTransfer && e.dataTransfer.items) {
|
||||
let file = e.dataTransfer.items[0]
|
||||
if (file.type.match('^image/')) {
|
||||
this.processImage(file.getAsFile())
|
||||
}
|
||||
}
|
||||
},
|
||||
processImage(file) {
|
||||
let reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
this.picture.file = file
|
||||
reader.addEventListener('load', () => {
|
||||
this.picture.dataURL = reader.result
|
||||
})
|
||||
},
|
||||
capitalizeFirstLetter(value) {
|
||||
return value.toUpperCase()
|
||||
},
|
||||
saveTeam() {
|
||||
this.teamGeneral.error = ''
|
||||
this.teamGeneral.loading = true
|
||||
this.axios
|
||||
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'teams/admin/modify/' + this.team.username, {
|
||||
description: this.teamGeneral.description,
|
||||
name: this.teamGeneral.name
|
||||
})
|
||||
.then(() => {
|
||||
this.teamGeneral.loading = false
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/view/${this.$route.params.username}`)
|
||||
.then(res => this.team = res.data)
|
||||
this.$nextTick(() => {
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/view/${this.$route.params.username}`)
|
||||
.then(res => {
|
||||
this.teamGeneral.description = res.data.description || ''
|
||||
this.teamGeneral.username = res.data.username
|
||||
this.teamGeneral.name = res.data.name || ''
|
||||
})
|
||||
.catch(e => {
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.teamGeneral.loading = false
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
|
||||
.then(res => {
|
||||
this.$store.commit('setUsername', res.data.username)
|
||||
this.$store.commit('setEmail', res.data.email)
|
||||
this.$store.commit('setEmailVerified', res.data.emailVerified)
|
||||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setDevMode', res.data.developerMode)
|
||||
})
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/view/${this.$route.params.username}`)
|
||||
.then(res => this.team = res.data)
|
||||
this.$nextTick(() => {
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/view/${this.$route.params.username}`)
|
||||
.then(res => {
|
||||
this.teamGeneral.description = res.data.description || ''
|
||||
this.teamGeneral.username = res.data.username
|
||||
this.teamGeneral.name = res.data.name || ''
|
||||
})
|
||||
.catch(e => {
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
})
|
||||
this.$store.dispatch('setTitle', 'General Team Settings')
|
||||
|
||||
logger('settingsGeneral')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='scss' scoped>
|
||||
@import '../../assets/scss/variables.scss';
|
||||
.profile_picture_preview {
|
||||
height: 5rem;
|
||||
width: 5rem;
|
||||
}
|
||||
.profile_picture_modal {
|
||||
padding-top: 1rem;
|
||||
transition: all 0.2s;
|
||||
@at-root #{&}--picture .dragging {
|
||||
background-color: $color__lightgray--primary;
|
||||
}
|
||||
@at-root #{&}__overlay {
|
||||
@include loading-overlay(rgba(0, 0, 0, 0.5), 0.125rem);
|
||||
}
|
||||
@at-root #{&}__upload_button input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
@at-root #{&}__drag_area {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
@at-root #{&}__image {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
display: inline-block;
|
||||
margin-top: -1rem;
|
||||
}
|
||||
@at-root #{&}__icon {
|
||||
font-size: 6rem;
|
||||
color: $color__gray--darker;
|
||||
transition: all 0.2s;
|
||||
@at-root #{&}--picture.dragging {
|
||||
transform: translateY(-0.5rem) scale(1.1);
|
||||
color: $color__gray--darkest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: $breakpoint--tablet) {
|
||||
.h1 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<main>
|
||||
|
||||
</main>
|
||||
</template>
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<main>
|
||||
|
||||
</main>
|
||||
</template>
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<main>
|
||||
|
||||
</main>
|
||||
</template>
|
|
@ -0,0 +1,219 @@
|
|||
<template>
|
||||
<div class='route_container section route_container--settings'>
|
||||
<div class='settings_menu columns'>
|
||||
<b-menu class="column box is-11">
|
||||
<b-menu-list :label="team.name + ' Team'">
|
||||
<b-menu-item
|
||||
:label=item.name
|
||||
:icon=item.icon
|
||||
:key='index'
|
||||
v-for='(item, index) in menuItems'
|
||||
:class="{'': index === selected}"
|
||||
@click='$router.push("/team/" + team.username + "/" + item.route)'
|
||||
></b-menu-item>
|
||||
</b-menu-list>
|
||||
</b-menu>
|
||||
</div>
|
||||
<div class='column box'>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TeamSettings',
|
||||
data () {
|
||||
return {
|
||||
menuItems: [
|
||||
{ name: 'General', route: 'general', icon: 'cog' },
|
||||
{ name: 'Roles', route: 'account', icon: 'user-lock'},
|
||||
{ name: 'Users', route: 'privacy', icon: 'users'},
|
||||
{ name: 'Join Requests', route: 'experiments', icon: 'envelope'},
|
||||
{ name: 'Team Privacy', route: 'about', icon: 'lock'},
|
||||
],
|
||||
selected: 0,
|
||||
team: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route (to) {
|
||||
this.selected = this.getIndexFromRoute(to.path)
|
||||
},
|
||||
'$store.state.username' (username) {
|
||||
if(!username) {
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.selected = this.getIndexFromRoute(this.$route.path)
|
||||
this.axios.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'userinfo')
|
||||
.then(res => {
|
||||
this.$store.commit('setUsername', res.data.username)
|
||||
this.$store.commit('setEmail', res.data.email)
|
||||
this.$store.commit('setEmailVerified', res.data.emailVerified)
|
||||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setKoins', res.data.koins)
|
||||
})
|
||||
.catch(e => {
|
||||
this.showConn(e)
|
||||
})
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/view/${this.$route.params.username}`)
|
||||
.then(res => this.team = res.data)
|
||||
},
|
||||
methods: {
|
||||
getIndexFromRoute (path) {
|
||||
let selectedIndex
|
||||
let route = path.split('/')[2]
|
||||
|
||||
this.menuItems.forEach((item, index) => {
|
||||
if(item.route === route) {
|
||||
selectedIndex = index
|
||||
}
|
||||
})
|
||||
|
||||
return selectedIndex
|
||||
}
|
||||
},
|
||||
beforeRouteEnter (to, from, next) {
|
||||
next(vm => {
|
||||
if(!vm.$store.state.username) {
|
||||
vm.$store.commit('setAccountModalState', true);
|
||||
next('/')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '../../assets/scss/variables.scss';
|
||||
|
||||
.route_container--settings {
|
||||
display: flex;
|
||||
align-items: flex-start
|
||||
}
|
||||
|
||||
.settings_menu {
|
||||
width: 15rem;
|
||||
padding: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
@at-root #{&}__title {
|
||||
cursor: default;
|
||||
font-weight: 500;
|
||||
font-variant: small-caps;
|
||||
font-size: 1.125rem;
|
||||
padding-left: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@at-root #{&}__item {
|
||||
padding: 0.5rem 1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
padding-right: 0;
|
||||
transition: background-color 0.2s;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
&:first-child { margin-top: 0.5rem; }
|
||||
&:last-child { margin-bottom: 0.5rem; }
|
||||
|
||||
&:hover { background-color: $color__lightgray--primary; }
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 0.25rem;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
border-radius: 0.25rem 0 0 0.25em;
|
||||
top: 0;
|
||||
background-color: $color__gray--darkest;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $color__text--secondary;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
@at-root #{&}--selected {
|
||||
background-color: $color__lightgray--darker;
|
||||
color: $color__text--primary;
|
||||
|
||||
span {
|
||||
color: $color__text--primary;
|
||||
}
|
||||
|
||||
&:hover { background-color: $color__lightgray--darker; }
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.settings_page {
|
||||
width: calc(100% - 15rem);
|
||||
background-color: #fff;
|
||||
border-radius: 0.25rem;
|
||||
margin-left: 2rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint--tablet) and (min-width: $breakpoint--phone) {
|
||||
div.settings_menu, div.settings_page {
|
||||
width: calc(100% - 4rem);
|
||||
margin: 0.5rem 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint--tablet) {
|
||||
.route_container--settings {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.settings_menu {
|
||||
width: 100%;
|
||||
|
||||
@at-root #{&}__items {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
@at-root #{&}__item {
|
||||
width: 7rem;
|
||||
margin-right: 0.5rem;
|
||||
color: $color__text--primary;
|
||||
|
||||
&:first-child, &:last-child {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&::before {
|
||||
height: 0.2rem;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
border-radius: 0 0 1rem 1rem;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings_page {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<main>
|
||||
|
||||
</main>
|
||||
</template>
|
|
@ -0,0 +1,433 @@
|
|||
<template>
|
||||
<main>
|
||||
<div class='' :class='{ "user_posts--no_border_bottom": posts && !posts.length }'>
|
||||
<div class='user_posts__title'>{{team.name}}'s team wall</div>
|
||||
<div
|
||||
class='editor'
|
||||
:class='{
|
||||
"editor--focus": focusInput,
|
||||
"editor--error": errors.content
|
||||
}'
|
||||
>
|
||||
<div class='editor__input'>
|
||||
<input-editor-core
|
||||
v-model='editor'
|
||||
@mentions='setMentions'
|
||||
@focus='setFocusInput(true)'
|
||||
@blur='setFocusInput(false)'
|
||||
></input-editor-core>
|
||||
</div>
|
||||
</div>
|
||||
<error-tooltip :error='errors.content' class='editor_error'></error-tooltip>
|
||||
<b-button class='submit' :loading='loading' @click='postThread'>Post on wall</b-button>
|
||||
<template v-if='!posts'>
|
||||
<thread-post-placeholder v-if='!posts'>
|
||||
</thread-post-placeholder>
|
||||
</template>
|
||||
|
||||
<scroll-load
|
||||
:loading='loadingPosts'
|
||||
@loadNext='loadNewPosts'
|
||||
v-else-if='posts.length'
|
||||
>
|
||||
<wall-post
|
||||
v-for='(post, index) in posts'
|
||||
:key='"thread-post-" + post.id'
|
||||
|
||||
:post='post'
|
||||
:show-thread='true'
|
||||
:click-for-post='true'
|
||||
:class='{"post--last": index === posts.length-1}'
|
||||
></wall-post>
|
||||
<template v-if='loadingPosts'>
|
||||
<thread-post-placeholder
|
||||
v-for='n in nextPostsCount'
|
||||
:key='"thread-post-placeholder-" + n'
|
||||
></thread-post-placeholder>
|
||||
</template>
|
||||
</scroll-load>
|
||||
<template v-else><br><br>There are no wall posts yet</template>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScrollLoad from '../ScrollLoad'
|
||||
import WallPost from '../WallPost'
|
||||
import ThreadPostPlaceholder from '../ThreadPostPlaceholder'
|
||||
import InputEditorCore from '../InputEditorCore'
|
||||
import ErrorTooltip from '../ErrorTooltip'
|
||||
import AjaxErrorHandler from '../../assets/js/errorHandler'
|
||||
import logger from '../../assets/js/logger'
|
||||
|
||||
export default {
|
||||
name: 'ThreadNew',
|
||||
props: ['username'],
|
||||
components: {
|
||||
WallPost,
|
||||
ScrollLoad,
|
||||
ThreadPostPlaceholder,
|
||||
InputEditorCore,
|
||||
ErrorTooltip
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
selectedCategory: this.$store.state.category.selectedCategory,
|
||||
editor: '',
|
||||
mentions: [],
|
||||
name: '',
|
||||
loading: false,
|
||||
focusInput: false,
|
||||
threads: null,
|
||||
loadingThreads: false,
|
||||
nextURL: '',
|
||||
nextThreadsCount: 0,
|
||||
posts: null,
|
||||
|
||||
errors: {
|
||||
content: '',
|
||||
name: '',
|
||||
pollQuestion: '',
|
||||
pollAnswer: ''
|
||||
},
|
||||
|
||||
showPoll: false,
|
||||
pollQuestion: '',
|
||||
newPollAnswer: '',
|
||||
pollAnswers: [],
|
||||
team: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
categories () {
|
||||
return this.$store.getters.categoriesWithoutAll
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadNewPosts () {
|
||||
if(this.nextURL === null) return
|
||||
|
||||
this.loadingThreads = true
|
||||
},
|
||||
togglePoll (val) {
|
||||
if(val !== undefined) {
|
||||
this.showPoll = val
|
||||
} else {
|
||||
this.showPoll = !this.showPoll
|
||||
}
|
||||
},
|
||||
addPollAnswer () {
|
||||
if(!this.newPollAnswer.trim().length) return
|
||||
|
||||
this.pollAnswers.push({ answer: this.newPollAnswer })
|
||||
this.newPollAnswer = ''
|
||||
},
|
||||
removePollAnswer ($index) {
|
||||
this.pollAnswers.splice($index, 1)
|
||||
},
|
||||
removePoll () {
|
||||
this.pollQuestion = ''
|
||||
this.pollAnswers = []
|
||||
this.newPollAnswer = ''
|
||||
|
||||
this.togglePoll()
|
||||
},
|
||||
|
||||
setErrors (errors) {
|
||||
errors.forEach(error => {
|
||||
this.errors[error.name] = error.error
|
||||
})
|
||||
},
|
||||
getUserInfo () {
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/view/${this.$route.params.username}`)
|
||||
.catch((e) => {
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
},
|
||||
clearErrors () {
|
||||
this.errors.content = ''
|
||||
this.errors.name = ''
|
||||
this.errors.pollQuestion = ''
|
||||
this.errors.pollAnswer = ''
|
||||
},
|
||||
|
||||
hasDuplicates (array, cb) {
|
||||
if(cb) array = array.map(cb)
|
||||
|
||||
return array.length !== (new Set(array)).size
|
||||
},
|
||||
|
||||
postThread () {
|
||||
let errors = []
|
||||
|
||||
this.clearErrors()
|
||||
|
||||
if(!this.editor.trim().length) {
|
||||
errors.push({name: 'content', error: 'Post content cannot be blank'})
|
||||
} if(errors.length) {
|
||||
this.setErrors(errors)
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
|
||||
this.axios.post(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/wall/post`, {
|
||||
username: this.$route.params.username,
|
||||
content: this.editor,
|
||||
mentions: this.mentions
|
||||
}).then(() => {
|
||||
let ajax = []
|
||||
return Promise.all(ajax)
|
||||
}).then(() => {
|
||||
this.loading = false
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/view/${this.$route.params.username}?wall=true`)
|
||||
.then(res => {
|
||||
this.posts = res.data.teamWalls
|
||||
this.nextPostsCount = res.data.meta.postNumber
|
||||
})
|
||||
}).catch(e => {
|
||||
this.loading = false
|
||||
|
||||
AjaxErrorHandler(this.$store)(e, (error, errors) => {
|
||||
let path = error.path
|
||||
|
||||
if(this.errors[path] !== undefined) {
|
||||
this.errors[path] = error.message
|
||||
} else {
|
||||
errors.push(error.message)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
setFocusInput (val) {
|
||||
this.focusInput = val
|
||||
},
|
||||
setMentions (mentions) {
|
||||
this.mentions = mentions
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$store.state.username' (username) {
|
||||
if(!username) {
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.getUserInfo();
|
||||
this.$store.dispatch('setTitle', this.$route.params.name + '\'s team wall')
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams/view/${this.$route.params.username}?wall=true`)
|
||||
.then(res => {
|
||||
this.loadingPosts = false
|
||||
this.posts = res.data.teamWalls
|
||||
this.team = res.data
|
||||
this.nextPostsCount = res.data.meta.nextPostsCount
|
||||
})
|
||||
.catch((e) => {
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
|
||||
logger('userWall', this.$route.params.username)
|
||||
},
|
||||
beforeRouteEnter (to, from, next) {
|
||||
next(vm => {
|
||||
if(!vm.$store.state.username) {
|
||||
vm.$store.commit('setAccountModalState', true);
|
||||
next('/')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '../../assets/scss/variables.scss';
|
||||
|
||||
.thread_new {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.thread_meta_info {
|
||||
background-color: #fff;
|
||||
border: thin solid $color__gray--darker;
|
||||
border-radius: 0.25rem;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
|
||||
@at-root #{&}__title {
|
||||
margin: 0 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@at-root #{&}__form {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
@at-root #{&}__add_poll {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@at-root #{&}__text {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@at-root #{&}__poll {
|
||||
border-top: thin solid $color__gray--primary;
|
||||
margin-top: 1rem;
|
||||
padding-top: 0.75rem;
|
||||
position: relative;
|
||||
|
||||
@at-root #{&}__top_bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
@at-root #{&}__answer {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
& > span {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: all 0.1s;
|
||||
|
||||
font-size: 1.5rem;
|
||||
margin-left: 0.5rem;
|
||||
cursor: pointer;
|
||||
@include user-select(none);
|
||||
}
|
||||
|
||||
&:hover > span {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.editor {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-radius: 0.25rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
|
||||
transition: all 0.2s;
|
||||
|
||||
@at-root #{&}--focus {
|
||||
border: thin solid $color__gray--darkest;
|
||||
}
|
||||
@at-root #{&}--error {
|
||||
border: thin solid $color__red--primary;
|
||||
}
|
||||
|
||||
@at-root #{&}__format_bar {
|
||||
height: 2.5rem;
|
||||
background-color: $color__gray--primary;
|
||||
display: flex;
|
||||
padding-right: 1rem;
|
||||
padding-bottom: 0.25rem;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
font-variant: small-caps;
|
||||
|
||||
@at-root #{&}--preview {
|
||||
border-radius: 0 0.25rem 0 0;
|
||||
}
|
||||
@at-root #{&}--editor {
|
||||
border-radius: 0.25rem 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@at-root #{&}__input {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
.input_editor_core__format_bar {
|
||||
left: 0rem;
|
||||
}
|
||||
.input_editor_core textarea {
|
||||
height: 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@at-root #{&}__preview {
|
||||
border-left: 1px solid $color__gray--darker;
|
||||
|
||||
div.input_editor_preview__markdownHTML {
|
||||
height: 14.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor_error {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
margin-top: 0.5rem;
|
||||
border-radius: 0.2rem;
|
||||
border: thin solid $color__red--primary;
|
||||
|
||||
&.error_tooltip--show {
|
||||
max-height: 4rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.thread_meta_info {
|
||||
@at-root #{&}__form {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@at-root #{&}__title.fancy_input {
|
||||
margin: 0;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@at-root #{&}__poll__question {
|
||||
width: 100%;
|
||||
|
||||
> div, input {
|
||||
width: 100% ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
|
||||
@at-root #{&}__input, #{&}__preview {
|
||||
border: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.user_posts {
|
||||
background: #fff;
|
||||
border-radius: 0.25rem;
|
||||
padding: 1rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
|
||||
@at-root #{&}__title {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint--tablet) {
|
||||
.user_posts {
|
||||
margin-top: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,14 +1,176 @@
|
|||
<style>
|
||||
.team-img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.vertical-alt {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<section class="hero is-info is-large">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h2>
|
||||
Kaverti Teams
|
||||
</h2>
|
||||
<h1 class="title">
|
||||
Kaverti Teams are coming soon
|
||||
</h1>
|
||||
<main>
|
||||
<section v-if="!$store.state.experimentsStore.teams" class="hero is-info is-large">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h2>
|
||||
Kaverti Teams
|
||||
</h2>
|
||||
<h1 class="title">
|
||||
Kaverti Teams are coming soon
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<div class="section" v-if="$store.state.experimentsStore.teams">
|
||||
<div class="">
|
||||
<div class="column">
|
||||
<b-button class="is-primary">Create Team</b-button>
|
||||
</div>
|
||||
<scroll-load
|
||||
key='user-row'
|
||||
class='columns is-multiline'
|
||||
v-if='users.length'
|
||||
:loading='loading'
|
||||
@loadNext='fetchData'
|
||||
>
|
||||
<div class="column is-4" v-for='user in users' :key='"user-row" + user.username' v-show="user && !user.hidden"><div class="card">
|
||||
<div class="card-content">
|
||||
<router-link :to="'/t/' + user.username"><b-button style="float:right;">View</b-button></router-link>
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-64x64">
|
||||
<img class="team-img" v-if="user.picture !== 'default' && user.approved" width="128px" height="128px" :src="user.picture">
|
||||
<img class="team-img" v-if="user.picture === 'default' && $store.state.theme === 'light' && user.approved" width="128px" height="128px" src="https://cdn.kaverti.com/teams/unknown-light.png">
|
||||
<img class="team-img" v-if="user.picture === 'default' && $store.state.theme === 'dark' && user.approved" width="128px" height="128px" src="https://cdn.kaverti.com/teams/unknown-dark.png">
|
||||
<img class="team-img" v-if="$store.state.theme === 'light' && !user.approved" width="128px" height="128px" src="https://cdn.kaverti.com/teams/pending-light.png">
|
||||
<img class="team-img" v-if="$store.state.theme === 'dark' && !user.approved" width="128px" height="128px" src="https://cdn.kaverti.com/teams/pending-dark.png">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{user.name}}</p>
|
||||
<p class="subtitle is-6">@{{user.username}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{{user.description}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</scroll-load>
|
||||
</div>
|
||||
</div>
|
||||
<p name='fade' mode='out-in'>
|
||||
<center><loading-message key='loading' v-if='loading'></loading-message></center>
|
||||
<center><div class='overlay_message' v-if='!loading && !users.length'>
|
||||
Something went wrong while loading the users, check your internet connection, or check the <a href="https://status.troplo.com">Service Status</a>
|
||||
</div></center></p>
|
||||
</main>
|
||||
</template>
|
||||
<script>
|
||||
import LoadingMessage from '../LoadingMessage';
|
||||
import throttle from 'lodash.throttle';
|
||||
import AjaxErrorHandler from '../../assets/js/errorHandler';
|
||||
|
||||
export default {
|
||||
name: 'UserList',
|
||||
components: {
|
||||
LoadingMessage
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
search: '',
|
||||
users: [],
|
||||
|
||||
loading: true,
|
||||
offset: 0,
|
||||
limit: 15,
|
||||
|
||||
roleOptions: [
|
||||
{ name: 'Admins', value: 'admin' },
|
||||
{ name: 'Users', value: 'user' }
|
||||
],
|
||||
roleSelected: ['admin', 'user'],
|
||||
|
||||
tableSort: {
|
||||
column: 'username',
|
||||
sort: 'desc'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
if(this.offset === null) return;
|
||||
|
||||
let url = process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams?
|
||||
sort=${this.tableSort.column}
|
||||
&order=${this.tableSort.sort}
|
||||
&offset=${this.offset}
|
||||
`;
|
||||
if(this.roleSelected.length === 1) {
|
||||
url += '&role=' + this.roleSelected[0];
|
||||
}
|
||||
if(this.search.length) {
|
||||
url += '&search=' + encodeURIComponent(this.search.trim());
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.axios
|
||||
.get(url)
|
||||
.then(res => {
|
||||
this.users.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) {
|
||||
this.offset = null;
|
||||
} else {
|
||||
this.offset+= this.limit;
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
AjaxErrorHandler(this.$store)(e);
|
||||
this.loading = /*loading =*/ false;
|
||||
});
|
||||
},
|
||||
resetFetchData () {
|
||||
this.offset = 0;
|
||||
this.users = [];
|
||||
|
||||
this.fetchData();
|
||||
}
|
||||
},
|
||||
getNewerUsers () {
|
||||
this.loadingNewer = true
|
||||
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'teams' + '?limit=' + this.newUsers)
|
||||
.then(res => {
|
||||
this.loadingNewer = false
|
||||
this.newUsers = 0
|
||||
|
||||
this.threads.unshift(...res.data.Threads)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.loadingNewer = false
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
this.fetchData();
|
||||
},
|
||||
watch: {
|
||||
tableSort: 'resetFetchData',
|
||||
roleSelected: 'resetFetchData',
|
||||
search: throttle(function () {
|
||||
this.resetFetchData();
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -58,10 +58,10 @@
|
|||
Manage user
|
||||
</button>
|
||||
</menu-button><br>
|
||||
<b-button v-if="$store.state.relationships && relationship && user.username !== $store.state.username && $store.state.username" class='is-danger button' icon-left="minus">
|
||||
<b-button v-if="$store.state.experimentsStore.relationships && relationship && user.username !== $store.state.username && $store.state.username" class='is-danger button' icon-left="minus">
|
||||
Remove Friend
|
||||
</b-button>
|
||||
<b-button :value="1" @click="doRelationship" v-if="$store.state.relationships && user && !relationship && user.username !== $store.state.username && $store.state.username" class='is-info button' icon-left="plus">
|
||||
<b-button :value="1" @click="doRelationship" v-if="$store.state.experimentsStore.relationships && user && !relationship && user.username !== $store.state.username && $store.state.username" class='is-info button' icon-left="plus">
|
||||
Send friend request
|
||||
</b-button>
|
||||
</div>
|
||||
|
|
|
@ -71,6 +71,13 @@ const SearchUsersThreads = () => import('./components/routes/SearchUsersThreads'
|
|||
|
||||
const Teams = () => import('./components/routes/Teams')
|
||||
const Team = () => import('./components/routes/Team')
|
||||
const TeamSettings = () => import('./components/routes/TeamSettings')
|
||||
const TeamRoles = () => import('./components/routes/TeamRoles')
|
||||
const TeamGeneral = () => import('./components/routes/TeamGeneral')
|
||||
const TeamPrivacy = () => import('./components/routes/TeamPrivacy')
|
||||
const TeamRequests = () => import('./components/routes/TeamRequests')
|
||||
const TeamUsers = () => import('./components/routes/TeamUsers')
|
||||
const TeamWall = () => import('./components/routes/TeamWall')
|
||||
|
||||
const User = () => import('./components/routes/User')
|
||||
const UserPosts = () => import('./components/routes/UserPosts')
|
||||
|
@ -177,9 +184,16 @@ const router = new VueRouter({
|
|||
{ path: '/marketplace', component: Marketplace },
|
||||
{ path: '/licenses', component: Licenses },
|
||||
{ path: '/teams', component: Teams },
|
||||
{ path: '/team/:username', redirect: '/team/:username/general', component: TeamSettings, children: [
|
||||
{ path: 'general', component: TeamGeneral },
|
||||
{ path: 'users', component: TeamUsers },
|
||||
{ path: 'roles', component: TeamRoles },
|
||||
{ path: 'privacy', component: TeamPrivacy },
|
||||
{ path: 'requests', component: TeamRequests },
|
||||
] },
|
||||
{ path: '/t/:username', component: Team, children: [
|
||||
{ path: 'items', component: UserMarketplace },
|
||||
{ path: 'wall', component: UserWall }
|
||||
{ path: 'wall', component: TeamWall }
|
||||
] },
|
||||
{ path: '/verify', component: EmailVerify },
|
||||
{ path: '/verify/check', component: EmailVerifyCheck },
|
||||
|
|
|
@ -130,11 +130,8 @@ export default new Vuex.Store({
|
|||
showConnModal(state, value) {
|
||||
state.connModal = value;
|
||||
},
|
||||
setUserWallExperiment(state, value) {
|
||||
state.userWall = value;
|
||||
},
|
||||
setRelationshipsExperiment(state, value) {
|
||||
state.relationships = value;
|
||||
setExperimentsStore(state, value) {
|
||||
state.experimentsStore = value;
|
||||
},
|
||||
setAjaxErrorsModalState(state, value) {
|
||||
state.ajaxErrorsModal = value;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.createTable('teamPictures', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
mimetype :{
|
||||
type: Sequelize.STRING
|
||||
},
|
||||
createdAt: Sequelize.DATE,
|
||||
updatedAt: Sequelize.DATE,
|
||||
|
||||
file: Sequelize.BLOB('long'),
|
||||
TeamId: Sequelize.BIGINT
|
||||
}, {
|
||||
charset: 'utf8mb4'
|
||||
})
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.dropTable('profilepictures');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
module.exports = {
|
||||
up(queryInterface, Sequelize) {
|
||||
return Promise.all([
|
||||
queryInterface.createTable(
|
||||
'teamWalls',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
createdAt: Sequelize.DATE,
|
||||
updatedAt: Sequelize.DATE,
|
||||
|
||||
content: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
plainText: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false
|
||||
},
|
||||
postNumber: Sequelize.BIGINT,
|
||||
teamId: Sequelize.BIGINT,
|
||||
fromUserId: Sequelize.BIGINT,
|
||||
replyingToUsername: Sequelize.STRING,
|
||||
removed: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
},
|
||||
)
|
||||
]);
|
||||
},
|
||||
};
|
|
@ -16,7 +16,14 @@ let options = {
|
|||
}
|
||||
}
|
||||
if(env === 'production') {
|
||||
var sequelize = new Sequelize(process.env.DATABASE_URL, options)
|
||||
var sequelize = new Sequelize(
|
||||
config[env].database, config[env].username, config[env].password, {
|
||||
host: config[env].host,
|
||||
dialect: config[env].dialect,
|
||||
logging: false,
|
||||
...options
|
||||
}
|
||||
);
|
||||
} else {
|
||||
var sequelize = new Sequelize(
|
||||
config[env].database, config[env].username, config[env].password, {
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
let sharp = require('sharp')
|
||||
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
let ProfilePicture = sequelize.define('ProfilePicture', {
|
||||
let teamPicture = sequelize.define('teamPicture', {
|
||||
file: DataTypes.BLOB('long'),
|
||||
mimetype: DataTypes.STRING
|
||||
}, {
|
||||
classMethods: {
|
||||
associate (models) {
|
||||
ProfilePicture.belongsTo(models.User)
|
||||
teamPicture.belongsTo(models.Team)
|
||||
}
|
||||
},
|
||||
hooks: {
|
||||
beforeUpdate (profilePicture, options, cb) {
|
||||
sharp(profilePicture.file)
|
||||
.resize(300, 300, { fit: 'cover' })
|
||||
beforeUpdate (teamPicture, options, cb) {
|
||||
sharp(teamPicture.file)
|
||||
.resize(256, 256, { fit: 'cover' })
|
||||
.toBuffer((err, buff) => {
|
||||
profilePicture.file = buff
|
||||
teamPicture.file = buff
|
||||
|
||||
cb(err || null, options)
|
||||
})
|
||||
|
@ -23,5 +23,5 @@ module.exports = (sequelize, DataTypes) => {
|
|||
}
|
||||
})
|
||||
|
||||
return ProfilePicture
|
||||
return teamPicture
|
||||
}
|
|
@ -154,7 +154,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||
return meta
|
||||
},
|
||||
async getWallMeta (limit) {
|
||||
let Post = sequelize.models.userWall
|
||||
let Post = sequelize.models.teamWall
|
||||
let meta = {}
|
||||
|
||||
let nextId = await pagination.getNextIdDesc(Post, { userId: this.id }, this.Posts)
|
||||
|
@ -177,6 +177,9 @@ module.exports = (sequelize, DataTypes) => {
|
|||
}
|
||||
},
|
||||
classMethods: {
|
||||
associate (models) {
|
||||
Team.hasMany(models.teamWall)
|
||||
},
|
||||
includeOptions (from, limit) {
|
||||
let models = sequelize.models
|
||||
let options = models.Post.includeOptions()
|
||||
|
@ -193,7 +196,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||
let models = sequelize.models
|
||||
|
||||
return [{
|
||||
model: models.userWall,
|
||||
model: models.teamWall,
|
||||
limit,
|
||||
where: { postNumber: { $gte: from } },
|
||||
order: [['id', 'ASC']]
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
var md = require('../kaverti_modules/markdown-it')({
|
||||
html: false, // Enable HTML tags in source
|
||||
xhtmlOut: false, // Use '/' to close single tags (<br />).
|
||||
// This is only for full CommonMark compatibility.
|
||||
breaks: true, // Convert '\n' in paragraphs into <br>
|
||||
langPrefix: 'language-', // CSS language prefix for fenced blocks. Can be
|
||||
// useful for external highlighters.
|
||||
linkify: true, // Autoconvert URL-like text to links
|
||||
image: false,
|
||||
|
||||
// Enable some language-neutral replacement + quotes beautification
|
||||
typographer: true,
|
||||
|
||||
// Double + single quotes replacement pairs, when typographer enabled,
|
||||
// and smartquotes on. Could be either a String or an Array.
|
||||
//
|
||||
// For example, you can use '«»„“' for Russian, '„“‚‘' for German,
|
||||
// and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
|
||||
quotes: '“”‘’',
|
||||
|
||||
// Highlighter function. Should return escaped HTML,
|
||||
// or '' if the source string is not changed and should be escaped externally.
|
||||
// If result starts with <pre... internal wrapper is skipped.
|
||||
highlight: function (/*str, lang*/) { return ''; }
|
||||
});
|
||||
md.disable('image')
|
||||
var emoji = require('markdown-it-emoji');
|
||||
var twemoji = require('twemoji')
|
||||
md.use(emoji);
|
||||
md.renderer.rules.emoji = function(token, idx) {
|
||||
return twemoji.parse(token[idx].content);
|
||||
};
|
||||
let createDOMPurify = require('dompurify');
|
||||
let { JSDOM } = require('jsdom');
|
||||
|
||||
let window = new JSDOM('').window;
|
||||
let DOMPurify = createDOMPurify(window);
|
||||
var escaped_str = require('querystring')
|
||||
const Errors = require('../lib/errors')
|
||||
let pagination = require('../lib/pagination.js')
|
||||
|
||||
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
let teamWall = sequelize.define('teamWall', {
|
||||
content: {
|
||||
type: DataTypes.TEXT,
|
||||
set (val) {
|
||||
if(!val) {
|
||||
throw Errors.sequelizeValidation(sequelize, {
|
||||
error: 'content must be a string',
|
||||
path: 'content'
|
||||
})
|
||||
}
|
||||
|
||||
let rawHTML = md.render(val);
|
||||
let cleanHTML = DOMPurify.sanitize(rawHTML);
|
||||
let plainText = (new JSDOM(cleanHTML)).window.document.body.textContent;
|
||||
|
||||
if (!plainText.trim().length) {
|
||||
throw Errors.sequelizeValidation(sequelize, {
|
||||
error: 'Post content must not be empty',
|
||||
path: 'content'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
this.setDataValue('content', cleanHTML)
|
||||
this.setDataValue('plainText', plainText)
|
||||
},
|
||||
allowNull: false
|
||||
},
|
||||
plainText: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
postNumber: DataTypes.INTEGER,
|
||||
replyingToUsername: DataTypes.STRING,
|
||||
teamId: DataTypes.BIGINT,
|
||||
removed: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
}
|
||||
}, {
|
||||
instanceMethods: {
|
||||
async getMeta(limit) {
|
||||
let Post = sequelize.models.userWall
|
||||
let meta = {}
|
||||
|
||||
let nextId = await pagination.getNextIdDesc(userWall, {fromUserId: this.id}, this.Posts)
|
||||
|
||||
if (nextId === null) {
|
||||
meta.nextURL = null
|
||||
meta.nextPostsCount = 0
|
||||
} else {
|
||||
meta.nextURL =
|
||||
process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `wall/show/${this.username}?posts=true&limit=${limit}&from=${nextId - 1}`
|
||||
|
||||
meta.nextPostsCount = await pagination.getNextCount(
|
||||
Post, this.Posts, limit,
|
||||
{fromUserId: this.id},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
},
|
||||
classMethods: {
|
||||
associate (models) {
|
||||
teamWall.belongsTo(models.User, { as: 'fromUser' })
|
||||
},
|
||||
includeOptions () {
|
||||
let models = sequelize.models
|
||||
|
||||
return [
|
||||
{ model: models.User, as: 'fromUser', attributes: ['username', 'createdAt', 'id', 'color', 'picture'] },
|
||||
]
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return teamWall
|
||||
}
|
|
@ -66,7 +66,7 @@
|
|||
"sendgrid": "^5.2.3",
|
||||
"sequelize": "3.35.1",
|
||||
"sequelize-cli": "^4.1.1",
|
||||
"sharp": "^0.25.4",
|
||||
"sharp": "^0.26.2",
|
||||
"simple-rate-limit": "^1.0.0",
|
||||
"socket.io": "^2.1.1",
|
||||
"speakeasy": "^2.0.0",
|
||||
|
|
186
routes/team.js
186
routes/team.js
|
@ -41,7 +41,7 @@ var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
|
|||
const Errors = require('../lib/errors.js')
|
||||
var format = require('date-format');
|
||||
let {
|
||||
User, Post, ProfilePicture, userWall, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize, Team, TeamMembers, TeamRoles
|
||||
User, Post, teamWall, teamPicture, userWall, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize, Team, TeamMembers, TeamRoles
|
||||
} = require('../models')
|
||||
let pagination = require('../lib/pagination.js')
|
||||
const sgMail = require('@sendgrid/mail');
|
||||
|
@ -106,102 +106,70 @@ router.post('/create', emailLimiter, auth, async(req, res, next) => {
|
|||
res.json(team.toJSON())
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
router.post('/job-application', auth, async(req, res, next) => {
|
||||
try {
|
||||
let userParams = {
|
||||
username: req.body.username,
|
||||
dob: req.body.dob,
|
||||
email: req.body.email,
|
||||
whyWork: req.body.whyWork,
|
||||
otherForm: req.body.otherForm,
|
||||
experience: req.body.experience,
|
||||
selectedOption: req.body.selectedOption,
|
||||
}
|
||||
await StaffApplications.submitApplication(userParams)
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.get('/view/:username', async(req, res, next) => {
|
||||
try {
|
||||
let queryObj = {
|
||||
attributes: {exclude: ['banReason']},
|
||||
where: {username: req.params.username}
|
||||
}
|
||||
let team = await Team.findOne(queryObj)
|
||||
if(!team) throw Errors.accountDoesNotExist
|
||||
|
||||
res.json(team.toJSON())
|
||||
|
||||
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/login', async(req, res, next) => {
|
||||
try {
|
||||
await Ban.isIpBanned(req.ip, req.body.email)
|
||||
|
||||
let user = await User.findOne({ where: {
|
||||
username: req.body.username
|
||||
}})
|
||||
if(user) {
|
||||
if(await user.comparePassword(req.body.password)) {
|
||||
await Ip.createIfNotExists(req.ip, user)
|
||||
|
||||
setUserSession(req, res, user.username, user.id, user.admin)
|
||||
res.json({
|
||||
username: user.username,
|
||||
admin: user.admin,
|
||||
success: true
|
||||
})
|
||||
} else {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.invalidLoginCredentials]
|
||||
})
|
||||
if(req.query.wall) {
|
||||
let {from, limit} = pagination.getPaginationProps(req.query, true)
|
||||
let postInclude = {
|
||||
model: teamWall,
|
||||
include: teamWall.includeOptions(),
|
||||
limit,
|
||||
order: [['id', 'DESC']],
|
||||
}
|
||||
if (from !== null) {
|
||||
postInclude.where = {id: {$lte: from}}
|
||||
}
|
||||
queryObj.include = [postInclude]
|
||||
|
||||
let user = await Team.findOne(queryObj)
|
||||
if (!user) throw Errors.accountDoesNotExist
|
||||
if (user.userWallOptOut) {
|
||||
throw Errors.userWallOptOut
|
||||
}
|
||||
|
||||
let meta = await user.getMeta(limit)
|
||||
res.json(Object.assign(user.toJSON(limit), {meta}))
|
||||
} else {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.invalidLoginCredentials]
|
||||
})
|
||||
let team = await Team.findOne(queryObj)
|
||||
if (!team) throw Errors.accountDoesNotExist
|
||||
|
||||
res.json(team.toJSON())
|
||||
}
|
||||
|
||||
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/:username/logout', auth, async(req, res) => {
|
||||
req.userData.destroy(() => {
|
||||
res.clearCookie('username')
|
||||
res.clearCookie('admin')
|
||||
res.json({
|
||||
success: true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
router.get('/:username/picture', async(req, res, next) => {
|
||||
router.get('/view/:username/picture', async (req, res, next) => {
|
||||
try {
|
||||
let user = await User.findOne({
|
||||
let user = await Team.findOne({
|
||||
where: {
|
||||
username: req.params.username
|
||||
}
|
||||
})
|
||||
if(!user) throw Errors.accountDoesNotExist
|
||||
|
||||
let picture = await ProfilePicture.findOne({
|
||||
let picture = await teamPicture.findOne({
|
||||
where: {
|
||||
UserId: user.id
|
||||
TeamId: user.id
|
||||
}
|
||||
})
|
||||
|
||||
if(!picture) {
|
||||
res.status(404)
|
||||
res.end('')
|
||||
res.json({picture: "https://cdn.kaverti.com/teams/unknown-light.png"})
|
||||
} else {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': picture.mimetype,
|
||||
'Content-disposition': 'attachment;filename=profile',
|
||||
'Content-disposition': 'attachment;filename=kaverti-team-profile-picture',
|
||||
'Content-Length': picture.file.length
|
||||
})
|
||||
res.end(new Buffer(picture.file, 'binary'))
|
||||
res.end(new Buffer.from(picture.file, 'binary'))
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
@ -211,18 +179,10 @@ router.get('/', async(req, res, next) => {
|
|||
let sortFields = {
|
||||
createdAt: 'X.id',
|
||||
username: 'X.username',
|
||||
threadCount: 'threadCount',
|
||||
postCount: 'postCount'
|
||||
};
|
||||
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
|
||||
let havingClause = '';
|
||||
if(req.query.role === 'admin') {
|
||||
havingClause = 'HAVING Users.admin = true';
|
||||
} else if(req.query.role === 'user') {
|
||||
havingClause = 'HAVING Users.admin = false';
|
||||
} else {
|
||||
havingClause = '';
|
||||
}
|
||||
|
||||
if(req.query.search) {
|
||||
//I.e. if there is not already a HAVING clause
|
||||
if(!havingClause.length) {
|
||||
|
@ -230,89 +190,27 @@ router.get('/', async(req, res, next) => {
|
|||
} else {
|
||||
havingClause += ' AND ';
|
||||
}
|
||||
havingClause += 'Users.username LIKE $search';
|
||||
havingClause += 'Team.username LIKE $search';
|
||||
}
|
||||
let sql = `
|
||||
SELECT X.username, X.admin, X.level, X.levelProgress, X.bot, X.booster, X.description, X.bodyColor, X.headColor, X.leftLegColor, X.rightLegColor, X.leftArmColor, X.rightArmColor, X.hidden, X.system, X.createdAt, X.contributor, X.postCount, COUNT(Threads.id) as threadCount
|
||||
SELECT X.username, X.approved, X.name, X.picture, X.id, X.forumEnabled, X.description, X.banned, X.createdAt, X.updatedAt
|
||||
FROM (
|
||||
SELECT Users.*, COUNT(Posts.id) as postCount
|
||||
FROM Users
|
||||
LEFT OUTER JOIN Posts
|
||||
ON Users.id = Posts.UserId
|
||||
GROUP BY Users.id
|
||||
SELECT Teams.*
|
||||
FROM Teams
|
||||
GROUP BY Teams.id
|
||||
${havingClause}
|
||||
) as X
|
||||
LEFT OUTER JOIN threads
|
||||
ON X.id = Threads.UserId
|
||||
GROUP BY X.id
|
||||
ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'DESC' : 'ASC'}
|
||||
LIMIT 2222
|
||||
OFFSET ${offset}
|
||||
`;
|
||||
let users = await sequelize.query(sql, {
|
||||
model: User,
|
||||
model: Team,
|
||||
bind: { search: req.query.search + '%' }
|
||||
});
|
||||
res.json(users)
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.all('*', auth, (req, res, next) => {
|
||||
if(req.userData.username) {
|
||||
next()
|
||||
} else {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.requestNotAuthorized]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.put('/:username', auth, async(req, res, next) => {
|
||||
try {
|
||||
if(req.userData.username !== req.params.username) {
|
||||
throw Errors.requestNotAuthorized
|
||||
}
|
||||
await Ban.ReadOnlyMode(req.userData.username)
|
||||
|
||||
if(req.autosan.body.description !== undefined) {
|
||||
let user = await User.update({ description: req.autosan.body.description }, { where: {
|
||||
username: req.userData.username
|
||||
}})
|
||||
|
||||
res.json({ success: true })
|
||||
|
||||
} else if(
|
||||
req.body.currentPassword !== undefined &&
|
||||
req.body.newPassword !== undefined
|
||||
) {
|
||||
let user = await User.findOne({
|
||||
where: {
|
||||
username: req.userData.username
|
||||
}
|
||||
})
|
||||
|
||||
await user.updatePassword(req.body.currentPassword, req.body.newPassword)
|
||||
res.json({success: true})
|
||||
} else if(
|
||||
req.body.emailCurrentPassword !== undefined &&
|
||||
req.body.newEmail !== undefined
|
||||
) {
|
||||
let user = await User.findOne({where: {
|
||||
username: req.userData.username
|
||||
}})
|
||||
|
||||
await user.updateEmail(req.body.emailCurrentPassword, req.body.newEmail)
|
||||
res.json({ success: true })
|
||||
} else if(
|
||||
req.body.username !== undefined &&
|
||||
req.body.changeUsername === true
|
||||
) {
|
||||
console.log("yes")
|
||||
res.json({ success: true })
|
||||
} else {
|
||||
res.json({ success: false })
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
module.exports = router;
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
@swagger
|
||||
components:
|
||||
schemas:
|
||||
Book:
|
||||
type: object
|
||||
required:
|
||||
- title
|
||||
- author
|
||||
- finished
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: The auto-generated id of the book.
|
||||
title:
|
||||
type: string
|
||||
description: The title of your book.
|
||||
author:
|
||||
type: string
|
||||
description: Who wrote the book?
|
||||
finished:
|
||||
type: boolean
|
||||
description: Have you finished reading it?
|
||||
createdAt:
|
||||
type: string
|
||||
format: date
|
||||
description: The date of the record creation.
|
||||
example:
|
||||
title: The Pragmatic Programmer
|
||||
author: Andy Hunt / Dave Thomas
|
||||
finished: true
|
||||
*/
|
||||
let bcrypt = require('bcryptjs')
|
||||
let multer = require('multer')
|
||||
let express = require('express')
|
||||
let router = express.Router()
|
||||
const auth = require('../lib/auth')
|
||||
var Recaptcha = require('express-recaptcha').RecaptchaV3;
|
||||
var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd');
|
||||
var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
|
||||
const Errors = require('../lib/errors.js')
|
||||
var format = require('date-format');
|
||||
let {
|
||||
User, Post, teamPicture, userWall, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize, Team, TeamMembers, TeamRoles
|
||||
} = require('../models')
|
||||
let pagination = require('../lib/pagination.js')
|
||||
const sgMail = require('@sendgrid/mail');
|
||||
const MailGen = require('mailgen')
|
||||
const crypto = require("crypto")
|
||||
const cryptoRandomString = require("crypto-random-string")
|
||||
const rateLimit = require("express-rate-limit");
|
||||
let upload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
limits:{
|
||||
fileSize: 1024 * 1024
|
||||
}
|
||||
})
|
||||
|
||||
const emailLimiter = rateLimit({
|
||||
windowMs: 60000,
|
||||
max: 1, // limit each IP to 100 requests per windowMs
|
||||
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint per minute.\",\"status\":429}]}"
|
||||
});
|
||||
|
||||
router.post('/:username/picture', auth, upload.single('picture'), async (req, res, next) => {
|
||||
try {
|
||||
let user = await Team.findOne({
|
||||
where: {
|
||||
username: req.params.username
|
||||
}
|
||||
})
|
||||
let picture = await teamPicture.findOne({
|
||||
where: { TeamId: user.id }
|
||||
})
|
||||
|
||||
let pictureObj = {
|
||||
file: req.file.buffer,
|
||||
mimetype: req.file.mimetype,
|
||||
TeamId: user.id
|
||||
}
|
||||
|
||||
//No picture set yet
|
||||
if(!picture) {
|
||||
await teamPicture.create(pictureObj)
|
||||
} else {
|
||||
await picture.update(pictureObj)
|
||||
}
|
||||
|
||||
//Add random query to end to force browser to reload background images
|
||||
await user.update({
|
||||
picture: '/api/v1/teams/view/' + req.params.username + '/picture?rand=' + Date.now()
|
||||
})
|
||||
|
||||
res.json(user.toJSON())
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.put('/modify/:username', auth, async(req, res, next) => {
|
||||
try {
|
||||
if(!req.userData.username) {
|
||||
throw Errors.requestNotAuthorized
|
||||
}
|
||||
await Ban.ReadOnlyMode(req.userData.username)
|
||||
let user1 = await Team.findOne({ where: {
|
||||
username: req.params.username
|
||||
}})
|
||||
let user2 = await User.findOne({ where: {
|
||||
username: req.userData.username
|
||||
}})
|
||||
console.log(user1.OwnerId, user2.id)
|
||||
if(user1 && user2.id === user1.OwnerId) {
|
||||
if(req.autosan.body.description !== undefined, req.autosan.body.name !== undefined) {
|
||||
|
||||
let user = await Team.update({description: req.autosan.body.description, name: req.autosan.body.name}, {
|
||||
where: {
|
||||
username: req.params.username
|
||||
}
|
||||
})
|
||||
res.status(200)
|
||||
res.json({success: true})
|
||||
} else {
|
||||
res.status(400)
|
||||
res.json({success: "Not passed body test"})
|
||||
}
|
||||
} else {
|
||||
res.status(400)
|
||||
res.json({ success: "Not passed u1 and u2 test" })
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,140 @@
|
|||
let express = require('express')
|
||||
let router = express.Router()
|
||||
const auth = require('../lib/auth')
|
||||
|
||||
const Errors = require('../lib/errors')
|
||||
let { User, Team, teamWall, Notification, Ban, Sequelize, sequelize } = require('../models')
|
||||
let pagination = require('../lib/pagination.js')
|
||||
const rateLimit = require("express-rate-limit");
|
||||
const postLimiter = rateLimit({
|
||||
windowMs: 60000,
|
||||
max: 10,
|
||||
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 10 requests to this endpoint per minute.\",\"status\":429}]}"
|
||||
});
|
||||
router.get('/show/:username', async(req, res, next) => {
|
||||
try {
|
||||
let { limit } = pagination.getPaginationProps(req.query, true)
|
||||
|
||||
let postInclude = {
|
||||
model: userWall,
|
||||
limit,
|
||||
order: [['id', 'DESC']]
|
||||
}
|
||||
|
||||
let user = await teamWall.findOne(postInclude)
|
||||
if (!user) throw Errors.accountDoesNotExist
|
||||
|
||||
let meta = await user.getMeta(limit)
|
||||
let Posts = await teamWall.find(postInclude)
|
||||
|
||||
res.json(Object.assign( user.toJSON(limit), { meta, Posts } )) } catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.all('*', auth, (req, res, next) => {
|
||||
if(req.userData.loggedIn) {
|
||||
next()
|
||||
} else {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.requestNotAuthorized]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/post', postLimiter, auth, async(req, res, next) => {
|
||||
let queryObj = {
|
||||
attributes: {include: ['emailVerified']},
|
||||
where: {username: req.userData.username}
|
||||
}
|
||||
let getSessionId = {
|
||||
attributes: {include: ['id']},
|
||||
where: {username: req.userData.username}
|
||||
}
|
||||
let teamToId = {
|
||||
attributes: {include: ['id']},
|
||||
where: {username: req.body.username}
|
||||
}
|
||||
let user = await User.findOne(queryObj)
|
||||
let sessionId = await User.findOne(getSessionId)
|
||||
let getWallUser = await Team.findOne(teamToId)
|
||||
try {
|
||||
//Will throw an error if banned
|
||||
await Ban.ReadOnlyMode(req.userData.username)
|
||||
|
||||
if (req.body.mentions) {
|
||||
uniqueMentions = Notification.filterMentions(req.body.mentions)
|
||||
}
|
||||
|
||||
if (!user.emailVerified) {
|
||||
throw Errors.verifyEmail
|
||||
}
|
||||
|
||||
if(getWallUser.userWallOptOut) {
|
||||
throw Errors.userWallOptOut
|
||||
}
|
||||
|
||||
if(teamToId.id == "null") throw Errors.sequelizeValidation(Sequelize, {
|
||||
error: 'User doesn\'t exist',
|
||||
path: 'id'
|
||||
})
|
||||
|
||||
user = await teamWall.findOne({ where: {
|
||||
fromUserId: sessionId.id
|
||||
}})
|
||||
|
||||
post = await teamWall.create({content: req.body.content, postNumber: "0", teamId: getWallUser.id, fromUserId: req.userData.UserId})
|
||||
|
||||
if (uniqueMentions.length) {
|
||||
let ioUsers = req.app.get('io-users')
|
||||
let io = req.app.get('io')
|
||||
|
||||
for (const mention of uniqueMentions) {
|
||||
let mentionNotification = await Notification.createPostNotification({
|
||||
usernameTo: mention,
|
||||
userFrom: user,
|
||||
type: 'mention',
|
||||
post
|
||||
})
|
||||
|
||||
if (mentionNotification) {
|
||||
await mentionNotification.emitNotificationMessage(ioUsers, io)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({success: true})
|
||||
|
||||
} catch (e) {
|
||||
next(e)
|
||||
}
|
||||
})
|
||||
router.all('*', auth, (req, res, next) => {
|
||||
if(!req.userData.admin) {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.requestNotAuthorized]
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/:post_id', auth, async(req, res, next) => {
|
||||
try {
|
||||
if(!req.userData.admin){
|
||||
res.status(401)
|
||||
res.json({errors: [Errors.requestNotAuthorized]})
|
||||
}
|
||||
let post = await userWall.findById(req.params.post_id)
|
||||
if(!post) throw Errors.sequelizeValidation(Sequelize, {
|
||||
error: 'post does not exist',
|
||||
path: 'id'
|
||||
})
|
||||
|
||||
await post.update({ content: '[This post has been removed by an administrator]', removed: true })
|
||||
|
||||
res.json({ success: true })
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
module.exports = router
|
|
@ -112,6 +112,8 @@ app.use('/api/v1/chat/message', require('./routes/message'));
|
|||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs, { explorer: true }));
|
||||
app.use(require('./lib/errorHandler'))
|
||||
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(profanity.init);
|
||||
app.set('trust proxy', true)
|
||||
function main () {
|
||||
|
|
90
yarn.lock
90
yarn.lock
|
@ -696,11 +696,6 @@ chownr@^1.1.1:
|
|||
resolved "https://npm.open-registry.dev/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||
|
||||
chownr@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
|
||||
|
||||
cli-color@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://npm.open-registry.dev/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f"
|
||||
|
@ -1896,13 +1891,6 @@ fs-extra@^8.0.1:
|
|||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-minipass@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
|
||||
integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
|
||||
dependencies:
|
||||
minipass "^3.0.0"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://npm.open-registry.dev/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
|
@ -3069,22 +3057,7 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5:
|
|||
resolved "https://npm.open-registry.dev/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
minipass@^3.0.0:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
|
||||
integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minizlib@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3"
|
||||
integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==
|
||||
dependencies:
|
||||
minipass "^3.0.0"
|
||||
yallist "^4.0.0"
|
||||
|
||||
mkdirp-classic@^0.5.2:
|
||||
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://npm.open-registry.dev/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
@ -3103,7 +3076,7 @@ mkdirp@^0.5.1:
|
|||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
mkdirp@^1.0.3, mkdirp@~1.0.3:
|
||||
mkdirp@~1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://npm.open-registry.dev/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
@ -3325,10 +3298,10 @@ node-abi@^2.7.0:
|
|||
dependencies:
|
||||
semver "^5.4.1"
|
||||
|
||||
node-addon-api@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.0.tgz#812446a1001a54f71663bed188314bba07e09247"
|
||||
integrity sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==
|
||||
node-addon-api@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681"
|
||||
integrity sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==
|
||||
|
||||
node-email-verification@^0.0.0:
|
||||
version "0.0.0"
|
||||
|
@ -3796,16 +3769,16 @@ postgresql@^0.0.1:
|
|||
dependencies:
|
||||
empty-dir "^0.1.0"
|
||||
|
||||
prebuild-install@^5.3.4:
|
||||
version "5.3.5"
|
||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.5.tgz#e7e71e425298785ea9d22d4f958dbaccf8bb0e1b"
|
||||
integrity sha512-YmMO7dph9CYKi5IR/BzjOJlRzpxGGVo1EsLSUZ0mt/Mq0HWZIHOKHHcHdT69yG54C9m6i45GpItwRHpk0Py7Uw==
|
||||
prebuild-install@^5.3.5:
|
||||
version "5.3.6"
|
||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.6.tgz#7c225568d864c71d89d07f8796042733a3f54291"
|
||||
integrity sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==
|
||||
dependencies:
|
||||
detect-libc "^1.0.3"
|
||||
expand-template "^2.0.3"
|
||||
github-from-package "0.0.0"
|
||||
minimist "^1.2.3"
|
||||
mkdirp "^0.5.1"
|
||||
mkdirp-classic "^0.5.3"
|
||||
napi-build-utils "^1.0.1"
|
||||
node-abi "^2.7.0"
|
||||
noop-logger "^0.1.1"
|
||||
|
@ -4351,19 +4324,19 @@ setprototypeof@1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
|
||||
|
||||
sharp@^0.25.4:
|
||||
version "0.25.4"
|
||||
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.25.4.tgz#1a8e542144a07ab7e9316ab89de80182b827c363"
|
||||
integrity sha512-umSzJJ1oBwIOfwFFt/fJ7JgCva9FvrEU2cbbm7u/3hSDZhXvkME8WE5qpaJqLIe2Har5msF5UG4CzYlEg5o3BQ==
|
||||
sharp@^0.26.2:
|
||||
version "0.26.2"
|
||||
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.26.2.tgz#3d5777d246ae32890afe82a783c1cbb98456a88c"
|
||||
integrity sha512-bGBPCxRAvdK9bX5HokqEYma4j/Q5+w8Nrmb2/sfgQCLEUx/HblcpmOfp59obL3+knIKnOhyKmDb4tEOhvFlp6Q==
|
||||
dependencies:
|
||||
color "^3.1.2"
|
||||
detect-libc "^1.0.3"
|
||||
node-addon-api "^3.0.0"
|
||||
node-addon-api "^3.0.2"
|
||||
npmlog "^4.1.2"
|
||||
prebuild-install "^5.3.4"
|
||||
prebuild-install "^5.3.5"
|
||||
semver "^7.3.2"
|
||||
simple-get "^4.0.0"
|
||||
tar "^6.0.2"
|
||||
tar-fs "^2.1.0"
|
||||
tunnel-agent "^0.6.0"
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
|
@ -4851,6 +4824,16 @@ tar-fs@^2.0.0:
|
|||
pump "^3.0.0"
|
||||
tar-stream "^2.0.0"
|
||||
|
||||
tar-fs@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5"
|
||||
integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==
|
||||
dependencies:
|
||||
chownr "^1.1.1"
|
||||
mkdirp-classic "^0.5.2"
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.0.0"
|
||||
|
||||
tar-stream@^2.0.0:
|
||||
version "2.1.2"
|
||||
resolved "https://npm.open-registry.dev/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325"
|
||||
|
@ -4862,18 +4845,6 @@ tar-stream@^2.0.0:
|
|||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39"
|
||||
integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==
|
||||
dependencies:
|
||||
chownr "^2.0.0"
|
||||
fs-minipass "^2.0.0"
|
||||
minipass "^3.0.0"
|
||||
minizlib "^2.1.0"
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
terraformer-wkt-parser@^1.1.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.1.tgz#8041e2aeb0c9f2b4cbbec8ec2c5c00c45ddfee02"
|
||||
|
@ -5343,11 +5314,6 @@ yallist@^3.0.2:
|
|||
resolved "https://npm.open-registry.dev/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yargs-parser@^13.1.2:
|
||||
version "13.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
|
||||
|
|
Loading…
Reference in New Issue