diff --git a/src/datastore/ProjectsDataStore.ts b/src/datastore/ProjectsDataStore.ts index 8b571fa..9d7602f 100644 --- a/src/datastore/ProjectsDataStore.ts +++ b/src/datastore/ProjectsDataStore.ts @@ -3,6 +3,24 @@ import ApiStatusCodes from '../api/ApiStatusCodes' import AppsDataStore from './AppsDataStore' const PROJECTS_DEFINITIONS = 'projectsDefinitions' + +function isNameAllowed(name: string) { + const isNameFormattingOk = + !!name && + name.length < 50 && + /^[a-z]/.test(name) && + /[a-z0-9]$/.test(name) && + /^[a-z0-9\-]+$/.test(name) && + name.indexOf('--') < 0 + return isNameFormattingOk && ['captain', 'root'].indexOf(name) < 0 +} + +function isValidUUID(uuid: string): boolean { + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i + return uuidRegex.test(uuid) +} + class ProjectsDataStore { constructor( private data: configstore, @@ -11,26 +29,86 @@ class ProjectsDataStore { saveProject(projectId: string, project: ProjectDefinition) { const self = this + projectId = `${projectId || ''}`.trim() + return Promise.resolve().then(function () { + project.name = `${project.name || ''}`.trim() + project.id = `${project.id || ''}`.trim() + project.description = `${project.description || ''}`.trim() + + if (!isNameAllowed(project.name)) { + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_OPERATION, + 'Project name is not allowed' + ) + } + + if (project.id !== projectId) { + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_OPERATION, + 'Project ID does not match' + ) + } + + if (!isValidUUID(project.id)) { + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_OPERATION, + 'Project ID is not a valid UUID' + ) + } + + const projectToSave: ProjectDefinition = { + id: project.id, + name: project.name, + description: project.description, + } + return self.data.set( `${PROJECTS_DEFINITIONS}.${projectId}`, - project + projectToSave ) }) } - getProject(projectName: string) { + getAllProjects(): Promise { const self = this + return Promise.resolve() + .then(function () { + return self.data.get(PROJECTS_DEFINITIONS) + }) + .then(function (projects) { + projects = projects || {} + return Object.keys(projects).map((key) => projects[key]) || [] + }) + } + + getProject(projectId: string) { + const self = this + projectId = `${projectId || ''}`.trim() return Promise.resolve().then(function () { return self.data.get( - `${PROJECTS_DEFINITIONS}.${projectName}` + `${PROJECTS_DEFINITIONS}.${projectId}` ) as ProjectDefinition | undefined }) } deleteProject(projectId: string) { const self = this + + projectId = `${projectId || ''}`.trim() + return Promise.resolve() + .then(function () { + return self.getProject(projectId) + }) + .then(function (project) { + if (!project) { + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_OPERATION, + 'Project not found' + ) + } + }) .then(function () { return self.appsDataStore.getAppDefinitions() }) diff --git a/src/routes/user/ProjectsRouter.ts b/src/routes/user/ProjectsRouter.ts new file mode 100644 index 0000000..de301d5 --- /dev/null +++ b/src/routes/user/ProjectsRouter.ts @@ -0,0 +1,114 @@ +import express = require('express') +import { v4 as uuid } from 'uuid' +import ApiStatusCodes from '../../api/ApiStatusCodes' +import BaseApi from '../../api/BaseApi' +import InjectionExtractor from '../../injection/InjectionExtractor' +import Logger from '../../utils/Logger' + +const router = express.Router() + +router.post('/register/', function (req, res, next) { + const dataStore = + InjectionExtractor.extractUserFromInjected(res).user.dataStore + + const projectName = req.body.projectName as string + + Promise.resolve() + .then(function () { + const projectId = uuid() + return dataStore.getProjectsDataStore().saveProject(projectId, { + id: projectId, + name: projectName, + description: '', + }) + }) + .then(function () { + Logger.d(`Project is saved: ${projectName}`) + res.send( + new BaseApi(ApiStatusCodes.STATUS_OK, 'App Definition Saved') + ) + }) + .catch(ApiStatusCodes.createCatcher(res)) +}) + +router.post('/delete/', function (req, res, next) { + const dataStore = + InjectionExtractor.extractUserFromInjected(res).user.dataStore + + const projectId = req.body.projectId + + Promise.resolve() + .then(function () { + return dataStore // + .getProjectsDataStore() + .deleteProject(projectId) + }) + .then(function () { + Logger.d(`Project is deleted: ${projectId}`) + res.send(new BaseApi(ApiStatusCodes.STATUS_OK, 'Project deleted')) + }) + .catch(ApiStatusCodes.createCatcher(res)) +}) + +router.post('/update/', function (req, res, next) { + const dataStore = + InjectionExtractor.extractUserFromInjected(res).user.dataStore + + const projectDefinition = req.body.projectDefinition as + | ProjectDefinition + | undefined + + Promise.resolve() + .then(function () { + if (!projectDefinition) { + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_OPERATION, + 'Project Definition is not provided' + ) + } + + if (!projectDefinition.id) { + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_OPERATION, + 'Project ID is not provided' + ) + } + + return dataStore + .getProjectsDataStore() + .saveProject(projectDefinition.id, { + id: projectDefinition.id, + name: `${projectDefinition.name || ''}`, + description: `${projectDefinition.description || ''}`, + }) + }) + .then(function () { + Logger.d(`Project is saved: ${projectDefinition?.name}`) + res.send(new BaseApi(ApiStatusCodes.STATUS_OK, 'Project Saved')) + }) + .catch(ApiStatusCodes.createCatcher(res)) +}) + +// Get All Projects +router.get('/', function (req, res, next) { + const dataStore = + InjectionExtractor.extractUserFromInjected(res).user.dataStore + + dataStore + .getProjectsDataStore() + .getAllProjects() + .then(function (projects) { + const baseApi = new BaseApi( + ApiStatusCodes.STATUS_OK, + 'Projects are retrieved.' + ) + baseApi.data = { + projects: projects, + } + + res.send(baseApi) + }) + .catch(ApiStatusCodes.createCatcher(res)) +}) + +export default router diff --git a/src/routes/user/UserRouter.ts b/src/routes/user/UserRouter.ts index 05b67af..e469f54 100644 --- a/src/routes/user/UserRouter.ts +++ b/src/routes/user/UserRouter.ts @@ -9,6 +9,7 @@ import Utils from '../../utils/Utils' import AppsRouter from './apps/AppsRouter' import OneClickAppRouter from './oneclick/OneClickAppRouter' import ProRouter from './pro/ProRouter' +import ProjectsRouter from './ProjectsRouter' import RegistriesRouter from './registeries/RegistriesRouter' import SystemRouter from './system/SystemRouter' import onFinished = require('on-finished') @@ -124,6 +125,8 @@ router.post('/changepassword/', function (req, res, next) { router.use('/apps/', AppsRouter) +router.use('/projects/', ProjectsRouter) + router.use('/oneclick/', OneClickAppRouter) router.use('/registries/', RegistriesRouter)