sicambria/skilltree

View on GitHub
app.js

Summary

Maintainability
F
3 wks
Test Coverage
const fs = require('fs');
const http = require('http');
const path = require('path');
const express = require('express');
const bodyParser  = require('body-parser');
const morgan      = require('morgan');
const mongoose    = require('mongoose');
const jwt    = require('jsonwebtoken'); // used to create, sign, and verify tokens

var config = require('./config'); // get our config file
var Category   = require('./models/categorymodel');
var User   = require('./models/usermodel'); // get our mongoose model
var Tree = require('./models/treemodel');
var ApprovableTree = require('./models/treesforapprovemodel')
var Skill = require('./models/skillmodel');
var ApprovableSkill = require('./models/skillsforapprovemodel');
var ApprovableTraining = require('./models/trainingsforapprovemodel');
var pbkdf2 = require('./pbkdf2'); // get hash generator and pw checker

const app = express();

mongoose.connect(config.database); // connect to database
app.set('superSecret', config.secret);

// use body parser so we can get info from POST and/or URL parameters
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// use morgan to log requests to the console
app.use(morgan('dev'));

// serving static files and opening login.html
app.use(express.static('./public'));
app.get('/', (req, res) => res.sendFile('login.html', { root: path.join(__dirname, './public') }));
app.get('/user', (req, res) => res.sendFile('chartandtree.html', { root: path.join(__dirname, './public/user') }));

app.get('/apitest', async function(req,res) {
    res.json({
        success: true
    });
});


app.post('/registration', async function(req, res) {
    // search for username in db
    var user = await findUser(req.body.username);

    if (!user) { // if new user
        var hashData = pbkdf2.hashPassword(req.body.password);

        var focusAreaTrees = await Tree.find({focusArea: req.body.focusArea}, {_id: 0, name: 1}, function (err, trees) {
                            if (err) throw err;
                            return trees;
                        });

        for (var i = 0; i < focusAreaTrees.length; ++i) {
            focusAreaTrees[i] = focusAreaTrees[i].name; //    lehet enelkul?
        }

        // get all categories from db
        var categories = await Category.find({}, function (err, categories) {
                            if (err) throw err;
                            return categories;
                        });

        var newUser = new User({
            username: req.body.username,
            email: req.body.email,
            hashData: hashData,
            categories: categories,
            /*focusArea: {
                    name: req.body.focusArea,
                    treeNames: focusAreaTrees,
                },
            willingToTeach: req.body.willingToTeach*/
        });

        newUser.save(function(err) {
            if (err) throw err;

            const payload = {
                username: req.body.username,
            };
            var token = jwt.sign(payload, app.get('superSecret'), {
                expiresIn: '1d' // expires in 1 hour
            });

            // return the information including token as JSON
            res.json({
                success: true,
                token: token,
            });
        });
    } else { // if user already exists
        res.json({
            success: false
        });
    }
});

app.post('/auth', function(req, res) {
    // find the user
    User.findOne({
        username: req.body.username
    }, function(err, user) {
        if (err) throw err;

        if (!user) {
            res.json({
                success: false,
                message: 'Authentication failed. User not found.'
            });
        } else if (user) {
            // check password
            if (!pbkdf2.verifyPassword(req.body.password, user.hashData)) {
                res.json({
                    success: false,
                    message: 'Authentication failed. Wrong password.'
                });
            } else {
                // if user is found and password is right
                // create a token with only our given payload
                // we don't want to pass in the entire user since that has the password
                const payload = {
                    username: req.body.username,
                    admin: user.admin
                };
                var token = jwt.sign(payload, app.get('superSecret'), {
                    expiresIn: '1d' // expires in 1 hour
                });

                // return the information including token as JSON
                res.json({
                    success: true,
                    token: token,
                    message: "Authenticated.",
                });
            }
        }
    });
});


//Creating a getRoute thats protected with token, API calls are under /get/...
var protectedRoute = express.Router();
app.use('/protected', protectedRoute);


protectedRoute.use(function(req, res, next) {
    var token = req.get('x-access-token');

    // decode token
    if (token) {
        // verifies secret and checks exp
        jwt.verify(token, app.get('superSecret'), function(err, decoded) {
            if (err) {
                return res.sendFile('login.html', { root: path.join(__dirname, './public') });
            } else {
                req.decoded = decoded;
                next();
            }
        });

    } else {
        return res.sendFile('login.html', { root: path.join(__dirname, './public') });
    }
});

protectedRoute.use(express.json());

protectedRoute.get('/userdata', function (req, res) {
    User.findOne({
        username: req.decoded.username
    }, async function(err, userdata) {
        if (err) throw err;

        if (!userdata) {
            res.json({
                success: false,
                message: 'User not found.'
            });
        } else if (userdata) {
            user = userdata.toObject();
            delete user.__v;
            delete user._id;
            //delete user.email;
            delete user.hashData;

            if (user.mainTree == undefined) { // first login
                var trees = await Tree.find({}, function (err, trees) {
                                    if (err) throw err;
                                    return trees;
                                });

                user.allTreeNames = trees;
            }

            if (user.admin) {
                var trees = await ApprovableTree.find({}, function(err, trees) {
                    if (err) throw err;
                    return trees;
                });

                var skills = await ApprovableSkill.find({}, function(err, skills) {
                    if (err) throw err;
                    return skills;
                });

                var trainings = await ApprovableTraining.find({}, function(err, trainings) {
                    if (err) throw err;
                    return trainings;
                });
                user.apprTrees = trees;
                user.apprSkills = skills;
                user.apprTrainings = trainings;
            }
              return res.json(user);
          }
    });
});

// getting the skilldata of a skillname (used for offers)
protectedRoute.get('/offers', function(req, res) {
    Skill.findOne({
        name: req.body.name
    }, async function(err, skilldata) {
        if(err) throw err;

        if(!skilldata){
            escape.json({
                success: false,
                message: 'User not found.'
            });
        } else if (skilldata) {
            skill = skilldata.toObject();

            return res.json(skilldata);
        }
    });
});

