forked from kaverti/website
Marketplace 60% complete, and Inventories.
This commit is contained in:
parent
81fd18f4dc
commit
5aa0656714
|
@ -50,7 +50,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.name' v-show="user && !user.hidden"><div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media-left">
|
||||
<figure class="image is-128x128">
|
||||
|
@ -65,7 +65,7 @@
|
|||
<div class="media">
|
||||
<div class="media-content">
|
||||
<p class="title is-4"><router-link :to="'/m/' + user.id">{{user.name}}</router-link></p>
|
||||
<p class="subtitle is-6">Created by <router-link :to="'/u/' + user.username"> @{{user.username}}</router-link></p>
|
||||
<p class="subtitle is-6">Created by <router-link :to="'/u/' + user.User.username"> @{{user.User.username}}</router-link></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -80,7 +80,7 @@
|
|||
<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>
|
||||
Something went wrong while loading the Marketplace Items, check your internet connection, or check the <a href="https://status.troplo.com">Service Status</a>
|
||||
</div></center></p>
|
||||
</div>
|
||||
</main>
|
||||
|
@ -92,7 +92,7 @@ import throttle from 'lodash.throttle';
|
|||
import AjaxErrorHandler from '../../assets/js/errorHandler';
|
||||
|
||||
export default {
|
||||
name: 'UserList',
|
||||
name: 'Marketplace',
|
||||
components: {
|
||||
LoadingMessage,
|
||||
ScrollLoad
|
||||
|
@ -122,7 +122,7 @@ export default {
|
|||
fetchData () {
|
||||
if(this.offset === null) return;
|
||||
|
||||
let url = process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `teams?
|
||||
let url = process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'marketplace/shirts' + `?
|
||||
sort=${this.tableSort.column}
|
||||
&order=${this.tableSort.sort}
|
||||
&offset=${this.offset}
|
||||
|
@ -138,7 +138,7 @@ export default {
|
|||
this.axios
|
||||
.get(url)
|
||||
.then(res => {
|
||||
this.users.push(...res.data);
|
||||
this.users.push(...res.data.Items);
|
||||
this.loading = /*loading =*/ false;
|
||||
|
||||
//If returned data is less than the limit
|
||||
|
@ -165,12 +165,12 @@ export default {
|
|||
this.loadingNewer = true
|
||||
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'teams' + '?limit=' + this.newUsers)
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + '/' + 'marketplace/shirts' + '?limit=' + this.newUsers)
|
||||
.then(res => {
|
||||
this.loadingNewer = false
|
||||
this.newUsers = 0
|
||||
|
||||
this.threads.unshift(...res.data.Threads)
|
||||
this.threads.unshift(...res.data.Items)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.loadingNewer = false
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
<style>
|
||||
.limit{
|
||||
margin-top: 0.5rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
.onsale {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<main>
|
||||
<section v-if='$store.state.experimentsStore.marketplace' 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 v-if="$store.state.experimentsStore.marketplace" class='route_container user_route'>
|
||||
<div class="section profile-heading card">
|
||||
<div class="columns is-mobile is-multiline">
|
||||
<div class="column is-3">
|
||||
<figure class="image is-512 is-centered">
|
||||
<img v-if="user.picture !== 'default' && user.approved" width="512px" height="512px" :src="user.picture">
|
||||
<img v-if="user.picture === 'default' && $store.state.theme === 'light' && user.approved" width="512px" height="512px" src="https://cdn.kaverti.com/teams/unknown-light.png">
|
||||
<img v-if="user.picture === 'default' && $store.state.theme === 'dark' && user.approved" width="512px" height="512px" src="https://cdn.kaverti.com/teams/unknown-dark.png">
|
||||
<img v-if="$store.state.theme === 'light' && !user.approved" width="512px" height="512px" src="https://cdn.kaverti.com/teams/pending-light.png">
|
||||
<img v-if="$store.state.theme === 'dark' && !user.approved" width="512px" height="512px" src="https://cdn.kaverti.com/user/avatars/full/default.png">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="column is-7">
|
||||
<p>
|
||||
<span class="title is-bold limit">{{user.name}}</span><br>
|
||||
<span class="subtitle limit">Created by <router-link :to="'/u/' + user.User.username"> @{{user.User.username}}</router-link></span>
|
||||
<br>
|
||||
</p>
|
||||
<p class="limit">
|
||||
{{user.description}}
|
||||
<br>
|
||||
Team founded at {{user.createdAt | formatDate}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column" style="float: right">
|
||||
<p v-if="!user.saleEnabled" style="float: right;">This item costs <b>{{user.price}} Koins</b></p>
|
||||
<p v-if="user.saleEnabled" style="float: right;">This item costs <b class="onsale">{{user.price}}</b><b> {{user.salePrice}} Koins</b></p><br>
|
||||
<b-button @click="buyItem()" style="float: right;">Buy item</b-button>
|
||||
<b-tooltip v-if="user.limited" style="float: right;" label="You own this item, if you don't want it anymore, you can sell it and put it on the market, this is only available for Limited Edition items">
|
||||
<b-button @click="sellItem()" style="float: right;">Sell item</b-button>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="column">
|
||||
<b-button v-if="user.limited" class="menu_button" :key='"user-menu-item-wall"' @click='$router.push(`/m/${user.id}/auction`)'>
|
||||
Auction
|
||||
</b-button>
|
||||
<b-tooltip v-if="!user.limited" label="Only limited edition items support auctioning">
|
||||
<b-button disabled="disabled" class="menu_button disabled" :key='"user-menu-item-wall"'>
|
||||
Auction
|
||||
</b-button>
|
||||
</b-tooltip>
|
||||
<br/> <br/>
|
||||
<div class="column box">
|
||||
<router-view :username='username'></router-view>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MenuButton from '../MenuButton'
|
||||
|
||||
export default {
|
||||
name: 'MarketplaceItem',
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {MenuButton},
|
||||
data () {
|
||||
return {
|
||||
menuItems: [
|
||||
/* { name: 'Wall', route: 'wall' }, */
|
||||
{ name: 'Posts', route: 'posts' },
|
||||
{ name: 'Threads', route: 'threads' },
|
||||
/* { name: 'Friends', route: 'friends' } */
|
||||
],
|
||||
selected: 0,
|
||||
|
||||
username: "unknown",
|
||||
user: null,
|
||||
savings: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route (to) {
|
||||
this.selected = this.getIndexFromRoute(to.path)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userColor () {
|
||||
if(this.user) {
|
||||
return this.user.color
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
userPicture () {
|
||||
if(this.user && this.user.picture) {
|
||||
return 'https://cdn.kaverti.com/user/avatars/full/' + this.user.picture + '.png'
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getPercentageChange() {
|
||||
const decreaseValue = this.user.price - this.user.salePrice;
|
||||
|
||||
this.savings = (decreaseValue / this.user.price) * 100;
|
||||
},
|
||||
resetFetchData () {
|
||||
this.offset = 0;
|
||||
this.users = [];
|
||||
|
||||
this.fetchData();
|
||||
},
|
||||
fetchData () {
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `marketplace/view/${this.$route.params.id}`)
|
||||
.then(res => this.user = res.data)
|
||||
},
|
||||
getIndexFromRoute (path) {
|
||||
let selectedIndex
|
||||
let route = path.split('/')[3]
|
||||
|
||||
this.menuItems.forEach((item, index) => {
|
||||
if(item.route === route) {
|
||||
selectedIndex = index
|
||||
}
|
||||
})
|
||||
|
||||
return selectedIndex
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.resetFetchData()
|
||||
this.selected = this.getIndexFromRoute(this.$route.path)
|
||||
this.fetchData()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '../../assets/scss/variables.scss';
|
||||
|
||||
.user_route {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.user_header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1.5rem;
|
||||
background-color: #fff;
|
||||
padding: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
|
||||
@at-root #{&}__icon {
|
||||
height: 128px;
|
||||
width: 128px;
|
||||
line-height: 5.5rem;
|
||||
@include text($font--role-emphasis, 5rem);
|
||||
text-align: center;
|
||||
background-color: $color__gray--darkest;
|
||||
color: #fff;
|
||||
}
|
||||
@at-root #{&}__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 1rem;
|
||||
width: calc(100% - 6rem);
|
||||
}
|
||||
@at-root #{&}__username {
|
||||
margin-top: -0.25rem;
|
||||
font-size: 2rem;
|
||||
font-weight: bold
|
||||
}
|
||||
@at-root #{&}__date {
|
||||
color: $color__darkgray--primary;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
.user_description {
|
||||
white-space: pre-line;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.user__view_holder {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.user__links {
|
||||
width: 8rem;
|
||||
display: table;
|
||||
|
||||
@at-root #{&}__menu_item {
|
||||
cursor: pointer;
|
||||
margin-bottom: 0.5rem;
|
||||
position: relative;
|
||||
|
||||
&:hover { color: $color__darkgray--primary; }
|
||||
|
||||
@at-root #{&}--selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
.menu_button {
|
||||
margin: 2px;
|
||||
}
|
||||
.user__view {
|
||||
flex-grow: 1;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint--tablet) {
|
||||
.user_route {
|
||||
width: inherit;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.user__view_holder {
|
||||
flex-direction: column;
|
||||
}
|
||||
.user_header {
|
||||
@at-root #{&}__icon {
|
||||
height: 256px;
|
||||
width: 256px;
|
||||
}
|
||||
@at-root #{&}__username {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
@at-root #{&}__date {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
.user__links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@at-root #{&}__menu_item {
|
||||
margin-right: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
color: $color__text--primary;
|
||||
}
|
||||
|
||||
@at-root #{&}--selected::before {
|
||||
width: 100%;
|
||||
height: 0.2rem;
|
||||
left: 0rem;
|
||||
top: auto;
|
||||
border-radius: 1rem;
|
||||
bottom: -0.375rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.user__view {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,95 @@
|
|||
<style>
|
||||
h1-gavel {
|
||||
font-size: 150px
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<main>
|
||||
<section v-if="!user.limited">
|
||||
<center><h1-gavel><i class="fas fa-gavel"></i></h1-gavel>
|
||||
<h1>Unfortunately, users can only auction off Limited Edition items.</h1>
|
||||
</center>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
<script>
|
||||
import MenuButton from '../MenuButton'
|
||||
|
||||
export default {
|
||||
name: 'MarketplaceItem',
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: {MenuButton},
|
||||
data () {
|
||||
return {
|
||||
menuItems: [
|
||||
/* { name: 'Wall', route: 'wall' }, */
|
||||
{ name: 'Posts', route: 'posts' },
|
||||
{ name: 'Threads', route: 'threads' },
|
||||
/* { name: 'Friends', route: 'friends' } */
|
||||
],
|
||||
selected: 0,
|
||||
|
||||
username: "unknown",
|
||||
user: null,
|
||||
relationship: false,
|
||||
relationships: {
|
||||
type: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route (to) {
|
||||
this.selected = this.getIndexFromRoute(to.path)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userColor () {
|
||||
if(this.user) {
|
||||
return this.user.color
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
userPicture () {
|
||||
if(this.user && this.user.picture) {
|
||||
return 'https://cdn.kaverti.com/user/avatars/full/' + this.user.picture + '.png'
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetFetchData () {
|
||||
this.offset = 0;
|
||||
this.users = [];
|
||||
|
||||
this.fetchData();
|
||||
},
|
||||
fetchData () {
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `marketplace/view/${this.$route.params.id}`)
|
||||
.then(res => this.user = res.data)
|
||||
},
|
||||
getIndexFromRoute (path) {
|
||||
let selectedIndex
|
||||
let route = path.split('/')[3]
|
||||
|
||||
this.menuItems.forEach((item, index) => {
|
||||
if(item.route === route) {
|
||||
selectedIndex = index
|
||||
}
|
||||
})
|
||||
|
||||
return selectedIndex
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.resetFetchData()
|
||||
this.selected = this.getIndexFromRoute(this.$route.path)
|
||||
|
||||
this.axios
|
||||
.get(process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `marketplace/view/${this.$route.params.id}`)
|
||||
.then(res => this.user = res.data)
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -133,6 +133,8 @@ const ConnectionProblems = () => import('./components/routes/ConnectionProblems'
|
|||
const Contributors = () => import('./components/routes/Contributors')
|
||||
|
||||
const Marketplace = () => import('./components/routes/Marketplace')
|
||||
const MarketplaceItem = () => import('./components/routes/MarketplaceItem')
|
||||
const MarketplaceItemAuction = () => import('./components/routes/MarketplaceItemAuction')
|
||||
|
||||
import NotFound from './components/routes/NotFound'
|
||||
import Reserved from './components/routes/Reserved'
|
||||
|
@ -182,6 +184,9 @@ 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: '/m/:id', component: MarketplaceItem, children: [
|
||||
{ path: 'auction', component: MarketplaceItemAuction }
|
||||
] },
|
||||
{ path: '/licenses', component: Licenses },
|
||||
{ path: '/teams', component: Teams },
|
||||
{ path: '/team/:username', redirect: '/team/:username/general', component: TeamSettings, children: [
|
||||
|
|
|
@ -50,6 +50,22 @@ let Errors = {
|
|||
'This category does not exist',
|
||||
400
|
||||
],
|
||||
invalidItem: [
|
||||
'This Marketplace item has either been deleted or doesn\'t exist.',
|
||||
400
|
||||
],
|
||||
offSale: [
|
||||
'This Marketplace item is off sale and is unable to be purchased at this time.',
|
||||
400
|
||||
],
|
||||
itemOwned: [
|
||||
'You already own this item',
|
||||
400
|
||||
],
|
||||
itemOwnedLimited: [
|
||||
'You already own this item, however this is an item that can be sold, so if you no longer want the item, you can sell it from the item page or from within your inventory, this is a Limited Edition item exclusive',
|
||||
400
|
||||
],
|
||||
invalidLoginCredentials: [
|
||||
'The username or password provided was incorrect',
|
||||
401
|
||||
|
|
|
@ -48,7 +48,7 @@ module.exports = {
|
|||
default: 0
|
||||
},
|
||||
price: {
|
||||
type: Sequelize.BOOLEAN
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
quantityAllowed: {
|
||||
type: Sequelize.BIGINT,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
up(queryInterface, Sequelize) {
|
||||
return Promise.all([
|
||||
queryInterface.addColumn(
|
||||
'Items',
|
||||
'approved',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
default: 0,
|
||||
defaultValue: 0
|
||||
},
|
||||
)
|
||||
]);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.createTable('itemcategories', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
createdAt: Sequelize.DATE,
|
||||
updatedAt: Sequelize.DATE,
|
||||
|
||||
name: {
|
||||
type: Sequelize.STRING(191),
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
},
|
||||
value: {
|
||||
type: Sequelize.STRING(191),
|
||||
unique: true
|
||||
}
|
||||
}, {
|
||||
charset: 'utf8mb4'
|
||||
})
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.dropTable('itemcategories');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
up(queryInterface, Sequelize) {
|
||||
return Promise.all([
|
||||
queryInterface.addColumn(
|
||||
'itemcategories',
|
||||
'locked',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: true
|
||||
},
|
||||
),
|
||||
]);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
up(queryInterface, Sequelize) {
|
||||
return Promise.all([
|
||||
queryInterface.addColumn(
|
||||
'items',
|
||||
'itemcategoryid',
|
||||
{
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: true
|
||||
},
|
||||
),
|
||||
]);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
up(queryInterface, Sequelize) {
|
||||
return Promise.all([
|
||||
queryInterface.addColumn(
|
||||
'items',
|
||||
'description',
|
||||
{
|
||||
type: Sequelize.TEXT,
|
||||
defaultValue: "No Marketplace item description provided",
|
||||
allowNull: true
|
||||
},
|
||||
),
|
||||
]);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
'use strict';
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.createTable('inventories', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
UserId: {
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
purchasePrice: {
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
isReselling: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
default: 0,
|
||||
defaultValue: 0
|
||||
},
|
||||
isResellingPrice: {
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
ItemId: {
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
boughtFrom: {
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
resellType: {
|
||||
type: Sequelize.INTEGER
|
||||
},
|
||||
auctionId: {
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
});
|
||||
},
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.dropTable('inventories');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
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 Inventory = sequelize.define('Inventory', {
|
||||
purchasePrice: {
|
||||
type: DataTypes.BIGINT,
|
||||
validate: {
|
||||
isNumeric: {
|
||||
args: true,
|
||||
msg: 'The purchase price of your Inventory Item needs to be a numeric value, numbers only.'
|
||||
}
|
||||
}
|
||||
},
|
||||
isReselling: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
default: 0,
|
||||
defaultValue: 0,
|
||||
validate: {
|
||||
isBoolean: {
|
||||
args: true,
|
||||
msg: 'You can only set this as a true or false value.'
|
||||
}
|
||||
}
|
||||
},
|
||||
isResellingPrice: {
|
||||
type: DataTypes.BIGINT,
|
||||
validate: {
|
||||
isNumeric: {
|
||||
args: true,
|
||||
msg: 'The quantity of your Marketplace Item Resell needs to be a numeric value, numbers only.'
|
||||
}
|
||||
}
|
||||
},
|
||||
ItemId: {
|
||||
type: DataTypes.BIGINT
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
boughtFrom: {
|
||||
type: DataTypes.BIGINT,
|
||||
validate: {
|
||||
isNumeric: {
|
||||
args: true,
|
||||
msg: 'The original owner user ID of your Marketplace Item needs to be a numeric value, numbers only.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
associate (models) {
|
||||
Inventory.belongsTo(models.User)
|
||||
Inventory.belongsTo(models.Item)
|
||||
}
|
||||
}
|
||||
)
|
||||
return Inventory
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
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 Item = sequelize.define('Item', {
|
||||
name: {
|
||||
type: DataTypes.STRING(191),
|
||||
validate: {
|
||||
len: {
|
||||
args: [3, 16],
|
||||
msg: 'Item name must be between 3 and 16 characters'
|
||||
},
|
||||
isString(val) {
|
||||
if (typeof val !== 'string') {
|
||||
throw new sequelize.ValidationError('Item name must be a string')
|
||||
}
|
||||
},
|
||||
containsNoBlankCharacters(val) {
|
||||
if (/\s/g.test(val)) {
|
||||
throw new sequelize.ValidationError('Item name can\'t contain blank characters')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: "shirt",
|
||||
default: "shirt",
|
||||
allowNull: false
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
UserId: {
|
||||
type: DataTypes.BIGINT,
|
||||
},
|
||||
sourceFile: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
previewFile: {
|
||||
type: DataTypes.TEXT
|
||||
},
|
||||
limited: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: 0,
|
||||
default: 0,
|
||||
validate: {
|
||||
isBoolean: {
|
||||
args: true,
|
||||
msg: 'You can only set this as a true or false value.'
|
||||
}
|
||||
}
|
||||
},
|
||||
approved: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: 0,
|
||||
default: 0,
|
||||
validate: {
|
||||
isBoolean: {
|
||||
args: true,
|
||||
msg: 'You can only set this as a true or false value.'
|
||||
}
|
||||
}
|
||||
},
|
||||
salePrice: {
|
||||
type: DataTypes.BIGINT,
|
||||
validate: {
|
||||
isNumeric: {
|
||||
args: true,
|
||||
msg: 'The on-sale price of your Marketplace Item needs to be a numeric value, numbers only.'
|
||||
}
|
||||
}
|
||||
},
|
||||
saleEnabled: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: 0,
|
||||
default: 0,
|
||||
validate: {
|
||||
isBoolean: {
|
||||
args: true,
|
||||
msg: 'You can only set this as a true or false value.'
|
||||
}
|
||||
}
|
||||
},
|
||||
price: {
|
||||
type: DataTypes.BIGINT,
|
||||
validate: {
|
||||
isNumeric: {
|
||||
args: true,
|
||||
msg: 'The price of your Marketplace Item needs to be a numeric value, numbers only.'
|
||||
}
|
||||
}
|
||||
},
|
||||
quantityAllowed: {
|
||||
type: DataTypes.BIGINT,
|
||||
default: 0,
|
||||
defaultValue: 0,
|
||||
validate: {
|
||||
isNumeric: {
|
||||
args: true,
|
||||
msg: 'The quantity of your Marketplace Item needs to be a numeric value, numbers only.'
|
||||
}
|
||||
}
|
||||
},
|
||||
offSale: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
default: 0,
|
||||
defaultValue: 0,
|
||||
validate: {
|
||||
isBoolean: {
|
||||
args: true,
|
||||
msg: 'You can only set this as a true or false value.'
|
||||
}
|
||||
}
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
default: "No Marketplace item description provided"
|
||||
}
|
||||
}, {
|
||||
instanceMethods: {
|
||||
},
|
||||
classMethods: {
|
||||
associate (models) {
|
||||
Item.belongsTo(models.ItemCategory)
|
||||
Item.belongsTo(models.Inventory)
|
||||
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
|
||||
|
||||
if(User) {
|
||||
if(PassKey) {
|
||||
let passkey = await PassKey.findOne({ where: { passkey } })
|
||||
|
||||
if(passkey && PassKey.isValid()) {
|
||||
await passkey.destroy()
|
||||
|
||||
return true
|
||||
} else {
|
||||
throw Errors.invalidPassKey
|
||||
}
|
||||
} else {
|
||||
throw Errors.sequelizeValidation(sequelize, {
|
||||
error: 'Invalid PassKey',
|
||||
path: 'passkey'
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
hooks: {
|
||||
async afterValidate(user, options) {
|
||||
if(user.changed('hash') && user.hash.length <= 50) {
|
||||
user.hash = await bcrypt.hash(user.hash, 12)
|
||||
}
|
||||
|
||||
options.hooks = false
|
||||
return options
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return Item
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
let randomColor = require('randomcolor')
|
||||
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
let ItemCategory = sequelize.define('ItemCategory', {
|
||||
name: {
|
||||
type: DataTypes.STRING(191),
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: 'The category name can\'t be empty'
|
||||
},
|
||||
isString (val) {
|
||||
if(typeof val !== 'string') {
|
||||
throw new sequelize.ValidationError('The category name must be a string')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.STRING(191),
|
||||
unique: true
|
||||
},
|
||||
locked: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
default: false
|
||||
}
|
||||
}, {
|
||||
hooks: {
|
||||
beforeCreate (category) {
|
||||
if(!category.name) {
|
||||
throw new sequelize.ValidationError('The category name cant\'t be empty')
|
||||
} else {
|
||||
let underscored = category.name.trim().replace(/\s/g, '_').toUpperCase()
|
||||
category.value = underscored
|
||||
}
|
||||
}
|
||||
},
|
||||
classMethods: {
|
||||
associate (models) {
|
||||
ItemCategory.hasMany(models.Item)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return ItemCategory
|
||||
}
|
|
@ -249,6 +249,13 @@ module.exports = (sequelize, DataTypes) => {
|
|||
}
|
||||
}, {
|
||||
instanceMethods: {
|
||||
async removeKoins(amount) {
|
||||
if(this.koins >= amount) {
|
||||
await this.update({koins: this.koins - amount})
|
||||
} else {
|
||||
throw Errors.insufficientKoins
|
||||
}
|
||||
},
|
||||
async rand() {
|
||||
await this.update({ emailToken: cryptoRandomString({length: 250})})
|
||||
},
|
||||
|
@ -421,6 +428,8 @@ module.exports = (sequelize, DataTypes) => {
|
|||
User.hasMany(models.Post)
|
||||
User.hasMany(models.Thread)
|
||||
User.hasMany(models.userWall)
|
||||
User.hasMany(models.Inventory)
|
||||
User.belongsToMany(models.Item, { through: 'UserId'})
|
||||
User.belongsToMany(models.Conversation, { through: models.UserConversation })
|
||||
User.belongsToMany(models.Ip, { through: 'UserIp' })
|
||||
},
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
let express = require('express')
|
||||
let router = express.Router()
|
||||
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')
|
||||
|
||||
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 }})
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
module.exports = router
|
|
@ -0,0 +1,149 @@
|
|||
let express = require('express')
|
||||
let router = express.Router()
|
||||
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')
|
||||
|
||||
|
||||
router.get('/', async(req, res) => {
|
||||
try {
|
||||
let categories = await ItemCategory.findAll()
|
||||
|
||||
res.json(categories)
|
||||
} catch (e) {
|
||||
res.status(500)
|
||||
res.json({
|
||||
errors: [Errors.unknown]
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
router.get('/:category', 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 }})
|
||||
}
|
||||
|
||||
function threadInclude(order) {
|
||||
let options = {
|
||||
model: Item,
|
||||
order: [['id', 'DESC']],
|
||||
limit,
|
||||
where: {},
|
||||
include: [
|
||||
ItemCategory,
|
||||
{ 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]
|
||||
}
|
||||
|
||||
if(req.params.category === 'ALL') {
|
||||
threads = await Thread.findAll( threadInclude('ASC')[0] )
|
||||
threadsLatestPost = await Thread.findAll( threadInclude('DESC')[0] )
|
||||
} else {
|
||||
threads = await ItemCategory.findOne({
|
||||
where: { value: req.params.category },
|
||||
include: threadInclude('ASC')
|
||||
})
|
||||
|
||||
threadsLatestPost = await ItemCategory.findOne({
|
||||
where: { value: req.params.category },
|
||||
include: threadInclude('DESC')
|
||||
})
|
||||
}
|
||||
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)
|
||||
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.get('/view/:id', async(req, res, next) => {
|
||||
try {
|
||||
let queryObj = {
|
||||
where: {id: req.params.id},
|
||||
include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] }
|
||||
}
|
||||
let marketplace = await Item.findOne(queryObj)
|
||||
if (!marketplace) throw Errors.invalidItem
|
||||
|
||||
res.json(marketplace.toJSON())
|
||||
|
||||
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.get('/purchase/:id', auth, async(req, res, next) => {
|
||||
await Ban.ReadOnlyMode(req.userData.username)
|
||||
try {
|
||||
let queryObj = {
|
||||
where: {id: req.params.id},
|
||||
}
|
||||
let queryObj2 = {
|
||||
where: {username: req.userData.username},
|
||||
}
|
||||
let marketplace = await Item.findOne(queryObj)
|
||||
let user = await User.findOne(queryObj2)
|
||||
if (!marketplace) throw Errors.invalidItem
|
||||
if (user.koins < marketplace.price && !marketplace.saleEnabled) {
|
||||
throw Errors.insufficientKoins
|
||||
}
|
||||
if (user.koins < marketplace.salePrice && marketplace.saleEnabled) {
|
||||
throw Errors.insufficientKoins
|
||||
}
|
||||
if (marketplace.offSale) {
|
||||
throw Errors.offSale
|
||||
}
|
||||
if(marketplace.saleEnabled) {
|
||||
const UserId = user.id
|
||||
const priceTotal = marketplace.salePrice
|
||||
console.log(priceTotal)
|
||||
await user.removeKoins(priceTotal)
|
||||
console.log(user.id)
|
||||
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 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})
|
||||
}
|
||||
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
module.exports = router
|
|
@ -65,12 +65,6 @@ function setUserSession(req, res, username, UserId, admin) {
|
|||
if(admin) { req.userData.admin = true }
|
||||
}
|
||||
|
||||
router.get('/test', async(req, res, next) => {
|
||||
try {
|
||||
throw Errors.unknown
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.post('/create', emailLimiter, auth, async(req, res, next) => {
|
||||
try {
|
||||
await Ban.isIpBanned(req.ip)
|
||||
|
|
|
@ -113,6 +113,8 @@ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs, { explorer: true })
|
|||
app.use('/api/v1/teams/', require('./routes/team'))
|
||||
app.use('/api/v1/teams/admin/', require('./routes/team_admin'))
|
||||
app.use('/api/v1/teams/wall/', require('./routes/team_wall'))
|
||||
app.use('/api/v1/marketplace', require('./routes/marketplace'))
|
||||
app.use('/api/v1/inventory', require('./routes/inventory'))
|
||||
app.use(require('./lib/errorHandler'))
|
||||
app.use(profanity.init);
|
||||
app.set('trust proxy', true)
|
||||
|
|
Loading…
Reference in New Issue