From 7ea36ca6be98be0ab6c43c9d7fc62e257ebfea37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C3=ABl=20Minelli?= <michael@minelli.me>
Date: Fri, 16 Jun 2023 20:36:12 +0200
Subject: [PATCH] Refactor commands & Start of the create enonce command
 (tests)

---
 NodeApp/src/Config/Config.ts                  |  45 --------
 .../Config/{LocalConfig => }/LocalConfig.ts   |  12 +-
 NodeApp/src/commander/CommanderApp.ts         |  60 ++--------
 NodeApp/src/commander/CommanderCommand.ts     |  25 ++++
 NodeApp/src/commander/enonce/EnonceCommand.ts |  33 ++++++
 .../commander/enonce/EnonceCreateCommand.ts   | 108 ++++++++++++++++++
 .../session/App/SessionAppCommand.ts          |  35 ++++++
 .../session/App/SessionAppLoginCommand.ts     |  49 ++++++++
 .../session/App/SessionAppLogoutCommand.ts    |  50 ++++++++
 .../session/Gitlab/SessionGitlabCommand.ts    |  35 ++++++
 .../Gitlab/SessionGitlabLoginCommand.ts       |  38 ++++++
 .../Gitlab/SessionGitlabLogoutCommand.ts      |  50 ++++++++
 .../src/commander/session/SessionCommand.ts   |  37 ++++++
 .../commander/session/SessionTestCommand.ts   |  34 ++++++
 NodeApp/src/main.ts                           |   2 +-
 NodeApp/src/managers/GitlabManager.ts         |  58 ++++++++--
 NodeApp/src/managers/HttpManager.ts           |   4 +-
 NodeApp/src/managers/SessionManager.ts        |  68 ++++++++---
 NodeApp/src/models/GitlabUser.ts              |  18 +++
 NodeApp/src/models/User.ts                    |   1 +
 .../LocalConfig => types}/LocalConfigKeys.ts  |   0
 NodeApp/src/types/Permissions.ts              |   7 ++
 22 files changed, 640 insertions(+), 129 deletions(-)
 delete mode 100644 NodeApp/src/Config/Config.ts
 rename NodeApp/src/Config/{LocalConfig => }/LocalConfig.ts (81%)
 create mode 100644 NodeApp/src/commander/CommanderCommand.ts
 create mode 100644 NodeApp/src/commander/enonce/EnonceCommand.ts
 create mode 100644 NodeApp/src/commander/enonce/EnonceCreateCommand.ts
 create mode 100644 NodeApp/src/commander/session/App/SessionAppCommand.ts
 create mode 100644 NodeApp/src/commander/session/App/SessionAppLoginCommand.ts
 create mode 100644 NodeApp/src/commander/session/App/SessionAppLogoutCommand.ts
 create mode 100644 NodeApp/src/commander/session/Gitlab/SessionGitlabCommand.ts
 create mode 100644 NodeApp/src/commander/session/Gitlab/SessionGitlabLoginCommand.ts
 create mode 100644 NodeApp/src/commander/session/Gitlab/SessionGitlabLogoutCommand.ts
 create mode 100644 NodeApp/src/commander/session/SessionCommand.ts
 create mode 100644 NodeApp/src/commander/session/SessionTestCommand.ts
 create mode 100644 NodeApp/src/models/GitlabUser.ts
 rename NodeApp/src/{Config/LocalConfig => types}/LocalConfigKeys.ts (100%)
 create mode 100644 NodeApp/src/types/Permissions.ts

diff --git a/NodeApp/src/Config/Config.ts b/NodeApp/src/Config/Config.ts
deleted file mode 100644
index 9d53f24..0000000
--- a/NodeApp/src/Config/Config.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import HttpManager    from '../managers/HttpManager';
-import getAppDataPath from 'appdata-path';
-
-
-class Config {
-    private static _instance: Config;
-
-    private _apiURL!: string;
-    public gitlabApiURL: string;
-
-    public readonly localConfig: {
-        folder: string; file: string;
-    };
-
-    private constructor() {
-        this.apiURL = process.env.API_URL || '';
-        this.gitlabApiURL = process.env.GITLAB_API_URL || '';
-
-        this.localConfig = {
-            folder: getAppDataPath('DojoCLI'),
-            file  : process.env.LOCAL_CONFIG_FILE || ''
-        };
-    }
-
-    get apiURL(): string {
-        return this._apiURL;
-    }
-
-    set apiURL(url: string) {
-        this._apiURL = url;
-
-        HttpManager.API_BASE_URL = this._apiURL;
-    }
-
-    public static get instance(): Config {
-        if ( !Config._instance ) {
-            Config._instance = new Config();
-        }
-
-        return Config._instance;
-    }
-}
-
-
-export default Config.instance;
diff --git a/NodeApp/src/Config/LocalConfig/LocalConfig.ts b/NodeApp/src/Config/LocalConfig.ts
similarity index 81%
rename from NodeApp/src/Config/LocalConfig/LocalConfig.ts
rename to NodeApp/src/Config/LocalConfig.ts
index d622cc1..4be9e9a 100644
--- a/NodeApp/src/Config/LocalConfig/LocalConfig.ts
+++ b/NodeApp/src/Config/LocalConfig.ts
@@ -1,9 +1,9 @@
 import * as fs         from 'fs';