// getting the skilldata of a skillname (used for offers)
protectedRoute.get('/skillsforapproval', function(req, res) {
    ApprovableSkill.find({} , async function(err, skillsforapproval) {
        if(err) throw err;

        if(skillsforapproval !== undefined){
                return res.json(skillsforapproval);
        } else
            {
                res.json({
                    success: false
                });
        }
    });
});

// Search for users to view by name
protectedRoute.post('/searchUsersByName', async function (req, res) {
        var data = req.body;
        var foundUsers = await User.find({
                    "username": {$regex : ".*" + data.value + ".*", '$options' : 'i'}
            }, function (err, user) {
                    if (err) throw err;
            return user;
        });
        var resUsers = [];
        for (var i = 0; i < foundUsers.length; i++) {
            resUsers[i] = {name: foundUsers[i].username};
        }
        res.json(resUsers);
});

// searchkes a skill for editor
protectedRoute.post('/searchSkillsByName', async function (req, res) {
        var data = req.body;

        var user = await findUser(req.decoded.username);

        user = user.toObject();
        var foundUserSkills = user.skills.filter(obj => obj.name.match(new RegExp(".*" + data.value + ".*", "i")) != null);

        var foundGlobalSkills = await Skill.find({
            "name": {$regex : ".*" + data.value + ".*", '$options' : 'i'}
        }, function (err, skills) {
            if (err) throw err;
            return skills;
        });

        var resSkills = [];
        for (var i = 0; foundUserSkills != undefined && i < foundUserSkills.length; i++) {
            resSkills.push({name: foundUserSkills[i].name});
        }
        for (var i = 0; i < foundGlobalSkills.length; i++) {
            if (resSkills.find(obj => obj == foundGlobalSkills[i].name) == undefined) resSkills[i] = {name: foundGlobalSkills[i].name};
        }
        res.json(resSkills);
});

// searchkes a skill for editor
protectedRoute.post('/searchUserSkillsByName', async function (req, res) {
        var data = req.body;

        var user = await findUser(req.decoded.username);

        user = user.toObject();
        var foundUserSkills = user.skills.filter(obj => obj.name.match(new RegExp(".*" + data.value + ".*", "i")) != null);

        var resSkills = [];
        for (var i = 0; foundUserSkills != undefined && i < foundUserSkills.length; i++) {
            resSkills.push({name: foundUserSkills[i].name});
        }

        res.json(resSkills);
});

// Search for trees to add while typing
protectedRoute.post('/searchTreesByName', async function (req, res) {
        var data = req.body;
        var foundTrees = await Tree.find({
                    "name": {$regex : ".*" + data.value + ".*", '$options' : 'i'}
            }, function (err, tree) {
                    if (err) throw err;
            return tree;
        });
        var resTrees = [];
        for (var i = 0; i < foundTrees.length; i++) {
            resTrees[i] = {name: foundTrees[i].name};
        }
        res.json(resTrees);
});


// Getting the name, willingToTeach, teachingDay, teachingTime, location, focusArea, skills and maintree of a user.
protectedRoute.post('/getPublicUserData', async function (req, res) {
        var data = req.body;
    var foundUsers = await User.find({
                    "username": {$regex : ".*" + data.value + ".*", '$options' : 'i'}
            }, 'username mainTree willingToTeach teachingDay teachingTime location focusArea skills trees', function (err, user) {
                    if (err) throw err;
            return user;
        });
        res.json(foundUsers);
});

// Getting the name, skillnames, focusarea of a tree.
protectedRoute.post('/getPublicTreeData', async function (req, res) {
        var data = req.body;
    var foundTrees = await Tree.find({
                    "name": {$regex : ".*" + data.value + ".*", '$options' : 'i'}
            }, function (err, tree) {
                    if (err) throw err;
            return tree;
        });
    res.json(foundTrees);
});

// Getting the name, caterory, descs of a skill, and give a list of the people, who have it.
protectedRoute.post('/getPublicSkillData', async function (req, res) {
    var data = req.body;
        var outData = []; // this is required, because adding a new param to a queried mongo object bugs rn.
        var outUsers = []; // array of users that have the skill.
    var foundSkills = await Skill.find({
                "name": {$regex : ".*" + data.value + ".*", '$options' : 'i'}
        }, 'name categoryName description descriptionWikipediaURL pointDescription', function(err, skill) {
                if (err) throw err;
                return skill;
        });
        for (var s = 0; s < foundSkills.length; s++) {
            foundSkills[s].users = [];
            var foundUsers = await User.find({}, 'username skills', function(err, user) {
                return user;
            });
            outUsers = [];
            for (var i = 0; i < foundUsers.length; i++) {
                if (foundUsers[i].skills.map(obj => obj.name).includes(foundSkills[s].name)) {
                    outUsers.push({username: foundUsers[i].username, skill: foundUsers[i].skills.find(obj => obj.name == foundSkills[s].name)});
                }
            }
            outData.push({
                name: foundSkills[s].name,
                categoryName: foundSkills[s].categoryName,
                description: foundSkills[s].description,
                descriptionWikipediaURL: foundSkills[s].descriptionWikipediaURL,
                pointDescription: foundSkills[s].pointDescription,
                users: outUsers
            });
        }
    res.json(outData);
});

// Adds a public tree to the current user.
protectedRoute.post('/addTreeToUser', async function (req, res){
    var data = req.body;
    var user = await findUser(req.decoded.username);
    var tree = await Tree.findOne({"name": data.value},  function (err, tree) {
        if (err) throw err;
        return tree;
    });

    if (tree != undefined) {
        if (user.trees.find(obj => obj.name == tree.name) == undefined){
            await sortAndAddTreeToUser(tree, user);

            res.json({
                success: true,
                name: tree.name
            });
        } else {
            res.json({
                message: "existing",
                success: false
            });
        }
    } else {
        res.json({
            message: "notfound",
            success: false
        });
    }
});

