diff --git a/config/server.js b/config/server.js index 742c474..c2108eb 100644 --- a/config/server.js +++ b/config/server.js @@ -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/" } diff --git a/legacyfrontend/src/components/routes/Character.vue b/legacyfrontend/src/components/routes/Character.vue index c1f9549..ab52ec7 100644 --- a/legacyfrontend/src/components/routes/Character.vue +++ b/legacyfrontend/src/components/routes/Character.vue @@ -384,6 +384,6 @@ export default { }, mounted () { this.fetchData(); - } + }, } diff --git a/legacyfrontend/src/components/routes/Conversation.vue b/legacyfrontend/src/components/routes/Conversation.vue index 9453704..c46370f 100644 --- a/legacyfrontend/src/components/routes/Conversation.vue +++ b/legacyfrontend/src/components/routes/Conversation.vue @@ -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); diff --git a/legacyfrontend/src/components/side-panel.vue b/legacyfrontend/src/components/side-panel.vue index 5b84ca4..dd3d15d 100644 --- a/legacyfrontend/src/components/side-panel.vue +++ b/legacyfrontend/src/components/side-panel.vue @@ -39,19 +39,19 @@ -
+
No conversations
diff --git a/lib/authCheck.js b/lib/authCheck.js new file mode 100644 index 0000000..573187a --- /dev/null +++ b/lib/authCheck.js @@ -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 + } +}; diff --git a/lib/socketErrorHandler.js b/lib/socketErrorHandler.js index 1dcca17..7fead8e 100644 --- a/lib/socketErrorHandler.js +++ b/lib/socketErrorHandler.js @@ -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' }] }); } }; \ No newline at end of file diff --git a/migrations/20210127123110-blog.js b/migrations/20210127123110-blog.js new file mode 100644 index 0000000..136d1e7 --- /dev/null +++ b/migrations/20210127123110-blog.js @@ -0,0 +1,13 @@ +module.exports = { + up(queryInterface, Sequelize) { + return Promise.all([ + queryInterface.addColumn( + 'blogposts', + 'plainText', + { + type: Sequelize.TEXT, + }, + ), + ]); + }, +}; \ No newline at end of file diff --git a/migrations/20210204053359-TeamAvatars.js b/migrations/20210204053359-TeamAvatars.js new file mode 100644 index 0000000..93c720b --- /dev/null +++ b/migrations/20210204053359-TeamAvatars.js @@ -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 + }, + ), + ]); + }, +} \ No newline at end of file diff --git a/migrations/20210204103215-feedback.js b/migrations/20210204103215-feedback.js new file mode 100644 index 0000000..567e9ef --- /dev/null +++ b/migrations/20210204103215-feedback.js @@ -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'); + } +} \ No newline at end of file diff --git a/migrations/20210204104653-feedbackdates.js b/migrations/20210204104653-feedbackdates.js new file mode 100644 index 0000000..7b0dce4 --- /dev/null +++ b/migrations/20210204104653-feedbackdates.js @@ -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, + }, + ), + ]); + }, +}; \ No newline at end of file diff --git a/models/blog.js b/models/blog.js index 5b583ac..2692203 100644 --- a/models/blog.js +++ b/models/blog.js @@ -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 (
). + // This is only for full CommonMark compatibility. + breaks: true, // Convert '\n' in paragraphs into
+ 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 { - 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 } diff --git a/models/conversation.js b/models/conversation.js index 247b672..968e447 100644 --- a/models/conversation.js +++ b/models/conversation.js @@ -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 }; \ No newline at end of file diff --git a/models/feedback.js b/models/feedback.js new file mode 100644 index 0000000..62a2c38 --- /dev/null +++ b/models/feedback.js @@ -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 +} \ No newline at end of file diff --git a/models/team.js b/models/team.js index e06f5cb..02962ca 100644 --- a/models/team.js +++ b/models/team.js @@ -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: { diff --git a/models/user.js b/models/user.js index aa88df3..dfccb34 100644 --- a/models/user.js +++ b/models/user.js @@ -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'}) diff --git a/models/userWall.js b/models/userWall.js index 0a49032..a23c4a3 100644 --- a/models/userWall.js +++ b/models/userWall.js @@ -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, diff --git a/rendering/preview/9650253457896447672190043079564275765258433403668372776443201272.py b/rendering/preview/9650253457896447672190043079564275765258433403668372776443201272.py new file mode 100644 index 0000000..49040b3 --- /dev/null +++ b/rendering/preview/9650253457896447672190043079564275765258433403668372776443201272.py @@ -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) \ No newline at end of file diff --git a/rendering/usercontent/1.py b/rendering/usercontent/1.py index 5bd65dd..89ee87f 100644 --- a/rendering/usercontent/1.py +++ b/rendering/usercontent/1.py @@ -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) \ No newline at end of file diff --git a/rendering/usercontent/hs/1.py b/rendering/usercontent/hs/1.py index befeec8..46c3d84 100644 --- a/rendering/usercontent/hs/1.py +++ b/rendering/usercontent/hs/1.py @@ -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) \ No newline at end of file diff --git a/server.js b/server.js index 35dea76..1233d6e 100644 --- a/server.js +++ b/server.js @@ -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) } diff --git a/routes/StaffApplications.js b/v1_routes/StaffApplications.js similarity index 100% rename from routes/StaffApplications.js rename to v1_routes/StaffApplications.js diff --git a/routes/UnbanRequest.js b/v1_routes/UnbanRequest.js similarity index 100% rename from routes/UnbanRequest.js rename to v1_routes/UnbanRequest.js diff --git a/v1_routes/about.js b/v1_routes/about.js new file mode 100644 index 0000000..492343c --- /dev/null +++ b/v1_routes/about.js @@ -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 diff --git a/v1_routes/admin.js b/v1_routes/admin.js new file mode 100644 index 0000000..e519120 --- /dev/null +++ b/v1_routes/admin.js @@ -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; diff --git a/routes/admin_kill_session.js b/v1_routes/admin_kill_session.js similarity index 100% rename from routes/admin_kill_session.js rename to v1_routes/admin_kill_session.js diff --git a/v1_routes/admin_token.js b/v1_routes/admin_token.js new file mode 100644 index 0000000..0cd0dec --- /dev/null +++ b/v1_routes/admin_token.js @@ -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 diff --git a/v1_routes/admin_user_list.js b/v1_routes/admin_user_list.js new file mode 100644 index 0000000..65fb7e1 --- /dev/null +++ b/v1_routes/admin_user_list.js @@ -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; diff --git a/v1_routes/avatar.js b/v1_routes/avatar.js new file mode 100644 index 0000000..990d314 --- /dev/null +++ b/v1_routes/avatar.js @@ -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; diff --git a/v1_routes/ban.js b/v1_routes/ban.js new file mode 100644 index 0000000..2836a06 --- /dev/null +++ b/v1_routes/ban.js @@ -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 diff --git a/routes/blog.js b/v1_routes/blog.js similarity index 100% rename from routes/blog.js rename to v1_routes/blog.js diff --git a/routes/category.js b/v1_routes/category.js similarity index 100% rename from routes/category.js rename to v1_routes/category.js diff --git a/routes/conversation.js b/v1_routes/conversation.js similarity index 94% rename from routes/conversation.js rename to v1_routes/conversation.js index 4f65146..79f2e8b 100644 --- a/routes/conversation.js +++ b/v1_routes/conversation.js @@ -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); } }); diff --git a/routes/experiments.js b/v1_routes/experiments.js similarity index 100% rename from routes/experiments.js rename to v1_routes/experiments.js diff --git a/routes/inventory.js b/v1_routes/inventory.js similarity index 96% rename from routes/inventory.js rename to v1_routes/inventory.js index 58904e4..65d72b0 100644 --- a/routes/inventory.js +++ b/v1_routes/inventory.js @@ -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) diff --git a/routes/link_preview.js b/v1_routes/link_preview.js similarity index 100% rename from routes/link_preview.js rename to v1_routes/link_preview.js diff --git a/routes/log.js b/v1_routes/log.js similarity index 100% rename from routes/log.js rename to v1_routes/log.js diff --git a/routes/login_status.js b/v1_routes/login_status.js similarity index 100% rename from routes/login_status.js rename to v1_routes/login_status.js diff --git a/routes/maintenance.js b/v1_routes/maintenance.js similarity index 100% rename from routes/maintenance.js rename to v1_routes/maintenance.js diff --git a/v1_routes/marketplace.js b/v1_routes/marketplace.js new file mode 100644 index 0000000..d946b1f --- /dev/null +++ b/v1_routes/marketplace.js @@ -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 diff --git a/routes/message.js b/v1_routes/message.js similarity index 93% rename from routes/message.js rename to v1_routes/message.js index 4ebe977..c847835 100644 --- a/routes/message.js +++ b/v1_routes/message.js @@ -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 diff --git a/v1_routes/notification.js b/v1_routes/notification.js new file mode 100644 index 0000000..6fb0c5c --- /dev/null +++ b/v1_routes/notification.js @@ -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 diff --git a/v1_routes/poll.js b/v1_routes/poll.js new file mode 100644 index 0000000..0771bcc --- /dev/null +++ b/v1_routes/poll.js @@ -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 diff --git a/v1_routes/post.js b/v1_routes/post.js new file mode 100644 index 0000000..adce40c --- /dev/null +++ b/v1_routes/post.js @@ -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 diff --git a/v1_routes/relationship.js b/v1_routes/relationship.js new file mode 100644 index 0000000..d04406a --- /dev/null +++ b/v1_routes/relationship.js @@ -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 diff --git a/v1_routes/report.js b/v1_routes/report.js new file mode 100644 index 0000000..150378f --- /dev/null +++ b/v1_routes/report.js @@ -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 diff --git a/v1_routes/search.js b/v1_routes/search.js new file mode 100644 index 0000000..d4d894c --- /dev/null +++ b/v1_routes/search.js @@ -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 diff --git a/routes/state.js b/v1_routes/state.js similarity index 100% rename from routes/state.js rename to v1_routes/state.js diff --git a/v1_routes/stats.js b/v1_routes/stats.js new file mode 100644 index 0000000..1a1bc40 --- /dev/null +++ b/v1_routes/stats.js @@ -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 diff --git a/v1_routes/team.js b/v1_routes/team.js new file mode 100644 index 0000000..73ce73f --- /dev/null +++ b/v1_routes/team.js @@ -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; diff --git a/v1_routes/team_admin.js b/v1_routes/team_admin.js new file mode 100644 index 0000000..1743fb3 --- /dev/null +++ b/v1_routes/team_admin.js @@ -0,0 +1,1540 @@ +/* +@swagger +components: +schemas: + Book: + type: object +required: + - title + - author + - finished +properties: + id: + type: integer +description: The auto-generated id of the book. + title: +type: string +description: The title of your book. + author: +type: string +description: Who wrote the book? + finished: + type: boolean +description: Have you finished reading it? + createdAt: + type: string +format: date +description: The date of the record creation. + example: +title: The Pragmatic Programmer +author: Andy Hunt / Dave Thomas +finished: true +*/ +let bcrypt = require('bcryptjs') +let multer = require('multer') +let express = require('express') +let router = express.Router() +const auth = require('../lib/auth') +var Recaptcha = require('express-recaptcha').RecaptchaV3; +var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd'); +var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy"; +const Errors = require('../lib/errors.js') +var format = require('date-format'); +let { + User, Post, teamPicture, TeamMemberRole, TeamInvite, 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") +let Promise = require('bluebird'); +const rateLimit = require("express-rate-limit"); +let upload = multer({ + storage: multer.memoryStorage(), + limits:{ + fileSize: 1024 * 1024 + } +}) + +const emailLimiter = rateLimit({ + windowMs: 60000, + max: 1, // limit each IP to 100 requests per windowMs + message: "{\"errors\":[{\"name\":\"rateLimit\",\"message\":\"You may only make 1 request to this endpoint per minute.\",\"status\":429}]}" +}); + +router.post('/:username/picture', auth, upload.single('picture'), async (req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(otherThanNull) { + let user = await Team.findOne({ + where: { + username: req.params.username + } + }) + let picture = await teamPicture.findOne({ + where: {TeamId: user.id} + }) + + let pictureObj = { + file: req.file.buffer, + mimetype: req.file.mimetype, + TeamId: user.id + } + + //No picture set yet + if (!picture) { + await teamPicture.create(pictureObj) + } else { + await picture.update(pictureObj) + } + + //Add random query to end to force browser to reload background images + await user.update({ + picture: '/api/v1/teams/view/' + req.params.username + '/picture?rand=' + Date.now() + }) + + res.json(user.toJSON()) + } else { + throw Errors.requestNotAuthorized + } + } catch (e) { next(e) } +}) + +router.put('/modify/:username', auth, async(req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(!req.userData.username) { + throw Errors.requestNotAuthorized + } + await Ban.ReadOnlyMode(req.userData.UserId) + let user1 = await Team.findOne({ where: { + username: req.params.username + }}) + let user2 = await User.findOne({ where: { + username: req.userData.username + }}) + console.log(user1.OwnerId, user2.id) + if(otherThanNull) { + if(req.autosan.body.description !== undefined, req.autosan.body.name !== undefined) { + await Team.update({description: req.autosan.body.description, name: req.autosan.body.name}, { + where: { + username: req.params.username + } + }) + res.status(200) + res.json({success: true}) + } else if(req.autosan.body.userWallOptOut !== undefined) { + await Team.update({teamWallOptOut: req.autosan.body.userWallOptOut}, { + where: { + username: req.params.username + } + }) + res.status(200) + res.json({success: true}) + } else { + throw Errors.requestNotAuthorized + } + } else { + throw Errors.requestNotAuthorized + } + } catch (e) { next(e) } +}) + +router.post('/roles/create/:username', auth, async(req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + let isAuth11 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, changeTeamRoles: true} + }) + let isAuth12 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, changeTeamRoles: true} + }) + let isAuth13 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, changeTeamRoles: true} + }) + let isAuth14 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, changeTeamRoles: true} + }) + let isAuth15 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, changeTeamRoles: true} + }) + let isAuth16 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, changeTeamRoles: true} + }) + let isAuth17 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, changeTeamRoles: true} + }) + let isAuth18 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, changeTeamRoles: true} + }) + let isAuth19 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, changeTeamRoles: true} + }) + let isAuth20 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, changeTeamRoles: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10, + isAuth11, + isAuth12, + isAuth13, + isAuth14, + isAuth15, + isAuth16, + isAuth17, + isAuth18, + isAuth19, + isAuth20 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(team && otherThanNull) { + let queryObj3 = { + where: {userId: req.userData.UserId, teamId: team.id}, + } + if(team.banned) { + res.status(200) + res.json({success: false}) + } + if(req.body.name === 'Administrators') { + throw Errors.roleReserved + } + if(req.body.name === 'Members') { + throw Errors.roleReserved + } + let teamJoinTest = await TeamMembers.findOne(queryObj3) + if (teamJoinTest) { + let makeRole = { + name: req.body.name, + administrator: req.body.administrator, + inviteUsers: req.body.inviteUsers, + changeTeamMeta: req.body.changeTeamMeta, + forumAdministrator: req.body.forumAdministrator, + moderateForumThreads: req.body.moderateForumThreads, + changeTeamPrivacy: req.body.changeTeamPrivacy, + submitTeamItems: req.body.submitTeamItems, + priority: req.body.priority, + changeTeamRoles: req.body.changeTeamRoles, + teamId: team.id + } + let teamCreate = await TeamRoles.create(makeRole) + res.status(200) + res.json(teamCreate.toJSON()) + } else if (!teamJoinTest) { + res.status(400) + res.json({success: false}) + } + } else { + throw Errors.teamDoesNotExistOrPermInvalid + } + } catch (e) { next(e) } +}) + +router.put('/roles/modify/:username/:id', auth, async(req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + let isAuth11 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, changeTeamRoles: true} + }) + let isAuth12 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, changeTeamRoles: true} + }) + let isAuth13 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, changeTeamRoles: true} + }) + let isAuth14 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, changeTeamRoles: true} + }) + let isAuth15 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, changeTeamRoles: true} + }) + let isAuth16 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, changeTeamRoles: true} + }) + let isAuth17 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, changeTeamRoles: true} + }) + let isAuth18 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, changeTeamRoles: true} + }) + let isAuth19 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, changeTeamRoles: true} + }) + let isAuth20 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, changeTeamRoles: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10, + isAuth11, + isAuth12, + isAuth13, + isAuth14, + isAuth15, + isAuth16, + isAuth17, + isAuth18, + isAuth19, + isAuth20 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(team && otherThanNull) { + 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) { + if(req.body.name) { + let find = await TeamRoles.findOne({ + where: { + id: req.params.id, + teamId: team.id + } + }) + if(find.name === 'Members') { + await TeamRoles.update({ + priority: req.body.priority, + administrator: req.body.administrator, + inviteUsers: req.body.inviteUsers, + changeTeamMeta: req.body.changeTeamMeta, + forumAdministrator: req.body.forumAdministrator, + moderateForumThreads: req.body.moderateForumThreads, + changeTeamPrivacy: req.body.changeTeamPrivacy, + submitTeamItems: req.body.submitTeamItems, + changeTeamRoles: req.body.changeTeamRoles + }, { + where: { + id: req.params.id, + teamId: team.id + } + }) + res.status(200) + res.json({success: true}) + } + if(find.name === 'Administrators') { + await TeamRoles.update({ + priority: req.body.priority, + administrator: req.body.administrator, + inviteUsers: req.body.inviteUsers, + changeTeamMeta: req.body.changeTeamMeta, + forumAdministrator: req.body.forumAdministrator, + moderateForumThreads: req.body.moderateForumThreads, + changeTeamPrivacy: req.body.changeTeamPrivacy, + submitTeamItems: req.body.submitTeamItems, + changeTeamRoles: req.body.changeTeamRoles + }, { + where: { + id: req.params.id, + teamId: team.id + } + }) + res.status(200) + res.json({success: true}) + } + + if(find && find.name !== 'Administrators' && find.name !== 'Members') { + await TeamRoles.update({ + priority: req.body.priority, + name: req.body.name, + administrator: req.body.administrator, + inviteUsers: req.body.inviteUsers, + changeTeamMeta: req.body.changeTeamMeta, + forumAdministrator: req.body.forumAdministrator, + moderateForumThreads: req.body.moderateForumThreads, + changeTeamPrivacy: req.body.changeTeamPrivacy, + submitTeamItems: req.body.submitTeamItems, + changeTeamRoles: req.body.changeTeamRoles + }, { + where: { + id: req.params.id, + teamId: team.id + } + }) + res.status(200) + res.json({success: true}) + } else { + res.status(400) + res.json({success: false}) + } + } else if(req.body.priority && !req.body.name) { + let find = await TeamRoles.findOne({ + where: { + id: req.params.id, + teamId: team.id + } + }) + if(find) { + await TeamRoles.update({priority: req.body.priority}, { + where: { + id: req.params.id, + teamId: team.id + } + }) + res.status(200) + res.json({success: true}) + } else { + res.status(400) + res.json({success: false}) + } + } else { + res.status(400) + res.json({success: false}) + } + } else if (!teamJoinTest) { + res.status(400) + res.json({success: false}) + } + } else { + throw Errors.teamDoesNotExistOrPermInvalid + } + } catch (e) { next(e) } +}) + +router.put('/roles/modify/:username', auth, async(req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + let isAuth11 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, changeTeamRoles: true} + }) + let isAuth12 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, changeTeamRoles: true} + }) + let isAuth13 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, changeTeamRoles: true} + }) + let isAuth14 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, changeTeamRoles: true} + }) + let isAuth15 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, changeTeamRoles: true} + }) + let isAuth16 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, changeTeamRoles: true} + }) + let isAuth17 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, changeTeamRoles: true} + }) + let isAuth18 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, changeTeamRoles: true} + }) + let isAuth19 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, changeTeamRoles: true} + }) + let isAuth20 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, changeTeamRoles: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10, + isAuth11, + isAuth12, + isAuth13, + isAuth14, + isAuth15, + isAuth16, + isAuth17, + isAuth18, + isAuth19, + isAuth20 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(team && otherThanNull) { + 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) { + let updateRoles = await TeamRoles.bulkCreate(req.body.roles, { updateOnDuplicate: ["id"] }) + res.status(200) + res.json(updateRoles) + } + } else { + throw Errors.teamDoesNotExistOrPermInvalid + } + } catch (e) { next(e) } +}) + +router.delete('/roles/delete/:username/:id', auth, async(req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + let isAuth11 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, changeTeamRoles: true} + }) + let isAuth12 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, changeTeamRoles: true} + }) + let isAuth13 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, changeTeamRoles: true} + }) + let isAuth14 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, changeTeamRoles: true} + }) + let isAuth15 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, changeTeamRoles: true} + }) + let isAuth16 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, changeTeamRoles: true} + }) + let isAuth17 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, changeTeamRoles: true} + }) + let isAuth18 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, changeTeamRoles: true} + }) + let isAuth19 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, changeTeamRoles: true} + }) + let isAuth20 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, changeTeamRoles: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10, + isAuth11, + isAuth12, + isAuth13, + isAuth14, + isAuth15, + isAuth16, + isAuth17, + isAuth18, + isAuth19, + isAuth20 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(team && otherThanNull) { + 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) { + let find = await TeamRoles.findOne({ + where: { + id: req.params.id, + teamId: team.id + } + }) + if(find.name === 'Members') { + throw Errors.roleCannotBeRemoved + } + if(find.name === 'Administrators') { + throw Errors.roleCannotBeRemoved + } + + if(find && find.name !== 'Administrators' && find.name !== 'Members') { + await find.destroy() + res.status(200) + res.json({success: true}) + } else { + res.status(400) + res.json({success: false}) + } + } else if (!teamJoinTest) { + res.status(400) + res.json({success: false}) + } + } else { + throw Errors.teamDoesNotExistOrPermInvalid + } + } catch (e) { next(e) } +}) + +router.put('/members/modify/:username/:id', auth, async(req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let userFind = await User.findOne({ + where: {username: req.params.id} + }) + let user = await TeamMemberRole.findOne({ + where: {UserId: userFind.id, TeamId: team.id} + }); + let currUser = await User.findOne({ + where: {id: req.userData.UserId} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + let isAuth11 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, changeTeamRoles: true} + }) + let isAuth12 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, changeTeamRoles: true} + }) + let isAuth13 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, changeTeamRoles: true} + }) + let isAuth14 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, changeTeamRoles: true} + }) + let isAuth15 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, changeTeamRoles: true} + }) + let isAuth16 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, changeTeamRoles: true} + }) + let isAuth17 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, changeTeamRoles: true} + }) + let isAuth18 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, changeTeamRoles: true} + }) + let isAuth19 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, changeTeamRoles: true} + }) + let isAuth20 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, changeTeamRoles: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10, + isAuth11, + isAuth12, + isAuth13, + isAuth14, + isAuth15, + isAuth16, + isAuth17, + isAuth18, + isAuth19, + isAuth20 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(team && otherThanNull) { + 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) { + if(team.OwnerId !== req.userData.UserId && team.OwnerId === userFind.id) { + throw Errors.modifyOwner + } + let isAuth1 = await TeamRoles.findOne({ + where: {id: req.body.RoleId, TeamId: team.id} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: req.body.Role2Id, TeamId: team.id} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: req.body.Role3Id, TeamId: team.id} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: req.body.Role4Id, TeamId: team.id} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: req.body.Role5Id, TeamId: team.id} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: req.body.Role6Id, TeamId: team.id} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: req.body.Role7Id, TeamId: team.id} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: req.body.Role8Id, TeamId: team.id} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: req.body.Role9Id, TeamId: team.id} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: req.body.Role10Id, TeamId: team.id} + }) + if(isAuth1) { + var role1 = isAuth1.id + } else { + var role1 = null + } + if(isAuth2) { + var role2 = isAuth2.id + } else { + var role2 = null + } + if(isAuth3) { + var role3 = isAuth3.id + } else { + var role3 = null + } + if(isAuth4) { + var role4 = isAuth4.id + } else { + var role4 = null + } + if(isAuth5) { + var role5 = isAuth5.id + } else { + var role5 = null + } + if(isAuth6) { + var role6 = isAuth6.id + } else { + var role6 = null + } + if(isAuth7) { + var role7 = isAuth7.id + } else { + var role7 = null + } + if(isAuth8) { + var role8 = isAuth8.id + } else { + var role8 = null + } + if(isAuth9) { + var role9 = isAuth9.id + } else { + var role9 = null + } + if(isAuth10) { + var role10 = isAuth10.id + } else { + var role10 = null + } + user.update({RoleId: role1, Role2Id: role2, Role3Id: role3, Role4Id: role4, Role5Id: role5, Role6Id: role6, Role7Id: role7, Role8Id: role8, Role9Id: role9, Role10Id: role10 }) + res.status(200) + res.json({success: true}) + } else if (!teamJoinTest) { + res.status(400) + res.json({success: false}) + } + } else { + throw Errors.teamDoesNotExistOrPermInvalid + } + } catch (e) { next(e) } +}) + +router.put('/members/reset/:username/:id', auth, async(req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let userFind = await User.findOne({ + where: {username: req.params.id} + }) + let user = await TeamMemberRole.findOne({ + where: {UserId: userFind.id, TeamId: team.id} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + let isAuth11 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, changeTeamRoles: true} + }) + let isAuth12 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, changeTeamRoles: true} + }) + let isAuth13 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, changeTeamRoles: true} + }) + let isAuth14 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, changeTeamRoles: true} + }) + let isAuth15 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, changeTeamRoles: true} + }) + let isAuth16 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, changeTeamRoles: true} + }) + let isAuth17 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, changeTeamRoles: true} + }) + let isAuth18 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, changeTeamRoles: true} + }) + let isAuth19 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, changeTeamRoles: true} + }) + let isAuth20 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, changeTeamRoles: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10, + isAuth11, + isAuth12, + isAuth13, + isAuth14, + isAuth15, + isAuth16, + isAuth17, + isAuth18, + isAuth19, + isAuth20 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(team && otherThanNull) { + 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) { + if(team.OwnerId !== req.userData.UserId && team.OwnerId === userFind.id) { + throw Errors.modifyOwner + } + let isAuth1 = await TeamRoles.findOne({ + where: {name: 'Members', TeamId: team.id} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {name: 'Administrators', TeamId: team.id} + }) + if(team.OwnerId === userFind.id) { + user.update({ + RoleId: isAuth1.id, + Role2Id: isAuth2.id, + Role3Id: null, + Role4Id: null, + Role5Id: null, + Role6Id: null, + Role7Id: null, + Role8Id: null, + Role9Id: null, + Role10Id: null + }) + } else { + user.update({ + RoleId: isAuth1.id, + Role2Id: null, + Role3Id: null, + Role4Id: null, + Role5Id: null, + Role6Id: null, + Role7Id: null, + Role8Id: null, + Role9Id: null, + Role10Id: null + }) + } + res.status(200) + res.json({success: true}) + } else if (!teamJoinTest) { + res.status(400) + res.json({success: false}) + } + } else { + throw Errors.teamDoesNotExistOrPermInvalid + } + } catch (e) { next(e) } +}) + +router.put('/:username/invites/create', auth, async(req, res, next) => { +try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: 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 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(team && otherThanNull) { + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10 + ] + let otherThanNullAdmin = allowArray.some(function (el) { + return el !== null; + }); + if(otherThanNullAdmin) { + let create = await TeamInvite.create({ + maxUses: req.body.maxUses, + RoleId: req.body.RoleId, + TeamId: team.id, + UserId: req.userData.UserId, + code: cryptoRandomString({length:8, type: "alphanumeric"}) + }) + let createJSON = create.toJSON() + res.status(200) + res.json(createJSON) + } else { + let create = await TeamInvite.create({ + maxUses: req.body.maxUses, + TeamId: team.id, + UserId: req.userData.UserId, + code: cryptoRandomString({length:8, type: "alphanumeric"}) + }) + let createJSON = create.toJSON() + res.status(200) + res.json(createJSON) + } + } else { + throw Errors.inviteDenied + } +} catch (e) { next(e) } +}) + +router.get('/:username/invites/list', auth, async(req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(team && otherThanNull) { + let roles = await TeamInvite.findAll({ + where: { + TeamId: team.id + }, + include: { model: User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'locked', 'admin', 'booster', 'executive', 'bot'] }, + }) + res.status(200) + res.json(roles) + } else { + throw Errors.teamDoesNotExistOrPermInvalid + } + } catch (e) { next(e) } +}) + +router.delete('/:username/invites/delete/:code', auth, async(req, res, next) => { + try { + let team = await Team.findOne({ + where: {username: req.params.username} + }); + let isAuthMem = await TeamMembers.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }); + if(!isAuthMem) { + throw Errors.notInTeam + } + let isAuthRole = await TeamMemberRole.findOne({ + where: {UserId: req.userData.UserId, TeamId: team.id} + }) + let isAuth1 = await TeamRoles.findOne({ + where: {id: isAuthRole.RoleId, administrator: true} + }) + let isAuth2 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role2Id, administrator: true} + }) + let isAuth3 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role3Id, administrator: true} + }) + let isAuth4 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role4Id, administrator: true} + }) + let isAuth5 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role5Id, administrator: true} + }) + let isAuth6 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role6Id, administrator: true} + }) + let isAuth7 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role7Id, administrator: true} + }) + let isAuth8 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role8Id, administrator: true} + }) + let isAuth9 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role9Id, administrator: true} + }) + let isAuth10 = await TeamRoles.findOne({ + where: {id: isAuthRole.Role10Id, administrator: true} + }) + const allowArray = [ + isAuth1, + isAuth2, + isAuth3, + isAuth4, + isAuth5, + isAuth6, + isAuth7, + isAuth8, + isAuth9, + isAuth10 + ] + let otherThanNull = allowArray.some(function (el) { + return el !== null; + }); + if(team && otherThanNull) { + let code = await TeamInvite.findOne({ + where: {code: req.params.code, TeamId: team.id} + }); + if(code) { + await code.killInvite(req.params.code) + res.status(200) + res.json({success: true}) + } else { + throw Errors.inviteInvalid + } + } else { + throw Errors.teamDoesNotExist + } + } catch (e) { next(e) } +}) + +module.exports = router; diff --git a/v1_routes/team_wall.js b/v1_routes/team_wall.js new file mode 100644 index 0000000..72cd32b --- /dev/null +++ b/v1_routes/team_wall.js @@ -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 diff --git a/v1_routes/thread.js b/v1_routes/thread.js new file mode 100644 index 0000000..ac537b5 --- /dev/null +++ b/v1_routes/thread.js @@ -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 diff --git a/v1_routes/transactions.js b/v1_routes/transactions.js new file mode 100644 index 0000000..53c7101 --- /dev/null +++ b/v1_routes/transactions.js @@ -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; diff --git a/v1_routes/user.js b/v1_routes/user.js new file mode 100644 index 0000000..785324f --- /dev/null +++ b/v1_routes/user.js @@ -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; diff --git a/v1_routes/user_passkey.js b/v1_routes/user_passkey.js new file mode 100644 index 0000000..c5c7809 --- /dev/null +++ b/v1_routes/user_passkey.js @@ -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 diff --git a/routes/user_wall.js b/v1_routes/user_wall.js similarity index 98% rename from routes/user_wall.js rename to v1_routes/user_wall.js index 9b3e188..690a8d1 100644 --- a/routes/user_wall.js +++ b/v1_routes/user_wall.js @@ -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') diff --git a/v1_routes/userinfo.js b/v1_routes/userinfo.js new file mode 100644 index 0000000..b699ff3 --- /dev/null +++ b/v1_routes/userinfo.js @@ -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 diff --git a/v1_routes/userutils.js b/v1_routes/userutils.js new file mode 100644 index 0000000..f7444ae --- /dev/null +++ b/v1_routes/userutils.js @@ -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 ', + 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; diff --git a/v2_routes/StaffApplications.js b/v2_routes/StaffApplications.js new file mode 100644 index 0000000..7e1447f --- /dev/null +++ b/v2_routes/StaffApplications.js @@ -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; diff --git a/v2_routes/UnbanRequest.js b/v2_routes/UnbanRequest.js new file mode 100644 index 0000000..004d294 --- /dev/null +++ b/v2_routes/UnbanRequest.js @@ -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 diff --git a/v2_routes/about.js b/v2_routes/about.js new file mode 100644 index 0000000..8d7ad21 --- /dev/null +++ b/v2_routes/about.js @@ -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 diff --git a/routes/admin.js b/v2_routes/admin.js similarity index 100% rename from routes/admin.js rename to v2_routes/admin.js diff --git a/v2_routes/admin_kill_session.js b/v2_routes/admin_kill_session.js new file mode 100644 index 0000000..2ef3437 --- /dev/null +++ b/v2_routes/admin_kill_session.js @@ -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; diff --git a/routes/admin_token.js b/v2_routes/admin_token.js similarity index 100% rename from routes/admin_token.js rename to v2_routes/admin_token.js diff --git a/routes/admin_user_list.js b/v2_routes/admin_user_list.js similarity index 100% rename from routes/admin_user_list.js rename to v2_routes/admin_user_list.js diff --git a/routes/avatar.js b/v2_routes/avatar.js similarity index 98% rename from routes/avatar.js rename to v2_routes/avatar.js index 4ed306f..c3260b4 100644 --- a/routes/avatar.js +++ b/v2_routes/avatar.js @@ -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; diff --git a/routes/award.js b/v2_routes/award.js similarity index 100% rename from routes/award.js rename to v2_routes/award.js diff --git a/routes/ban.js b/v2_routes/ban.js similarity index 100% rename from routes/ban.js rename to v2_routes/ban.js diff --git a/v2_routes/blog.js b/v2_routes/blog.js new file mode 100644 index 0000000..894e18f --- /dev/null +++ b/v2_routes/blog.js @@ -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 \ No newline at end of file diff --git a/v2_routes/category.js b/v2_routes/category.js new file mode 100644 index 0000000..495bd97 --- /dev/null +++ b/v2_routes/category.js @@ -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 diff --git a/v2_routes/conversation.js b/v2_routes/conversation.js new file mode 100644 index 0000000..fc0f6e4 --- /dev/null +++ b/v2_routes/conversation.js @@ -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; \ No newline at end of file diff --git a/v2_routes/experiments.js b/v2_routes/experiments.js new file mode 100644 index 0000000..e69de29 diff --git a/v2_routes/feedback.js b/v2_routes/feedback.js new file mode 100644 index 0000000..70b1b45 --- /dev/null +++ b/v2_routes/feedback.js @@ -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 \ No newline at end of file diff --git a/v2_routes/inventory.js b/v2_routes/inventory.js new file mode 100644 index 0000000..678269e --- /dev/null +++ b/v2_routes/inventory.js @@ -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 diff --git a/v2_routes/link_preview.js b/v2_routes/link_preview.js new file mode 100644 index 0000000..f227d6b --- /dev/null +++ b/v2_routes/link_preview.js @@ -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; diff --git a/v2_routes/log.js b/v2_routes/log.js new file mode 100644 index 0000000..330da29 --- /dev/null +++ b/v2_routes/log.js @@ -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 diff --git a/v2_routes/login_status.js b/v2_routes/login_status.js new file mode 100644 index 0000000..0632f59 --- /dev/null +++ b/v2_routes/login_status.js @@ -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; diff --git a/v2_routes/maintenance.js b/v2_routes/maintenance.js new file mode 100644 index 0000000..06a4878 --- /dev/null +++ b/v2_routes/maintenance.js @@ -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 diff --git a/routes/marketplace.js b/v2_routes/marketplace.js similarity index 96% rename from routes/marketplace.js rename to v2_routes/marketplace.js index 11b75e6..137518e 100644 --- a/routes/marketplace.js +++ b/v2_routes/marketplace.js @@ -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' diff --git a/v2_routes/message.js b/v2_routes/message.js new file mode 100644 index 0000000..6928a3d --- /dev/null +++ b/v2_routes/message.js @@ -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; diff --git a/routes/node.js b/v2_routes/node.js similarity index 100% rename from routes/node.js rename to v2_routes/node.js diff --git a/routes/notification.js b/v2_routes/notification.js similarity index 100% rename from routes/notification.js rename to v2_routes/notification.js diff --git a/routes/poll.js b/v2_routes/poll.js similarity index 100% rename from routes/poll.js rename to v2_routes/poll.js diff --git a/routes/post.js b/v2_routes/post.js similarity index 100% rename from routes/post.js rename to v2_routes/post.js diff --git a/routes/relationship.js b/v2_routes/relationship.js similarity index 89% rename from routes/relationship.js rename to v2_routes/relationship.js index 887ca8a..de7a224 100644 --- a/routes/relationship.js +++ b/v2_routes/relationship.js @@ -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 = { diff --git a/routes/report.js b/v2_routes/report.js similarity index 100% rename from routes/report.js rename to v2_routes/report.js diff --git a/routes/search.js b/v2_routes/search.js similarity index 100% rename from routes/search.js rename to v2_routes/search.js diff --git a/v2_routes/state.js b/v2_routes/state.js new file mode 100644 index 0000000..d9fd648 --- /dev/null +++ b/v2_routes/state.js @@ -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 diff --git a/routes/stats.js b/v2_routes/stats.js similarity index 100% rename from routes/stats.js rename to v2_routes/stats.js diff --git a/routes/team.js b/v2_routes/team.js similarity index 100% rename from routes/team.js rename to v2_routes/team.js diff --git a/routes/team_admin.js b/v2_routes/team_admin.js similarity index 100% rename from routes/team_admin.js rename to v2_routes/team_admin.js diff --git a/routes/team_wall.js b/v2_routes/team_wall.js similarity index 99% rename from routes/team_wall.js rename to v2_routes/team_wall.js index 7b2a95c..23806a1 100644 --- a/routes/team_wall.js +++ b/v2_routes/team_wall.js @@ -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) => { diff --git a/routes/thread.js b/v2_routes/thread.js similarity index 100% rename from routes/thread.js rename to v2_routes/thread.js diff --git a/routes/transactions.js b/v2_routes/transactions.js similarity index 100% rename from routes/transactions.js rename to v2_routes/transactions.js diff --git a/routes/user.js b/v2_routes/user.js similarity index 98% rename from routes/user.js rename to v2_routes/user.js index 3296ca8..2f85743 100644 --- a/routes/user.js +++ b/v2_routes/user.js @@ -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} } diff --git a/routes/user_passkey.js b/v2_routes/user_passkey.js similarity index 89% rename from routes/user_passkey.js rename to v2_routes/user_passkey.js index c81dad1..7421f72 100644 --- a/routes/user_passkey.js +++ b/v2_routes/user_passkey.js @@ -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 diff --git a/v2_routes/user_wall.js b/v2_routes/user_wall.js new file mode 100644 index 0000000..397e632 --- /dev/null +++ b/v2_routes/user_wall.js @@ -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 diff --git a/routes/userinfo.js b/v2_routes/userinfo.js similarity index 100% rename from routes/userinfo.js rename to v2_routes/userinfo.js diff --git a/routes/userutils.js b/v2_routes/userutils.js similarity index 93% rename from routes/userutils.js rename to v2_routes/userutils.js index f69e918..b3fcf89 100644 --- a/routes/userutils.js +++ b/v2_routes/userutils.js @@ -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) } })