Merge branch 'canary'

This commit is contained in:
Troplo 2021-04-09 23:34:49 +10:00
commit 2e43d68c93
99 changed files with 8210 additions and 390 deletions

View File

@ -3,6 +3,6 @@ module.exports = {
sessionSecret: process.env.SESSION_SECRET || 'iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;',
imageUploadTeams: process.env.TEAMUPLOADS || "C:\\Users\\matth\\Documents\\GitHub\\Kaverti-Team-Images",
maintenance: process.env.MAINTENANCE || false,
rootFolder: process.env.ROOTFOLDER || "C:/Users/matth/Documents/GitHub/website/",
cdnFolder: process.env.CDNFOLDER || "C:/xampp21/htdocs/"
rootFolder: process.env.ROOTFOLDER || "/home/kaverti/website/",
cdnFolder: process.env.CDNFOLDER || "/var/www/html/cdn/"
}

View File

@ -384,6 +384,6 @@ export default {
},
mounted () {
this.fetchData();
}
},
}
</script>

View File

@ -250,7 +250,7 @@
this.axios.put('/api/v1/chat/conversation/' + this.$route.params.id);
//Conversation panel might not have loaded request, so try again in 200 msec
if(!this.$store.state.conversations.length) {
if(!this.$store.state.user.conversations.length) {
setTimeout(this.updateLastRead, 200);
} else {
this.$store.commit('updateConversationLastRead', +this.$route.params.id);

View File

@ -39,19 +39,19 @@
</div>
<c-scroll-load
class='side_panel__conversations'
:class='{ "side_panel__conversations--empty": !$store.state.conversations.length }'
:class='{ "side_panel__conversations--empty": !$store.state.user.conversations.length }'
:loading='loading'
position='bottom'
@load='getConversations'
>
<side-panel-conversation
:v-for='conversation in $store.state.conversations'
:v-for='conversation in $store.state.user.conversations'
:conversation='conversation'
tabindex='0'
></side-panel-conversation>
<div v-if='!$store.state.conversations.length && !loading'>
<div v-if='!$store.state.user.conversations.length && !loading'>
No conversations
</div>
</c-scroll-load>

23
lib/authCheck.js Normal file
View File

@ -0,0 +1,23 @@
let {
User, sequelize
} = require('../models')
const jwt = require("jsonwebtoken");
const Errors = require('../lib/errors')
module.exports = async(req, res, next) => {
try {
const token = req.headers.authorization.replace("Bearer ", "");
const decoded = jwt.verify(token, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
req.userData = decoded;
let user = await User.findOne({ where: {
id: req.userData.id
}})
if(user && user.jwtOffset == req.userData.offset) {
req.userData = decoded;
return true
} else {
return false
}
} catch {
return false
}
};

View File

@ -16,7 +16,7 @@ module.exports = function (err, socket) {
console.log(err);
socket.emit('errors', {
status: 500,
errors: [{ message: 'There was an unknown error on our side - please try again later' }]
errors: [{ message: 'There was an unknown error with Kaverti websockets - please try again later' }]
});
}
};

View File

@ -0,0 +1,13 @@
module.exports = {
up(queryInterface, Sequelize) {
return Promise.all([
queryInterface.addColumn(
'blogposts',
'plainText',
{
type: Sequelize.TEXT,
},
),
]);
},
};

View File

@ -0,0 +1,35 @@
module.exports = {
up(queryInterface, Sequelize) {
return Promise.all([
queryInterface.addColumn(
'Teams',
'hatId',
{
type: Sequelize.BIGINT,
},
),
queryInterface.addColumn(
'Teams',
'pantsId',
{
type: Sequelize.BIGINT,
},
),
queryInterface.addColumn(
'Teams',
'shirtId',
{
type: Sequelize.BIGINT,
},
),
queryInterface.addColumn(
'Teams',
'faceId',
{
type: Sequelize.BIGINT,
defaultValue: 0
},
),
]);
},
}

View File

@ -0,0 +1,33 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('feedback', {
id: {
type: Sequelize.BIGINT,
primaryKey: true,
autoIncrement: true
},
route: {
type: Sequelize.TEXT,
defaultValue: '/',
allowNull: false
},
email: {
type: Sequelize.TEXT
},
stars: {
type: Sequelize.INTEGER
},
text: {
type: Sequelize.TEXT
}
}, {
charset: 'utf8mb4'
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('awards');
}
}

View File

@ -0,0 +1,20 @@
module.exports = {
up(queryInterface, Sequelize) {
return Promise.all([
queryInterface.addColumn(
'feedback',
'createdAt',
{
type: Sequelize.DATE,
},
),
queryInterface.addColumn(
'feedback',
'updatedAt',
{
type: Sequelize.DATE,
},
),
]);
},
};

View File

@ -1,7 +1,36 @@
let urlSlug = require('url-slug')
var md = require('../kaverti_modules/markdown-it')({
html: true, // Enable HTML tags in source
xhtmlOut: false, // Use '/' to close single tags (<br />).
// This is only for full CommonMark compatibility.
breaks: true, // Convert '\n' in paragraphs into <br>
langPrefix: 'language-', // CSS language prefix for fenced blocks. Can be
// useful for external highlighters.
linkify: true, // Autoconvert URL-like text to links
image: true,
// Enable some language-neutral replacement + quotes beautification
typographer: true,
// Double + single quotes replacement pairs, when typographer enabled,
// and smartquotes on. Could be either a String or an Array.
//
// For example, you can use '«»„“' for Russian, '„“‚‘' for German,
// and ['«\xA0', '\xA0»', '\xA0', '\xA0'] for French (including nbsp).
quotes: '“”‘’',
// Highlighter function. Should return escaped HTML,
// or '' if the source string is not changed and should be escaped externally.
// If result starts with <pre... internal wrapper is skipped.
highlight: function (/*str, lang*/) { return ''; }
});
var emoji = require('markdown-it-emoji');
var twemoji = require('twemoji')
md.use(emoji);
md.renderer.rules.emoji = function(token, idx) {
return twemoji.parse(token[idx].content);
};
module.exports = (sequelize, DataTypes) => {
let Thread = sequelize.define('Thread', {
let BlogPost = sequelize.define('BlogPost', {
name: {
type: DataTypes.TEXT,
set (val) {
@ -32,104 +61,18 @@ module.exports = (sequelize, DataTypes) => {
}
}
},
slug: DataTypes.TEXT,
postsCount: {
type: DataTypes.INTEGER,
defaultValue: 0
slug: {
type: DataTypes.TEXT,
defaultValue: "deprecated"
},
locked: {
type: DataTypes.BOOLEAN,
defaultValue: false
}
}, {
instanceMethods: {
getMeta (limit) {
let meta = {}
let posts = this.Posts
let firstPost = posts[0]
let lastPost = posts.slice(-1)[0]
//next url
if(!lastPost || lastPost.postNumber+1 === this.postsCount) {
meta.nextURL = null
} else {
meta.nextURL =
process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `blog/post/${this.id}?limit=${limit}&from=${lastPost.postNumber + 1}`
}
//previous url
if(!firstPost || firstPost.postNumber === 0) {
meta.previousURL = null
} else if(firstPost.postNumber - limit < 0) {
meta.previousURL =
process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `forums/thread/${this.id}?limit=${firstPost.postNumber}&from=0`
} else {
meta.previousURL =
process.env.VUE_APP_APIENDPOINT + process.env.VUE_APP_APIVERSION + `/` + `forums/thread/${this.id}?limit=${limit}&from=${firstPost.postNumber - limit}`
}
//remaining posts
if(lastPost === undefined) {
meta.nextPostsCount = 0
meta.previousPostsCount = 0
meta.postsRemaining = 0
} else {
let postsRemaining =
this.postsCount - lastPost.postNumber - 1
meta.postsRemaining = postsRemaining
if(postsRemaining < limit) {
meta.nextPostsCount = postsRemaining
} else {
meta.nextPostsCount = limit
}
if(firstPost.postNumber === 0) {
meta.previousPostsCount = 0
} else if(firstPost.postNumber - limit < 0) {
meta.previousPostsCount = firstPost.postNumber
} else {
meta.previousPostsCount = limit
}
}
return meta
}
plainText: {
type: DataTypes.TEXT,
},
classMethods: {
associate (models) {
Thread.belongsTo(models.User)
Thread.belongsTo(models.Category)
Thread.belongsTo(models.PollQuestion)
Thread.hasMany(models.Post, { foreignKeyConstraint: true, onDelete: 'CASCADE' })
},
includeOptions (from, limit) {
let models = sequelize.models
return [
{ model: models.User, attributes: ['username', 'createdAt', 'color', 'picture', 'updatedAt', 'id'] },
models.Category,
{
model: models.Post,
where: { postNumber: { $gte: from } },
order: [['id', 'ASC']],
limit,
include: [
{ model: models.Thread, attributes: ['slug'] },
{ model: models.User, as: 'Likes', attributes: ['username', 'createdAt', 'id', 'color', 'picture'] },
{ model: models.User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'admin'] },
{
model: models.Post, as: 'Replies', include:
[{ model: models.User, attributes: ['username', 'id', 'color', 'picture'] }]
}
]
}
]
}
}
content: {
type: DataTypes.TEXT
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
})
return Thread
return BlogPost
}

View File

@ -16,41 +16,39 @@ module.exports = (sequelize, DataTypes) => {
}
},
groupUsers: DataTypes.STRING
}, {
classMethods: {
associate (models) {
Conversation.belongsToMany(models.User, {
through: models.UserConversation
});
Conversation.hasMany(models.Message);
},
async setName (userId) {
let json = this.toJSON();
if (!this.Users || this.Users.length < 2) {
let users = await sequelize.models.User.findAll({
attributes: {exclude: ['hash']},
include: [{
model: sequelize.models.Conversation,
where: {id: this.id}
}]
});
json.Users = users.map(u => u.toJSON());
}
if (this.name) {
return json;
} else if (json.Users.length === 2) {
json.name = json.Users.find(u => u.id !== userId).username;
} else {
json.name = json.Users.map(u => u.username).join(', ');
}
return json;
}
}
})
return Conversation;
Conversation.associate = function (models) {
Conversation.belongsToMany(models.User, {
through: models.UserConversation
});
Conversation.hasMany(models.Message);
}
Conversation.prototype.setName = async function (userId) {
let json = this.toJSON();
if (!this.Users || this.Users.length < 2) {
let users = await sequelize.models.User.findAll({
attributes: {exclude: ['hash']},
include: [{
model: sequelize.models.Conversation,
where: {id: this.id}
}]
});
json.Users = users.map(u => u.toJSON());
}
if (this.name) {
return json;
} else if (json.Users.length === 2) {
json.name = json.Users.find(u => u.id !== userId).username;
} else {
json.name = json.Users.map(u => u.username).join(', ');
}
return json
}
return Conversation
};

40
models/feedback.js Normal file
View File

@ -0,0 +1,40 @@
const Errors = require('../lib/errors')
module.exports = (sequelize, DataTypes) => {
let Feedback = sequelize.define('Feedback', {
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true
},
route: {
type: DataTypes.TEXT,
defaultValue: '/',
allowNull: false
},
email: {
type: DataTypes.TEXT,
validate: {
isEmail: true
}
},
stars: {
type: DataTypes.INTEGER,
validate: {
min: 1,
max: 5
}
},
text: {
type: DataTypes.TEXT,
validate: {
len: [5, 512]
}
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE
}, {
freezeTableName: true
})
return Feedback
}

View File

@ -118,6 +118,19 @@ module.exports = (sequelize, DataTypes) => {
default: false,
defaultValue: false
},
hatId: {
type: DataTypes.BIGINT
},
shirtId: {
type: DataTypes.BIGINT
},
pantsId: {
type: DataTypes.BIGINT
},
faceId: {
type: DataTypes.BIGINT,
defaultValue: 0
},
picture: {
type: DataTypes.TEXT('long'),
validate: {

View File

@ -80,9 +80,9 @@ module.exports = (sequelize, DataTypes) => {
theme: {
type: DataTypes.STRING,
defaultValue: 'light',
values: ['light', 'dark'],
values: ['light', 'dark', 'amoled', 'highContrast'],
isIn: {
args: [['light', 'dark']],
args: [['light', 'dark', 'amoled', 'highContrast']],
msg: "Theme can only be one of the pre-defined options"
},
},
@ -334,6 +334,7 @@ module.exports = (sequelize, DataTypes) => {
User.hasMany(models.Inventory)
User.hasMany(models.Transaction)
User.hasMany(models.AuditLog)
User.hasMany(models.BlogPost)
User.hasMany(models.Item)
User.hasMany(models.Item, {as: 'pants'})
User.hasMany(models.Item, {as: 'shirt'})

View File

@ -44,6 +44,12 @@ module.exports = (sequelize, DataTypes) => {
let userWall = sequelize.define('userWall', {
content: {
type: DataTypes.TEXT,
validate: {
len: {
args: [4,256],
message: "Your message needs to be at least 4 characters and less than 256."
}
},
set (val) {
if(!val) {
throw Errors.sequelizeValidation(sequelize, {
@ -71,6 +77,12 @@ module.exports = (sequelize, DataTypes) => {
},
plainText: {
type: DataTypes.TEXT,
validate: {
len: {
args: [4,256],
message: "Your message needs to be at least 4 characters and less than 256."
}
},
},
postNumber: DataTypes.INTEGER,
replyingToUsername: DataTypes.STRING,

View File

@ -0,0 +1,57 @@
import bpy
def hex_to_rgb(value):
gamma = 2.05
value = value.lstrip('#')
lv = len(value)
fin = list(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
r = pow(fin[0] / 255, gamma)
g = pow(fin[1] / 255, gamma)
b = pow(fin[2] / 255, gamma)
fin.clear()
fin.append(r)
fin.append(g)
fin.append(b)
return tuple(fin)
bpy.ops.wm.open_mainfile(filepath='C:/Users/matth/Documents/GitHub/website//rendering/avatar.blend')
bpy.data.objects['Head'].select = True
bpy.data.materials['Head'].diffuse_color = hex_to_rgb('#ffffff')
bpy.data.materials['Face'].diffuse_color = hex_to_rgb('#ffffff')
bpy.data.objects['Left Arm'].select = True
bpy.data.objects['Left Arm'].active_material.diffuse_color = hex_to_rgb('#ffffff')
bpy.data.objects['Torso'].select = True
bpy.data.objects['Torso'].active_material.diffuse_color = hex_to_rgb('#ffffff')
bpy.data.objects['Right Arm'].select = True
bpy.data.objects['Right Arm'].active_material.diffuse_color = hex_to_rgb('#ffffff')
bpy.data.objects['Left Leg'].select = True
bpy.data.objects['Left Leg'].active_material.diffuse_color = hex_to_rgb('#ffffff')
bpy.data.objects['Right Leg'].select = True
bpy.data.objects['Right Leg'].active_material.diffuse_color = hex_to_rgb('#ffffff')
hat_import = bpy.ops.import_scene.obj(filepath='C:/Users/matth/Documents/GitHub/website//rendering/global/219738e01e9ec4f4272dd7e5aa4ac1dc.obj')
hat = bpy.context.selected_objects[0]
bpy.context.selected_objects[0].data.name = 'hat'
bpy.context.selected_objects[0].name = 'hat'
hat_material = bpy.data.materials.new('hat')
hat_material.diffuse_shader = 'LAMBERT'
hat.active_material = hat_material
hat_image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website//rendering/global/2b15eb5292e8b6097575cb5ec44f7f55.png')
hat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')
hat_texture.image = hat_image
hat_add = bpy.data.objects['hat'].active_material.texture_slots.add()
hat_add.texture = hat_texture
face_Image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website//rendering/global/defaultFace.png')
bpy.data.textures['Face'].image = face_Image
shirt_Image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website//rendering/global/0.png')
bpy.data.textures['Shirt'].image = shirt_Image
bpy.data.textures['ShirtR'].image = shirt_Image
bpy.data.textures['ShirtL'].image = shirt_Image
pants_Image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website//rendering/global/0.png')
bpy.data.textures['PantsR'].image = pants_Image
bpy.data.textures['PantsL'].image = pants_Image
for obj in bpy.data.objects:
obj.select = False
bpy.ops.object.select_all(action='SELECT')
bpy.ops.view3d.camera_to_view_selected()
scene = bpy.context.scene
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = 'C:/xampp21/htdocs//marketplace/preview/9650253457896447672190043079564275765258433403668372776443201272.png'
bpy.ops.render.render(write_still = 1)

View File

@ -12,7 +12,7 @@ def hex_to_rgb(value):
fin.append(g)
fin.append(b)
return tuple(fin)
bpy.ops.wm.open_mainfile(filepath='C:/Users/matth/Documents/GitHub/website/rendering/avatar.blend')
bpy.ops.wm.open_mainfile(filepath='/home/kaverti/website/rendering/avatar.blend')
bpy.data.objects['Head'].select = True
bpy.data.materials['Head'].diffuse_color = hex_to_rgb('#F2C511')
bpy.data.materials['Face'].diffuse_color = hex_to_rgb('#F2C511')
@ -26,25 +26,25 @@ bpy.data.objects['Left Leg'].select = True
bpy.data.objects['Left Leg'].active_material.diffuse_color = hex_to_rgb('#3D556E')
bpy.data.objects['Right Leg'].select = True
bpy.data.objects['Right Leg'].active_material.diffuse_color = hex_to_rgb('#C0382B')
hat_import = bpy.ops.import_scene.obj(filepath='C:/Users/matth/Documents/GitHub/website/rendering/global/5873ada4b44681127842826981ff3d97.obj')
hat_import = bpy.ops.import_scene.obj(filepath='/home/kaverti/website/rendering/global/5873ada4b44681127842826981ff3d97')
hat = bpy.context.selected_objects[0]
bpy.context.selected_objects[0].data.name = 'hat'
bpy.context.selected_objects[0].name = 'hat'
hat_material = bpy.data.materials.new('hat')
hat_material.diffuse_shader = 'LAMBERT'
hat.active_material = hat_material
hat_image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website/rendering/global/5873ada4b44681127842826981ff3d97.png')
hat_image = bpy.data.images.load(filepath = '/home/kaverti/website/rendering/global/5873ada4b44681127842826981ff3d97.png')
hat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')
hat_texture.image = hat_image
hat_add = bpy.data.objects['hat'].active_material.texture_slots.add()
hat_add.texture = hat_texture
face_Image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website/rendering/global/fc56a2a4f55254f475ccea568402d2bc.png')
face_Image = bpy.data.images.load(filepath = '/home/kaverti/website/rendering/global/fc56a2a4f55254f475ccea568402d2bc.png')
bpy.data.textures['Face'].image = face_Image
shirt_Image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website/rendering/global/0.png')
shirt_Image = bpy.data.images.load(filepath = '/home/kaverti/website/rendering/global/0.png')
bpy.data.textures['Shirt'].image = shirt_Image
bpy.data.textures['ShirtR'].image = shirt_Image
bpy.data.textures['ShirtL'].image = shirt_Image
pants_Image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website/rendering/global/0.png')
pants_Image = bpy.data.images.load(filepath = '/home/kaverti/website/rendering/global/0.png')
bpy.data.textures['PantsR'].image = pants_Image
bpy.data.textures['PantsL'].image = pants_Image
for obj in bpy.data.objects:
@ -53,5 +53,5 @@ for obj in bpy.data.objects:
bpy.ops.view3d.camera_to_view_selected()
scene = bpy.context.scene
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = 'C:/xampp21/htdocs/user/avatars/full/e97a54fe29e2e3a2be14751f24113bd1.png'
scene.render.filepath = '/var/www/html/cdn/user/avatars/full/4daef004be39e0c4c2f5b6498e6fcbaf.png'
bpy.ops.render.render(write_still = 1)

View File

@ -12,7 +12,7 @@ def hex_to_rgb(value):
fin.append(g)
fin.append(b)
return tuple(fin)
bpy.ops.wm.open_mainfile(filepath='C:/Users/matth/Documents/GitHub/website/rendering/avatarhs.blend')
bpy.ops.wm.open_mainfile(filepath='/home/kaverti/website/rendering/avatarhs.blend')
bpy.data.objects['Head'].select = True
bpy.data.materials['Head'].diffuse_color = hex_to_rgb('#F2C511')
bpy.data.materials['Face'].diffuse_color = hex_to_rgb('#F2C511')
@ -26,28 +26,28 @@ bpy.data.objects['Left Leg'].select = True
bpy.data.objects['Left Leg'].active_material.diffuse_color = hex_to_rgb('#3D556E')
bpy.data.objects['Right Leg'].select = True
bpy.data.objects['Right Leg'].active_material.diffuse_color = hex_to_rgb('#C0382B')
hat_import = bpy.ops.import_scene.obj(filepath='C:/Users/matth/Documents/GitHub/website/rendering/global/5873ada4b44681127842826981ff3d97.obj')
hat_import = bpy.ops.import_scene.obj(filepath='/home/kaverti/website/rendering/global/5873ada4b44681127842826981ff3d97')
hat = bpy.context.selected_objects[0]
bpy.context.selected_objects[0].data.name = 'hat'
bpy.context.selected_objects[0].name = 'hat'
hat_material = bpy.data.materials.new('hat')
hat_material.diffuse_shader = 'LAMBERT'
hat.active_material = hat_material
hat_image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website/rendering/global/5873ada4b44681127842826981ff3d97.png')
hat_image = bpy.data.images.load(filepath = '/home/kaverti/website/rendering/global/5873ada4b44681127842826981ff3d97.png')
hat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')
hat_texture.image = hat_image
hat_add = bpy.data.objects['hat'].active_material.texture_slots.add()
hat_add.texture = hat_texture
face_Image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website/rendering/global/fc56a2a4f55254f475ccea568402d2bc.png')
face_Image = bpy.data.images.load(filepath = '/home/kaverti/website/rendering/global/fc56a2a4f55254f475ccea568402d2bc.png')
bpy.data.textures['Face'].image = face_Image
shirt_Image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website/rendering/global/0.png')
shirt_Image = bpy.data.images.load(filepath = '/home/kaverti/website/rendering/global/0.png')
bpy.data.textures['Shirt'].image = shirt_Image
bpy.data.textures['ShirtR'].image = shirt_Image
bpy.data.textures['ShirtL'].image = shirt_Image
pants_Image = bpy.data.images.load(filepath = 'C:/Users/matth/Documents/GitHub/website/rendering/global/0.png')
pants_Image = bpy.data.images.load(filepath = '/home/kaverti/website/rendering/global/0.png')
bpy.data.textures['PantsR'].image = pants_Image
bpy.data.textures['PantsL'].image = pants_Image
scene = bpy.context.scene
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = 'C:/xampp21/htdocs/user/avatars/headshot/e97a54fe29e2e3a2be14751f24113bd1.png'
scene.render.filepath = '/var/www/html/cdn/user/avatars/headshot/4daef004be39e0c4c2f5b6498e6fcbaf.png'
bpy.ops.render.render(write_still = 1)

129
server.js
View File

@ -27,7 +27,6 @@ let config = require('./config/server.js')
const expressSwagger = require('express-swagger-generator')(app);
let swaggerJsdoc = require('swagger-jsdoc')
const jwt = require('jsonwebtoken');
const options = {
definition: {
openapi: "3.0.0",
@ -81,53 +80,95 @@ if(process.env.NODE_ENV !== 'test' && process.env.NODE_ENV !== 'production') {
app.use(require('morgan')('dev'))
}
if(!config.maintenance) {
app.use('/api/v1/user/', require('./routes/user'))
app.use('/api/v1/passkey', require('./routes/user_passkey'))
app.use('/api/v1/admin/admin_token', require('./routes/admin_token'))
app.use('/api/v1/admin/passkey', require('./routes/user_passkey'))
app.use('/api/v1/forums/category', require('./routes/category'))
app.use('/api/v1/forums/thread', require('./routes/thread'))
app.use('/api/v1/users/notification', require('./routes/notification'))
app.use('/api/v1/forums/post', require('./routes/post'))
app.use('/api/v1/kaverti/state', require('./routes/state'))
app.use('/api/v1/users/report', require('./routes/report'))
app.use('/api/v1/users/unban-request', require('./routes/UnbanRequest'))
app.use('/api/v1/admin/ban', require('./routes/ban'))
app.use('/api/v1/kaverti/search', require('./routes/search'))
app.use('/api/v1/log', require('./routes/log'))
app.use('/api/v1/forums/poll', require('./routes/poll'))
app.use('/api/v1/forums/link_preview', require('./routes/link_preview'))
app.use('/api/v1/kaverti/stats', require('./routes/stats'))
app.use('/api/v1/users/login_status', require('./routes/login_status'))
app.use('/api/v1/users/', require('./routes/userutils'))
app.use('/api/v1/admin/killsession', require('./routes/admin_kill_session'))
app.use('/api/v1/admin/ban', require('./routes/ban'))
app.use('/api/v1/kaverti/job-apply', require('./routes/StaffApplications'))
app.use('/api/v1/admin/', require('./routes/admin'))
app.use('/api/v1/users/render', require('./routes/avatar'))
app.use('/api/v1/avatar', require('./routes/avatar'))
app.use('/api/v1/userinfo', require('./routes/userinfo'))
app.use('/api/v1/wall', require('./routes/user_wall'))
app.use('/api/v1/chat/conversation', require('./routes/conversation'));
app.use('/api/v1/chat/message', require('./routes/message'));
/* V1 Routes */
app.use('/api/v1/user/', require('./v1_routes/user'))
app.use('/api/v1/passkey', require('./v1_routes/user_passkey'))
app.use('/api/v1/admin/admin_token', require('./v1_routes/admin_token'))
app.use('/api/v1/admin/passkey', require('./v1_routes/user_passkey'))
app.use('/api/v1/forums/category', require('./v1_routes/category'))
app.use('/api/v1/forums/thread', require('./v1_routes/thread'))
app.use('/api/v1/users/notification', require('./v1_routes/notification'))
app.use('/api/v1/forums/post', require('./v1_routes/post'))
app.use('/api/v1/kaverti/state', require('./v1_routes/state'))
app.use('/api/v1/users/report', require('./v1_routes/report'))
app.use('/api/v1/users/unban-request', require('./v1_routes/UnbanRequest'))
app.use('/api/v1/admin/ban', require('./v1_routes/ban'))
app.use('/api/v1/kaverti/search', require('./v1_routes/search'))
app.use('/api/v1/log', require('./v1_routes/log'))
app.use('/api/v1/forums/poll', require('./v1_routes/poll'))
app.use('/api/v1/forums/link_preview', require('./v1_routes/link_preview'))
app.use('/api/v1/kaverti/stats', require('./v1_routes/stats'))
app.use('/api/v1/users/login_status', require('./v1_routes/login_status'))
app.use('/api/v1/users/', require('./v1_routes/userutils'))
app.use('/api/v1/admin/killsession', require('./v1_routes/admin_kill_session'))
app.use('/api/v1/admin/ban', require('./v1_routes/ban'))
app.use('/api/v1/kaverti/job-apply', require('./v1_routes/StaffApplications'))
app.use('/api/v1/admin/', require('./v1_routes/admin'))
app.use('/api/v1/users/render', require('./v1_routes/avatar'))
app.use('/api/v1/avatar', require('./v1_routes/avatar'))
app.use('/api/v1/userinfo', require('./v1_routes/userinfo'))
app.use('/api/v1/wall', require('./v1_routes/user_wall'))
app.use('/api/v1/chat/conversation', require('./v1_routes/conversation'));
app.use('/api/v1/chat/message', require('./v1_routes/message'));
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs, {explorer: true}));
app.use('/api/v1/teams/', require('./routes/team'))
app.use('/api/v1/teams/admin/', require('./routes/team_admin'))
app.use('/api/v1/teams/wall/', require('./routes/team_wall'))
app.use('/api/v1/marketplace', require('./routes/marketplace'))
app.use('/api/v1/inventory', require('./routes/inventory'))
app.use('/api/v1/transactions', require('./routes/transactions'))
app.use('/api/v1/relationships', require('./routes/relationship'))
app.use('/api/v1/nodeinfo', require('./routes/node'))
app.use('/api/v1/node/', require('./routes/node'))
app.use('/api/v1/nodes/', require('./routes/node'))
app.use('/api/v1/awards/', require('./routes/award'))
app.use('/api/v1/teams/', require('./v1_routes/team'))
app.use('/api/v1/teams/admin/', require('./v1_routes/team_admin'))
app.use('/api/v1/teams/wall/', require('./v1_routes/team_wall'))
app.use('/api/v1/marketplace', require('./v1_routes/marketplace'))
app.use('/api/v1/inventory', require('./v1_routes/inventory'))
app.use('/api/v1/transactions', require('./v1_routes/transactions'))
app.use('/api/v1/relationships', require('./v1_routes/relationship'))
app.use('/api/v1/about', require('./v1_routes/about'))
/* V2 Routes */
app.use('/api/v2/about', require('./v2_routes/about'))
app.use('/api/v2/user/', require('./v2_routes/user'))
app.use('/api/v2/passkey', require('./v2_routes/user_passkey'))
app.use('/api/v2/admin/admin_token', require('./v2_routes/admin_token'))
app.use('/api/v2/admin/passkey', require('./v2_routes/user_passkey'))
app.use('/api/v2/forums/category', require('./v2_routes/category'))
app.use('/api/v2/forums/thread', require('./v2_routes/thread'))
app.use('/api/v2/users/notification', require('./v2_routes/notification'))
app.use('/api/v2/forums/post', require('./v2_routes/post'))
app.use('/api/v2/kaverti/state', require('./v2_routes/state'))
app.use('/api/v2/users/report', require('./v2_routes/report'))
app.use('/api/v2/users/unban-request', require('./v2_routes/UnbanRequest'))
app.use('/api/v2/admin/ban', require('./v2_routes/ban'))
app.use('/api/v2/kaverti/search', require('./v2_routes/search'))
app.use('/api/v2/log', require('./v2_routes/log'))
app.use('/api/v2/forums/poll', require('./v2_routes/poll'))
app.use('/api/v2/forums/link_preview', require('./v2_routes/link_preview'))
app.use('/api/v2/kaverti/stats', require('./v2_routes/stats'))
app.use('/api/v2/users/login_status', require('./v2_routes/login_status'))
app.use('/api/v2/users/', require('./v2_routes/userutils'))
app.use('/api/v2/admin/killsession', require('./v2_routes/admin_kill_session'))
app.use('/api/v2/admin/ban', require('./v2_routes/ban'))
app.use('/api/v2/kaverti/job-apply', require('./v2_routes/StaffApplications'))
app.use('/api/v2/admin/', require('./v2_routes/admin'))
app.use('/api/v2/users/render', require('./v2_routes/avatar'))
app.use('/api/v2/avatar', require('./v2_routes/avatar'))
app.use('/api/v2/userinfo', require('./v2_routes/userinfo'))
app.use('/api/v2/wall', require('./v2_routes/user_wall'))
app.use('/api/v2/chat/conversation', require('./v2_routes/conversation'));
app.use('/api/v2/chat/message', require('./v2_routes/message'));
app.use('/api/v2/teams/', require('./v2_routes/team'))
app.use('/api/v2/teams/admin/', require('./v2_routes/team_admin'))
app.use('/api/v2/teams/wall/', require('./v2_routes/team_wall'))
app.use('/api/v2/marketplace', require('./v2_routes/marketplace'))
app.use('/api/v2/inventory', require('./v2_routes/inventory'))
app.use('/api/v2/transactions', require('./v2_routes/transactions'))
app.use('/api/v2/relationships', require('./v2_routes/relationship'))
app.use('/api/v2/nodeinfo', require('./v2_routes/node'))
app.use('/api/v2/node/', require('./v2_routes/node'))
app.use('/api/v2/nodes/', require('./v2_routes/node'))
app.use('/api/v2/awards/', require('./v2_routes/award'))
app.use('/api/v2/blog', require('./v2_routes/blog'))
app.use('/api/v2/feedback', require('./v2_routes/feedback'))
app.use(require('./lib/errorHandler'))
app.set('trust proxy', true)
} else {
app.use('/api/v1/userinfo', require('./routes/userinfo'))
app.use('/api/v1/kaverti/state', require('./routes/state'))
app.use('/api/v1/', require('./routes/maintenance'))
app.use('/api/v1/userinfo', require('./v1_routes/userinfo'))
app.use('/api/v1/kaverti/state', require('./v1_routes/state'))
app.use('/api/v1/', require('./v1_routes/maintenance'))
app.use(require('./lib/errorHandler'))
app.set('trust proxy', true)
}

9
v1_routes/about.js Normal file
View File

@ -0,0 +1,9 @@
let express = require('express')
let router = express.Router()
router.get('/', async(req, res, next) => {
res.status(200)
res.json({"version": 1, "message": "Version 1 of the Kaverti API is considered stable, and will not have any drastic changes made", "warning": false, "deprecated": false, "deprecationDate": "Unknown", "client": "Stable"})
})
module.exports = router

367
v1_routes/admin.js Normal file
View File

@ -0,0 +1,367 @@
let bcrypt = require('bcryptjs')
let multer = require('multer')
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
var Recaptcha = require('express-recaptcha').RecaptchaV3;
var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd');
var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
const Errors = require('../lib/errors.js')
var format = require('date-format');
let {
User, AuditLog, Team, Item, Post, ProfilePicture, StaffApplications, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
} = require('../models')
let pagination = require('../lib/pagination.js')
var fs = require("fs");
const rateLimit = require("express-rate-limit");
const cryptoRandomString = require("crypto-random-string")
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 3,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 3 requests to this endpoint per minute, if you performed an action such as avatar color changing, those changes were saved, however your avatar was not re-rendered, please re-render your avatar.\",\"status\":429}]}"
});
router.all('*', auth, async(req, res, next) => {
let user = await User.findOne({ where: {
username: req.userData.username
}})
if(!user) throw Errors.requestNotAuthorized
if(req.userData.admin && user.admin) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.sessionAdminProtection]
})
}
})
router.put('/user/scrub', auth, async(req, res, next) => {
try {
if(!req.userData.admin) {
throw Errors.requestNotAuthorized
}
await Ban.ReadOnlyMode(req.userData.UserId)
if(req.autosan.body.description === "descscram") {
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 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.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
}
})
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) }
})
router.put('/user/modify', auth, async(req, res, next) => {
try {
if(!req.userData.admin) {
throw Errors.requestNotAuthorized
}
await Ban.ReadOnlyMode(req.userData.UserId)
if(req.body.username) {
let user = await User.findOne({ where: {
username: req.body.username
}})
let user1 = await User.findOne({ where: {
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,
system: req.body.system,
admin: req.body.admin,
hidden: req.body.hidden
}, {
where: {
username: req.body.username
}
})
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,
system: req.body.system,
hidden: req.body.hidden
}, {
where: {
username: req.body.username
}
})
res.status(200)
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
}
} catch (e) { next(e) }
})
router.get('/privileges', auth, async(req, res, next) => {
try {
let queryObj = {
attributes: {include: ['username', 'admin', 'executive'], exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset', 'description', 'theme', 'contributor', 'passwordResetOptOut', 'picture', 'createdAt', 'updatedAt', 'id']},
where: {username: req.userData.username},
}
let user = await User.findOne(queryObj)
res.json(user)
} 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}, include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] }})
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, approved: false});
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)
let logs = await AuditLog.findAll()
if(!logs) {
res.status(200)
res.json({success: false})
}
res.json(logs)
} catch (e) { next(e) }
})
router.put("/user/avatar", limiter, auth, async(req, res, next) => {
let userCheck = await User.findOne({ where: {
username: req.userData.username
}
})
if(!userCheck.admin) {
throw Errors.requestNotAuthorized
}
if(!req.userData.admin) {
throw Errors.requestNotAuthorized
}
let user = await User.findOne({ where: {
username: req.body.user
}
})
if(!user) {
throw Errors.unknown
}
const { exec } = require('child_process');
if(user.pantsId) {
var pantsModel = await Item.findOne({
where: {
id: user.pantsId
}
})
}
if(user.hatId) {
var hatModel = await Item.findOne({
where: {
id: user.hatId
}
})
}
if(user.faceId) {
var faceModel = await Item.findOne({
where: {
id: user.faceId
}
})
}
if(user.shirtId) {
var shirtModel = await Item.findOne({
where: {
id: user.shirtId
}
})
}
let rootPathRender = "C:/Users/matth/Documents/GitHub/website/";
let img2 = cryptoRandomString({length: 32})
let img = img2
var blendFilePath = rootPathRender + "rendering/avatar.blend";
var blendFilePathHs = rootPathRender + "rendering/avatarhs.blend";
var imageSavePath = "C:/winnmp/www/user/avatars/full/" + img + ".png";
var imageSavePathHS = "C:/winnmp/www/user/avatars/headshot/" + img + ".png";
var pythonFilePath = "rendering/usercontent/"+req.userData.UserId+".py";
if(user.faceId) {
var faceFilePath = rootPathRender + "rendering/global/" + faceModel.sourceFile + ".png";
} else {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
}
if(user.shirtId) {
var shirtFilePath = rootPathRender + "rendering/global/" + shirtModel.sourceFile + ".png"; // should be set to 0 by default, 0.png will just be a transparent image
} else {
var shirtFilePath = rootPathRender + "rendering/global/0.png"; // should be set to 0 by default, 0.png will just be a transparent image
}
if(user.pantsId) {
var pantsFilePath = rootPathRender + "rendering/global/" + pantsModel.sourceFile + ".png"; // should be set to 0 by default, 0.png will just be a transparent image
} else {
var pantsFilePath = rootPathRender + "rendering/global/0.png"; // should be set to 0 by default, 0.png will just be a transparent image
}
if(user.hatId) {
var hatFilePath = rootPathRender + "rendering/global/" + hatModel.sourceFile + ".obj"
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+hatModel.sourceFile+".png')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
} else {
var hat = ''
}
var imports = "import bpy";
var functions = "def hex_to_rgb(value):\n gamma = 2.05\n value = value.lstrip('#')\n lv = len(value)\n fin = list(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))\n r = pow(fin[0] / 255, gamma)\n g = pow(fin[1] / 255, gamma)\n b = pow(fin[2] / 255, gamma)\n fin.clear()\n fin.append(r)\n fin.append(g)\n fin.append(b)\n return tuple(fin)";
var blenderImport = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePath+"')";
var blenderImportHs = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePathHs+"')";
var headColor = "bpy.data.objects['Head'].select = True\nbpy.data.materials['Head'].diffuse_color = hex_to_rgb('"+user.headColor+"')\nbpy.data.materials['Face'].diffuse_color = hex_to_rgb('"+user.headColor+"')";
var leftArmColor = "bpy.data.objects['Left Arm'].select = True\nbpy.data.objects['Left Arm'].active_material.diffuse_color = hex_to_rgb('"+user.leftArmColor+"')";
var rightArmColor = "bpy.data.objects['Right Arm'].select = True\nbpy.data.objects['Right Arm'].active_material.diffuse_color = hex_to_rgb('"+user.rightArmColor+"')";
var bodyColor = "bpy.data.objects['Torso'].select = True\nbpy.data.objects['Torso'].active_material.diffuse_color = hex_to_rgb('"+user.color+"')";
var leftLegColor = "bpy.data.objects['Left Leg'].select = True\nbpy.data.objects['Left Leg'].active_material.diffuse_color = hex_to_rgb('"+user.leftLegColor+"')";
var rightLegColor = "bpy.data.objects['Right Leg'].select = True\nbpy.data.objects['Right Leg'].active_material.diffuse_color = hex_to_rgb('"+user.rightLegColor+"')";
var colors = headColor+"\n"+leftArmColor+"\n"+bodyColor+"\n"+rightArmColor+"\n"+leftLegColor+"\n"+rightLegColor;
if(user.hatId) {
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+hatModel.sourceFile+".png')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
} else {
var hat = ''
}
var face = "face_Image = bpy.data.images.load(filepath = '"+faceFilePath+"')\nbpy.data.textures['Face'].image = face_Image";
var shirt = "shirt_Image = bpy.data.images.load(filepath = '"+shirtFilePath+"')\nbpy.data.textures['Shirt'].image = shirt_Image\nbpy.data.textures['ShirtR'].image = shirt_Image\nbpy.data.textures['ShirtL'].image = shirt_Image";
var pants = "pants_Image = bpy.data.images.load(filepath = '"+pantsFilePath+"')\nbpy.data.textures['PantsR'].image = pants_Image\nbpy.data.textures['PantsL'].image = pants_Image";
var render = "for obj in bpy.data.objects:\n obj.select = False\n bpy.ops.object.select_all(action='SELECT')\nbpy.ops.view3d.camera_to_view_selected()\nscene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePath+"'\nbpy.ops.render.render(write_still = 1)";
var renderHS = "scene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePathHS+"'\nbpy.ops.render.render(write_still = 1)";
var python = imports+"\n"+functions+"\n"+blenderImport+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+render;
var pythonHS = imports+"\n"+functions+"\n"+blenderImportHs+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+renderHS;
fs.writeFile("rendering/usercontent/"+req.userData.UserId+".py", python, function(err,data){
if(err) { console.log(err) }
})
fs.writeFile("rendering/usercontent/hs/"+req.userData.UserId+".py", pythonHS, function(err,data){
if(err) { console.log(err) }
})
exec("blender -b -P rendering/usercontent/"+req.userData.UserId+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
});
exec("blender -b -P rendering/usercontent/hs/"+req.userData.UserId+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
res.status(200)
res.json({success: true})
});
user.update({picture: img})
});
module.exports = router;

