7 Commits

Author SHA1 Message Date
Kasra Bigdeli
0fa8643fae Moved uploadCaptainDefinitionContent to handler-- backend is almost done 2025-09-02 23:42:26 -07:00
Kasra Bigdeli
a0247e068e Moved updateConfigAndSave to handler 2025-09-02 23:33:45 -07:00
Kasra Bigdeli
c3abec0367 Moved GetAllApps to handler 2025-09-02 23:18:48 -07:00
Kasra Bigdeli
6a35863cd3 Fixed register project in one click app handler 2025-09-02 23:10:33 -07:00
Kasra Bigdeli
b592ebed6e Unify the definiton of handler resp 2025-09-02 23:06:24 -07:00
Kasra Bigdeli
ef3a4db51f updated project handler 2025-09-02 23:02:33 -07:00
Kasra Bigdeli
c07a202523 Moved register app to handler 2025-09-02 22:57:29 -07:00
10 changed files with 572 additions and 299 deletions

View File

@@ -0,0 +1,3 @@
export interface BaseHandlerResult {
message: string
}

View File

@@ -0,0 +1,46 @@
import { v4 as uuid } from 'uuid'
import DataStore from '../../datastore/DataStore'
import { ProjectDefinition } from '../../models/ProjectDefinition'
import Logger from '../../utils/Logger'
import { BaseHandlerResult } from '../BaseHandlerResult'
export interface RegisterProjectParams {
name: string
parentProjectId?: string
description: string
}
export interface RegisterProjectResult extends BaseHandlerResult {
data: ProjectDefinition
}
export async function registerProject(
params: RegisterProjectParams,
dataStore: DataStore
): Promise<RegisterProjectResult> {
const { name, parentProjectId, description } = params
Logger.d(`Creating project: ${name}`)
try {
const projectId = uuid()
const project = await dataStore
.getProjectsDataStore()
.saveProject(projectId, {
id: projectId,
name: name,
parentProjectId: parentProjectId,
description: description,
})
Logger.d(`Project created: ${name}`)
return {
message: `Project created: ${name}`,
data: project,
}
} catch (error: any) {
Logger.e(`Failed to create project: ${error}`)
throw error
}
}

View File

@@ -0,0 +1,69 @@
import ServiceManager from '../../../../user/ServiceManager'
import Logger from '../../../../utils/Logger'
import { BaseHandlerResult } from '../../../BaseHandlerResult'
export interface UploadCaptainDefinitionContentParams {
appName: string
isDetachedBuild: boolean
captainDefinitionContent?: string
gitHash?: string
uploadedTarPathSource?: string
}
export async function uploadCaptainDefinitionContent(
params: UploadCaptainDefinitionContentParams,
serviceManager: ServiceManager
): Promise<BaseHandlerResult> {
const {
appName,
isDetachedBuild,
captainDefinitionContent,
gitHash,
uploadedTarPathSource,
} = params
const hasTar = !!uploadedTarPathSource
const hasCaptainDef = !!captainDefinitionContent
if (hasTar === hasCaptainDef) {
throw new Error(
'Either tarballfile or captainDefinitionContent should be present.'
)
}
const promiseToDeployNewVer = serviceManager.scheduleDeployNewVersion(
appName,
{
uploadedTarPathSource: hasTar
? {
uploadedTarPath: uploadedTarPathSource as string,
gitHash: `${gitHash || ''}`,
}
: undefined,
captainDefinitionContentSource: hasCaptainDef
? {
captainDefinitionContent:
captainDefinitionContent as string,
gitHash: `${gitHash || ''}`,
}
: undefined,
}
)
if (isDetachedBuild) {
// Avoid unhandled promise rejection
promiseToDeployNewVer.catch(function (err: any) {
Logger.e(err)
})
return {
message: 'Deploy is started',
}
}
await promiseToDeployNewVer
return {
message: 'Deploy is done',
}
}

View File