// gets a skill and all it's dependencies by name.
protectedRoute.post('/getskill', async function (req, res) {
    var data = req.body;

    var user = await findUser(req.decoded.username);

    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    } else {
        var skill = user.skills.find(obj => obj.name == data.value);

        if (skill == undefined) {
          var skill = await findSkillByName(data.value);
            if (!skill) {
                res.json({
                    success: false
                });
            }
        }

        var dependency = [];
        await getDependency(user.skills, skill, dependency);

        res.json({
            success: true,
            skill: skill,
            dependency: dependency
        });
    }
});

// recursive function for dependency determinating
async function getDependency (userSkills, skill, dependency) {
    var parents = [];
    for (var i = 0; skill.parents != undefined && i < skill.parents.length; ++i) {
        var parent = userSkills.find(obj => obj.name == skill.parents[i]);
        if (parent == undefined) {
            parent = await findSkillByName(skill.parents[i]);
        }

        parents.push(parent);
        dependency.push(parent);
    }

    for (var i = 0; i < parents.length; ++i) {
        await getDependency(userSkills, parents[i], dependency);
    }
}

async function insertSkill(skillToInsert, skillMatrix) {
    for (var component = 0; component < skillMatrix.length; component++) {
        for (var child = 0; child < skillToInsert.children.length; child++) {
            for (var row = 0; row < skillMatrix[component].length; row++) {
                if ((skillMatrix[component][row].map(obj => obj.name)).includes(skillToInsert.children[child].name)) {
                    if (row == 0) {
                        await addRowToComponent(skillMatrix, component);
                        skillMatrix[component][0].push(skillToInsert);
                        // console.log(skillToInsert.name + " added to root(skills child found in tree)"); // for debugging reasons
                        // this checks if the row found was the root level.
                        // if yes, it adds another row to the top, and inserts the skill there.
                        return;
                    }
                    else {
                        skillMatrix[component][row - 1].push(skillToInsert);
                        // console.log(skillToInsert.name + " added to the row above(skills child found in tree)"); // for debugging reasons
                        // if no, it inserts the skill to the row above.
                        return;
                    }
                }
            }
        }
        for (var par = 0; par < skillToInsert.parents.length; par++) {
            for (var row = 0; row < skillMatrix[component].length; row++) {
                if ((skillMatrix[component][row].map(obj => obj.name)).includes(skillToInsert.parents[par])) {
                    if (skillMatrix[component][row + 1] == undefined) skillMatrix[component].push([]);
                    skillMatrix[component][row + 1].push(skillToInsert);
                    // console.log(skillToInsert.name + " added to the row below(skills parent found in tree.)"); // for debugging reasons
                    // this checks if the skill has any parents in any row in any component,
                    // if yes, then it inserts the skill to the row below.
                    return;
                }
            }
        }
    }
    skillMatrix.push([[skillToInsert]]);
    // console.log(skillToInsert.name + " added to a new component."); // for debugging reasons
    // this inits the first element of every component
    return;
}

async function addRowToComponent(skillMatrix, component){
    skillMatrix[component].push([]);
    for (var i = skillMatrix[component].length - 2; i >= 0; i--) {
        skillMatrix[component][i + 1] = skillMatrix[component][i];
    }
    skillMatrix[component][0] = [];
}

async function assembleTree(skillMatrix){
    var assembledTree = [];
    var l = true;
    var j = 0;
    while (l) {
        l = false;
        for (var component = 0; component < skillMatrix.length; component++) {
            if (skillMatrix[component][j] != undefined) {
                l = true;
                assembledTree = assembledTree.concat(skillMatrix[component][j]);
            }
        }
        j++;
    }
    return assembledTree;
}

// gets the skillnames of a skillarray.
async function extractNames(skillArray){
    return skillArray.map(obj => obj.name);
}

// creates an ordered tree from an array of skills.
async function sortTree(skillArray){
    var sortedArray = []; // output array
    var skillMatrix = []; // 3d array, represents the components, the rows, and the elements of rows in the graph.
    for (var i = 0; i < skillArray.length; i++) {
        await insertSkill(skillArray[i], skillMatrix);
    }
    sortedArray = await assembleTree(skillMatrix);
    skillArray = await extractNames(sortedArray);
    return skillArray;
}

async function sortAndAddTreeToUser(treeToSort, user){
    var skills = await Skill.find({
        name: treeToSort.skillNames,
    }, function (err, skills) {
        if (err) throw err;
        return skills;
    });

    var sn = await sortTree(skills);
    user.trees.push({
        name: treeToSort.name,
        focusArea: treeToSort.focusArea,
        description: treeToSort.description,
        skillNames: sn
    });

    await skills.forEach(function (skill) {
        skill.achievedPoint = 0;
        if (user.skills.find(obj => obj.name == skill.name) == undefined){
            user.skills.push(skill);
        }});

    user.save(function (err) {if (err) throw err;});
    return;
}

protectedRoute.post('/newtraining', async function(req, res) {
    var data = req.body;

    var user = await findUser(req.decoded.username);

    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    } else if (user.skills.find(obj => obj.name == data.skillName) != undefined) {
        for (var i = 0; i < data.trainings.length; ++i) {
            user.skills.find(obj => obj.name == data.skillName).trainings.push({
                name: data.trainings[i].name,
                level: data.trainings[i].level,
                shortDescription: data.trainings[i].shortDescription,
                URL: data.trainings[i].URL,
                goal: data.trainings[i].goal,
                length: data.trainings[i].length,
                language: data.trainings[i].language
            });
        }

        user.save(function (err) {if (err) throw err;});

        for (var i = 0; i < data.trainings.length; ++i) {
            var apprTraining = new ApprovableTraining({
                username: user.username,
                skillName: data.skillName,
                name: data.trainings[i].name,
                level: data.trainings[i].level,
                shortDescription: data.trainings[i].shortDescription,
                URL: data.trainings[i].URL,
                goal: data.trainings[i].goal,
                length: data.trainings[i].length,
                language: data.trainings[i].language
            });

            apprTraining.save(function (err) {if (err) throw err;});
        }

        res.json({
            success: true
        });
    } else {
        res.json({
            success: false,
            message: 'skillnotexists'
        });
    }
});