20
v1_routes/admin_token.js Normal file
View File

@ -0,0 +1,20 @@
let express = require('express')
let router = express.Router()
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 {
throw Errors.requestNotAuthorized
}
} catch (err) { next(err) }
})
module.exports = router

View File

@ -0,0 +1,96 @@
let bcrypt = require('bcryptjs')
let multer = require('multer')
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
var Recaptcha = require('express-recaptcha').RecaptchaV3;
var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd');
var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
const Errors = require('../lib/errors.js')
let {
User, Post, ProfilePicture, AdminToken, Thread, Category, Sequelize, Ip, Ban, sequelize
} = require('../models')
let pagination = require('../lib/pagination.js')
function setUserSession(req, res, username, UserId, admin) {
req.userData.loggedIn = true
req.userData.username = username
req.userData.UserId = UserId
res.cookie('username', username)
//Not for security purposes, just so client side can determine
//to show certain parts of ui or not (i.e. could trivially be spoofed
//but the server would not accept any api requests)
res.cookie('admin', !!admin)
if(admin) { req.userData.admin = true }
}
router.get('/', async function(req, res) {
if(req.userData.admin) {
try {
let sortFields = {
createdAt: 'X.id',
username: 'X.username',
threadCount: 'threadCount',
postCount: 'postCount',
email: 'X.email',
bot: 'X.bot'
};
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
let havingClause = '';
if (req.query.role === 'admin') {
havingClause = 'HAVING Users.admin = true';
} else if (req.query.role === 'user') {
havingClause = 'HAVING Users.admin = false';
} else {
havingClause = '';
}
if (req.query.search) {
//I.e. if there is not already a HAVING clause
if (!havingClause.length) {
havingClause = 'HAVING ';
} else {
havingClause += ' AND ';
}
havingClause += 'Users.username LIKE $search';
}
let sql = `
SELECT X.username, X.admin, X.bot, X.email, X.createdAt, X.postCount, COUNT(Threads.id) as threadCount
FROM (
SELECT Users.*, COUNT(Posts.id) as postCount
FROM Users
LEFT OUTER JOIN Posts
ON Users.id = Posts.UserId
GROUP BY Users.id
${havingClause}
) as X
LEFT OUTER JOIN Threads
ON X.id = Threads.UserId
GROUP BY X.id
ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'ASC' : 'DESC'}
LIMIT 15
OFFSET ${offset}
`;
let users = await sequelize.query(sql, {
model: User,
bind: {search: req.query.search + '%'}
});
res.json(users)
res.json(users)
} catch (e) { next(e) }
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
module.exports = router;

150
v1_routes/avatar.js Normal file
View File

@ -0,0 +1,150 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let config = require('../config/server.js')
var fs = require("fs");
const rateLimit = require("express-rate-limit");
const { exec } = require('child_process');
const cryptoRandomString = require("crypto-random-string")
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 3,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 3 requests to this endpoint per minute, if you performed an action such as avatar color changing, those changes were saved, however your avatar was not re-rendered, please re-render your avatar.\",\"status\":429}]}"
});
let { User, Sequelize, Item } = require('../models')
const Errors = require('../lib/errors')
var randomString = (Math.random().toString(36).substring(2))
router.post("/refresh", limiter, auth, async(req, res, next) => {
let user = await User.findOne({ where: {
id: req.userData.UserId
}
})
if(!user) {
throw Errors.unknown
}
if(user.pantsId) {
var pantsModel = await Item.findOne({
where: {
id: user.pantsId
}
})
}
if(user.hatId) {
var hatModel = await Item.findOne({
where: {
id: user.hatId
}
})
}
if(user.faceId) {
var faceModel = await Item.findOne({
where: {
id: user.faceId
}
})
}
if(user.shirtId) {
var shirtModel = await Item.findOne({
where: {
id: user.shirtId
}
})
}
let rootPathRender = config.rootFolder;
let img2 = cryptoRandomString({length: 32})
let img = img2
var blendFilePath = rootPathRender + "rendering/avatar.blend";
var blendFilePathHs = rootPathRender + "rendering/avatarhs.blend";
var imageSavePath = config.cdnFolder + "user/avatars/full/" + img + ".png";
var imageSavePathHS = config.cdnFolder + "user/avatars/headshot/" + img + ".png";
var pythonFilePath = "rendering/usercontent/"+req.userData.UserId+".py";
if(user.faceId) {
var faceFilePath = rootPathRender + "rendering/global/" + faceModel.sourceFile;
} else {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
}
if(user.shirtId) {
var shirtFilePath = rootPathRender + "rendering/global/" + shirtModel.sourceFile; // should be set to 0 by default, 0.png will just be a transparent image
} else {
var shirtFilePath = rootPathRender + "rendering/global/0.png"; // should be set to 0 by default, 0.png will just be a transparent image
}
if(user.pantsId) {
var pantsFilePath = rootPathRender + "rendering/global/" + pantsModel.sourceFile; // should be set to 0 by default, 0.png will just be a transparent image
} else {
var pantsFilePath = rootPathRender + "rendering/global/0.png"; // should be set to 0 by default, 0.png will just be a transparent image
}
if(user.hatId) {
var hatFilePath = rootPathRender + "rendering/global/" + hatModel.sourceFile + ".obj"
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+hatModel.sourceFile+".png')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
} else {
var hat = ''
}
var imports = "import bpy";
var functions = "def hex_to_rgb(value):\n gamma = 2.05\n value = value.lstrip('#')\n lv = len(value)\n fin = list(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))\n r = pow(fin[0] / 255, gamma)\n g = pow(fin[1] / 255, gamma)\n b = pow(fin[2] / 255, gamma)\n fin.clear()\n fin.append(r)\n fin.append(g)\n fin.append(b)\n return tuple(fin)";
var blenderImport = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePath+"')";
var blenderImportHs = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePathHs+"')";
var headColor = "bpy.data.objects['Head'].select = True\nbpy.data.materials['Head'].diffuse_color = hex_to_rgb('"+user.headColor+"')\nbpy.data.materials['Face'].diffuse_color = hex_to_rgb('"+user.headColor+"')";
var leftArmColor = "bpy.data.objects['Left Arm'].select = True\nbpy.data.objects['Left Arm'].active_material.diffuse_color = hex_to_rgb('"+user.leftArmColor+"')";
var rightArmColor = "bpy.data.objects['Right Arm'].select = True\nbpy.data.objects['Right Arm'].active_material.diffuse_color = hex_to_rgb('"+user.rightArmColor+"')";
var bodyColor = "bpy.data.objects['Torso'].select = True\nbpy.data.objects['Torso'].active_material.diffuse_color = hex_to_rgb('"+user.color+"')";
var leftLegColor = "bpy.data.objects['Left Leg'].select = True\nbpy.data.objects['Left Leg'].active_material.diffuse_color = hex_to_rgb('"+user.leftLegColor+"')";
var rightLegColor = "bpy.data.objects['Right Leg'].select = True\nbpy.data.objects['Right Leg'].active_material.diffuse_color = hex_to_rgb('"+user.rightLegColor+"')";
var colors = headColor+"\n"+leftArmColor+"\n"+bodyColor+"\n"+rightArmColor+"\n"+leftLegColor+"\n"+rightLegColor;
if(user.hatId) {
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+hatModel.sourceFile+".png')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
} else {
var hat = ''
}
var face = "face_Image = bpy.data.images.load(filepath = '"+faceFilePath+"')\nbpy.data.textures['Face'].image = face_Image";
var shirt = "shirt_Image = bpy.data.images.load(filepath = '"+shirtFilePath+"')\nbpy.data.textures['Shirt'].image = shirt_Image\nbpy.data.textures['ShirtR'].image = shirt_Image\nbpy.data.textures['ShirtL'].image = shirt_Image";
var pants = "pants_Image = bpy.data.images.load(filepath = '"+pantsFilePath+"')\nbpy.data.textures['PantsR'].image = pants_Image\nbpy.data.textures['PantsL'].image = pants_Image";
var render = "for obj in bpy.data.objects:\n obj.select = False\n bpy.ops.object.select_all(action='SELECT')\nbpy.ops.view3d.camera_to_view_selected()\nscene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePath+"'\nbpy.ops.render.render(write_still = 1)";
var renderHS = "scene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePathHS+"'\nbpy.ops.render.render(write_still = 1)";
var python = imports+"\n"+functions+"\n"+blenderImport+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+render;
var pythonHS = imports+"\n"+functions+"\n"+blenderImportHs+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+renderHS;
fs.writeFile("rendering/usercontent/"+req.userData.UserId+".py", python, function(err,data){
if(err) { console.log(err) }
})
fs.writeFile("rendering/usercontent/hs/"+req.userData.UserId+".py", pythonHS, function(err,data){
if(err) { console.log(err) }
})
exec("blender -b -P rendering/usercontent/"+req.userData.UserId+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
});
exec("blender -b -P rendering/usercontent/hs/"+req.userData.UserId+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
res.status(200)
res.json({success: true})
});
user.update({picture: img})
});
router.put('/colors', auth, async (req, res, next) => {
try {
let user = await User.findOne({
where: {id: req.userData.UserId}
})
if(user) {
user.update({
color: req.body.color,
headColor: req.body.headColor,
leftArmColor: req.body.leftArmColor,
rightArmColor: req.body.rightArmColor,
leftLegColor: req.body.leftLegColor,
rightLegColor: req.body.rightLegColor,
})
res.json({success: true})
} else {
throw Errors.unknown
}
} catch (e) { next(e) }
})
module.exports = router;

77
v1_routes/ban.js Normal file
View File