-import logger          from '../../shared/logging/WinstonLogger';
-import SessionManager  from '../../managers/SessionManager';
-import Config          from '../Config';
-import LocalConfigKeys from './LocalConfigKeys';
-import GitlabManager   from '../../managers/GitlabManager';
+import logger          from '../shared/logging/WinstonLogger';
+import SessionManager  from '../managers/SessionManager';
+import Config          from './Config';
+import LocalConfigKeys from '../types/LocalConfigKeys';
+import GitlabManager   from '../managers/GitlabManager';
 
 
 class LocalConfig {
@@ -45,7 +45,7 @@ class LocalConfig {
 
         (this._config as any)[key] = value;
 
-        fs.writeFile(this.configPath, JSON.stringify(this._config), (err) => {
+        fs.writeFile(this.configPath, JSON.stringify(this._config, null, 4), (err) => {
             if ( err ) {
                 logger.error(err);
             }
diff --git a/NodeApp/src/commander/CommanderApp.ts b/NodeApp/src/commander/CommanderApp.ts
index 52836c4..0c92978 100644
--- a/NodeApp/src/commander/CommanderApp.ts
+++ b/NodeApp/src/commander/CommanderApp.ts
@@ -1,13 +1,11 @@
 import { Command }    from 'commander';
-import SessionManager from '../managers/SessionManager';
-import Config         from '../Config/Config';
-import GitlabManager  from '../managers/GitlabManager';
-import chalk          from 'chalk';
-import inquirer       from 'inquirer';
+import Config         from '../config/Config';
+import EnonceCommand  from './enonce/EnonceCommand';
+import SessionCommand from './session/SessionCommand';
 
 
 class CommanderApp {
-    program = new Command();
+    program: Command = new Command();
 
     constructor() {
         this.program
@@ -26,56 +24,14 @@ class CommanderApp {
             Config.apiURL = this.program.opts().host;
         });
 
-        this.loginCommand();
-        this.gitlabRegisterCommand();
-        this.testAccessesCommand();
+        this.registerCommands();
 
         this.program.parse();
     }
 
-    loginCommand() {
-        this.program.command('login')
-        .description('Login into the application')
-        .requiredOption('-u, --user <string>', '[required] username to use when connecting to server.')
-        .option('-p, --password <string>', 'password to use when connecting to server. If password is not given it\'s asked.')
-        .action(async (options) => {
-            if ( !options.password ) {
-                options.password = (await inquirer.prompt({
-                                                              type   : 'password',
-                                                              name   : 'password',
-                                                              message: 'Please enter your password',
-                                                              mask   : ''
-                                                          })).password;
-            }
-
-            console.log(chalk.cyan('Please wait while we are logging in you to Dojo...'));
-
-            await SessionManager.login(options.user, options.password);
-        });
-    }
-
-    gitlabRegisterCommand() {
-        this.program.command('gitlab_register')
-        .description('Register the gitlab token')
-        .argument('<token>', 'Personal access token from GitLab with api scope.')
-        .action(async (token) => {
-            console.log(chalk.cyan('Please wait while we are testing your Gitlab token...'));
-
-            GitlabManager.token = token;
-            await GitlabManager.testToken();
-        });
-    }
-
-    testAccessesCommand() {
-        this.program.command('test_accesses')
-        .description('Test availability of registered Dojo API and Gitlab API sessions')
-        .action(async _ => {
-            console.log(chalk.cyan('Please wait while we are testing your Dojo accesses...'));
-            await SessionManager.testSession();
-
-            console.log(chalk.cyan('Please wait while we are testing your Gitlab accesses...'));
-            await GitlabManager.testToken();
-        });
+    private registerCommands() {
+        SessionCommand.registerOnCommand(this.program);
+        EnonceCommand.registerOnCommand(this.program);
     }
 }
 
diff --git a/NodeApp/src/commander/CommanderCommand.ts b/NodeApp/src/commander/CommanderCommand.ts
new file mode 100644
index 0000000..f4c9bea
--- /dev/null
+++ b/NodeApp/src/commander/CommanderCommand.ts
@@ -0,0 +1,25 @@
+import { Command } from 'commander';
+
+
+abstract class CommanderCommand {
+    protected abstract commandName: string;
+    command: Command = new Command();
+
+    protected constructor() {}
+
+    registerOnCommand(parent: Command) {
+        this.command = parent.command(this.commandName);
+
+        this.defineCommand();
+        this.defineSubCommands();
+    };
+
+    protected abstract defineCommand(): void;
+
+    protected defineSubCommands() {}
+
+    protected abstract commandAction(...args: Array<any>): Promise<void>;
+}
+
+
+export default CommanderCommand;
\ No newline at end of file
diff --git a/NodeApp/src/commander/enonce/EnonceCommand.ts b/NodeApp/src/commander/enonce/EnonceCommand.ts
new file mode 100644
index 0000000..c81d0d8
--- /dev/null
+++ b/NodeApp/src/commander/enonce/EnonceCommand.ts
@@ -0,0 +1,33 @@
+import CommanderCommand    from '../CommanderCommand';
+import CreateEnonceCommand from './EnonceCreateCommand';
+
+
+class EnonceCommand extends CommanderCommand {
+    protected commandName: string = 'enonce';
+
+    private static _instance: EnonceCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): EnonceCommand {
+        if ( !EnonceCommand._instance ) {
+            EnonceCommand._instance = new EnonceCommand();
+        }
+
+        return EnonceCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('Manage an enonce.');
+    }
+
+    protected defineSubCommands() {
+        CreateEnonceCommand.registerOnCommand(this.command);
+    }
+
+    protected async commandAction(options: any): Promise<void> { }
+}
+
+
+export default EnonceCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/commander/enonce/EnonceCreateCommand.ts b/NodeApp/src/commander/enonce/EnonceCreateCommand.ts
new file mode 100644
index 0000000..9291f3a
--- /dev/null
+++ b/NodeApp/src/commander/enonce/EnonceCreateCommand.ts
@@ -0,0 +1,108 @@
+import CommanderCommand from '../CommanderCommand';
+import GitlabUser       from '../../models/GitlabUser';
+import chalk            from 'chalk';
+import ora              from 'ora';
+import GitlabManager    from '../../managers/GitlabManager';
+import Config           from '../../config/Config';
+import SessionManager   from '../../managers/SessionManager';
+
+
+class EnonceCreateCommand extends CommanderCommand {
+    protected commandName: string = 'create';
+
+    private static _instance: EnonceCreateCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): EnonceCreateCommand {
+        if ( !EnonceCreateCommand._instance ) {
+            EnonceCreateCommand._instance = new EnonceCreateCommand();
+        }
+
+        return EnonceCreateCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('Create a new repository for an enonce.')
+        .requiredOption('-n, --name <name>', 'name of the enonce.')
+        .option('-i, --members_id <ids...>', 'list of members ids (teaching staff) to add to the repository.')
+        .option('-m, --members_username <usernames...>', 'list of members username (teaching staff) to add to the repository.')
+        .action(this.commandAction.bind(this));
+    }
+
+    private async checkAccesses(): Promise<boolean> {
+        let sessionResult = await SessionManager.testSession(true, [ 'teachingStaff' ]);
+
+        if ( !sessionResult || !sessionResult.teachingStaff ) {
+            return false;
+        }
+
+        return (await GitlabManager.testToken(true)).every(result => result);
+    }
+
+    private async fetchMembers(options: any): Promise<Array<GitlabUser> | false> {
+        options.members_id = options.members_id ? options.members_id.concat([ Config.gitlab.dojoAccountId ]) : [ Config.gitlab.dojoAccountId ];
+
+        const spinner: ora.Ora = ora('Checking Gitlab members: Fetching members by id...').start();
+
+        let members: Array<GitlabUser> = [];
+
+        async function getMembers<T>(context: any, functionName: string, paramsToSearch: Array<T>, spinner: ora.Ora): Promise<boolean> {
+            const result = await (context[functionName] as (arg: Array<T>) => Promise<Array<GitlabUser | undefined>>)(paramsToSearch);
+            if ( result.every(user => user) ) {
+                members = members.concat(result as Array<GitlabUser>);
+                return true;
+            } else {
+                spinner.fail('Checking Gitlab members: Some members were not found : ' + result.map((value, index: number) => {
+                    if ( value === undefined ) {
+                        return paramsToSearch[index];
+                    } else {
+                        return undefined;
+                    }
+                }).filter((value) => value).join(', ') + '.');
+                return false;
+            }
+        }
+
+        if ( options.members_id ) {
+            spinner.text = 'Checking Gitlab members: Fetching members by id...';
+            if ( !await getMembers(GitlabManager, 'getUsersById', options.members_id, spinner) ) {
+                return false;
+            }
+        }
+
+        if ( options.members_username ) {
+            spinner.text = 'Checking Gitlab members: Fetching members by username...';
+            if ( !await getMembers(GitlabManager, 'getUsersByUsername', options.members_username, spinner) ) {
+                return false;
+            }
+        }
+
+        members = members.reduce((unique, user) => (unique.findIndex(uniqueUser => uniqueUser.id === user.id) !== -1 ? unique : [ ...unique, user ]), Array<GitlabUser>());
+
+        spinner.succeed('Checking Gitlab members: All members were found.');
+        return members;
+    }
+
+    protected async commandAction(options: any): Promise<void> {
+        let members!: Array<GitlabUser> | false;
+
+        // Check access and retrieve data
+        {
+            console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
+
+            if ( !await this.checkAccesses() ) {
+                return;
+            }
+
+            members = await this.fetchMembers(options);
+            if ( !members ) {
+                return;
+            }
+        }
+    }
+}
+
+
+export default EnonceCreateCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/App/SessionAppCommand.ts b/NodeApp/src/commander/session/App/SessionAppCommand.ts
new file mode 100644
index 0000000..0aeda2f
--- /dev/null
+++ b/NodeApp/src/commander/session/App/SessionAppCommand.ts
@@ -0,0 +1,35 @@
+import CommanderCommand        from '../../CommanderCommand';
+import SessionAppLoginCommand  from './SessionAppLoginCommand';
+import SessionAppLogoutCommand from './SessionAppLogoutCommand';
+
+
+class SessionAppCommand extends CommanderCommand {
+    protected commandName: string = 'application';
+
+    private static _instance: SessionAppCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): SessionAppCommand {
+        if ( !SessionAppCommand._instance ) {
+            SessionAppCommand._instance = new SessionAppCommand();
+        }
+
+        return SessionAppCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('manage application session.');
+    }
+
+    protected defineSubCommands() {
+        SessionAppLoginCommand.registerOnCommand(this.command);
+        SessionAppLogoutCommand.registerOnCommand(this.command);
+    }
+
+    protected async commandAction(): Promise<void> { }
+}
+
+
+export default SessionAppCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/App/SessionAppLoginCommand.ts b/NodeApp/src/commander/session/App/SessionAppLoginCommand.ts
new file mode 100644
index 0000000..5575036
--- /dev/null
+++ b/NodeApp/src/commander/session/App/SessionAppLoginCommand.ts
@@ -0,0 +1,49 @@
+import chalk            from 'chalk';
+import CommanderCommand from '../../CommanderCommand';
+import inquirer         from 'inquirer';
+import SessionManager   from '../../../managers/SessionManager';
+
+
+class SessionAppLoginCommand extends CommanderCommand {
+    protected commandName: string = 'login';
+
+    private static _instance: SessionAppLoginCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): SessionAppLoginCommand {
+        if ( !SessionAppLoginCommand._instance ) {
+            SessionAppLoginCommand._instance = new SessionAppLoginCommand();
+        }
+
+        return SessionAppLoginCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('login into the application.')
+        .requiredOption('-u, --user <string>', '[required] username to use when connecting to server.')
+        .option('-p, --password <string>', 'password to use when connecting to server. If password is not given it\'s asked.')
+        .action(this.commandAction.bind(this));
+    }
+
+    protected async commandAction(options: any): Promise<void> {
+        if ( !options.password ) {
+            options.password = (await inquirer.prompt({
+                                                          type   : 'password',
+                                                          name   : 'password',
+                                                          message: 'Please enter your password',
+                                                          mask   : ''
+                                                      })).password;
+        }
+
+        console.log(chalk.cyan('Please wait while we are logging in you to Dojo...'));
+
+        await SessionManager.login(options.user, options.password);
+
+        SessionManager.checkPermissions(true);
+    }
+}
+
+
+export default SessionAppLoginCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/App/SessionAppLogoutCommand.ts b/NodeApp/src/commander/session/App/SessionAppLogoutCommand.ts
new file mode 100644
index 0000000..6ab3f85
--- /dev/null
+++ b/NodeApp/src/commander/session/App/SessionAppLogoutCommand.ts
@@ -0,0 +1,50 @@
+import CommanderCommand from '../../CommanderCommand';
+import inquirer         from 'inquirer';
+import SessionManager   from '../../../managers/SessionManager';
+import ora              from 'ora';
+
+
+class SessionAppLogoutCommand extends CommanderCommand {
+    protected commandName: string = 'logout';
+
+    private static _instance: SessionAppLogoutCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): SessionAppLogoutCommand {
+        if ( !SessionAppLogoutCommand._instance ) {
+            SessionAppLogoutCommand._instance = new SessionAppLogoutCommand();
+        }
+
+        return SessionAppLogoutCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('logout of the application.')
+        .option('-f, --force', 'attempt to logout without prompting for confirmation.')
+        .action(this.commandAction.bind(this));
+    }
+
+    protected async commandAction(options: any): Promise<void> {
+        if ( !options.force ) {
+            const confirm: boolean = (await inquirer.prompt({
+                                                                name   : 'confirm',
+                                                                message: 'Are you sure?',
+                                                                type   : 'confirm',
+                                                                default: false
+                                                            })).confirm;
+
+            if ( !confirm ) {
+                return;
+            }
+        }
+
+        const spinner: ora.Ora = ora('Please wait while we are logout you from Dojo...').start();
+        SessionManager.logout();
+        spinner.succeed('You are now logged out from Dojo.');
+    }
+}
+
+
+export default SessionAppLogoutCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/Gitlab/SessionGitlabCommand.ts b/NodeApp/src/commander/session/Gitlab/SessionGitlabCommand.ts
new file mode 100644
index 0000000..90b805b
--- /dev/null
+++ b/NodeApp/src/commander/session/Gitlab/SessionGitlabCommand.ts
@@ -0,0 +1,35 @@
+import CommanderCommand           from '../../CommanderCommand';
+import SessionGitlabLoginCommand  from './SessionGitlabLoginCommand';
+import SessionGitlabLogoutCommand from './SessionGitlabLogoutCommand';
+
+
+class SessionGitlabCommand extends CommanderCommand {
+    protected commandName: string = 'gitlab';
+
+    private static _instance: SessionGitlabCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): SessionGitlabCommand {
+        if ( !SessionGitlabCommand._instance ) {
+            SessionGitlabCommand._instance = new SessionGitlabCommand();
+        }
+
+        return SessionGitlabCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('manage Gitlab session.');
+    }
+
+    protected defineSubCommands() {
+        SessionGitlabLoginCommand.registerOnCommand(this.command);
+        SessionGitlabLogoutCommand.registerOnCommand(this.command);
+    }
+
+    protected async commandAction(): Promise<void> { }
+}
+
+
+export default SessionGitlabCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/Gitlab/SessionGitlabLoginCommand.ts b/NodeApp/src/commander/session/Gitlab/SessionGitlabLoginCommand.ts
new file mode 100644
index 0000000..d7f2f56
--- /dev/null
+++ b/NodeApp/src/commander/session/Gitlab/SessionGitlabLoginCommand.ts
@@ -0,0 +1,38 @@
+import chalk            from 'chalk';
+import CommanderCommand from '../../CommanderCommand';
+import GitlabManager    from '../../../managers/GitlabManager';
+
+
+class SessionGitlabLoginCommand extends CommanderCommand {
+    protected commandName: string = 'login';
+
+    private static _instance: SessionGitlabLoginCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): SessionGitlabLoginCommand {
+        if ( !SessionGitlabLoginCommand._instance ) {
+            SessionGitlabLoginCommand._instance = new SessionGitlabLoginCommand();
+        }
+
+        return SessionGitlabLoginCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('Register the gitlab token.')
+        .argument('<token>', 'personal access token from GitLab with api scope.')
+        .action(this.commandAction.bind(this));
+    }
+
+    protected async commandAction(token: string): Promise<void> {
+        console.log(chalk.cyan('Please wait while we are testing your Gitlab token...'));
+
+        GitlabManager.login(token);
+
+        await GitlabManager.testToken();
+    }
+}
+
+
+export default SessionGitlabLoginCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/Gitlab/SessionGitlabLogoutCommand.ts b/NodeApp/src/commander/session/Gitlab/SessionGitlabLogoutCommand.ts
new file mode 100644
index 0000000..945cbc7
--- /dev/null
+++ b/NodeApp/src/commander/session/Gitlab/SessionGitlabLogoutCommand.ts
@@ -0,0 +1,50 @@
+import CommanderCommand from '../../CommanderCommand';
+import inquirer         from 'inquirer';
+import ora              from 'ora';
+import GitlabManager    from '../../../managers/GitlabManager';
+
+
+class SessionGitlabLogoutCommand extends CommanderCommand {
+    protected commandName: string = 'logout';
+
+    private static _instance: SessionGitlabLogoutCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): SessionGitlabLogoutCommand {
+        if ( !SessionGitlabLogoutCommand._instance ) {
+            SessionGitlabLogoutCommand._instance = new SessionGitlabLogoutCommand();
+        }
+
+        return SessionGitlabLogoutCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('logout of Gitlab.')
+        .option('-f, --force', 'attempt to logout without prompting for confirmation.')
+        .action(this.commandAction.bind(this));
+    }
+
+    protected async commandAction(options: any): Promise<void> {
+        if ( !options.force ) {
+            const confirm: boolean = (await inquirer.prompt({
+                                                                name   : 'confirm',
+                                                                message: 'Are you sure?',
+                                                                type   : 'confirm',
+                                                                default: false
+                                                            })).confirm;
+
+            if ( !confirm ) {
+                return;
+            }
+        }
+
+        const spinner: ora.Ora = ora('Please wait while we are logout you from Gitlab...').start();
+        GitlabManager.logout();
+        spinner.succeed('You are now logged out from Gitlab.');
+    }
+}
+
+
+export default SessionGitlabLogoutCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/SessionCommand.ts b/NodeApp/src/commander/session/SessionCommand.ts
new file mode 100644
index 0000000..7cd6b01
--- /dev/null
+++ b/NodeApp/src/commander/session/SessionCommand.ts
@@ -0,0 +1,37 @@
+import CommanderCommand     from '../CommanderCommand';
+import SessionTestCommand   from './SessionTestCommand';
+import SessionAppCommand    from './App/SessionAppCommand';
+import SessionGitlabCommand from './Gitlab/SessionGitlabCommand';
+
+
+class SessionCommand extends CommanderCommand {
+    protected commandName: string = 'session';
+
+    private static _instance: SessionCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): SessionCommand {
+        if ( !SessionCommand._instance ) {
+            SessionCommand._instance = new SessionCommand();
+        }
+
+        return SessionCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('manage session for API and Gitlab');
+    }
+
+    protected defineSubCommands() {
+        SessionAppCommand.registerOnCommand(this.command);
+        SessionGitlabCommand.registerOnCommand(this.command);
+        SessionTestCommand.registerOnCommand(this.command);
+    }
+
+    protected async commandAction(options: any): Promise<void> { }
+}
+
+
+export default SessionCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/commander/session/SessionTestCommand.ts b/NodeApp/src/commander/session/SessionTestCommand.ts
new file mode 100644
index 0000000..890fc03
--- /dev/null
+++ b/NodeApp/src/commander/session/SessionTestCommand.ts
@@ -0,0 +1,34 @@
+import CommanderCommand from '../CommanderCommand';
+import SessionManager   from '../../managers/SessionManager';
+import GitlabManager    from '../../managers/GitlabManager';
+
+
+class SessionTestCommand extends CommanderCommand {
+    protected commandName: string = 'test';
+
+    private static _instance: SessionTestCommand;
+
+    private constructor() { super(); }
+
+    public static get instance(): SessionTestCommand {
+        if ( !SessionTestCommand._instance ) {
+            SessionTestCommand._instance = new SessionTestCommand();
+        }
+
+        return SessionTestCommand._instance;
+    }
+
+    protected defineCommand() {
+        this.command
+        .description('test availability of registered Dojo API and Gitlab API sessions')
+        .action(this.commandAction.bind(this));
+    }
+
+    protected async commandAction(): Promise<void> {
+        await SessionManager.testSession();
+        await GitlabManager.testToken();
+    }
+}
+
+
+export default SessionTestCommand.instance;
\ No newline at end of file
diff --git a/NodeApp/src/main.ts b/NodeApp/src/main.ts
index b57df51..34264d2 100644
--- a/NodeApp/src/main.ts
+++ b/NodeApp/src/main.ts
@@ -5,7 +5,7 @@ require('dotenv').config({ path: path.join(__dirname, '../.env') });
 
 import CommanderApp from './commander/CommanderApp';
 import HttpManager  from './managers/HttpManager';
