Transaction, Inventory, Marketplace complete

This commit is contained in:
Troplo 2020-11-18 20:32:21 +11:00
parent e94a89b160
commit 7012da56b8
36 changed files with 2158 additions and 423 deletions

View File

@ -660,7 +660,7 @@ blockquote {
</div>
</section>
<b-navbar>
<template slot="brand" v-if="!$store.state.username">
<template slot="brand">
<b-navbar-item tag="router-link" :to="{ path: '/' }">
<img
:src = $store.state.meta.icon
@ -668,14 +668,6 @@ blockquote {
<h2 style="padding-left: 3px;">{{name}}</h2>
</b-navbar-item>
</template>
<template slot="brand" v-if="$store.state.username">
<b-navbar-item tag="router-link" :to="{ path: '/dashboard' }">
<img
:src = $store.state.meta.icon
>
<h2 style="padding-left: 3px;">{{name}}</h2>
</b-navbar-item>
</template>
<template slot="start">
<b-navbar-item tag="router-link" :to="{ path: '/forums' }"><b>
Forums
@ -734,6 +726,8 @@ blockquote {
<a class="navbar-item is-hidden-mobile" v-if="$store.state.experimentsStore.newsettings" @click="settingsModal = true">Settings</a>
<router-link class="navbar-item is-hidden-desktop is-hidden-tablet" to='/settings' v-if="$store.state.experimentsStore.newsettings">Settings</router-link>
<router-link class="navbar-item " to='/settings' v-if="!$store.state.experimentsStore.newsettings">Settings</router-link>
<router-link to='/inventory' class="navbar-item">Inventory</router-link>
<router-link to='/transactions' class="navbar-item">Transactions</router-link>
<router-link class="navbar-item is-hidden-desktop" to='/notifications'>Notifications</router-link>
<router-link to='/admin' class="navbar-item" v-if='$store.state.admin'>Admin Panel</router-link>
<router-link class="navbar-item is-active" to='/premium'>Upgrade</router-link>

View File

@ -0,0 +1,124 @@
<template>
<div
class='item'
>
<div class="column">
<div class="box">
<div class="card-content">
<div class="media">
<div class="media-content">
<p class="title is-4"><router-link :to="'/m/' + post.id">{{post.name}}</router-link></p>
<p class="subtitle is-6">Created by <router-link :to="'/u/' + post.User.username"> @{{post.User.username}}</router-link></p>
</div>
</div>
<div class="content limit">
{{post.description | truncate(70)}}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ThreadPost',
props: [
'post',
'highlight',
'showReply',
'showThread',
'showSelect',
'clickForPost',
'allowQuote'
],
data () {
let post = this.post
return {
hover: false,
showShareModal: false,
showReportPostModal: false,
postURL: `${location.origin}/p/${post.id}`,
selected: false,
showQuote: false,
quoteX: 0,
quoteY: 0,
quoteSelection: '',
postContentHTML: post.content
}
},
computed: {
username () {
if(this.post.User) {
return this.post.User.username
} else {
return '[deleted]'}
},
showActions () {
return this.hover || this.showShareModal || this.showReportPostModal
}
},
methods: {
emitReply () {
this.showQuote = false;
this.$emit('reply', this.post.id, this.username, this.quoteSelection);
},
setShowQuote () {
let rootCoords = this.$el.getBoundingClientRect();
let selection = window.getSelection();
let coords = selection.getRangeAt(0).getBoundingClientRect();
let text = selection.toString();
if(text.length) {
this.quoteY = coords.top - rootCoords.top - 30;
this.quoteX = coords.left - rootCoords.left;
this.quoteSelection = '> ' + text.replace(/\n/g, '\n> ') + '\n\n';
this.showQuote = true;
} else {
this.showQuote = false;
}
},
setShareModalState (val) {
this.showShareModal = val
},
setShowReportPostModal (val) {
this.showReportPostModal = val
},
goToThread () {
this.$router.push(`/thread/${this.post.Thread.slug}/${this.post.Thread.id}`)
},
goToPost () {
if(this.clickForPost) {
this.$router.push(
'/thread/' +
this.post.Thread.slug + '/' +
this.post.Thread.id + '/' +
this.post.postNumber
)
}
},
toggleSelected () {
this.selected = !this.selected
this.$emit('selected', this.post.id)
}
},
watch: {
showSelect () {
if(this.selected) {
this.$emit('selected', this.post.id)
}
this.selected = false
}
},
mounted () {
this.$linkExpander(this.post.content, v => this.postContentHTML = v);
}
}
</script>

View File

@ -64,6 +64,11 @@
title: 'Audit Logs',
route: 'logs',
icon: 'edit'
},
{
title: 'Marketplace',
route: 'marketplace',
icon: 'shopping-cart'
},
]
}

View File