// Creates a skill from the data the user added.
protectedRoute.post('/newskill', async function(req, res) {
    var data = req.body;

    var user = await findUser(req.decoded.username);

    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    } else if (user.skills.find(obj => obj.name == data.name) == undefined) {
        var parentNames = [];
        for (var i = 0; i < data.parents.length; ++i) {
            if (user.skills.find(obj => obj.name == data.parents[i].name) == undefined) { // add parent skill to user if not already there
                var parent = await findSkillByName(data.parents[i].name);
                user.skills.push(parent);
            }
            // add new skill as child of parent skill
            user.skills.find(obj => obj.name == data.parents[i].name).children.push({name: data.name, minPoint: data.parents[i].minPoint, recommended: data.parents[i].recommended});
            // when we save the new skill we only need the name of the parents (no minPoint and recommended)
            parentNames.push(data.parents[i].name);
        }

                user.skills.push({
              name: data.name,
              description: data.description,
                    descriptionWikipediaURL: data.descriptionWikipediaURL,
              skillIcon: data.skillIcon,
              categoryName: data.categoryName,
              achievedPoint: 0,
              maxPoint: data.maxPoint,
              pointDescription: data.pointDescription,
              parents: parentNames,
              //children: data.children,
              trainings: data.trainings
          });

        /*for (var i = 0; i < data.children.length; ++i) {
            user.skills.find(obj => obj.name == data.children[i].name).parents.push(data.name);
        }*/

        user.save(function (err) {if (err) throw err;});

        for (var i = 0; i < data.parents.length; ++i) {
            var apprParent = await ApprovableSkill.find({
                name: data.parents[i].name
            }, function (err, skill) {
                if (err) throw err;
                return skill;
            });

            if (apprParent != undefined) {
                apprParent.children.push({name: data.name, minPoint: data.parents[i].minPoint, recommended: data.parents[i].recommended});
                apprParent.save(function (err) {if (err) throw err;});
            }
        }

        var apprSkill = new ApprovableSkill({
            username: user.username,
            name: data.name,
            description: data.description,
            descriptionWikipediaURL: data.descriptionWikipediaURL,
            skillIcon: data.skillIcon,
            categoryName: data.categoryName,
            maxPoint: data.maxPoint,
            pointDescription: data.pointDescription,
            parents: parentNames,
            children: data.children,
            trainings: data.trainings
        });

        apprSkill.save(function (err) {if (err) throw err;});

        res.json({
            success: true
        });
    } else {
        res.json({
            success: false,
            message: 'skillexists'
        });
    }
});

// creates a new tree only for the user.
protectedRoute.post('/newtree', async function (req, res) {
    var data = req.body;
    var user = await findUser(req.decoded.username);
    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    }
    else if (user.trees.find(obj => obj.name == data.name) == undefined) {
        var sn = await sortTree(data.skills);
        user.trees.push({
                name: data.name,
                focusArea: data.focusArea,
                description: data.description,
                skillNames: sn
            });

        await data.skills.forEach(async function (skill) {

        skill.achievedPoint = 0;
        if (user.skills.find(obj => obj.name == skill.name) == undefined) {
            user.skills.push(skill);
        }
        });

        user.save(function (err) {if (err) throw err;});

        var tree = new ApprovableTree({
            name: data.name,
            username: user.username,
            focusArea: data.focusArea,
            description: data.description,
            skillNames: sn
        });

        tree.save(function (err) {if (err) throw err;});

        res.json({
            success: true
        });
    }
    else {
        res.json({
            success: false,
            message: 'treeexists'
        });
    }
});

// creates a new tree only for the user.
protectedRoute.post('/editmytree', async function (req, res) {
    var data = req.body;
    var user = await findUser(req.decoded.username);

    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    } else if (user.trees.find(obj => obj.name == data.name) != undefined) {
        var sn = await sortTree(data.skills);
        user.trees = user.trees.filter(obj => obj.name != data.name);
        user.trees.push({
            name: data.name,
            focusArea: data.focusArea,
            description: data.description,
            skillNames: sn
        });

        await data.skills.forEach(async function (skill) {
            if (skill.achievedPoint == undefined) skill.achievedPoint = 0;
            if (user.skills.find(obj => obj.name == skill.name) == undefined) {
                user.skills.push(skill);
            }
        });

        user.save(function (err) {if (err) throw err;});

        res.json({
            success: true
        });
    } else {
        res.json({
            success: false,
            message: 'tree not exists'
        });
    }
});

protectedRoute.post('/editmyskill', async function (req, res) {
    var data = req.body;
    var user = await findUser(req.decoded.username);

    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    } else if (user.skills.find(obj => obj.name == data.name) != undefined) {
        var skill = (user.skills.find(obj => obj.name == data.name));

        for (var i = 0; i < skill.parents.length; ++i) user.skills.find(obj => obj.name == skill.parents[i]).children = user.skills.find(obj => obj.name == skill.parents[i]).children.filter(obj => obj.name != skill.name);
        for (var i = 0; i < skill.children.length; ++i) {
            if (user.skills.find(obj => obj.name == skill.children[i].name) != undefined) user.skills.find(obj => obj.name == skill.children[i].name).parents = user.skills.find(obj => obj.name == skill.children[i].name).parents.filter(obj => obj != skill.name);
        }

        var parentNames = [];
        for (var i = 0; i < data.parents.length; ++i) {
            if (user.skills.find(obj => obj.name == data.parents[i].name) == undefined) { // add parent skill to user if not already there
                var parent = await findSkillByName(data.parents[i].name);
                user.skills.push(parent);
            }
            // add new skill as child of parent skill
            user.skills.find(obj => obj.name == data.parents[i].name).children.push({name: data.name, minPoint: data.parents[i].minPoint, recommended: data.parents[i].recommended});
            // when we save the new skill we only need the name of the parents (no minPoint and recommended)
            parentNames.push(data.parents[i].name);
        }

        for (var i = 0; i < data.children.length; ++i) {
            if (user.skills.find(obj => obj.name == data.children[i].name) == undefined) { // add parent skill to user if not already there
                var child = await findSkillByName(data.child[i].name);
                user.skills.push(child);
            }
            // add new skill as child of parent skill
            user.skills.find(obj => obj.name == data.children[i].name).parents.push(data.name);

            var trees = user.trees.filter(obj => obj.skillNames.find(obj => obj == data.children[i].name) != undefined);
            for (var j = 0; j < trees.length; ++j) {
                var skillList = trees[j].skillNames;
                if (skillList.find(obj => obj == data.name) == undefined) skillList.push(data.name);
                var skillsToSort = [];
                for (var k = 0; k < skillList.length; ++k) skillsToSort = user.skills.filter(obj => skillList.find(obj2 => obj2 == obj.name) != undefined);
                var sn = await sortTree(skillsToSort);
                user.trees.find(obj => obj.name == trees[j].name).skillNames = sn;
            }
        }

                skill.name = data.name;
                skill.description = data.description;
                skill.descriptionWikipediaURL = data.descriptionWikipediaURL;
                skill.skillIcon = data.skillIcon;
                skill.categoryName = data.categoryName;
                skill.maxPoint = data.maxPoint;
                skill.pointDescription = data.pointDescription;
                skill.parents = parentNames;
                skill.children = data.children;
                skill.trainings = data.trainings;

        if (data.maxPoint < skill.achievedPoint) skill.achievedPoint = data.maxPoint;

        //user.skills.find(obj => obj.name == data.name) = skill;
        user.save(function (err) {if (err) throw err;});

        res.json({
            success: true
        });
    } else {
        res.json({
            success: false,
            message: 'skill not exists'
        });
    }
});

