Passed
Pull Request — main (#44)
by Julia
01:54
created

server/src/models/user.js   A

Complexity

Total Complexity 17
Complexity/F 1.13

Size

Lines of Code 175
Function Count 15

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 17
eloc 62
mnd 2
bc 2
fnc 15
dl 0
loc 175
bpm 0.1333
cpm 1.1333
noi 0
c 0
b 0
f 0
rs 10
1
import jwt from "jsonwebtoken";
2
import { db } from "./db.js";
3
import express from "express";
4
5
const jwtSecret = String(process.env.JWT_SECRET);
6
7
const user = {
8
    /**
9
     * Uses github access token to get user's email info
10
     * @param {String} githubToken 
11
     * @returns {Promise<String>} user's email
12
     */
13
    extractEmail: async function(githubToken) {
14
        const emailResponse = await fetch('https://api.github.com/user/emails', {
15
            headers: { Authorization: `Bearer ${githubToken}` }
16
        });
17
18
        // the email data contains the user's emailaddressess and whether they are verified etc.
19
        const emailData = await emailResponse.json();
20
21
        const theEmail = emailData.find((email) => email.primary && email.verified)?.email;
22
        if (typeof theEmail !== "string" || !theEmail) {
23
            throw new Error("User has no verified primary email");
24
        }
25
        return theEmail;
26
    },
27
    /**
28
     * Extracts id from token and adds to body as userId
29
     * @param {express.Request} req 
30
     * @param {express.Response} res 
31
     * @param {express.NextFunction} next
32
     */
33
    checkToken: function(req, res, next) {
34
        let token = req.headers["x-access-token"];
35
36
        /**
37
         * @typedef {Object} JwtPayload
38
         * @property {String} role
39
         * @property {String} id
40
         */
41
        jwt.verify(token, jwtSecret, function (err, /** @type {JwtPayload} */decoded) {
42
            // if no token has been provided,
43
            // or if provided token is expired
44
            // this block will be executed
45
            if (err) {
46
                return res.status(401).json({
47
                    errors: {
48
                        status: 401,
49
                        source: "authorization",
50
                        title: "Failed authentication",
51
                        detail: err.message
52
                    }
53
                });
54
            }
55
56
            req.body.user_id = decoded.id;
57
58
            return next();
59
        });
60
    },
61
    /**
62
     * Inserts a new user into the
63
     * database (to be used in the register
64
     * method). Returns an object containing
65
     * the id and the email of the user
66
     * @param {String} email 
67
     * @returns {Promise<Object>}
68
     */
69
    db: async function(email) {
70
71
        const result = await db.queryWithArgs(`CALL user_login(?);`, [email]);
72
73
        return result[0][0];
74
    },
75
    /**
76
     * Logs in user. If user does not have
77
     * an account, registers the user
78
     * Body should contain Github Token,
79
     * Card nr as string and card type as int
80
     * @param {express.Request} req
81
     * @param {express.Response} res
82
     * @param {express.NextFunction} next
83
     */
84
    login: async function(req, res, next) {
85
        const email = await this.extractEmail(req.body.token);
86
        let payload;
87
        try {
88
            payload = await this.db(email);
89
        } catch (err) {
90
            return next(err);
91
        }
92
93
        const jwtToken = jwt.sign(payload, jwtSecret, { expiresIn: "24h" });
94
        return res.json({
95
            data: {
96
                type: "success",
97
                message: "User logged in",
98
                user: payload,
99
                token: jwtToken
100
            }
101
        });
102
    },
103
    /**
104
     * 
105
     * @param {String | Number} what id or email to search for, for wildcard search add % before, after or both
106
     * @return {Promise<Array>} if the search string is not a wildcard the array will only contain one object. Use this method so get data for single user, make sure to pick out the first elem from array
107
     */
108
    search: async function(what) {
109
        const result = await db.queryWithArgs(`CALL user_search(?);`, [what]);
110
        return result[0].map((user) => {
111
            return this.adjTypes(user);
112
        });
113
    },
114
    /**
115
     * Updates the user's active-status.
116
     * Returns an updated user-object containing:
117
     * id, email, balance and active (bool)
118
     * @param {Number} userId 
119
     * @param {Boolean} active 
120
     * @returns {Promise<Object>}
121
     */
122
    updStatus: async function(userId, active) {
123
        const result = await db.queryWithArgs(`CALL upd_user_status(?, ?);`, [userId, active]);
124
        return this.adjTypes(result[0][0]);
125
    },
126
    /**
127
     * Updates the user's email. Returns an
128
     * updated user-object containing:
129
     * id, email, balance and active (bool)
130
     * @param {Number} userId
131
     * @param {String} email
132
     * @returns {Promise<Object>}
133
     */
134
    updEmail: async function(userId, email) {
135
        const result = await db.queryWithArgs(`CALL upd_user_email(?, ?);`, [userId, email]);
136
        return this.adjTypes(result[0][0]);
137
    },
138
    /**
139
     * Returns an array with all users.
140
     * Each user-object contains:
141
     * id, email, balance and active attribute (bool)
142
     * @returns {Promise<Array>}
143
     */
144
    all: async function() {
145
        const result = await db.queryNoArgs(`CALL all_users();`);
146
        return result[0].map((user) => {
147
            return this.adjTypes(user);
148
        });
149
    },
150
    /**
151
     * Returns all users in an interval.
152
     * @param {Number} offset
153
     * @param {Number} limit
154
     * @returns {Promise<Array>}
155
     */
156
    allPag: async function(offset, limit) {
157
        const result = await db.queryWithArgs(`CALL all_users_pag(?, ?);`, [offset, limit]);
158
        return result[0].map((user) => {
159
            return this.adjTypes(user);
160
        });
161
    },
162
    
163
    /**
164
     * Database returns all attributes
165
     * as either integers or strings.
166
     * This function ensures that decimal
167
     * numbers are represented as floating point
168
     * and active is a boolean
169
     * @param {Object} userObj 
170
     * @returns {Object}
171
     */
172
    adjTypes(userObj) {
173
        userObj.balance = parseFloat(userObj.balance);
174
        userObj.active = userObj.active === 1;
175
        return userObj;
176
    },
177
};
178
179
export default user;
180