@ -1,249 +1,184 @@
<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%);
}
.limit{
margin-top: 0.5rem;
word-break: break-all;
}
</style>
<template>
<div class='admin_users' ref='scrollElement'>
<h1 class='admin_users__header'>Audit Logs</h1>
<div class='category_widget__box'>
<h2>Search</h2>
<div class='admin_users__filters'>
<b-input
:large='false'
v-model='search'
placeholder="Search for a user"
></b-input>
&nbsp;
<main>
<h1>Audit Logs</h1>
<div class="section">
<div class="">
<b-table
:data="users"
:paginated="isPaginated"
:per-page="perPage"
:current-page.sync="currentPage"
:pagination-simple="isPaginationSimple"
:pagination-position="paginationPosition"
:default-sort-direction="defaultSortDirection"
:pagination-rounded="isPaginationRounded"
:sort-icon="sortIcon"
:opened-detailed="defaultOpenedDetails"
:sort-icon-size="sortIconSize"
default-sort="user.first_name"
aria-next-label="Next page"
aria-previous-label="Previous page"
aria-page-label="Page"
aria-current-label="Current page">
<b-table-column field="Text" label="Action" sortable v-slot="props">
{{ props.row.action }}
</b-table-column>
<b-table-column field="date" label="Date" sortable centered v-slot="props">
<span class="tag is-success">
{{ new Date(props.row.createdAt).toLocaleDateString() }}
</span>
</b-table-column>
</b-table>
</div>
</div>
<scroll-load
class='category_widget__box'
@loadNext='fetchData'
:loading='loading'
query-selector='.admin_users'
:padding-bottom='100'
>
<table>
<tr>
<th>
<sort-menu v-model='tableSort' column='action' display='Action'></sort-menu>
</th>
<th>
<sort-menu v-model='tableSort' column='username' display='Username'></sort-menu>
</th>
<th>
<sort-menu v-model='tableSort' column='createdAt' display='Time/Date'></sort-menu>
</th>
</tr>
<tr v-for='user in users' :key='"user-row" + user.username' v-show="user && !user.hidden">
<td class='admin_users__user_column'>
Action
</td>
<td>
<router-link :to='"/user/" + user.username'>{{user.username}}</router-link>
</td>
<td>{{user.createdAt | formatDate}}</td>
<td>{{user.postCount}}</td>
<td>{{user.threadCount}}</td>
</tr>
</table>
<transition name='fade' mode='out-in'>
<loading-message key='loading' v-if='loading'></loading-message>
<div class='overlay_message' v-if='!loading && !users.length'>
No users found
</div>
</transition>
</scroll-load>
</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 Audit Log, check your internet connection, or check the <a href="https://status.troplo.com">Service Status</a>
</div></center></p>
</main>
</template>
<script>
import SortMenu from '../SortMenu.vue';
import LoadingMessage from '../LoadingMessage';
import ScrollLoad from '../ScrollLoad';
import LoadingMessage from '../LoadingMessage';
import throttle from 'lodash.throttle';
import AjaxErrorHandler from '../../assets/js/errorHandler';
import throttle from 'lodash.throttle';
import AjaxErrorHandler from '../../assets/js/errorHandler';
export default {
name: 'Transactions',
components: {
LoadingMessage
},
data () {
return {
search: '',
users: [],
isPaginated: true,
isPaginationSimple: false,
isPaginationRounded: false,
paginationPosition: 'bottom',
defaultSortDirection: 'asc',
sortIcon: 'arrow-up',
sortIconSize: 'is-small',
currentPage: 1,
perPage: 5,
defaultOpenedDetails: [1],
showDetailIcon: true,
export default {
name: 'UserList',
components: {
SortMenu,
LoadingMessage,
ScrollLoad
},
data () {
return {
search: '',
users: [],
showBadgeModal: false,
loading: true,
offset: 0,
limit: 15,
showTeamTab: 0,
tcreateProd: {
username: '',
name: '',
loading: true,
offset: 0,
limit: 15,
loading: false,
roleOptions: [
{ name: 'Admin', value: 'admin' },
{ name: 'Booster', value: 'booster' },
{ name: 'System', value: 'system' },
{ name: 'Bot', value: 'bot' }
],
roleSelected: ['admin', 'booster', 'system', 'bot'],
tableSort: {
column: 'username',
sort: 'desc'
},
user: {
errors: {
username: '',
booster: '',
bot: '',
system: '',
admin: ''
},
}
},
methods: {
recaptcha() {
this.$recaptcha('login').then((token) => {
console.log(token) // Will print the token
})},
toggleBadgeModal (user) {
this.user.username = user.username
this.user.booster = user.booster
this.user.bot = user.bot
this.user.system = user.system
this.user.admin = user.admin
this.showBadgeModal = true
name: ''
}
},
modifyUser (user) {
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'admin/user/modify', {
username: user.username,
bot: user.bot,
system: user.system,
booster: user.booster
})
.then(() => {
this.showBadgeModal = false
this.resetFetchData()
})
.catch(e => {
AjaxErrorHandler(this.$store)(e, error => {
this.description.error = error.message
})
})
},
fetchData () {
if(this.offset === null) return;
let url = process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `admin/logs?
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 + `/` + `admin/logs?
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();
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;
});
},
mounted () {
resetFetchData () {
this.offset = 0;
this.users = [];
this.fetchData();
},
watch: {
tableSort: 'resetFetchData',
roleSelected: 'resetFetchData',
search: throttle(function () {
this.resetFetchData();
}, 200)
}
},
getNewerUsers () {
this.loadingNewer = true
this.axios
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'admin/logs' + '?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>
<style lang='scss' scoped>
@import '../../assets/scss/variables.scss';
.admin_users {
padding: 1rem 2rem;
@at-root #{&}__header {
margin: 0.5rem 0 1rem 0;
}
@at-root #{&}__filters {
margin-bottom: 0.5rem;
.select_filter {
margin-right: 0.5rem;
}
}
table {
border-collapse: collapse;
width: 100%;
th {
border-bottom: 0.125rem solid $color__gray--darker;
padding: 0.5rem 0.75rem;
text-align: left;
}
tr {
cursor: default;
&:first-child {
background-color: #fff;
}
&:nth-child(odd) {
background-color: lighten($color__gray--primary, 20%);
}
&:nth-child(even) {
background-color: $color__gray--primary;
}
}
td {
padding: 0.75rem;
}
}
@at-root #{&}__user_column {
display: flex;
align-items: center;
a {
margin: 0 0.25rem;
}
}
.overlay_message {
padding-top: 2rem;
padding-bottom: 1rem;
}
}
</style>

View File

@ -0,0 +1,266 @@
<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%);
}
.limit{
margin-top: 0.5rem;
word-break: break-all;
}
</style>
<template>
<main>
<modal-window v-model='banModal' style='z-index: 100' width='25rem' :no-padding='true' :is-error="true">
<div slot="header">
Deleting {{selectedTeam}}
</div>
<div slot='main' class="card-content">
<p style='margin: 1rem;'>You are deleting this item, are you sure you want to?</p>
</div>
<button
slot='footer'
class='button is-danger'
style='z-index: 100; width: 50%;'
@click='teamBan(selectedTeam)'
ref='ajaxErrorsModalButton'
>
Delete
</button>
<button
slot='footer'
class='button is-info'
style='z-index: 100; width: 20%; float: right;'
@click='banModal = false'
ref='ajaxErrorsModalButton'
>
Close
</button>
</modal-window>
<section v-if='$store.state.experimentsStore.teams' class="hero is-info">
<div class="hero-body" style="padding: 1rem 1rem !important;">
<div class="mobile-container">
<div class="container">
<p style="text-align: center;">Teams are currently in development, expect missing features.</p>
</div>
</div>
</div>
</section>
<div class="section">
<div class="">
<div class="column" v-if="$store.state.username">
<b-button @click="createTeamModal = true" 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.banned"><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'" width="128px" height="128px" :src="user.picture">
<p v-if="user.picture === 'default'">DEFAULT IMG</p>
</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 limit">
{{user.description | truncate(70)}}
</div>
<b-button @click="teamApprove(user.id)" class="is-info">Approve</b-button> <b-button @click="initialTeamBan(user.id)" class="is-danger">Delete item</b-button>
</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 ScrollLoad from '../ScrollLoad';
import ModalWindow from '../ModalWindow'
import throttle from 'lodash.throttle';
import AjaxErrorHandler from '../../assets/js/errorHandler';
export default {
name: 'AdminTeams',
components: {
LoadingMessage,
ScrollLoad,
ModalWindow
},
data () {
return {
search: '',
users: [],
loading: true,
offset: 0,
limit: 15,
showTeamTab: 0,
createTeamModal: false,
reason: "None provided",
banModal: false,
selectedTeam: "Unknown Item",
tcreateProd: {
username: '',
name: '',
loading: false,
errors: {
username: '',
name: ''
}
},
roleOptions: [
{ name: 'Admins', value: 'admin' },
{ name: 'Users', value: 'user' }
],
roleSelected: ['admin', 'user'],
tableSort: {
column: 'username',
sort: 'desc'
}
}
},
methods: {
teamApprove(id) {
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'admin/marketplace/approve', {
id: id,
approve: true,
delete: false
})
.then(() => {
this.showBadgeModal = false
this.resetFetchData()
})
.catch(e => {
AjaxErrorHandler(this.$store)(e, error => {
this.description.error = error.message
})
})
},
initialTeamBan(username) {
this.selectedTeam = username
this.banModal = true
},
teamBan(id) {
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'admin/marketplace/approve', {
id: id,
approve: false,
delete: true
})
.then(() => {
this.showBadgeModal = false
this.resetFetchData()
})
.catch(e => {
AjaxErrorHandler(this.$store)(e, error => {
this.description.error = error.message
})
})
},
clearTeamErrors() {
this.tcreateProd.errors.username = ''
this.tcreateProd.errors.name = ''
},
closeAccountModal() {
this.createTeamModal = false
},
fetchData () {
if(this.offset === null) return;
let url = process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `admin/marketplace/pending?
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 + '/' + 'admin/marketplace/pending' + '?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>

View File

@ -0,0 +1,266 @@
<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%);
}
.limit{
margin-top: 0.5rem;
word-break: break-all;
}
</style>
<template>
<main>
<modal-window v-model='banModal' style='z-index: 100' width='25rem' :no-padding='true' :is-error="true">
<div slot="header">
Banning {{selectedTeam}}
</div>
<div slot='main' class="card-content">
<p style='margin: 1rem;'>Please provide a ban reason in the input box below</p>
<b-input v-model="reason"></b-input>
</div>
<button
slot='footer'
class='button is-danger'
style='z-index: 100; width: 50%;'
@click='teamBan(selectedTeam)'
ref='ajaxErrorsModalButton'
>
Ban
</button>
<button
slot='footer'
class='button is-info'
style='z-index: 100; width: 20%; float: right;'
@click='banModal = false'
ref='ajaxErrorsModalButton'
>
Close
</button>
</modal-window>
<section v-if='$store.state.experimentsStore.teams' class="hero is-info">
<div class="hero-body" style="padding: 1rem 1rem !important;">
<div class="mobile-container">
<div class="container">
<p style="text-align: center;">Teams are currently in development, expect missing features.</p>
</div>
</div>
</div>
</section>
<div class="section">
<div class="">
<div class="column" v-if="$store.state.username">
<b-button @click="createTeamModal = true" 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.banned"><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'" width="128px" height="128px" :src="user.picture">
<p v-if="user.picture === 'default'">DEFAULT IMG</p>
</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 limit">
{{user.description | truncate(70)}}
</div>
<b-button @click="teamApprove(user.username)" class="is-info">Approve</b-button> <b-button @click="initialTeamBan(user.username)" class="is-danger">Ban Team</b-button>
</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 ScrollLoad from '../ScrollLoad';
import ModalWindow from '../ModalWindow'
import throttle from 'lodash.throttle';
import AjaxErrorHandler from '../../assets/js/errorHandler';
export default {
name: 'AdminTeams',
components: {
LoadingMessage,
ScrollLoad,
ModalWindow
},
data () {
return {
search: '',
users: [],
loading: true,
offset: 0,
limit: 15,
showTeamTab: 0,
createTeamModal: false,
reason: "None provided",
banModal: false,
selectedTeam: "Unknown Team",
tcreateProd: {
username: '',
name: '',
loading: false,
errors: {
username: '',
name: ''
}
},
roleOptions: [
{ name: 'Admins', value: 'admin' },
{ name: 'Users', value: 'user' }
],
roleSelected: ['admin', 'user'],
tableSort: {
column: 'username',
sort: 'desc'
}
}
},
methods: {
teamApprove(username) {
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'admin/teams/approve', {
username: username,
approve: true
})
.then(() => {
this.showBadgeModal = false
this.resetFetchData()
})
.catch(e => {
AjaxErrorHandler(this.$store)(e, error => {
this.description.error = error.message
})
})
},
initialTeamBan(username) {
this.selectedTeam = username
this.banModal = true
},
teamBan(username) {
this.axios
.put(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'admin/teams/approve', {
username: username,
approve: false,
reason: this.reason
})
.then(() => {
this.showBadgeModal = false
this.resetFetchData()
})
.catch(e => {
AjaxErrorHandler(this.$store)(e, error => {
this.description.error = error.message
})
})
},
clearTeamErrors() {
this.tcreateProd.errors.username = ''
this.tcreateProd.errors.name = ''
},
closeAccountModal() {
this.createTeamModal = false
},
fetchData () {
if(this.offset === null) return;
let url = process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `admin/teams/pending?
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 + '/' + 'admin/teams/pending' + '?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>

View File

@ -0,0 +1,487 @@
<template>
<main>
<section v-if="!$store.state.username" class="hero is-info is-link is-large is-fullheight-with-navbar" style="overflow: auto">
<div class="hero-body">
<div class="container has-text-centered">
<h1 class="title">
Kaverti
</h1>
<h2 class="subtitle">
Kaverti is a new 3D sandbox gaming platform, and avatar social website.
<br />
Kaverti is home to hundreds of users who enjoy using it
<br />
So why not sign up today!
</h2>
</div>
</div>
</section>
<section v-if="$store.state.username">
<div class="columns is-centered">
<div class="column is-3">
<div class="box">
<center>
<avatar-icon
:user='user'
></avatar-icon>
<h1>{{$store.state.username}}</h1>
<tooltips style="padding-left: 5px;">
<b-tooltip v-if='user && user.system' class="is-success" label="This user is a system user operated by administrators that mainly run API operations.">
<b-tag rounded class="is-success">&nbsp;SYSTEM&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<b-tooltip v-if='user && user.bot' class="is-info" label="This user is a bot account that can run automated API operations.">
<b-tag rounded class="is-info">&nbsp;BOT&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<b-tooltip v-if='user && user.admin' class="is-danger" label="User is an official Kaverti administrator.">
<b-tag class="is-danger" rounded>&nbsp;ADMIN&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<b-tooltip v-if='user && user.hidden' class="is-info" label="User is not discoverable in the user list.">
<b-tag rounded>&nbsp;HIDDEN&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<b-tooltip v-if='user && user.booster' class="is-light" label="User is boosting the Kaverti Discord server.">
<b-tag class="is-light" rounded>&nbsp;BOOSTER&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
</tooltips>
</center>
</div>
</div>
<div class='column is-5' :class='{ "user_posts--no_border_bottom": posts && !posts.length }'>
<div class="box">
<h2>Global Wall</h2>
<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>
</div>
</div>
</section>
</main>
</template>
<script>
import ScrollLoad from '../ScrollLoad'
import WallPost from '../WallPost'
import ThreadPostPlaceholder from '../ThreadPostPlaceholder'
import InputEditorCore from '../InputEditorCore'
import ErrorTooltip from '../ErrorTooltip'
import AvatarIcon from '../AvatarIcon'
import AjaxErrorHandler from '../../assets/js/errorHandler'
export default {
name: 'HomeAuthenticated',
props: ['username'],
components: {
WallPost,
ScrollLoad,
ThreadPostPlaceholder,
InputEditorCore,
ErrorTooltip,
AvatarIcon
},
data () {
return {
selectedCategory: this.$store.state.category.selectedCategory,
editor: '',
mentions: [],
name: '',
loading: false,
focusInput: false,
threads: null,
loadingThreads: false,
nextURL: '',
nextThreadsCount: 0,
posts: null,
user: null,
errors: {
content: '',
name: '',
pollQuestion: '',
pollAnswer: ''
},
showPoll: false,
pollQuestion: '',
newPollAnswer: '',
pollAnswers: []
}
},
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 + '/' + 'userinfo')
},
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 + `/` + `wall/post`, {
username: "GlobalWall",
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 + `/` + `user/GlobalWall?wall=true`)
.then(res => {
this.posts = res.data.userWalls
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', 'Dashboard')
this.axios
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `user/GlobalWall?wall=true`)
.then(res => {
this.loadingPosts = false
this.posts = res.data.userWalls
this.nextPostsCount = res.data.meta.nextPostsCount
})
this.axios
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `userinfo`)
.then(res => {
this.loadingPosts = false
this.user = res.data
this.nextPostsCount = res.data.meta.nextPostsCount
})
},
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>