protectedRoute.post('/deletemytree', async function (req, res) {
    var data = req.body;
    var user = await findUser(req.decoded.username);

    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    } else if (user.trees.find(obj => obj.name == data.name) != undefined) {
        user.trees = user.trees.filter(obj => obj.name != data.name);
        user.save(function (err) {if (err) throw err;});

        res.json({
            success: true
        });
    } else {
        res.json({
            success: false,
            message: 'tree not exists'
        });
    }
});

// Search for trees to add while typing
protectedRoute.post('/gettree', async function (req, res) {
        var data = req.body;
        var foundTree = await Tree.findOne({
                    "name": data.name
            }, function (err, tree) {
                    if (err) throw err;
            return tree;
        });
        res.json(foundTree);
});

// add skill to user tree
protectedRoute.post('/addskilltotree', async function(req, res) {
    var data = req.body;

    var user = await findUser(req.decoded.username);

    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    } else {
        user.trees.find(obj => obj.name == data.treeName).skillNames.push(data.name);
        user.save(function (err) {if (err) throw err;});
    }
});

// gets the skilldata of a skillname
protectedRoute.post('/skilldata', function(req, res) {
    Skill.findOne({
        name: req.body.name
    }, async function(err, skilldata) {
        if(err) throw err;

        if(!skilldata){
            escape.json({
        // FIXME
                succes: false,
                message: 'Skill not found.'
            });
        } else if (skilldata) {
            //skill = skilldata.toObject();

            return res.json(skilldata);
        }
    });
});

// sets the user data aquired from the first login
protectedRoute.post('/firstlogindata', async function (req, res) {
    var data = req.body;

    var user = await findUser(req.decoded.username);

    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    } else {
        user.focusArea.name = data.focusArea;
        user.mainTree = data.mainTree;

        var mainTree = await Tree.findOne({
            name: user.mainTree,
        }, function (err, tree) {
            if (err) throw err;
            return tree;
        });

        await sortAndAddTreeToUser(mainTree, user);

        res.json({
            success: true,
        });
    }
});

// submits all changes made by the user to the skills.
protectedRoute.post('/submitall', async function (req, res) {
    var data = req.body;

    var user = await findUser(req.decoded.username);

    if (!user) {
        res.json({
            success: false,
            message: 'User not found.'
        });
    } else {
        for (var i = 0; i < data.length; ++i) user.skills[i].achievedPoint = data[i].achievedPoint;

        if (user.willingToTeach) {
            var globalSkills = await Skill.find({}, function(err, skills) {
                if (err) throw err;
                return skills;
            });

            data.forEach(function (userSkill) {
                var globalSkill = globalSkills.find(obj => obj.name == userSkill.name);
                if (globalSkill != undefined) {
                    if (userSkill.achievedPoint > 0) {
                        if (globalSkill.offers.find(obj => obj.username == user.username) == undefined) {
                            globalSkills.find(obj => obj.name == userSkill.name).offers.push({
                                username: user.username,
                                location: user.location,
                                teachingDay: user.teachingDay,
                                teachingTime: user.teachingTime,
                                achievedPoint: userSkill.achievedPoint,
                            });
                        } else globalSkill.offers.find(obj => obj.username == user.username).achievedPoint = userSkill.achievedPoint;
                    } else {
                        if (globalSkill.offers.find(obj => obj.username == user.username) != undefined) {
                            globalSkills.find(obj => obj.name == userSkill.name).offers = globalSkill.offers.filter(obj => obj.username != user.username);
                        }
                    }
                }
            });
            globalSkills.forEach(function (skill) {
                skill.save(function (err) {if (err) throw err;});
            });
        }
        user.save(function (err) {if (err) throw err;});
        res.json({
            success: true,
        });
    }
});

// searches a userskill
protectedRoute.post('/searchUserSkillByName', async function (req, res) {

    var user = await findUser(req.decoded.username);

    var skill = user.skills.find(obj => obj.name == req.body.name);

    res.json(skill);
});

/*protectedRoute.post('/parentTableData', async function (req, res) {

    var user = await findUser(req.decoded.username);


    var parents = [];
    req.body.parents.forEach(function(parentname){

        //find the parent, each of them in the foreach 1 by 1
        var parent = user.skills.find(obj => obj.name == parentname);

        //find the child in the parent for minpoint and recommended
        var child = parent.children.find(obj => obj.name == req.body.name );


        //build the json file we need
        var name_minpoint_required = {
            name: parent.name,
            minPoint: child.minPoint,
            recommended: child.recommended
        }

        //push the json file to an array that we are going to return, includes all rows for the table.
        parents.push(name_minpoint_required);
    });

    res.json(parents);
});*/

