Marketplace 60% complete, and Inventories.

This commit is contained in:
Troplo 2020-11-17 23:41:10 +11:00
parent 81fd18f4dc
commit 5aa0656714
20 changed files with 1076 additions and 15 deletions

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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: [

View File

@ -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

View File

@ -48,7 +48,7 @@ module.exports = {
default: 0
},
price: {
type: Sequelize.BOOLEAN
type: Sequelize.BIGINT
},
quantityAllowed: {
type: Sequelize.BIGINT,

View File

@ -0,0 +1,15 @@
module.exports = {
up(queryInterface, Sequelize) {
return Promise.all([
queryInterface.addColumn(
'Items',
'approved',
{
type: Sequelize.BOOLEAN,
default: 0,
defaultValue: 0
},
)
]);
},
};

View File

@ -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');
}
};

View File

@ -0,0 +1,15 @@
module.exports = {
up(queryInterface, Sequelize) {
return Promise.all([
queryInterface.addColumn(
'itemcategories',
'locked',
{
type: Sequelize.BOOLEAN,
defaultValue: false,
allowNull: true
},
),
]);
},
};

View File

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

View File

@ -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
},
),
]);
},
};

View File

@ -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');
}
};

65
models/inventory.js Normal file
View File

@ -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
}

199
models/item.js Normal file
View File

@ -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
}

47
models/itemcategory.js Normal file
View File

@ -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
}

View File

@ -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' })
},

69
routes/inventory.js Normal file
View File

@ -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

149
routes/marketplace.js Normal file
View File

@ -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

View File

@ -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)

View File

@ -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)