View File

@ -1,20 +0,0 @@
<template>
<main>
<section class="hero is-info is-link is-large is-fullheight-with-navbar" style="overflow: auto">
<div class="hero-body">
<div class="container has-text-centered">
<h1 class="title">
Kaverti
</h1>
<h2 class="subtitle">
Kaverti is a new 3D sandbox gaming platform, and avatar social website.
<br />
Kaverti is home to hundreds of users who enjoy using it
<br />
So why not sign up today!
</h2>
</div>
</div>
</section>
</main>
</template>

View File

@ -0,0 +1,165 @@
<style>
.vertical-alt {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.limit{
margin-top: 0.5rem;
word-break: break-all;
}
</style>
<template>
<main>
<div class="section">
<div class="">
<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.name' v-show="user && !user.hidden"><div class="card">
<div class="card-content">
<div class="media-left">
<figure class="image is-128x128">
<img v-if="user.picture !== 'default' && user.approved" width="128px" height="128px" :src="user.Item.picture">
<img v-if="user.picture === 'default' && $store.state.theme === 'light' && user.Item.approved" width="128px" height="128px" src="https://cdn.kaverti.com/teams/unknown-light.png">
<img v-if="user.picture === 'default' && $store.state.theme === 'dark' && user.Item.approved" width="128px" height="128px" src="https://cdn.kaverti.com/teams/unknown-dark.png">
<img v-if="$store.state.theme === 'light' && !user.Item.approved" width="128px" height="128px" src="https://cdn.kaverti.com/teams/pending-light.png">
<img v-if="$store.state.theme === 'dark' && !user.Item.approved" width="128px" height="128px" src="https://cdn.kaverti.com/teams/pending-dark.png">
</figure>
</div>
<br>
<div class="media">
<div class="media-content">
<p class="title is-4"><router-link :to="'/m/' + user.Item.id">{{user.Item.name}}</router-link></p>
<p class="subtitle is-6">Created by <router-link :to="'/u/' + user.Item.User.username"> @{{user.Item.User.username}}</router-link></p>
</div>
</div>
<div class="content limit">
{{user.Item.description | truncate(70)}}
</div>
</div>
</div>
</div>
</scroll-load>
</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 Marketplace Items, check your internet connection, or check the <a href="https://status.troplo.com">Service Status</a> or you don't have any items.
</div></center></p>
</div>
</main>
</template>
<script>
import LoadingMessage from '../LoadingMessage';
import ScrollLoad from '../ScrollLoad';
import throttle from 'lodash.throttle';
import AjaxErrorHandler from '../../assets/js/errorHandler';
export default {
name: 'Inventory',
components: {
LoadingMessage,
ScrollLoad
},
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 + '/' + 'inventory' + `?
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 + '/' + 'inventory' + '?limit=' + this.newUsers)
.then(res => {
this.loadingNewer = false
this.newUsers = 0
this.threads.unshift(...res.data)
})
.catch((e) => {
this.loadingNewer = false
AjaxErrorHandler(this.$store)(e)
})
},
mounted () {
this.fetchData();
},
watch: {
tableSort: 'resetFetchData',
roleSelected: 'resetFetchData',
search: throttle(function () {
this.resetFetchData();
}, 200)
}
}
</script>

View File

@ -58,7 +58,7 @@
</div>
<div class="column is-7">
<p>
<span class="title is-bold limit">{{user.name}}</span><br>
<span class="title is-bold limit">{{user.name}}&nbsp;<b-tooltip v-if="user.banned" class="is-danger" label="Team is banned and cannot be interacted with."><b-tag v-if="user.banned" class="is-danger">BANNED</b-tag></b-tooltip></span><br>
<span class="subtitle limit">@{{user.username}}</span>
<br>
</p>

View File

@ -113,7 +113,7 @@
: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="column is-4" v-for='user in users' :key='"user-row" + user.username' v-show="user && !user.banned"><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">

View File

@ -0,0 +1,189 @@
<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%);
}
.limit{
margin-top: 0.5rem;
word-break: break-all;
}
</style>
<template>
<main>
<div class="section column">
<div class="box">
<h1>Transaction Log</h1>
<b-table
:data="users"
:paginated="isPaginated"
:per-page="perPage"
:current-page.sync="currentPage"
:pagination-simple="isPaginationSimple"
:pagination-position="paginationPosition"
:default-sort-direction="defaultSortDirection"
:pagination-rounded="isPaginationRounded"
:sort-icon="sortIcon"
:opened-detailed="defaultOpenedDetails"
:sort-icon-size="sortIconSize"
default-sort="user.first_name"
aria-next-label="Next page"
aria-previous-label="Previous page"
aria-page-label="Page"
aria-current-label="Current page">
<b-table-column field="Text" label="Message" sortable v-slot="props">
{{ props.row.text }}
</b-table-column>
<b-table-column field="user.last_name" label="Limited Edition" sortable v-slot="props">
{{ props.row.limited }}
</b-table-column>
<b-table-column field="date" label="Date" sortable centered v-slot="props">
<span class="tag is-success">
{{ new Date(props.row.createdAt).toLocaleDateString() }}
</span>
</b-table-column>
</b-table>
</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 Transaction Log, 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: 'Transactions',
components: {
LoadingMessage
},
data () {
return {
search: '',
users: [],
isPaginated: true,
isPaginationSimple: false,
isPaginationRounded: false,
paginationPosition: 'bottom',
defaultSortDirection: 'asc',
sortIcon: 'arrow-up',
sortIconSize: 'is-small',
currentPage: 1,
perPage: 5,
defaultOpenedDetails: [1],
showDetailIcon: true,
loading: true,
offset: 0,
limit: 15,
showTeamTab: 0,
tcreateProd: {
username: '',
name: '',
loading: false,
errors: {
username: '',
name: ''
}
},
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 + `/` + `transactions?
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 + '/' + 'transactions' + '?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>

View File

@ -79,6 +79,9 @@
<b-button class="menu_button" :key='"user-menu-item-threads"' @click='$router.push(`/user/${username}/threads`)'>
Threads
</b-button>
<b-button class="menu_button" :key='"user-menu-item-threads"' @click='$router.push(`/user/${username}/items`)'>
Items
</b-button>
<br/> <br/>
<div class="column box">
<router-view :username='username'></router-view>

View File

@ -0,0 +1,5 @@
<template>
<main>
</main>
</template>

View File

@ -9,7 +9,7 @@
<scroll-load
:loading='loadingMarketplace'
@loadNext='loadNewmarketplace'
@loadNext='loadNewMarketplace'
v-else-if='marketplace.length'
>
<thread-post
@ -28,13 +28,13 @@
></thread-post-placeholder>
</template>
</scroll-load>
<template v-else>This user hasn't posted anything yet</template>
<template v-else>This user doesn't have any Marketplace Items yet</template>
</div>
</template>
<script>
import ScrollLoad from '../ScrollLoad'
import ThreadPost from '../ThreadPost'
import ThreadPost from '../MarketplaceItemView'
import ThreadPostPlaceholder from '../ThreadPostPlaceholder'
import AjaxErrorHandler from '../../assets/js/errorHandler'
@ -57,7 +57,7 @@
}
},
methods: {
loadNewmarketplace () {
loadNewMarketplace () {
if(this.nextURL === null) return
this.loadingmarketplace = true
@ -88,9 +88,9 @@
this.$store.dispatch('setTitle', this.$route.params.username + ' - Marketplace')
this.axios
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `marketplace/user/${this.$route.params.username}`)
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `user/${this.$route.params.username}?marketplace=true`)
.then(res => {
this.marketplace = res.data.Marketplace
this.marketplace = res.data.Items
this.nextURL = res.data.meta.nextURL
this.nextMarketplaceCount = res.data.meta.nextMarketplaceCount
})

View File

@ -57,7 +57,7 @@ Vue.component('font-awesome-icon', FontAwesomeIcon);
const Index = () => import('./components/routes/Index')
const CategorySelect = () => import('./components/routes/CategorySelect')
const HomeUnauthenticated = () => import('./components/routes/HomeUnauthenticated')
const Home = () => import('./components/routes/Home')
const HomeAuthenticated = () => import('./components/routes/HomeAuthenticated')
const P = () => import('./components/routes/P')
@ -84,6 +84,7 @@ const UserPosts = () => import('./components/routes/UserPosts')
const UserThreads = () => import('./components/routes/UserThreads')
const UserMarketplace = () => import('./components/routes/UserMarketplace')
const UserWall = () => import('./components/routes/UserWall')
const UserInventory = () => import('./components/routes/UserInventory')
const UsersList = () => import('./components/routes/UsersList')
const Notifications = () => import('./components/routes/Notifications')
const Banned = () => import('./components/routes/Banned')
@ -127,14 +128,19 @@ const AdminOther = () => import('./components/routes/AdminOther')
const AdminBadges = () => import('./components/routes/AdminAudit')
const AdminApplications = () => import('./components/routes/AdminApplications')
const AdminPrivileges = () => import('./components/routes/AdminPrivileges')
const AdminTeams = () => import('./components/routes/AdminTeams')
const AdminMarketplace = () => import('./components/routes/AdminMarketplace')
const Licenses = () => import('./components/routes/LICENSES')
const ConnectionProblems = () => import('./components/routes/ConnectionProblems')
const Contributors = () => import('./components/routes/Contributors')
const Inventory = () => import('./components/routes/Inventory')
const Marketplace = () => import('./components/routes/Marketplace')
const MarketplaceItem = () => import('./components/routes/MarketplaceItem')
const MarketplaceItemAuction = () => import('./components/routes/MarketplaceItemAuction')
const TransactionLog = () => import('./components/routes/TransactionLog')
import NotFound from './components/routes/NotFound'
import Reserved from './components/routes/Reserved'
@ -165,8 +171,8 @@ Vue.use({
const router = new VueRouter({
routes: [
{ path: '/', component: HomeUnauthenticated },
{ path: '/dashboard', component: HomeAuthenticated },
{ path: '/', component: Home },
{ path: '/dashboard', redirect: '/', component: HomeAuthenticated },
{ path: '/category/select', component: CategorySelect },
{ path: '/category/:category', component: Index },
{ path: '/p/:id', component: P },
@ -184,6 +190,7 @@ const router = new VueRouter({
{ path: '/thread/:slug/:id/:post_number', name: 'thread-post', component: Thread },
{ path: '/thread/new', component: ThreadNew },
{ path: '/marketplace', component: Marketplace },
{ path: '/transactions', component: TransactionLog },
{ path: '/m/:id', component: MarketplaceItem, children: [
{ path: 'auction', component: MarketplaceItemAuction }
] },
@ -238,6 +245,7 @@ const router = new VueRouter({
{ path: '/user/:username/items', component: User, redirect: '/u/:username/items' },
{ path: '/user/:username/wall', component: User, redirect: '/u/:username/wall' },
{ path: '/notifications', component: Notifications },
{ path: '/inventory', component: Inventory },
{
path: '/chat',
component: Chat,
@ -251,7 +259,8 @@ const router = new VueRouter({
{ path: 'posts', component: UserPosts },
{ path: 'threads', component: UserThreads },
{ path: 'items', component: UserMarketplace },
{ path: 'wall', component: UserWall }
{ path: 'wall', component: UserWall },
{ path: 'inventory', component: UserInventory }
] },
{ path: '/settings', redirect: '/settings/general', component: Settings, children: [
{ path: 'general', component: SettingsGeneral },
@ -273,6 +282,8 @@ const router = new VueRouter({
{ path: 'logs', component: AdminBadges },
{ path: 'applications', component: AdminApplications },
{ path: 'privileges', component: AdminPrivileges },
{ path: 'teams', component: AdminTeams },
{ path: 'marketplace', component: AdminMarketplace },
] },
{ path: '*', component: NotFound }
],

View File

@ -15,6 +15,14 @@ let Errors = {
'You may not perform this action while in Read-Only mode.',
400
],
teamBanned: [
'This team is banned and cannot be interacted or viewed.',
400
],
itemUnavailable: [
'This item is currently unavailable at this time.',
400
],
LoginRequired: [
'Due to the nature of this action, a login is required.',
401

View File

@ -0,0 +1,58 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('transactions', {
id: {
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true
},
itemCategoryId: {
type: Sequelize.BIGINT,
defaultValue: 1,
default: 1,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
},
text: {
type: Sequelize.TEXT,
allowNull: false,
},
UserId: {
type: Sequelize.BIGINT,
},
boughtFromId: {
type: Sequelize.TEXT,
},
limited: {
type: Sequelize.BOOLEAN,
defaultValue: 0,
default: 0
},
priceOfPurchase: {
type: Sequelize.BIGINT
},
team: {
type: Sequelize.BOOLEAN,
default: false
},
teamId: {
type: Sequelize.BIGINT,
default: 0
},
itemId: {
type: Sequelize.BIGINT
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('transactions');
}
};

View File

@ -0,0 +1,14 @@
module.exports = {
up(queryInterface, Sequelize) {
return Promise.all([
queryInterface.addColumn(
'transactions',
'ipId',
{
type: Sequelize.INTEGER,
allowNull: true
},
),
]);
},
};

View File

@ -0,0 +1,16 @@
module.exports = {
up(queryInterface, Sequelize) {
return Promise.all([
queryInterface.addColumn(
'items',
'deleted',
{
type: Sequelize.BOOLEAN,
allowNull: false,
default: false,
defaultValue: false
},
),
]);
},
};

16
models/auditlog.js Normal file
View File

@ -0,0 +1,16 @@
let bcrypt = require('bcryptjs')
let randomColor = require('randomcolor')
var passportLocalSequelize = require('passport-local-sequelize');
let pagination = require('../lib/pagination.js')
const Errors = require('../lib/errors.js')
var crypto = require("crypto");
var cryptoRandomString = require("crypto-random-string");
module.exports = (sequelize, DataTypes) => {
let AuditLog = sequelize.define('AuditLog', {
updatedAt: DataTypes.DATE,
createdAt: DataTypes.DATE,
action: DataTypes.TEXT
})
return AuditLog
}

View File

@ -58,9 +58,11 @@ module.exports = (sequelize, DataTypes) => {
}
}
}, {
associate (models) {
Inventory.belongsTo(models.User)
Inventory.belongsTo(models.Item, { as: 'User'})
classMethods: {
associate(models) {
Inventory.belongsTo(models.User, {through: 'User'})
Inventory.belongsTo(models.Item)
}
}
}
)

View File

@ -122,6 +122,11 @@ module.exports = (sequelize, DataTypes) => {
}
}
},
deleted: {
type: DataTypes.BOOLEAN,
default: false,
defaultValue: false
},
description: {
type: DataTypes.TEXT,
default: "No Marketplace item description provided"
@ -134,28 +139,6 @@ module.exports = (sequelize, DataTypes) => {
Item.belongsTo(models.ItemCategory)
Item.belongsTo(models.User, {through: 'User'})
},
includeOptions (from, limit) {
let models = sequelize.models
let options = models.Post.includeOptions()
return [{
model: models.Post,
include: options,
limit,
where: { postNumber: { $gte: from } },
order: [['id', 'ASC']]
}]
},
includeWallOptions (from, limit) {
let models = sequelize.models
return [{
model: models.userWall,
limit,
where: { postNumber: { $gte: from } },
order: [['id', 'ASC']]
}]
},
async canBeUser (passkey) {
let { User, PassKey } = sequelize.models

56
models/transaction.js Normal file
View File

@ -0,0 +1,56 @@
let randomColor = require('randomcolor')
module.exports = (sequelize, DataTypes) => {
let Transaction = sequelize.define('Transaction', {
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true
},
itemCategoryId: {
type: DataTypes.BIGINT,
defaultValue: 1,
default: 1,
allowNull: false
},
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
},
text: {
type: DataTypes.TEXT,
allowNull: false,
},
UserId: {
type: DataTypes.BIGINT,
},
boughtFromId: {
type: DataTypes.TEXT,
},
limited: {
type: DataTypes.BOOLEAN,
defaultValue: 0,
default: 0
},
priceOfPurchase: {
type: DataTypes.BIGINT
},
team: {
type: DataTypes.BOOLEAN,
default: false
},
teamId: {
type: DataTypes.BIGINT,
default: 0
},
itemId: {
type: DataTypes.BIGINT
}
})
return Transaction
}

View File

@ -429,7 +429,9 @@ module.exports = (sequelize, DataTypes) => {
User.hasMany(models.Thread)
User.hasMany(models.userWall)
User.hasMany(models.Inventory)
User.belongsToMany(models.Item, { through: 'UserId'})
User.hasMany(models.Transaction)
User.hasMany(models.AuditLog)
User.hasMany(models.Item)
User.belongsToMany(models.Conversation, { through: models.UserConversation })
User.belongsToMany(models.Ip, { through: 'UserIp' })
},
@ -445,6 +447,16 @@ module.exports = (sequelize, DataTypes) => {
order: [['id', 'ASC']]
}]
},
includeMarketplaceOptions (from, limit) {
let models = sequelize.models
return [{
model: models.Item,
limit,
where: { postNumber: { $gte: from } },
order: [['id', 'ASC']]
}]
},
includeWallOptions (from, limit) {
let models = sequelize.models

View File

@ -9,7 +9,7 @@ var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
const Errors = require('../lib/errors.js')
var format = require('date-format');
let {
User, Post, ProfilePicture, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
User, AuditLog, Team, Item, Post, ProfilePicture, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
} = require('../models')
let pagination = require('../lib/pagination.js')
@ -40,21 +40,25 @@ router.put('/user/scrub', auth, async(req, res, next) => {
username: req.autosan.body.user
}})
if(user.admin) {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' attempted to modify ' + req.autosan.body.user + ' but an error was thrown (Is admin, scrub description).'})
throw Errors.modifyAdminUser
}
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' modified user ' + req.autosan.body.user + ' and succeeded (scrub description).'})
let userUpdate = await User.update({ description: "Description was removed by an administrator"}, { where: {
username: req.autosan.body.user
}})
res.status(200)
res.json({success: "true"})
} else if(req.autosan.body.username === "usernamescram") {
} else if(req.body.username === "usernamescram") {
let user = await User.findOne({ where: {
username: req.autosan.body.user
}})
if(user.admin) {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' attempted to modify ' + req.autosan.body.user + ' but an error was thrown (Is admin, scrub username).'})
throw Errors.modifyAdminUser
}
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' modified user ' + req.autosan.body.user + ' and succeeded (scrub username).'})
let userUpdate = await User.update({username: Math.random().toString(36).substring(2)}, {
where: {
username: req.autosan.body.user
@ -62,6 +66,7 @@ router.put('/user/scrub', auth, async(req, res, next) => {
})
res.json({success: true})
} else {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' attempted to modify ' + req.autosan.body.user + ' but an error was thrown (unknown, scrub username).'})
res.json({ success: false })
}
} catch (e) { next(e) }
@ -83,13 +88,17 @@ router.put('/user/modify', auth, async(req, res, next) => {
username: req.userData.username
}})
if(!user) throw Errors.accountDoesNotExist
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' modified ' + req.body.username + ' and succeeded (changed roles).'})
if(user.admin && !user1.executive) {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' attempted to modify ' + req.body.username + ' but an error was thrown (Is admin, changed roles).'})
throw Errors.modifyAdminUser
}
if(user.executive) {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' attempted to modify ' + req.body.username + ' but an error was thrown (Is executive, changed roles).'})
throw Errors.modifyAdminUser
}
if(user1.executive) {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' modified ' + req.body.username + ' and succeeded (changed roles, executive action).'})
let userUpdate = await User.update({
booster: req.body.booster,
bot: req.body.bot,
@ -104,6 +113,7 @@ router.put('/user/modify', auth, async(req, res, next) => {
res.status(200)
res.json({success: true})
} else {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' modified ' + req.body.username + ' and succeeded (changed roles, admin action).'})
let userUpdate = await User.update({
booster: req.body.booster,
bot: req.body.bot,
@ -118,6 +128,7 @@ router.put('/user/modify', auth, async(req, res, next) => {
res.json({success: true})
}
} else {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' attempted to modify ' + req.body.username + ' but an error was thrown (account does not exist).'})
res.status(400)
throw Errors.accountDoesNotExist
}
@ -136,11 +147,90 @@ router.get('/privileges', auth, async(req, res, next) => {
} catch (e) { next(e) }
})
router.get('/teams/pending', auth, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let team = await Team.findAll({where: {approved: false, banned: false}})
if(!team) {
res.status(200)
res.json({success: false})
}
res.json(team)
} catch (e) { next(e) }
})
router.put('/teams/approve', auth, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let team = await Team.findOne({where: {username: req.body.username}})
if(!team) {
throw Errors.accountDoesNotExist
}
if(req.body.approve) {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' approved the ' + req.body.username + ' team and succeeded (approved team).'})
await team.update({approved: true});
res.status(200)
res.json({success: true})
} else if(!req.body.approve && req.body.reason) {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' banned the ' + req.body.username + ' team and succeeded (banned team).'})
await team.update({banned: true, banReason: req.body.reason})
res.status(200)
res.json({success: true})
} else {
throw Errors.requestNotAuthorized
}
} catch (e) { next(e) }
})
router.get('/marketplace/pending', auth, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let item = await Item.findAll({where: {approved: false, deleted: false}})
if(!item) {
res.status(200)
res.json({success: false})
}
res.json(item)
} catch (e) { next(e) }
})
router.put('/marketplace/approve', auth, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let item = await Item.findOne({where: {id: req.body.id}})
if(!item) {
throw Errors.accountDoesNotExist
}
if(req.body.approve && !req.body.delete) {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' approved the Marketplace Item MID: ' + req.body.id + ' and succeeded (approved marketplace item).'})
await item.update({approved: true});
res.status(200)
res.json({success: true})
} else if(req.body.delete && !req.body.approve) {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' removed the Marketplace Item MID: ' + req.body.id + ' and succeeded (removed marketplace item).'})
await item.update({deleted: true});
res.status(200)
res.json({success: true})
} else {
throw Errors.requestNotAuthorized
}
} catch (e) { next(e) }
})
router.get('/logs', auth, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
throw Errors.featureDisabled
let logs = await AuditLog.findAll()
if(!logs) {
res.status(200)
res.json({success: false})
}
res.json(logs)
} catch (e) { next(e) }
})
module.exports = router;

