init
This commit is contained in:
68
.gitignore
vendored
Normal file
68
.gitignore
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
#----
|
||||
#Unity
|
||||
unity/
|
||||
uploads/
|
||||
public/.well-known
|
||||
|
||||
21
activity/activity.controller.js
Normal file
21
activity/activity.controller.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// MODELS / TOOLS
|
||||
const Activity = require("./activity.model");
|
||||
|
||||
exports.GetAllActivities = async (req, res) => {
|
||||
|
||||
let activityRequest;
|
||||
if (req.body.type) {
|
||||
activityRequest = { type: req.body.type };
|
||||
} else if (req.body.username) {
|
||||
activityRequest = { username: req.body.username };
|
||||
}
|
||||
else {
|
||||
activityRequest = { };
|
||||
}
|
||||
|
||||
const a = await Activity.Get(activityRequest);
|
||||
if (!a) return res.status(500).send({ error: "Failed!!" });
|
||||
|
||||
return res.status(200).send(a);
|
||||
};
|
||||
|
||||
56
activity/activity.model.js
Normal file
56
activity/activity.model.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const activitySchema = new Schema(
|
||||
{
|
||||
type: {type: String},
|
||||
username: {type: String},
|
||||
timestamp: {type: Date},
|
||||
data: {type: Object, _id: false},
|
||||
});
|
||||
|
||||
activitySchema.methods.toObj = function () {
|
||||
var elem = this.toObject();
|
||||
delete elem.__v;
|
||||
delete elem._id;
|
||||
return elem;
|
||||
};
|
||||
|
||||
const Activity = mongoose.model("Activity", activitySchema);
|
||||
exports.Activity = Activity;
|
||||
|
||||
// ------------------------------
|
||||
|
||||
exports.LogActivity = async (type, username, data) => {
|
||||
var activity_data = {
|
||||
type: type,
|
||||
username: username,
|
||||
timestamp: Date.now(),
|
||||
data: data
|
||||
}
|
||||
try {
|
||||
const activity = new Activity(activity_data);
|
||||
return await activity.save();
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.GetAll = async () => {
|
||||
try {
|
||||
const logs = await Activity.find({});
|
||||
return logs;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.Get = async (data) => {
|
||||
try {
|
||||
const logs = await Activity.find(data);
|
||||
return logs;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
19
activity/activity.routes.js
Normal file
19
activity/activity.routes.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const ActivityController = require("./activity.controller");
|
||||
const AuthTool = require("../authorization/auth.tool");
|
||||
const config = require("../config");
|
||||
|
||||
const ADMIN = config.permissions.ADMIN; //Highest permision, can read and write all users
|
||||
const SERVER = config.permissions.SERVER; //Middle permission, can read all users
|
||||
const USER = config.permissions.USER; //Lowest permision, can only do things on same user
|
||||
|
||||
exports.route = function (app) {
|
||||
|
||||
app.get("/activity", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
ActivityController.GetAllActivities,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
94
authorization/auth.controller.js
Normal file
94
authorization/auth.controller.js
Normal file
@@ -0,0 +1,94 @@
|
||||
|
||||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
const config = require('../config.js');
|
||||
const jwtSecret = config.jwt_secret;
|
||||
const UserModel = require('../users/users.model');
|
||||
const UserTool = require('../users/users.tool');
|
||||
|
||||
exports.Login = (req, res) => {
|
||||
try {
|
||||
|
||||
let refreshId = req.login.userId + jwtSecret;
|
||||
let refresh_key = crypto.randomBytes(16).toString('base64');
|
||||
let refresh_hash = crypto.createHmac('sha512', refresh_key).update(refreshId).digest("base64");
|
||||
req.login.refresh_key = refresh_key;
|
||||
|
||||
let access_token = jwt.sign(req.login, jwtSecret);
|
||||
|
||||
//Delete some keys for security, empty keys are never valid, also update login time
|
||||
var update = {refresh_key: refresh_key, proof_key: "", password_recovery_key: "", last_login_time: new Date(), last_online_time: new Date()};
|
||||
UserModel.patch(req.login.userId, update);
|
||||
|
||||
var odata = {
|
||||
id: req.login.userId,
|
||||
username: req.login.username,
|
||||
access_token: access_token,
|
||||
refresh_token: refresh_hash,
|
||||
permission_level: req.login.permission_level,
|
||||
validation_level: req.login.validation_level,
|
||||
duration: config.jwt_expiration,
|
||||
server_time: new Date(),
|
||||
version: config.version
|
||||
}
|
||||
|
||||
return res.status(201).send(odata);
|
||||
|
||||
} catch (err) {
|
||||
return res.status(500).send({error: err});
|
||||
}
|
||||
};
|
||||
|
||||
exports.KeepOnline = async(req, res, next) => {
|
||||
|
||||
var token = req.jwt;
|
||||
UserModel.patch(token.userId, {last_online_time: new Date()});
|
||||
|
||||
var data = {id: token.userId, username: token.username, login_time: new Date(token.iat * 1000), server_time: new Date() };
|
||||
return res.status(200).send(data);
|
||||
};
|
||||
|
||||
exports.GetVersion = (req, res) =>{
|
||||
return res.status(200).send({version: config.version});
|
||||
};
|
||||
|
||||
// ----- verify user -----------
|
||||
|
||||
exports.ValidateToken = async(req, res, next) => {
|
||||
|
||||
var token = req.jwt;
|
||||
var data = {id: token.userId, username: token.username, login_time: new Date(token.iat * 1000), server_time: new Date() };
|
||||
return res.status(200).send(data);
|
||||
};
|
||||
|
||||
exports.CreateProof = async(req, res) =>
|
||||
{
|
||||
var userId = req.jwt.userId;
|
||||
|
||||
var user = await UserModel.getById(userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found"});
|
||||
|
||||
user.proof_key = crypto.randomBytes(20).toString('base64');
|
||||
await UserModel.save(user);
|
||||
|
||||
return res.status(200).send({proof: user.proof_key});
|
||||
}
|
||||
|
||||
exports.ValidateProof = async(req, res) =>
|
||||
{
|
||||
var username = req.params.username;
|
||||
var proof = req.params.proof;
|
||||
|
||||
if(!username || typeof username != "string" || !proof || typeof proof != "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var user = await UserModel.getByUsername(username);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found"});
|
||||
|
||||
if(!user.proof_key || user.proof_key != proof)
|
||||
return res.status(403).send({error: "Invalid Proof"});
|
||||
|
||||
return res.status(200).send();
|
||||
}
|
||||
44
authorization/auth.routes.js
Normal file
44
authorization/auth.routes.js
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
const AuthController = require('./auth.controller');
|
||||
const AuthTool = require('./auth.tool');
|
||||
|
||||
exports.route = function (app) {
|
||||
|
||||
//Body: username, password
|
||||
app.post('/auth', app.auth_limiter, [
|
||||
AuthTool.isLoginValid,
|
||||
AuthController.Login
|
||||
]);
|
||||
|
||||
//Body: refresh_token
|
||||
app.post('/auth/refresh', app.auth_limiter, [
|
||||
AuthTool.isRefreshValid,
|
||||
AuthController.Login
|
||||
]);
|
||||
|
||||
app.get('/auth/keep',[
|
||||
AuthTool.isValidJWT,
|
||||
AuthController.KeepOnline
|
||||
]);
|
||||
|
||||
app.get('/auth/validate',[
|
||||
AuthTool.isValidJWT,
|
||||
AuthController.ValidateToken
|
||||
]);
|
||||
|
||||
app.get("/auth/proof/create", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthController.CreateProof
|
||||
]);
|
||||
|
||||
app.get("/auth/proof/:username/:proof", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthController.ValidateProof
|
||||
]);
|
||||
|
||||
app.get('/version', [
|
||||
AuthController.GetVersion
|
||||
]);
|
||||
|
||||
|
||||
};
|
||||
162
authorization/auth.tool.js
Normal file
162
authorization/auth.tool.js
Normal file
@@ -0,0 +1,162 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const config = require('../config.js');
|
||||
const UserModel = require('../users/users.model');
|
||||
|
||||
//-----Validations------
|
||||
|
||||
var AuthTool = {};
|
||||
|
||||
|
||||
AuthTool.isValidJWT = (req, res, next) => {
|
||||
|
||||
if (!req.headers['authorization'])
|
||||
return res.status(401).send();
|
||||
|
||||
try {
|
||||
//Validate access token
|
||||
let authorization = req.headers['authorization'];
|
||||
req.jwt = jwt.verify(authorization, config.jwt_secret);
|
||||
|
||||
//Validate expiry time
|
||||
const nowSeconds = Math.round(Number(new Date()) / 1000);
|
||||
const expiration = req.jwt.iat + config.jwt_expiration;
|
||||
if(nowSeconds > expiration)
|
||||
return res.status(403).send({error: "Expired"});
|
||||
|
||||
} catch (err) {
|
||||
return res.status(403).send({error: "Invalid Token"});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
AuthTool.isLoginValid = async(req, res, next) => {
|
||||
|
||||
if (!req.body || !req.body.password)
|
||||
return res.status(400).send({error: 'Invalid params'});
|
||||
|
||||
//Requires EITHER username or email, dont need both
|
||||
if (!req.body.email && !req.body.username)
|
||||
return res.status(400).send({error: 'Invalid params'});
|
||||
|
||||
var user = null;
|
||||
|
||||
if(req.body.email)
|
||||
user = await UserModel.getByEmail(req.body.email);
|
||||
else if(req.body.username)
|
||||
user = await UserModel.getByUsername(req.body.username);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "Invalid username or password"});
|
||||
|
||||
let validPass = AuthTool.validatePassword(user, req.body.password);
|
||||
if(!validPass)
|
||||
return res.status(400).send({error: 'Invalid username or password'});
|
||||
|
||||
if(user.permission_level <= 0)
|
||||
return res.status(403).send({error: "Your account has been disabled, please contact support!"});
|
||||
|
||||
req.login = {
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
permission_level: user.permission_level,
|
||||
validation_level: user.validation_level,
|
||||
provider: req.body.email ? 'email' : 'username',
|
||||
};
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
AuthTool.isRefreshValid = async(req, res, next) => {
|
||||
|
||||
if (!req.body || !req.body.refresh_token)
|
||||
return res.status(400).send();
|
||||
|
||||
if (!req.headers['authorization'])
|
||||
return res.status(401).send();
|
||||
|
||||
if (typeof req.body.refresh_token !== "string")
|
||||
return res.status(400).send();
|
||||
|
||||
try {
|
||||
//Validate access token
|
||||
let authorization = req.headers['authorization'];
|
||||
req.jwt = jwt.verify(authorization, config.jwt_secret);
|
||||
|
||||
//Validate expiry time
|
||||
const nowUnixSeconds = Math.round(Number(new Date()) / 1000);
|
||||
const expiration = req.jwt.iat + config.jwt_refresh_expiration;
|
||||
if(nowUnixSeconds > expiration)
|
||||
return res.status(403).send({error: "Token Expired"});
|
||||
|
||||
//Validate refresh token
|
||||
let refresh_token = req.body.refresh_token;
|
||||
let hash = crypto.createHmac('sha512', req.jwt.refresh_key).update(req.jwt.userId + config.jwt_secret).digest("base64");
|
||||
if (hash !== refresh_token)
|
||||
return res.status(403).send({error: 'Invalid refresh token'});
|
||||
|
||||
//Validate refresh key in DB
|
||||
var user = await UserModel.getById(req.jwt.userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "Invalid user"});
|
||||
|
||||
if(user.refresh_key !== req.jwt.refresh_key)
|
||||
return res.status(403).send({error: 'Invalid refresh key'});
|
||||
|
||||
} catch (err) {
|
||||
return res.status(403).send({error: "Invalid Token"});
|
||||
}
|
||||
|
||||
req.login = req.jwt;
|
||||
delete req.login.iat; //Delete previous iat to generate a new one
|
||||
return next();
|
||||
};
|
||||
|
||||
AuthTool.hashPassword = (password) => {
|
||||
let saltNew = crypto.randomBytes(16).toString('base64');
|
||||
let hashNew = crypto.createHmac('sha512', saltNew).update(password).digest("base64");
|
||||
let newPass = saltNew + "$" + hashNew;
|
||||
return newPass;
|
||||
}
|
||||
|
||||
AuthTool.validatePassword = (user, password) =>
|
||||
{
|
||||
let passwordFields = user.password.split('$');
|
||||
let salt = passwordFields[0];
|
||||
let hash = crypto.createHmac('sha512', salt).update(password).digest("base64");
|
||||
return hash === passwordFields[1];
|
||||
}
|
||||
|
||||
//--- Permisions -----
|
||||
|
||||
AuthTool.isPermissionLevel = (required_permission) => {
|
||||
return (req, res, next) => {
|
||||
let user_permission_level = parseInt(req.jwt.permission_level);
|
||||
if (user_permission_level >= required_permission) {
|
||||
return next();
|
||||
} else {
|
||||
return res.status(403).send({error: "Permission Denied"});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
AuthTool.isSameUserOr = (required_permission) => {
|
||||
return (req, res, next) => {
|
||||
let user_permission_level = parseInt(req.jwt.permission_level);
|
||||
let userId = req.params.userId || "";
|
||||
let same_user = (req.jwt.userId === userId || req.jwt.username.toLowerCase() === userId.toLowerCase());
|
||||
if (userId && same_user) {
|
||||
return next();
|
||||
} else {
|
||||
if (user_permission_level >= required_permission) {
|
||||
return next();
|
||||
} else {
|
||||
return res.status(403).send({error: "Permission Denied"});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = AuthTool;
|
||||
134
cards/cards.controller.js
Normal file
134
cards/cards.controller.js
Normal file
@@ -0,0 +1,134 @@
|
||||
const CardModel = require('../cards/cards.model');
|
||||
const Activity = require("../activity/activity.model");
|
||||
const config = require('../config');
|
||||
|
||||
exports.AddCard = async(req, res) =>
|
||||
{
|
||||
var tid = req.body.tid;
|
||||
var type = req.body.type;
|
||||
var team = req.body.team;
|
||||
var rarity = req.body.rarity || "";
|
||||
var mana = req.body.mana || 0;
|
||||
var attack = req.body.attack || 0;
|
||||
var hp = req.body.hp || 0;
|
||||
var cost = req.body.cost || 1;
|
||||
var packs = req.body.packs || [];
|
||||
|
||||
if(!tid || typeof tid !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!type || typeof type !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!team || typeof team !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!rarity || typeof rarity !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!Number.isInteger(mana) || !Number.isInteger(attack) || !Number.isInteger(hp) || !Number.isInteger(cost))
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(packs && !Array.isArray(packs))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var data = {
|
||||
tid: tid,
|
||||
type: type,
|
||||
team: team,
|
||||
rarity: rarity,
|
||||
mana: mana,
|
||||
attack: attack,
|
||||
hp: hp,
|
||||
cost: cost,
|
||||
packs: packs,
|
||||
}
|
||||
|
||||
//Update or create
|
||||
var card = await CardModel.get(tid);
|
||||
if(card)
|
||||
card = await CardModel.update(card, data);
|
||||
else
|
||||
card = await CardModel.create(data);
|
||||
|
||||
if(!card)
|
||||
return res.status(500).send({error: "Error updating card"});
|
||||
|
||||
return res.status(200).send(data);
|
||||
};
|
||||
|
||||
exports.AddCardList = async(req, res) =>
|
||||
{
|
||||
var cards = req.body.cards;
|
||||
if(!Array.isArray(cards))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var ocards = [];
|
||||
for(var i=0; i<cards.length; i++)
|
||||
{
|
||||
var card = cards[i];
|
||||
if(card && card.tid && card.type && card.team)
|
||||
{
|
||||
var data = {
|
||||
tid: card.tid,
|
||||
type: card.type,
|
||||
team: card.team,
|
||||
rarity: card.rarity || "",
|
||||
mana: card.mana || 0,
|
||||
attack: card.attack || 0,
|
||||
hp: card.hp || 0,
|
||||
cost: card.cost || 0,
|
||||
packs: card.packs || [],
|
||||
}
|
||||
|
||||
var ccard = await CardModel.get(card.tid);
|
||||
if(ccard)
|
||||
ccard = await CardModel.update(ccard, data);
|
||||
else
|
||||
ccard = await CardModel.create(data);
|
||||
|
||||
ocards.push(ccard);
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).send(ocards);
|
||||
};
|
||||
|
||||
exports.DeleteCard = async(req, res) =>
|
||||
{
|
||||
CardModel.remove(req.params.tid);
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.DeleteAll = async(req, res) =>
|
||||
{
|
||||
CardModel.removeAll();
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.GetCard = async(req, res) =>
|
||||
{
|
||||
var tid = req.params.tid;
|
||||
|
||||
if(!tid)
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var card = await CardModel.get(tid);
|
||||
if(!card)
|
||||
return res.status(404).send({error: "Card not found: " + tid});
|
||||
|
||||
return res.status(200).send(card.toObj());
|
||||
};
|
||||
|
||||
exports.GetAll = async(req, res) =>
|
||||
{
|
||||
var cards = await CardModel.getAll();
|
||||
|
||||
for(var i=0; i<cards.length; i++){
|
||||
cards[i] = cards[i].toObj();
|
||||
}
|
||||
|
||||
return res.status(200).send(cards);
|
||||
};
|
||||
|
||||
|
||||
111
cards/cards.model.js
Normal file
111
cards/cards.model.js
Normal file
@@ -0,0 +1,111 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const cardsSchema = new Schema({
|
||||
|
||||
tid: { type: String, index: true, unique: true, default: "" },
|
||||
type: { type: String, default: "" },
|
||||
team: { type: String, default: "" },
|
||||
rarity: {type: String, default: ""},
|
||||
mana: {type: Number, default: 0},
|
||||
attack: {type: Number, default: 0},
|
||||
hp: {type: Number, default: 0},
|
||||
cost: {type: Number, default: 0},
|
||||
|
||||
packs: [{type: String}], //Card is available in those packs
|
||||
});
|
||||
|
||||
cardsSchema.methods.toObj = function() {
|
||||
var card = this.toObject();
|
||||
delete card.__v;
|
||||
delete card._id;
|
||||
return card;
|
||||
};
|
||||
|
||||
const Card = mongoose.model('Cards', cardsSchema);
|
||||
|
||||
exports.get = async(tid) => {
|
||||
try{
|
||||
var card = await Card.findOne({tid: tid});
|
||||
return card;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAll = async(filter) => {
|
||||
|
||||
try{
|
||||
filter = filter || {};
|
||||
var cards = await Card.find(filter);
|
||||
return cards || [];
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.getByPack = async(packId, filter) => {
|
||||
|
||||
try{
|
||||
filter = filter || {};
|
||||
if(packId)
|
||||
{
|
||||
filter.packs = {$in:[packId]};
|
||||
}
|
||||
var cards = await Card.find(filter);
|
||||
return cards || [];
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.create = async(data) => {
|
||||
try{
|
||||
var card = new Card(data);
|
||||
return await card.save();
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.update = async(card, data) => {
|
||||
|
||||
try{
|
||||
if(!card) return null;
|
||||
|
||||
for (let i in data) {
|
||||
card[i] = data[i];
|
||||
card.markModified(i);
|
||||
}
|
||||
|
||||
var updated = await card.save();
|
||||
return updated;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = async(tid) => {
|
||||
try{
|
||||
var result = await Card.deleteOne({tid: tid});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.removeAll = async() => {
|
||||
try{
|
||||
var result = await Card.deleteMany({});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
42
cards/cards.routes.js
Normal file
42
cards/cards.routes.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const CardController = require('./cards.controller');
|
||||
const AuthTool = require('../authorization/auth.tool');
|
||||
const config = require('../config');
|
||||
|
||||
const ADMIN = config.permissions.ADMIN; //Highest permision, can read and write all users
|
||||
const SERVER = config.permissions.SERVER; //Higher permission, can read all users
|
||||
const USER = config.permissions.USER; //Lowest permision, can only do things on same user
|
||||
|
||||
exports.route = function (app) {
|
||||
|
||||
app.get('/cards/:tid', [
|
||||
CardController.GetCard
|
||||
]);
|
||||
|
||||
app.get('/cards', [
|
||||
CardController.GetAll
|
||||
]);
|
||||
|
||||
app.post('/cards/add', [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
CardController.AddCard
|
||||
]);
|
||||
|
||||
app.post('/cards/add/list', [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
CardController.AddCardList
|
||||
]);
|
||||
|
||||
app.delete("/cards/:tid", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
CardController.DeleteCard
|
||||
]);
|
||||
|
||||
app.delete("/cards", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
CardController.DeleteAll
|
||||
]);
|
||||
};
|
||||
111
cards/cards.tool.js
Normal file
111
cards/cards.tool.js
Normal file
@@ -0,0 +1,111 @@
|
||||
const config = require('../config.js');
|
||||
const crypto = require('crypto');
|
||||
const CardModel = require('../cards/cards.model');
|
||||
|
||||
const CardTool = {};
|
||||
|
||||
CardTool.getPackCards = async(pack) =>
|
||||
{
|
||||
var pack_cards = await CardModel.getByPack(pack.tid);
|
||||
|
||||
var cards = [];
|
||||
for(var i=0; i<pack.cards; i++)
|
||||
{
|
||||
if(pack.random)
|
||||
{
|
||||
//Randomized set
|
||||
var rarity_tid = CardTool.getRandomRarity(pack, i==0);
|
||||
var variant_tid = CardTool.getRandomVariant(pack);
|
||||
var rarity_cards = CardTool.getCardArray(pack_cards, rarity_tid);
|
||||
var card = CardTool.getRandomCard(rarity_cards);
|
||||
if(card)
|
||||
{
|
||||
var cardQ = {tid: card.tid, variant: variant_tid, quantity: 1};
|
||||
cards.push(cardQ);
|
||||
}
|
||||
}
|
||||
else if(i < pack_cards.length)
|
||||
{
|
||||
//Fixed set
|
||||
var card = pack_cards[i];
|
||||
var variant_tid = CardTool.getRandomVariant(pack);
|
||||
var cardQ = {tid: card.tid, variant: variant_tid, quantity: 1};
|
||||
cards.push(cardQ);
|
||||
}
|
||||
}
|
||||
return cards;
|
||||
};
|
||||
|
||||
CardTool.getRandomRarity = (pack, is_first) =>
|
||||
{
|
||||
var rarities = is_first ? pack.rarities_1st : pack.rarities;
|
||||
if(!rarities || rarities.length == 0)
|
||||
return ""; //Any rarity
|
||||
|
||||
var total = 0;
|
||||
for(var rarity of rarities) {
|
||||
total += rarity.value;
|
||||
}
|
||||
|
||||
var rvalue = Math.floor(Math.random()*total);
|
||||
|
||||
for(var i=0; i<rarities.length; i++)
|
||||
{
|
||||
var rarity = rarities[i];
|
||||
if(rvalue < rarity.value)
|
||||
{
|
||||
return rarity.tid;
|
||||
}
|
||||
rvalue -= rarity.value;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
CardTool.getRandomVariant = (pack) =>
|
||||
{
|
||||
var variants = pack.variants;
|
||||
if(!variants || variants.length == 0)
|
||||
return "";
|
||||
|
||||
var total = 0;
|
||||
for(var variant of variants) {
|
||||
total += variant.value;
|
||||
}
|
||||
|
||||
var rvalue = Math.floor(Math.random()*total);
|
||||
|
||||
for(var i=0; i<variants.length; i++)
|
||||
{
|
||||
var variant = variants[i];
|
||||
if(rvalue < variant.value)
|
||||
{
|
||||
return variant.tid;
|
||||
}
|
||||
rvalue -= variant.value;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
CardTool.getCardArray = (all_cards, rarity) =>
|
||||
{
|
||||
var valid_cards = [];
|
||||
for(var i=0; i<all_cards.length; i++)
|
||||
{
|
||||
var card = all_cards[i];
|
||||
if(!rarity || card.rarity == rarity)
|
||||
valid_cards.push(card);
|
||||
}
|
||||
return valid_cards;
|
||||
}
|
||||
|
||||
CardTool.getRandomCard = (all_cards, suffix) =>
|
||||
{
|
||||
if(all_cards.length > 0)
|
||||
{
|
||||
var card = all_cards[Math.floor(Math.random()*all_cards.length)];
|
||||
return card;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports = CardTool;
|
||||
73
config.js
Normal file
73
config.js
Normal file
@@ -0,0 +1,73 @@
|
||||
module.exports = {
|
||||
version: "1.13",
|
||||
|
||||
port: 80,
|
||||
port_https: 443,
|
||||
api_title: "TCG Engine API", //Display name
|
||||
api_url: "", //If you set the URL, will block all direct IP access, or wrong url access, leave blank to allow all url access
|
||||
|
||||
//HTTPS config, certificate is required if you want to enable HTTPS
|
||||
https_key: "/etc/letsencrypt/live/yoursite.com/privkey.pem",
|
||||
https_ca: "/etc/letsencrypt/live/yoursite.com/chain.pem",
|
||||
https_cert: "/etc/letsencrypt/live/yoursite.com/cert.pem",
|
||||
allow_http: true,
|
||||
allow_https: false,
|
||||
|
||||
//JS Web Token Config
|
||||
jwt_secret: "JWT_123456789", //Change this to a unique secret value
|
||||
jwt_expiration: 3600 * 10, //In seconds (10 hours)
|
||||
jwt_refresh_expiration: 3600 * 100, //In seconds (100 hours)
|
||||
|
||||
//User Permissions Config
|
||||
permissions: {
|
||||
USER: 1,
|
||||
SERVER: 5,
|
||||
ADMIN: 10,
|
||||
},
|
||||
|
||||
//Mongo Connection
|
||||
mongo_user: "",
|
||||
mongo_pass: "",
|
||||
mongo_host: "127.0.0.1",
|
||||
mongo_port: "27017",
|
||||
mongo_db: "tcgengine",
|
||||
|
||||
//Limiter to protect from DDOS, will block IP that do too many requests
|
||||
limiter_window: 1000 * 120, //in ms, will reset the counts after this time
|
||||
limiter_max: 500, //max nb of GET requests within the time window
|
||||
limiter_post_max: 100, //max nb of POST requests within the time window
|
||||
limiter_auth_max: 10, //max nb of Login/Register request within the time window
|
||||
limiter_proxy: false, //Must be set to true if your server is behind a proxy, otherwise the proxy itself will be blocked
|
||||
|
||||
ip_whitelist: ["127.0.0.1"], //These IP are not affected by the limiter, for example you could add your game server's IP
|
||||
ip_blacklist: [], //These IP are blocked forever
|
||||
|
||||
//Email config, required for the API to send emails
|
||||
smtp_enabled: false,
|
||||
smtp_name: "TCG Engine", //Name of sender in emails
|
||||
smtp_email: "", //Email used to send
|
||||
smtp_server: "", //SMTP server URL
|
||||
smtp_port: "465",
|
||||
smtp_user: "", //SMTP auth user
|
||||
smtp_password: "", //SMTP auth password
|
||||
|
||||
//ELO settings
|
||||
elo_k: 32, //Higher K number will affect elo more each match
|
||||
elo_ini_k: 128, //K value for the first X matches can be higher
|
||||
elo_ini_match: 5, //X number of match for the previous value
|
||||
|
||||
//New Users
|
||||
start_coins: 5000,
|
||||
start_elo: 1000,
|
||||
|
||||
//Match Rewards
|
||||
coins_victory: 200, //Victory coins reward
|
||||
coins_defeat: 100, //Defeat coins reward
|
||||
xp_victory: 100, //Victory xp reward
|
||||
xp_defeat: 50, //Defeat xp reward
|
||||
|
||||
//Market
|
||||
sell_ratio: 0.8, //Sell ratio compared to buy price
|
||||
avatar_cost: 500,
|
||||
cardback_cost: 1000,
|
||||
};
|
||||
75
decks/decks.controller.js
Normal file
75
decks/decks.controller.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const DeckModel = require('../decks/decks.model');
|
||||
const Activity = require("../activity/activity.model");
|
||||
const config = require('../config');
|
||||
|
||||
exports.AddDeck = async(req, res) =>
|
||||
{
|
||||
var tid = req.body.tid;
|
||||
var title = req.body.title;
|
||||
var hero = req.body.hero;
|
||||
var cards = req.body.cards;
|
||||
|
||||
if(!tid || typeof tid !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!title || typeof title !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!hero || typeof hero !== "object")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(cards && !Array.isArray(cards))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var deck_data = {
|
||||
tid: tid,
|
||||
title: title,
|
||||
hero: hero,
|
||||
cards: cards || [],
|
||||
}
|
||||
|
||||
//Update or create
|
||||
var deck = await DeckModel.get(tid);
|
||||
if(deck)
|
||||
deck = await DeckModel.update(deck, deck_data);
|
||||
else
|
||||
deck = await DeckModel.create(deck_data);
|
||||
|
||||
if(!deck)
|
||||
res.status(500).send({error: "Error updating deck"});
|
||||
|
||||
return res.status(200).send(deck);
|
||||
};
|
||||
|
||||
exports.DeleteDeck = async(req, res) => {
|
||||
DeckModel.remove(req.params.tid);
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.DeleteAll = async(req, res) => {
|
||||
DeckModel.removeAll();
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.GetDeck = async(req, res) =>
|
||||
{
|
||||
var deckId = req.params.tid;
|
||||
|
||||
if(!deckId)
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var deck = await DeckModel.get(deckId);
|
||||
if(!deck)
|
||||
return res.status(404).send({error: "Deck not found: " + deckId});
|
||||
|
||||
return res.status(200).send(deck.toObj());
|
||||
};
|
||||
|
||||
|
||||
exports.GetAll = async(req, res) =>
|
||||
{
|
||||
var decks = await DeckModel.getAll();
|
||||
return res.status(200).send(decks);
|
||||
};
|
||||
|
||||
|
||||
99
decks/decks.model.js
Normal file
99
decks/decks.model.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const deckSchema = new Schema({
|
||||
|
||||
tid: { type: String, index: true, unique: true, default: "" },
|
||||
title: { type: String, default: "" },
|
||||
hero: {tid: String, variant: String, _id: false},
|
||||
cards: [{tid: String, variant: String, quantity: Number, _id: false}],
|
||||
|
||||
});
|
||||
|
||||
deckSchema.methods.toObj = function() {
|
||||
var deck = this.toObject();
|
||||
delete deck.__v;
|
||||
delete deck._id;
|
||||
return deck;
|
||||
};
|
||||
|
||||
const Deck = mongoose.model('Decks', deckSchema);
|
||||
|
||||
exports.get = async(deckId) => {
|
||||
try{
|
||||
var deck = await Deck.findOne({tid: deckId});
|
||||
return deck;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getList = async(decks_tids) => {
|
||||
try{
|
||||
var decks = await Deck.find({tid: { $in: decks_tids } });
|
||||
return decks || [];
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAll = async() => {
|
||||
|
||||
try{
|
||||
var decks = await Deck.find()
|
||||
return decks || [];
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.create = async(data) => {
|
||||
try{
|
||||
var deck = new Deck(data);
|
||||
return await deck.save();
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.update = async(deck, data) => {
|
||||
|
||||
try{
|
||||
if(!deck) return null;
|
||||
|
||||
for (let i in data) {
|
||||
deck[i] = data[i];
|
||||
deck.markModified(i);
|
||||
}
|
||||
|
||||
var updated = await deck.save();
|
||||
return updated;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = async(deckId) => {
|
||||
try{
|
||||
var result = await Deck.deleteOne({tid: deckId});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.removeAll = async() => {
|
||||
try{
|
||||
var result = await Deck.deleteMany({});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
36
decks/decks.routes.js
Normal file
36
decks/decks.routes.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const DeckController = require('./decks.controller');
|
||||
const AuthTool = require('../authorization/auth.tool');
|
||||
const config = require('../config');
|
||||
|
||||
const ADMIN = config.permissions.ADMIN; //Highest permision, can read and write all users
|
||||
const SERVER = config.permissions.SERVER; //Higher permission, can read all users
|
||||
const USER = config.permissions.USER; //Lowest permision, can only do things on same user
|
||||
|
||||
exports.route = function (app) {
|
||||
|
||||
app.get('/decks/:tid', [
|
||||
DeckController.GetDeck
|
||||
]);
|
||||
|
||||
app.get('/decks', [
|
||||
DeckController.GetAll
|
||||
]);
|
||||
|
||||
app.post('/decks/add', [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
DeckController.AddDeck
|
||||
]);
|
||||
|
||||
app.delete("/decks/:tid", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
DeckController.DeleteDeck
|
||||
]);
|
||||
|
||||
app.delete("/decks", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
DeckController.DeleteAll
|
||||
]);
|
||||
};
|
||||
21
jobs/jobs.js
Normal file
21
jobs/jobs.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const schedule = require('node-schedule');
|
||||
|
||||
const ExecuteJobs = async() =>
|
||||
{
|
||||
//console.log('Run Hourly Jobs.....');
|
||||
|
||||
//Add custom hourly jobs here
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
exports.InitJobs = function()
|
||||
{
|
||||
schedule.scheduleJob('* 1 * * *', function(){ // this for one hour
|
||||
ExecuteJobs();
|
||||
});
|
||||
|
||||
//Test run when starting
|
||||
ExecuteJobs();
|
||||
}
|
||||
215
market/market.controller.js
Normal file
215
market/market.controller.js
Normal file
@@ -0,0 +1,215 @@
|
||||
const UserModel = require('../users/users.model');
|
||||
const MarketModel = require('./market.model');
|
||||
const UserTool = require('../users/users.tool');
|
||||
const DateTool = require('../tools/date.tool');
|
||||
const Activity = require("../activity/activity.model");
|
||||
const config = require('../config');
|
||||
|
||||
exports.addOffer = async(req, res) => {
|
||||
|
||||
var username = req.jwt.username;
|
||||
var card_tid = req.body.card;
|
||||
var variant = req.body.variant;
|
||||
var quantity = req.body.quantity;
|
||||
var price = req.body.price;
|
||||
|
||||
//Validate params
|
||||
if (!username || !card_tid || !variant || !quantity || !price)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(typeof username !== "string"|| typeof quantity !== "number" || typeof price !== "number" || typeof card_tid !== "string" || typeof variant !== "string" )
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!Number.isInteger(quantity) || !Number.isInteger(price) || price <= 0 || quantity <= 0)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get user
|
||||
var user = await UserModel.getByUsername(username);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Can't find user " + username });
|
||||
|
||||
if(!UserTool.hasCard(user, card_tid, variant, quantity))
|
||||
return res.status(400).send({ error: "You don't have those cards!" });
|
||||
|
||||
//Offer
|
||||
var offer = {
|
||||
seller: username,
|
||||
card: card_tid,
|
||||
variant: variant,
|
||||
quantity: quantity,
|
||||
price: price,
|
||||
}
|
||||
|
||||
//Remove card from user
|
||||
var removeCards = [{tid: card_tid, variant: variant, quantity: -quantity}];
|
||||
var addSucc = await UserTool.addCards(user, removeCards);
|
||||
if(!addSucc)
|
||||
return res.status(500).send({ error: "Error removing cards from user " + username });
|
||||
|
||||
//Update database
|
||||
var uOffer = await MarketModel.add(username, card_tid, variant, offer);
|
||||
var uUser = await UserModel.update(user, { cards: user.cards, });
|
||||
|
||||
if(!uUser || !uOffer)
|
||||
return res.status(500).send({ error: "Error creating market offer " + username });
|
||||
|
||||
//Activity
|
||||
//var act = await Activity.LogActivity("market_add", req.jwt.username, uOffer.toObj());
|
||||
//if (!act) return res.status(500).send({ error: "Failed to log activity!" });
|
||||
|
||||
return res.status(200).send(uOffer.toObj());
|
||||
};
|
||||
|
||||
exports.removeOffer = async(req, res) => {
|
||||
|
||||
var username = req.jwt.username;
|
||||
var card_tid = req.body.card;
|
||||
var variant = req.body.variant;
|
||||
|
||||
//Validate params
|
||||
if (!username || !card_tid || !variant)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(typeof username !== "string"|| typeof card_tid !== "string" || typeof variant !== "string" )
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
var user = await UserModel.getByUsername(username);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Can't find user " + username });
|
||||
|
||||
var offer = await MarketModel.getOffer(username, card_tid, variant)
|
||||
if (!offer)
|
||||
return res.status(404).send({ error: "No market offer for " + username + " " + card_tid });
|
||||
|
||||
//Add cards user
|
||||
var addCards = [{tid: card_tid, variant: variant, quantity: offer.quantity}];
|
||||
var addSucc = await UserTool.addCards(user, addCards);
|
||||
if(!addSucc)
|
||||
return res.status(500).send({ error: "Error adding cards to user " + username });
|
||||
|
||||
//Update database
|
||||
var uUser = await UserModel.update(user, { cards: user.cards });
|
||||
var uOffer = await MarketModel.remove(username, card_tid, variant);
|
||||
|
||||
if(!uUser || !uOffer)
|
||||
return res.status(500).send({ error: "Error removing market offer " + username });
|
||||
|
||||
//Activity
|
||||
//var act = await Activity.LogActivity("market_remove", req.jwt.username, {});
|
||||
//if (!act) return res.status(500).send({ error: "Failed to log activity!" });
|
||||
|
||||
return res.status(200).send({success: uOffer});
|
||||
};
|
||||
|
||||
exports.trade = async(req, res) => {
|
||||
|
||||
var username = req.jwt.username;
|
||||
var seller_user = req.body.seller;
|
||||
var card_tid = req.body.card;
|
||||
var variant = req.body.variant;
|
||||
var quantity = req.body.quantity;
|
||||
|
||||
//Validate params
|
||||
if (!username || !seller_user || !card_tid || !variant || !quantity)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(typeof seller_user !== "string" || typeof card_tid !== "string" || typeof variant !== "string" || typeof quantity !== "number")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!Number.isInteger(quantity) || quantity <= 0)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get user
|
||||
var user = await UserModel.getByUsername(username);
|
||||
var seller = await UserModel.getByUsername(seller_user);
|
||||
if (!user || !seller)
|
||||
return res.status(404).send({ error: "Can't find user " + username + " or " + seller_user });
|
||||
|
||||
if(user.id == seller.id)
|
||||
return res.status(403).send({ error: "Can't trade with yourself!" });
|
||||
|
||||
//Get offer
|
||||
var offer = await MarketModel.getOffer(seller_user, card_tid, variant)
|
||||
if (!offer)
|
||||
return res.status(404).send({ error: "No market offer for " + seller_user + " " + card_tid });
|
||||
|
||||
var value = quantity * offer.price;
|
||||
if(user.coins < value)
|
||||
return res.status(403).send({ error: "Not enough coins to trade!" });
|
||||
if(quantity > offer.quantity)
|
||||
return res.status(403).send({ error: "Not enough cards to trade!" });
|
||||
|
||||
//Add cards and coins
|
||||
var addCards = [{tid: card_tid, variant: variant, quantity: quantity}];
|
||||
var addSucc = await UserTool.addCards(user, addCards);
|
||||
if(!addSucc)
|
||||
return res.status(500).send({ error: "Error adding cards to user " + username });
|
||||
|
||||
user.coins -= value;
|
||||
seller.coins += value;
|
||||
|
||||
//Update database
|
||||
var uUser = await UserModel.update(user, { coins: user.coins, cards: user.cards });
|
||||
var uSeller = await UserModel.update(seller, { coins: seller.coins });
|
||||
var uOffer = await MarketModel.reduce(seller_user, card_tid, variant, quantity);
|
||||
if(!uUser || !uOffer || !uSeller)
|
||||
return res.status(500).send({ error: "Error trading market offer " + username + " " + seller_user });
|
||||
|
||||
//Activity
|
||||
var aData = {buyer: username, seller: seller_user, card: card_tid, quantity: quantity, price: offer.price };
|
||||
var act = await Activity.LogActivity("market_trade", req.jwt.username, aData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!" });
|
||||
|
||||
return res.status(200).send(aData);
|
||||
};
|
||||
|
||||
exports.getBySeller = async(req, res) => {
|
||||
|
||||
if(!req.params.username)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
var list = await MarketModel.getBySeller(req.params.username);
|
||||
for(var i=0; i<list.length; i++){
|
||||
list[i] = list[i].toObj();
|
||||
}
|
||||
return res.status(200).send(list);
|
||||
};
|
||||
|
||||
exports.getByCard = async(req, res) => {
|
||||
|
||||
var tid = req.params.tid;
|
||||
var variant = req.params.variant;
|
||||
|
||||
if(!tid || !variant)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
var list = await MarketModel.getByCard(tid, variant);
|
||||
for(var i=0; i<list.length; i++){
|
||||
list[i] = list[i].toObj();
|
||||
}
|
||||
return res.status(200).send(list);
|
||||
};
|
||||
|
||||
exports.getOffer = async(req, res) => {
|
||||
|
||||
var tid = req.params.tid;
|
||||
var variant = req.params.variant;
|
||||
var username = req.params.username;
|
||||
|
||||
if(!tid || !variant || !username)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
var offer = await MarketModel.getOffer(username, tid, variant);
|
||||
if(!offer)
|
||||
return res.status(404).send({ error: "Offer not found" });
|
||||
|
||||
return res.status(200).send(offer.toObj());
|
||||
};
|
||||
|
||||
exports.getAll = async(req, res) => {
|
||||
var list = await MarketModel.getAll();
|
||||
for(var i=0; i<list.length; i++){
|
||||
list[i] = list[i].toObj();
|
||||
}
|
||||
return res.status(200).send(list);
|
||||
};
|
||||
146
market/market.model.js
Normal file
146
market/market.model.js
Normal file
@@ -0,0 +1,146 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const marketSchema = new Schema({
|
||||
|
||||
seller: {type: String, index: true},
|
||||
card: {type: String, index: true},
|
||||
variant: {type: String},
|
||||
quantity: {type: Number},
|
||||
price: {type: Number},
|
||||
time: {type: Date},
|
||||
|
||||
});
|
||||
|
||||
marketSchema.methods.toObj = function() {
|
||||
var offer = this.toObject();
|
||||
delete offer.__v;
|
||||
delete offer._id;
|
||||
return offer;
|
||||
};
|
||||
|
||||
const Market = mongoose.model('Markets', marketSchema);
|
||||
|
||||
// Market DATA MODELS ------------------------------------------------
|
||||
|
||||
exports.getOffer = async(user, card_tid, variant_id) => {
|
||||
|
||||
try{
|
||||
var regex = new RegExp(["^", user, "$"].join(""), "i");
|
||||
var offer = await Market.findOne({seller: regex, card: card_tid, variant: variant_id});
|
||||
return offer;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getBySeller = async(user) => {
|
||||
|
||||
try{
|
||||
var regex = new RegExp(["^", user, "$"].join(""), "i");
|
||||
var offers = await Market.find({seller: regex});
|
||||
offers = offers || [];
|
||||
return offers;
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.getByCard = async(card_tid, variant_id) => {
|
||||
|
||||
try{
|
||||
var offers = await Market.find({card: card_tid, variant: variant_id});
|
||||
offers = offers || [];
|
||||
return offers;
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAll = async() => {
|
||||
|
||||
try{
|
||||
var offers = await Market.find()
|
||||
offers = offers || [];
|
||||
return offers;
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAllLimit = async(perPage, page) => {
|
||||
|
||||
try{
|
||||
var offers = await Market.find().limit(perPage).skip(perPage * page)
|
||||
offers = offers || [];
|
||||
return offers;
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.add = async(user, card, variant, data) => {
|
||||
|
||||
try{
|
||||
var offer = await Market.findOne({seller: user, card: card, variant: variant});
|
||||
|
||||
if(!offer)
|
||||
{
|
||||
offer = new Market(data);
|
||||
offer.date = Date.now();
|
||||
return await offer.save();
|
||||
}
|
||||
else
|
||||
{
|
||||
offer.quantity += data.quantity;
|
||||
offer.price = data.price;
|
||||
offer.date = Date.now();
|
||||
|
||||
var updated = await offer.save();
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.reduce = async(user, card, variant, quantity) => {
|
||||
|
||||
try{
|
||||
var offer = await Market.findOne({seller: user, card: card, variant: variant});
|
||||
if(offer)
|
||||
{
|
||||
offer.quantity -= quantity;
|
||||
if(offer.quantity > 0)
|
||||
{
|
||||
var updated = await offer.save();
|
||||
return updated;
|
||||
}
|
||||
else{
|
||||
var result = await Market.deleteOne({seller: user, card: card});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = async(user, card, variant) => {
|
||||
|
||||
try{
|
||||
var result = await Market.deleteOne({seller: user, card: card, variant: variant});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
51
market/market.routes.js
Normal file
51
market/market.routes.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const MarketController = require('./market.controller');
|
||||
const AuthTool = require('../authorization/auth.tool');
|
||||
const config = require('../config');
|
||||
|
||||
const ADMIN = config.permissions.ADMIN; //Highest permision, can read and write all users
|
||||
const SERVER = config.permissions.SERVER; //Middle permission, can read all users and grant rewards
|
||||
const USER = config.permissions.USER; //Lowest permision, can only do things on same user
|
||||
|
||||
exports.route = function (app) {
|
||||
|
||||
app.post("/market/cards/add", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
MarketController.addOffer,
|
||||
]);
|
||||
app.post("/market/cards/remove", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
MarketController.removeOffer,
|
||||
]);
|
||||
app.post("/market/cards/trade", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
MarketController.trade,
|
||||
]);
|
||||
|
||||
app.get("/market/cards/", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
MarketController.getAll,
|
||||
]);
|
||||
|
||||
app.get("/market/cards/user/:username", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
MarketController.getBySeller,
|
||||
]);
|
||||
|
||||
app.get("/market/cards/card/:tid", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
MarketController.getByCard,
|
||||
]);
|
||||
|
||||
app.get("/market/cards/offer/:username/:tid", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
MarketController.getOffer,
|
||||
]);
|
||||
|
||||
};
|
||||
123
matches/matches.controller.js
Normal file
123
matches/matches.controller.js
Normal file
@@ -0,0 +1,123 @@
|
||||
const MatchModel = require('./matches.model');
|
||||
const MatchTool = require('./matches.tool');
|
||||
const UserModel = require('../users/users.model');
|
||||
const DateTool = require('../tools/date.tool');
|
||||
const config = require('../config');
|
||||
|
||||
exports.addMatch = async(req, res) => {
|
||||
|
||||
var tid = req.body.tid;
|
||||
var players = req.body.players;
|
||||
var ranked = req.body.ranked ? true : false;
|
||||
var mode = req.body.mode || "";
|
||||
|
||||
if (!tid || !players || !Array.isArray(players) || players.length != 2)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if (mode && typeof mode !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
var fmatch = await MatchModel.get(tid);
|
||||
if(fmatch)
|
||||
return res.status(400).send({error:"Match already exists: " + tid});
|
||||
|
||||
var player0 = await UserModel.getByUsername(players[0]);
|
||||
var player1 = await UserModel.getByUsername(players[1]);
|
||||
if(!player0 || !player1)
|
||||
return res.status(404).send({error:"Can't find players"});
|
||||
|
||||
if(player0.id == player1.id)
|
||||
return res.status(400).send({error:"Can't play against yourself"});
|
||||
|
||||
var match = {};
|
||||
match.tid = tid;
|
||||
match.players = players;
|
||||
match.winner = "";
|
||||
match.completed = false;
|
||||
match.ranked = ranked;
|
||||
match.mode = mode;
|
||||
match.start = Date.now();
|
||||
match.end = Date.now();
|
||||
match.udata = [];
|
||||
match.udata.push(MatchTool.GetPlayerData(player0));
|
||||
match.udata.push(MatchTool.GetPlayerData(player1));
|
||||
|
||||
var match = await MatchModel.create(match);
|
||||
if(!match)
|
||||
return res.status(500).send({error:"Unable to create match"});
|
||||
|
||||
res.status(200).send(match);
|
||||
|
||||
};
|
||||
|
||||
exports.completeMatch = async(req, res) => {
|
||||
|
||||
var matchId = req.body.tid;
|
||||
var winner = req.body.winner;
|
||||
|
||||
if (!matchId || !winner)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(typeof matchId != "string" || typeof winner != "string")
|
||||
return res.status(400).send({error: "Invalid parameters" });
|
||||
|
||||
var match = await MatchModel.get(matchId);
|
||||
if(!match)
|
||||
return res.status(404).send({error: "Match not found"});
|
||||
|
||||
if(match.completed)
|
||||
return res.status(400).send({error: "Match already completed"});
|
||||
|
||||
var player0 = await UserModel.getByUsername(match.players[0]);
|
||||
var player1 = await UserModel.getByUsername(match.players[1]);
|
||||
if(!player0 || !player1)
|
||||
return res.status(404).send({error:"Can't find players"});
|
||||
|
||||
match.end = Date.now();
|
||||
match.winner = winner;
|
||||
match.completed = true;
|
||||
|
||||
//Add Rewards
|
||||
if(match.ranked)
|
||||
{
|
||||
match.udata[0].reward = await MatchTool.GainMatchReward(player0, player1, winner);
|
||||
match.udata[1].reward = await MatchTool.GainMatchReward(player1, player0, winner);
|
||||
match.markModified('udata');
|
||||
}
|
||||
|
||||
//Save match
|
||||
var uMatch = await match.save();
|
||||
|
||||
//Return
|
||||
res.status(200).send(uMatch);
|
||||
};
|
||||
|
||||
exports.getAll = async(req, res) => {
|
||||
|
||||
var start = req.query.start ? DateTool.tagToDate(req.query.start) : null;
|
||||
var end = req.query.end ? DateTool.tagToDate(req.query.end) : null;
|
||||
|
||||
var matches = await MatchModel.list(start, end);
|
||||
if(!matches)
|
||||
return res.status(400).send({error: "Invalid Parameters"});
|
||||
|
||||
return res.status(200).send(matches);
|
||||
};
|
||||
|
||||
exports.getByTid = async(req, res) => {
|
||||
|
||||
var match = await MatchModel.get(req.params.tid);
|
||||
if(!match)
|
||||
return res.status(404).send({error: "Match not found " + req.params.tid});
|
||||
|
||||
return res.status(200).send(match);
|
||||
};
|
||||
|
||||
exports.getLatest = async(req, res) => {
|
||||
|
||||
var match = await MatchModel.getLast(req.params.userId);
|
||||
if(!match)
|
||||
return res.status(404).send({error: "Match not found for user " + req.params.userId});
|
||||
|
||||
return res.status(200).send(match);
|
||||
};
|
||||
87
matches/matches.model.js
Normal file
87
matches/matches.model.js
Normal file
@@ -0,0 +1,87 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const matchSchema = new Schema({
|
||||
|
||||
tid: { type: String, index: true, default: "" },
|
||||
players: [{type: String, default: []}],
|
||||
winner: {type: String, default: ""},
|
||||
completed: {type: Boolean, default: false},
|
||||
ranked: {type: Boolean, default: false},
|
||||
mode: { type: String, default: "" },
|
||||
|
||||
start: {type: Date, default: null},
|
||||
end: {type: Date, default: null},
|
||||
|
||||
udata: [{ type: Object, _id: false }],
|
||||
});
|
||||
|
||||
matchSchema.methods.toObj = function() {
|
||||
var match = this.toObject();
|
||||
delete match.__v;
|
||||
delete match._id;
|
||||
return match;
|
||||
};
|
||||
|
||||
const Match = mongoose.model('Matches', matchSchema);
|
||||
|
||||
exports.get = async(matchId) => {
|
||||
try{
|
||||
var match = await Match.findOne({tid: matchId});
|
||||
return match;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAll = async() => {
|
||||
|
||||
try{
|
||||
var matches = await Match.find()
|
||||
return matches || [];
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.create = async(matchData) => {
|
||||
const match = new Match(matchData);
|
||||
return await match.save();
|
||||
};
|
||||
|
||||
exports.list = async(startTime, endTime, winnerId, completed) => {
|
||||
|
||||
startTime = startTime || new Date(-8640000000000000);
|
||||
endTime = endTime || new Date(8640000000000000);
|
||||
|
||||
var options = {};
|
||||
|
||||
if(startTime && endTime)
|
||||
options.end = { $gte: startTime, $lte: endTime };
|
||||
|
||||
if(winnerId)
|
||||
options.players = winnerId;
|
||||
|
||||
if(completed)
|
||||
options.completed = true;
|
||||
|
||||
try{
|
||||
var matches = await Match.find(options)
|
||||
return matches || [];
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = async(matchId) => {
|
||||
try{
|
||||
var result = await Match.deleteOne({tid: matchId});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
34
matches/matches.routes.js
Normal file
34
matches/matches.routes.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const MatchesController = require('./matches.controller');
|
||||
const MatchesTool = require('./matches.tool');
|
||||
const AuthTool = require('../authorization/auth.tool');
|
||||
const config = require('../config');
|
||||
|
||||
const ADMIN = config.permissions.ADMIN; //Highest permision, can read and write all users
|
||||
const SERVER = config.permissions.SERVER; //Higher permission, can read all users
|
||||
const USER = config.permissions.USER; //Lowest permision, can only do things on same user
|
||||
|
||||
exports.route = function (app) {
|
||||
|
||||
app.post('/matches/add', app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(SERVER),
|
||||
MatchesController.addMatch
|
||||
]);
|
||||
app.post('/matches/complete', app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(SERVER),
|
||||
MatchesController.completeMatch
|
||||
]);
|
||||
|
||||
//-- Getter
|
||||
app.get('/matches', [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(SERVER),
|
||||
MatchesController.getAll
|
||||
]);
|
||||
app.get('/matches/:tid', [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
MatchesController.getByTid
|
||||
]);
|
||||
};
|
||||
71
matches/matches.tool.js
Normal file
71
matches/matches.tool.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const UserTool = require('../users/users.tool');
|
||||
const config = require('../config.js');
|
||||
|
||||
|
||||
var MatchTool = {};
|
||||
|
||||
|
||||
MatchTool.calculateELO = (player_elo, opponent_elo, progress, won, lost) =>
|
||||
{
|
||||
var p_elo = player_elo || 1000;
|
||||
var o_elo = opponent_elo || 1000;
|
||||
|
||||
var p_elo_log = Math.pow(10.0, p_elo / 400.0);
|
||||
var o_elo_log = Math.pow(10.0, o_elo / 400.0);
|
||||
var p_expected = p_elo_log / (p_elo_log + o_elo_log);
|
||||
var p_score = won ? 1.0 : (lost ? 0.0 : 0.5);
|
||||
|
||||
progress = Math.min(Math.max(progress, 0.0), 1.0);
|
||||
var elo_k = progress * config.elo_k + (1.0 - progress) * config.elo_ini_k;
|
||||
var new_elo = Math.round(p_elo + elo_k * (p_score - p_expected));
|
||||
return new_elo;
|
||||
}
|
||||
|
||||
MatchTool.GetPlayerData = (player) =>
|
||||
{
|
||||
var data = {};
|
||||
data.username = player.username;
|
||||
data.elo = player.elo;
|
||||
data.reward = {};
|
||||
return data;
|
||||
}
|
||||
|
||||
MatchTool.GainMatchReward = async(player, opponent, winner_username) => {
|
||||
|
||||
var player_elo = player.elo;
|
||||
var opponent_elo = opponent.elo;
|
||||
var won = winner_username == player.username;
|
||||
var lost = winner_username == opponent.username;
|
||||
|
||||
//Rewards
|
||||
var xp = won ? config.xp_victory : config.xp_defeat;
|
||||
var coins = won ? config.coins_victory : config.coins_defeat;
|
||||
|
||||
player.xp += xp;
|
||||
player.coins += coins;
|
||||
|
||||
//Match winrate
|
||||
player.matches +=1;
|
||||
|
||||
if(won)
|
||||
player.victories += 1;
|
||||
else if (lost)
|
||||
player.defeats += 1;
|
||||
|
||||
//Calculate elo
|
||||
var match_count = player.matches || 0;
|
||||
var match_progress = Math.min(Math.max(match_count / config.elo_ini_match, 0.0), 1.0);
|
||||
var new_elo = MatchTool.calculateELO(player_elo, opponent_elo, match_progress, won, lost);
|
||||
player.elo = new_elo;
|
||||
player.save();
|
||||
|
||||
var reward = {
|
||||
elo: player.elo,
|
||||
xp: xp,
|
||||
coins: coins
|
||||
};
|
||||
|
||||
return reward;
|
||||
};
|
||||
|
||||
module.exports = MatchTool;
|
||||
16
package.json
Normal file
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "tcg-engine-api",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"author": "IndieMarc",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"express-slow-down": "^1.5.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"mongoose": "^6.8.4",
|
||||
"node-schedule": "^2.1.1",
|
||||
"nodemailer": "^6.9.0"
|
||||
}
|
||||
}
|
||||
80
packs/packs.controller.js
Normal file
80
packs/packs.controller.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const PackModel = require("./packs.model");
|
||||
|
||||
exports.AddPack = async(req, res) =>
|
||||
{
|
||||
var tid = req.body.tid;
|
||||
var cards = req.body.cards || 1;
|
||||
var cost = req.body.cost || 1;
|
||||
var random = req.body.random || false;
|
||||
var rarities_1st = req.body.rarities_1st || [];
|
||||
var rarities = req.body.rarities || [];
|
||||
var variants = req.body.variants || [];
|
||||
|
||||
if(!tid || typeof tid !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!Number.isInteger(cards) || !Number.isInteger(cost))
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!Array.isArray(rarities_1st) || !Array.isArray(rarities) || !Array.isArray(variants))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var data = {
|
||||
tid: tid,
|
||||
cards: cards,
|
||||
cost: cost,
|
||||
random: random,
|
||||
rarities_1st: rarities_1st,
|
||||
rarities: rarities,
|
||||
variants: variants,
|
||||
}
|
||||
|
||||
//Update or create
|
||||
var pack = await PackModel.get(tid);
|
||||
if(pack)
|
||||
pack = await PackModel.update(pack, data);
|
||||
else
|
||||
pack = await PackModel.create(data);
|
||||
|
||||
if(!pack)
|
||||
return res.status(500).send({error: "Error updating pack"});
|
||||
|
||||
return res.status(200).send(data);
|
||||
};
|
||||
|
||||
exports.DeletePack = async(req, res) =>
|
||||
{
|
||||
PackModel.remove(req.params.tid);
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.DeleteAll = async(req, res) =>
|
||||
{
|
||||
PackModel.removeAll();
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.GetPack = async(req, res) =>
|
||||
{
|
||||
var tid = req.params.tid;
|
||||
|
||||
if(!tid)
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var pack = await PackModel.get(tid);
|
||||
if(!pack)
|
||||
return res.status(404).send({error: "Pack not found: " + tid});
|
||||
|
||||
return res.status(200).send(pack.toObj());
|
||||
};
|
||||
|
||||
exports.GetAll = async(req, res) =>
|
||||
{
|
||||
var packs = await PackModel.getAll();
|
||||
|
||||
for(var i=0; i<packs.length; i++){
|
||||
packs[i] = packs[i].toObj();
|
||||
}
|
||||
|
||||
return res.status(200).send(packs);
|
||||
};
|
||||
96
packs/packs.model.js
Normal file
96
packs/packs.model.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const packsSchema = new Schema({
|
||||
|
||||
tid: { type: String, index: true, unique: true },
|
||||
cards: {type: Number, default: 1}, //Number of cards per pack
|
||||
cost: {type: Number, default: 0}, //Cost in coins
|
||||
random: {type: Boolean, default: true},
|
||||
rarities: [{type: Object}], //Probabilities to get each rarities
|
||||
rarities_1st: [{type: Object}], //Probabilities but for the first card only
|
||||
variants: [{type: Object}], //Probabilities of variants
|
||||
});
|
||||
|
||||
packsSchema.methods.toObj = function() {
|
||||
var elem = this.toObject();
|
||||
delete elem.__v;
|
||||
delete elem._id;
|
||||
return elem;
|
||||
};
|
||||
|
||||
const Pack = mongoose.model("Packs", packsSchema);
|
||||
exports.Pack = Pack;
|
||||
|
||||
exports.create = async(data) => {
|
||||
|
||||
try{
|
||||
var pack = new Pack(data);
|
||||
return await pack.save();
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.get = async(set_tid) => {
|
||||
|
||||
try{
|
||||
var pack = await Pack.findOne({tid: set_tid});
|
||||
return pack;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAll = async() => {
|
||||
|
||||
try{
|
||||
var packs = await Pack.find({});
|
||||
return packs;
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.update = async(pack, data) => {
|
||||
|
||||
try{
|
||||
if(!pack) return null;
|
||||
|
||||
for (let i in data) {
|
||||
pack[i] = data[i];
|
||||
pack.markModified(i);
|
||||
}
|
||||
|
||||
var updated = await pack.save();
|
||||
return updated;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = async(pack_tid) => {
|
||||
|
||||
try{
|
||||
var result = await Pack.deleteOne({tid: pack_tid});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.removeAll = async() => {
|
||||
try{
|
||||
var result = await Pack.deleteMany({});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
36
packs/packs.routes.js
Normal file
36
packs/packs.routes.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const config = require("../config");
|
||||
const PacksController = require("./packs.controller");
|
||||
const AuthTool = require("../authorization/auth.tool");
|
||||
|
||||
const ADMIN = config.permissions.ADMIN; //Highest permision, can read and write all users
|
||||
const SERVER = config.permissions.SERVER; //Higher permission, can read all users
|
||||
const USER = config.permissions.USER; //Lowest permision, can only do things on same user
|
||||
|
||||
exports.route = (app) => {
|
||||
|
||||
app.get("/packs", [
|
||||
PacksController.GetAll
|
||||
]);
|
||||
|
||||
app.get("/packs/:tid", [
|
||||
PacksController.GetPack
|
||||
]);
|
||||
|
||||
app.post("/packs/add", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
PacksController.AddPack
|
||||
]);
|
||||
|
||||
app.delete("/packs/:tid", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
PacksController.DeletePack
|
||||
]);
|
||||
|
||||
app.delete("/packs", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
PacksController.DeleteAll
|
||||
]);
|
||||
};
|
||||
99
rewards/rewards.controller.js
Normal file
99
rewards/rewards.controller.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const RewardModel = require('../rewards/rewards.model');
|
||||
const Activity = require("../activity/activity.model");
|
||||
const config = require('../config');
|
||||
|
||||
exports.AddReward = async(req, res) =>
|
||||
{
|
||||
var rewardId = req.body.tid;
|
||||
var group = req.body.group;
|
||||
var repeat = req.body.repeat;
|
||||
var xp = req.body.xp;
|
||||
var coins = req.body.coins;
|
||||
var cards = req.body.cards;
|
||||
var packs = req.body.packs;
|
||||
var decks = req.body.decks;
|
||||
var avatars = req.body.avatars;
|
||||
var cardbacks = req.body.cardbacks;
|
||||
|
||||
if(!rewardId || typeof rewardId !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(group && typeof group !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(xp && !Number.isInteger(xp))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
if(coins && !Number.isInteger(coins))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
if(cards && !Array.isArray(cards))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
if(packs && !Array.isArray(packs))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
if(decks && !Array.isArray(decks))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
if(avatars && !Array.isArray(avatars))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
if(cardbacks && !Array.isArray(cardbacks))
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var reward_data = {
|
||||
tid: rewardId,
|
||||
group: group || "",
|
||||
repeat: repeat || false,
|
||||
xp: xp || 0,
|
||||
coins: coins || 0,
|
||||
cards: cards || [],
|
||||
packs: packs || [],
|
||||
decks: decks || [],
|
||||
avatars: avatars || [],
|
||||
cardbacks: cardbacks || [],
|
||||
}
|
||||
|
||||
//Update or create
|
||||
var reward = await RewardModel.get(rewardId);
|
||||
if(reward)
|
||||
reward = await RewardModel.update(reward, reward_data);
|
||||
else
|
||||
reward = await RewardModel.create(reward_data);
|
||||
|
||||
if(!reward)
|
||||
res.status(500).send({error: "Error updating reward"});
|
||||
|
||||
//Activity
|
||||
const act = await Activity.LogActivity("reward_add", req.jwt.username, reward);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!" });
|
||||
|
||||
return res.status(200).send(reward);
|
||||
};
|
||||
|
||||
exports.DeleteReward = async(req, res) => {
|
||||
RewardModel.remove(req.params.tid);
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.DeleteAll = async(req, res) => {
|
||||
RewardModel.removeAll();
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.GetReward = async(req, res) =>
|
||||
{
|
||||
var rewardTid = req.params.tid;
|
||||
|
||||
if(!rewardTid)
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var reward = await RewardModel.get(rewardTid);
|
||||
if(!reward)
|
||||
return res.status(404).send({error: "Reward not found: " + rewardTid});
|
||||
|
||||
return res.status(200).send(reward.toObj());
|
||||
};
|
||||
|
||||
exports.GetAll = async(req, res) =>
|
||||
{
|
||||
var rewards = await RewardModel.getAll();
|
||||
return res.status(200).send(rewards);
|
||||
};
|
||||
|
||||
|
||||
107
rewards/rewards.model.js
Normal file
107
rewards/rewards.model.js
Normal file
@@ -0,0 +1,107 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const rewardSchema = new Schema({
|
||||
|
||||
tid: { type: String, index: true, unique: true, default: "" },
|
||||
group: { type: String, index: true, default: "" },
|
||||
repeat: { type : Boolean, default: false }, //If true, can be gained multiple times but only server/admin can grant it
|
||||
|
||||
xp: { type: Number, default: 0 },
|
||||
coins: { type: Number, default: 0 },
|
||||
cards: [{type: String}],
|
||||
packs: [{type: String}],
|
||||
decks: [{type: String}],
|
||||
avatars: [{type: String}],
|
||||
cardbacks: [{type: String}],
|
||||
|
||||
});
|
||||
|
||||
rewardSchema.methods.toObj = function() {
|
||||
var reward = this.toObject();
|
||||
delete reward.__v;
|
||||
delete reward._id;
|
||||
return reward;
|
||||
};
|
||||
|
||||
const Reward = mongoose.model('Rewards', rewardSchema);
|
||||
|
||||
exports.get = async(rewardId) => {
|
||||
try{
|
||||
var reward = await Reward.findOne({tid: rewardId});
|
||||
return reward;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getGroup = async(group) => {
|
||||
|
||||
try{
|
||||
var rewards = await Reward.find({group: group})
|
||||
return rewards || [];
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAll = async() => {
|
||||
|
||||
try{
|
||||
var rewards = await Reward.find()
|
||||
return rewards || [];
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.create = async(data) => {
|
||||
try{
|
||||
var reward = new Reward(data);
|
||||
return await reward.save();
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.update = async(reward, data) => {
|
||||
|
||||
try{
|
||||
if(!reward) return null;
|
||||
|
||||
for (let i in data) {
|
||||
reward[i] = data[i];
|
||||
reward.markModified(i);
|
||||
}
|
||||
|
||||
var updated = await reward.save();
|
||||
return updated;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = async(rewardId) => {
|
||||
try{
|
||||
var result = await Reward.deleteOne({tid: rewardId});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.removeAll = async() => {
|
||||
try{
|
||||
var result = await Reward.deleteMany({});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
40
rewards/rewards.routes.js
Normal file
40
rewards/rewards.routes.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const RewardController = require('./rewards.controller');
|
||||
const AuthTool = require('../authorization/auth.tool');
|
||||
const config = require('../config');
|
||||
|
||||
const ADMIN = config.permissions.ADMIN; //Highest permision, can read and write all users
|
||||
const SERVER = config.permissions.SERVER; //Higher permission, can read all users
|
||||
const USER = config.permissions.USER; //Lowest permision, can only do things on same user
|
||||
|
||||
exports.route = function (app) {
|
||||
|
||||
app.get('/rewards/:tid', [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
RewardController.GetReward
|
||||
]);
|
||||
|
||||
app.get('/rewards', [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(SERVER),
|
||||
RewardController.GetAll
|
||||
]);
|
||||
|
||||
app.post('/rewards/add', [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
RewardController.AddReward
|
||||
]);
|
||||
|
||||
app.delete("/rewards/:tid", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
RewardController.DeleteReward
|
||||
]);
|
||||
|
||||
app.delete("/rewards", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
RewardController.DeleteAll
|
||||
]);
|
||||
};
|
||||
134
server.js
Normal file
134
server.js
Normal file
@@ -0,0 +1,134 @@
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const express = require('express');
|
||||
const config = require('./config.js');
|
||||
const Limiter = require('./tools/limiter.tool');
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const app = express();
|
||||
|
||||
// CONNECTION TO DATABASE
|
||||
var user = ""; //User is optional if auth not enabled
|
||||
if(config.mongo_user && config.mongo_pass)
|
||||
user = config.mongo_user + ":" + config.mongo_pass + "@";
|
||||
|
||||
var connect = "mongodb://" + user + config.mongo_host + ":" + config.mongo_port + "/" + config.mongo_db + "?authSource=admin";
|
||||
mongoose.set('strictQuery', false);
|
||||
mongoose.connection.on("connected", () => {
|
||||
console.log("Connected to MongoDB!");
|
||||
});
|
||||
mongoose.connection.on('error', function(err) {
|
||||
console.error('Connection to MongoDB failed!');
|
||||
});
|
||||
mongoose.connect(connect);
|
||||
|
||||
//Limiter to prevent attacks
|
||||
Limiter.limit(app);
|
||||
|
||||
//Headers
|
||||
app.use(function (req, res, next) {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Credentials', 'true');
|
||||
res.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE');
|
||||
res.header('Access-Control-Expose-Headers', 'Content-Length');
|
||||
res.header('Access-Control-Allow-Headers', 'Accept, Authorization, Content-Type, X-Requested-With, Range');
|
||||
if (req.method === 'OPTIONS') {
|
||||
return res.send(200);
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
});
|
||||
|
||||
//Parse JSON body
|
||||
app.use(express.json({ limit: "100kb" }));
|
||||
|
||||
//Log request
|
||||
app.use((req, res, next) => {
|
||||
var today = new Date();
|
||||
var date = today.getFullYear() +'-'+(today.getMonth()+1)+'-'+today.getDate();
|
||||
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
|
||||
var date_tag = "[" + date + " " + time + "]";
|
||||
console.log(date_tag + " " + req.method + " " + req.originalUrl);
|
||||
next();
|
||||
})
|
||||
|
||||
//Route root DIR
|
||||
app.get('/', function(req, res){
|
||||
res.status(200).send(config.api_title + " " + config.version);
|
||||
});
|
||||
|
||||
//Public folder
|
||||
app.use('/', express.static('public'))
|
||||
|
||||
//Routing
|
||||
const AuthorizationRouter = require('./authorization/auth.routes');
|
||||
AuthorizationRouter.route(app);
|
||||
|
||||
const UsersRouter = require('./users/users.routes');
|
||||
UsersRouter.route(app);
|
||||
|
||||
const CardsRouter = require('./cards/cards.routes');
|
||||
CardsRouter.route(app);
|
||||
|
||||
const PacksRouter = require('./packs/packs.routes');
|
||||
PacksRouter.route(app);
|
||||
|
||||
const DecksRouter = require('./decks/decks.routes');
|
||||
DecksRouter.route(app);
|
||||
|
||||
const VariantRouter = require('./variants/variants.routes');
|
||||
VariantRouter.route(app);
|
||||
|
||||
const MatchesRouter = require('./matches/matches.routes');
|
||||
MatchesRouter.route(app);
|
||||
|
||||
const RewardsRouter = require('./rewards/rewards.routes');
|
||||
RewardsRouter.route(app);
|
||||
|
||||
const MarketRouter = require('./market/market.routes');
|
||||
MarketRouter.route(app);
|
||||
|
||||
const ActivityRouter = require("./activity/activity.routes");
|
||||
ActivityRouter.route(app);
|
||||
|
||||
//Read SSL cert
|
||||
var ReadSSL = function()
|
||||
{
|
||||
var privateKey = fs.readFileSync(config.https_key, 'utf8');
|
||||
var certificate = fs.readFileSync(config.https_cert, 'utf8');
|
||||
var cert_authority = fs.readFileSync(config.https_ca, 'utf8');
|
||||
var credentials = {key: privateKey, cert: certificate, ca: cert_authority};
|
||||
return credentials;
|
||||
};
|
||||
|
||||
//HTTP
|
||||
if(config.allow_http){
|
||||
var httpServer = http.createServer(app);
|
||||
httpServer.listen(config.port, function () {
|
||||
console.log('http listening port %s', config.port);
|
||||
});
|
||||
}
|
||||
|
||||
//HTTPS
|
||||
if(config.allow_https && fs.existsSync(config.https_key)) {
|
||||
var httpsServer = https.createServer(ReadSSL(), app);
|
||||
httpsServer.listen(config.port_https, function () {
|
||||
console.log('https listening port %s', config.port_https);
|
||||
});
|
||||
|
||||
//HTTPS auto-reload ssl
|
||||
var timeout;
|
||||
fs.watch(config.https_cert, () => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
httpsServer.setSecureContext(ReadSSL());
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
//Start jobs
|
||||
const Jobs = require("./jobs/jobs");
|
||||
Jobs.InitJobs();
|
||||
|
||||
module.exports = app
|
||||
65
tools/date.tool.js
Normal file
65
tools/date.tool.js
Normal file
@@ -0,0 +1,65 @@
|
||||
var DateTool = {};
|
||||
|
||||
// -------- Date & timestamp -------
|
||||
DateTool.isDate = function(date)
|
||||
{
|
||||
if (Object.prototype.toString.call(date) === "[object Date]") {
|
||||
return !isNaN(date.getTime());
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
DateTool.tagToDate = function(tag)
|
||||
{
|
||||
if(typeof tag !== "string")
|
||||
return null;
|
||||
|
||||
[year, month, day] = tag.split("-");
|
||||
var d = new Date(year, month - 1, day, 0, 0, 0, 0);
|
||||
return DateTool.isDate(d) ? d : null;
|
||||
};
|
||||
|
||||
DateTool.dateToTag = function(d)
|
||||
{
|
||||
if(!DateTool.isDate(d))
|
||||
return "";
|
||||
|
||||
var year = '' + d.getFullYear();
|
||||
var month = '' + (d.getMonth() + 1);
|
||||
var day = '' + d.getDate();
|
||||
if (day.length < 2) day = '0' + day;
|
||||
return [year, month, day].join('-');
|
||||
};
|
||||
|
||||
DateTool.getStartOfDay = function(date){
|
||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
}
|
||||
|
||||
DateTool.addDays = function(date, days) {
|
||||
return new Date(date.getTime() + days*24*60*60*1000);
|
||||
}
|
||||
|
||||
DateTool.addHours = function(date, hours) {
|
||||
return new Date(date.getTime() + hours*60*60*1000);
|
||||
}
|
||||
|
||||
DateTool.addMinutes = function(date, minutes) {
|
||||
return new Date(date.getTime() + minutes*60000);
|
||||
}
|
||||
|
||||
DateTool.minDate = function()
|
||||
{
|
||||
return new Date(-8640000000000000);
|
||||
}
|
||||
|
||||
DateTool.maxDate = function()
|
||||
{
|
||||
return new Date(8640000000000000);
|
||||
}
|
||||
|
||||
DateTool.countDays = function(from, to) {
|
||||
const ms_per_day = 1000 * 60 * 60 * 24;
|
||||
return Math.round((to - from) / ms_per_day);
|
||||
}
|
||||
|
||||
module.exports = DateTool;
|
||||
121
tools/email.tool.js
Normal file
121
tools/email.tool.js
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
const nodeMailer = require('nodemailer');
|
||||
const config = require('../config');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
//Send One Mail
|
||||
exports.SendEmail = function(email_to, subject, text, callback){
|
||||
|
||||
if(!config.smtp_enabled)
|
||||
return;
|
||||
|
||||
console.log("Sending email to: " + email_to);
|
||||
|
||||
let transporter = nodeMailer.createTransport({
|
||||
host: config.smtp_server,
|
||||
port: config.smtp_port, //Port must be 465 (encrypted) or 587 (STARTTSL, first pre-request is unencrypted to know the encryption method supported, followed by encrypted request)
|
||||
secure: (config.smtp_port == "465"), //On port 587 secure must be false since it will first send unsecured pre-request to know which encryption to use
|
||||
requireTLS: true, //Force using encryption on port 587 on the second request
|
||||
auth: {
|
||||
user: config.smtp_user,
|
||||
pass: config.smtp_password,
|
||||
}
|
||||
});
|
||||
|
||||
let mailOptions = {
|
||||
from: '"' + config.smtp_name + '" <' + config.smtp_email + '>', // sender address
|
||||
to: email_to, // list of receivers
|
||||
subject: subject, // Subject line
|
||||
//text: text, // plain text body
|
||||
html: text, // html body
|
||||
};
|
||||
|
||||
transporter.sendMail(mailOptions, (error, info) => {
|
||||
if (error) {
|
||||
if(callback)
|
||||
callback(false, error);
|
||||
console.log(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if(callback)
|
||||
callback(true);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
//Send same mail to multiple recipients (emails array)
|
||||
exports.SendEmailList = function(emails, subject, text, callback){
|
||||
|
||||
if(!config.smtp_enabled)
|
||||
return;
|
||||
|
||||
if(!Array.isArray(emails))
|
||||
return;
|
||||
|
||||
if(emails.length == 0)
|
||||
return;
|
||||
|
||||
let transporter = nodeMailer.createTransport({
|
||||
pool: true,
|
||||
host: config.smtp_server,
|
||||
port: config.smtp_port,
|
||||
secure: (config.smtp_port == "465"),
|
||||
requireTLS: true,
|
||||
auth: {
|
||||
user: config.smtp_user,
|
||||
pass: config.smtp_password,
|
||||
}
|
||||
});
|
||||
|
||||
var email_list = emails;
|
||||
var email_from = '"' + config.smtp_name + '" <' + config.smtp_email + '>';
|
||||
var total = emails.length;
|
||||
var sent_success = 0;
|
||||
var sent_count = 0;
|
||||
var ended = false;
|
||||
|
||||
transporter.on("idle", function () {
|
||||
|
||||
while (transporter.isIdle() && email_list.length > 0)
|
||||
{
|
||||
var email_to = email_list.shift();
|
||||
let mailOptions = {
|
||||
from: email_from,
|
||||
to: email_to,
|
||||
subject: subject,
|
||||
html: text,
|
||||
};
|
||||
|
||||
transporter.sendMail(mailOptions, (error, info) => {
|
||||
sent_count++;
|
||||
if (!error) {
|
||||
sent_success++;
|
||||
}
|
||||
|
||||
if(email_list.length == 0 && sent_count == total && !ended)
|
||||
{
|
||||
ended = true;
|
||||
if(callback)
|
||||
callback(sent_success);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.ReadTemplate = function(template)
|
||||
{
|
||||
const rootDir = path.dirname(require.main.filename);
|
||||
const fullpath = rootDir + "/emails/" + template;
|
||||
|
||||
try{
|
||||
const html = fs.readFileSync(fullpath, "utf8");
|
||||
return html;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
16
tools/file.tool.js
Normal file
16
tools/file.tool.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var fs = require('fs');
|
||||
|
||||
exports.readFileArraySync = function(filename){
|
||||
|
||||
var data = fs.readFileSync(filename, {encoding: "utf8"});
|
||||
var adata = data.split('\r\n');
|
||||
return adata;
|
||||
};
|
||||
|
||||
exports.readFileArray = function(filename, callback){
|
||||
|
||||
fs.readFile(filename, {encoding: "utf8"}, function(data){
|
||||
var adata = data.split('\r\n');
|
||||
callback(adata);
|
||||
});
|
||||
};
|
||||
46
tools/limiter.tool.js
Normal file
46
tools/limiter.tool.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const RateLimit = require('express-rate-limit');
|
||||
//const Slowdown = require('express-slow-down');
|
||||
const config = require('../config.js');
|
||||
|
||||
exports.limit = function(app)
|
||||
{
|
||||
//Restrict to access from domain only
|
||||
app.use(function(req, res, next)
|
||||
{
|
||||
//Ip address
|
||||
req.ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
||||
|
||||
if(config.ip_blacklist.includes(req.ip))
|
||||
return res.status(401).send("Forbidden");
|
||||
|
||||
//Check server host
|
||||
var host = req.hostname;
|
||||
if(config.api_url && host != config.api_url)
|
||||
return res.status(401).send("Forbidden");
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
//Rate limiter
|
||||
if(config.limiter_proxy)
|
||||
app.enable('trust proxy'); // only if your server is behind a reverse proxy
|
||||
|
||||
app.use(RateLimit({
|
||||
windowMs: config.limiter_window,
|
||||
max: config.limiter_max,
|
||||
skip: function(req) { return config.ip_whitelist.includes(req.ip); },
|
||||
}));
|
||||
app.auth_limiter = RateLimit({
|
||||
windowMs: config.limiter_window,
|
||||
max: config.limiter_auth_max,
|
||||
skip: function(req) { return config.ip_whitelist.includes(req.ip); },
|
||||
handler: function (req, res) {
|
||||
res.status(429).send({error: "Too many requests!"});
|
||||
},
|
||||
});
|
||||
app.post_limiter = RateLimit({
|
||||
windowMs: config.limiter_window,
|
||||
max: config.limiter_post_max,
|
||||
skip: function(req) { return config.ip_whitelist.includes(req.ip); },
|
||||
});
|
||||
}
|
||||
92
tools/validator.tool.js
Normal file
92
tools/validator.tool.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const FileTool = require('../tools/file.tool');
|
||||
|
||||
var Validator = {};
|
||||
|
||||
Validator.isInteger = function(value){
|
||||
return Number.isInteger(value);
|
||||
}
|
||||
|
||||
Validator.isNumber = function(value){
|
||||
return !isNaN(parseFloat(value)) && isFinite(value);
|
||||
};
|
||||
|
||||
Validator.validateUsername = function(username){
|
||||
if(typeof username != "string")
|
||||
return false;
|
||||
|
||||
if(username.length < 3 || username.length > 50)
|
||||
return false;
|
||||
|
||||
//Cant have some special characters, must be letters or digits and start with a letter
|
||||
var regex = /^[a-zA-Z][a-zA-Z\d]+$/;
|
||||
if(!regex.test(username))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Validator.validatePhone = function(phone){
|
||||
if(typeof phone != "string")
|
||||
return false;
|
||||
|
||||
if(phone.length < 7)
|
||||
return false;
|
||||
|
||||
if(!/^[0-9]+$/.test(phone))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Validator.validateEmail = function(email){
|
||||
|
||||
if(typeof email != "string")
|
||||
return false;
|
||||
|
||||
if(email.length < 7 || email.length > 320)
|
||||
return false;
|
||||
|
||||
var regex_email = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
if(!regex_email.test(email))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
Validator.validatePassword = function(pass){
|
||||
|
||||
if(typeof pass != "string")
|
||||
return false;
|
||||
|
||||
if(pass.length < 4 || pass.length > 50)
|
||||
return false;
|
||||
|
||||
//Password validations could be improved here
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
Validator.countQuantity = function(array){
|
||||
|
||||
if (!array || !Array.isArray(array))
|
||||
return 0;
|
||||
|
||||
var total = 0;
|
||||
for (const elem of array) {
|
||||
var q = elem.quantity || 1;
|
||||
total += q;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
//Returns true or false checking if array has the expected quantity
|
||||
Validator.validateArray = function(array, quantity){
|
||||
var nb = Validator.countQuantity(array);
|
||||
return quantity == nb;
|
||||
}
|
||||
|
||||
module.exports = Validator;
|
||||
100
tools/web.tool.js
Normal file
100
tools/web.tool.js
Normal file
@@ -0,0 +1,100 @@
|
||||
var http = require('http');
|
||||
var url = require('url');
|
||||
|
||||
var WebTool = {};
|
||||
|
||||
// -------- Http -----------------
|
||||
WebTool.get = function(path, callback) {
|
||||
|
||||
var hostname = url.parse(path).hostname;
|
||||
var pathname = url.parse(path).pathname;
|
||||
|
||||
var post_options = {
|
||||
host: hostname,
|
||||
port: '80',
|
||||
path: pathname,
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
var request = http.request(post_options, function(res) {
|
||||
res.setEncoding('utf8');
|
||||
var oData = "";
|
||||
res.on('data', function (chunk) {
|
||||
oData += chunk;
|
||||
});
|
||||
res.on('end', function(){
|
||||
callback(oData, res.statusCode);
|
||||
});
|
||||
});
|
||||
|
||||
request.end();
|
||||
};
|
||||
|
||||
WebTool.post = function(path, data, callback) {
|
||||
|
||||
var post_data = JSON.stringify(data);
|
||||
var hostname = url.parse(path).hostname;
|
||||
var pathname = url.parse(path).pathname;
|
||||
|
||||
var post_options = {
|
||||
host: hostname,
|
||||
port: '80',
|
||||
path: pathname,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Content-Length': post_data.length
|
||||
}
|
||||
};
|
||||
|
||||
var request = http.request(post_options, function(res) {
|
||||
res.setEncoding('utf8');
|
||||
var oData = "";
|
||||
res.on('data', function (chunk) {
|
||||
oData += chunk;
|
||||
});
|
||||
res.on('end', function(){
|
||||
callback(oData, res.statusCode);
|
||||
});
|
||||
});
|
||||
|
||||
request.write(post_data);
|
||||
request.end();
|
||||
};
|
||||
|
||||
WebTool.toObject = function(json)
|
||||
{
|
||||
try{
|
||||
var data = JSON.parse(json);
|
||||
return data;
|
||||
}
|
||||
catch{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
WebTool.toJson = function(data)
|
||||
{
|
||||
try{
|
||||
var data = JSON.stringify(json);
|
||||
return data;
|
||||
}
|
||||
catch{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
WebTool.GenerateUID = function(length, numberOnly)
|
||||
{
|
||||
var result = '';
|
||||
var characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
if(numberOnly)
|
||||
characters = '0123456789';
|
||||
var charactersLength = characters.length;
|
||||
for ( var i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = WebTool;
|
||||
529
users/users.cards.controller.js
Normal file
529
users/users.cards.controller.js
Normal file
@@ -0,0 +1,529 @@
|
||||
const UserModel = require("./users.model");
|
||||
const PackModel = require("../packs/packs.model");
|
||||
const CardModel = require("../cards/cards.model");
|
||||
const VariantModel = require('../variants/variants.model');
|
||||
const UserTool = require("./users.tool");
|
||||
const CardTool = require("../cards/cards.tool");
|
||||
const Activity = require("../activity/activity.model");
|
||||
const config = require('../config');
|
||||
|
||||
exports.UpdateDeck = async(req, res) => {
|
||||
|
||||
if(!req.params.deckId)
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var userId = req.jwt.userId;
|
||||
var deckId = req.params.deckId;
|
||||
|
||||
var ndeck = {
|
||||
tid: req.params.deckId,
|
||||
title: req.body.title || "Deck",
|
||||
hero: req.body.hero || {},
|
||||
cards: req.body.cards || [],
|
||||
};
|
||||
|
||||
var user = await UserModel.getById(userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found: " + userId});
|
||||
|
||||
var decks = user.decks || [];
|
||||
var found = false;
|
||||
var index = 0;
|
||||
for(var i=0; i<decks.length; i++){
|
||||
var deck = decks[i];
|
||||
if(deck.tid == deckId)
|
||||
{
|
||||
decks[i]= ndeck;
|
||||
found = true;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
//Add new
|
||||
if(!found && ndeck.cards.length > 0)
|
||||
decks.push(ndeck);
|
||||
|
||||
//Delete deck
|
||||
if(found && ndeck.cards.length == 0)
|
||||
decks.splice(index, 1);
|
||||
|
||||
var userData = { decks: decks};
|
||||
var upUser = await UserModel.update(user, userData);
|
||||
if (!upUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
return res.status(200).send(upUser.decks);
|
||||
};
|
||||
|
||||
exports.DeleteDeck = async(req, res) => {
|
||||
|
||||
if(!req.params.deckId)
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var userId = req.jwt.userId;
|
||||
var deckId = req.params.deckId;
|
||||
|
||||
var user = await UserModel.getById(userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found: " + userId});
|
||||
|
||||
var decks = user.decks || {};
|
||||
var index = -1;
|
||||
for(var i=0; i<decks.length; i++){
|
||||
var deck = decks[i];
|
||||
if(deck.tid == deckId)
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if(index >= 0)
|
||||
decks.splice(index, 1);
|
||||
|
||||
var userData = { decks: decks};
|
||||
var upUser = await UserModel.update(user, userData);
|
||||
if (!upUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
return res.status(200).send(upUser.decks);
|
||||
};
|
||||
|
||||
exports.BuyCard = async (req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const cardId = req.body.card;
|
||||
const variantId = req.body.variant;
|
||||
const quantity = req.body.quantity || 1;
|
||||
|
||||
if (!cardId || typeof cardId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!variantId || typeof variantId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!Number.isInteger(quantity) || quantity <= 0)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get the user add update the array
|
||||
var user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Cant find user " + userId });
|
||||
|
||||
var card = await CardModel.get(cardId);
|
||||
if (!card)
|
||||
return res.status(404).send({ error: "Cant find card " + cardId });
|
||||
|
||||
if(card.cost <= 0)
|
||||
return res.status(400).send({ error: "Can't be purchased" });
|
||||
|
||||
var variant = await VariantModel.get(variantId);
|
||||
var factor = variant != null ? variant.cost_factor : 1;
|
||||
var cost = quantity * factor * card.cost;
|
||||
if(user.coins < cost)
|
||||
return res.status(400).send({ error: "Not enough coins" });
|
||||
|
||||
user.coins -= cost;
|
||||
|
||||
var valid = await UserTool.addCards(user, [{tid: cardId, variant: variantId, quantity: quantity}]);
|
||||
if (!valid)
|
||||
return res.status(500).send({ error: "Error when adding cards" });
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["coins", "cards"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
// Activity Log -------------
|
||||
const activityData = {card: cardId, variant: variantId, quantity: quantity};
|
||||
const act = await Activity.LogActivity("user_buy_card", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
// -------------
|
||||
return res.status(200).send();
|
||||
|
||||
};
|
||||
|
||||
exports.SellCard = async (req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const cardId = req.body.card;
|
||||
const variantId = req.body.variant;
|
||||
const quantity = req.body.quantity || 1;
|
||||
|
||||
if (!cardId || typeof cardId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!variantId || typeof variantId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!Number.isInteger(quantity) || quantity <= 0)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get the user add update the array
|
||||
var user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Cant find user " + userId });
|
||||
|
||||
var card = await CardModel.get(cardId);
|
||||
if (!card)
|
||||
return res.status(404).send({ error: "Cant find card " + cardId });
|
||||
|
||||
if(card.cost <= 0)
|
||||
return res.status(400).send({ error: "Can't be sold" });
|
||||
|
||||
var variant = await VariantModel.get(variantId);
|
||||
|
||||
if(!UserTool.hasCard(user, cardId, variantId, quantity))
|
||||
return res.status(400).send({ error: "Not enough cards" });
|
||||
|
||||
var factor = variant != null ? variant.cost_factor : 1;
|
||||
var cost = quantity * Math.round(card.cost * factor * config.sell_ratio);
|
||||
user.coins += cost;
|
||||
|
||||
var valid = await UserTool.addCards(user, [{tid: cardId, variant: variantId, quantity: -quantity}]);
|
||||
if (!valid)
|
||||
return res.status(500).send({ error: "Error when removing cards" });
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["coins", "cards"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
// Activity Log -------------
|
||||
const activityData = {card: cardId, variant: variantId, quantity: quantity};
|
||||
const act = await Activity.LogActivity("user_sell_card", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
// -------------
|
||||
return res.status(200).send();
|
||||
};
|
||||
|
||||
exports.SellDuplicateCards = async (req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const rarityId = req.body.rarity || ""; //If not set, will sell cards of all rarities
|
||||
const variantId = req.body.variant || ""; //If not set, will sell cards of all variants
|
||||
const keep = req.body.keep; //Number of copies to keep
|
||||
|
||||
if(typeof rarityId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(typeof variantId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!Number.isInteger(keep) || keep < 0)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get the user add update the array
|
||||
var user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Cant find user " + userId });
|
||||
|
||||
var all_variants = await VariantModel.getAll();
|
||||
if (!all_variants)
|
||||
return res.status(404).send({ error: "Cant find variants" });
|
||||
|
||||
var all_cards = await CardModel.getAll();
|
||||
if (!all_cards)
|
||||
return res.status(404).send({ error: "Cant find cards" });
|
||||
|
||||
var cards_to_sell = [];
|
||||
var coins = 0;
|
||||
for(var i=0; i<user.cards.length; i++)
|
||||
{
|
||||
var card = user.cards[i];
|
||||
var card_data = UserTool.getData(all_cards, card.tid);
|
||||
if(card_data && card_data.cost > 0 && card.quantity > keep)
|
||||
{
|
||||
if(!variantId || card.variant == variantId)
|
||||
{
|
||||
if(!rarityId || card_data.rarity == rarityId)
|
||||
{
|
||||
var variant = UserTool.getData(all_variants, card.variant);
|
||||
var quantity = card.quantity - keep;
|
||||
var sell = {tid: card.tid, variant: card.variant, quantity: -quantity};
|
||||
var factor = variant != null ? variant.cost_factor : 1;
|
||||
var cost = quantity * Math.round(card_data.cost * factor * config.sell_ratio);
|
||||
cards_to_sell.push(sell);
|
||||
coins += cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(cards_to_sell.length == 0)
|
||||
return res.status(200).send();
|
||||
|
||||
user.coins += coins;
|
||||
|
||||
var valid = await UserTool.addCards(user, cards_to_sell);
|
||||
if (!valid)
|
||||
return res.status(500).send({ error: "Error when removing cards" });
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["coins", "cards"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
// Activity Log -------------
|
||||
const activityData = {rarity: rarityId, variant: variantId, keep: keep};
|
||||
const act = await Activity.LogActivity("user_sell_cards_duplicate", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
// -------------
|
||||
return res.status(200).send();
|
||||
};
|
||||
|
||||
exports.BuyPack = async (req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const packId = req.body.pack;
|
||||
const quantity = req.body.quantity || 1;
|
||||
|
||||
if (!packId || typeof packId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!Number.isInteger(quantity) || quantity <= 0)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get the user add update the array
|
||||
var user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Cant find user " + userId });
|
||||
|
||||
var pack = await PackModel.get(packId);
|
||||
if (!pack)
|
||||
return res.status(404).send({ error: "Cant find pack " + packId });
|
||||
|
||||
if(pack.cost <= 0)
|
||||
return res.status(400).send({ error: "Can't be purchased" });
|
||||
|
||||
var cost = quantity * pack.cost;
|
||||
if(user.coins < cost)
|
||||
return res.status(400).send({ error: "Not enough coins" });
|
||||
|
||||
user.coins -= cost;
|
||||
|
||||
var valid = await UserTool.addPacks(user, [{tid: packId, quantity: quantity}]);
|
||||
if (!valid)
|
||||
return res.status(500).send({ error: "Error when adding packs" });
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["coins", "packs"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
// Activity Log -------------
|
||||
const activityData = {pack: packId, quantity: quantity};
|
||||
const act = await Activity.LogActivity("user_buy_pack", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
// -------------
|
||||
return res.status(200).send();
|
||||
|
||||
};
|
||||
|
||||
exports.SellPack = async (req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const packId = req.body.pack;
|
||||
const quantity = req.body.quantity || 1;
|
||||
|
||||
if (!packId || typeof packId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if(!Number.isInteger(quantity) || quantity <= 0)
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get the user add update the array
|
||||
var user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Cant find user " + userId });
|
||||
|
||||
var pack = await PackModel.get(packId);
|
||||
if (!pack)
|
||||
return res.status(404).send({ error: "Cant find pack " + packId });
|
||||
|
||||
if(pack.cost <= 0)
|
||||
return res.status(400).send({ error: "Can't be sold" });
|
||||
|
||||
if(!UserTool.hasPack(user, packId, quantity))
|
||||
return res.status(400).send({ error: "Not enough coins" });
|
||||
|
||||
var cost = quantity * Math.round(pack.cost * config.sell_ratio);
|
||||
user.coins += cost;
|
||||
|
||||
var valid = await UserTool.addPacks(user, [{tid: packId, quantity: -quantity}]);
|
||||
if (!valid)
|
||||
return res.status(500).send({ error: "Error when adding packs" });
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["coins", "packs"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
// Activity Log -------------
|
||||
const activityData = {pack: packId, quantity: quantity};
|
||||
const act = await Activity.LogActivity("user_sell_pack", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
// -------------
|
||||
return res.status(200).send();
|
||||
|
||||
};
|
||||
|
||||
exports.OpenPack = async (req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const packId = req.body.pack;
|
||||
|
||||
if (!packId || typeof packId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get the user add update the array
|
||||
var user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Cant find user " + userId });
|
||||
|
||||
var pack = await PackModel.get(packId);
|
||||
if (!pack)
|
||||
return res.status(404).send({ error: "Cant find pack " + packId });
|
||||
|
||||
if(!UserTool.hasPack(user, packId, 1))
|
||||
return res.status(400).send({ error: "You don't have this pack" });
|
||||
|
||||
var cardsToAdd = await CardTool.getPackCards(pack);
|
||||
var validCards = await UserTool.addCards(user, cardsToAdd);
|
||||
var validPacks = await UserTool.addPacks(user, [{tid: packId, quantity: -1}]);
|
||||
|
||||
if (!validCards || !validPacks)
|
||||
return res.status(500).send({ error: "Error when adding cards" });
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["cards", "packs"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
// Activity Log -------------
|
||||
const activityData = {pack: packId, cards: cardsToAdd};
|
||||
const act = await Activity.LogActivity("user_open_pack", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
// -------------
|
||||
return res.status(200).send(cardsToAdd);
|
||||
|
||||
};
|
||||
|
||||
exports.BuyAvatar = async (req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const avatarId = req.body.avatar;
|
||||
|
||||
if (!avatarId || typeof avatarId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get the user add update the array
|
||||
var user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Cant find user " + userId });
|
||||
|
||||
var cost = config.avatar_cost;
|
||||
if(user.coins < cost)
|
||||
return res.status(400).send({ error: "Not enough coins" });
|
||||
|
||||
if(UserTool.hasAvatar(user, avatarId))
|
||||
return res.status(400).send({ error: "Already have this avatar" });
|
||||
|
||||
user.coins -= cost;
|
||||
UserTool.addAvatars(user, [avatarId]);
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["coins", "avatars"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
// Activity Log -------------
|
||||
const activityData = {avatar: avatarId};
|
||||
const act = await Activity.LogActivity("user_buy_avatar", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
return res.status(200).send();
|
||||
};
|
||||
|
||||
exports.BuyCardback = async (req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const cardbackId = req.body.cardback;
|
||||
|
||||
if (!cardbackId || typeof cardbackId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get the user add update the array
|
||||
var user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Cant find user " + userId });
|
||||
|
||||
var cost = config.cardback_cost;
|
||||
if(user.coins < cost)
|
||||
return res.status(400).send({ error: "Not enough coins" });
|
||||
|
||||
if(UserTool.hasCardback(user, cardbackId))
|
||||
return res.status(400).send({ error: "Already have this cardback" });
|
||||
|
||||
user.coins -= cost;
|
||||
UserTool.addCardbacks(user, [cardbackId]);
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["coins", "cardbacks"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
// Activity Log -------------
|
||||
const activityData = {cardback: cardbackId};
|
||||
const act = await Activity.LogActivity("user_buy_cardback", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
return res.status(200).send();
|
||||
};
|
||||
|
||||
//Fix variant from previous version
|
||||
exports.FixVariants = async (req, res) =>
|
||||
{
|
||||
var from = req.body.from || "";
|
||||
var to = req.body.to || "";
|
||||
|
||||
if (from && typeof packId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
if (to && typeof packId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
var users = await UserModel.getAll();
|
||||
var default_variant = await VariantModel.getDefault();
|
||||
var default_tid = default_variant ? default_variant.tid : "";
|
||||
var count = 0;
|
||||
|
||||
for(var u=0; u<users.length; u++)
|
||||
{
|
||||
var user = users[u];
|
||||
var changed = false;
|
||||
for(var i=0; i<user.cards.length; i++)
|
||||
{
|
||||
var card = user.cards[i];
|
||||
if(!card.variant)
|
||||
{
|
||||
card.variant = default_tid;
|
||||
changed = true;
|
||||
}
|
||||
if(from && to && card.variant == from)
|
||||
{
|
||||
card.variant = to;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(changed)
|
||||
{
|
||||
var new_cards = user.cards;
|
||||
user.cards = [];
|
||||
await UserTool.addCards(user, new_cards); //Re-add in correct format
|
||||
UserModel.save(user, ["cards"]);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Activity Log -------------
|
||||
const act = await Activity.LogActivity("fix_variants", req.jwt.username, {});
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
return res.status(200).send({updated: count});
|
||||
}
|
||||
505
users/users.controller.js
Normal file
505
users/users.controller.js
Normal file
@@ -0,0 +1,505 @@
|
||||
const UserModel = require('./users.model');
|
||||
const UserTool = require('./users.tool');
|
||||
const RewardModel = require('../rewards/rewards.model');
|
||||
const DateTool = require('../tools/date.tool');
|
||||
const Activity = require("../activity/activity.model");
|
||||
const Validator = require('../tools/validator.tool');
|
||||
const AuthTool = require('../authorization/auth.tool');
|
||||
const Email = require('../tools/email.tool');
|
||||
const config = require('../config');
|
||||
|
||||
//Register new user
|
||||
exports.RegisterUser = async (req, res, next) => {
|
||||
|
||||
if(!req.body.email || !req.body.username || !req.body.password){
|
||||
return res.status(400).send({error: 'Invalid parameters'});
|
||||
}
|
||||
|
||||
var email = req.body.email;
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
var avatar = req.body.avatar || "";
|
||||
|
||||
//Validations
|
||||
if(!Validator.validateUsername(username)){
|
||||
return res.status(400).send({error: 'Invalid username'});
|
||||
}
|
||||
|
||||
if(!Validator.validateEmail(email)){
|
||||
return res.status(400).send({error: 'Invalid email'});
|
||||
}
|
||||
|
||||
if(!Validator.validatePassword(password)){
|
||||
return res.status(400).send({error: 'Invalid password'});
|
||||
}
|
||||
|
||||
if(avatar && typeof avatar !== "string")
|
||||
return res.status(400).send({error: "Invalid avatar"});
|
||||
|
||||
var user_username = await UserModel.getByUsername(username);
|
||||
var user_email = await UserModel.getByEmail(email);
|
||||
|
||||
if(user_username)
|
||||
return res.status(400).send({error: 'Username already exists'});
|
||||
if(user_email)
|
||||
return res.status(400).send({error: 'Email already exists'});
|
||||
|
||||
//Check if its first user
|
||||
var nb_users = await UserModel.count();
|
||||
var permission = nb_users > 0 ? 1 : 10; //First user has 10
|
||||
var validation = nb_users > 0 ? 0 : 1; //First user has 1
|
||||
|
||||
//User Data
|
||||
var user = {};
|
||||
|
||||
user.username = username;
|
||||
user.email = email;
|
||||
user.avatar = avatar;
|
||||
user.permission_level = permission;
|
||||
user.validation_level = validation;
|
||||
|
||||
user.coins = config.start_coins;
|
||||
user.elo = config.start_elo;
|
||||
user.xp = 0;
|
||||
|
||||
user.account_create_time = new Date();
|
||||
user.last_login_time = new Date();
|
||||
user.last_online_time = new Date();
|
||||
user.email_confirm_key = UserTool.generateID(20);
|
||||
|
||||
UserTool.setUserPassword(user, password);
|
||||
|
||||
//Create user
|
||||
var nUser = await UserModel.create(user);
|
||||
if(!nUser)
|
||||
return res.status(500).send({ error: "Unable to create user" });
|
||||
|
||||
//Send confirm email
|
||||
UserTool.sendEmailConfirmKey(nUser, user.email, user.email_confirm_key);
|
||||
|
||||
// Activity Log -------------
|
||||
var activityData = {username: user.username, email: user.email };
|
||||
var act = await Activity.LogActivity("register", user.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
//Return response
|
||||
return res.status(200).send({ success: true, id: nUser._id });
|
||||
};
|
||||
|
||||
exports.GetAll = async(req, res) => {
|
||||
|
||||
let user_permission_level = parseInt(req.jwt.permission_level);
|
||||
let is_admin = (user_permission_level >= config.permissions.SERVER);
|
||||
|
||||
var list = await UserModel.getAll();
|
||||
for(var i=0; i<list.length; i++){
|
||||
if(is_admin)
|
||||
list[i] = list[i].deleteSecrets();
|
||||
else
|
||||
list[i] = list[i].deleteAdminOnly();
|
||||
}
|
||||
|
||||
return res.status(200).send(list);
|
||||
};
|
||||
|
||||
exports.GetUser = async(req, res) => {
|
||||
var user = await UserModel.getById(req.params.userId);
|
||||
if(!user)
|
||||
user = await UserModel.getByUsername(req.params.userId);
|
||||
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found " + req.params.userId});
|
||||
|
||||
let user_permission_level = parseInt(req.jwt.permission_level);
|
||||
let is_admin = (user_permission_level >= config.permissions.SERVER);
|
||||
if(is_admin || req.params.userId == req.jwt.userId || req.params.userId == req.jwt.username)
|
||||
user = user.deleteSecrets();
|
||||
else
|
||||
user = user.deleteAdminOnly();
|
||||
|
||||
user.server_time = new Date(); //Return server time
|
||||
return res.status(200).send(user);
|
||||
};
|
||||
|
||||
exports.EditUser = async(req, res) => {
|
||||
|
||||
var userId = req.params.userId;
|
||||
var avatar = req.body.avatar;
|
||||
var cardback = req.body.cardback;
|
||||
|
||||
if(!userId || typeof userId !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(avatar && typeof avatar !== "string")
|
||||
return res.status(400).send({error: "Invalid avatar"});
|
||||
|
||||
if(cardback && typeof cardback !== "string")
|
||||
return res.status(400).send({error: "Invalid avatar"});
|
||||
|
||||
var user = await UserModel.getById(userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found: " + userId});
|
||||
|
||||
var userData = {};
|
||||
|
||||
if(avatar && avatar.length < 50)
|
||||
userData.avatar = avatar;
|
||||
|
||||
if(cardback && cardback.length < 50)
|
||||
userData.cardback = cardback;
|
||||
|
||||
//Add other variables you'd like to be able to edit here
|
||||
//Avoid allowing changing username, email or password here, since those require additional security validations and should have their own functions
|
||||
|
||||
//Update user
|
||||
var result = await UserModel.update(user, userData);
|
||||
if(!result)
|
||||
return res.status(400).send({error: "Error updating user: " + userId});
|
||||
|
||||
return res.status(200).send(result.deleteSecrets());
|
||||
};
|
||||
|
||||
exports.EditEmail = async(req, res) => {
|
||||
|
||||
var userId = req.jwt.userId;
|
||||
var email = req.body.email;
|
||||
|
||||
if(!userId || typeof userId !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!email || !Validator.validateEmail(email))
|
||||
return res.status(400).send({error: "Invalid email"});
|
||||
|
||||
var user = await UserModel.getById(userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found: " + userId});
|
||||
|
||||
if(email == user.email)
|
||||
return res.status(400).send({error: "Email unchanged"});
|
||||
|
||||
//Find email
|
||||
var foundUserEmail = await UserModel.getByEmail(email);
|
||||
if(foundUserEmail)
|
||||
return res.status(403).send({error: "Email already exists"});
|
||||
|
||||
var prev_email = user.email;
|
||||
var userData = {};
|
||||
userData.email = email;
|
||||
userData.validation_level = 0;
|
||||
userData.email_confirm_key = UserTool.generateID(20);
|
||||
|
||||
//Update user
|
||||
var result = await UserModel.update(user, userData);
|
||||
if(!result)
|
||||
return res.status(400).send({error: "Error updating user email: " + userId});
|
||||
|
||||
//Send confirmation email
|
||||
UserTool.sendEmailConfirmKey(user, email, userData.email_confirm_key);
|
||||
UserTool.sendEmailChangeEmail(user, prev_email, email);
|
||||
|
||||
// Activity Log -------------
|
||||
var activityData = {prev_email: prev_email, new_email: email };
|
||||
var a = await Activity.LogActivity("edit_email", req.jwt.username, {activityData});
|
||||
if (!a) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
return res.status(200).send(result.deleteSecrets());
|
||||
};
|
||||
|
||||
exports.EditPassword = async(req, res) => {
|
||||
|
||||
var userId = req.jwt.userId;
|
||||
var password = req.body.password_new;
|
||||
var password_previous = req.body.password_previous;
|
||||
|
||||
if(!userId || typeof userId !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!password || !password_previous || typeof password !== "string" || typeof password_previous !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var user = await UserModel.getById(userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found: " + userId});
|
||||
|
||||
let validPass = AuthTool.validatePassword(user, password_previous);
|
||||
if(!validPass)
|
||||
return res.status(401).send({error: "Invalid previous password"});
|
||||
|
||||
UserTool.setUserPassword(user, password);
|
||||
|
||||
var result = await UserModel.save(user, ["password", "refresh_key", "password_recovery_key"]);
|
||||
if(!result)
|
||||
return res.status(500).send({error: "Error updating user password: " + userId});
|
||||
|
||||
//Send confirmation email
|
||||
UserTool.sendEmailChangePassword(user, user.email);
|
||||
|
||||
// Activity Log -------------
|
||||
var a = await Activity.LogActivity("edit_password", req.jwt.username, {});
|
||||
if (!a) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.EditPermissions = async(req, res) => {
|
||||
|
||||
var userId = req.params.userId;
|
||||
var permission_level = req.body.permission_level;
|
||||
|
||||
if(!userId || typeof userId !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!Validator.isInteger(permission_level))
|
||||
return res.status(400).send({error: "Invalid permission"});
|
||||
|
||||
var user = await UserModel.getById(userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found: " + userId});
|
||||
|
||||
var userData = {};
|
||||
|
||||
//Change avatar
|
||||
userData.permission_level = permission_level;
|
||||
|
||||
//Update user
|
||||
var result = await UserModel.update(user, userData);
|
||||
if(!result)
|
||||
return res.status(400).send({error: "Error updating user: " + userId});
|
||||
|
||||
// Activity Log -------------
|
||||
var activityData = {username: user.username, permission_level: userData.permission_level };
|
||||
var act = await Activity.LogActivity("edit_permission", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
return res.status(200).send(result.deleteSecrets());
|
||||
};
|
||||
|
||||
exports.ResetPassword = async(req, res) => {
|
||||
|
||||
var email = req.body.email;
|
||||
|
||||
if(!config.smtp_enabled)
|
||||
return res.status(400).send({error: "Email SMTP is not configured"});
|
||||
|
||||
if(!email || typeof email !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var user = await UserModel.getByEmail(email);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found: " + email});
|
||||
|
||||
user.password_recovery_key = UserTool.generateID(10, true);
|
||||
await UserModel.save(user, ["password_recovery_key"]);
|
||||
|
||||
UserTool.sendEmailPasswordRecovery(user, email);
|
||||
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
|
||||
exports.ResetPasswordConfirm = async(req, res) => {
|
||||
|
||||
var email = req.body.email;
|
||||
var code = req.body.code;
|
||||
var password = req.body.password;
|
||||
|
||||
if(!email || typeof email !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!code || typeof code !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!password || typeof password !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var user = await UserModel.getByEmail(email);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found: " + email});
|
||||
|
||||
if(!user.password_recovery_key || user.password_recovery_key.toUpperCase() != code)
|
||||
return res.status(403).send({error: "Invalid Recovery Code"});
|
||||
|
||||
UserTool.setUserPassword(user, password);
|
||||
|
||||
var result = await UserModel.save(user, ["password", "refresh_key", "password_recovery_key"]);
|
||||
if(!result)
|
||||
return res.status(500).send({error: "Error updating user password: " + email});
|
||||
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
//In this function all message are returned in direct text because the email link is accessed from browser
|
||||
exports.ConfirmEmail = async (req, res) =>{
|
||||
|
||||
if(!req.params.userId || !req.params.code){
|
||||
return res.status(404).send("Code invalid");
|
||||
}
|
||||
|
||||
var user = await UserModel.getById(req.params.userId);
|
||||
if(!user)
|
||||
return res.status(404).send("Code invalid");
|
||||
|
||||
if(user.email_confirm_key != req.params.code)
|
||||
return res.status(404).send("Code invalid");
|
||||
|
||||
if(user.validation_level >= 1)
|
||||
return res.status(400).send("Email already confirmed!");
|
||||
|
||||
//Code valid!
|
||||
var data = {validation_level: Math.max(user.validation_level, 1)};
|
||||
await UserModel.update(user, data);
|
||||
|
||||
return res.status(200).send("Email confirmed!");
|
||||
};
|
||||
|
||||
exports.ResendEmail = async(req, res) =>
|
||||
{
|
||||
var userId = req.jwt.userId;
|
||||
var user = await UserModel.getById(userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found " + userId});
|
||||
|
||||
if(user.validation_level > 0)
|
||||
return res.status(403).send({error: "Email already confirmed"});
|
||||
|
||||
UserTool.sendEmailConfirmKey(user, user.email, user.email_confirm_key);
|
||||
|
||||
return res.status(200).send();
|
||||
}
|
||||
|
||||
exports.SendEmail = async (req, res) =>{
|
||||
|
||||
var subject = req.body.title;
|
||||
var text = req.body.text;
|
||||
var email = req.body.email;
|
||||
|
||||
if(!subject || typeof subject !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!text || typeof text !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(!email || typeof email !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
Email.SendEmail(email, subject, text, function(result){
|
||||
console.log("Sent email to: " + email + ": " + result);
|
||||
return res.status(200).send({success: result});
|
||||
});
|
||||
};
|
||||
|
||||
// reward is an object containing rewards to give
|
||||
exports.GiveReward = async(req, res) =>
|
||||
{
|
||||
var userId = req.params.userId;
|
||||
var reward = req.body.reward;
|
||||
|
||||
//Validate params
|
||||
if (!userId || typeof userId !== "string")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
if (!reward || typeof reward !== "object")
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
//Get the user add update the array
|
||||
var user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Cant find user " + userId });
|
||||
|
||||
var valid = await UserTool.GainUserReward(user, reward);
|
||||
if (!valid)
|
||||
return res.status(500).send({ error: "Error when adding rewards " + userId });
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["cards"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
// Activity Log -------------
|
||||
var activityData = {reward: reward, user: user.username};
|
||||
var act = await Activity.LogActivity("reward_give", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
// -------------
|
||||
return res.status(200).send(updatedUser.deleteSecrets());
|
||||
};
|
||||
|
||||
// reward is an ID of reward to give
|
||||
exports.GainReward = async(req, res) =>
|
||||
{
|
||||
var userId = req.params.userId;
|
||||
var rewardId = req.body.reward;
|
||||
|
||||
if(!userId || !rewardId)
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
if(typeof rewardId !== "string")
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var user = await UserModel.getById(userId);
|
||||
if(!user)
|
||||
return res.status(404).send({error: "User not found: " + userId});
|
||||
|
||||
var reward = await RewardModel.get(rewardId);
|
||||
if(!reward)
|
||||
return res.status(404).send({error: "Reward not found: " + rewardId});
|
||||
|
||||
if(reward.repeat && req.jwt.permission_level < config.permissions.SERVER)
|
||||
return res.status(404).send({error: "Insufficient Permission"});
|
||||
|
||||
if(!reward.repeat && user.rewards.includes(rewardId))
|
||||
return res.status(403).send({error: "Reward already claimed: " + rewardId});
|
||||
|
||||
if(!reward.repeat && reward.group && user.rewards.includes(reward.group))
|
||||
return res.status(403).send({error: "Reward group already claimed: " + reward.group});
|
||||
|
||||
//Save reward
|
||||
if(!user.rewards.includes(reward.tid))
|
||||
user.rewards.push(reward.tid);
|
||||
|
||||
if(reward.group && !user.rewards.includes(reward.group))
|
||||
user.rewards.push(reward.group);
|
||||
|
||||
//Add reward to user
|
||||
var valid = await UserTool.GainUserReward(user, reward);
|
||||
|
||||
//Check if succeed
|
||||
if(!valid)
|
||||
return res.status(500).send({error: "Failed adding reward: " + rewardId + " for " + userId});
|
||||
|
||||
//Update the user
|
||||
var updatedUser = await UserModel.save(user, ["rewards", "xp", "coins", "cards", "decks", "avatars", "cardbacks"]);
|
||||
if (!updatedUser) return res.status(500).send({ error: "Error updating user: " + userId });
|
||||
|
||||
//Log activity
|
||||
var activityData = {reward: reward, user: user.username};
|
||||
var act = await Activity.LogActivity("reward_gain", req.jwt.username, activityData);
|
||||
if (!act) return res.status(500).send({ error: "Failed to log activity!!" });
|
||||
|
||||
return res.status(200).send(user.deleteSecrets());
|
||||
};
|
||||
|
||||
exports.GetOnline = async(req, res) =>
|
||||
{
|
||||
//Count online users
|
||||
var time = new Date();
|
||||
time = DateTool.addMinutes(time, -10);
|
||||
|
||||
var count = 0;
|
||||
var users = await UserModel.getAll();
|
||||
var usernames = [];
|
||||
for(var i=0; i<users.length; i++)
|
||||
{
|
||||
var user = users[i];
|
||||
if(user.last_online_time > time)
|
||||
{
|
||||
usernames.push(user.username);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return res.status(200).send({online: count, total: users.length, users: usernames});
|
||||
};
|
||||
|
||||
exports.Delete = async(req, res) => {
|
||||
UserModel.remove(req.params.userId);
|
||||
return res.status(204).send({});
|
||||
};
|
||||
135
users/users.friends.controller.js
Normal file
135
users/users.friends.controller.js
Normal file
@@ -0,0 +1,135 @@
|
||||
const UserModel = require("./users.model");
|
||||
const Activity = require("../activity/activity.model");
|
||||
const UserTool = require('./users.tool');
|
||||
const config = require('../config.js');
|
||||
|
||||
exports.AddFriend = async (req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const username = req.body.username;
|
||||
|
||||
//Validate params
|
||||
if (!username || !userId) {
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
}
|
||||
|
||||
//Get the user
|
||||
const user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Can't find user" });
|
||||
|
||||
const friend = await UserModel.getByUsername(username);
|
||||
if (!friend)
|
||||
return res.status(404).send({ error: "Can't find friend" });
|
||||
|
||||
if(user.id == friend.id)
|
||||
return res.status(400).send({ error: "Can't add yourself" });
|
||||
|
||||
//Add Friend
|
||||
if(!user.friends.includes(friend.username))
|
||||
user.friends.push(friend.username);
|
||||
|
||||
//Add request other friend
|
||||
if(!friend.friends.includes(user.username) && !friend.friends_requests.includes(user.username))
|
||||
friend.friends_requests.push(user.username)
|
||||
|
||||
//Remove self request
|
||||
if(user.friends_requests.includes(friend.username))
|
||||
user.friends_requests.remove(friend.username);
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["friends", "friends_requests"]);
|
||||
if (!updatedUser) return res.status(400).send({ error: "Error updating user" });
|
||||
|
||||
//Update the other user
|
||||
var updatedFriend = await UserModel.save(friend, ["friends_requests"]);
|
||||
if (!updatedFriend) return res.status(400).send({ error: "Error updating user" });
|
||||
|
||||
// -------------
|
||||
return res.status(200).send(updatedUser.deleteSecrets());
|
||||
};
|
||||
|
||||
exports.RemoveFriend = async(req, res) => {
|
||||
|
||||
const userId = req.jwt.userId;
|
||||
const username = req.body.username;
|
||||
|
||||
//Validate params
|
||||
if (!username || !userId) {
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
}
|
||||
|
||||
//Get the user
|
||||
const user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Can't find user" });
|
||||
|
||||
const friend = await UserModel.getByUsername(username);
|
||||
if (!friend)
|
||||
return res.status(404).send({ error: "Can't find friend" });
|
||||
|
||||
if(user.friends.includes(friend.username))
|
||||
user.friends.remove(friend.username);
|
||||
if(user.friends_requests.includes(friend.username))
|
||||
user.friends_requests.remove(friend.username);
|
||||
if(friend.friends_requests.includes(user.username))
|
||||
friend.friends_requests.remove(user.username)
|
||||
|
||||
//Update the user array
|
||||
var updatedUser = await UserModel.save(user, ["friends", "friends_requests"]);
|
||||
if (!updatedUser) return res.status(400).send({ error: "Error updating user" });
|
||||
|
||||
var updatedFriend = await UserModel.save(friend, ["friends_requests"]);
|
||||
if (!updatedFriend) return res.status(400).send({ error: "Error updating user" });
|
||||
|
||||
// -------------
|
||||
return res.status(200).send(updatedUser.deleteSecrets());
|
||||
};
|
||||
|
||||
exports.ListFriends = async(req, res) =>
|
||||
{
|
||||
const userId = req.jwt.userId;
|
||||
|
||||
//Validate params
|
||||
if (!userId) {
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
}
|
||||
|
||||
//Get the user
|
||||
const user = await UserModel.getById(userId);
|
||||
if (!user)
|
||||
return res.status(404).send({ error: "Can't find user" });
|
||||
|
||||
var friends_users = user.friends || [];
|
||||
var requests_users = user.friends_requests || [];
|
||||
|
||||
const friends = await UserModel.getUsernameList(friends_users);
|
||||
if (!friends)
|
||||
return res.status(404).send({ error: "Can't find user friends" });
|
||||
|
||||
const requests = await UserModel.getUsernameList(requests_users);
|
||||
if (!requests)
|
||||
return res.status(404).send({ error: "Can't find user friends" });
|
||||
|
||||
//Reduce visible fields
|
||||
for(var i=0; i<friends.length; i++)
|
||||
{
|
||||
friends[i] = {
|
||||
username: friends[i].username,
|
||||
avatar: friends[i].avatar,
|
||||
last_online_time: friends[i].last_online_time,
|
||||
}
|
||||
}
|
||||
|
||||
for(var i=0; i<requests.length; i++)
|
||||
{
|
||||
requests[i] = {
|
||||
username: requests[i].username,
|
||||
avatar: requests[i].avatar,
|
||||
last_online_time: requests[i].last_online_time,
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).send({username: user.username, friends: friends, friends_requests: requests, server_time: new Date()});
|
||||
|
||||
}
|
||||
246
users/users.model.js
Normal file
246
users/users.model.js
Normal file
@@ -0,0 +1,246 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const userSchema = new Schema({
|
||||
|
||||
username: {type: String, required: true, index: true, unique: true, default: ""},
|
||||
email: {type: String, required: true, index: true, default: ""},
|
||||
password: {type: String, required: true, default: ""},
|
||||
|
||||
permission_level: {type: Number, required: true, default: 1}, //Admin or not?
|
||||
validation_level: {type: Number, required: true, default: 0}, //Validation level increases by confirming email
|
||||
account_create_time: {type: Date, default: null},
|
||||
last_login_time: {type: Date, default: null},
|
||||
last_online_time: {type: Date, default: null},
|
||||
|
||||
refresh_key: {type: String, default: ""}, //Used for refreshing the current login JWT token
|
||||
proof_key: {type: String, default: ""}, //Used to proof to a another server who you are
|
||||
email_confirm_key: {type: String, default: ""}, //Used to confirm email
|
||||
password_recovery_key: {type: String, default: ""}, //Used for password recovery
|
||||
|
||||
avatar: {type: String, default: ""},
|
||||
cardback: {type: String, default: ""},
|
||||
coins: {type: Number, default: 0},
|
||||
xp: {type: Number, default: 0},
|
||||
elo: {type: Number, default: 1000},
|
||||
|
||||
matches: {type: Number, default: 0},
|
||||
victories: {type: Number, default: 0},
|
||||
defeats: {type: Number, default: 0},
|
||||
|
||||
cards: [{ tid: String, variant: String, quantity: Number, _id: false }],
|
||||
packs: [{ tid: String, quantity: Number, _id: false }],
|
||||
decks: [{ type: Object, _id: false }],
|
||||
avatars: [{type: String}],
|
||||
cardbacks: [{type: String}],
|
||||
rewards: [{type: String}],
|
||||
|
||||
friends: [{type: String}],
|
||||
friends_requests: [{type: String}],
|
||||
});
|
||||
|
||||
userSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
userSchema.methods.toObj = function() {
|
||||
var user = this.toObject();
|
||||
user.id = user._id;
|
||||
delete user.__v;
|
||||
delete user._id;
|
||||
return user;
|
||||
};
|
||||
|
||||
//Hide sensitive information
|
||||
userSchema.methods.deleteSecrets = function(){
|
||||
var user = this.toObject();
|
||||
user.id = user._id;
|
||||
delete user.__v;
|
||||
delete user._id;
|
||||
delete user.password;
|
||||
delete user.refresh_key;
|
||||
delete user.proof_key;
|
||||
delete user.email_confirm_key;
|
||||
delete user.password_recovery_key;
|
||||
return user;
|
||||
};
|
||||
|
||||
//Hide non-admin information, for example only admins can read user emails
|
||||
userSchema.methods.deleteAdminOnly = function(){
|
||||
var user = this.toObject();
|
||||
delete user.__v;
|
||||
delete user._id;
|
||||
delete user.email;
|
||||
delete user.permission_level;
|
||||
delete user.validation_level;
|
||||
delete user.password;
|
||||
delete user.refresh_key;
|
||||
delete user.proof_key;
|
||||
delete user.email_confirm_key;
|
||||
delete user.password_recovery_key;
|
||||
return user;
|
||||
};
|
||||
|
||||
const User = mongoose.model('Users', userSchema);
|
||||
exports.UserModel = User;
|
||||
|
||||
// USER DATA MODELS ------------------------------------------------
|
||||
|
||||
exports.getById = async(id) => {
|
||||
|
||||
try{
|
||||
var user = await User.findOne({_id: id});
|
||||
return user;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getByEmail = async(email) => {
|
||||
|
||||
try{
|
||||
var regex = new RegExp(["^", email, "$"].join(""), "i");
|
||||
var user = await User.findOne({email: regex});
|
||||
return user;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getByUsername = async(username) => {
|
||||
|
||||
try{
|
||||
var regex = new RegExp(["^", username, "$"].join(""), "i");
|
||||
var user = await User.findOne({username: regex});
|
||||
return user;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.create = async(userData) => {
|
||||
const user = new User(userData);
|
||||
return await user.save();
|
||||
};
|
||||
|
||||
exports.getAll = async() => {
|
||||
|
||||
try{
|
||||
var users = await User.find()
|
||||
users = users || [];
|
||||
return users;
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAllLimit = async(perPage, page) => {
|
||||
|
||||
try{
|
||||
var users = await User.find().limit(perPage).skip(perPage * page)
|
||||
users = users || [];
|
||||
return users;
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
//List users contained in the username list
|
||||
exports.getUsernameList = async(username_list) => {
|
||||
|
||||
try{
|
||||
var users = await User.find({ username: { $in: username_list } });
|
||||
return users || [];
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
//Saves an already loaded User, by providing a string list of changed keys
|
||||
exports.save = async(user, modified_list) => {
|
||||
|
||||
try{
|
||||
if(!user) return null;
|
||||
|
||||
if(modified_list)
|
||||
{
|
||||
for (let i=0; i<modified_list.length; i++) {
|
||||
user.markModified(modified_list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return await user.save();
|
||||
}
|
||||
catch(e){
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
//Update an already loaded user, by providing an object containing new values
|
||||
exports.update = async(user, userData) => {
|
||||
|
||||
try{
|
||||
if(!user) return null;
|
||||
|
||||
for (let i in userData) {
|
||||
user[i] = userData[i];
|
||||
user.markModified(i);
|
||||
}
|
||||
|
||||
var updatedUser = await user.save();
|
||||
return updatedUser;
|
||||
}
|
||||
catch(e){
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
//Load, and then update a user, based on userId and an object containing new values
|
||||
exports.patch = async(userId, userData) => {
|
||||
|
||||
try{
|
||||
var user = await User.findById ({_id: userId});
|
||||
if(!user) return null;
|
||||
|
||||
for (let i in userData) {
|
||||
user[i] = userData[i];
|
||||
}
|
||||
|
||||
var updatedUser = await user.save();
|
||||
return updatedUser;
|
||||
}
|
||||
catch(e){
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = async(userId) => {
|
||||
|
||||
try{
|
||||
var result = await User.deleteOne({_id: userId});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.count = async() =>
|
||||
{
|
||||
try{
|
||||
var count = await User.countDocuments({});
|
||||
return count;
|
||||
}
|
||||
catch{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
215
users/users.routes.js
Normal file
215
users/users.routes.js
Normal file
@@ -0,0 +1,215 @@
|
||||
const UsersController = require('./users.controller');
|
||||
const UsersCardsController = require("./users.cards.controller");
|
||||
const UsersFriendsController = require("./users.friends.controller");
|
||||
const AuthTool = require('../authorization/auth.tool');
|
||||
const config = require('../config');
|
||||
|
||||
const ADMIN = config.permissions.ADMIN; //Highest permision, can read and write all users
|
||||
const SERVER = config.permissions.SERVER; //Middle permission, can read all users and grant rewards
|
||||
const USER = config.permissions.USER; //Lowest permision, can only do things on same user
|
||||
|
||||
exports.route = function (app) {
|
||||
|
||||
//Body: username, email, password, avatar
|
||||
app.post("/users/register", app.auth_limiter, [
|
||||
UsersController.RegisterUser,
|
||||
]);
|
||||
|
||||
app.get("/users", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersController.GetAll,
|
||||
]);
|
||||
|
||||
app.get("/users/:userId", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersController.GetUser,
|
||||
]);
|
||||
|
||||
// USER - EDITS ----------------------
|
||||
|
||||
//Body: avatar, userId allows an admin to edit another user
|
||||
app.post("/users/edit/:userId", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
AuthTool.isSameUserOr(ADMIN),
|
||||
UsersController.EditUser,
|
||||
]);
|
||||
|
||||
//Body: permission
|
||||
app.post("/users/permission/edit/:userId", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
UsersController.EditPermissions,
|
||||
]);
|
||||
|
||||
//Body: email
|
||||
app.post("/users/email/edit", app.auth_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersController.EditEmail,
|
||||
]);
|
||||
|
||||
//Body: password_previous, password_new
|
||||
app.post("/users/password/edit", app.auth_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersController.EditPassword,
|
||||
]);
|
||||
|
||||
//Body: email
|
||||
app.post("/users/password/reset", app.auth_limiter, [
|
||||
UsersController.ResetPassword,
|
||||
]);
|
||||
|
||||
//body: email, code, password (password is the new one)
|
||||
app.post("/users/password/reset/confirm", app.auth_limiter, [
|
||||
UsersController.ResetPasswordConfirm,
|
||||
]);
|
||||
|
||||
/*app.post("/users/delete/:userId", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
UsersController.Delete,
|
||||
]);*/
|
||||
|
||||
// USER - EMAIL CONFIRMATION ---------------------------
|
||||
|
||||
//Email confirm
|
||||
app.get("/users/email/confirm/:userId/:code", [
|
||||
UsersController.ConfirmEmail,
|
||||
]);
|
||||
|
||||
//Ask to resend confirmation email
|
||||
app.post("/users/email/resend", app.auth_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersController.ResendEmail,
|
||||
]);
|
||||
|
||||
//Send a test email to one email address
|
||||
//body: title, text, email
|
||||
app.post("/users/email/send", app.auth_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
UsersController.SendEmail,
|
||||
]);
|
||||
|
||||
// USER - CARDS --------------------------------------
|
||||
|
||||
app.post("/users/packs/open/", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.OpenPack,
|
||||
]);
|
||||
|
||||
app.post("/users/packs/buy/", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.BuyPack,
|
||||
]);
|
||||
|
||||
app.post("/users/packs/sell/", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.SellPack,
|
||||
]);
|
||||
|
||||
app.post("/users/cards/buy/", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.BuyCard,
|
||||
]);
|
||||
|
||||
app.post("/users/cards/sell/", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.SellCard,
|
||||
]);
|
||||
|
||||
app.post("/users/cards/sell/duplicate", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.SellDuplicateCards,
|
||||
]);
|
||||
|
||||
app.post("/users/cards/variants/fix/", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isSameUserOr(SERVER),
|
||||
UsersCardsController.FixVariants,
|
||||
]);
|
||||
|
||||
app.post("/users/avatar/buy", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.BuyAvatar,
|
||||
]);
|
||||
|
||||
app.post("/users/cardback/buy", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.BuyCardback,
|
||||
]);
|
||||
|
||||
|
||||
// USER - DECKS --------------------------------------
|
||||
|
||||
//Decks
|
||||
app.post('/users/deck/:deckId', app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.UpdateDeck
|
||||
]);
|
||||
app.delete('/users/deck/:deckId', app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersCardsController.DeleteDeck
|
||||
]);
|
||||
|
||||
// USER - Friends --------------------------------------
|
||||
|
||||
//body: username (friend username)
|
||||
app.post("/users/friends/add/", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersFriendsController.AddFriend,
|
||||
]);
|
||||
|
||||
//body: username (friend username)
|
||||
app.post("/users/friends/remove/", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersFriendsController.RemoveFriend,
|
||||
]);
|
||||
|
||||
app.get("/users/friends/list/", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
UsersFriendsController.ListFriends,
|
||||
]);
|
||||
|
||||
// USER - REWARDS ---------------------------
|
||||
|
||||
//body: reward (object containing all rewards to give, doesnt exist in mongo db)
|
||||
app.post("/users/rewards/give/:userId", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(SERVER),
|
||||
UsersController.GiveReward,
|
||||
]);
|
||||
|
||||
//body: reward (ID of the reward to give already in mongo db), only SERVER can give repeating rewards
|
||||
app.post("/users/rewards/gain/:userId", app.post_limiter, [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(USER),
|
||||
AuthTool.isSameUserOr(SERVER),
|
||||
UsersController.GainReward,
|
||||
]);
|
||||
|
||||
// USER - STATS ---------------------------
|
||||
|
||||
app.get("/online", [
|
||||
UsersController.GetOnline
|
||||
]);
|
||||
|
||||
};
|
||||
352
users/users.tool.js
Normal file
352
users/users.tool.js
Normal file
@@ -0,0 +1,352 @@
|
||||
const config = require('../config.js');
|
||||
const crypto = require('crypto');
|
||||
const Email = require('../tools/email.tool');
|
||||
const AuthTool = require('../authorization/auth.tool');
|
||||
const DeckModel = require('../decks/decks.model');
|
||||
const Validator = require('../tools/validator.tool');
|
||||
const VariantModel = require('../variants/variants.model.js');
|
||||
|
||||
const UserTool = {};
|
||||
|
||||
UserTool.generateID = function(length, easyRead) {
|
||||
var result = '';
|
||||
var characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
if(easyRead)
|
||||
characters = 'abcdefghijklmnpqrstuvwxyz123456789'; //Remove confusing characters like 0 and O
|
||||
var charactersLength = characters.length;
|
||||
for ( var i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
UserTool.setUserPassword = (user, password) =>
|
||||
{
|
||||
user.password = AuthTool.hashPassword(password);
|
||||
user.password_recovery_key = ""; //After changing password, disable recovery until inited again
|
||||
user.refresh_key = crypto.randomBytes(16).toString('base64'); //Logout previous logins by changing the refresh_key
|
||||
}
|
||||
|
||||
//--------- Rewards -----------
|
||||
|
||||
UserTool.GainUserReward = async(user, reward) =>
|
||||
{
|
||||
//Add reward to user
|
||||
user.coins += reward.coins || 0;
|
||||
user.xp += reward.xp || 0;
|
||||
|
||||
UserTool.addAvatars(user, reward.avatars);
|
||||
UserTool.addCardbacks(user, reward.cardbacks);
|
||||
|
||||
//Add cards and decks
|
||||
var valid_c = await UserTool.addCards(user, reward.cards || []);
|
||||
var valid_p = await UserTool.addPacks(user, reward.packs || []);
|
||||
var valid_d = await UserTool.addDecks(user, reward.decks || []);
|
||||
return valid_c && valid_p && valid_d;
|
||||
};
|
||||
|
||||
//--------- Cards, Packs and Decks --------
|
||||
|
||||
//newCards is just an array of string (card tid), or an array of object {tid: "", quantity: 1}
|
||||
UserTool.addCards = async(user, newCards) =>
|
||||
{
|
||||
var cards = user.cards;
|
||||
|
||||
if(!Array.isArray(cards) || !Array.isArray(newCards))
|
||||
return false; //Wrong params
|
||||
|
||||
if(newCards.length == 0)
|
||||
return true; //No card to add, succeeded
|
||||
|
||||
//Count quantities
|
||||
var prevTotal = Validator.countQuantity(cards);
|
||||
var addTotal = Validator.countQuantity(newCards);
|
||||
|
||||
var variant_default = await VariantModel.getDefault();
|
||||
var default_tid = variant_default ? variant_default.tid : "";
|
||||
|
||||
//Loop on cards to add
|
||||
for (let c = 0; c < newCards.length; c++) {
|
||||
|
||||
var cardAdd = newCards[c];
|
||||
var cardAddTid = typeof cardAdd === 'object' ? cardAdd.tid : cardAdd;
|
||||
var cardAddVariant = typeof cardAdd === 'object' ? cardAdd.variant : default_tid;
|
||||
var cardAddQ = typeof cardAdd === 'object' ? cardAdd.quantity : 1;
|
||||
|
||||
if (cardAddTid && typeof cardAddTid === "string") {
|
||||
var quantity = cardAddQ || 1; //default is 1
|
||||
var found = false;
|
||||
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
if (cards[i].tid == cardAddTid && cards[i].variant == cardAddVariant) {
|
||||
cards[i].quantity += quantity;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
cards.push({
|
||||
tid: cardAddTid,
|
||||
variant: cardAddVariant,
|
||||
quantity: quantity,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Remove empty
|
||||
for(var i=cards.length-1; i>=0; i--)
|
||||
{
|
||||
var card = cards[i];
|
||||
if(!card.quantity || card.quantity <= 0)
|
||||
cards.splice(i, 1);
|
||||
}
|
||||
|
||||
//Validate quantities to make sure the array was updated correctly, this is to prevent users from loosing all their cards because of server error which would be terrible.
|
||||
var valid = Validator.validateArray(cards, prevTotal + addTotal);
|
||||
return valid;
|
||||
};
|
||||
|
||||
UserTool.addPacks = async (user, newPacks) => {
|
||||
|
||||
var packs = user.packs;
|
||||
|
||||
if(!Array.isArray(packs) || !Array.isArray(newPacks))
|
||||
return false; //Wrong params
|
||||
|
||||
if(newPacks.length == 0)
|
||||
return true; //No pack to add, succeeded
|
||||
|
||||
//Count quantities
|
||||
var prevTotal = Validator.countQuantity(packs);
|
||||
var addTotal = Validator.countQuantity(newPacks);
|
||||
|
||||
//Loop on packs to add
|
||||
for (let c = 0; c < newPacks.length; c++) {
|
||||
|
||||
var packAdd = newPacks[c];
|
||||
var packAddTid = typeof packAdd === 'object' ? packAdd.tid : packAdd;
|
||||
var packAddQ = typeof packAdd === 'object' ? packAdd.quantity : 1;
|
||||
|
||||
if (packAddTid && typeof packAddTid === "string") {
|
||||
var quantity = packAddQ || 1; //default is 1
|
||||
var found = false;
|
||||
|
||||
for (let i = 0; i < packs.length; i++) {
|
||||
if (packs[i].tid == packAddTid) {
|
||||
packs[i].quantity += quantity;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
packs.push({
|
||||
tid: packAddTid,
|
||||
quantity: quantity,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Remove empty
|
||||
for(var i=packs.length-1; i>=0; i--)
|
||||
{
|
||||
var pack = packs[i];
|
||||
if(!pack.quantity || pack.quantity <= 0)
|
||||
packs.splice(i, 1);
|
||||
}
|
||||
|
||||
//Validate quantities to make sure the array was updated correctly, this is to prevent users from loosing all their packs because of server error which would be terrible.
|
||||
var valid = Validator.validateArray(packs, prevTotal + addTotal);
|
||||
return valid;
|
||||
};
|
||||
|
||||
//newDecks is just an array of string (deck tid)
|
||||
UserTool.addDecks = async(user, newDecks) =>
|
||||
{
|
||||
var decks = user.decks;
|
||||
|
||||
if(!Array.isArray(decks) || !Array.isArray(newDecks))
|
||||
return false; //Wrong params
|
||||
|
||||
if(newDecks.length == 0)
|
||||
return true; //No deck to add, succeeded
|
||||
|
||||
var ndecks = await DeckModel.getList(newDecks);
|
||||
if(!ndecks)
|
||||
return false; //Decks not found
|
||||
|
||||
//Loop on cards to add
|
||||
for (let c = 0; c < ndecks.length; c++) {
|
||||
|
||||
var deckAdd = ndecks[c];
|
||||
var valid_c = await UserTool.addCards(user, deckAdd.cards);
|
||||
if(!valid_c)
|
||||
return false; //Failed adding cards
|
||||
|
||||
decks.push({
|
||||
tid: deckAdd.tid + "_" + UserTool.generateID(5),
|
||||
title: deckAdd.title || "",
|
||||
hero: deckAdd.hero || {},
|
||||
cards: deckAdd.cards || [],
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
UserTool.addAvatars = (user, avatars) =>
|
||||
{
|
||||
if(!avatars || !Array.isArray(avatars))
|
||||
return;
|
||||
|
||||
for (let i = 0; i < avatars.length; i++) {
|
||||
var avatar = avatars[i];
|
||||
if(avatar && typeof avatar === "string" && !user.avatars.includes(avatar))
|
||||
user.avatars.push(avatar);
|
||||
}
|
||||
};
|
||||
|
||||
UserTool.addCardbacks = (user, cardbacks) =>
|
||||
{
|
||||
if(!cardbacks || !Array.isArray(cardbacks))
|
||||
return;
|
||||
|
||||
for (let i = 0; i < cardbacks.length; i++) {
|
||||
var cardback = cardbacks[i];
|
||||
if(cardback && typeof cardback === "string" && !user.cardbacks.includes(cardback))
|
||||
user.cardbacks.push(cardback);
|
||||
}
|
||||
};
|
||||
|
||||
UserTool.hasCard = (user, card_id, variant_id, quantity) =>
|
||||
{
|
||||
for (let c = 0; c < user.cards.length; c++) {
|
||||
var acard = user.cards[c];
|
||||
var aquantity = acard.quantity || 1;
|
||||
if(acard.tid == card_id && acard.variant == variant_id && aquantity >= quantity)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
UserTool.hasPack = (user, card_tid, quantity) =>
|
||||
{
|
||||
for (let c = 0; c < user.packs.length; c++) {
|
||||
var apack = user.packs[c];
|
||||
var aquantity = apack.quantity || 1;
|
||||
if(apack.tid == card_tid && aquantity >= quantity)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
UserTool.hasAvatar = (user, avatarId) =>
|
||||
{
|
||||
return user.avatars.includes(avatarId);
|
||||
}
|
||||
|
||||
UserTool.hasCardback = (user, cardbackId) =>
|
||||
{
|
||||
return user.cardbacks.includes(cardbackId);
|
||||
}
|
||||
|
||||
UserTool.getDeck = (user, deck_tid) =>
|
||||
{
|
||||
var deck = {};
|
||||
if(user && user.decks)
|
||||
{
|
||||
for(var i=0; i<user.decks.length; i++)
|
||||
{
|
||||
var adeck = user.decks[i];
|
||||
if(adeck.tid == deck_tid)
|
||||
{
|
||||
deck = adeck;
|
||||
}
|
||||
}
|
||||
}
|
||||
return deck;
|
||||
};
|
||||
|
||||
UserTool.getData = (all_data, tid) =>
|
||||
{
|
||||
for(var i=0; i<all_data.length; i++)
|
||||
{
|
||||
if(all_data[i].tid == tid)
|
||||
return all_data[i];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
//--------- Emails --------
|
||||
|
||||
UserTool.sendEmailConfirmKey = (user, email, email_confirm_key) => {
|
||||
|
||||
if(!email || !user) return;
|
||||
|
||||
var subject = config.api_title + " - Email Confirmation";
|
||||
var http = config.allow_https ? "https://" : "http://";
|
||||
var confirm_link = http + config.api_url + "/users/email/confirm/" + user.id + "/" + email_confirm_key;
|
||||
|
||||
var text = "Hello " + user.username + "<br>";
|
||||
text += "Welcome! <br><br>";
|
||||
text += "To confirm your email, click here: <br><a href='" + confirm_link + "'>" + confirm_link + "</a><br><br>";
|
||||
text += "Thank you and see you soon!<br>";
|
||||
|
||||
Email.SendEmail(email, subject, text, function(result){
|
||||
console.log("Sent email to: " + email + ": " + result);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
UserTool.sendEmailChangeEmail = (user, email, new_email) => {
|
||||
|
||||
if(!email || !user) return;
|
||||
|
||||
var subject = config.api_title + " - Email Changed";
|
||||
|
||||
var text = "Hello " + user.username + "<br>";
|
||||
text += "Your email was succesfully changed to: " + new_email + "<br>";
|
||||
text += "If you believe this is an error, please contact support immediately.<br><br>"
|
||||
text += "Thank you and see you soon!<br>";
|
||||
|
||||
Email.SendEmail(email, subject, text, function(result){
|
||||
console.log("Sent email to: " + email + ": " + result);
|
||||
});
|
||||
};
|
||||
|
||||
UserTool.sendEmailChangePassword = (user, email) => {
|
||||
|
||||
if(!email || !user) return;
|
||||
|
||||
var subject = config.api_title + " - Password Changed";
|
||||
|
||||
var text = "Hello " + user.username + "<br>";
|
||||
text += "Your password was succesfully changed<br>";
|
||||
text += "If you believe this is an error, please contact support immediately.<br><br>"
|
||||
text += "Thank you and see you soon!<br>";
|
||||
|
||||
Email.SendEmail(email, subject, text, function(result){
|
||||
console.log("Sent email to: " + email + ": " + result);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
UserTool.sendEmailPasswordRecovery = (user, email) => {
|
||||
|
||||
if(!email || !user) return;
|
||||
|
||||
var subject = config.api_title + " - Password Recovery";
|
||||
|
||||
var text = "Hello " + user.username + "<br>";
|
||||
text += "Here is your password recovery code: " + user.password_recovery_key.toUpperCase() + "<br><br>";
|
||||
text += "Thank you and see you soon!<br>";
|
||||
|
||||
Email.SendEmail(email, subject, text, function(result){
|
||||
console.log("Sent email to: " + email + ": " + result);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = UserTool;
|
||||
66
variants/variants.controller.js
Normal file
66
variants/variants.controller.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const VariantModel = require("./variants.model");
|
||||
|
||||
exports.AddVariant = async(req, res) =>
|
||||
{
|
||||
var tid = req.body.tid;
|
||||
var cost_factor = req.body.cost_factor || 1;
|
||||
var is_default = req.body.is_default || false;
|
||||
|
||||
if(!Number.isInteger(cost_factor))
|
||||
return res.status(400).send({ error: "Invalid parameters" });
|
||||
|
||||
var data = {
|
||||
tid: tid,
|
||||
cost_factor: cost_factor,
|
||||
is_default: is_default,
|
||||
}
|
||||
|
||||
//Update or create
|
||||
var variant = await VariantModel.get(tid);
|
||||
if(variant)
|
||||
variant = await VariantModel.update(variant, data);
|
||||
else
|
||||
variant = await VariantModel.create(data);
|
||||
|
||||
if(!variant)
|
||||
return res.status(500).send({error: "Error updating variant"});
|
||||
|
||||
return res.status(200).send(data);
|
||||
};
|
||||
|
||||
exports.DeleteVariant = async(req, res) =>
|
||||
{
|
||||
VariantModel.remove(req.params.tid);
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.DeleteAll = async(req, res) =>
|
||||
{
|
||||
VariantModel.removeAll();
|
||||
return res.status(204).send({});
|
||||
};
|
||||
|
||||
exports.GetVariant = async(req, res) =>
|
||||
{
|
||||
var tid = req.params.tid;
|
||||
|
||||
if(!tid)
|
||||
return res.status(400).send({error: "Invalid parameters"});
|
||||
|
||||
var variant = await VariantModel.get(tid);
|
||||
if(!variant)
|
||||
return res.status(404).send({error: "Variant not found: " + tid});
|
||||
|
||||
return res.status(200).send(variant.toObj());
|
||||
};
|
||||
|
||||
exports.GetAll = async(req, res) =>
|
||||
{
|
||||
var variants = await VariantModel.getAll();
|
||||
|
||||
for(var i=0; i<variants.length; i++){
|
||||
variants[i] = variants[i].toObj();
|
||||
}
|
||||
|
||||
return res.status(200).send(variants);
|
||||
};
|
||||
103
variants/variants.model.js
Normal file
103
variants/variants.model.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const variantsSchema = new Schema({
|
||||
|
||||
tid: { type: String, index: true, unique: true },
|
||||
cost_factor: {type: Number, default: 1}, //Cost multiplier
|
||||
is_default: {type: Boolean, default: false},
|
||||
});
|
||||
|
||||
variantsSchema.methods.toObj = function() {
|
||||
var elem = this.toObject();
|
||||
delete elem.__v;
|
||||
delete elem._id;
|
||||
return elem;
|
||||
};
|
||||
|
||||
const Variant = mongoose.model("Variants", variantsSchema);
|
||||
exports.Variant = Variant;
|
||||
|
||||
exports.create = async(data) => {
|
||||
|
||||
try{
|
||||
var variant = new Variant(data);
|
||||
return await variant.save();
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.get = async(variant_tid) => {
|
||||
|
||||
try{
|
||||
var variant = await Variant.findOne({tid: variant_tid});
|
||||
return variant;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getDefault = async() => {
|
||||
|
||||
try{
|
||||
var variant = await Variant.findOne({is_default: true});
|
||||
return variant;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAll = async() => {
|
||||
|
||||
try{
|
||||
var variants = await Variant.find({});
|
||||
return variants;
|
||||
}
|
||||
catch{
|
||||
return [];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.update = async(variant, data) => {
|
||||
|
||||
try{
|
||||
if(!variant) return null;
|
||||
|
||||
for (let i in data) {
|
||||
variant[i] = data[i];
|
||||
variant.markModified(i);
|
||||
}
|
||||
|
||||
var updated = await variant.save();
|
||||
return updated;
|
||||
}
|
||||
catch{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = async(variant_tid) => {
|
||||
|
||||
try{
|
||||
var result = await Variant.deleteOne({tid: variant_tid});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.removeAll = async() => {
|
||||
try{
|
||||
var result = await Variant.deleteMany({});
|
||||
return result && result.deletedCount > 0;
|
||||
}
|
||||
catch{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
36
variants/variants.routes.js
Normal file
36
variants/variants.routes.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const config = require("../config");
|
||||
const VariantsController = require("./variants.controller");
|
||||
const AuthTool = require("../authorization/auth.tool");
|
||||
|
||||
const ADMIN = config.permissions.ADMIN; //Highest permision, can read and write all users
|
||||
const SERVER = config.permissions.SERVER; //Higher permission, can read all users
|
||||
const USER = config.permissions.USER; //Lowest permision, can only do things on same user
|
||||
|
||||
exports.route = (app) => {
|
||||
|
||||
app.get("/variants", [
|
||||
VariantsController.GetAll
|
||||
]);
|
||||
|
||||
app.get("/variants/:tid", [
|
||||
VariantsController.GetVariant
|
||||
]);
|
||||
|
||||
app.post("/variants/add", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
VariantsController.AddVariant
|
||||
]);
|
||||
|
||||
app.delete("/variants/:tid", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
VariantsController.DeleteVariant
|
||||
]);
|
||||
|
||||
app.delete("/variants", [
|
||||
AuthTool.isValidJWT,
|
||||
AuthTool.isPermissionLevel(ADMIN),
|
||||
VariantsController.DeleteAll
|
||||
]);
|
||||
};
|
||||
Reference in New Issue
Block a user