//API call for request onclick
protectedRoute.post('/request', async function (req, res){

    var user = await await findUser(req.decoded.username);

    var skill = await findSkillByName(req.body.name);

    if (skill !== undefined) {
        var userskill = user.skills.find(obj => obj.name == skill.name);

        if(req.body.requestType == "beginner")
        {
            if(skill.beginnerRequests.find(obj => obj.username == user.username) == undefined)
            {
                skill.beginnerRequests.push({    username: user.username,
                                        achievedPoint: userskill.achievedPoint,
                                        email: user.email  });

                skill.save(function (err) {if (err) throw err;});

                res.json({
                    succes: true,
                    message: 'Added request.',
                    sumRequest: skill.beginnerRequests.length
                });
            }
            else
            {
                res.json({
                    succes: false,
                    message: 'Already requested.',
                    sumRequest: skill.beginnerRequests.length
                });
            }
        }

        if(req.body.requestType == "intermediate")
        {
            if(skill.intermediateRequests.find(obj => obj.username == user.username) == undefined)
            {
                skill.intermediateRequests.push({    username: user.username,
                                        achievedPoint: userskill.achievedPoint,
                                        email: user.email  });

                skill.save(function (err) {if (err) throw err;});

                res.json({
                    succes: true,
                    message: 'Added request.',
                    sumRequest: skill.intermediateRequests.length
                });
            }
            else
            {
                res.json({
                    succes: false,
                    message: 'Already requested.',
                    sumRequest: skill.intermediateRequests.length
                });
            }
        }

        if(req.body.requestType == "advanced")
        {
            if(skill.advancedRequests.find(obj => obj.username == user.username) == undefined)
            {
                skill.advancedRequests.push({    username: user.username,
                                        achievedPoint: userskill.achievedPoint,
                                        email: user.email  });
                skill.save(function (err) {if (err) throw err;});
                res.json({
                    succes: true,
                    message: 'Added request.',
                    sumRequest: skill.advancedRequests.length
                });
            }
            else
            {
                res.json({
                    succes: false,
                    message: 'Already requested.',
                    sumRequest: skill.advancedRequests.length
                });
            }
        }
    }
    else
    {
        res.json({
            succes: false,
            message: "Skill not found"
        });
    }

});

protectedRoute.post('/endorse', async function (req, res) {
  var data = req.body;
  var user = await findUser(data.username);
  if (!user) {
    res.json({
      success: false,
      message: 'User not found.'
    });
  } else {
    if (user.skills.find(obj => obj.name == data.skillName).endorsement == undefined) user.skills.find(obj => obj.name == data.skillName).endorsement = [];
    if (user.skills.find(obj => obj.name == data.skillName).endorsement.find(obj => obj == req.decoded.username) == undefined) {
      user.skills.find(obj => obj.name == data.skillName).endorsement.push(req.decoded.username);
            user.save(function (err) {if (err) throw err;});

      res.json({
              success: true
          });
    }
  }
});

protectedRoute.post('/newpassword', async function (req, res) {
  var data = req.body;
  var user = await findUser(req.decoded.username);
  if (!user) {
    res.json({
      success: false,
      message: 'User not found.'
    });
  } else {
      if (!pbkdf2.verifyPassword(data.oldPassword, user.hashData)) {
          res.json({
              success: false,
              message: 'wrong password'
          });
      } else {
          user.hashData = pbkdf2.hashPassword(data.newPassword);
          user.save(function (err) {if (err) throw err;});

          res.json({
              success: true
          });
      }
  }
});



protectedRoute.post('/newplace', async function (req, res) {
  var data = req.body;
  var user = await findUser(req.decoded.username);
  if (!user) {
    res.json({
      success: false,
      message: 'User not found.'
    });
  } else {
      user.location = data.location;
      user.save(function (err) {if (err) throw err;});

      res.json({
          success: true
      });
  }
});


protectedRoute.post('/newemail', async function (req, res) {
  var data = req.body;
  var user = await findUser(req.decoded.username);
  if (!user) {
    res.json({
      success: false,
      message: 'User not found.'
    });
  } else {
      user.email = data.email;
      user.save(function (err) {if (err) throw err;});

      res.json({
          success: true
      });
  }
});

protectedRoute.post('/newhelp', async function (req, res) {
  var data = req.body;
  var user = await findUser(req.decoded.username);
  if (!user) {
    res.json({
      success: false,
      message: 'User not found.'
    });
  } else {
      user.willingToTeach = data.help;
      user.save(function (err) {if (err) throw err;});

      res.json({
          success: true
      });
  }
});

/*
*   ADMIN
*/
var adminRoute = express.Router();
app.use('/admin', adminRoute);

adminRoute.use(function(req, res, next) {
    var token = req.get('x-access-token');

    // decode token
    if (token) {
        // verifies secret and checks exp
        jwt.verify(token, app.get('superSecret'), function(err, decoded) {
            if (err) {
                return res.status(403).send({
                    success: false,
                    message: 'Failed to authenticate token.'
                });
            } else if (decoded.admin) {
                req.decoded = decoded;
                next();
            } else {
                return res.status(403).send({
                    success: false,
                    message: 'Not admin.'
                });
            }
        });

    } else {
        // if there is no token
        // return an error
        return res.status(403).send({
            success: false,
            message: 'No token provided.'
        });
    }
});

adminRoute.use(express.json());