-import LocalConfig  from './Config/LocalConfig/LocalConfig';
+import LocalConfig  from './config/LocalConfig';
 
 
 LocalConfig.loadConfig();
diff --git a/NodeApp/src/managers/GitlabManager.ts b/NodeApp/src/managers/GitlabManager.ts
index 6290490..8223e6e 100644
--- a/NodeApp/src/managers/GitlabManager.ts
+++ b/NodeApp/src/managers/GitlabManager.ts
@@ -1,8 +1,9 @@
-import LocalConfig     from '../Config/LocalConfig/LocalConfig';
-import LocalConfigKeys from '../Config/LocalConfig/LocalConfigKeys';
+import LocalConfig     from '../config/LocalConfig';
+import LocalConfigKeys from '../types/LocalConfigKeys';
 import axios           from 'axios';
-import Config          from '../Config/Config';
+import Config          from '../config/Config';
 import ora             from 'ora';
+import GitlabUser      from '../models/GitlabUser';
 
 
 class GitlabManager {
@@ -34,7 +35,19 @@ class GitlabManager {
         LocalConfig.updateConfig(LocalConfigKeys.GITLAB_PERSONAL_TOKEN, token);
     }
 
+    login(token: string): void {
+        this.token = token;
+    }
+
+    logout(): void {
+        this.token = '';
+    }
+
     public async testToken(verbose: boolean = true): Promise<[ boolean, boolean ]> {
+        if ( verbose ) {
+            ora('Checking Gitlab token: ').start().info();
+        }
+
         let result: [ boolean, boolean ] = [ false, false ];
 
         type NotificationSettings = { level: string }
@@ -43,12 +56,14 @@ class GitlabManager {
 
         // Test read access
         {
-            const spinnerRead: ora.Ora = ora('Read access');
+            const spinnerRead: ora.Ora = ora({
+                                                 text  : `Read access`,
+                                                 indent: 4
+                                             });
             if ( verbose ) {
                 spinnerRead.start();
             }
 
-
             try {
                 notificationSettings = (await this.getNotificationSettings()).data as NotificationSettings;
 
@@ -66,7 +81,10 @@ class GitlabManager {
 
         // Test write access
         {
-            const spinnerWrite: ora.Ora = ora('Write access');
+            const spinnerWrite: ora.Ora = ora({
+                                                  text  : `Write access`,
+                                                  indent: 4
+                                              });
             if ( verbose ) {
                 spinnerWrite.start();
             }
@@ -96,11 +114,35 @@ class GitlabManager {
     }
 
     public getNotificationSettings() {
-        return axios.get(`${ Config.gitlabApiURL }/notification_settings`);
+        return axios.get(`${ Config.gitlab.apiURL }/notification_settings`);
     }
 
     public setNotificationSettings(newSettings: any) {
-        return axios.put(`${ Config.gitlabApiURL }/notification_settings`, { params: new URLSearchParams(newSettings) });
+        return axios.put(`${ Config.gitlab.apiURL }/notification_settings`, { params: new URLSearchParams(newSettings) });
+    }
+
+    private async getGitlabUser(paramsToSearch: Array<string | number>, paramName: string): Promise<Array<GitlabUser | undefined>> {
+        try {
+            return await Promise.all(paramsToSearch.map(async param => {
+                const params: any = {};
+                params[paramName] = param;
+                const user = await axios.get(`${ Config.gitlab.apiURL }/users`, { params: params });
+
+                if ( user.data[0] ) {
+                    return GitlabUser.createFromJson(user.data[0]);
+                }
+            }));
+        } catch ( e ) {
+            return [ undefined ];
+        }
+    }
+
+    public async getUsersById(ids: Array<number>): Promise<Array<GitlabUser | undefined>> {
+        return await this.getGitlabUser(ids, 'id');
+    }
+
+    public async getUsersByUsername(usernames: Array<string>): Promise<Array<GitlabUser | undefined>> {
+        return await this.getGitlabUser(usernames, 'search');
     }
 }
 
diff --git a/NodeApp/src/managers/HttpManager.ts b/NodeApp/src/managers/HttpManager.ts
index 46631aa..2efb5e2 100644
--- a/NodeApp/src/managers/HttpManager.ts
+++ b/NodeApp/src/managers/HttpManager.ts
@@ -1,5 +1,5 @@
 import axios, { AxiosRequestHeaders } from 'axios';
-import Config                         from '../Config/Config';
+import Config                         from '../config/Config';
 import SessionManager                 from './SessionManager';
 import FormData                       from 'form-data';
 import logger                         from '../shared/logging/WinstonLogger';
@@ -49,7 +49,7 @@ class HttpManager {
                 config.headers.Authorization = 'Bearer ' + SessionManager.token;
             }
 
-            if ( GitlabManager.isLogged && config.url && config.url.indexOf(Config.gitlabApiURL) !== -1 ) {
+            if ( GitlabManager.isLogged && config.url && config.url.indexOf(Config.gitlab.apiURL) !== -1 ) {
                 config.headers['PRIVATE-TOKEN'] = GitlabManager.token;
             }
 
diff --git a/NodeApp/src/managers/SessionManager.ts b/NodeApp/src/managers/SessionManager.ts
index 8790c6d..6d81a64 100644
--- a/NodeApp/src/managers/SessionManager.ts
+++ b/NodeApp/src/managers/SessionManager.ts
@@ -1,10 +1,11 @@
 import * as jwt              from 'jsonwebtoken';
 import User                  from '../models/User';
-import LocalConfig           from '../Config/LocalConfig/LocalConfig';
-import LocalConfigKeys       from '../Config/LocalConfig/LocalConfigKeys';
+import LocalConfig           from '../config/LocalConfig';
+import LocalConfigKeys       from '../types/LocalConfigKeys';
 import axios, { AxiosError } from 'axios';
 import HttpManager           from './HttpManager';
 import ora                   from 'ora';
+import Permissions           from '../types/Permissions';
 
 
 class SessionManager {
@@ -35,10 +36,14 @@ class SessionManager {
     set token(token: string) {
         this._token = token;
 
-        const payload = jwt.decode(token);
+        try {
+            const payload = jwt.decode(token);
 
-        if ( payload && typeof payload === 'object' && payload.profile ) {
-            this.profile = User.createFromJson(payload.profile);
+            if ( payload && typeof payload === 'object' && payload.profile ) {
+                this.profile = User.createFromJson(payload.profile);
+            }
+        } catch ( error ) {
+            this.profile = new User();
         }
 
         LocalConfig.updateConfig(LocalConfigKeys.API_TOKEN, token);
@@ -72,31 +77,64 @@ class SessionManager {
         }
     }
 
-    async testSession(verbose: boolean = true): Promise<boolean> {
-        HttpManager.handleCommandErrors = false;
+    logout() {
+        this.token = '';
+    }
+
+    checkPermissions(verbose: boolean = true, ...checkPermissions: Array<string>): Permissions {
+        function hasPermission(permissionPredicate: () => boolean, verboseText: string): boolean {
+            const spinner: ora.Ora = ora({
+                                             text  : verboseText,
+                                             indent: 8
+                                         });
 
-        let result: boolean = false;
+            let isAllowed: boolean = permissionPredicate();
+
+            if ( verbose ) {
+                spinner.start();
+                isAllowed ? spinner.succeed() : spinner.fail();
+            }
+
+            return isAllowed;
+        }
 
-        const spinner: ora.Ora = ora('Testing Dojo session');
+        return {
+            teachingStaff: checkPermissions.length == 0 || checkPermissions.includes('teachingStaff') ? hasPermission(() => this.profile.isTeachingStaff, 'Teaching staff permissions') : false,
+            student      : checkPermissions.length == 0 || checkPermissions.includes('student') ? hasPermission(() => true, 'Student permissions') : false
+        };
+    }
+
+
+    async testSession(verbose: boolean = true, checkPermissions: Array<string> = []): Promise<false | Permissions> {
+        if ( verbose ) {
+            ora('Checking Dojo session: ').start().info();
+        }
+
+        HttpManager.handleCommandErrors = false;
+
+        const spinner: ora.Ora = ora({
+                                         text  : `Testing Dojo session`,
+                                         indent: 4
+                                     });
         if ( verbose ) {
             spinner.start();
         }
 
         try {
-            const response = await axios.get(HttpManager.TEST_SESSION_URL, {});
-
-            result = true;
+            await axios.get(HttpManager.TEST_SESSION_URL, {});
 
             if ( verbose ) {
-                spinner.succeed('The session is valid');
+                spinner.succeed(`The session is valid`);
             }
         } catch ( error ) {
             if ( verbose ) {
-                spinner.succeed('The session is invalid');
+                spinner.fail(`The session is invalid`);
             }
+
+            return false;
         }
 
-        return true;
+        return this.checkPermissions(verbose, ...checkPermissions);
     }
 }
 
diff --git a/NodeApp/src/models/GitlabUser.ts b/NodeApp/src/models/GitlabUser.ts
new file mode 100644
index 0000000..38bcf05
--- /dev/null
+++ b/NodeApp/src/models/GitlabUser.ts
@@ -0,0 +1,18 @@
+import Model from './Model';
+
+
+class GitlabUser extends Model {
+    id: number = -1;
+    username: string = '';
+    name: string = '';
+    state: string = '';
+    avatar_url: string = '';
+    web_url: string = '';
+
+    constructor() {
+        super();
+    }
+}
+
+
+export default GitlabUser;
diff --git a/NodeApp/src/models/User.ts b/NodeApp/src/models/User.ts
index 717acc8..e9b2882 100644
--- a/NodeApp/src/models/User.ts
+++ b/NodeApp/src/models/User.ts
@@ -7,6 +7,7 @@ class User extends Model {
     lastName: string = '';
     mail: string = '';
     role: string = '';
+    isTeachingStaff: boolean = false;
     deleted: boolean = true;
 
     constructor() {
diff --git a/NodeApp/src/Config/LocalConfig/LocalConfigKeys.ts b/NodeApp/src/types/LocalConfigKeys.ts
similarity index 100%
rename from NodeApp/src/Config/LocalConfig/LocalConfigKeys.ts
rename to NodeApp/src/types/LocalConfigKeys.ts
diff --git a/NodeApp/src/types/Permissions.ts b/NodeApp/src/types/Permissions.ts
new file mode 100644
index 0000000..8d91a16
--- /dev/null
+++ b/NodeApp/src/types/Permissions.ts
@@ -0,0 +1,7 @@
+interface Permissions {
+    teachingStaff: boolean,
+    student: boolean
+}
+
+
+export default Permissions;
\ No newline at end of file
-- 
GitLab