View File

@ -4,9 +4,11 @@ const auth = require('../lib/auth')
const Errors = require('../lib/errors.js')
let AdminToken = require('../models').AdminToken
let AuditLog = require('../models').AuditLog
router.post('/', auth, async(req, res, next) => {
try {
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' attempted to use a deprecated administration route (admin token)'})
if(!req.userData.loggedIn && !req.userData.admin) {
throw Errors.requestNotAuthorized
} else {

View File

@ -2,7 +2,7 @@ let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let { User, Ban, Sequelize } = require('../models')
let { User, Ban, AuditLog, Sequelize } = require('../models')
const Errors = require('../lib/errors')
router.all('*', auth, async(req, res, next) => {
@ -28,6 +28,8 @@ router.post('/', auth, async(req, res, next) => {
value: req.body.userId
})
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' banned ' + req.body.username + ' and succeeded (banned).'})
let ban = await Ban.create({
message: req.body.message,
ipBanned: req.body.ipBanned,
@ -64,6 +66,7 @@ router.delete('/:ban_id', auth, async(req, res, next) => {
error: 'ban does not exist',
value: req.body.userId
})
AuditLog.create({UserId: req.userData.UserId, action: req.userData.username + ' unbanned UID:' + ban.UserId + ' and succeeded (unbanned).'})
await ban.destroy()
res.json({ success: true })

View File

@ -8,62 +8,16 @@ let { Ban, Item, Inventory, ItemCategory, User, sequelize, Sequelize } = require
router.get('/', auth, async(req, res, next) => {
try {
let threads, threadsLatestPost, resThreads, user
let { from, limit } = pagination.getPaginationProps(req.query, true)
if(req.query.username) {
user = await User.findOne({ where: { username: req.query.username }})
let queryObj = {
where: {UserId: req.userData.UserId},
include: { model: Item, include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] } }
}
function threadInclude(order) {
let options = {
model: Inventory,
order: [['id', 'DESC']],
limit,
where: {},
include: [
User,
{ model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] },
]
}
if(user) {
options.where.userId = user.id
}
if(from !== null) {
options.where.id = { $lte: from }
}
return [options]
let transaction = await Inventory.findAll(queryObj)
if(!transaction) {
res.status(200)
res.json({success: false})
}
if(req.userData.id === 'ALL') {
threads = await Thread.findAll( threadInclude('ASC')[0] )
threadsLatestPost = await Thread.findAll( threadInclude('DESC')[0] )
} else {
threads = await User.findOne({
where: { username: req.userData.username },
include: threadInclude('ASC')
})
}
if(!threads) throw Errors.invalidParameter('ID','Category doesn\'t exist')
if(Array.isArray(threads)) {
resThreads = {
name: 'All',
value: 'ALL',
Threads: threads,
meta: {}
}
threadsLatestPost = { Threads: threadsLatestPost }
} else {
resThreads = threads.toJSON()
resThreads.meta = {}
}
res.json(resThreads)
res.json(transaction)
} catch (e) { next(e) }
})
module.exports = router

View File

@ -4,7 +4,7 @@ const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let pagination = require('../lib/pagination')
let { Ban, Item, Inventory, ItemCategory, User, sequelize, Sequelize } = require('../models')
let { Ban, Item, Transaction, Inventory, ItemCategory, User, Ip, sequelize, Sequelize } = require('../models')
router.get('/', async(req, res) => {
@ -95,8 +95,18 @@ router.get('/view/:id', async(req, res, next) => {
}
let marketplace = await Item.findOne(queryObj)
if (!marketplace) throw Errors.invalidItem
res.json(marketplace.toJSON())
if(marketplace.deleted) {
let queryObj = {
where: {id: req.params.id},
attributes: ['name', 'deleted', 'createdAt', 'id', 'updatedAt', 'price'],
include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] }
}
let marketplace = await Item.findOne(queryObj)
res.json(marketplace.toJSON())
}
if(!marketplace.deleted) {
res.json(marketplace.toJSON())
}
} catch (err) { next(err) }
@ -108,12 +118,12 @@ router.get('/purchase/:id', auth, async(req, res, next) => {
let queryObj = {
where: {id: req.params.id},
}
let queryObj3 = {
where: {UserId: req.userData.UserId, ItemId: req.params.id},
}
let queryObj2 = {
where: {username: req.userData.username},
}
let queryObj3 = {
where: {UserId: req.userData.UserId, ItemId: req.params.id},
}
let marketplace = await Item.findOne(queryObj)
let user = await User.findOne(queryObj2)
let inventory = await Inventory.findOne(queryObj3)
@ -127,6 +137,12 @@ router.get('/purchase/:id', auth, async(req, res, next) => {
if (marketplace.offSale) {
throw Errors.offSale
}
if(marketplace.deleted) {
throw Errors.itemUnavailable
}
if(!marketplace.approved) {
throw Errors.itemUnavailable
}
if(inventory && !marketplace.limited) {
throw Errors.itemOwned
}
@ -136,18 +152,16 @@ router.get('/purchase/:id', auth, async(req, res, next) => {
if(marketplace.saleEnabled) {
const UserId = user.id
const priceTotal = marketplace.salePrice
console.log(priceTotal)
await user.removeKoins(priceTotal)
console.log(user.id)
await Transaction.create({UserId: UserId, priceOfPurchase: priceTotal, text: user.username + ' purchased ' + marketplace.name + ' (' + marketplace.id + ') from the Marketplace for ' + priceTotal + ' koins', itemId: marketplace.id, limited: marketplace.limited, ipId: Ip.createIfNotExists(req.ip, user)})
await Inventory.create({UserId: UserId, purchasePrice: priceTotal, isReselling: false, isResellingPrice: 0, ItemId: marketplace.id, boughtFrom: marketplace.UserId, resellType: 0, auctionId: 0})
res.status(200)
res.json({success: true})
} else {
const UserId = user.id
const priceTotal = marketplace.price
console.log(priceTotal)
await user.removeKoins(priceTotal)
console.log(user.id)
await Transaction.create({UserId: UserId, priceOfPurchase: priceTotal, text: user.username + ' purchased ' + marketplace.name + ' (' + marketplace.id + ') from the Marketplace for ' + priceTotal, itemId: marketplace.id, limited: marketplace.limited, ipId: Ip.createIfNotExists(req.ip, user)})
await Inventory.create({UserId: UserId, purchasePrice: priceTotal, isReselling: false, isResellingPrice: 0, ItemId: marketplace.id, boughtFrom: marketplace.UserId, resellType: 0, auctionId: 0})
res.status(200)
res.json({success: true})

View File

@ -41,7 +41,7 @@ var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
const Errors = require('../lib/errors.js')
var format = require('date-format');
let {
User, Post, teamWall, teamPicture, userWall, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize, Team, TeamMembers, TeamRoles
User, Post, teamWall, Transaction, 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');
@ -68,51 +68,62 @@ function setUserSession(req, res, username, UserId, admin) {
router.post('/create', emailLimiter, auth, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
name: req.body.name,
description: "This is the " + req.body.username + " team!",
banned: false,
banReason: "No reason provided",
itemsOptOut: false,
forumEnabled: false,
teamWallOptOut: false,
approved: false,
picture: "default",
OwnerId: req.userData.id
let user = await User.findOne({where: {
username: req.userData.username
}})
if(user.koins >= 300) {
await Transaction.create({UserId: user.id, priceOfPurchase: 300, text: user.username + ' purchased a Kaverti Team for 300 koins', limited: false, ipId: Ip.createIfNotExists(req.ip, user)})
let userParams = {
username: req.body.username,
name: req.body.name,
description: "This is the " + req.body.username + " team!",
banned: false,
banReason: "No reason provided",
itemsOptOut: false,
forumEnabled: false,
teamWallOptOut: false,
approved: false,
picture: "default",
OwnerId: req.userData.id
}
let team = await Team.create(userParams)
let teamInfo = team.toJSON()
let teamRoleMembers = {
name: "Members",
teamId: teamInfo.id,
priority: 2
}
let teamRoleAdmins = {
name: "Administrators",
teamId: teamInfo.id,
administrator: true,
priority: 1
}
let memberRole = await TeamRoles.create(teamRoleMembers)
let memberRoleInfo = memberRole.toJSON()
let adminRole = await TeamRoles.create(teamRoleAdmins)
let adminRoleInfo = adminRole.toJSON()
let teamMemberOwner = {
userId: req.userData.id,
teamId: teamInfo.id,
roles: memberRoleInfo.id + ", " + adminRoleInfo.id
}
await TeamMembers.create(teamMemberOwner)
res.json(team.toJSON())
} else {
throw Errors.insufficientKoins
}
let team = await Team.create(userParams)
let teamInfo = team.toJSON()
let teamRoleMembers = {
name: "Members",
teamId: teamInfo.id,
priority: 2
}
let teamRoleAdmins = {
name: "Administrators",
teamId: teamInfo.id,
administrator: true,
priority: 1
}
let memberRole = await TeamRoles.create(teamRoleMembers)
let memberRoleInfo = memberRole.toJSON()
let adminRole = await TeamRoles.create(teamRoleAdmins)
let adminRoleInfo = adminRole.toJSON()
let teamMemberOwner = {
userId: req.userData.id,
teamId: teamInfo.id,
roles: memberRoleInfo.id + ", " + adminRoleInfo.id
}
await TeamMembers.create(teamMemberOwner)
res.json(team.toJSON())
} catch (e) { next(e) }
})
router.get('/view/:username', async(req, res, next) => {
try {
let queryObj = {
attributes: {exclude: ['banReason']},
attributes: {exclude: ['banReason', 'TeamRoleId']},
where: {username: req.params.username}
}
let queryObjBanned = {
attributes: {exclude: ['banReason', 'description', 'picture', 'forumEnabled', 'TeamRoleId', 'OwnerId']},
where: {username: req.params.username}
}
if(req.query.wall) {
@ -130,17 +141,23 @@ router.get('/view/:username', async(req, res, next) => {
let user = await Team.findOne(queryObj)
if (!user) throw Errors.accountDoesNotExist
if(user.banned) {
throw Errors.teamBanned
}
if (user.userWallOptOut) {
throw Errors.userWallOptOut
}
let meta = await user.getMeta(limit)
res.json(Object.assign(user.toJSON(limit), {meta}))
} else {
let team = await Team.findOne(queryObj)
if (!team) throw Errors.accountDoesNotExist
res.json(team.toJSON())
if(!team.banned) {
res.json(team.toJSON())
} else {
let team = await Team.findOne(queryObjBanned)
res.json(team.toJSON())
}
}
@ -165,13 +182,16 @@ router.get('/view/:username/picture', async (req, res, next) => {
if(!picture) {
res.status(404)
res.json({picture: "https://cdn.kaverti.com/teams/unknown-light.png"})
} else {
} else if(!user.banned) {
res.writeHead(200, {
'Content-Type': picture.mimetype,
'Content-disposition': 'attachment;filename=kaverti-team-profile-picture',
'Content-Length': picture.file.length
})
res.end(new Buffer.from(picture.file, 'binary'))
} else {
res.status(404)
res.json({picture: "https://cdn.kaverti.com/teams/unknown-light.png"})
}
} catch (e) { next(e) }
})
@ -183,6 +203,9 @@ router.get('/view/:username/members', async(req, res, next) => {
username: req.params.username
}
})
if(user.banned) {
throw Errors.teamBanned
}
if(user) {
let team = await TeamMembers.findAll({
order: [['id', 'DESC']],
@ -211,7 +234,7 @@ router.get('/', async(req, res, next) => {
username: 'X.username',
};
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
let havingClause = '';
let havingClause = 'Having Teams.banned = false';
if(req.query.search) {
//I.e. if there is not already a HAVING clause
@ -251,6 +274,9 @@ router.put('/join/:username', auth, async(req, res, next) => {
})
console.log(team.id)
if(team) {
if(team.banned) {
throw Errors.teamBanned
}
let role = await TeamRoles.findOne({
where: {teamId: team.id, name: "Members"}
})

View File

@ -61,6 +61,10 @@ router.post('/post', postLimiter, auth, async(req, res, next) => {
//Will throw an error if banned
await Ban.ReadOnlyMode(req.userData.username)
if(getWallUser.banned) {
throw Errors.teamBanned
}
if (req.body.mentions) {
uniqueMentions = Notification.filterMentions(req.body.mentions)
}

19
routes/transactions.js Normal file
View File

@ -0,0 +1,19 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/authUserInfo')
const Errors = require('../lib/errors')
let { User, Transaction } = require('../models')
router.get('/', auth, async(req, res, next) => {
let queryObj = {
where: {UserId: req.userData.UserId},
}
let transaction = await Transaction.findAll(queryObj)
if(!transaction) {
res.status(200)
res.json({success: false})
}
res.json(transaction)
})
module.exports = router;

View File

@ -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
User, Post, ProfilePicture, Item, userWall, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
} = require('../models')
let pagination = require('../lib/pagination.js')
const sgMail = require('@sendgrid/mail');
@ -147,9 +147,9 @@ router.get('/:username', async(req, res, next) => {
where: {username: req.params.username}
}
if(req.query.posts) {
if (req.query.posts) {
let { from, limit } = pagination.getPaginationProps(req.query, true)
let {from, limit} = pagination.getPaginationProps(req.query, true)
let postInclude = {
model: Post,
@ -157,40 +157,40 @@ router.get('/:username', async(req, res, next) => {
limit,
order: [['id', 'DESC']]
}
if(from !== null) {
postInclude.where = { id: { $lte: from } }
if (from !== null) {
postInclude.where = {id: {$lte: from}}
}
queryObj.include = [postInclude]
let user = await User.findOne(queryObj)
if(!user) throw Errors.accountDoesNotExist
if (!user) throw Errors.accountDoesNotExist
let meta = await user.getMeta(limit)
res.json(Object.assign( user.toJSON(limit), { meta } ))
} else if(req.query.wall) {
let { from, limit } = pagination.getPaginationProps(req.query, true)
res.json(Object.assign(user.toJSON(limit), {meta}))
} else if (req.query.wall) {
let {from, limit} = pagination.getPaginationProps(req.query, true)
let postInclude = {
model: userWall,
include: userWall.includeOptions(),
limit,
order: [['id', 'DESC']],
}
if(from !== null) {
postInclude.where = { id: { $lte: from } }
if (from !== null) {
postInclude.where = {id: {$lte: from}}
}
queryObj.include = [postInclude]
let user = await User.findOne(queryObj)
if(!user) throw Errors.accountDoesNotExist
if(user.userWallOptOut) {
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 if(req.query.threads) {
res.json(Object.assign(user.toJSON(limit), {meta}))
} else if (req.query.threads) {
let queryString = ''
Object.keys(req.query).forEach(query => {
@ -198,6 +198,32 @@ router.get('/:username', async(req, res, next) => {
})
res.redirect('/api/v1/forums/category/ALL?username=' + req.params.username + queryString)
} else if(req.query.marketplace) {
let {from, limit} = pagination.getPaginationProps(req.query, true)
let UserId = await User.findOne({
where: {
username: req.params.username
}
})
if(!UserId) throw Errors.accountDoesNotExist
let marketplace = await Item.findAll({
where: {
UserId: UserId.id
}
})
let postInclude = {
model: Item,
include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] },
limit,
order: [['id', 'DESC']],
}
queryObj.include = [postInclude]
let user = await User.findOne(queryObj)
res.status(200)
let meta = await user.getMeta(limit)
res.json(Object.assign(user.toJSON(limit), {meta}))
} else {
let user = await User.findOne(queryObj)
if(!user) throw Errors.accountDoesNotExist
@ -295,7 +321,7 @@ router.get('/', async(req, res, next) => {
} else if(req.query.role === 'user') {
havingClause = 'HAVING Users.admin = false';
} else {
havingClause = '';
havingClause = 'Having Users.hidden = false';
}
if(req.query.search) {
//I.e. if there is not already a HAVING clause

View File

@ -12,7 +12,7 @@ var format = require('date-format');
var speakeasy = require('speakeasy');
var secret = speakeasy.generateSecret();
let {
User, Post, ProfilePicture, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
User, Post, ProfilePicture, Transaction, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
} = require('../models')
let pagination = require('../lib/pagination.js');
const mailgun = require("mailgun-js");
@ -667,6 +667,7 @@ router.put('/preferences', auth, async(req, res, next) => {
username: req.userData.username
}})
if(user.koins >= 200) {
await Transaction.create({UserId: user.id, priceOfPurchase: 200, text: user.username + ' purchased a Kaverti username change for 200 koins', limited: false, ipId: Ip.createIfNotExists(req.ip, user)})
await user.invalidateJWT()
await user.updateUsername(req.body.username, req.body.password)
} else {

View File

@ -115,6 +115,7 @@ 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)