diff --git a/NodeApp/src/commander/CommanderApp.ts b/NodeApp/src/commander/CommanderApp.ts index 37dbc06cd7783fa8de53d828417c22e981bd9769..8fad32ae891aaca66f2bf1471671110bf9e7078a 100644 --- a/NodeApp/src/commander/CommanderApp.ts +++ b/NodeApp/src/commander/CommanderApp.ts @@ -1,7 +1,8 @@ -import { Command } from 'commander'; -import Config from '../config/Config'; -import EnonceCommand from './enonce/EnonceCommand'; -import SessionCommand from './session/SessionCommand'; +import { Command } from 'commander'; +import Config from '../config/Config'; +import EnonceCommand from './enonce/EnonceCommand'; +import SessionCommand from './session/SessionCommand'; +import ExerciceCommand from './exercice/ExerciceCommand'; class CommanderApp { @@ -32,6 +33,7 @@ class CommanderApp { private registerCommands() { SessionCommand.registerOnCommand(this.program); EnonceCommand.registerOnCommand(this.program); + ExerciceCommand.registerOnCommand(this.program); } } diff --git a/NodeApp/src/commander/exercice/ExerciceCommand.ts b/NodeApp/src/commander/exercice/ExerciceCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..7833c5719a10af723351c59511095535a4f5a8a9 --- /dev/null +++ b/NodeApp/src/commander/exercice/ExerciceCommand.ts @@ -0,0 +1,33 @@ +import CommanderCommand from '../CommanderCommand'; +import ExerciceCreateCommand from './ExerciceCreateCommand'; + + +class ExerciceCommand extends CommanderCommand { + protected commandName: string = 'exercice'; + + private static _instance: ExerciceCommand; + + private constructor() { super(); } + + public static get instance(): ExerciceCommand { + if ( !ExerciceCommand._instance ) { + ExerciceCommand._instance = new ExerciceCommand(); + } + + return ExerciceCommand._instance; + } + + protected defineCommand() { + this.command + .description('manage an exercice'); + } + + protected defineSubCommands() { + ExerciceCreateCommand.registerOnCommand(this.command); + } + + protected async commandAction(options: any): Promise<void> { } +} + + +export default ExerciceCommand.instance; \ No newline at end of file diff --git a/NodeApp/src/commander/exercice/ExerciceCreateCommand.ts b/NodeApp/src/commander/exercice/ExerciceCreateCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3c0fdc9d6893856a087a7e2d78634a111c6c435 --- /dev/null +++ b/NodeApp/src/commander/exercice/ExerciceCreateCommand.ts @@ -0,0 +1,114 @@ +import CommanderCommand from '../CommanderCommand'; +import chalk from 'chalk'; +import GitlabManager from '../../managers/GitlabManager'; +import SessionManager from '../../managers/SessionManager'; +import GitlabUser from '../../shared/types/Gitlab/GitlabUser'; +import Enonce from '../../types/Enonce'; +import ora from 'ora'; +import DojoBackendManager from '../../managers/DojoBackendManager'; +import Exercice from '../../types/Exercice'; + + +class ExerciceCreateCommand extends CommanderCommand { + protected commandName: string = 'create'; + + private static _instance: ExerciceCreateCommand; + + private constructor() { super(); } + + public static get instance(): ExerciceCreateCommand { + if ( !ExerciceCreateCommand._instance ) { + ExerciceCreateCommand._instance = new ExerciceCreateCommand(); + } + + return ExerciceCreateCommand._instance; + } + + protected defineCommand() { + this.command + .description('create a new exercice from an enonce') + .requiredOption('-e, --enonce <value>', 'enonce source (Dojo enonce ID, Dojo enonce name or Gitlab enonce URL)') + .option('-i, --members_id <ids...>', 'list of gitlab members ids (group\'s student) to add to the repository') + .option('-u, --members_username <usernames...>', 'list of gitlab members username (group\'s student) to add to the repository') + .action(this.commandAction.bind(this)); + } + + private async checkAccesses(): Promise<boolean> { + let sessionResult = await SessionManager.testSession(true, [ 'student' ]); + + if ( !sessionResult ) { + return false; + } + + return (await GitlabManager.testToken(true)).every(result => result); + } + + protected async commandAction(options: any): Promise<void> { + let members!: Array<GitlabUser> | false; + let enonce!: Enonce | undefined; + + // Check access and retrieve data + { + console.log(chalk.cyan('Please wait while we verify and retrieve data...')); + + if ( !await this.checkAccesses() ) { + return; + } + + members = await GitlabManager.fetchMembers(options); + if ( !members ) { + return; + } + + ora('Checking enonce:').start().info(); + const enonceGetSpinner: ora.Ora = ora({ + text : 'Checking if enonce exists', + indent: 4 + }).start(); + enonce = await DojoBackendManager.getEnonce(options.enonce); + if ( !enonce ) { + enonceGetSpinner.fail(`Enonce "${ options.enonce }" doesn't exists`); + return; + } + enonceGetSpinner.succeed(`Enonce "${ options.enonce }" exists`); + + const enoncePublishedSpinner: ora.Ora = ora({ + text : 'Checking if enonce is published', + indent: 4 + }).start(); + //TODO : Check if the enonce is published + //if ( false ) { + //enoncePublishedSpinner.fail(`Enonce "${ enonce.name }" isn't published`); + //return; + //} + enoncePublishedSpinner.succeed(`Enonce "${ enonce.name }" is published`); + } + + //Create the exercice + { + console.log(chalk.cyan('Please wait while we are creating the exercice...')); + + try { + const exercice: Exercice = await DojoBackendManager.createExercice((enonce as Enonce).name, members); + + const oraInfo = (message: string) => { + ora({ + text : message, + indent: 4 + }).start().info(); + }; + + oraInfo(`${ chalk.magenta('Id:') } ${ exercice.id }`); + oraInfo(`${ chalk.magenta('Name:') } ${ exercice.name }`); + oraInfo(`${ chalk.magenta('Web URL:') } ${ exercice.gitlabCreationInfo.web_url }`); + oraInfo(`${ chalk.magenta('HTTP Repo:') } ${ exercice.gitlabCreationInfo.http_url_to_repo }`); + oraInfo(`${ chalk.magenta('SSH Repo:') } ${ exercice.gitlabCreationInfo.ssh_url_to_repo }`); + } catch ( error ) { + return; + } + } + } +} + + +export default ExerciceCreateCommand.instance; \ No newline at end of file diff --git a/NodeApp/src/managers/DojoBackendManager.ts b/NodeApp/src/managers/DojoBackendManager.ts index 1f36638118b1b6f8f8eee6fced3712936f19974c..151168612b588897cdece6b6c4be949825c80498 100644 --- a/NodeApp/src/managers/DojoBackendManager.ts +++ b/NodeApp/src/managers/DojoBackendManager.ts @@ -6,6 +6,7 @@ import { StatusCodes } from 'http-status-codes'; import Enonce from '../types/Enonce'; import GitlabUser from '../shared/types/Gitlab/GitlabUser'; import DojoResponse from '../types/DojoResponse'; +import Exercice from '../types/Exercice'; class DojoBackendManager { @@ -106,6 +107,40 @@ class DojoBackendManager { throw error; } } + + public async createExercice(enonceName: string, members: Array<GitlabUser>, verbose: boolean = true): Promise<Exercice> { + const spinner: ora.Ora = ora('Creating exercice...'); + + if ( verbose ) { + spinner.start(); + } + + try { + const response = await axios.post<DojoResponse<Exercice>>(this.getApiUrl(ApiRoutes.EXERCICE_CREATE).replace('{{nameOrUrl}}', String(enonceName)), { members: JSON.stringify(members) }); + + if ( verbose ) { + spinner.succeed(`Exercice successfully created`); + } + + return response.data.data; + } catch ( error ) { + if ( verbose ) { + if ( error instanceof AxiosError ) { + if ( error.response ) { + if ( error.response.status === StatusCodes.CONFLICT ) { + spinner.fail(`You've reached the max number of exercice of this enonce.`); + } else { + spinner.fail(`Exercice creation error: ${ error.response.statusText }`); + } + } + } else { + spinner.fail(`Exercice creation error: unknown error`); + } + } + + throw error; + } + } } diff --git a/NodeApp/src/types/ApiRoutes.ts b/NodeApp/src/types/ApiRoutes.ts index cc62f3f7fe264915d2a6ac92fe5ac5f0b2895791..63f492a3a0620f32b4d1c498051704416306ef4c 100644 --- a/NodeApp/src/types/ApiRoutes.ts +++ b/NodeApp/src/types/ApiRoutes.ts @@ -4,6 +4,7 @@ enum ApiRoutes { GITLAB_CHECK_TEMPLATE_ACCESS = '/gitlab/project/{{id}}/checkTemplateAccess', ENONCE_GET = '/enonces/{{nameOrUrl}}', ENONCE_CREATE = '/enonces', + EXERCICE_CREATE = '/enonces/{{nameOrUrl}}/exercices', } diff --git a/NodeApp/src/types/Exercice.ts b/NodeApp/src/types/Exercice.ts new file mode 100644 index 0000000000000000000000000000000000000000..12bb8da658e209f25f535b08554b73864fea3b07 --- /dev/null +++ b/NodeApp/src/types/Exercice.ts @@ -0,0 +1,16 @@ +import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; + + +interface Exercice { + id: string; + enonceName: string; + name: string; + gitlabId: number; + gitlabLink: string; + gitlabCreationInfo: GitlabRepository; + gitlabLastInfo: GitlabRepository; + gitlabLastInfoTs: number; +} + + +export default Exercice; \ No newline at end of file