diff --git a/packages/backend/.eslintrc.json b/packages/backend/.eslintrc.json index b1e97cd0..da3b01c6 100644 --- a/packages/backend/.eslintrc.json +++ b/packages/backend/.eslintrc.json @@ -24,6 +24,7 @@ { "allowArgumentsExplicitlyTypedAsAny": true } - ] + ], + "@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }] } } diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 4716af25..db08672b 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -8,7 +8,7 @@ import { User } from './entity/User'; import { Organization } from './entity/Organization'; import { UserOrganization } from './entity/UserOrganization'; import { Project } from './entity/Project'; -import { Deployment } from './entity/Deployment'; +import { Deployment, Environment } from './entity/Deployment'; import { ProjectMember } from './entity/ProjectMember'; import { EnvironmentVariable } from './entity/EnvironmentVariable'; @@ -243,4 +243,36 @@ export class Database { return false; } } + + async redeployToProdById (deploymentId: string): Promise { + const deploymentRepository = this.dataSource.getRepository(Deployment); + const deployment = await deploymentRepository.findOne({ + relations: { + project: true, + domain: true + }, + where: { + id: Number(deploymentId) + } + }); + + if (deployment === null) { + throw new Error('Deployment not found'); + } + const { id, createdAt, updatedAt, ...updatedDeployment } = deployment; + + if (updatedDeployment.environment === Environment.Production) { + // TODO: Put isCurrent field in project + updatedDeployment.isCurrent = true; + } + + await deploymentRepository.update({ id: Number(deploymentId) }, { domain: null, isCurrent: false }); + const savedUpdatedDeployment = await deploymentRepository.save(updatedDeployment); + + if (savedUpdatedDeployment) { + return true; + } else { + return false; + } + } } diff --git a/packages/backend/src/entity/Deployment.ts b/packages/backend/src/entity/Deployment.ts index eb0ea9a1..1d2e20c2 100644 --- a/packages/backend/src/entity/Deployment.ts +++ b/packages/backend/src/entity/Deployment.ts @@ -35,7 +35,7 @@ export class Deployment { @OneToOne(() => Domain) @JoinColumn({ name: 'domainId' }) - domain!: Domain; + domain!: Domain | null; @Column('varchar') branch!: string; diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 7b4508d5..7d049296 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -146,6 +146,15 @@ export const createResolvers = async (db: Database): Promise => { log(err); return false; } + }, + + redeployToProd: async (_: any, { deploymentId }: {deploymentId: string }) => { + try { + return db.redeployToProdById(deploymentId); + } catch (err) { + log(err); + return false; + } } } }; diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index a0c6c13c..aaf678e3 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -127,6 +127,7 @@ type Mutation { addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean! updateDeploymentToProd(deploymentId: String!): Boolean! updateProject(projectId: String!, updateProject: UpdateProjectInput): Boolean! + redeployToProd(deploymentId: String!): Boolean! } input AddEnvironmentVariableInput { diff --git a/packages/backend/test/fixtures/deployments.json b/packages/backend/test/fixtures/deployments.json index 73c869fa..d55da5a9 100644 --- a/packages/backend/test/fixtures/deployments.json +++ b/packages/backend/test/fixtures/deployments.json @@ -5,7 +5,7 @@ "title": "nextjs-boilerplate-1", "status": "Building", "environment": "Production", - "isCurrent": false, + "isCurrent": true, "branch": "prod", "commitHash": "testXyz" }, @@ -15,7 +15,7 @@ "title": "nextjs-boilerplate-2", "status": "Ready", "environment": "Preview", - "isCurrent": true, + "isCurrent": false, "branch": "prod", "commitHash": "testXyz" }, @@ -35,7 +35,7 @@ "title": "nextjs-boilerplate-1", "status": "Building", "environment": "Production", - "isCurrent": false, + "isCurrent": true, "branch": "prod", "commitHash": "testXyz" }, @@ -45,7 +45,7 @@ "title": "nextjs-boilerplate-2", "status": "Ready", "environment": "Preview", - "isCurrent": true, + "isCurrent": false, "branch": "prod", "commitHash": "testXyz" }, @@ -65,7 +65,7 @@ "title": "nextjs-boilerplate-1", "status": "Building", "environment": "Production", - "isCurrent": false, + "isCurrent": true, "branch": "prod", "commitHash": "testXyz" }, @@ -75,7 +75,7 @@ "title": "nextjs-boilerplate-2", "status": "Ready", "environment": "Preview", - "isCurrent": true, + "isCurrent": false, "branch": "prod", "commitHash": "testXyz" }, diff --git a/packages/backend/test/initialize-db.ts b/packages/backend/test/initialize-db.ts index 7b763199..e89de0c1 100644 --- a/packages/backend/test/initialize-db.ts +++ b/packages/backend/test/initialize-db.ts @@ -117,14 +117,12 @@ const main = async () => { await dataSource.initialize(); await generateTestData(dataSource); + log('Data loaded successfully'); } else { - throw new Error('Database already exists'); + log('WARNING: Database already exists'); } }; -main().then(() => { - log('Data loaded successfully'); -}) - .catch((err) => { - log(err); - }); +main().catch((err) => { + log(err); +}); diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx index 7727f5f1..d67d02d2 100644 --- a/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx +++ b/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx @@ -50,6 +50,16 @@ const DeploymentDetailsCard = ({ } }; + const redeployToProd = async () => { + const isRedeployed = await client.redeployToProd(deployment.id); + if (isRedeployed) { + await onUpdate(); + toast.success('Redeployed to production'); + } else { + toast.error('Unable to redeploy to production'); + } + }; + return (
@@ -63,7 +73,9 @@ const DeploymentDetailsCard = ({ />
- {deployment.isProduction ? 'Production (Current)' : 'Preview'} + {deployment.isProduction + ? `Production ${deployment.isCurrent ? '(Current)' : ''}` + : 'Preview'}
@@ -94,6 +106,7 @@ const DeploymentDetailsCard = ({
setRedeployToProduction(!redeployToProduction)} + disabled={!(deployment.isProduction && deployment.isCurrent)} > ^ Redeploy to production @@ -113,8 +126,8 @@ const DeploymentDetailsCard = ({ open={changeToProduction} confirmButtonTitle="Change" color="blue" - handleConfirm={() => { - updateDeployment(); + handleConfirm={async () => { + await updateDeployment(); setChangeToProduction((preVal) => !preVal); }} > @@ -140,7 +153,10 @@ const DeploymentDetailsCard = ({ open={redeployToProduction} confirmButtonTitle="Redeploy" color="blue" - handleConfirm={() => setRedeployToProduction((preVal) => !preVal)} + handleConfirm={async () => { + await redeployToProd(); + setRedeployToProduction((preVal) => !preVal); + }} >
diff --git a/packages/gql-client/src/client.ts b/packages/gql-client/src/client.ts index 720b6194..39e6406b 100644 --- a/packages/gql-client/src/client.ts +++ b/packages/gql-client/src/client.ts @@ -1,8 +1,8 @@ import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables, getProject } from './queries'; -import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput } from './types'; -import { removeMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation } from './mutations'; +import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput, RedeployToProdResponse } from './types'; +import { removeMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd } from './mutations'; export interface GraphQLConfig { gqlEndpoint: string; @@ -147,4 +147,15 @@ export class GQLClient { return data; } + + async redeployToProd (deploymentId: string): Promise { + const { data } = await this.client.mutate({ + mutation: redeployToProd, + variables: { + deploymentId + } + }); + + return data; + } } diff --git a/packages/gql-client/src/mutations.ts b/packages/gql-client/src/mutations.ts index 3cc81968..242b17b9 100644 --- a/packages/gql-client/src/mutations.ts +++ b/packages/gql-client/src/mutations.ts @@ -21,5 +21,10 @@ mutation ($deploymentId: String!) { export const updateProjectMutation = gql` mutation ($projectId: String!, $updateProject: UpdateProjectInput) { updateProject(projectId: $projectId, updateProject: $updateProject) +}`; + +export const redeployToProd = gql` +mutation ($deploymentId: String!) { + redeployToProd(deploymentId: $deploymentId) } `; diff --git a/packages/gql-client/src/types.ts b/packages/gql-client/src/types.ts index ace1ace3..a55fea2b 100644 --- a/packages/gql-client/src/types.ts +++ b/packages/gql-client/src/types.ts @@ -182,3 +182,7 @@ export type UpdateProjectInput = { name: string description: string } + +export type RedeployToProdResponse = { + redeployToProd: boolean +}