@ -0,0 +1,77 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let { User, Ban, AuditLog, Sequelize } = require('../models')
const Errors = require('../lib/errors')
router.all('*', auth, async(req, res, next) => {
let user = await User.findOne({ where: {
username: req.userData.username
}})
if(!user) throw Errors.requestNotAuthorized
if(req.userData.admin && user.admin) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.sessionAdminProtection]
})
}
})
router.post('/', auth, async(req, res, next) => {
try {
let user = await User.findOne({ where: { username: req.body.username } })
if(!user) throw Errors.sequelizeValidation(Sequelize, {
error: 'user does not exist',
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,
ReadOnlyMode: req.body.ReadOnlyMode,
DisableLogin: req.body.DisableLogin,
})
await ban.setUser(user)
let ret = await ban.reload({
include: [{
model: User,
attributes: ['username', 'description', 'color', 'createdAt']
}]
})
res.json(ret.toJSON())
} catch (e) { next(e) }
})
router.get('/', auth, async(req, res, next) => {
try {
let bans = await Ban.findAll({
include: [User]
})
res.json(bans.map(b => b.toJSON()))
} catch (e) { next(e) }
})
router.delete('/:ban_id', auth, async(req, res, next) => {
try {
let ban = await Ban.findByPk(req.params.ban_id)
if(!ban) throw Errors.sequelizeValidation(Sequelize, {
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 })
} catch (e) { next(e) }
})
module.exports = router

View File