@@ -0,0 +1,305 @@
import DataStore from '../../../../datastore/DataStore'
import { ICaptainDefinition } from '../../../../models/ICaptainDefinition'
import ServiceManager from '../../../../user/ServiceManager'
import CaptainConstants from '../../../../utils/CaptainConstants'
import Logger from '../../../../utils/Logger'
import ApiStatusCodes from '../../../../api/ApiStatusCodes'
import {
AppDeployTokenConfig,
IAppEnvVar,
IAppPort,
IAppTag,
IAppVolume,
IHttpAuth,
RepoInfo,
} from '../../../../models/AppDefinition'
import { BaseHandlerResult } from '../../../BaseHandlerResult'
export interface RegisterAppDefinitionParams {
appName: string
projectId: string
hasPersistentData: boolean
isDetachedBuild: boolean
}
export async function registerAppDefinition(
params: RegisterAppDefinitionParams,
dataStore: DataStore,
serviceManager: ServiceManager
): Promise<BaseHandlerResult> {
const { appName, projectId, hasPersistentData, isDetachedBuild } = params
let appCreated = false
Logger.d(`Registering app started: ${appName}`)
try {
// Validate project if projectId is provided
if (projectId) {
await dataStore.getProjectsDataStore().getProject(projectId)
// if project is not found, it will throw an error
}
// Register the app definition
await dataStore
.getAppsDataStore()
.registerAppDefinition(appName, projectId, hasPersistentData)
appCreated = true
// Create captain definition content
const captainDefinitionContent: ICaptainDefinition = {
schemaVersion: 2,
imageName: CaptainConstants.configs.appPlaceholderImageName,
}
// Schedule deployment (unless detached build)
const promiseToIgnore = serviceManager
.scheduleDeployNewVersion(appName, {
captainDefinitionContentSource: {
captainDefinitionContent: JSON.stringify(
captainDefinitionContent
),
gitHash: '',
},
})
.catch(function (error) {
Logger.e(error)
})
if (!isDetachedBuild) {
await promiseToIgnore
}
Logger.d(`AppName is saved: ${appName}`)
return {
message: 'App Definition Saved',
}
} catch (error: any) {
// Cleanup if app was created but something failed
if (appCreated) {
try {
await dataStore.getAppsDataStore().deleteAppDefinition(appName)
} catch (cleanupError) {
Logger.e(
`Failed to cleanup app definition after error: ${cleanupError}`
)
}
}
// Re-throw the error
throw error
}
}
export interface GetAllAppDefinitionsResult extends BaseHandlerResult {
data: {
appDefinitions: any[]
rootDomain: string
captainSubDomain: string
defaultNginxConfig: any
}
}
export async function getAllAppDefinitions(
dataStore: DataStore,
serviceManager: ServiceManager
): Promise<GetAllAppDefinitionsResult> {
Logger.d('Getting all app definitions started')
try {
const apps = await dataStore.getAppsDataStore().getAppDefinitions()
const appsArray: any[] = []
Object.keys(apps).forEach(function (key) {
const app = apps[key]
app.appName = key
app.isAppBuilding = serviceManager.isAppBuilding(key)
app.appPushWebhook = app.appPushWebhook || undefined
appsArray.push(app)
})
const defaultNginxConfig = await dataStore.getDefaultAppNginxConfig()
Logger.d(`App definitions retrieved: ${appsArray.length} apps`)
return {
message: 'App definitions are retrieved.',
data: {
appDefinitions: appsArray,
rootDomain: dataStore.getRootDomain(),
captainSubDomain: CaptainConstants.configs.captainSubDomain,
defaultNginxConfig: defaultNginxConfig,
},
}
} catch (error: any) {
Logger.e(`Failed to get app definitions: ${error}`)
throw error
}
}
export interface UpdateAppDefinitionParams {
appName: string
projectId?: string
description?: string
instanceCount?: number | string
captainDefinitionRelativeFilePath?: string
envVars?: IAppEnvVar[]
volumes?: IAppVolume[]
tags?: IAppTag[]
nodeId?: string
notExposeAsWebApp?: boolean
containerHttpPort?: number | string
httpAuth?: any
forceSsl?: boolean
ports?: IAppPort[]
repoInfo?: RepoInfo | any
customNginxConfig?: string
redirectDomain?: string
preDeployFunction?: string
serviceUpdateOverride?: string
websocketSupport?: boolean
appDeployTokenConfig?: AppDeployTokenConfig
}
export async function updateAppDefinition(
params: UpdateAppDefinitionParams,
serviceManager: ServiceManager
): Promise<BaseHandlerResult> {
const {
appName,
projectId,
description,
instanceCount,
captainDefinitionRelativeFilePath,
envVars,
volumes,
tags,
nodeId,
notExposeAsWebApp,
containerHttpPort,
httpAuth,
forceSsl,
ports,
repoInfo: inputRepoInfo,
customNginxConfig,
redirectDomain,
preDeployFunction,
serviceUpdateOverride,
websocketSupport,
appDeployTokenConfig,
} = params
// Defaults & normalization
const normalizedDescription = `${description || ''}`
const instanceCountNum = Number(instanceCount ?? 0)
const containerHttpPortNum = Number(containerHttpPort ?? 80)
const normalizedEnvVars = envVars || []
const normalizedVolumes = volumes || []
const normalizedTags = tags || []
const normalizedPorts = ports || []
const normalizedNotExposeAsWebApp = !!notExposeAsWebApp
const normalizedForceSsl = !!forceSsl
const normalizedWebsocketSupport = !!websocketSupport
const normalizedRedirectDomain = `${redirectDomain || ''}`
const normalizedPreDeployFunction = `${preDeployFunction || ''}`
const normalizedServiceUpdateOverride = `${serviceUpdateOverride || ''}`
let normalizedDeployTokenConfig: AppDeployTokenConfig | undefined
if (!appDeployTokenConfig) {
normalizedDeployTokenConfig = { enabled: false }
} else {
normalizedDeployTokenConfig = {
enabled: !!appDeployTokenConfig.enabled,
appDeployToken: `${
appDeployTokenConfig.appDeployToken
? appDeployTokenConfig.appDeployToken
: ''
}`.trim(),
}
}
const repoInfo: any = inputRepoInfo || {}
if (repoInfo.user) {
repoInfo.user = repoInfo.user.trim()
}
if (repoInfo.repo) {
repoInfo.repo = repoInfo.repo.trim()
}
if (repoInfo.branch) {
repoInfo.branch = repoInfo.branch.trim()
}
if (
(repoInfo.branch ||
repoInfo.user ||
repoInfo.repo ||
repoInfo.password ||
repoInfo.sshKey) &&
(!repoInfo.branch ||
!repoInfo.repo ||
(!repoInfo.sshKey && !repoInfo.user && !repoInfo.password) ||
(repoInfo.password && !repoInfo.user) ||
(repoInfo.user && !repoInfo.password))
) {
throw ApiStatusCodes.createError(
ApiStatusCodes.ILLEGAL_PARAMETER,
'Missing required Github/BitBucket/Gitlab field'
)
}
if (
repoInfo &&
repoInfo.sshKey &&
repoInfo.sshKey.indexOf('ENCRYPTED') > 0 &&
!CaptainConstants.configs.disableEncryptedCheck
) {
throw ApiStatusCodes.createError(
ApiStatusCodes.ILLEGAL_PARAMETER,
'You cannot use encrypted SSH keys'
)
}
if (
repoInfo &&
repoInfo.sshKey &&
repoInfo.sshKey.indexOf('END OPENSSH PRIVATE KEY-----') > 0
) {
repoInfo.sshKey = repoInfo.sshKey.trim()
repoInfo.sshKey = repoInfo.sshKey + '\n'
}
Logger.d(`Updating app started: ${appName}`)
await serviceManager.updateAppDefinition(
appName,
`${projectId || ''}`,
normalizedDescription,
instanceCountNum,
`${captainDefinitionRelativeFilePath || ''}`,
normalizedEnvVars,
normalizedVolumes,
normalizedTags,
`${nodeId || ''}`,
normalizedNotExposeAsWebApp,
containerHttpPortNum,
httpAuth as IHttpAuth,
normalizedForceSsl,
normalizedPorts,
repoInfo,
`${customNginxConfig || ''}`,
normalizedRedirectDomain,
normalizedPreDeployFunction,
normalizedServiceUpdateOverride,
normalizedWebsocketSupport,
normalizedDeployTokenConfig
)
Logger.d(`AppName is updated: ${appName}`)
return {
message: 'Updated App Definition Saved',
}
}