//Approve a skill thats sent in the body as skillforaproval to the api
adminRoute.post('/approveskill', async function (req, res)  {
    var skillforapproval = req.body;
    var approvecollection = await ApprovableSkill.find( {} , async function(err, approvecollection) {
        if(err) throw err;
        else return approvecollection;
    });

    //Look for the skill in the database, if already exists
    var globalskill = undefined;
    globalskill = await findSkillByName(skillforaproval.name);

    //Check if skill is already in the database or not
    if(globalskill !== null )
    {
        res.json({
            success: false,
            message: "Skill already exists"
        });
    }
    else  //If its not, add to the database
    {
        newGlobalSkill = new Skill({
            name: skillforapproval.name,
            categoryName: skillforapproval.categoryName,
            skillIcon: skillforapproval.skillIcon,
            description: skillforapproval.description,
            descriptionWikipediaURL: skillforapproval.descriptionWikipediaURL,
            pointDescription: skillforapproval.pointDescription,
            maxPoint: skillforapproval.maxPoint,
            parents: skillforapproval.parent,
            children: [
                {
                    name: skillforapproval.name,
                    minPoint: skillforapproval.minPoint,
                    recommended: skillforapproval.recommended
                }
            ],
            trainings: [
                {
                    name: skillforapproval.training.name,
                    level: skillforapproval.training.level,
                    shortDescription: skillforapproval.training.shortDescription,
                    URL: skillforapproval.training.URL,
                    goal: skillforapproval.training.goal,
                    length: skillforapproval.traininglength,
                    language: skillforapproval.training.language
                }
            ]
        });
        newGlobalSkill.save();

        console.log(approvecollection);
        console.log("----------");
        var dependency = [];
        await getDependency(approvecollection, skillforapproval, dependency);

        console.log(dependency);

        var lastdependency = dependency[dependency.length-1];

        for(var i=0;i<dependency.length;i++)
        {
            var globalskill = await findSkillByName(dependency[i].name);
            if(globalskill !== null)
            {
                res.json({
                    success: false,
                    message: "dependency " +i + " " + dependency[i].name + " is already in database"
                });
            }
            else
            {
                newGlobalSkill = new Skill({
                    name: dependency[i].name,
                    categoryName: dependency[i].categoryName,
                    skillIcon: dependency[i].skillIcon,
                    description: dependency[i].description,
                    pointDescription: dependency[i].pointDescription,
                    maxPoint: dependency[i].maxPoint,
                    parents: dependency[i].parent,
                    children: [
                        {
                            name: dependency[i].name,
                            minPoint: dependency[i].minPoint,
                            recommended: dependency[i].recommended
                        }
                    ],
                    trainings: [
                        {
                            name: dependency[i].training.name,
                            level: dependency[i].training.level,
                            shortDescription: dependency[i].training.shortDescription,
                            URL: dependency[i].training.URL,
                            goal: dependency[i].training.goal,
                            length: dependency[i].traininglength,
                            language: dependency[i].training.language
                        }
                    ]
                });
                newGlobalSkill.save();
            }
        }

        for(var i=0; i<lastdependency.parents.length; i++)
        {
            var lastdependencyParent =  await Skill.find( { name : lastdependency.parents[i] } , async function(err, lastdependencyParent){
                if(err) throw err;
                else return lastdependencyParent;
            });

            lastdependencyParent.children.push({
                name: lastdependency.name,
                minPoint: 0, //TODO skillsforapproval model to be changed, got no real data to be read
                recommended: false // ^
            });

            lastdependencyParent.save();

            res.json({
                message: "Succes",
                success: true
            });
        }
    }
});

adminRoute.post('/edittree', async function (req, res) {
    var data = req.body;

    var globalTree = await Tree.findOne({
                "name": data.name
        }, function (err, tree) {
                if (err) throw err;
        return tree;
    });

    var sn = await sortTree(data.skills);
    globalTree.focusArea = data.focusArea;
    globalTree.description = data.description;
    globalTree.skillNames = sn;
    globalTree.save(function (err) {if (err) throw err;});

    User.find({} , (err, users) => {
        if (err) throw err;

        users.map(user => {
            if (user.trees.find(obj => obj.name == data.name) != undefined) {
                user.trees.find(obj => obj.name == data.name).focusArea = data.focusArea;
                user.trees.find(obj => obj.name == data.name).description = data.description;
                user.trees.find(obj => obj.name == data.name).skillNames = sn;

                user.save(function (err) {if (err) throw err;});
            }
        })
    });
    res.json({
        success: true
    });
});

adminRoute.post('/editskill', async function (req, res) {
    var data = req.body;

    var skill = await findSkillByName(data.name);

    for (var i = 0; i < skill.parents.length; ++i) {
        var parent = await findSkillByName(skill.parents[i]);

        parent.children = parent.children.filter(obj => obj.name != skill.name);

        parent.save(function (err) {if (err) throw err;});
    }

    for (var i = 0; i < skill.children.length; ++i) {
        var child = await findSkillByName(skill.children[i].name);

        child.parents = child.parents.filter(obj => obj != skill.name);

        child.save(function (err) {if (err) throw err;});
    }

    skill.name = data.name;
    skill.description = data.description;
    skill.descriptionWikipediaURL = data.descriptionWikipediaURL;
    skill.skillIcon = data.skillIcon;
    skill.categoryName = data.categoryName;
    skill.maxPoint = data.maxPoint;
    skill.pointDescription = data.pointDescription;
    skill.parents = data.parents.map(obj => obj.name);
    skill.children = data.children;
    skill.trainings = data.trainings;

    if (data.maxPoint < skill.achievedPoint) skill.achievedPoint = data.maxPoint;

    skill.save(function (err) {if (err) throw err;});

    User.find({} , async function (err, users) {
        if (err) throw err;

        users.map(async function (user)  {
            if (user.skills.find(obj => obj.name == data.name) != undefined) {
                var userSkill = (user.skills.find(obj => obj.name == data.name));

                for (var i = 0; i < userSkill.parents.length; ++i) user.skills.find(obj => obj.name == userSkill.parents[i]).children = user.skills.find(obj => obj.name == userSkill.parents[i]).children.filter(obj => obj.name != userSkill.name);
                for (var i = 0; i < userSkill.children.length; ++i) {
                    if (user.skills.find(obj => obj.name == userSkill.children[i].name) != undefined) user.skills.find(obj => obj.name == userSkill.children[i].name).parents = user.skills.find(obj => obj.name == userSkill.children[i].name).parents.filter(obj => obj != userSkill.name);
                }

                var parentNames = [];
                for (var i = 0; i < data.parents.length; ++i) {
                    if (user.skills.find(obj => obj.name == data.parents[i].name) == undefined) { // add parent skill to user if not already there
                        var parent = await findSkillByName(data.parents[i].name);
                        user.skills.push(parent);
                    }
                    // add new skill as child of parent skill
                    user.skills.find(obj => obj.name == data.parents[i].name).children.push({name: data.name, minPoint: data.parents[i].minPoint, recommended: data.parents[i].recommended});
                    // when we save the new skill we only need the name of the parents (no minPoint and recommended)
                    parentNames.push(data.parents[i].name);
                }

                for (var i = 0; i < data.children.length; ++i) {
                    if (user.skills.find(obj => obj.name == data.children[i].name) == undefined) { // add parent skill to user if not already there
                        var child = await findSkillByName(data.children[i].name);
                        user.skills.push(child);
                    }
                    // add new skill as child of parent skill
                    user.skills.find(obj => obj.name == data.children[i].name).parents.push(data.name);

                    var trees = user.trees.filter(obj => obj.skillNames.find(obj => obj == data.children[i].name) != undefined);
                    for (var j = 0; j < trees.length; ++j) {
                        var skillList = trees[j].skillNames;
                        if (skillList.find(obj => obj == data.name) == undefined) skillList.push(data.name);
                        var skillsToSort = [];
                        for (var k = 0; k < skillList.length; ++k) skillsToSort = user.skills.filter(obj => skillList.find(obj2 => obj2 == obj.name) != undefined);
                        var sn = await sortTree(skillsToSort);
                        user.trees.find(obj => obj.name == trees[j].name).skillNames = sn;
                    }
                }

                userSkill.name = data.name;
            userSkill.description = data.description;
                userSkill.descriptionWikipediaURL = data.descriptionWikipediaURL;
            userSkill.skillIcon = data.skillIcon;
            userSkill.categoryName = data.categoryName;
            userSkill.maxPoint = data.maxPoint;
            userSkill.pointDescription = data.pointDescription;
                userSkill.parents = parentNames;
                userSkill.children = data.children;
            userSkill.trainings = data.trainings;

                if (data.maxPoint < userSkill.achievedPoint) userSkill.achievedPoint = data.maxPoint;

                //user.skills.find(obj => obj.name == data.name) = skill;
                user.save(function (err) {if (err) throw err;});
            }
        })
    })

    res.json({
        success: true
    });
});