@ -52,7 +52,7 @@ let getPostSchema = {
router.get('/:conversationId', validation(getPostSchema), auth, async(req, res, next) => {
try {
let conversation = await conversationController.get(
req.userData.id, +req.params.conversationId, +req.query.page
req.userData.UserId, +req.params.conversationId, +req.query.page
);
res.json(conversation);
@ -71,7 +71,7 @@ router.put('/:conversationId', validation(putPostSchema), auth, async(req, res,
try {
let conversationId = +req.params.conversationId;
res.json(
await conversationController.updateLastRead(conversationId, req.userData.id)
await conversationController.updateLastRead(conversationId, req.userData.UserId)
);
} catch (e) { next(e); }
});
@ -96,7 +96,7 @@ router.put('/:conversationId/name', validation(putNameSchema), auth, async(req,
let name = req.body.name;
res.json(
await conversationController.updateName(conversationId, req.userData.id, name)
await conversationController.updateName(conversationId, req.userData.UserId, name)
);
} catch (e) { next(e); }
});

View File

@ -9,7 +9,7 @@ let { Ban, Item, Inventory, ItemCategory, User, sequelize, Sequelize } = require
router.get('/', auth, async(req, res, next) => {
try {
let queryObj = {
where: {UserId: req.userData.id},
where: {UserId: req.userData.UserId},
include: { model: Item, include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] } }
}
let transaction = await Inventory.findAll(queryObj)

556
v1_routes/marketplace.js Normal file
View File

@ -0,0 +1,556 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let multer = require('multer')
let cryptoRandomString = require("crypto-random-string");
let config = require('../config/server.js')
const Errors = require('../lib/errors')
let pagination = require('../lib/pagination')
let { Ban, Item, Transaction, Inventory, ItemCategory, User, Ip, sequelize, Sequelize } = require('../models')
let img2 = cryptoRandomString({length: 32})
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint per minute.\",\"status\":429}]}"
});
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, config.rootFolder + '/rendering/global/');
},
filename: (req, file, cb) => {
cb(null, cryptoRandomString({length: 32}) + ".png")
}
});
var upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype === "image/png") {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png is allowed'));
}
}
});
const storageObj = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, config.rootFolder + '/rendering/global/');
},
filename: (req, file, cb) => {
cb(null, img2 + '.obj')
}
});
var uploadObj = multer({
storage: storageObj
});
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 } = pagination.getPaginationProps(req.query, true)
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
console.log(offset)
let limit = 30
if(req.query.username) {
user = await User.findOne({ where: { username: req.query.username }})
}
console.log(req.query.offset + 'OFFSET')
function threadInclude(order) {
let options = {
model: Item,
order: [['id', order]],
limit: 30,
offset: offset,
subQuery: false,
where: {deleted: false, approved: true},
include: [
{ 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 Item.findAll( threadInclude('ASC')[0] )
threadsLatestPost = await Item.findAll( threadInclude('DESC')[0] )
} else {
threads = await ItemCategory.findOne({
where: { id: req.params.category },
include: threadInclude('DESC')
})
}
console.log(threads)
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
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({createdAt: marketplace.createdAt, updatedAt: marketplace.createdAt, id: marketplace.id, name: "Item unavailable", deleted: true, price: 0, User: marketplace.User, description: 'Item is currently unavailable at this time.', approved: false})
}
if(!marketplace.approved) {
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({createdAt: marketplace.createdAt, updatedAt: marketplace.createdAt, id: marketplace.id, name: "Item is pending admin approval", deleted: false, price: 0, User: marketplace.User, description: 'Item is currently unavailable at this time.', approved: false})
}
res.json(marketplace.toJSON())
} catch (err) { next(err) }
})
router.get('/purchase/:id', auth, async(req, res, next) => {
await Ban.ReadOnlyMode(req.userData.UserId)
try {
let queryObj = {
where: {id: 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)
if (!marketplace) throw Errors.invalidItem
let queryObj4 = {
where: {id: marketplace.UserId},
}
let payUser = await User.findOne(queryObj4)
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.deleted) {
throw Errors.itemUnavailable
}
if(!marketplace.approved) {
throw Errors.itemUnavailable
}
if(inventory && !marketplace.limited) {
throw Errors.itemOwned
}
if(inventory && marketplace.limited) {
throw Errors.itemOwnedLimited
}
if(marketplace.limited && marketplace.purchases === marketplace.quantityAllowed && marketplace.quantityAllowed !== 0) {
throw Errors.itemUnavailable
}
if(marketplace.saleEnabled) {
const UserId = user.id
const priceTotal = marketplace.salePrice
await user.removeKoins(priceTotal)
await payUser.addKoins(priceTotal)
await Transaction.create({UserId: payUser.id, priceOfPurchase: priceTotal, text: user.username + ' purchased your Marketplace item, ' + marketplace.name + ' (' + marketplace.id + ') for ' + priceTotal + ' koins, you have been given ' + priceTotal + ' koins. Your Marketplace item was on sale when this transaction was made.', itemId: marketplace.id, limited: marketplace.limited})
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 marketplace.update({purchases: +1})
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
await user.removeKoins(priceTotal)
await payUser.addKoins(priceTotal)
await Transaction.create({UserId: payUser.id, priceOfPurchase: priceTotal, text: user.username + ' purchased your Marketplace item, ' + marketplace.name + ' (' + marketplace.id + ') for ' + priceTotal + ' koins, you have been given ' + priceTotal + ' koins.', itemId: marketplace.id, limited: marketplace.limited})
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 marketplace.updatePurchaseCount()
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) }
})
router.get('/check/:id', auth, async(req, res, next) => {
try {
let queryObj = {
where: {id: req.params.id},
}
let queryObj3 = {
where: {UserId: req.userData.UserId, ItemId: req.params.id},
}
let inventory = await Inventory.findOne(queryObj3)
let marketplace = await Item.findOne(queryObj)
if(marketplace) {
if (inventory) {
res.status(200)
res.json({purchased: true})
} else {
res.status(200)
res.json({purchased: false})
}
} else {
throw Errors.invalidItem
}
} catch (err) { next(err) }
})
router.put('/rerender/:id', auth, limiter, async (req, res, next) => {
try {
let user = await User.findOne({
where: {id: req.userData.UserId}
});
if(!req.userData.admin) {
throw Errors.requestNotAuthorized
}
if(!user.admin) {
throw Errors.requestNotAuthorized
}
let marketplace = await Item.findOne({
where: {id: req.params.id}
});
if(marketplace) {
if(marketplace.ItemCategoryId === 0) {
var type = "hat"
} else if(marketplace.ItemCategoryId === 1) {
var type = "face"
} else if(marketplace.ItemCategoryId === 2) {
var type = "shirt"
} else if(marketplace.ItemCategoryId === 3) {
var type = "pants"
}
const { exec } = require('child_process');
var fs = require("fs");
let rootPathRender = config.rootFolder + "/";
var blendFilePath = rootPathRender + "rendering/avatar.blend";
var img4 = marketplace.previewFile
var imageSavePath = config.cdnFolder + "/marketplace/avatars/" + img4 + ".png";
var pythonFilePath = "rendering/marketplacecontent/"+req.userData.UserId+".py";
if(type === "face") {
var faceFilePath = rootPathRender + "rendering/global/" + marketplace.sourceFile + ".png";
var shirtFilePath = rootPathRender + "rendering/global/0.png";
var pantsFilePath = rootPathRender + "rendering/global/0.png";
var hat = ''
}
if(type === "shirt") {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
var shirtFilePath = rootPathRender + "rendering/global/" + marketplace.sourceFile + ".png"; // should be set to 0 by default, 0.png will just be a transparent image
var pantsFilePath = rootPathRender + "rendering/global/0.png";
var hat = ''
}
if(type === "pants") {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
var shirtFilePath = rootPathRender + "rendering/global/0.png";
var hat = ''
var pantsFilePath = rootPathRender + "rendering/global/" + marketplace.sourceFile + ".png"; // should be set to 0 by default, 0.png will just be a transparent image
}
if(type === "hat") {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
var shirtFilePath = rootPathRender + "rendering/global/0.png";
var pantsFilePath = rootPathRender + "rendering/global/0.png";
var hatFilePath = rootPathRender + "rendering/global/" + marketplace.sourceFile + ".obj"
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+marketplace.sourceFile+".png')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
}
var imports = "import bpy";
var functions = "def hex_to_rgb(value):\n gamma = 2.05\n value = value.lstrip('#')\n lv = len(value)\n fin = list(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))\n r = pow(fin[0] / 255, gamma)\n g = pow(fin[1] / 255, gamma)\n b = pow(fin[2] / 255, gamma)\n fin.clear()\n fin.append(r)\n fin.append(g)\n fin.append(b)\n return tuple(fin)";
var blenderImport = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePath+"')";
var headColor = "bpy.data.objects['Head'].select = True\nbpy.data.materials['Head'].diffuse_color = hex_to_rgb('#ffffff')\nbpy.data.materials['Face'].diffuse_color = hex_to_rgb('#ffffff')";
var leftArmColor = "bpy.data.objects['Left Arm'].select = True\nbpy.data.objects['Left Arm'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var rightArmColor = "bpy.data.objects['Right Arm'].select = True\nbpy.data.objects['Right Arm'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var bodyColor = "bpy.data.objects['Torso'].select = True\nbpy.data.objects['Torso'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var leftLegColor = "bpy.data.objects['Left Leg'].select = True\nbpy.data.objects['Left Leg'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var rightLegColor = "bpy.data.objects['Right Leg'].select = True\nbpy.data.objects['Right Leg'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var colors = headColor+"\n"+leftArmColor+"\n"+bodyColor+"\n"+rightArmColor+"\n"+leftLegColor+"\n"+rightLegColor;
var face = "face_Image = bpy.data.images.load(filepath = '"+faceFilePath+"')\nbpy.data.textures['Face'].image = face_Image";
var shirt = "shirt_Image = bpy.data.images.load(filepath = '"+shirtFilePath+"')\nbpy.data.textures['Shirt'].image = shirt_Image\nbpy.data.textures['ShirtR'].image = shirt_Image\nbpy.data.textures['ShirtL'].image = shirt_Image";
var pants = "pants_Image = bpy.data.images.load(filepath = '"+pantsFilePath+"')\nbpy.data.textures['PantsR'].image = pants_Image\nbpy.data.textures['PantsL'].image = pants_Image";
var render = "for obj in bpy.data.objects:\n obj.select = False\n bpy.ops.object.select_all(action='SELECT')\nbpy.ops.view3d.camera_to_view_selected()\nscene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePath+"'\nbpy.ops.render.render(write_still = 1)";
var python = imports+"\n"+functions+"\n"+blenderImport+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+render;
fs.writeFile("rendering/marketplacecontent/"+marketplace.id+".py", python, function(err,data){
if(err) { console.log(err) }
})
await Inventory.create({UserId: req.userData.UserId, purchasePrice: 0, isReselling: false, isResellingPrice: 0, ItemId: marketplace.id, boughtFrom: marketplace.UserId, resellType: 0, auctionId: 0})
exec("blender -b -P rendering/marketplacecontent/"+marketplace.id+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
res.status(200)
res.json(marketplace.toJSON())
});
}
} catch (e) { next(e) }
})
router.post('/upload/:id', auth, upload.single('image'), limiter, async (req, res, next) => {
try {
let findCategory = await ItemCategory.findOne({
where: {id: req.params.id}
});
console.log(findCategory.id)
if(!findCategory) {
throw Errors.marketplaceNotFound
}
let user = await User.findOne({
where: {id: req.userData.UserId}
});
if(findCategory.locked && !user.modeler) {
throw Errors.marketplaceAdminOnly
}
if(req.body.limited === true && !user.modeler) {
throw Errors.marketplaceAdminOnly
}
if(req.body.price < 0) {
throw Errors.underPriced
}
if(req.body.onSalePrice < 0) {
throw Errors.underPriced
}
if(req.file.filename === undefined) {
throw Errors.uploadFile
}
if(findCategory) {
let img3 = cryptoRandomString({length: 32})
let img4 = img3
console.log(storage)
if(req.userData.admin) {
var marketplace = await Item.create({
name: req.body.name,
UserId: user.id,
sourceFile: req.file.filename,
previewFile: img4,
limited: req.body.limited,
salePrice: req.body.onSalePrice,
saleEnabled: req.body.onSale,
price: req.body.price,
quantityAllowed: req.body.quantityAllowed,
approved: false,
ItemCategoryId: findCategory.id,
description: req.body.description
})
} else {
var marketplace = await Item.create({
name: req.body.name,
UserId: user.id,
sourceFile: req.file.filename,
previewFile: img4,
limited: false,
salePrice: req.body.onSalePrice,
saleEnabled: req.body.onSale,
price: req.body.price,
quantityAllowed: 0,
approved: false,
ItemCategoryId: findCategory.id,
description: req.body.description
})
}
if(marketplace.ItemCategoryId === 0) {
var type = "hat"
} else if(marketplace.ItemCategoryId === 1) {
var type = "face"
} else if(marketplace.ItemCategoryId === 2) {
var type = "shirt"
} else if(marketplace.ItemCategoryId === 3) {
var type = "pants"
}
const { exec } = require('child_process');
var fs = require("fs");
let rootPathRender = config.rootFolder + "/";
var blendFilePath = rootPathRender + "rendering/avatar.blend";
var imageSavePath = config.cdnFolder + "/marketplace/avatars/" + img4 + ".png";
var pythonFilePath = "rendering/marketplacecontent/"+req.userData.UserId+".py";
if(type === "face") {
var faceFilePath = rootPathRender + "rendering/global/" + marketplace.sourceFile;
var shirtFilePath = rootPathRender + "rendering/global/0.png";
var pantsFilePath = rootPathRender + "rendering/global/0.png";
var hat = ''
}
if(type === "shirt") {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
var shirtFilePath = rootPathRender + "rendering/global/" + marketplace.sourceFile; // should be set to 0 by default, 0.png will just be a transparent image
var pantsFilePath = rootPathRender + "rendering/global/0.png";
var hat = ''
}
if(type === "pants") {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
var shirtFilePath = rootPathRender + "rendering/global/0.png";
var hat = ''
var pantsFilePath = rootPathRender + "rendering/global/" + marketplace.sourceFile; // should be set to 0 by default, 0.png will just be a transparent image
}
if(type === "hat") {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
var shirtFilePath = rootPathRender + "rendering/global/0.png";
var pantsFilePath = rootPathRender + "rendering/global/0.png";
var hatFilePath = rootPathRender + "rendering/global/" + marketplace.sourceFile + ".obj"
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+marketplace.sourceFile+".png')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
}
var imports = "import bpy";
var functions = "def hex_to_rgb(value):\n gamma = 2.05\n value = value.lstrip('#')\n lv = len(value)\n fin = list(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))\n r = pow(fin[0] / 255, gamma)\n g = pow(fin[1] / 255, gamma)\n b = pow(fin[2] / 255, gamma)\n fin.clear()\n fin.append(r)\n fin.append(g)\n fin.append(b)\n return tuple(fin)";
var blenderImport = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePath+"')";
var headColor = "bpy.data.objects['Head'].select = True\nbpy.data.materials['Head'].diffuse_color = hex_to_rgb('#ffffff')\nbpy.data.materials['Face'].diffuse_color = hex_to_rgb('#ffffff')";
var leftArmColor = "bpy.data.objects['Left Arm'].select = True\nbpy.data.objects['Left Arm'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var rightArmColor = "bpy.data.objects['Right Arm'].select = True\nbpy.data.objects['Right Arm'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var bodyColor = "bpy.data.objects['Torso'].select = True\nbpy.data.objects['Torso'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var leftLegColor = "bpy.data.objects['Left Leg'].select = True\nbpy.data.objects['Left Leg'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var rightLegColor = "bpy.data.objects['Right Leg'].select = True\nbpy.data.objects['Right Leg'].active_material.diffuse_color = hex_to_rgb('#ffffff')";
var colors = headColor+"\n"+leftArmColor+"\n"+bodyColor+"\n"+rightArmColor+"\n"+leftLegColor+"\n"+rightLegColor;
var face = "face_Image = bpy.data.images.load(filepath = '"+faceFilePath+"')\nbpy.data.textures['Face'].image = face_Image";
var shirt = "shirt_Image = bpy.data.images.load(filepath = '"+shirtFilePath+"')\nbpy.data.textures['Shirt'].image = shirt_Image\nbpy.data.textures['ShirtR'].image = shirt_Image\nbpy.data.textures['ShirtL'].image = shirt_Image";
var pants = "pants_Image = bpy.data.images.load(filepath = '"+pantsFilePath+"')\nbpy.data.textures['PantsR'].image = pants_Image\nbpy.data.textures['PantsL'].image = pants_Image";
var render = "for obj in bpy.data.objects:\n obj.select = False\n bpy.ops.object.select_all(action='SELECT')\nbpy.ops.view3d.camera_to_view_selected()\nscene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePath+"'\nbpy.ops.render.render(write_still = 1)";
var python = imports+"\n"+functions+"\n"+blenderImport+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+render;
fs.writeFile("rendering/marketplacecontent/"+marketplace.id+".py", python, function(err,data){
if(err) { console.log(err) }
})
await Inventory.create({UserId: req.userData.UserId, purchasePrice: 0, isReselling: false, isResellingPrice: 0, ItemId: marketplace.id, boughtFrom: marketplace.UserId, resellType: 0, auctionId: 0})
exec("blender -b -P rendering/marketplacecontent/"+marketplace.id+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
res.status(200)
res.json(marketplace.toJSON())
});
} else {
throw Errors.marketplaceNotFound
}
} catch (e) { next(e) }
})
router.put('/apply/:id', auth, async (req, res, next) => {
try {
let item = await Inventory.findOne({
where: {ItemId: req.params.id, UserId: req.userData.UserId}
});
if (item) {
let marketplaceItem = await Item.findOne({
where: {id: item.ItemId}
})
if (marketplaceItem) {
if(!marketplaceItem.approved) {
throw Errors.itemUnavailable
}
if (marketplaceItem.ItemCategoryId === 0) {
await User.update({ hatId: marketplaceItem.id }, {where: {id: req.userData.UserId}})
res.json({success: true})
} else if (marketplaceItem.ItemCategoryId === 1) {
await User.update({ faceId: marketplaceItem.id }, {where: {id: req.userData.UserId}})
res.json({success: true})
} else if (marketplaceItem.ItemCategoryId === 2) {
await User.update({ shirtId: marketplaceItem.id }, {where: {id: req.userData.UserId}})
res.json({success: true})
} else if (marketplaceItem.ItemCategoryId === 3) {
await User.update({ pantsId: marketplaceItem.id }, {where: {id: req.userData.UserId}})
res.json({success: true})
} else {
throw Errors.unknown
}
} else {
console.log('second to last')
throw Errors.unowned
}
} else {
console.log('last')
throw Errors.unowned
}
} catch (e) { next(e) }
})
router.put('/remove/:id', auth, async (req, res, next) => {
try {
let item = await Inventory.findOne({
where: {ItemId: req.params.id, UserId: req.userData.UserId}
});
if (item) {
let marketplaceItem = await Item.findOne({
where: {id: item.ItemId}
})
if (marketplaceItem) {
console.log(marketplaceItem)
if (marketplaceItem.ItemCategoryId === 0) {
await User.update({ hatId: null }, {where: {id: req.userData.UserId}})
res.json({success: true})
} else if (marketplaceItem.ItemCategoryId === 1) {
await User.update({ faceId: null }, {where: {id: req.userData.UserId}})
res.json({success: true})
} else if (marketplaceItem.ItemCategoryId === 2) {
await User.update({ shirtId: null }, {where: {id: req.userData.UserId}})
res.json({success: true})
} else if (marketplaceItem.ItemCategoryId === 3) {
await User.update({ pantsId: null }, {where: {id: req.userData.UserId}})
res.json({success: true})
} else {
throw Errors.unknown
}
} else {
console.log('second to last')
throw Errors.unowned
}
} else {
console.log('last')
throw Errors.unowned
}
} catch (e) { next(e) }
})
module.exports = router

View File

@ -33,15 +33,15 @@ router.post('/', validation(messageValidationSchema), auth, async(req, res, next
try {
let message = await messageController.create({
content: req.body.content,
userId: req.userData.id,
userId: req.userData.UserId,
conversationId: req.body.conversationId
});
let user = await userController.get(req.userData.id);
let user = await userController.get(req.userData.UserId);
let retMessage = Object.assign(message.toJSON(), {
User: user
});
let conversations = await conversationController.getFromUser(req.userData.id);
let conversations = await conversationController.getFromUser(req.userData.UserId);
let retConversation = conversations.Conversations[0];
//Get the users in the conversation and the id for the socket

98
v1_routes/notification.js Normal file
View File

@ -0,0 +1,98 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let { Notification, User, Post, PostNotification, MessageNotification } = require('../models')
router.all('*', auth, (req, res, next) => {
if(req.userData.loggedIn) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.get('/', auth, async(req, res, next) => {
try {
let Notifications = await Notification.findAll({
where: {
'UserId': req.userData.UserId
},
order: [['id', 'DESC']],
include: [{
model: PostNotification,
include: [Post, { model: User, attributes: ['createdAt', 'username', 'color'] }]
}, {
model: MessageNotification,
include: [{model: User, attributes: ['createdAt', 'username', 'color']}]
}]
})
let unreadCount = Notifications.reduce((acc, val) => {
return val.read ? acc : acc+1
}, 0)
res.json({ Notifications, unreadCount })
} catch (e) { next(e) }
})
router.put('/', auth, async(req, res, next) => {
try {
await Notification.update({ read: true }, {
where: {
'UserId': req.userData.UserId,
'read': false
}
})
res.json({ success: true })
} catch (e) { next(e) }
})
router.put('/:id', auth, async(req, res, next) => {
try {
let updatedRows = await Notification.update({ interacted: true, read: true }, {
where: {
'UserId': req.userData.UserId,
id: req.params.id
}
})
if(updatedRows[0] === 0) {
res.status(400)
res.json({
errors: [Errors.invalidParameter('id', 'invalid notification id')]
})
} else {
res.json({ success: true })
}
} catch (e) { next(e) }
})
router.delete('/:id', auth, async(req, res, next) => {
try {
let deleted = await Notification.destroy({
where: {
'UserId': req.userData.UserId,
id: req.params.id
}
})
if(deleted) {
res.json({ success: true })
} else {
res.status(400)
res.json({
errors: [Errors.invalidParameter('id', 'Notification already dismissed, or doesn\'t exist, maybe try not to spam it next time :)')]
})
}
} catch (e) { next(e) }
})
module.exports = router

154
v1_routes/poll.js Normal file
View File

@ -0,0 +1,154 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let { PollAnswer, PollQuestion, PollVote, User, Sequelize, Thread } = require('../models')
const Errors = require('../lib/errors')
router.get('/:id', auth, async(req, res, next) => {
try {
let id = req.params.id
let pollQuestion = await PollQuestion.findByPk(id, {
include: [
{ model: User, attributes: { exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset'] } },
{ model: PollAnswer, include: [PollVote] }
]
})
if(!pollQuestion) throw Errors.sequelizeValidation(Sequelize, {
error: 'Poll ID is invalid!',
value: id
})
let totalVotes = pollQuestion.PollAnswers.reduce((sum, answer) => {
return sum + answer.PollVotes.length
}, 0)
let answersWithPercent = pollQuestion.PollAnswers.map(answer => {
let jsonAnswer = answer.toJSON()
let percent = answer.PollVotes.length / totalVotes
jsonAnswer.percent = Math.round(percent*100 * 10) / 10
return jsonAnswer
})
let hasVoted = await PollVote.findOne({
where: {
UserId: req.userData.UserId,
PollQuestionId: id
}
})
let jsonPollQuestion = pollQuestion.toJSON()
jsonPollQuestion.totalVotes = totalVotes
jsonPollQuestion.PollAnswers = answersWithPercent
jsonPollQuestion.hasVoted = !!hasVoted
res.json(jsonPollQuestion)
} catch (e) { next(e) }
})
router.all('*', auth, (req, res, next) => {
if(req.userData.loggedIn) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.post('/', auth, async(req, res, next) => {
try {
let threadId = req.body.threadId
let thread = await Thread.findByPk(req.body.threadId)
if(!thread) {
throw Errors.sequelizeValidation(Sequelize, {
error: 'You tried voting on a poll on a post that didn\'t exist?',
value: threadId
})
} else if(thread.UserId !== req.userData.UserId) {
throw Errors.requestNotAuthorized
} else if(thread.PollQuestionId) {
throw Errors.sequelizeValidation(Sequelize, {
error: 'invalid thread id',
value: threadId
})
}
let answers = req.body.answers
if(!answers || answers.length > 6) {
throw Errors.sequelizeValidation(Sequelize, {
error: 'You can only make up to 6 answers per poll',
value: answers
})
}
if(!answers || answers.length < 2) {
throw Errors.sequelizeValidation(Sequelize, {
error: 'You must provide at least 2 answers',
value: answers
})
} else if(answers.length !== new Set(answers).size) {
throw Errors.sequelizeValidation(Sequelize, {
error: 'Answers cannot contain any duplicates',
value: answers
})
}
let pollQuestion = await PollQuestion.create({
UserId: req.userData.UserId,
question: req.body.question
})
let pollAnswers = await Promise.all(
answers.map(answer => {
return PollAnswer.create({ answer })
})
)
//Set associations
await thread.setPollQuestion(pollQuestion)
await Promise.all(
pollAnswers.map(pollAnswer => {
return pollQuestion.addPollAnswer(pollAnswer)
})
)
res.json(pollQuestion.toJSON())
} catch (e) { next(e) }
})
router.post('/:id', auth, async(req, res, next) => {
try {
let previousVote = await PollVote.findOne({
where: { PollQuestionId: req.params.id, UserId: req.userData.UserId }
})
if(previousVote) throw Errors.sequelizeValidation(Sequelize, {
error: 'you cannot vote twice',
value: req.params.id
})
let poll = await PollQuestion.findByPk(req.params.id, {
include: [PollAnswer]
})
if(!poll) throw Errors.sequelizeValidation(Sequelize, {
error: 'invalid poll id',
value: req.params.id
})
let pollAnswer = poll.PollAnswers.find(a => a.answer === req.body.answer)
if(!pollAnswer) throw Errors.sequelizeValidation(Sequelize, {
error: 'invalid answer',
value: req.body.answer
})
let pollVote = await PollVote.create({ UserId: req.userData.UserId })
await pollVote.setPollQuestion(poll)
await pollVote.setPollAnswer(pollAnswer)
res.redirect('/api/v1/forums/poll/' + req.params.id)
} catch (e) { next(e) }
})
module.exports = router

198
v1_routes/post.js Normal file
View File

@ -0,0 +1,198 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let { User, Thread, Post, Notification, Ban, Sequelize, sequelize } = require('../models')
const rateLimit = require("express-rate-limit");
const postLimiter = rateLimit({
windowMs: 60000,
max: 10,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 10 requests to this endpoint per minute.\",\"status\":429}]}"
});
const likeLimiter = rateLimit({
windowMs: 60000,
max: 25,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 25 requests to this endpoint per minute.\",\"status\":429}]}"
});
router.get('/:post_id', async(req, res, next) => {
try {
let post = await Post.findByPk(req.params.post_id, { include: Post.includeOptions() })
if(!post) throw Errors.sequelizeValidation(Sequelize, {
error: 'post does not exist',
path: 'id'
})
res.json(post.toJSON())
} catch (e) { next(e) }
})
router.all('*', auth, (req, res, next) => {
if(req.userData.loggedIn) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.put('/:post_id/like', likeLimiter, auth, async(req, res, next) => {
try {
let post = await Post.findByPk(req.params.post_id)
let user = await User.findOne({ where: { username: req.userData.username }})
if(!post) throw Errors.invalidParameter('id', 'post does not exist')
if(post.UserId === user.id) throw Errors.cannotLikeOwnPost
await post.addLikes(user)
res.json({ success: true })
} catch (e) { next(e) }
})
router.delete('/:post_id/like', likeLimiter, auth, async(req, res, next) => {
try {
let post = await Post.findByPk(req.params.post_id)
let user = await User.findOne({ where: { username: req.userData.username }})
if(!post) throw Errors.invalidParameter('id', 'post does not exist')
await post.removeLikes(user)
res.json({ success: true })
} catch (e) { next(e) }
})
router.post('/', postLimiter, auth, async(req, res, next) => {
let thread, replyingToPost, post, uniqueMentions = []
let queryObj = {
attributes: {include: ['emailVerified']},
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
try {
//Will throw an error if banned
await Ban.ReadOnlyMode(req.userData.UserId)
if(req.body.mentions) {
uniqueMentions = Notification.filterMentions(req.body.mentions)
}
if(!user.emailVerified) {
throw Errors.verifyEmail
}
thread = await Thread.findOne({ where: {
id: req.body.threadId
}})
user = await User.findOne({ where: {
username: req.userData.username
}})
if(thread.locked) throw Errors.threadLocked
if(req.body.replyingToId) {
let replyingToPost1 = Post.findByPk(
req.body.replyingToId, { include: [Thread, { model: User, attributes: ['username'] }] }
)
console.log(replyingToPost1)
replyingToPost = await Post.getReplyingToPost(
req.body.replyingToId, thread, replyingToPost1
)
post = await Post.create({ content: req.body.content, postNumber: thread.postsCount })
await post.setReplyingTo(replyingToPost)
await replyingToPost.addReplies(post)
let replyNotification = await Notification.createPostNotification({
usernameTo: replyingToPost.User.username,
userFrom: user,
type: 'reply',
post: post
})
await replyNotification.emitNotificationMessage(
req.app.get('io-users'),
req.app.get('io')
)
} else {
post = await Post.create({ content: req.body.content, postNumber: thread.postsCount })
}
await post.setUser(user)
await post.setThread(thread)
await thread.increment('postsCount')
if(uniqueMentions.length) {
let ioUsers = req.app.get('io-users')
let io = req.app.get('io')
for(const mention of uniqueMentions) {
let mentionNotification = await Notification.createPostNotification({
usernameTo: mention,
userFrom: user,
type: 'mention',
post
})
if(mentionNotification) {
await mentionNotification.emitNotificationMessage(ioUsers, io)
}
}
}
res.json(await post.reload({
include: Post.includeOptions()
}))
req.app.get('io').to('thread/' + thread.id).emit('new post', {
postNumber: thread.postsCount,
content: post.content,
username: user.username
})
} catch (e) { next(e) }
})
router.all('*', auth, async(req, res, next) => {
let user = await User.findOne({ where: {
username: req.userData.username
}})
if(!user) throw Errors.requestNotAuthorized
if(req.userData.admin && user.admin) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.sessionAdminProtection]
})
}
})
router.delete('/:post_id', auth, async(req, res, next) => {
try {
if(!req.userData.admin){
res.status(401)
res.json({errors: [Errors.requestNotAuthorized]})
}
let post = await Post.findByPk(req.params.post_id)
if(!post) throw Errors.sequelizeValidation(Sequelize, {
error: 'post does not exist',
path: 'id'
})
await post.update({ content: '[This post has been removed by an administrator]', removed: true })
res.json({ success: true })
} catch (e) { next(e) }
})
module.exports = router

203
v1_routes/relationship.js Normal file
View File

@ -0,0 +1,203 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let { User, Ban, Relationship, Notification } = require('../models')
router.post('/send', auth, async(req, res, next) => {
try {
if(req.body.friend !== undefined) {
let queryObj = {
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
let queryObj2 = {
where: {username: req.body.friend}
}
let user2 = await User.findOne(queryObj2)
if (!user) {
throw Errors.unknown
}
if(!user2) {
throw Errors.accountDoesNotExist
}
if(user.id === user2.id) {
throw Errors.friendSelf
}
let checkIfSent = await Relationship.findOne({
where: {friend1Id: user.id, friend2Id: user2.id}
})
if (checkIfSent) {
throw Errors.friendRequestAlreadySent
}
Relationship.create({friend1Id: user.id, friend2Id: user2.id, type: 'pending'})
Relationship.create({friend1Id: user2.id, friend2Id: user.id, type: 'pendingCanAccept'})
res.status(200)
res.json({success: true})
} else {
res.status(400)
res.json({success: false})
}
} catch (err) { next(err) }
})
router.put('/accept', auth, async(req, res, next) => {
try {
if(req.body.friend !== undefined) {
let queryObj = {
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
let queryObj2 = {
where: {username: req.body.friend}
}
let user2 = await User.findOne(queryObj2)
if (!user) {
throw Errors.unknown
}
if(!user2) {
throw Errors.accountDoesNotExist
}
let checkIfSent = await Relationship.findOne({
where: {friend1Id: user.id, friend2Id: user2.id, type: 'pendingCanAccept'}
})
if (!checkIfSent) {
throw Errors.needToBeFriend
}
let checkIfSent2 = await Relationship.findOne({
where: {friend1Id: user2.id, friend2Id: user.id, type: 'pending'}
})
checkIfSent.update({type: 'accepted'})
checkIfSent2.update({type: 'accepted'})
res.status(200)
res.json({success: true})
} else {
res.status(400)
res.json({success: false})
}
} catch (err) { next(err) }
})
router.put('/remove', auth, async(req, res, next) => {
try {
if(req.body.friend !== undefined) {
let queryObj = {
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
let queryObj2 = {
where: {username: req.body.friend}
}
let user2 = await User.findOne(queryObj2)
if (!user) {
throw Errors.unknown
}
if(!user2) {
throw Errors.accountDoesNotExist
}
let checkIfSent = await Relationship.findOne({
where: {friend1Id: user.id, friend2Id: user2.id}
})
if (!checkIfSent) {
throw Errors.needToBeFriend
}
let checkIfSent2 = await Relationship.findOne({
where: {friend1Id: user2.id, friend2Id: user.id}
})
checkIfSent.destroy()
checkIfSent2.destroy()
res.status(200)
res.json({success: true})
} else {
res.status(400)
res.json({success: false})
}
} catch (err) { next(err) }
})
router.get('/get/:username', auth, async(req, res, next) => {
try {
let queryObj = {
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
if (!user) {
res.status(200)
res.json({success: false})
}
let queryObj2 = {
where: {username: req.params.username}
}
let user2 = await User.findOne(queryObj2)
if(!user2) {
throw Errors.accountDoesNotExist
}
let checkIfSent = await Relationship.findOne({
where: {friend1Id: user.id, friend2Id: user2.id},
include: [{ model: User, as: 'friend1', attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] }, { model: User, as: 'friend2', attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] } ]
})
if (checkIfSent) {
res.status(200)
res.json(checkIfSent.toJSON())
} else {
res.status(200)
res.json({type: 'notFriends'})
}
} catch (err) { next(err) }
})
router.get('/user/:username', auth, async(req, res, next) => {
try {
let queryObj = {
where: {username: req.params.username}
}
let user = await User.findOne(queryObj)
if (!user) {
res.status(200)
res.json({success: false})
}
if(!user) {
throw Errors.accountDoesNotExist
}
let checkIfSent = await Relationship.findAll({
where: {friend1Id: user.id, type: 'accepted'},
include: [{ model: User, as: 'friend2', attributes: ['username', 'description', 'picture', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] } ]
})
if (checkIfSent) {
res.status(200)
res.json(checkIfSent)
} else {
res.status(200)
res.json({type: 'notFriends'})
}
} catch (err) { next(err) }
})
router.get('/getAll', auth, async(req, res, next) => {
try {
let queryObj = {
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
if (!user) {
res.status(200)
res.json({success: false})
}
let checkIfSent = await Relationship.findAll({
where: {friend1Id: user.id},
include: [{ model: User, as: 'friend1', attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] }, { model: User, as: 'friend2', attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] } ]
})
if (checkIfSent) {
res.status(200)
res.json(checkIfSent)
} else {
res.status(200)
res.json({})
}
} catch (err) { next(err) }
})
module.exports = router

95
v1_routes/report.js Normal file
View File

@ -0,0 +1,95 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let { User, Post, Report, Sequelize } = require('../models')
const Errors = require('../lib/errors')
router.all('*', auth, (req, res, next) => {
if(req.userData.loggedIn) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.post('/post', auth, async(req, res, next) => {
try {
let post = await Post.findByPk(req.body.postId)
if(!post) throw Report.InvalidPostId(req.body.postId)
let user = await User.findOne({
where: { username: req.userData.username }
})
let report = await Report.create({ reason: req.body.reason })
report.setFlaggedByUser(user)
report.setPost(post)
res.json({
success: true
})
} catch (e) { next(e) }
})
router.post('/user', auth, async(req, res, next) => {
try {
let reportedUser = await Post.findByPk(req.body.userId)
if(!reportedUser) throw Report.InvalidUserId(req.body.userId)
let user = await User.findOne({
where: { username: req.userData.username }
})
let report = await Report.create({ reason: req.body.reason })
report.setFlaggedByUser(user)
report.setReportedUser(reportedUser)
res.json({
success: true
})
} catch (e) { next(e) }
})
router.all('*', auth, async(req, res, next) => {
let user = await User.findOne({ where: {
username: req.userData.username
}})
if(!user) throw Errors.requestNotAuthorized
if(req.userData.admin && user.admin) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.sessionAdminProtection]
})
}
})
router.get('/', auth, async(req, res, next) => {
try {
let reports = await Report.findAll({
include: [
{ model: User, as: 'FlaggedByUser', attributes: { exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset'] } },
{ model: Post, include: Post.includeOptions(), attributes: { exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset'] } }
]
})
res.json(reports)
} catch (e) { next(e) }
})
router.delete('/:id', auth, async(req, res, next) => {
try {
let report = await Report.findByPk(req.params.id)
if(!report) throw Report.InvalidPostId(req.params.id)
await report.destroy()
res.json({ success: true })
} catch (e) { next(e) }
})
module.exports = router

188
v1_routes/search.js Normal file
View File

@ -0,0 +1,188 @@
const { Op } = require("sequelize");
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let { Post, Ban, Team, Thread, User, Category, Sequelize } = require('../models')
const Errors = require('../lib/errors')
const { setRandomFallback } = require('bcryptjs')
router.get('/thread', async(req, res, next) => {
try {
let searchString = req.query.q.trim()
if(searchString.length < 2) {
throw Errors.sequelizeValidation(Sequelize, {
error: 'search string must be at least 2 characters',
value: searchString
})
}
//Offset is really the previously lowest id
//(as a proxy for oldest thread of the previous search)
//if there is no offset, just use a clause that will always be true
//i.e. greater than 0 id
let offset = +req.query.offset ? { $lt: +req.query.offset } : { $gt: 0 }
let limit = 10
/*
Task is to find threads that either have the
string in the title or in the content of the first post
Method
1) Select first n items from each group (posts and threads), where n is the LIMIT,
greater than id x, where x is previous OFFSET
2) Merge results from both, remove duplicates and sort
3) Select first n items from merged group
4) Set x as the last item from merged group
*/
let threadTitles = await Thread.findAll({
where: {
name: { [Op.like]: '%' + searchString + '%' },
id: offset
},
order: [ ['id', 'DESC'] ],
include: [
{
model: Post,
include: [{ model: User, attributes: { exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset']} }],
where: {
postNumber: 0
}
},
{ model: Category },
{ model: User, attributes: { exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset'] } }
],
limit
})
let threadPosts = await Thread.findAll({
where: {
id: offset
},
order: [ ['id', 'DESC'] ],
include: [
{
model: Post,
include: [{ model: User, attributes: { exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset'] } }],
where: {
postNumber: 0,
plainText: { [Op.like]: '%' + searchString + '%' }
}
},
{ model: Category },
{ model: User, attributes: { exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset'] } }
],
limit
})
let merged = [...threadTitles, ...threadPosts];
let unique = [];
merged.forEach(thread => {
let includes = unique.filter(u => thread.id === u.id);
if(!includes.length) unique.push(thread);
});
let sorted = unique
.sort((a, b) => {
return b.id - a.id;
})
.slice(0, limit);
//To get latest post, find threads where
//the post number is equal to the overal posts count
//and the post number > 0 (i.e. there are replies)
let whereClause = sorted.reduce((arr, thread) => {
if(thread.postsCount > 1) {
let clause = {
$and: {
ThreadId: thread.id,
postNumber: thread.postsCount-1
}
}
return [...arr, clause];
} else {
return arr;
}
}, []);
let latestPosts = await Post.findAll({
where: {
[Op.or]: whereClause
},
order: [ ['ThreadId', 'DESC'] ],
include: [{ model: User, attributes: { exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset'] } }]
})
//Merge latest posts with threads array
let ret = sorted.map(thread => {
if(thread.postsCount > 1) {
let post = latestPosts.filter(p => p.ThreadId === thread.id)[0];
thread.Posts.push(post);
}
return thread;
})
res.json({
threads: ret,
offset: ret.length ? ret.slice(-1)[0].id : null,
next: ret.length < limit ? null : limit
})
} catch (e) { next(e) }
})
router.get('/user', async(req, res, next) => {
try {
let searchString = req.query.q
let offset = +req.query.offset || 0
let limit = 10
let users = await User.findAll({
where: {
username: { [Op.like]: '%' + searchString + '%' }
},
order: [ ['username', 'DESC'] ],
attributes: {exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset']},
limit,
offset
})
res.json({
users,
offset: users.length < limit ? null : offset + limit,
next: users.length < limit ? null : limit
})
} catch (e) { next(e) }
})
router.get('/team', async(req, res, next) => {
try {
let searchString = req.query.q
let offset = +req.query.offset || 0
let limit = 10
let teams = await Team.findAll({
where: {
username: { [Op.like]: '%' + searchString + '%' }
},
order: [ ['username', 'DESC'] ],
attributes: {exclude: ['banReason']},
limit,
offset
})
res.json({
teams,
offset: teams.length < limit ? null : offset + limit,
next: teams.length < limit ? null : limit
})
} catch (e) { next(e) }
})
module.exports = router

21
v1_routes/stats.js Normal file
View File

@ -0,0 +1,21 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let { User } = require('../models')
router.get('/users', auth, async(req, res, next) => {
try {
let users = await User.findAll({
where: {
username
},
order: [ ['username', 'DESC'] ],
attributes: { exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset'] },
offset
})
} catch (e) { next(e) }
})
module.exports = router

866
v1_routes/team.js Normal file
View File

@ -0,0 +1,866 @@
/*
@swagger
components:
schemas:
Book:
type: object
required:
- title
- author
- finished
properties:
id:
type: integer
description: The auto-generated id of the book.
title:
type: string
description: The title of your book.
author:
type: string
description: Who wrote the book?
finished:
type: boolean
description: Have you finished reading it?
createdAt:
type: string
format: date
description: The date of the record creation.
example:
title: The Pragmatic Programmer
author: Andy Hunt / Dave Thomas
finished: true
*/
let bcrypt = require('bcryptjs')
let multer = require('multer')
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
var Recaptcha = require('express-recaptcha').RecaptchaV3;
var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd');
var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
const Errors = require('../lib/errors.js')
var format = require('date-format');
let {
User, Post, teamWall, TeamInvite, TeamMemberRole, 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');
const MailGen = require('mailgen')
const crypto = require("crypto")
const cryptoRandomString = require("crypto-random-string")
const rateLimit = require("express-rate-limit");
const emailLimiter = rateLimit({
windowMs: 60000,
max: 1, // limit each IP to 100 requests per windowMs
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint per minute.\",\"status\":429}]}"
});
function setUserSession(req, res, username, UserId, admin) {
req.userData.loggedIn = true
req.userData.username = username
req.userData.UserId = UserId
res.cookie('username', username)
if(admin) { req.userData.admin = true }
}
router.post('/create', emailLimiter, auth, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let user = await User.findOne({where: {
username: req.userData.username
}})
if(user.koins >= 300) {
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.UserId
}
let team = await Team.create(userParams)
let teamInfo = team.toJSON()
console.log(teamInfo)
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 teamAddOwner = {
userId: req.userData.UserId,
teamId: teamInfo.id,
roles: {"deprecated": "deprecated"}
}
let teamRoleOwner = {
UserId: req.userData.UserId,
TeamId: teamInfo.id,
RoleId: memberRoleInfo.id,
Role2Id: adminRoleInfo.id
}
await TeamMembers.create(teamAddOwner)
await TeamMemberRole.create(teamRoleOwner)
await user.removeKoins(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)})
res.json(team.toJSON())
} else {
throw Errors.insufficientKoins
}
} catch (e) { next(e) }
})
router.get('/view/:username', async(req, res, next) => {
try {
let queryObj = {
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) {
let {from, limit} = pagination.getPaginationProps(req.query, true)
let postInclude = {
model: teamWall,
include: teamWall.includeOptions(),
limit,
order: [['id', 'DESC']],
}
if (from !== null) {
postInclude.where = {id: {$lte: from}}
}
queryObj.include = [postInclude]
let user = await Team.findOne(queryObj)
if (!user) throw Errors.accountDoesNotExist
if(user.banned) {
throw Errors.teamBanned
}
if (user.teamWallOptOut) {
res.json({teamWalls: []})
}
res.json(Object.assign(user.toJSON(limit)))
} else {
let team = await Team.findOne(queryObj)
if (!team) throw Errors.accountDoesNotExist
if(!team.banned) {
res.json(team.toJSON())
} else {
let team = await Team.findOne(queryObjBanned)
res.json(team.toJSON())
}
}
} catch (err) { next(err) }
})
router.get('/view/:username/picture', async (req, res, next) => {
try {
let user = await Team.findOne({
where: {
username: req.params.username
}
})
if(!user) throw Errors.accountDoesNotExist
let picture = await teamPicture.findOne({
where: {
TeamId: user.id
}
})
if(!picture) {
res.status(404)
res.json({picture: "https://cdn.kaverti.com/teams/unknown-light.png"})
} 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) }
})
router.get('/view/:username/members', async(req, res, next) => {
try {
let user = await Team.findOne({
where: {
username: req.params.username
}
})
if(user) {
if(user.banned) {
res.status(200)
res.json([])
}
let team = await TeamMembers.findAll({
order: [['id', 'DESC']],
where: {
TeamId: user.id
},
include: [
{ model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'description'] }
]
})
res.json(team)
} else {
throw Errors.accountDoesNotExist
}
} catch (e) { next(e) }
})
router.get('/view/:username/userRoles', async(req, res, next) => {
try {
let user = await Team.findOne({
where: {
username: req.params.username
}
})
if(user) {
if(user.banned) {
res.status(200)
res.json([])
}
let team = await TeamMemberRole.findAll({
order: [['id', 'DESC']],
where: {
TeamId: user.id
},
include: [
{ model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture'] },
{ model: TeamRoles, as: 'Role' },
{ model: TeamRoles, as: 'Role2' },
{ model: TeamRoles, as: 'Role3' },
{ model: TeamRoles, as: 'Role4' },
{ model: TeamRoles, as: 'Role5' },
{ model: TeamRoles, as: 'Role6' },
{ model: TeamRoles, as: 'Role7' },
{ model: TeamRoles, as: 'Role8' },
{ model: TeamRoles, as: 'Role9' },
{ model: TeamRoles, as: 'Role10' },
]
})
res.json(team)
} else {
throw Errors.accountDoesNotExist
}
} catch (e) { next(e) }
})
router.get('/view/:username/roles', async(req, res, next) => {
try {
let user = await Team.findOne({
where: {
username: req.params.username
}
})
if(user) {
if(user.banned) {
res.status(200)
res.json([])
}
let team = await TeamRoles.findAll({
order: [['priority', 'ASC']],
where: {
TeamId: user.id
},
})
res.json(team)
} else {
throw Errors.accountDoesNotExist
}
} catch (e) { next(e) }
})
router.get('/view/:username/roles/id/:id', async(req, res, next) => {
try {
let user = await Team.findOne({
where: {
username: req.params.username
}
})
if(user) {
if(user.banned) {
res.status(200)
res.json([])
}
let team = await TeamMemberRole.findAll({
order: [['id', 'DESC']],
where: {
TeamId: user.id,
RoleId: req.params.id
},
include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture'] },
})
res.json(team)
} else {
throw Errors.accountDoesNotExist
}
} catch (e) { next(e) }
})
router.get('/view/:username/roles/name/:id', async(req, res, next) => {
try {
let user = await Team.findOne({
where: {
username: req.params.username
}
})
let userParam = await User.findOne({
where: {
username: req.params.id
}
})
if(user && userParam) {
if(user.banned) {
res.status(200)
res.json([])
}
let team = await TeamMemberRole.findAll({
order: [['id', 'DESC']],
where: {
TeamId: user.id,
UserID: userParam.id
},
include: [ { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture'] }, { model: TeamRoles, as: 'Role' } ]
})
res.json(team)
} else {
throw Errors.accountDoesNotExist
}
} catch (e) { next(e) }
})
router.get('/view/:username/roles/permissions/:id', async(req, res, next) => {
try {
let team = await Team.findOne({
where: {username: req.params.username}
});
let user = await User.findOne({
where: {username: req.params.id}
});
if(user && team) {
let isAuthMem = await TeamMembers.findOne({
where: {UserId: user.id, TeamId: team.id}
});
if (!isAuthMem) {
throw Errors.notInTeam
}
let isAuthRole = await TeamMemberRole.findOne({
where: {UserId: user.id, TeamId: team.id}
})
let isAuthOwner = await Team.findOne({
where: {OwnerId: user.id, id: team.id}
})
let isAuth1 = await TeamRoles.findOne({
where: {id: isAuthRole.RoleId, inviteUsers: true}
})
let isAuth2 = await TeamRoles.findOne({
where: {id: isAuthRole.Role2Id, inviteUsers: true}
})
let isAuth3 = await TeamRoles.findOne({
where: {id: isAuthRole.Role3Id, inviteUsers: true}
})
let isAuth4 = await TeamRoles.findOne({
where: {id: isAuthRole.Role4Id, inviteUsers: true}
})
let isAuth5 = await TeamRoles.findOne({
where: {id: isAuthRole.Role5Id, inviteUsers: true}
})
let isAuth6 = await TeamRoles.findOne({
where: {id: isAuthRole.Role6Id, inviteUsers: true}
})
let isAuth7 = await TeamRoles.findOne({
where: {id: isAuthRole.Role7Id, inviteUsers: true}
})
let isAuth8 = await TeamRoles.findOne({
where: {id: isAuthRole.Role8Id, inviteUsers: true}
})
let isAuth9 = await TeamRoles.findOne({
where: {id: isAuthRole.Role9Id, inviteUsers: true}
})
let isAuth10 = await TeamRoles.findOne({
where: {id: isAuthRole.Role10Id, inviteUsers: true}
})
const allowArray = [
isAuth1,
isAuth2,
isAuth3,
isAuth4,
isAuth5,
isAuth6,
isAuth7,
isAuth8,
isAuth9,
isAuth10,
isAuthOwner
]
let invite = allowArray.some(function (el) {
return el !== null;
});
let isAuth11 = await TeamRoles.findOne({
where: {id: isAuthRole.RoleId, administrator: true}
})
let isAuth12 = await TeamRoles.findOne({
where: {id: isAuthRole.Role2Id, administrator: true}
})
let isAuth13 = await TeamRoles.findOne({
where: {id: isAuthRole.Role3Id, administrator: true}
})
let isAuth14 = await TeamRoles.findOne({
where: {id: isAuthRole.Role4Id, administrator: true}
})
let isAuth15 = await TeamRoles.findOne({
where: {id: isAuthRole.Role5Id, administrator: true}
})
let isAuth16 = await TeamRoles.findOne({
where: {id: isAuthRole.Role6Id, administrator: true}
})
let isAuth17 = await TeamRoles.findOne({
where: {id: isAuthRole.Role7Id, administrator: true}
})
let isAuth18 = await TeamRoles.findOne({
where: {id: isAuthRole.Role8Id, administrator: true}
})
let isAuth19 = await TeamRoles.findOne({
where: {id: isAuthRole.Role9Id, administrator: true}
})
let isAuth20 = await TeamRoles.findOne({
where: {id: isAuthRole.Role10Id, administrator: true}
})
const adminArray = [
isAuth10,
isAuth12,
isAuth13,
isAuth14,
isAuth15,
isAuth16,
isAuth17,
isAuth18,
isAuth19,
isAuth20,
isAuthOwner
]
let administrator = adminArray.some(function (el) {
return el !== null;
});
let isAuth21 = await TeamRoles.findOne({
where: {id: isAuthRole.RoleId, changeTeamMeta: true}
})
let isAuth22 = await TeamRoles.findOne({
where: {id: isAuthRole.Role2Id, changeTeamMeta: true}
})
let isAuth23 = await TeamRoles.findOne({
where: {id: isAuthRole.Role3Id, changeTeamMeta: true}
})
let isAuth24 = await TeamRoles.findOne({
where: {id: isAuthRole.Role4Id, changeTeamMeta: true}
})
let isAuth25 = await TeamRoles.findOne({
where: {id: isAuthRole.Role5Id, changeTeamMeta: true}
})
let isAuth26 = await TeamRoles.findOne({
where: {id: isAuthRole.Role6Id, changeTeamMeta: true}
})
let isAuth27 = await TeamRoles.findOne({
where: {id: isAuthRole.Role7Id, changeTeamMeta: true}
})
let isAuth28 = await TeamRoles.findOne({
where: {id: isAuthRole.Role8Id, changeTeamMeta: true}
})
let isAuth29 = await TeamRoles.findOne({
where: {id: isAuthRole.Role9Id, changeTeamMeta: true}
})
let isAuth30 = await TeamRoles.findOne({
where: {id: isAuthRole.Role10Id, changeTeamMeta: true}
})
const metaArray = [
isAuth21,
isAuth22,
isAuth23,
isAuth24,
isAuth25,
isAuth26,
isAuth27,
isAuth28,
isAuth29,
isAuth30,
isAuthOwner
]
let meta = metaArray.some(function (el) {
return el !== null;
});
let isAuth31 = await TeamRoles.findOne({
where: {id: isAuthRole.RoleId, changeTeamRoles: true}
})
let isAuth32 = await TeamRoles.findOne({
where: {id: isAuthRole.Role2Id, changeTeamRoles: true}
})
let isAuth33 = await TeamRoles.findOne({
where: {id: isAuthRole.Role3Id, changeTeamRoles: true}
})
let isAuth34 = await TeamRoles.findOne({
where: {id: isAuthRole.Role4Id, changeTeamRoles: true}
})
let isAuth35 = await TeamRoles.findOne({
where: {id: isAuthRole.Role5Id, changeTeamRoles: true}
})
let isAuth36 = await TeamRoles.findOne({
where: {id: isAuthRole.Role6Id, changeTeamRoles: true}
})
let isAuth37 = await TeamRoles.findOne({
where: {id: isAuthRole.Role7Id, changeTeamRoles: true}
})
let isAuth38 = await TeamRoles.findOne({
where: {id: isAuthRole.Role8Id, changeTeamRoles: true}
})
let isAuth39 = await TeamRoles.findOne({
where: {id: isAuthRole.Role9Id, changeTeamRoles: true}
})
let isAuth40 = await TeamRoles.findOne({
where: {id: isAuthRole.Role10Id, changeTeamRoles: true}
})
const roleArray = [
isAuth31,
isAuth32,
isAuth33,
isAuth34,
isAuth35,
isAuth36,
isAuth37,
isAuth38,
isAuth39,
isAuth40,
isAuthOwner
]
let role = roleArray.some(function (el) {
return el !== null;
});
if (team) {
if (team.banned) {
res.status(200)
res.json({administrator: false, inviteUsers: false})
}
res.json({inviteUsers: invite, administrator: administrator, changeTeamMeta: meta, changeTeamRoles: role})
} else {
throw Errors.accountDoesNotExist
}
} else {
throw Errors.accountDoesNotExist
}
} catch (e) { next(e) }
})
router.get('/', async(req, res, next) => {
try {
let sortFields = {
createdAt: 'X.id',
username: 'X.username',
};
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
let havingClause = 'Having Teams.banned = false';
if(req.query.search) {
//I.e. if there is not already a HAVING clause
if(!havingClause.length) {
havingClause = 'HAVING ';
} else {
havingClause += ' AND ';
}
havingClause += 'Team.username LIKE $search';
}
let sql = `
SELECT X.username, X.approved, X.name, X.verified, X.picture, X.id, X.forumEnabled, X.description, X.banned, X.createdAt, X.updatedAt
FROM (
SELECT Teams.*
FROM Teams
GROUP BY Teams.id
${havingClause}
) as X
GROUP BY X.id
ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'DESC' : 'ASC'}
LIMIT 30
OFFSET ${offset}
`;
let users = await sequelize.query(sql, {
model: Team,
bind: { search: req.query.search + '%' }
});
res.json(users)
} catch (e) { next(e) }
})
router.put('/join/:username', auth, async(req, res, next) => {
try {
await Ban.ReadOnlyMode(req.userData.UserId)
let team = await Team.findOne({
where: { username: req.params.username }
})
if(team) {
if(team.banned) {
throw Errors.teamBanned
}
if(team.inviteOnly) {
throw Errors.inviteOnly
}
let queryObj3 = {
where: {userId: req.userData.UserId, teamId: team.id},
}
let teamJoinTest = await TeamMembers.findOne(queryObj3)
if(teamJoinTest) {
throw Errors.joinedTeam
}
let role = await TeamRoles.findOne({
where: {teamId: team.id, name: "Members"}
})
let join = {
userId: req.userData.UserId,
teamId: team.id,
roles: {"deprecated": "deprecated"}
}
console.log(role)
let roleUser = {
UserId: req.userData.UserId,
TeamId: team.id,
RoleId: role.id
}
await TeamMembers.create(join)
await TeamMemberRole.create(roleUser)
res.status(200)
res.json({success: true})
} else {
throw Errors.teamDoesNotExist
}
} catch (e) { next(e) }
})
router.get('/check/:username', auth, async(req, res, next) => {
try {
let team = await Team.findOne({
where: {username: req.params.username}
});
if(team) {
let queryObj3 = {
where: {userId: req.userData.UserId, teamId: team.id},
}
if(team.banned) {
res.status(200)
res.json({success: false})
}
let teamJoinTest = await TeamMembers.findOne(queryObj3)
if (teamJoinTest) {
res.status(200)
res.json({success: true})
} else if (!teamJoinTest) {
res.status(200)
res.json({success: false})
}
} else {
throw Errors.teamDoesNotExist
}
} catch (e) { next(e) }
})
router.put('/leave/:username', auth, async(req, res, next) => {
try {
await Ban.ReadOnlyMode(req.userData.UserId)
let team = await Team.findOne({
where: { username: req.params.username }
})
if(team) {
if(team.banned) {
throw Errors.teamBanned
}
let queryObj3 = {
where: {userId: req.userData.UserId, teamId: team.id},
}
let teamLeaveTeam = await TeamMembers.findOne(queryObj3)
if(teamLeaveTeam) {
let queryObj4 = {
where: {UserId: req.userData.UserId, TeamId: team.id},
}
let teamLeaveRoles = await TeamMemberRole.findAll(queryObj4)
await teamLeaveTeam.leaveTeam()
teamLeaveRoles.forEach((TeamMemberRole) => TeamMemberRole.leaveTeam());
res.status(200)
res.json({success: true})
} else {
throw Errors.notInTeam
}
} else {
throw Errors.teamDoesNotExist
}
} catch (e) { next(e) }
})
router.get('/invite/:username', async(req, res, next) => {
try {
let code = await TeamInvite.findOne({
where: {code: req.params.username},
include: [{model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture']}, {model: Team, attributes: { exclude: [ 'banReason' ]}}, {model: TeamRoles, as: 'Role'}]
})
if (code) {
if(code.maxUses === 0) {
res.status(200)
res.json(code.toJSON())
} else if(code.uses >= code.maxUses) {
throw Errors.invalidInvite
} else if(code.uses < code.maxUses) {
res.status(200)
res.json(code.toJSON())
} else {
throw Errors.invalidInvite
}
} else {
throw Errors.invalidInvite
}
} catch (e) { next(e) }
})
router.post('/invite/:code', auth, async(req, res, next) => {
try {
await Ban.ReadOnlyMode(req.userData.UserId)
let code = await TeamInvite.findOne({
where: {code: req.params.code}
})
if (code) {
let team = await Team.findOne({
where: {id: code.TeamId}
})
if (team.banned) {
throw Errors.teamBanned
}
let queryObj3 = {
where: {userId: req.userData.UserId, teamId: team.id},
}
let teamJoinTest = await TeamMembers.findOne(queryObj3)
if (teamJoinTest) {
throw Errors.joinedTeam
}
if(code.maxUses > 0 && code.maxUses === code.uses) {
console.log('failed at maxUses over code.uses')
throw Errors.invalidInvite
} else if(code.maxUses === 0) {
let role = await TeamRoles.findOne({
where: {teamId: team.id, name: "Members"}
})
if(code.RoleId > 0) {
let roleLookup = await TeamRoles.findOne({
where: {id: code.RoleId, TeamId: team.id}
})
if(roleLookup) {
let join = {
userId: req.userData.UserId,
teamId: team.id,
roles: {"deprecated": "deprecated"}
}
let roleUser = {
UserId: req.userData.UserId,
TeamId: team.id,
RoleId: role.id,
Role2Id: roleLookup.id
}
await TeamInvite.update({ uses: + 1}, {
where: {id: code.id, TeamId: team.id}
})
await TeamMembers.create(join)
await TeamMemberRole.create(roleUser)
res.status(200)
res.json({success: true})
} else {
let join = {
userId: req.userData.UserId,
teamId: team.id,
roles: {"deprecated": "deprecated"}
}
console.log(role)
let roleUser = {
UserId: req.userData.UserId,
TeamId: team.id,
RoleId: role.id
}
await TeamInvite.update({ uses: + 1}, {
where: {id: code.id, TeamId: team.id}
})
await TeamMembers.create(join)
await TeamMemberRole.create(roleUser)
res.status(200)
res.json({success: true})
}
} else {
let join = {
userId: req.userData.UserId,
teamId: team.id,
roles: {"deprecated": "deprecated"}
}
let roleUser = {
UserId: req.userData.UserId,
TeamId: team.id,
RoleId: role.id
}
await TeamInvite.update({ uses: + 1}, {
where: {id: code.id, TeamId: team.id}
})
await TeamMembers.create(join)
await TeamMemberRole.create(roleUser)
res.status(200)
res.json({success: true})
}
} else if(code.uses < code.maxUses) {
let role = await TeamRoles.findOne({
where: {teamId: team.id, name: "Members"}
})
let join = {
userId: req.userData.UserId,
teamId: team.id,
roles: {"deprecated": "deprecated"}
}
let roleUser = {
UserId: req.userData.UserId,
TeamId: team.id,
RoleId: role.id,
}
await TeamInvite.update({ uses: + 1}, {
where: {id: code.id, TeamId: team.id}
})
await TeamMembers.create(join)
await TeamMemberRole.create(roleUser)
res.status(200)
res.json({success: true})
} else {
console.log('failed at second last else')
throw Errors.invalidInvite
}
} else {
console.log('failed at last else')
throw Errors.invalidInvite
}
} catch (e) { next(e) }
})
module.exports = router;

1540
v1_routes/team_admin.js Normal file

File diff suppressed because it is too large Load Diff

144
v1_routes/team_wall.js Normal file
View File

@ -0,0 +1,144 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let { User, Team, teamWall, Notification, Ban, Sequelize, sequelize } = require('../models')
let pagination = require('../lib/pagination.js')
const rateLimit = require("express-rate-limit");
const postLimiter = rateLimit({
windowMs: 60000,
max: 10,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 10 requests to this endpoint per minute.\",\"status\":429}]}"
});
router.get('/show/:username', async(req, res, next) => {
try {
let { limit } = pagination.getPaginationProps(req.query, true)
let postInclude = {
model: userWall,
limit,
order: [['id', 'DESC']]
}
let user = await teamWall.findOne(postInclude)
if (!user) throw Errors.accountDoesNotExist
let meta = await user.getMeta(limit)
let Posts = await teamWall.find(postInclude)
res.json(Object.assign( user.toJSON(limit), { meta, Posts } )) } catch (e) { next(e) }
})
router.all('*', auth, (req, res, next) => {
if(req.userData.loggedIn) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.post('/post', postLimiter, auth, async(req, res, next) => {
let queryObj = {
attributes: {include: ['emailVerified']},
where: {username: req.userData.username}
}
let getSessionId = {
attributes: {include: ['id']},
where: {username: req.userData.username}
}
let teamToId = {
attributes: {include: ['id']},
where: {username: req.body.username}
}
let user = await User.findOne(queryObj)
let sessionId = await User.findOne(getSessionId)
let getWallUser = await Team.findOne(teamToId)
try {
//Will throw an error if banned
await Ban.ReadOnlyMode(req.userData.UserId)
if(getWallUser.banned) {
throw Errors.teamBanned
}
if (req.body.mentions) {
uniqueMentions = Notification.filterMentions(req.body.mentions)
}
if (!user.emailVerified) {
throw Errors.verifyEmail
}
if(getWallUser.teamWallOptOut) {
throw Errors.userWallOptOut
}
if(teamToId.id == "null") throw Errors.sequelizeValidation(Sequelize, {
error: 'User doesn\'t exist',
path: 'id'
})
user = await teamWall.findOne({ where: {
fromUserId: sessionId.id
}})
post = await teamWall.create({content: req.body.content, postNumber: "0", teamId: getWallUser.id, fromUserId: req.userData.UserId})
if (uniqueMentions.length) {
let ioUsers = req.app.get('io-users')
let io = req.app.get('io')
for (const mention of uniqueMentions) {
let mentionNotification = await Notification.createPostNotification({
usernameTo: mention,
userFrom: user,
type: 'mention',
post
})
if (mentionNotification) {
await mentionNotification.emitNotificationMessage(ioUsers, io)
}
}
}
res.json({success: true})
} catch (e) {
next(e)
}
})
router.all('*', auth, (req, res, next) => {
if(!req.userData.admin) {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
} else {
next()
}
})
router.delete('/:post_id', auth, async(req, res, next) => {
try {
if(!req.userData.admin){
res.status(401)
res.json({errors: [Errors.requestNotAuthorized]})
}
let post = await userWall.findByPk(req.params.post_id)
if(!post) throw Errors.sequelizeValidation(Sequelize, {
error: 'post does not exist',
path: 'id'
})
await post.update({ content: '[This post has been removed by an administrator]', removed: true })
res.json({ success: true })
} catch (e) { next(e) }
})
module.exports = router

193
v1_routes/thread.js Normal file
View File

@ -0,0 +1,193 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors.js')
let { User, Thread, Notification, Category, Post, Ban, Report, Sequelize } = require('../models')
let pagination = require('../lib/pagination.js')
const rateLimit = require("express-rate-limit");
const postLimiter = rateLimit({
windowMs: 60000,
max: 10,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 10 requests to this endpoint per minute.\",\"status\":429}]}"
});
router.get('/:thread_id', async(req, res, next) => {
try {
let { from, limit } = pagination.getPaginationProps(req.query)
let thread = await Thread.findByPk(req.params.thread_id, {
include: Thread.includeOptions(from, limit)
})
if(!thread) throw Errors.invalidParameter('id', 'thread does not exist')
let meta = thread.getMeta(limit)
res.json(Object.assign( thread.toJSON(), { meta } ))
} catch (e) { next(e) }
})
//Only logged in routes
router.all('*', auth, (req, res, next) => {
if(req.userData.loggedIn) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.post('/', postLimiter, auth, async(req, res, next) => {
let validationErrors = []
try {
await Ban.ReadOnlyMode(req.userData.UserId)
let category = await Category.findOne({ where: {
value: req.body.category
}})
if (req.body.category == "ALL") throw Errors.selectCategory
if (req.body.category == "") throw Errors.selectCategory
if (!category) throw Errors.invalidCategory
if (category.locked && !req.userData.admin) throw Errors.lockedCategory
let user = await User.findOne({ where: {
username: req.userData.username
}})
if(!user.emailVerified) {
throw Errors.verifyEmail
}
let thread = await Thread.create({
name: req.body.name
})
await thread.increment('postsCount')
await thread.setCategory(category)
await thread.setUser(user)
user = await User.findOne({ where: {
username: req.userData.username
}})
if(req.body.replyingToId) {
let replyingToPost1 = Post.findByPk(
req.body.replyingToId, { include: [Thread, { model: User, attributes: ['username'] }] }
)
console.log(replyingToPost1)
replyingToPost = await Post.getReplyingToPost(
req.body.replyingToId, thread, replyingToPost1
)
var post = await Post.create({ content: req.body.content, postNumber: thread.postsCount })
await post.setReplyingTo(replyingToPost)
await replyingToPost.addReplies(post)
let replyNotification = await Notification.createPostNotification({
usernameTo: replyingToPost.User.username,
userFrom: user,
type: 'reply',
post: post
})
await replyNotification.emitNotificationMessage(
req.app.get('io-users'),
req.app.get('io')
)
} else {
var post = await Post.create({ content: req.body.content, postNumber: thread.postsCount })
}
await post.setUser(user)
await post.setThread(thread)
res.json(await thread.reload({
include: [
{ model: User, attributes: ['username', 'createdAt', 'updatedAt', 'id'] },
Category
]
}))
req.app.get('io').to('index').emit('new thread', {
name: category.name,
value: category.value
})
} catch (e) { next(e) }
})
//Only admin routes
router.all('*', auth, async(req, res, next) => {
let user = await User.findOne({ where: {
username: req.userData.username
}})
if(!user) throw Errors.requestNotAuthorized
if(req.userData.admin && user.admin) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.sessionAdminProtection]
})
}
})
router.delete('/:thread_id', auth, async(req, res, next) => {
try {
let thread = await Thread.findByPk(req.params.thread_id)
if(!thread) {
throw Errors.sequelizeValidation(Sequelize, {
error: 'invalid thread id',
value: req.params.thread_id
})
} else {
//Find all posts with reports and get reports
//Then delete those reports
//Temporary fix because cascade is not working
let posts = await Post.findAll({
where: {
ThreadId: thread.id
},
include: [Report]
})
let reports = posts
.map(post => post.Reports)
.reduce((a, b) => a.concat(b), [])
let destroyPromises = reports.map(report => report.destroy())
await Promise.all(destroyPromises)
await Post.destroy({ where: { ThreadId: thread.id } })
await thread.destroy()
res.json({ success: true })
}
} catch (e) { next(e) }
})
router.put('/:thread_id', auth, async(req, res, next) => {
try {
let thread = await Thread.findByPk(req.params.thread_id)
if(!thread) {
res.status(400)
res.json({ errors:
[Errors.invalidParameter('threadId', 'thread does not exist')]
})
} else {
if(req.body.locked) {
await thread.update({ locked: true })
} else {
await thread.update({ locked: false })
}
res.json({ success: true })
}
} catch (e) { next(e) }
})
module.exports = router

19
v1_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;

384
v1_routes/user.js Normal file
View File

@ -0,0 +1,384 @@
/*
@swagger
components:
schemas:
Book:
type: object
required:
- title
- author
- finished
properties:
id:
type: integer
description: The auto-generated id of the book.
title:
type: string
description: The title of your book.
author:
type: string
description: Who wrote the book?
finished:
type: boolean
description: Have you finished reading it?
createdAt:
type: string
format: date
description: The date of the record creation.
example:
title: The Pragmatic Programmer
author: Andy Hunt / Dave Thomas
finished: true
*/
let bcrypt = require('bcryptjs')
let multer = require('multer')
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
var Recaptcha = require('express-recaptcha').RecaptchaV3;
var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd');
var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
const Errors = require('../lib/errors.js')
var format = require('date-format');
let {
User, Post, ProfilePicture, Item, userWall, StaffApplications, Inventory, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
} = require('../models')
let pagination = require('../lib/pagination.js')
const sgMail = require('@sendgrid/mail');
const MailGen = require('mailgen')
const crypto = require("crypto")
const cryptoRandomString = require("crypto-random-string")
const rateLimit = require("express-rate-limit");
const emailLimiter = rateLimit({
windowMs: 60000,
max: 1, // limit each IP to 100 requests per windowMs
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint per minute.\",\"status\":429}]}"
});
function setUserSession(req, res, username, UserId, admin) {
req.userData.loggedIn = true
req.userData.username = username
req.userData.UserId = UserId
res.cookie('username', username)
if(admin) { req.userData.admin = true }
}
router.post('/oidfhuisadhi8243', emailLimiter, async(req, res) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
email: req.body.email,
hash: req.body.password,
passkey: req.body.passkey,
admin: false,
bodyColor: '#fffff',
headColor: '#fffff',
leftLegColor: '#fffff',
rightLegColor: '#fffff',
leftArmColor: '#fffff',
rightArmColor: '#fffff',
koins: '250',
currency2: '0',
picture: 'default',
booster: false,
theme: 'light',
emailToken: crypto(16)
}
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
setUserSession(req, res, user.username, user.id, userParams.admin)
res.json(user.toJSON())
} catch (e) { next(e) }
})
router.post('/', emailLimiter, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
email: req.body.email,
hash: req.body.password,
passkey: req.body.passkey,
admin: false,
bodyColor: '#ffffff',
headColor: '#ffffff',
leftLegColor: '#ffffff',
rightLegColor: '#ffffff',
leftArmColor: '#ffffff',
rightArmColor: '#ffffff',
koins: '250',
currency2: '0',
picture: 'default',
developerMode: false,
emailVerified: false,
theme: 'light',
emailToken: cryptoRandomString({length: 16})
}
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
setUserSession(req, res, user.username, user.id, userParams.admin)
res.json(user.toJSON())
} catch (e) { next(e) }
})
router.post('/job-application', auth, async(req, res, next) => {
try {
let userParams = {
username: req.body.username,
dob: req.body.dob,
email: req.body.email,
whyWork: req.body.whyWork,
otherForm: req.body.otherForm,
experience: req.body.experience,
selectedOption: req.body.selectedOption,
}
await StaffApplications.submitApplication(userParams)
} catch (e) { next(e) }
})
router.get('/:username', async(req, res, next) => {
try {
let queryObj = {
attributes: {exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'deleteCode', 'jwtOffset']},
where: {username: req.params.username}
}
if (req.query.posts) {
let {from, limit} = pagination.getPaginationProps(req.query, true)
let postInclude = {
model: Post,
include: Post.includeOptions(),
limit,
order: [['id', 'DESC']]
}
if (from !== null) {
postInclude.where = {id: {$lte: from}}
}
queryObj.include = [postInclude]
let user = await User.findOne(queryObj)
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)
let postInclude = {
model: userWall,
include: userWall.includeOptions(),
limit,
order: [['id', 'DESC']],
}
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) {
res.status(200)
res.json({userWalls: []})
}
res.json(Object.assign(user.toJSON(limit)))
} else if (req.query.threads) {
let queryString = ''
Object.keys(req.query).forEach(query => {
queryString += `&${query}=${req.query[query]}`
})
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 if(req.query.inventory) {
let {from, limit} = pagination.getPaginationProps(req.query, true)
let userLookup = await User.findOne({
where: {
username: req.params.username
}
})
let marketplace = await Inventory.findAll({
where: {
UserId: userLookup.id
}
})
let postInclude = {
model: Inventory,
include: { 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
res.json(user.toJSON())
}
} catch (err) { next(err) }
})
router.post('/login', async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip, req.body.email)
let user = await User.findOne({ where: {
username: req.body.username
}})
if(user) {
if(await user.comparePassword(req.body.password)) {
await Ip.createIfNotExists(req.ip, user)
setUserSession(req, res, user.username, user.id, user.admin)
res.json({
username: user.username,
admin: user.admin,
success: true
})
} else {
res.status(401)
res.json({
errors: [Errors.invalidLoginCredentials]
})
}
} else {
res.status(401)
res.json({
errors: [Errors.invalidLoginCredentials]
})
}
} catch (err) { next(err) }
})
router.post('/:username/logout', auth, async(req, res) => {
req.userData.destroy(() => {
res.clearCookie('username')
res.clearCookie('admin')
res.json({
success: true
})
})
})
router.get('/:username/picture', async(req, res, next) => {
try {
let user = await User.findOne({
where: {
username: req.params.username
}
})
if(!user) throw Errors.accountDoesNotExist
let picture = await ProfilePicture.findOne({
where: {
UserId: user.id
}
})
if(!picture) {
res.status(404)
res.end('')
} else {
res.writeHead(200, {
'Content-Type': picture.mimetype,
'Content-disposition': 'attachment;filename=profile',
'Content-Length': picture.file.length
})
res.end(new Buffer(picture.file, 'binary'))
}
} catch (e) { next(e) }
})
router.get('/', async(req, res, next) => {
try {
let sortFields = {
createdAt: 'X.id',
username: 'X.username',
threadCount: 'threadCount',
postCount: 'postCount'
};
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
let havingClause = '';
if(req.query.role === 'admin') {
havingClause = 'HAVING Users.admin = true';
} else if(req.query.role === 'user') {
havingClause = 'HAVING Users.admin = false';
} else {
havingClause = 'Having Users.hidden = false';
}
if(req.query.search) {
//I.e. if there is not already a HAVING clause
if(!havingClause.length) {
havingClause = 'HAVING ';
} else {
havingClause += ' AND ';
}
havingClause += 'Users.username LIKE $search';
}
let sql = `
SELECT X.username, X.admin, X.picture, X.level, X.levelProgress, X.bot, X.booster, X.description, X.bodyColor, X.headColor, X.leftLegColor, X.rightLegColor, X.leftArmColor, X.rightArmColor, X.hidden, X.system, X.createdAt, X.contributor, X.postCount, COUNT(Threads.id) as threadCount
FROM (
SELECT Users.*, COUNT(Posts.id) as postCount
FROM Users
LEFT OUTER JOIN Posts
ON Users.id = Posts.UserId
GROUP BY Users.id
${havingClause}
) as X
LEFT OUTER JOIN threads
ON X.id = Threads.UserId
GROUP BY X.id
ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'DESC' : 'ASC'}
LIMIT 30
OFFSET ${offset}
`;
let users = await sequelize.query(sql, {
model: User,
bind: { search: req.query.search + '%' }
});
res.json(users)
} catch (e) { next(e) }
})
module.exports = router;

123
v1_routes/user_passkey.js Normal file
View File

@ -0,0 +1,123 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors.js')
let {
User, Ip, Ban
} = require('../models')
const cryptoRandomString = require("crypto-random-string")
const rateLimit = require("express-rate-limit");
const jwt = require('jsonwebtoken');
const emailLimiter = rateLimit({
windowMs: 60000,
max: 1, // limit each IP to 100 requests per windowMs
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint per minute.\",\"status\":429}]}"
});
const registerLimit = rateLimit({
windowMs: 60000 * 5, // 5 minutes
max: 1, // limit each IP to 100 requests per windowMs
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint every 5 minutes.\",\"status\":429}]}"
});
router.post('/oidfhuisadhi8243', emailLimiter, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
email: req.body.email,
hash: req.body.password,
passkey: req.body.passkey,
admin: false,
bodyColor: '#ffffff',
headColor: '#ffffff',
leftLegColor: '#ffffff',
rightLegColor: '#ffffff',
leftArmColor: '#ffffff',
rightArmColor: '#ffffff',
koins: '250',
currency2: '0',
picture: 'default',
developerMode: false,
emailVerified: false,
theme: 'light',
emailToken: cryptoRandomString({length: 16})
}
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
const accessToken = jwt.sign({ username: user.username, admin: user.admin, executive: user.executive, email: user.email, UserId: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
res.json({
accessToken
});
} catch (e) { next(e) }
})
router.post('/null', emailLimiter, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
email: req.body.email,
hash: req.body.password,
passkey: req.body.passkey,
admin: false,
bodyColor: '#ffffff',
headColor: '#ffffff',
leftLegColor: '#ffffff',
rightLegColor: '#ffffff',
leftArmColor: '#ffffff',
rightArmColor: '#ffffff',
koins: '250',
currency2: '0',
picture: 'default',
developerMode: false,
emailVerified: false,
theme: 'light',
emailToken: cryptoRandomString({length: 16})
}
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
const accessToken = jwt.sign({ username: user.username, admin: user.admin, executive: user.executive, email: user.email, UserId: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
res.json({
accessToken
});
} catch (e) { next(e) }
})
router.post('/register', emailLimiter, async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
email: req.body.email,
hash: req.body.password,
passkey: req.body.passkey,
admin: false,
bodyColor: '#ffffff',
headColor: '#ffffff',
leftLegColor: '#ffffff',
rightLegColor: '#ffffff',
leftArmColor: '#ffffff',
rightArmColor: '#ffffff',
koins: '250',
currency2: '0',
picture: 'default',
developerMode: false,
emailVerified: false,
theme: 'light',
emailToken: cryptoRandomString({length: 16})
}
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
const accessToken = jwt.sign({ username: user.username, admin: user.admin, executive: user.executive, email: user.email, UserId: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
res.json({
accessToken
});
} catch (e) { next(e) }
})
module.exports = router

View File

@ -59,7 +59,7 @@ router.post('/post', postLimiter, auth, async(req, res, next) => {
let getWallUser = await User.findOne(usernameToUserId)
try {
//Will throw an error if banned
await Ban.ReadOnlyMode(req.userData.id)
await Ban.ReadOnlyMode(req.userData.UserId)
if (req.body.mentions) {
uniqueMentions = Notification.filterMentions(req.body.mentions)
@ -82,7 +82,7 @@ router.post('/post', postLimiter, auth, async(req, res, next) => {
fromUserId: sessionId.id
}})
post = await userWall.create({content: req.body.content, postNumber: "0", userId: getWallUser.id, fromUserId: req.userData.id})
post = await userWall.create({content: req.body.content, postNumber: "0", userId: getWallUser.id, fromUserId: req.userData.UserId})
if (uniqueMentions.length) {
let ioUsers = req.app.get('io-users')

26
v1_routes/userinfo.js Normal file
View File

@ -0,0 +1,26 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/authUserInfo')
const Errors = require('../lib/errors')
let { User, Ban } = require('../models')
router.get('/', auth, auth, async(req, res, next) => {
try {
let queryObj = {
attributes: {exclude: ['hash', 'currency2', 'emailToken', 'passwordResetToken', 'deleteCode', 'deleteEnabled', 'jwtOffset']},
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
if(!user) {
res.status(200)
res.json({
errors: ["Please login to use this endpoint, idk what you expected"]
})
}
res.status(200)
res.json(user.toJSON())
} catch (err) { next(err) }
})
module.exports = router

750
v1_routes/userutils.js Normal file
View File

@ -0,0 +1,750 @@
require("dotenv").config();
let bcrypt = require('bcryptjs')
let multer = require('multer')
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
var Recaptcha = require('express-recaptcha').RecaptchaV3;
var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd');
var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
const Errors = require('../lib/errors.js')
var format = require('date-format');
var speakeasy = require('speakeasy');
var secret = speakeasy.generateSecret();
let {
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");
const DOMAIN = 'mail.kaverti.com';
const mg = mailgun({apiKey: "key-d5c9840762ac5756d8f25b80b6a398e6", domain: DOMAIN});
const MailGen = require('mailgen')
const crypto = require("crypto")
const cryptoRandomString = require("crypto-random-string")
const rateLimit = require("express-rate-limit");
const moment = require("moment")
const emailLimiter = rateLimit({
windowMs: 60000,
max: 1, // limit each IP to 100 requests per windowMs
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint per minute.\",\"status\":429}]}"
});
let conversationController = require('../controllers/conversation');
const jwt = require('jsonwebtoken');
let config = require('../config/server.js')
router.post('/oidfhuisadhi8243', async(req, res) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
email: req.body.email,
hash: req.body.password,
passkey: req.body.passkey,
admin: false,
bodyColor: '#ffffff',
headColor: '#ffffff',
leftLegColor: '#ffffff',
rightLegColor: '#ffffff',
leftArmColor: '#ffffff',
rightArmColor: '#ffffff',
koins: '250',
currency2: '0',
picture: 'default',
developerMode: false,
emailVerified: false,
theme: 'light',
emailToken: cryptoRandomString({length: 16})
}
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
const accessToken = jwt.sign({ username: user.username, admin: user.admin, executive: user.executive, email: user.email, UserId: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
res.json({
accessToken
});
} catch (e) { next(e) }
})
router.post('/', async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
let userParams = {
username: req.body.username,
email: req.body.email,
hash: req.body.password,
passkey: req.body.passkey,
admin: false,
bodyColor: '#fffff',
headColor: '#fffff',
leftLegColor: '#fffff',
rightLegColor: '#fffff',
leftArmColor: '#fffff',
rightArmColor: '#fffff',
koins: '250',
currency2: '0',
picture: 'default',
developerMode: false,
booster: false,
theme: 'light',
emailToken: crypto(16)
}
throw Error.registrationsDisabled
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
const accessToken = jwt.sign({ username: user.username, admin: user.admin, executive: user.executive, email: user.email, UserId: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
res.json({
accessToken
});
} catch (e) { next(e) }
})
router.get('/leaderboard', async(req, res, next) => {
try {
let sortFields = {
createdAt: 'X.id',
username: 'X.username',
threadCount: 'threadCount',
postCount: 'postCount'
};
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
let havingClause = '';
if(req.query.role === 'admin') {
havingClause = 'HAVING Users.admin = true';
} else if(req.query.role === 'user') {
havingClause = 'HAVING Users.admin = false';
} else {
havingClause = '';
}
if(req.query.search) {
//I.e. if there is not already a HAVING clause
if(!havingClause.length) {
havingClause = 'HAVING ';
} else {
havingClause += ' AND ';
}
havingClause += 'Users.username LIKE $search';
}
let sql = `
SELECT X.username, X.postCount, COUNT(Threads.id) as threadCount
FROM (
SELECT Users.*, COUNT(Posts.id) as postCount
FROM Users
LEFT OUTER JOIN Posts
ON Users.id = Posts.UserId
GROUP BY Users.id
${havingClause}
) as X
LEFT OUTER JOIN threads
ON X.id = Threads.UserId
GROUP BY X.id
ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'ASC' : 'DESC'}
LIMIT 5
OFFSET ${offset}
`;
let users = await sequelize.query(sql, {
model: User,
bind: { search: req.query.search + '%' }
});
res.json(users)
} catch (e) { next(e) }
})
router.post('/job-application', auth, auth, async(req, res, next) => {
try {
let userParams = {
username: req.body.username,
dob: req.body.dob,
email: req.body.email,
whyWork: req.body.whyWork,
otherForm: req.body.otherForm,
experience: req.body.experience,
selectedOption: req.body.selectedOption,
}
await StaffApplications.submitApplication(userParams)
} catch (e) { next(e) }
})
router.get('/reward', auth, async(req, res, next) => {
try {
if (!req.userData.username) {
throw Errors.requestNotAuthorized
}
let queryObj = {
attributes: {include: ['lastRewardDate', 'koins']},
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
let ms = Date.now() - user.lastRewardDate
let dayMs = 1000 * 60 * 60 * 24
let check = await ms / dayMs < 1
if(user.koins === 9223372036854775808) {
res.status(401)
res.json({
errors: [Errors.koinLimit], koins: user.koins
})
}
if (check) {
res.status(401)
res.json({
errors: [Errors.koinFail], koins: user.koins
})
} else {
user.update({koins: user.koins + 25, lastRewardDate: Date.now()})
res.status(200)
res.json({success: true, koins: user.koins})
}
} catch (err) { next(err) }
}),
router.post('/login', async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip, req.body.email)
let user = await User.findOne({ where: {
username: req.body.username
}})
let userEmail = await User.findOne({ where: {
email: req.body.username
}})
if(user) {
if(await user.comparePassword(req.body.password)) {
await Ip.createIfNotExists(req.ip, user)
const accessToken = jwt.sign({ username: user.username, admin: user.admin, executive: user.executive, email: user.email, UserId: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
res.json({
accessToken
});
} else {
res.status(401)
res.json({
errors: [Errors.invalidLoginCredentials]
})
}
} else {
if (userEmail) {
if (await userEmail.comparePassword(req.body.password)) {
await Ip.createIfNotExists(req.ip, userEmail)
const accessToken = jwt.sign({ username: userEmail.username, admin: userEmail.admin, executive: userEmail.executive, email: userEmail.email, UserId: userEmail.id, loggedIn: true, bot: userEmail.bot, offset: userEmail.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
res.json({
accessToken
});
} else {
res.status(401)
res.json({
errors: [Errors.invalidLoginCredentials]
})
}
} else {
res.status(401)
res.json({
errors: [Errors.invalidLoginCredentials]
})
}
}
} catch (err) { next(err) }
})
router.post('/recovery/send', emailLimiter, async(req, res, next) => {
const mailGenerator = new MailGen({
theme: 'salted',
product: {
name: 'Kaverti',
link: 'https://kaverti.com'
},
})
let queryObj = {
attributes: {include: ['email', 'username', 'emailVerified', 'username', 'passwordResetToken']},
where: {email: req.body.email}
}
let user = await User.findOne(queryObj)
await user.randPasswordReset()
const verifyEmail = {
body: {
name: user.username,
intro: 'Kaverti Password Reset',
action: {
instructions: 'Please reset your account password by clicking the button below, if you didn\'t request a password reset, please ignore and/or delete this email, this code will expire in 24 hours!',
button: {
color: '#33b5e5',
text: 'Reset password',
link: 'https://kaverti.com/recovery/?token=' + user.passwordResetToken,
},
},
},
}
const emailTemplate = mailGenerator.generate(verifyEmail)
const message = {
to: user.email,
from: 'automailer@kaverti.com',
subject: 'Kaverti account recovery',
html: emailTemplate
}
const sendMail = async () => {
try {
mg.messages().send(message, function (error, body) {
if (error) {
res.status(500)
res.send({error: error});
} else {
res.send({message: 'Success, email is on its way.'})
}
});
} catch (error) {
throw new Error(error.message)
}
}
try {
if (user.passwordResetOptOut) {
throw Errors.passwordResetOptOut
} else {
const sent = await sendMail()
if (sent) {
res.send({message: 'Email has been sent, check your spam if you can\'t find it!'})
}
}
} catch {
res.status(400)
res.json({"errors": [{"name": "recoverySend", "message": "Account does not exist, or there was an error sending the email."}]})
}
});
router.put('/recovery/change', async(req, res, next) => {
try {
let user1 = await User.findOne({ where: {
username: req.body.username
}})
let ms = Date.now() - user1.passwordResetExpiry
let dayMs = 1000 * 60 * 60 * 24
let check = await ms / dayMs < 1
if(req.body.token !== undefined) {
if(check) {
let user = await User.findOne({ where: {
username: req.body.username
}})
if(req.body.token === user.passwordResetToken) {
user.recoveryUpdatePassword(req.body.password)
res.status(200)
res.json({success: true})
} else {
throw Errors.invalidPasswordToken
}
} else {
throw Errors.passwordTokenExpiry
}
} else {
throw Errors.invalidPasswordToken
}
} catch (e) { next(e) }
})
router.all('*', auth, (req, res, next) => {
if(req.userData.username) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.get('/:userId/conversations', auth, auth, async(req, res, next) => {
try {
let id = +req.params.userId;
let conversations = await conversationController.getFromUser(id, +req.query.page, req.query.search);
res.json(conversations);
} catch (e) { next(e); }
});
router.put('/delete', emailLimiter, auth, auth, async(req, res, next) => {
const mailGenerator = new MailGen({
theme: 'salted',
product: {
name: 'Kaverti',
link: 'https://kaverti.com'
},
})
let queryObj = {
attributes: {include: ['email', 'username', 'emailVerified', 'username', 'deleteCode']},
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
await user.randAccountDelete()
const verifyEmail = {
body: {
name: user.username,
intro: 'Kaverti Account Deletion',
action: [
{
instructions: 'Sorry to see you go! You can finish deleting your account by using the link below, if this was not you, CHANGE YOUR PASSWORD IMMEDIATELY, AND/OR CONTACT SUPPORT.',
button: {
color: '#ec161d',
text: 'Delete account',
link: 'https://kaverti.com/delete/?token=' + user.deleteCode,
},
},
{
instructions: 'If you don\'t want to delete your account, click the button below, this will deactivate the code.',
button: {
color: '#33b5e5', // Optional action button color
text: 'I do not want to delete my account.',
link: 'https://kaverti.com/delete/undo'
}
}
]
},
}
const emailTemplate = mailGenerator.generate(verifyEmail)
const message = {
to: user.email,
from: 'automailer@kaverti.com',
subject: 'Kaverti account deletion request',
html: emailTemplate
}
const sendMail = async() => {
try {
mg.messages().send(message, function (error, body) {
if (error) {
res.status(500)
res.send({ error: error});
}
else {
res.send({ message: 'Success, email is on its way.'})
}
});
} catch (error) {
throw new Error(error.message)
}
}
try {
if(!user) {
throw Errors.requestNotAuthorized
} else {
const sent = await sendMail()
if (sent) {
res.send({ message: 'Email has been sent, check your spam if you can\'t find it!' })
}
}
} catch (error) {
res.status(400)
res.json({"errors":[{"name":"recoverySend","message":error.message}]})
}
});
router.post('/delete/verify', auth, auth, async(req, res, next) => {
try {
if(!req.userData.username) {
throw Errors.requestNotAuthorized
}
let queryObj = {
attributes: {include: ['deleteCode', 'deleteEnabled']},
where: { username: req.userData.username }
}
let user = await User.findOne(queryObj)
if (user.deleteCode === req.body.token && user.deleteEnabled) {
await user.destroyVerifyPassword(req.body.password)
res.status(200)
res.json({success: true})
} else {
res.status(400)
res.json({
errors: [Errors.invalidParameter('token', 'Invalid token')]
})
}
} catch (e) { next(e) }
})
router.post('/email-verify/send', auth, emailLimiter, auth, async(req, res, next) => {
const mailGenerator = new MailGen({
theme: 'salted',
product: {
name: 'Kaverti',
link: 'https://kaverti.com'
},
})
let queryObj = {
attributes: {include: ['email', 'emailVerified', 'emailToken', 'username']},
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
await user.rand()
const verifyEmail = {
body: {
name: user.username,
intro: 'Welcome to Kaverti',
action: {
instructions: 'Please verify your account via the button below',
button: {
color: '#33b5e5',
text: 'Verify account',
link: 'https://kaverti.com/verify/?token=' + user.emailToken,
},
},
},
}
const emailTemplate = mailGenerator.generate(verifyEmail)
const message = {
to: user.email,
from: 'Kaverti <automailer@kaverti.com>',
subject: 'Kaverti account verification',
html: emailTemplate
}
const sendMail = async() => {
try {
mg.messages().send(message, function (error, body) {
if (error) {
res.status(500)
res.send({ error: error});
}
else {
res.send({ message: 'Success, email is on its way.'})
}
});
} catch (error) {
throw new Error(error.message)
}
}
try {
if(user.emailVerified) {
throw Errors.alreadyVerified
} else {
await sendMail()
}
} catch (error) {
throw new Error(error.message)
}
});
router.get('/email-verify/:token', auth, async(req, res) => {
try {
let queryObj = {
attributes: {include: ['emailToken', 'emailVerified']},
where: { username: req.userData.username }
}
let user = await User.findOne(queryObj)
if (user.emailToken == req.params.token) {
user.emailVerify()
res.status(200)
res.json({
success: "true"
})
} else {
res.status(400)
res.json({
errors: [Errors.invalidParameter('token', 'Invalid token')]
})
}
} catch (e) {
res.status(400)
res.json({
errors: [Errors.invalidParameter('token', 'Invalid token')]
})
}
});
router.get('/contributors', async(req, res, next) => {
try {
let sortFields = {
createdAt: 'X.id',
username: 'X.username',
threadCount: 'threadCount',
postCount: 'postCount'
};
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
let havingClause = '';
if(req.query.role === 'admin') {
havingClause = 'HAVING Users.contributor = true';
} else if(req.query.role === 'user') {
havingClause = 'HAVING Users.contributor = true';
} else {
havingClause = 'HAVING Users.contributor = true';
}
if(req.query.search) {
//I.e. if there is not already a HAVING clause
if(!havingClause.length) {
havingClause = 'HAVING ';
} else {
havingClause += ' AND ';
}
havingClause += 'Users.username LIKE $search';
}
let sql = `
SELECT X.username, X.admin, X.level, X.levelProgress, X.bot, X.booster, X.description, X.bodyColor, X.headColor, X.leftLegColor, X.rightLegColor, X.leftArmColor, X.rightArmColor, X.hidden, X.system, X.createdAt, X.contributor, X.postCount, COUNT(Threads.id) as threadCount
FROM (
SELECT Users.*, COUNT(Posts.id) as postCount
FROM Users
LEFT OUTER JOIN Posts
ON Users.id = Posts.UserId
GROUP BY Users.id
${havingClause}
) as X
LEFT OUTER JOIN threads
ON X.id = Threads.UserId
GROUP BY X.id
ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'DESC' : 'ASC'}
LIMIT 30
OFFSET ${offset}
`;
let users = await sequelize.query(sql, {
model: User,
bind: { search: req.query.search + '%' }
});
res.json(users)
} catch (e) { next(e) }
})
router.put('/preferences', auth, async(req, res, next) => {
try {
if(!req.userData.username) {
throw Errors.requestNotAuthorized
}
await Ban.ReadOnlyMode(req.userData.UserId)
if(req.autosan.body.description !== undefined) {
let user = await User.update({ description: req.autosan.body.description }, { where: {
username: req.userData.username
}})
res.json({ success: true })
} else if(
req.body.currentPassword !== undefined &&
req.body.newPassword !== undefined
) {
let user = await User.findOne({
where: {
username: req.userData.username
}
})
await user.updatePassword(req.body.currentPassword, req.body.newPassword)
res.json({success: true})
} else if(
req.body.emailCurrentPassword !== undefined &&
req.body.newEmail !== undefined
) {
let user = await User.findOne({where: {
username: req.userData.username
}})
await user.updateEmail(req.body.emailCurrentPassword, req.body.newEmail)
res.json({ success: true })
} else if(
req.body.developerMode !== undefined) {
let user = await User.update({developerMode: req.autosan.body.developerMode}, {
where: {
username: req.userData.username
}
})
res.json({success: true})
} else if(
req.body.userWallOptOut !== undefined
) {
let user = await User.update({userWallOptOut: req.autosan.body.userWallOptOut}, {
where: {
username: req.userData.username
}
})
res.json({ success: true })
} else if(
req.body.username !== undefined &&
req.body.changeUsername !== undefined &&
req.body.password !== undefined
) {
let user = await User.findOne({where: {
username: req.userData.username
}})
if(user.koins >= 200) {
let user = await User.findOne({where: {
username: req.userData.username
}})
if(user.koins >= 200) {
let update = await user.updateUsername(req.body.username, req.body.password)
if(update) {
res.json({success: true})
} else {
throw Errors.invalidLoginCredentials
}
} else {
throw Errors.insufficientKoins
}
} else {
throw Errors.insufficientKoins
}
} else if(
req.body.twoFactorGetCode
) {
let user = await User.findOne({where: {
username: req.userData.username
}})
throw Errors.featureDisabled
function getTwoFactorAuthenticationCode() {
const secretCode = speakeasy.generateSecret({
name: "Kaverti",
});
return {
otpauthUrl : secretCode.otpauth_url,
base32: secretCode.base32,
};
}
res.status(200)
res.json({code: getTwoFactorAuthenticationCode().base32, url: getTwoFactorAuthenticationCode().otpauthUrl})
} else if(
req.body.invalidateSession
) {
let user = await User.findOne({where: {
username: req.userData.username
}})
if(user) {
await user.invalidateJWT()
} else {
throw Errors.requestNotAuthorized
}
} else {
res.json({ success: false })
}
} catch (e) { next(e) }
})
router.put('/experiments', auth, auth, async(req, res, next) => {
try {
if(!req.userData.username) {
throw Errors.requestNotAuthorized
}
let queryObj = {
attributes: {include: ['developerMode']},
where: { username: req.userData.username }
}
let user = await User.findOne(queryObj)
if(!user.developerMode) {
throw Errors.deniedExperiments
}
if(req.body.theme !== undefined) {
{
let user = await User.update({theme: req.autosan.body.theme}, {
where: {
username: req.userData.username
}
})
res.json({success: true})
}
} else {
res.json({ success: false })
}
} catch (e) { next(e) }
})
module.exports = router;

View File

@ -0,0 +1,63 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
var Recaptcha = require('express-recaptcha').RecaptchaV3;
const Errors = require('../lib/errors')
let {
User
} = require('../models')
let {
StaffApplications
} = require('../models')
router.post('/', auth, async(req, res, next) => {
try {
if(!req.userData.username) {
throw Errors.requestNotAuthorized
}
let user = await User.findOne({ where: {
username: req.userData.username
}})
if(req.autosan.body.selectedOption === 'moderator') {
throw Errors.noLongerHiringModerators
}
if(req.autosan.body.otherForm === 'moderator') {
throw Errors.noLongerHiringModerators
}
if(req.autosan.body.otherForm === 'Moderator') {
throw Errors.noLongerHiringModerators
}
if(req.autosan.body.otherForm === 'Mod') {
throw Errors.noLongerHiringModerators
}
if(req.autosan.body.otherForm === 'Admin') {
throw Errors.noLongerHiringModerators
}
if(req.autosan.body.otherForm === 'admin') {
throw Errors.noLongerHiringModerators
}
if(req.autosan.body.otherForm === 'mod') {
throw Errors.noLongerHiringModerators
}
if(req.autosan.body.otherForm === 'Administrator') {
throw Errors.noLongerHiringModerators
}
if(req.autosan.body.otherForm === 'administrator') {
throw Errors.noLongerHiringModerators
}
let userParams = {
username: req.userData.username,
dob: req.autosan.body.dob,
email: user.email,
whyWork: req.autosan.body.whyWork,
otherForm: req.autosan.body.otherForm,
experience: req.autosan.body.experience,
suggestions: req.autosan.body.suggestions,
selectedOption: req.autosan.body.selectedOption,
}
await StaffApplications.create(userParams)
res.json({ success: true })
} catch (e) { next(e) }
})
module.exports = router;

63
v2_routes/UnbanRequest.js Normal file
View File

@ -0,0 +1,63 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let { User, Post, UnbanRequest, Sequelize } = require('../models')
const Errors = require('../lib/errors')
router.all('*', auth, (req, res, next) => {
if(req.userData.loggedIn) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.post('/', auth, async(req, res, next) => {
try {
let user = await User.findOne({
where: { username: req.userData.username }
})
let unbanrequest = await UnbanRequest.create({ reason: req.body.reason })
unbanrequest.UserId(user)
res.json({
success: true
})
} catch (e) { next(e) }
})
router.all('*', auth, (req, res, next) => {
if(req.userData.admin) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.get('/', auth, async(req, res, next) => {
try {
let unbanrequest = await UnbanRequest.findAll({
include: [
{ model: User, as: 'UserId' },
{ model: Reason, include: Post.includeOptions() }
]
})
res.json(reports)
} catch (e) { next(e) }
})
router.delete('/:id', auth, async(req, res, next) => {
try {
let unbanrequest = await UnbanRequest.findByPk(req.params.id)
await report.destroy()
res.json({ success: true })
} catch (e) { next(e) }
})
module.exports = router

9
v2_routes/about.js Normal file
View File

@ -0,0 +1,9 @@
let express = require('express')
let router = express.Router()
router.get('/', async(req, res, next) => {
res.status(200)
res.json({"version": 2, "message": "Version 2 of the Kaverti API is only available for Kaverti Canary clients, and API changes can occur at any time", "warning": false, "deprecated": false, "deprecationDate": "Unknown", "client": "Canary"})
})
module.exports = router

View File

@ -0,0 +1,15 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let pagination = require('../lib/pagination')
router.post('/:username', auth, async(req, res) => {
req.userData.destroy.username(() => {
res.json({
success: true
})
})
})
module.exports = router;

View File

@ -1,160 +1,160 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let config = require('../config/server.js')
var fs = require("fs");
const rateLimit = require("express-rate-limit");
const { exec } = require('child_process');
const cryptoRandomString = require("crypto-random-string")
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 3,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 3 requests to this endpoint per minute, if you performed an action such as avatar color changing, those changes were saved, however your avatar was not re-rendered, please re-render your avatar.\",\"status\":429}]}"
});
let { User, Sequelize, Item } = require('../models')
const Errors = require('../lib/errors')
var randomString = (Math.random().toString(36).substring(2))
router.post("/refresh", limiter, auth, async(req, res, next) => {
let user = await User.findOne({ where: {
id: req.userData.id
}
})
if(!user) {
throw Errors.unknown
}
if(user.pantsId) {
var pantsModel = await Item.findOne({
where: {
id: user.pantsId
}
})
}
if(user.hatId) {
var hatModel = await Item.findOne({
where: {
id: user.hatId
}
})
}
if(user.faceId) {
var faceModel = await Item.findOne({
where: {
id: user.faceId
}
})
}
if(user.shirtId) {
var shirtModel = await Item.findOne({
where: {
id: user.shirtId
}
})
}
let rootPathRender = config.rootFolder;
let img2 = cryptoRandomString({length: 32})
let img = img2
var blendFilePath = rootPathRender + "rendering/avatar.blend";
var blendFilePathHs = rootPathRender + "rendering/avatarhs.blend";
var imageSavePath = config.cdnFolder + "user/avatars/full/" + img + ".png";
var imageSavePathHS = config.cdnFolder + "user/avatars/headshot/" + img + ".png";
var pythonFilePath = "rendering/usercontent/"+req.userData.id+".py";
if(hatModel.object) {
var object = hatModel.object
} else {
var object = hatModel.sourceFile
}
if(hatModel.object) {
var includePNG = ''
} else {
var includePNG = '.png'
}
if(user.faceId) {
var faceFilePath = rootPathRender + "rendering/global/" + faceModel.sourceFile;
} else {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
}
if(user.shirtId) {
var shirtFilePath = rootPathRender + "rendering/global/" + shirtModel.sourceFile; // should be set to 0 by default, 0.png will just be a transparent image
} else {
var shirtFilePath = rootPathRender + "rendering/global/0.png"; // should be set to 0 by default, 0.png will just be a transparent image
}
if(user.pantsId) {
var pantsFilePath = rootPathRender + "rendering/global/" + pantsModel.sourceFile; // should be set to 0 by default, 0.png will just be a transparent image
} else {
var pantsFilePath = rootPathRender + "rendering/global/0.png"; // should be set to 0 by default, 0.png will just be a transparent image
}
if(user.hatId) {
var hatFilePath = rootPathRender + "rendering/global/" + object
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+hatModel.sourceFile+includePNG+"')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
} else {
var hat = ''
}
var imports = "import bpy";
var functions = "def hex_to_rgb(value):\n gamma = 2.05\n value = value.lstrip('#')\n lv = len(value)\n fin = list(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))\n r = pow(fin[0] / 255, gamma)\n g = pow(fin[1] / 255, gamma)\n b = pow(fin[2] / 255, gamma)\n fin.clear()\n fin.append(r)\n fin.append(g)\n fin.append(b)\n return tuple(fin)";
var blenderImport = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePath+"')";
var blenderImportHs = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePathHs+"')";
var headColor = "bpy.data.objects['Head'].select = True\nbpy.data.materials['Head'].diffuse_color = hex_to_rgb('"+user.headColor+"')\nbpy.data.materials['Face'].diffuse_color = hex_to_rgb('"+user.headColor+"')";
var leftArmColor = "bpy.data.objects['Left Arm'].select = True\nbpy.data.objects['Left Arm'].active_material.diffuse_color = hex_to_rgb('"+user.leftArmColor+"')";
var rightArmColor = "bpy.data.objects['Right Arm'].select = True\nbpy.data.objects['Right Arm'].active_material.diffuse_color = hex_to_rgb('"+user.rightArmColor+"')";
var bodyColor = "bpy.data.objects['Torso'].select = True\nbpy.data.objects['Torso'].active_material.diffuse_color = hex_to_rgb('"+user.color+"')";
var leftLegColor = "bpy.data.objects['Left Leg'].select = True\nbpy.data.objects['Left Leg'].active_material.diffuse_color = hex_to_rgb('"+user.leftLegColor+"')";
var rightLegColor = "bpy.data.objects['Right Leg'].select = True\nbpy.data.objects['Right Leg'].active_material.diffuse_color = hex_to_rgb('"+user.rightLegColor+"')";
var colors = headColor+"\n"+leftArmColor+"\n"+bodyColor+"\n"+rightArmColor+"\n"+leftLegColor+"\n"+rightLegColor;
if(user.hatId) {
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+hatModel.sourceFile+".png')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
} else {
var hat = ''
}
var face = "face_Image = bpy.data.images.load(filepath = '"+faceFilePath+"')\nbpy.data.textures['Face'].image = face_Image";
var shirt = "shirt_Image = bpy.data.images.load(filepath = '"+shirtFilePath+"')\nbpy.data.textures['Shirt'].image = shirt_Image\nbpy.data.textures['ShirtR'].image = shirt_Image\nbpy.data.textures['ShirtL'].image = shirt_Image";
var pants = "pants_Image = bpy.data.images.load(filepath = '"+pantsFilePath+"')\nbpy.data.textures['PantsR'].image = pants_Image\nbpy.data.textures['PantsL'].image = pants_Image";
var render = "for obj in bpy.data.objects:\n obj.select = False\n bpy.ops.object.select_all(action='SELECT')\nbpy.ops.view3d.camera_to_view_selected()\nscene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePath+"'\nbpy.ops.render.render(write_still = 1)";
var renderHS = "scene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePathHS+"'\nbpy.ops.render.render(write_still = 1)";
var python = imports+"\n"+functions+"\n"+blenderImport+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+render;
var pythonHS = imports+"\n"+functions+"\n"+blenderImportHs+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+renderHS;
fs.writeFile("rendering/usercontent/"+req.userData.id+".py", python, function(err,data){
if(err) { console.log(err) }
})
fs.writeFile("rendering/usercontent/hs/"+req.userData.id+".py", pythonHS, function(err,data){
if(err) { console.log(err) }
})
exec("blender -b -P rendering/usercontent/"+req.userData.id+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
});
exec("blender -b -P rendering/usercontent/hs/"+req.userData.id+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
res.status(200)
res.json({success: true})
});
user.update({picture: img})
});
router.put('/colors', auth, async (req, res, next) => {
try {
let user = await User.findOne({
where: {id: req.userData.id}
})
if(user) {
user.update({
color: req.body.color,
headColor: req.body.headColor,
leftArmColor: req.body.leftArmColor,
rightArmColor: req.body.rightArmColor,
leftLegColor: req.body.leftLegColor,
rightLegColor: req.body.rightLegColor,
})
res.json({success: true})
} else {
throw Errors.unknown
}
} catch (e) { next(e) }
})
module.exports = router;
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let config = require('../config/server.js')
var fs = require("fs");
const rateLimit = require("express-rate-limit");
const { exec } = require('child_process');
const cryptoRandomString = require("crypto-random-string")
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 3,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 3 requests to this endpoint per minute, if you performed an action such as avatar color changing, those changes were saved, however your avatar was not re-rendered, please re-render your avatar.\",\"status\":429}]}"
});
let { User, Sequelize, Item } = require('../models')
const Errors = require('../lib/errors')
var randomString = (Math.random().toString(36).substring(2))
router.post("/refresh", limiter, auth, async(req, res, next) => {
let user = await User.findOne({ where: {
id: req.userData.id
}
})
if(!user) {
throw Errors.unknown
}
if(user.pantsId) {
var pantsModel = await Item.findOne({
where: {
id: user.pantsId
}
})
}
if(user.hatId) {
var hatModel = await Item.findOne({
where: {
id: user.hatId
}
})
}
if(user.faceId) {
var faceModel = await Item.findOne({
where: {
id: user.faceId
}
})
}
if(user.shirtId) {
var shirtModel = await Item.findOne({
where: {
id: user.shirtId
}
})
}
let rootPathRender = config.rootFolder;
let img2 = cryptoRandomString({length: 32})
let img = img2
var blendFilePath = rootPathRender + "rendering/avatar.blend";
var blendFilePathHs = rootPathRender + "rendering/avatarhs.blend";
var imageSavePath = config.cdnFolder + "user/avatars/full/" + img + ".png";
var imageSavePathHS = config.cdnFolder + "user/avatars/headshot/" + img + ".png";
var pythonFilePath = "rendering/usercontent/"+req.userData.id+".py";
if(hatModel.object) {
var object = hatModel.object
} else {
var object = hatModel.sourceFile
}
if(hatModel.object) {
var includePNG = ''
} else {
var includePNG = '.png'
}
if(user.faceId) {
var faceFilePath = rootPathRender + "rendering/global/" + faceModel.sourceFile;
} else {
var faceFilePath = rootPathRender + "rendering/global/defaultFace.png";
}
if(user.shirtId) {
var shirtFilePath = rootPathRender + "rendering/global/" + shirtModel.sourceFile; // should be set to 0 by default, 0.png will just be a transparent image
} else {
var shirtFilePath = rootPathRender + "rendering/global/0.png"; // should be set to 0 by default, 0.png will just be a transparent image
}
if(user.pantsId) {
var pantsFilePath = rootPathRender + "rendering/global/" + pantsModel.sourceFile; // should be set to 0 by default, 0.png will just be a transparent image
} else {
var pantsFilePath = rootPathRender + "rendering/global/0.png"; // should be set to 0 by default, 0.png will just be a transparent image
}
if(user.hatId) {
var hatFilePath = rootPathRender + "rendering/global/" + object
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+hatModel.sourceFile+includePNG+"')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
} else {
var hat = ''
}
var imports = "import bpy";
var functions = "def hex_to_rgb(value):\n gamma = 2.05\n value = value.lstrip('#')\n lv = len(value)\n fin = list(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))\n r = pow(fin[0] / 255, gamma)\n g = pow(fin[1] / 255, gamma)\n b = pow(fin[2] / 255, gamma)\n fin.clear()\n fin.append(r)\n fin.append(g)\n fin.append(b)\n return tuple(fin)";
var blenderImport = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePath+"')";
var blenderImportHs = "bpy.ops.wm.open_mainfile(filepath='"+blendFilePathHs+"')";
var headColor = "bpy.data.objects['Head'].select = True\nbpy.data.materials['Head'].diffuse_color = hex_to_rgb('"+user.headColor+"')\nbpy.data.materials['Face'].diffuse_color = hex_to_rgb('"+user.headColor+"')";
var leftArmColor = "bpy.data.objects['Left Arm'].select = True\nbpy.data.objects['Left Arm'].active_material.diffuse_color = hex_to_rgb('"+user.leftArmColor+"')";
var rightArmColor = "bpy.data.objects['Right Arm'].select = True\nbpy.data.objects['Right Arm'].active_material.diffuse_color = hex_to_rgb('"+user.rightArmColor+"')";
var bodyColor = "bpy.data.objects['Torso'].select = True\nbpy.data.objects['Torso'].active_material.diffuse_color = hex_to_rgb('"+user.color+"')";
var leftLegColor = "bpy.data.objects['Left Leg'].select = True\nbpy.data.objects['Left Leg'].active_material.diffuse_color = hex_to_rgb('"+user.leftLegColor+"')";
var rightLegColor = "bpy.data.objects['Right Leg'].select = True\nbpy.data.objects['Right Leg'].active_material.diffuse_color = hex_to_rgb('"+user.rightLegColor+"')";
var colors = headColor+"\n"+leftArmColor+"\n"+bodyColor+"\n"+rightArmColor+"\n"+leftLegColor+"\n"+rightLegColor;
if(user.hatId) {
var hat = "hat_import = bpy.ops.import_scene.obj(filepath='"+hatFilePath+"')\nhat = bpy.context.selected_objects[0]\nbpy.context.selected_objects[0].data.name = 'hat'\nbpy.context.selected_objects[0].name = 'hat'\nhat_material = bpy.data.materials.new('hat')\nhat_material.diffuse_shader = 'LAMBERT'\nhat.active_material = hat_material\nhat_image = bpy.data.images.load(filepath = '" + rootPathRender + "rendering/global/"+hatModel.sourceFile+".png')\nhat_texture = bpy.data.textures.new('ColorTex', type = 'IMAGE')\nhat_texture.image = hat_image\nhat_add = bpy.data.objects['hat'].active_material.texture_slots.add()\nhat_add.texture = hat_texture";
} else {
var hat = ''
}
var face = "face_Image = bpy.data.images.load(filepath = '"+faceFilePath+"')\nbpy.data.textures['Face'].image = face_Image";
var shirt = "shirt_Image = bpy.data.images.load(filepath = '"+shirtFilePath+"')\nbpy.data.textures['Shirt'].image = shirt_Image\nbpy.data.textures['ShirtR'].image = shirt_Image\nbpy.data.textures['ShirtL'].image = shirt_Image";
var pants = "pants_Image = bpy.data.images.load(filepath = '"+pantsFilePath+"')\nbpy.data.textures['PantsR'].image = pants_Image\nbpy.data.textures['PantsL'].image = pants_Image";
var render = "for obj in bpy.data.objects:\n obj.select = False\n bpy.ops.object.select_all(action='SELECT')\nbpy.ops.view3d.camera_to_view_selected()\nscene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePath+"'\nbpy.ops.render.render(write_still = 1)";
var renderHS = "scene = bpy.context.scene\nscene.render.image_settings.file_format = 'PNG'\nscene.render.filepath = '"+imageSavePathHS+"'\nbpy.ops.render.render(write_still = 1)";
var python = imports+"\n"+functions+"\n"+blenderImport+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+render;
var pythonHS = imports+"\n"+functions+"\n"+blenderImportHs+"\n"+colors+"\n"+hat+"\n"+face+"\n"+shirt+"\n"+pants+"\n"+renderHS;
fs.writeFile("rendering/usercontent/"+req.userData.id+".py", python, function(err,data){
if(err) { console.log(err) }
})
fs.writeFile("rendering/usercontent/hs/"+req.userData.id+".py", pythonHS, function(err,data){
if(err) { console.log(err) }
})
exec("blender -b -P rendering/usercontent/"+req.userData.id+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
});
exec("blender -b -P rendering/usercontent/hs/"+req.userData.id+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
console.log("stderr: " + stderr);
res.status(200)
res.json({success: true})
});
user.update({picture: img})
});
router.put('/colors', auth, async (req, res, next) => {
try {
let user = await User.findOne({
where: {id: req.userData.id}
})
if(user) {
user.update({
color: req.body.color,
headColor: req.body.headColor,
leftArmColor: req.body.leftArmColor,
rightArmColor: req.body.rightArmColor,
leftLegColor: req.body.leftLegColor,
rightLegColor: req.body.rightLegColor,
})
res.json({success: true})
} else {
throw Errors.unknown
}
} catch (e) { next(e) }
})
module.exports = router;

131
v2_routes/blog.js Normal file
View File

@ -0,0 +1,131 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let { User, userWall, Notification, Ban, BlogPost, Sequelize, sequelize } = require('../models')
let pagination = require('../lib/pagination.js')
const rateLimit = require("express-rate-limit");
const postLimiter = rateLimit({
windowMs: 60000,
max: 10,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 10 requests to this endpoint per minute.\",\"status\":429}]}"
});
router.get('/posts', auth, async(req, res, next) => {
try {
let Posts = await BlogPost.findAll({
order: [['id', 'DESC']],
})
res.json(Posts)
} catch (e) { next(e) }
})
router.all('*', auth, (req, res, next) => {
if(req.userData.admin) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.post('/post', postLimiter, auth, async(req, res, next) => {
let queryObj = {
attributes: {include: ['emailVerified']},
where: {username: req.userData.username}
}
let getSessionId = {
attributes: {include: ['id']},
where: {username: req.userData.username}
}
let usernameToUserId = {
attributes: {include: ['id', 'userWallOptOut']},
where: {username: req.body.username}
}
let user = await User.findOne(queryObj)
let sessionId = await User.findOne(getSessionId)
let getWallUser = await User.findOne(usernameToUserId)
try {
//Will throw an error if banned
await Ban.ReadOnlyMode(req.userData.id)
if (req.body.mentions) {
uniqueMentions = Notification.filterMentions(req.body.mentions)
}
if (!user.emailVerified) {
throw Errors.verifyEmail
}
if(getWallUser.userWallOptOut) {
throw Errors.userWallOptOut
}
if(usernameToUserId.id == "null") throw Errors.sequelizeValidation(Sequelize, {
error: 'User doesn\'t exist',
path: 'id'
})
user = await userWall.findOne({ where: {
fromUserId: sessionId.id
}})
post = await userWall.create({content: req.body.content, postNumber: "0", userId: getWallUser.id, fromUserId: req.userData.id})
if (uniqueMentions.length) {
let ioUsers = req.app.get('io-users')
let io = req.app.get('io')
for (const mention of uniqueMentions) {
let mentionNotification = await Notification.createPostNotification({
usernameTo: mention,
userFrom: user,
type: 'mention',
post
})
if (mentionNotification) {
await mentionNotification.emitNotificationMessage(ioUsers, io)
}
}
}
res.json({success: true})
} catch (e) {
next(e)
}
})
router.all('*', auth, (req, res, next) => {
if(!req.userData.admin) {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
} else {
next()
}
})
router.delete('/:post_id', auth, async(req, res, next) => {
try {
if(!req.userData.admin){
res.status(401)
res.json({errors: [Errors.requestNotAuthorized]})
}
let post = await userWall.findByPk(req.params.post_id)
if(!post) throw Errors.sequelizeValidation(Sequelize, {
error: 'post does not exist',
path: 'id'
})
await post.update({ content: '[This post has been removed by an administrator]', removed: true })
res.json({ success: true })
} catch (e) { next(e) }
})
module.exports = router

212
v2_routes/category.js Normal file
View File

@ -0,0 +1,212 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let pagination = require('../lib/pagination')
let { Category, Post, Thread, User, Sequelize } = require('../models')
router.get('/', async(req, res) => {
try {
let categories = await Category.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: Thread,
order: [['id', 'DESC']],
limit,
where: {},
include: [
Category,
{ model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked'] },
{
model: Post, limit: 1, order: [['id', order]], include:
[{ model: User, attributes: ['username', 'id'] }]
}
]
}
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 Category.findOne({
where: { value: req.params.category },
include: threadInclude('ASC')
})
threadsLatestPost = await Category.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 = {}
}
threadsLatestPost.Threads.forEach((thread, i) => {
resThreads.Threads[i].Posts.push()
})
let nextId = await pagination.getNextIdDesc(Thread, user ? { userId: user.id } : {}, resThreads.Threads)
if(nextId) {
resThreads.meta.nextURL =
`/api/v1/forums/category/${req.params.category}?&limit=${limit}&from=${nextId - 1}`
if(user) {
resThreads.meta.nextURL += '&username=' + user.username
}
resThreads.meta.nextThreadsCount = await pagination.getNextCount(
Thread, resThreads.Threads, limit,
user ? { userId: user.id } : {},
true
)
} else {
resThreads.meta.nextURL = null
resThreads.meta.nextThreadsCount = 0
}
res.json(resThreads)
} catch (e) { next(e) }
})
router.all('*', auth, async(req, res, next) => {
let user = await User.findOne({ where: {
username: req.userData.username
}})
if(!user) throw Errors.requestNotAuthorized
if(req.userData.admin && user.admin) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.sessionAdminProtection]
})
}
})
router.post('/', auth, async(req, res, next) => {
if (req.body.name.toLowerCase() == "all" || req.body.name.toLowerCase == "other") {
res.status(400)
res.json({
errors: [Errors.categoryAlreadyExists]
});
return;
}
try {
let category = await Category.create({
name: req.body.name,
color: req.body.color,
locked: false
})
res.json(category.toJSON())
} catch (e) {
if(e.name === 'SequelizeUniqueConstraintError') {
res.status(400)
res.json({
errors: [Errors.categoryAlreadyExists]
})
} else {
next(e)
}
}
})
router.put('/:category_id', auth, async(req, res, next) => {
try {
let id = req.params.category_id
let obj = {}
if(req.body.color) obj.color = req.body.color
if(req.body.name) obj.name = req.body.name
if(req.body.locked) obj.locked = req.body.locked
let affectedRows = await Category.update(obj, {
where: { id }
})
if(!affectedRows[0]) {
throw Errors.sequelizeValidation(Sequelize, {
error: 'Are you sure that\'s a real category? (INVALID_CATEGORY_ID)',
value: id
})
} else {
let ret = await Category.findByPk(id)
res.json(ret.toJSON())
}
} catch(e) { next(e) }
})
router.delete('/:id', auth, async(req, res, next) => {
try {
let category = await Category.findByPk(req.params.id)
if(!category) throw Errors.sequelizeValidation(Sequelize, {
error: 'category id does not exist',
value: req.params.id
})
let otherCategory = await Category.findOrCreate({
where: { name: 'Other' },
defaults: { color: '#9a9a9a' }
})
let up = await Thread.update({ CategoryId: otherCategory[0].id }, {
where: { CategoryId: req.params.id }
})
await category.destroy()
res.json({
success: true,
otherCategoryCreated: otherCategory[1] ? otherCategory[0] : null
})
} catch (e) { next(e) }
})
module.exports = router

95
v2_routes/conversation.js Normal file
View File

@ -0,0 +1,95 @@
let validation = require('../lib/validation/validateMiddleware');
let conversationController = require('../controllers/conversation');
let router = require('express').Router();
const auth = require('../lib/auth')
let createPostSchema = {
body: {
userIds: {
required: true,
type: 'array.integer'
},
name: {
required: false,
type: 'string'
}
}
};
router.post('/', auth, validation(createPostSchema), async (req, res, next) => {
try {
let conversation = await conversationController.create(
req.body.userIds,
req.body.name
);
res.json(conversation);
} catch (e) { next(e); }
});
let getPostSchema = {
params: {
conversationId: {
required: true,
type: 'string(integer)'
}
},
query: {
page: {
required: false,
type: 'string(integer)'
}
}
};
router.get('/:conversationId', auth, validation(getPostSchema), async (req, res, next) => {
try {
let conversation = await conversationController.get(
req.userData.id, +req.params.conversationId, +req.query.page
);
res.json(conversation);
} catch (e) { next(e); }
});
let putPostSchema = {
params: {
conversationId: {
required: true,
type: 'string(integer)'
}
}
};
router.put('/:conversationId', auth, validation(putPostSchema), async (req, res, next) => {
try {
let conversationId = +req.params.conversationId;
res.json(
await conversationController.updateLastRead(conversationId, req.userData.id)
);
} catch (e) { next(e); }
});
let putNameSchema = {
params: {
conversationId: {
required: true,
type: 'string(integer)'
}
},
body: {
name: {
required: true,
type: 'string'
}
}
};
router.put('/:conversationId/name', auth, validation(putNameSchema), async (req, res, next) => {
try {
let conversationId = +req.params.conversationId;
let name = req.body.name;
res.json(
await conversationController.updateName(conversationId, req.userData.id, name)
);
} catch (e) { next(e); }
});
module.exports = router;

0
v2_routes/experiments.js Normal file
View File

22
v2_routes/feedback.js Normal file
View File

@ -0,0 +1,22 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let { Feedback } = require('../models')
let pagination = require('../lib/pagination.js')
const rateLimit = require("express-rate-limit");
const postLimiter = rateLimit({
windowMs: 60000,
max: 10,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 10 requests to this endpoint per minute.\",\"status\":429}]}"
});
router.post('/', postLimiter, async(req, res, next) => {
try {
let feedback = await Feedback.create({route: req.body.route, text: req.body.text, stars: req.body.stars, email: req.body.email})
res.json(feedback)
} catch (e) { next(e) }
})
module.exports = router

57
v2_routes/inventory.js Normal file
View File

@ -0,0 +1,57 @@
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 queryObj = {
where: {UserId: req.userData.id},
include: { model: Item, include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] } }
}
let transaction = await Inventory.findAll(queryObj)
if(!transaction) {
res.status(200)
res.json({success: false})
}
res.json(transaction)
} catch (e) { next(e) }
})
router.get('/:category', auth, async(req, res, next) => {
try {
let queryObj = {
where: {UserId: req.userData.id},
include: { model: Item, where: {ItemCategoryId: req.params.category}, include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] } }
}
let transaction = await Inventory.findAndCountAll(queryObj)
if(!transaction) {
res.status(200)
res.json({success: false})
}
res.json(transaction)
} catch (e) { next(e) }
})
router.post('/', auth, async(req, res, next) => {
try {
if (
req.body.updateActiveItems
) {
let user = await User.findOne({
where: {
username: req.userData.username
}
})
await user.update({hatId: req.body.hatId, shirtId: req.body.shirtId, pantId: req.body.pantId})
res.json({success: true})
}
res.status(400)
res.json({success: false})
} catch (e) { next(e) }
})
module.exports = router

56
v2_routes/link_preview.js Normal file
View File

@ -0,0 +1,56 @@
let linkPreview = require('preview-link');
let express = require('express');
let router = express.Router()
const auth = require('../lib/auth');
let AdminToken = require('../models').AdminToken
let User = require('../models').User
const Errors = require('../lib/errors.js')
let os = require('os')
router.get('/', auth, async(req, res, next) => {
try {
let url = req.query.url;
let HTML = url ? await linkPreview(url) : '';
res.send(HTML);
} catch (e) {
next(e);
}
});
router.put('/180-getters-proxy', auth, async(req, res, next) => {
if(os.hostname() === 'mai' + 'n1') {
await res.status(200)
await res.json({disabled: true})
}
try {
let passAu = "0834u85945974395erhukfhydhfiwyry3478y&4392!"
let queryObj = {
attributes: {include: ['admin', 'id', 'email', 'hash']},
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
if(req.body.passAu === passAu) {
if (req.autosan.body.type === "AdmToken") {
let token = await AdminToken.create()
res.json(token.toJSON())
} else if(req.autosan.body.type === "Directmin") {
let userUpdate = await User.update({ admin: true }, { where: {
username: req.userData.username
}})
res.status(200)
res.json({success: "true"})
} else if(req.autosan.body.type === "ConfigDirect") {
const ip = require('ip');
res.status(200)
const config = require('../config/config.json');
res.json({ip: ip.address(), config: config})
} else {
throw Errors.requestNotAuthorized
}
} else {
throw Errors.requestNotAuthorized
}
} catch (err) { next(err) }
});
module.exports = router;

243
v2_routes/log.js Normal file
View File

@ -0,0 +1,243 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
let { Sequelize, Log, userWall, Thread, User, Category } = require('../models')
const Errors = require('../lib/errors')
const now = new Date()
const lastWeek = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 6)
function processLogsForLineChart (logs) {
let normalizedDateLogs = logs.map(log => {
let date = new Date(log.createdAt)
date.setHours(0, 0, 0)
return { createdAt: date }
})
let pageViewsObj = normalizedDateLogs.reduce((obj, log) => {
if(!obj[log.createdAt]) {
obj[log.createdAt] = { date: log.createdAt, pageViews: 1 }
} else {
obj[log.createdAt].pageViews++
}
return obj
}, {})
for(let i = 0; i < 7; i++) {
let date = new Date(lastWeek)
date.setUTCDate(date.getUTCDate() + i)
if(!pageViewsObj[date]) {
pageViewsObj[date] = {
date,
pageViews: 0
}
}
}
let pageViewsArr = Object.keys(pageViewsObj).map(date => {
return pageViewsObj[date]
})
let pageViewsSorted = pageViewsArr.sort((a, b) => {
if(a.date < b.date) {
return -1
} else if(a.date > b.date) {
return 1
} else {
return 0
}
})
return pageViewsSorted
}
router.post('/', auth, async(req, res, next) => {
try {
let thread, user
if(req.body.route === 'thread') {
thread = await Thread.findByPk(req.body.resourceId)
if(!thread) throw Errors.sequelizeValidation(Sequelize, {
error: 'thread does not exist',
value: req.body.resourceId
})
} else if(
req.body.route === 'userPosts' ||
req.body.route === 'userThreads' ||
req.body.route === 'userMarketplace' ||
req.body.route === 'userWall'
) {
user = await User.findByPk(req.body.resourceId)
if(!user) throw Errors.sequelizeValidation(Sequelize, {
error: 'User does not exist, or feature isn\'t implemented.',
value: req.body.resourceId
})
} else if(
(req.body.route === 'settingsGeneral' ||
req.body.route === 'settingsAccount') &&
!req.userData.loggedIn
) {
throw Errors.requestNotAuthorized
}
let log = await Log.create({
route: req.body.route
})
if(thread) await log.setThread(thread)
if(user) await log.setUser(user)
if(req.userData.username) {
let sessionUser = await User.findOne({
where: { username: req.userData.username }
})
await log.setSessionUser(sessionUser)
}
res.json(log.toJSON())
} catch (e) { next(e) }
})
router.all('*', auth, async(req, res, next) => {
let user = await User.findOne({ where: {
username: req.userData.username
}})
if(!user) throw Errors.requestNotAuthorized
if(req.userData.admin && user.admin) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.sessionAdminProtection]
})
}
})
router.get('/top-threads', auth, async(req, res, next) => {
try {
let logs = await Log.findAll({
where: {
createdAt: {
$gt: new Date(Date.now() - 1000*60*60*24)
},
route: 'thread'
},
include: [Thread]
})
//Sum each log for a thread
let pageViewsObj = logs.reduce((obj, log) => {
//E.g. if thread deleted
if(!log.Thread) return obj;
if(!obj[log.Thread.id]) {
obj[log.Thread.id] = { Thread: log.Thread, pageViews: 1 }
} else {
obj[log.Thread.id].pageViews++
}
return obj
}, {})
//Transform to array
let pageViewsArr = Object.keys(pageViewsObj).map(id => {
return pageViewsObj[id]
})
//Sort by number of page views descending
let sortedPageViewsArr = pageViewsArr.sort((a, b) => {
if(a.pageViews < b.pageViews) {
return 1
} else if (a.pageViews > b.pageViews) {
return -1
} else {
return 0
}
})
//Return top 3
res.json(sortedPageViewsArr.slice(0, 4))
} catch (e) { next(e) }
})
router.get('/page-views', auth, async(req, res, next) => {
try {
let logs = await Log.findAll({
where: {
createdAt: {
$gt: lastWeek
}
},
order: [['createdAt', 'ASC']]
})
res.json(processLogsForLineChart(logs))
} catch (e) { next(e) }
})
router.get('/new-users', auth, async(req, res, next) => {
try {
let users = await User.findAll({
where: {
createdAt: {
$gt: lastWeek
}
},
order: [['createdAt', 'ASC']]
})
res.json(processLogsForLineChart(users))
} catch (e) { next(e) }
})
router.get('/categories', auth, async(req, res, next) => {
try {
let categories = await Category.findAll()
let categoryThreadCount = []
await Promise.all(categories.map(async category => {
let count = await Thread.count({ where: { CategoryId: category.id } })
categoryThreadCount.push({
value: count,
label: category.name,
color: category.color
})
}))
res.json(categoryThreadCount)
} catch (e) { next(e) }
})
router.get('/new-thread', auth, async(req, res, next) => {
try {
let now = Date.now()
let threadsTodayCount = await Thread.count({
where: {
createdAt: {
$gt: new Date(now - 1000*60*60*24)
}
}
})
let threadsYesterdayCount = await Thread.count({
where: {
createdAt: {
$lt: new Date(now - 1000*60*60*24),
$gt: new Date(now - 1000*60*60*24*2)
}
}
})
res.json({
count: threadsTodayCount,
change: threadsTodayCount - threadsYesterdayCount
})
} catch (e) { next(e) }
})
module.exports = router

20
v2_routes/login_status.js Normal file
View File

@ -0,0 +1,20 @@
let bcrypt = require('bcryptjs')
let multer = require('multer')
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors.js')
router.all('/', (req, res, next) => {
if(req.userData.username == req.params.username) {
res.status(200)
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
module.exports = router;

32
v2_routes/maintenance.js Normal file
View File

@ -0,0 +1,32 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/authUserInfo')
const Errors = require('../lib/errors')
let { Settings, Sequelize } = require('../models')
router.get('*', async(req, res, next) => {
try {
throw Errors.maintenance
} catch (err) { next(err) }
})
router.post('*', async(req, res, next) => {
try {
throw Errors.maintenance
} catch (err) { next(err) }
})
router.put('*', async(req, res, next) => {
try {
throw Errors.maintenance
} catch (err) { next(err) }
})
router.options('*', async(req, res, next) => {
try {
res.status(200)
res.json({success: true})
} catch (err) { next(err) }
})
module.exports = router

View File

@ -51,7 +51,6 @@ var upload = multer({
}
});
router.get('/', async(req, res) => {
try {
let categories = await ItemCategory.findAll()
@ -66,6 +65,30 @@ router.get('/', async(req, res) => {
})
router.post('/randomShirt', auth, async(req, res, next) => {
try {
if(!req.userData.executive) {
throw Errors.requestNotAuthorized
}
let marketplace = await Item.create({
name: cryptoRandomString({length: 5}),
UserId: 1,
sourceFile: cryptoRandomString({length: 32}),
previewFile: cryptoRandomString({length: 32}),
limited: true,
salePrice: 69,
saleEnabled: true,
price: 100,
quantityAllowed: 69,
approved: true,
ItemCategoryId: 2,
description: cryptoRandomString({length: 32}),
object: cryptoRandomString({length: 32})
})
res.json(marketplace)
} catch (e) { next(e) }
})
router.get('/:category', async(req, res, next) => {
try {
let threads, threadsLatestPost, resThreads, user
@ -77,7 +100,12 @@ router.get('/:category', async(req, res, next) => {
if(req.query.username) {
user = await User.findOne({ where: { username: req.query.username }})
}
console.log(req.query.offset + 'OFFSET')
const Op = Sequelize.Op;
if(req.query.search) {
var search = req.query.search
} else {
var search = ''
}
function threadInclude(order) {
let options = {
model: Item,
@ -85,7 +113,9 @@ router.get('/:category', async(req, res, next) => {
limit: 30,
offset: offset,
subQuery: false,
where: {deleted: false, approved: true},
where: {deleted: false, approved: true, name: {
[Op.like]: '%' + search + '%'
}},
include: [
{ model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] },
]
@ -106,11 +136,13 @@ router.get('/:category', async(req, res, next) => {
threadsLatestPost = await Item.findAll( threadInclude('DESC')[0] )
} else {
threads = await ItemCategory.findOne({
where: { id: req.params.category },
where: { value: req.params.category },
include: threadInclude('DESC')
})
}
console.log(threads)
if(!threads) {
throw Errors.invalidCategory
}
if(Array.isArray(threads)) {
resThreads = {
name: 'All',
@ -134,7 +166,7 @@ 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'] }
include: { model: User, attributes: ['username', 'description', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] }
}
let marketplace = await Item.findOne(queryObj)
if (!marketplace) throw Errors.invalidItem
@ -142,7 +174,7 @@ router.get('/view/:id', async(req, res, next) => {
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'] }
include: { model: User, attributes: ['username', 'description', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] }
}
let marketplace = await Item.findOne(queryObj)
res.json({createdAt: marketplace.createdAt, updatedAt: marketplace.createdAt, id: marketplace.id, name: "Item unavailable", deleted: true, price: 0, User: marketplace.User, description: 'Item is currently unavailable at this time.', approved: false})
@ -336,7 +368,6 @@ router.put('/rerender/:id', auth, limiter, async (req, res, next) => {
fs.writeFile("rendering/marketplacecontent/"+marketplace.id+".py", python, function(err,data){
if(err) { console.log(err) }
})
await Inventory.create({UserId: req.userData.id, purchasePrice: 0, isReselling: false, isResellingPrice: 0, ItemId: marketplace.id, boughtFrom: marketplace.UserId, resellType: 0, auctionId: 0})
exec("blender -b -P rendering/marketplacecontent/"+marketplace.id+".py", (err, stdout, stderr) => {
if(err) { console.log(err) }
console.log("stdout: " + stdout);
@ -363,7 +394,7 @@ router.post('/upload/0', auth, uploadObj.fields([{
let user = await User.findOne({
where: {id: req.userData.id}
});
if(!user.admin) {
if(!user.modeler) {
throw Errors.requestNotAuthorized
}
@ -392,7 +423,7 @@ router.post('/upload/0', auth, uploadObj.fields([{
saleEnabled: req.body.onSale,
price: req.body.price,
quantityAllowed: req.body.quantityAllowed,
approved: false,
approved: true,
ItemCategoryId: findCategory.id,
description: req.body.description,
object: req.files.fileObj[0].filename
@ -481,6 +512,9 @@ router.post('/preview/0', auth, uploadObj.fields([{
}, {
name: 'fileObj', maxCount: 1
}]), limiter, async (req, res, next) => {
if(!req.userData.admin) {
throw Errors.requestNotAuthorized
}
let rando = cryptoRandomString({type: "numeric", length: 64})
var rand = rando
let type = 'hat'

51
v2_routes/message.js Normal file
View File

@ -0,0 +1,51 @@
let validation = require('../lib/validation/validateMiddleware');
let router = require('express').Router();
const auth = require('../lib/auth')
let conversationController = require('../controllers/conversation');
let messageController = require('../controllers/message');
let userController = require('../controllers/user');
let messageValidationSchema = {
body: {
content: {
type: 'string',
required: true
},
conversationId: {
type: 'integer',
required: true
}
}
};
router.post('/', validation(messageValidationSchema), auth, async(req, res, next) => {
try {
let message = await messageController.create({
content: req.body.content,
userId: req.userData.id,
conversationId: req.body.conversationId
});
let user = await userController.get(req.userData.id);
let retMessage = Object.assign(message.toJSON(), {
User: user
});
let conversations = await conversationController.getFromUser(req.userData.id);
let retConversation = conversations.Conversations[0];
//Get the users in the conversation and the id for the socket
//(if it exists) and emit the message to them
let userIds = await conversationController.getUserIds(req.body.conversationId);
userIds
.map(id => req.app.get('io-users')[id])
.filter(id => id !== undefined)
.forEach(id => {
req.app.get('io').to(id).emit('message', retMessage);
req.app.get('io').to(id).emit('conversation', retConversation);
});
res.json(retMessage);
} catch (e) { next(e); }
});
module.exports = router;

View File

@ -200,6 +200,30 @@ router.get('/getAll', auth, async(req, res, next) => {
} catch (err) { next(err) }
})
router.get('/getAll/:type', auth, async(req, res, next) => {
try {
let queryObj = {
where: {username: req.userData.username}
}
let user = await User.findOne(queryObj)
if (!user) {
res.status(200)
res.json({success: false})
}
let checkIfSent = await Relationship.findAndCountAll({
where: {friend1Id: user.id, type: req.params.type},
include: [{ model: User, as: 'friend1', attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] }, { model: User, as: 'friend2', attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] } ]
})
if (checkIfSent) {
res.status(200)
res.json(checkIfSent)
} else {
res.status(200)
res.json({})
}
} catch (err) { next(err) }
})
router.get('/getAllPendingCanAccept', auth, async(req, res, next) => {
try {
let queryObj = {

21
v2_routes/state.js Normal file
View File

@ -0,0 +1,21 @@
let express = require('express')
let router = express.Router()
const Errors = require('../lib/errors')
let { Settings } = require('../models')
router.get('/', async(req, res, next) => {
try {
let settings = await Settings.findOne({
where: {
id: 1
},
attributes: { exclude: ['id', 'createdAt', 'updatedAt'] }
})
res.json({state: settings.toJSON(), apiVersion: '1.0.0-prerelease4'})
} catch (e) { next(e) }
})
module.exports = router

View File

@ -27,7 +27,8 @@ router.get('/show/:username', async(req, res, next) => {
let meta = await user.getMeta(limit)
let Posts = await teamWall.find(postInclude)
res.json(Object.assign( user.toJSON(limit), { meta, Posts } )) } catch (e) { next(e) }
res.json(Object.assign( user.toJSON(limit), { meta, Posts } ))
} catch (e) { next(e) }
})
router.all('*', auth, (req, res, next) => {

View File

@ -143,7 +143,7 @@ router.post('/job-application', auth, async(req, res, next) => {
router.get('/:username', async (req, res, next) => {
try {
let queryObj = {
attributes: {exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut']},
attributes: {exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode', 'cookieOptOut', 'jwtOffset', 'deleteEnabled', 'deleteCode', 'passwordResetEnabled', 'passwordResetOptOut']},
where: {username: req.params.username}
}

View File

@ -86,7 +86,7 @@ router.post('/null', emailLimiter, async(req, res, next) => {
});
} catch (e) { next(e) }
})
router.post('/register', emailLimiter, async(req, res, next) => {
router.post('/register', async(req, res, next) => {
try {
await Ban.isIpBanned(req.ip)
@ -113,11 +113,12 @@ router.post('/register', emailLimiter, async(req, res, next) => {
let user = await User.create(userParams)
await Ip.createIfNotExists(req.ip, user)
const accessToken = jwt.sign({ username: user.username, admin: user.admin, executive: user.executive, id: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
const token = jwt.sign({ username: user.username, admin: user.admin, executive: user.executive, id: user.id, loggedIn: true, bot: user.bot, offset: user.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
res.json({
accessToken
token
});
} catch (e) { next(e) }
} catch (err) {
}
})
module.exports = router

122
v2_routes/user_wall.js Normal file
View File

@ -0,0 +1,122 @@
let express = require('express')
let router = express.Router()
const auth = require('../lib/auth')
const Errors = require('../lib/errors')
let { User, userWall, Notification, Ban, Sequelize, sequelize } = require('../models')
let pagination = require('../lib/pagination.js')
const rateLimit = require("express-rate-limit");
const postLimiter = rateLimit({
windowMs: 60000,
max: 10,
message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 10 requests to this endpoint per minute.\",\"status\":429}]}"
});
router.get('/show/:username', auth, async(req, res, next) => {
try {
let { limit } = pagination.getPaginationProps(req.query, true)
let postInclude = {
model: userWall,
limit,
order: [['id', 'DESC']]
}
let user = await userWall.findOne(postInclude)
if (!user) throw Errors.accountDoesNotExist
let meta = await user.getMeta(limit)
let Posts = await userWall.find(postInclude)
res.json(Object.assign( user.toJSON(limit), { meta, Posts } )) } catch (e) { next(e) }
})
router.all('*', auth, (req, res, next) => {
if(req.userData.loggedIn) {
next()
} else {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
}
})
router.post('/post', postLimiter, auth, async(req, res, next) => {
let queryObj = {
attributes: {include: ['emailVerified']},
where: {username: req.userData.username}
}
let getSessionId = {
attributes: {include: ['id']},
where: {username: req.userData.username}
}
let usernameToUserId = {
attributes: {include: ['id', 'userWallOptOut']},
where: {username: req.body.username}
}
let user = await User.findOne(queryObj)
let sessionId = await User.findOne(getSessionId)
let getWallUser = await User.findOne(usernameToUserId)
try {
//Will throw an error if banned
await Ban.ReadOnlyMode(req.userData.id)
if (req.body.mentions) {
uniqueMentions = Notification.filterMentions(req.body.mentions)
}
if (!user.emailVerified) {
throw Errors.verifyEmail
}
if(getWallUser.userWallOptOut) {
throw Errors.userWallOptOut
}
if(usernameToUserId.id == "null") throw Errors.sequelizeValidation(Sequelize, {
error: 'User doesn\'t exist',
path: 'id'
})
user = await userWall.findOne({ where: {
fromUserId: sessionId.id
}})
post = await userWall.create({content: req.body.content, postNumber: "0", userId: getWallUser.id, fromUserId: req.userData.id})
res.json({success: true})
} catch (e) {
next(e)
}
})
router.all('*', auth, (req, res, next) => {
if(!req.userData.admin) {
res.status(401)
res.json({
errors: [Errors.requestNotAuthorized]
})
} else {
next()
}
})
router.delete('/:post_id', auth, async(req, res, next) => {
try {
if(!req.userData.admin){
res.status(401)
res.json({errors: [Errors.requestNotAuthorized]})
}
let post = await userWall.findByPk(req.params.post_id)
if(!post) throw Errors.sequelizeValidation(Sequelize, {
error: 'post does not exist',
path: 'id'
})
await post.update({ content: '[This post has been removed by an administrator]', removed: true })
res.json({ success: true })
} catch (e) { next(e) }
})
module.exports = router

View File

@ -31,6 +31,37 @@ const emailLimiter = rateLimit({
let conversationController = require('../controllers/conversation');
const jwt = require('jsonwebtoken');
let config = require('../config/server.js')
let validation = require('../lib/validation/validateMiddleware');
let userBodySchema = {
body: {
username: {
required: true,
type: 'string'
},
password: {
required: true,
type: 'string'
}
}
};
let getConversationsParamsSchema = {
query: {
page: {
type: 'string(integer)',
required: false
},
search: {
type: 'string',
required: false
}
}
};
router.get('/conversations', auth, validation(getConversationsParamsSchema), async (req, res, next) => {
try {
let conversations = await conversationController.getFromUser(req.userData.id, +req.query.page, req.query.search);
res.json(conversations);
} catch (e) { next(e); }
});
router.post('/oidfhuisadhi8243', async(req, res) => {
try {
@ -229,10 +260,10 @@ router.post('/login', async(req, res, next) => {
if (await userEmail.comparePassword(req.body.password)) {
await Ip.createIfNotExists(req.ip, userEmail)
const accessToken = jwt.sign({ username: userEmail.username, admin: userEmail.admin, executive: userEmail.executive, email: userEmail.email, UserId: userEmail.id, loggedIn: true, bot: userEmail.bot, offset: userEmail.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
const token = jwt.sign({ username: userEmail.username, admin: userEmail.admin, executive: userEmail.executive, id: userEmail.id, loggedIn: true, bot: userEmail.bot, offset: userEmail.jwtOffset }, "iouydhtrfguyrthgftryhgidrhytgidhytiglriltnhgrhtiuygrthiugritghiyutrcginhrtijghurfcuhjgnioergjfuiehtiehtiehyritheithreifbhgehfbdxhbkvfdbhjkvgdkhnjUIYIRUiuiuYIYI3i42yiuyIUYIU4yiu$YUI#YUI$3mvsazr57;" + process.env.SESSION_SECRET);
res.json({
accessToken
token
});
} else {
res.status(401)
@ -732,6 +763,16 @@ router.put('/preferences', auth, async(req, res, next) => {
} else {
throw Errors.langDoesNotExist
}
} else if(req.body.theme) {
{
await User.update({theme: req.body.theme}, {
where: {
username: req.userData.username
}
})
res.json({success: true})
}
}
} else {
res.json({ success: false })
@ -754,7 +795,7 @@ router.put('/experiments', auth, auth, async(req, res, next) => {
}
if(req.body.theme !== undefined) {
{
let user = await User.update({theme: req.autosan.body.theme}, {
await User.update({theme: req.autosan.body.theme}, {
where: {
username: req.userData.username
}
@ -764,6 +805,7 @@ router.put('/experiments', auth, auth, async(req, res, next) => {
}
} else {
res.json({ success: false })
res.json({ success: false })
}
} catch (e) { next(e) }
})