View File

@@ -1,7 +1,7 @@
import express = require('express')
import { v4 as uuid } from 'uuid'
import ApiStatusCodes from '../../api/ApiStatusCodes'
import BaseApi from '../../api/BaseApi'
import { registerProject } from '../../handlers/users/ProjectHandler'
import InjectionExtractor from '../../injection/InjectionExtractor'
import { ProjectDefinition } from '../../models/ProjectDefinition'
import Logger from '../../utils/Logger'
@@ -16,23 +16,15 @@ router.post('/register/', function (req, res, next) {
const parentProjectId = `${req.body.parentProjectId || ''}`.trim()
const description = `${req.body.description || ''}`.trim()
Promise.resolve()
.then(function () {
const projectId = uuid()
return dataStore.getProjectsDataStore().saveProject(projectId, {
id: projectId,
name: projectName,
parentProjectId: parentProjectId,
description: description,
})
})
.then(function (project) {
Logger.d(`Project created: ${projectName}`)
const resp = new BaseApi(
ApiStatusCodes.STATUS_OK,
`Project created: ${projectName}`
)
resp.data = project
return registerProject(
{ name: projectName, parentProjectId, description },
dataStore
)
.then(function (result) {
const resp = new BaseApi(ApiStatusCodes.STATUS_OK, result.message)
if (result.data) {
resp.data = result.data
}
res.send(resp)
})
.catch(ApiStatusCodes.createCatcher(res))

View File

@@ -1,8 +1,8 @@
import express = require('express')
import ApiStatusCodes from '../../../../api/ApiStatusCodes'
import BaseApi from '../../../../api/BaseApi'
import { uploadCaptainDefinitionContent as uploadCaptainDefinitionContentHandler } from '../../../../handlers/users/apps/appdata/AppDataHandler'
import InjectionExtractor from '../../../../injection/InjectionExtractor'
import Logger from '../../../../utils/Logger'
import multer = require('multer')
const TEMP_UPLOAD = 'temp_upload/'
@@ -70,6 +70,7 @@ router.post('/:appName/', function (req, res, next) {
.catch(ApiStatusCodes.createCatcher(res))
})
// uploadCaptainDefinitionContent
router.post(
'/:appName/',
upload.single('sourceFile'),
@@ -79,63 +80,27 @@ router.post(
const appName = req.params.appName
const isDetachedBuild = !!req.query.detached
const captainDefinitionContent =
(req.body.captainDefinitionContent || '') + ''
const gitHash = (req.body.gitHash || '') + ''
const captainDefinitionContent = `${req.body.captainDefinitionContent || ''}`
const gitHash = `${req.body.gitHash || ''}`
const tarballSourceFilePath: string = req.file ? req.file.path : ''
if (!!tarballSourceFilePath === !!captainDefinitionContent) {
res.send(
new BaseApi(
ApiStatusCodes.ILLEGAL_OPERATION,
'Either tarballfile or captainDefinitionContent should be present.'
)
)
return
}
return Promise.resolve().then(function () {
const promiseToDeployNewVer =
serviceManager.scheduleDeployNewVersion(appName, {
uploadedTarPathSource: tarballSourceFilePath
? {
uploadedTarPath: tarballSourceFilePath,
gitHash,
}
: undefined,
captainDefinitionContentSource: captainDefinitionContent
? {
captainDefinitionContent,
gitHash,
}
: undefined,
})
if (isDetachedBuild) {
res.send(
new BaseApi(
ApiStatusCodes.STATUS_OK_DEPLOY_STARTED,
'Deploy is started'
)
)
// To avoid unhandled promise error
promiseToDeployNewVer.catch(function (err) {
Logger.e(err)
})
} else {
promiseToDeployNewVer
.then(function () {
res.send(
new BaseApi(
ApiStatusCodes.STATUS_OK,
'Deploy is done'
)
)
})
.catch(ApiStatusCodes.createCatcher(res))
}
})
return uploadCaptainDefinitionContentHandler(
{
appName,
isDetachedBuild,
captainDefinitionContent: captainDefinitionContent || undefined,
gitHash: gitHash || undefined,
uploadedTarPathSource: tarballSourceFilePath || undefined,
},
serviceManager
)
.then(function (result) {
const status = isDetachedBuild
? ApiStatusCodes.STATUS_OK_DEPLOY_STARTED
: ApiStatusCodes.STATUS_OK
res.send(new BaseApi(status, result.message))
})
.catch(ApiStatusCodes.createCatcher(res))
}
)

View File

@@ -1,12 +1,14 @@
import express = require('express')
import ApiStatusCodes from '../../../../api/ApiStatusCodes'
import BaseApi from '../../../../api/BaseApi'
import {
getAllAppDefinitions,
registerAppDefinition,
updateAppDefinition,
} from '../../../../handlers/users/apps/appdefinition/AppDefinitionHandler'
import InjectionExtractor from '../../../../injection/InjectionExtractor'
import { AppDeployTokenConfig, IAppDef } from '../../../../models/AppDefinition'
import { ICaptainDefinition } from '../../../../models/ICaptainDefinition'
import { CaptainError } from '../../../../models/OtherTypes'
import { AppDeployTokenConfig } from '../../../../models/AppDefinition'
import CaptainManager from '../../../../user/system/CaptainManager'
import CaptainConstants from '../../../../utils/CaptainConstants'
import Logger from '../../../../utils/Logger'
import Utils from '../../../../utils/Utils'
@@ -60,39 +62,14 @@ router.get('/', function (req, res, next) {
InjectionExtractor.extractUserFromInjected(res).user.dataStore
const serviceManager =
InjectionExtractor.extractUserFromInjected(res).user.serviceManager
const appsArray: IAppDef[] = []
return dataStore
.getAppsDataStore()
.getAppDefinitions()
.then(function (apps) {
const promises: Promise<void>[] = []
Object.keys(apps).forEach(function (key, index) {
const app = apps[key]
app.appName = key
app.isAppBuilding = serviceManager.isAppBuilding(key)
app.appPushWebhook = app.appPushWebhook || undefined
appsArray.push(app)
})
return Promise.all(promises)
})
.then(function () {
return dataStore.getDefaultAppNginxConfig()
})
.then(function (defaultNginxConfig) {
return getAllAppDefinitions(dataStore, serviceManager)
.then(function (result) {
const baseApi = new BaseApi(
ApiStatusCodes.STATUS_OK,
'App definitions are retrieved.'
result.message
)
baseApi.data = {
appDefinitions: appsArray,
rootDomain: dataStore.getRootDomain(),
captainSubDomain: CaptainConstants.configs.captainSubDomain,
defaultNginxConfig: defaultNginxConfig,
}
baseApi.data = result.data
res.send(baseApi)
})
.catch(ApiStatusCodes.createCatcher(res))
@@ -190,69 +167,13 @@ router.post('/register/', function (req, res, next) {
const hasPersistentData = !!req.body.hasPersistentData
const isDetachedBuild = !!req.query.detached
let appCreated = false
Logger.d(`Registering app started: ${appName}`)
return Promise.resolve()
.then(function () {
if (projectId) {
return dataStore.getProjectsDataStore().getProject(projectId)
// if project is not found, it will throw an error
}
})
.then(function () {
return dataStore
.getAppsDataStore()
.registerAppDefinition(appName, projectId, hasPersistentData)
})
.then(function () {
appCreated = true
})
.then(function () {
const captainDefinitionContent: ICaptainDefinition = {
schemaVersion: 2,
imageName: CaptainConstants.configs.appPlaceholderImageName,
}
const promiseToIgnore = serviceManager
.scheduleDeployNewVersion(appName, {
captainDefinitionContentSource: {
captainDefinitionContent: JSON.stringify(
captainDefinitionContent
),
gitHash: '',
},
})
.catch(function (error) {
Logger.e(error)
})
if (!isDetachedBuild) return promiseToIgnore
})
.then(function () {
Logger.d(`AppName is saved: ${appName}`)
res.send(
new BaseApi(ApiStatusCodes.STATUS_OK, 'App Definition Saved')
)
})
.catch(function (error: CaptainError) {
function createRejectionPromise() {
return new Promise<void>(function (resolve, reject) {
reject(error)
})
}
if (appCreated) {
return dataStore
.getAppsDataStore()
.deleteAppDefinition(appName)
.then(function () {
return createRejectionPromise()
})
} else {
return createRejectionPromise()
}
return registerAppDefinition(
{ appName, projectId, hasPersistentData, isDetachedBuild },
dataStore,
serviceManager
)
.then(function (result) {
res.send(new BaseApi(ApiStatusCodes.STATUS_OK, result.message))
})
.catch(ApiStatusCodes.createCatcher(res))
})
@@ -329,6 +250,7 @@ router.post('/rename/', function (req, res, next) {
.catch(ApiStatusCodes.createCatcher(res))
})
// Update app configs
router.post('/update/', function (req, res, next) {
const serviceManager =
InjectionExtractor.extractUserFromInjected(res).user.serviceManager
@@ -339,103 +261,33 @@ router.post('/update/', function (req, res, next) {
const captainDefinitionRelativeFilePath =
req.body.captainDefinitionRelativeFilePath
const notExposeAsWebApp = req.body.notExposeAsWebApp
const tags = req.body.tags || []
const tags = req.body.tags
const customNginxConfig = req.body.customNginxConfig
const forceSsl = !!req.body.forceSsl
const websocketSupport = !!req.body.websocketSupport
const forceSsl = req.body.forceSsl
const websocketSupport = req.body.websocketSupport
const repoInfo = req.body.appPushWebhook
? req.body.appPushWebhook.repoInfo || {}
: {}
const envVars = req.body.envVars || []
const volumes = req.body.volumes || []
const ports = req.body.ports || []
const instanceCount = req.body.instanceCount || '0'
const redirectDomain = req.body.redirectDomain || ''
const preDeployFunction = req.body.preDeployFunction || ''
const serviceUpdateOverride = req.body.serviceUpdateOverride || ''
const containerHttpPort = Number(req.body.containerHttpPort) || 80
? req.body.appPushWebhook.repoInfo
: undefined
const envVars = req.body.envVars
const volumes = req.body.volumes
const ports = req.body.ports
const instanceCount = req.body.instanceCount
const redirectDomain = req.body.redirectDomain
const preDeployFunction = req.body.preDeployFunction
const serviceUpdateOverride = req.body.serviceUpdateOverride
const containerHttpPort = req.body.containerHttpPort
const httpAuth = req.body.httpAuth
let appDeployTokenConfig = req.body.appDeployTokenConfig as
const appDeployTokenConfig = req.body.appDeployTokenConfig as
| AppDeployTokenConfig
| undefined
const description = req.body.description || ''
const description = req.body.description
if (!appDeployTokenConfig) {
appDeployTokenConfig = { enabled: false }
} else {
appDeployTokenConfig = {
enabled: !!appDeployTokenConfig.enabled,
appDeployToken: `${
appDeployTokenConfig.appDeployToken
? appDeployTokenConfig.appDeployToken
: ''
}`.trim(),
}
}
if (repoInfo.user) {
repoInfo.user = repoInfo.user.trim()
}
if (repoInfo.repo) {
repoInfo.repo = repoInfo.repo.trim()
}
if (repoInfo.branch) {
repoInfo.branch = repoInfo.branch.trim()
}
if (
(repoInfo.branch ||
repoInfo.user ||
repoInfo.repo ||
repoInfo.password ||
repoInfo.sshKey) &&
(!repoInfo.branch ||
!repoInfo.repo ||
(!repoInfo.sshKey && !repoInfo.user && !repoInfo.password) ||
(repoInfo.password && !repoInfo.user) ||
(repoInfo.user && !repoInfo.password))
) {
res.send(
new BaseApi(
ApiStatusCodes.ILLEGAL_PARAMETER,
'Missing required Github/BitBucket/Gitlab field'
)
)
return
}
if (
repoInfo &&
repoInfo.sshKey &&
repoInfo.sshKey.indexOf('ENCRYPTED') > 0 &&
!CaptainConstants.configs.disableEncryptedCheck
) {
res.send(
new BaseApi(
ApiStatusCodes.ILLEGAL_PARAMETER,
'You cannot use encrypted SSH keys'
)
)
return
}
if (
repoInfo &&
repoInfo.sshKey &&
repoInfo.sshKey.indexOf('END OPENSSH PRIVATE KEY-----') > 0
) {
repoInfo.sshKey = repoInfo.sshKey.trim()
repoInfo.sshKey = repoInfo.sshKey + '\n'
}
Logger.d(`Updating app started: ${appName}`)
return serviceManager
.updateAppDefinition(
return updateAppDefinition(
{
appName,
projectId,
description,
Number(instanceCount),
instanceCount,
captainDefinitionRelativeFilePath,
envVars,
volumes,
@@ -452,16 +304,12 @@ router.post('/update/', function (req, res, next) {
preDeployFunction,
serviceUpdateOverride,
websocketSupport,
appDeployTokenConfig
)
.then(function () {
Logger.d(`AppName is updated: ${appName}`)
res.send(
new BaseApi(
ApiStatusCodes.STATUS_OK,
'Updated App Definition Saved'
)
)
appDeployTokenConfig,
},
serviceManager
)
.then(function (result) {
res.send(new BaseApi(ApiStatusCodes.STATUS_OK, result.message))
})
.catch(ApiStatusCodes.createCatcher(res))
})

View File

@@ -272,6 +272,11 @@ router.get('/template/app', function (req, res, next) {
})
router.post('/deploy', function (req, res, next) {
const dataStore =
InjectionExtractor.extractUserFromInjected(res).user.dataStore
const serviceManager =
InjectionExtractor.extractUserFromInjected(res).user.serviceManager
const template = req.body.template
const values = req.body.values
const deploymentJobRegistry = OneClickDeploymentJobRegistry.getInstance()
@@ -291,13 +296,20 @@ router.post('/deploy', function (req, res, next) {
Logger.dev(`Template: ${JSON.stringify(template, null, 2)}`)
Logger.dev(`Values: ${JSON.stringify(values, null, 2)}`)
new OneClickAppDeployManager((deploymentState) => {
deploymentJobRegistry.updateJobProgress(jobId, deploymentState)
Logger.dev(`Deployment state updated for jobId: ${jobId}`)
Logger.dev(
`Deployment state: ${JSON.stringify(deploymentState, null, 2)}`
)
}).startDeployProcess(template, values)
new OneClickAppDeployManager(
dataStore,
serviceManager,
(deploymentState) => {
deploymentJobRegistry.updateJobProgress(
jobId,
deploymentState
)
Logger.dev(`Deployment state updated for jobId: ${jobId}`)
Logger.dev(
`Deployment state: ${JSON.stringify(deploymentState, null, 2)}`
)
}
).startDeployProcess(template, values)
const baseApi = new BaseApi(
ApiStatusCodes.STATUS_OK,

View File

@@ -1,9 +1,11 @@
import DataStore from '../../datastore/DataStore'
import { IHashMapGeneric } from '../../models/ICacheGeneric'
import {
IDockerComposeService,
IOneClickTemplate,
} from '../../models/IOneClickAppModels'
import Utils from '../../utils/Utils'
import ServiceManager from '../ServiceManager'
import OneClickAppDeploymentHelper from './OneClickAppDeploymentHelper'
export const ONE_CLICK_APP_NAME_VAR_NAME = '$$cap_appname'
@@ -28,15 +30,19 @@ function replaceWith(
}
export default class OneClickAppDeployManager {
private deploymentHelper: OneClickAppDeploymentHelper =
new OneClickAppDeploymentHelper()
private deploymentHelper: OneClickAppDeploymentHelper
private template: IOneClickTemplate | undefined
constructor(
dataStore: DataStore,
serviceManager: ServiceManager,
private onDeploymentStateChanged: (
deploymentState: IDeploymentState
) => void
) {
//
this.deploymentHelper = new OneClickAppDeploymentHelper(
dataStore,
serviceManager
)
}
startDeployProcess(

View File

@@ -1,52 +1,79 @@
import DataStore from '../../datastore/DataStore'
import { uploadCaptainDefinitionContent as uploadCaptainDefinitionContentHandler } from '../../handlers/users/apps/appdata/AppDataHandler'
import {
getAllAppDefinitions,
registerAppDefinition,
updateAppDefinition,
} from '../../handlers/users/apps/appdefinition/AppDefinitionHandler'
import { registerProject } from '../../handlers/users/ProjectHandler'
import { IAppDef } from '../../models/AppDefinition'
import { ICaptainDefinition } from '../../models/ICaptainDefinition'
import { IDockerComposeService } from '../../models/IOneClickAppModels'
import { ProjectDefinition } from '../../models/ProjectDefinition'
import DockerComposeToServiceOverride from '../../utils/DockerComposeToServiceOverride'
import Utils from '../../utils/Utils'
// TODO - Replace with actual API implementation
// Step 1- find the endpoint using the mapping here:
// https://github.com/caprover/caprover-api/blob/master/src/api/ApiManager.ts
// Step 2- Lookup the implementation based on the path found above
// Step 3- Replace the mock implementation below with the implementation found in step 2
import ServiceManager from '../ServiceManager'
class ApiManager {
constructor(
private dataStore: DataStore,
private serviceManager: ServiceManager
) {
// Initialize if needed
}
registerNewApp(
appName: string,
projectId: string,
hasVolume: boolean,
skipIfExists: boolean
hasPersistentData: boolean,
isDetachedBuild: boolean
): Promise<any> {
// Mock implementation
return Promise.resolve({ success: true })
return registerAppDefinition(
{
appName,
projectId,
hasPersistentData,
isDetachedBuild,
},
this.dataStore,
this.serviceManager
)
}
registerProject(projectDef: ProjectDefinition): Promise<any> {
// Mock implementation
return Promise.resolve({ id: 'mock-project-id' })
return registerProject(projectDef, this.dataStore)
}
getAllApps(): Promise<any> {
// Mock implementation
return Promise.resolve({ appDefinitions: [] })
return getAllAppDefinitions(this.dataStore, this.serviceManager)
}
updateConfigAndSave(appName: string, appDef: IAppDef): Promise<any> {
// Mock implementation
return Promise.resolve({ success: true })
return updateAppDefinition({ appName, ...appDef }, this.serviceManager)
}
uploadCaptainDefinitionContent(
appName: string,
captainDefinition: ICaptainDefinition,
tarFileBase64: string,
skipIfExists: boolean
gitHash: string,
isDetachedBuild: boolean
): Promise<any> {
// Mock implementation
return Promise.resolve({ success: true })
const captainDefinitionContent = JSON.stringify(captainDefinition)
return uploadCaptainDefinitionContentHandler(
{
appName,
isDetachedBuild,
captainDefinitionContent,
gitHash,
},
this.serviceManager
)
}
}
export default class OneClickAppDeploymentHelper {
private apiManager: ApiManager = new ApiManager()
private apiManager: ApiManager
constructor(dataStore: DataStore, serviceManager: ServiceManager) {
this.apiManager = new ApiManager(dataStore, serviceManager)
}
createRegisterPromise(
appName: string,