// approves a tree.
adminRoute.post('/approvetree', async function (req, res) {
    var data = req.body;

    var globalTree = await Tree.findOne({
        name: data.name
    }, function(err, tree) {
        if (err) throw err;
        return tree;
    });

    if (globalTree == undefined) {
        var tree = await ApprovableTree.findOne({
            username: data.username,
            name: data.name
        }, function(err, tree) {
            if (err) throw err;
            return tree;
        });

        var newTree = new Tree({
            name: tree.name,
            focusArea: tree.focusArea,
            description: tree.description,
            skillNames: tree.skillNames
        });

        newTree.save(function (err) {if (err) throw err;});

        await ApprovableTree.remove({ // delete ALL trees from approve with this name
            name: data.name
        });
    }
});

adminRoute.post('/approvetraining', async function (req, res) {
    var data = req.body;

    var globalSkill = await findSkillByName(data.skillName);

    if (globalSkill.trainings.find(obj => obj.name == data.name) == undefined) {
        var training = await ApprovableTraining.findOne({
            username: data.username,
            skillName: data.skillName,
            name: data.name
        }, function(err, training) {
            if (err) throw err;
            return training;
        });

        globalSkill.trainings.push({
            name: training.name,
            level: training.level,
            shortDescription: training.shortDescription,
            URL: training.URL,
            goal: training.goal,
            length: training.length,
            language: training.language
        });

        globalSkill.save(function (err) {if (err) throw err;});

        User.find({} , (err, users) => {
            if (err) throw err;

            users.map(user => {
                if (user.skills.find(obj => obj.name == data.skillName) != undefined) {
                    user.skills.find(obj => obj.name == data.skillName).trainings.push({
                        name: training.name,
                        level: training.level,
                        shortDescription: training.shortDescription,
                        URL: training.URL,
                        goal: training.goal,
                        length: training.length,
                        language: training.language
                    });

                    user.save(function (err) {if (err) throw err;});
                }
            })
        });
        await ApprovableTraining.remove({ // delete ALL trainings for the skill from approve with this name
            skillName: data.skillName,
            name: data.name
        });
    }
});

//drops the offers from global skills. Needed if we delete users
adminRoute.post('/dropoffers', async function (req, res) {
    Skill.find({} , (err, skills) => {
        if(err) console.log("error");
        skills.map(skill => {
            skill.offers = [];
            skill.save(  function (err) {if (err) throw err;} );
        })
    })
});

adminRoute.post('/setadmin', async function (req, res) {
    User.findOne({
        username: req.body.username
    }, function(err, user) {
        if (err) throw err;

        if (!user) {
            res.json({
                success: false,
                message: 'User not found.'
            });
        } else if (user) {
            if (req.body.give) user.admin = true;
            else user.admin = false;
            user.save(function (err) {if (err) throw err;});

            res.json({
                success: true
            });
        }
    });
});

/*
*   END OF ADMIN
*/

//DELETE FUNCTIONS, to be separated in a delete.js file or smth

adminRoute.post('/deleteUser', async function (req,res){
    User.deleteOne( {username: request.body.username }  , function (err) {
        if (err) throw err;
        else res.json({
            success: true,
            message: "User deleted"
        })
    });
});

adminRoute.get('/testAdmin', async function (req,res){
    res.json({
        success: true
    });
});

///////////////// END of DELETE SECTION

// returns the user data of the username provided
async function findUser(unm) {
    var user = await User.findOne({
        username: unm,
    }, function (err, user) {
        if (err) throw err;
        return user;
    });
    return user;
}

// returns the skill data of the skillname provided
async function findSkillByName(qname){
    var skillToReturn = await Skill.findOne({
        "name": qname
    }, function (err, parent) {
        if (err) throw err;
        return skillToReturn;
    });
    return skillToReturn;
}

module.exports = app;

const httpServer = http.createServer(app);
httpServer.listen(3000);