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