forked from cerc-io/snowballtools-base
		
	Create new deployment on changing preview deployment to production (#61)
* Create new deployment when changing to production * Remove unnecessary todos * Move deployment id and url creation in database method * Display correct details in deployment dialog box * Rename relativeTime function to relativeTimeISO * Refactor resolver methods to service class * Refactor to move github app to service class --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
		
							parent
							
								
									e0001466e0
								
							
						
					
					
						commit
						bd6a6b330c
					
				| @ -2,6 +2,8 @@ import { DataSource, DeepPartial, FindManyOptions, FindOneOptions, FindOptionsWh | ||||
| import path from 'path'; | ||||
| import debug from 'debug'; | ||||
| import assert from 'assert'; | ||||
| import { customAlphabet } from 'nanoid'; | ||||
| import { lowercase, numbers } from 'nanoid-dictionary'; | ||||
| 
 | ||||
| import { DatabaseConfig } from './config'; | ||||
| import { User } from './entity/User'; | ||||
| @ -15,6 +17,8 @@ import { PROJECT_DOMAIN } from './constants'; | ||||
| 
 | ||||
| const log = debug('snowball:database'); | ||||
| 
 | ||||
| const nanoid = customAlphabet(lowercase + numbers, 8); | ||||
| 
 | ||||
| // TODO: Fix order of methods
 | ||||
| export class Database { | ||||
|   private dataSource: DataSource; | ||||
| @ -171,7 +175,16 @@ export class Database { | ||||
| 
 | ||||
|   async addDeployement (data: DeepPartial<Deployment>): Promise<Deployment> { | ||||
|     const deploymentRepository = this.dataSource.getRepository(Deployment); | ||||
|     const deployment = await deploymentRepository.save(data); | ||||
| 
 | ||||
|     const id = nanoid(); | ||||
|     const url = `${data.project!.name}-${id}.${PROJECT_DOMAIN}`; | ||||
| 
 | ||||
|     const updatedData = { | ||||
|       ...data, | ||||
|       id, | ||||
|       url | ||||
|     }; | ||||
|     const deployment = await deploymentRepository.save(updatedData); | ||||
| 
 | ||||
|     return deployment; | ||||
|   } | ||||
|  | ||||
| @ -19,10 +19,6 @@ export const main = async (): Promise<void> => { | ||||
|   // TODO: get config path using cli
 | ||||
|   const { server, database, githubOauth } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH); | ||||
| 
 | ||||
|   const db = new Database(database); | ||||
|   await db.init(); | ||||
|   const service = new Service(db); | ||||
| 
 | ||||
|   // TODO: Move to Service class
 | ||||
|   const app = new OAuthApp({ | ||||
|     clientType: 'oauth-app', | ||||
| @ -30,8 +26,12 @@ export const main = async (): Promise<void> => { | ||||
|     clientSecret: githubOauth.clientSecret | ||||
|   }); | ||||
| 
 | ||||
|   const db = new Database(database); | ||||
|   await db.init(); | ||||
|   const service = new Service(db, app); | ||||
| 
 | ||||
|   const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString(); | ||||
|   const resolvers = await createResolvers(db, app, service); | ||||
|   const resolvers = await createResolvers(service); | ||||
| 
 | ||||
|   await createAndStartServer(typeDefs, resolvers, server); | ||||
| }; | ||||
|  | ||||
| @ -1,10 +1,7 @@ | ||||
| import debug from 'debug'; | ||||
| import { DeepPartial, FindOptionsWhere } from 'typeorm'; | ||||
| 
 | ||||
| import { OAuthApp } from '@octokit/oauth-app'; | ||||
| 
 | ||||
| import { Service } from './service'; | ||||
| import { Database } from './database'; | ||||
| import { Permission } from './entity/ProjectMember'; | ||||
| import { Domain } from './entity/Domain'; | ||||
| import { Project } from './entity/Project'; | ||||
| @ -13,7 +10,7 @@ import { EnvironmentVariable } from './entity/EnvironmentVariable'; | ||||
| const log = debug('snowball:database'); | ||||
| 
 | ||||
| // TODO: Remove Database argument and refactor code to Service
 | ||||
| export const createResolvers = async (db: Database, app: OAuthApp, service: Service): Promise<any> => { | ||||
| export const createResolvers = async (service: Service): Promise<any> => { | ||||
|   return { | ||||
|     Query: { | ||||
|       // TODO: add custom type for context
 | ||||
| @ -121,9 +118,9 @@ export const createResolvers = async (db: Database, app: OAuthApp, service: Serv | ||||
|         } | ||||
|       }, | ||||
| 
 | ||||
|       updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }) => { | ||||
|       updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => { | ||||
|         try { | ||||
|           return await service.updateDeploymentToProd(deploymentId); | ||||
|           return Boolean(await service.updateDeploymentToProd(context.userId, deploymentId)); | ||||
|         } catch (err) { | ||||
|           log(err); | ||||
|           return false; | ||||
| @ -201,19 +198,17 @@ export const createResolvers = async (db: Database, app: OAuthApp, service: Serv | ||||
|       }, | ||||
| 
 | ||||
|       authenticateGitHub: async (_: any, { code }: { code: string }, context: any) => { | ||||
|         // TOO: Move to Service class
 | ||||
|         const { authentication: { token } } = await app.createToken({ | ||||
|           code | ||||
|         }); | ||||
| 
 | ||||
|         await db.updateUser(context.userId, { gitHubToken: token }); | ||||
| 
 | ||||
|         return { token }; | ||||
|         try { | ||||
|           return await service.authenticateGitHub(code, context.userId); | ||||
|         } catch (err) { | ||||
|           log(err); | ||||
|           return false; | ||||
|         } | ||||
|       }, | ||||
| 
 | ||||
|       unauthenticateGitHub: async (_: any, __: object, context: any) => { | ||||
|         try { | ||||
|           return db.updateUser(context.userId, { gitHubToken: null }); | ||||
|           return service.unauthenticateGitHub(context.userId, { gitHubToken: null }); | ||||
|         } catch (err) { | ||||
|           log(err); | ||||
|           return false; | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| import assert from 'assert'; | ||||
| import { customAlphabet } from 'nanoid'; | ||||
| import { lowercase, numbers } from 'nanoid-dictionary'; | ||||
| import { DeepPartial, FindOptionsWhere } from 'typeorm'; | ||||
| 
 | ||||
| import { OAuthApp } from '@octokit/oauth-app'; | ||||
| 
 | ||||
| import { Database } from './database'; | ||||
| import { Deployment, Environment } from './entity/Deployment'; | ||||
| import { Domain } from './entity/Domain'; | ||||
| @ -11,15 +11,13 @@ import { Organization } from './entity/Organization'; | ||||
| import { Project } from './entity/Project'; | ||||
| import { Permission, ProjectMember } from './entity/ProjectMember'; | ||||
| import { User } from './entity/User'; | ||||
| import { PROJECT_DOMAIN } from './constants'; | ||||
| 
 | ||||
| const nanoid = customAlphabet(lowercase + numbers, 8); | ||||
| 
 | ||||
| export class Service { | ||||
|   private db: Database; | ||||
|   private app: OAuthApp; | ||||
| 
 | ||||
|   constructor (db: Database) { | ||||
|   constructor (db: Database, app: OAuthApp) { | ||||
|     this.db = db; | ||||
|     this.app = app; | ||||
|   } | ||||
| 
 | ||||
|   async getUser (userId: string): Promise<User | null> { | ||||
| @ -149,7 +147,7 @@ export class Service { | ||||
|     return this.db.deleteEnvironmentVariable(environmentVariableId); | ||||
|   } | ||||
| 
 | ||||
|   async updateDeploymentToProd (deploymentId: string): Promise<boolean> { | ||||
|   async updateDeploymentToProd (userId: string, deploymentId: string): Promise<Deployment> { | ||||
|     const deployment = await this.db.getDeployment({ where: { id: deploymentId }, relations: { project: true } }); | ||||
| 
 | ||||
|     if (!deployment) { | ||||
| @ -173,13 +171,18 @@ export class Service { | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     const updateResult = await this.db.updateDeploymentById(deploymentId, { | ||||
|       environment: Environment.Production, | ||||
|       domain: prodBranchDomains[0], | ||||
|       isCurrent: true | ||||
|     const { createdAt, updatedAt, ...updatedDeployment } = deployment; | ||||
| 
 | ||||
|     updatedDeployment.isCurrent = true; | ||||
|     updatedDeployment.environment = Environment.Production; | ||||
|     updatedDeployment.domain = prodBranchDomains[0]; | ||||
|     updatedDeployment.createdBy = Object.assign(new User(), { | ||||
|       id: userId | ||||
|     }); | ||||
| 
 | ||||
|     return updateResult; | ||||
|     const newDeployement = await this.db.addDeployement(updatedDeployment); | ||||
| 
 | ||||
|     return newDeployement; | ||||
|   } | ||||
| 
 | ||||
|   async addProject (userId: string, organizationSlug: string, data: DeepPartial<Project>): Promise<Project | undefined> { | ||||
| @ -243,9 +246,6 @@ export class Service { | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     updatedDeployment.id = nanoid(); | ||||
|     updatedDeployment.url = `${updatedDeployment.project.name}-${updatedDeployment.id}.${PROJECT_DOMAIN}`; | ||||
| 
 | ||||
|     const oldDeployment = await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false }); | ||||
|     const newDeployement = await this.db.addDeployement(updatedDeployment); | ||||
| 
 | ||||
| @ -360,4 +360,18 @@ export class Service { | ||||
| 
 | ||||
|     return updateResult; | ||||
|   } | ||||
| 
 | ||||
|   async authenticateGitHub (code:string, userId: string): Promise<{token: string}> { | ||||
|     const { authentication: { token } } = await this.app.createToken({ | ||||
|       code | ||||
|     }); | ||||
| 
 | ||||
|     await this.db.updateUser(userId, { gitHubToken: token }); | ||||
| 
 | ||||
|     return { token }; | ||||
|   } | ||||
| 
 | ||||
|   async unauthenticateGitHub (userId: string, data: DeepPartial<User>): Promise<boolean> { | ||||
|     return this.db.updateUser(userId, data); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -2,11 +2,11 @@ | ||||
|   { | ||||
|     "id": "2379cf1f-a232-4ad2-ae14-4d881131cc26", | ||||
|     "name": "Snowball Tools", | ||||
|     "slug": "snowball-tools" | ||||
|     "slug": "snowball-tools-1" | ||||
|   }, | ||||
|   { | ||||
|     "id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb", | ||||
|     "name": "AirFoil", | ||||
|     "slug": "airfoil" | ||||
|     "slug": "airfoil-2" | ||||
|   } | ||||
| ] | ||||
|  | ||||
| @ -9,7 +9,7 @@ import { | ||||
|   Typography, | ||||
| } from '@material-tailwind/react'; | ||||
| 
 | ||||
| import { relativeTime } from '../../utils/time'; | ||||
| import { relativeTimeISO } from '../../utils/time'; | ||||
| import { ProjectDetails } from '../../types/project'; | ||||
| 
 | ||||
| interface ProjectCardProps { | ||||
| @ -45,7 +45,7 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => { | ||||
|           {project.latestCommit.message} | ||||
|         </Typography> | ||||
|         <Typography variant="small" color="gray"> | ||||
|           {relativeTime(project.latestCommit.createdAt)} on{' '} | ||||
|           {relativeTimeISO(project.latestCommit.createdAt)} on{' '} | ||||
|           {project.latestCommit.branch} | ||||
|         </Typography> | ||||
|       </div> | ||||
|  | ||||
| @ -3,7 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom'; | ||||
| 
 | ||||
| import { Chip, IconButton } from '@material-tailwind/react'; | ||||
| 
 | ||||
| import { relativeTime } from '../../../utils/time'; | ||||
| import { relativeTimeISO } from '../../../utils/time'; | ||||
| import { GitRepositoryDetails } from '../../../types/project'; | ||||
| import { useGQLClient } from '../../../context/GQLClientContext'; | ||||
| 
 | ||||
| @ -24,7 +24,6 @@ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => { | ||||
| 
 | ||||
|     const { addProject } = await client.addProject(orgSlug!, { | ||||
|       name: `${repository.owner!.login}-${repository.name}`, | ||||
|       // TODO: Get organization id from context or URL
 | ||||
|       prodBranch: repository.default_branch!, | ||||
|       repository: repository.full_name, | ||||
|     }); | ||||
| @ -50,7 +49,7 @@ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => { | ||||
|             /> | ||||
|           )} | ||||
|         </div> | ||||
|         <p>{repository.updated_at && relativeTime(repository.updated_at)}</p> | ||||
|         <p>{repository.updated_at && relativeTimeISO(repository.updated_at)}</p> | ||||
|       </div> | ||||
|       <div className="hidden group-hover:block"> | ||||
|         <IconButton size="sm">{'>'}</IconButton> | ||||
|  | ||||
| @ -2,7 +2,7 @@ import React from 'react'; | ||||
| 
 | ||||
| import { Typography, IconButton } from '@material-tailwind/react'; | ||||
| 
 | ||||
| import { relativeTime } from '../../../utils/time'; | ||||
| import { relativeTimeISO } from '../../../utils/time'; | ||||
| import { GitCommitDetails } from '../../../types/project'; | ||||
| 
 | ||||
| interface ActivityCardProps { | ||||
| @ -17,7 +17,8 @@ const ActivityCard = ({ activity }: ActivityCardProps) => { | ||||
|       <div className="grow"> | ||||
|         <Typography>{activity.commit.author?.name}</Typography> | ||||
|         <Typography variant="small" color="gray"> | ||||
|           {relativeTime(activity.commit.author!.date!)} ^ {activity.branch.name} | ||||
|           {relativeTimeISO(activity.commit.author!.date!)} ^{' '} | ||||
|           {activity.branch.name} | ||||
|         </Typography> | ||||
|         <Typography variant="small" color="gray"> | ||||
|           {activity.commit.message} | ||||
|  | ||||
| @ -3,7 +3,7 @@ import React from 'react'; | ||||
| import { Typography, Chip, Card } from '@material-tailwind/react'; | ||||
| import { color } from '@material-tailwind/react/types/components/chip'; | ||||
| import { DeploymentDetails } from '../../../../types/project'; | ||||
| import { relativeTime } from '../../../../utils/time'; | ||||
| import { relativeTimeMs } from '../../../../utils/time'; | ||||
| 
 | ||||
| interface DeploymentDialogBodyCardProps { | ||||
|   deployment: DeploymentDetails; | ||||
| @ -28,14 +28,14 @@ const DeploymentDialogBodyCard = ({ | ||||
|         /> | ||||
|       )} | ||||
|       <Typography variant="small" className="text-black"> | ||||
|         {deployment.title} | ||||
|         {deployment.url} | ||||
|       </Typography> | ||||
|       <Typography variant="small"> | ||||
|         ^ {deployment.branch} ^ {deployment.commitHash}{' '} | ||||
|         {deployment.commit.message} | ||||
|       </Typography> | ||||
|       <Typography variant="small"> | ||||
|         ^ {relativeTime(deployment.updatedAt)} ^ {deployment.author} | ||||
|         ^ {relativeTimeMs(deployment.createdAt)} ^ {deployment.createdBy.name} | ||||
|       </Typography> | ||||
|     </Card> | ||||
|   ); | ||||
|  | ||||
| @ -5,3 +5,6 @@ export const COMMIT_DETAILS = { | ||||
| }; | ||||
| 
 | ||||
| export const ORGANIZATION_ID = '2379cf1f-a232-4ad2-ae14-4d881131cc26'; | ||||
| 
 | ||||
| export const GIT_TEMPLATE_LINK = | ||||
|   'https://git.vdb.to/cerc-io/test-progressive-web-app'; | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { Outlet, useLocation, useSearchParams } from 'react-router-dom'; | ||||
| 
 | ||||
| import Stepper from '../../../../components/Stepper'; | ||||
| import templateDetails from '../../../../assets/templates.json'; | ||||
| import { GIT_TEMPLATE_LINK } from '../../../../constants'; | ||||
| 
 | ||||
| const STEPPER_VALUES = [ | ||||
|   { step: 1, route: '/projects/create/template', label: 'Create repository' }, | ||||
| @ -31,8 +32,11 @@ const CreateWithTemplate = () => { | ||||
|       <div className="flex justify-between w-5/6 my-4 bg-gray-200 rounded-xl p-6"> | ||||
|         <div>^</div> | ||||
|         <div className="grow">{template?.name}</div> | ||||
|         {/* TODO: Get template Git link from DB */} | ||||
|         <div>^snowball-tools/react-native-starter</div> | ||||
|         <div> | ||||
|           <a href={GIT_TEMPLATE_LINK} target="_blank" rel="noreferrer"> | ||||
|             cerc-io/test-progressive-web-app | ||||
|           </a> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div className="grid grid-cols-3 w-5/6 p-6"> | ||||
|         <div> | ||||
|  | ||||
| @ -50,7 +50,6 @@ const CreateRepo = () => { | ||||
| 
 | ||||
|         const { addProject } = await client.addProject(orgSlug!, { | ||||
|           name: `${gitRepo.data.owner!.login}-${gitRepo.data.name}`, | ||||
|           // TODO: Get organization id from context or URL
 | ||||
|           prodBranch: gitRepo.data.default_branch ?? 'main', | ||||
|           repository: gitRepo.data.full_name, | ||||
|         }); | ||||
| @ -95,8 +94,6 @@ const CreateRepo = () => { | ||||
|     } | ||||
|   }, [gitAccounts]); | ||||
| 
 | ||||
|   // TODO: Get users and orgs from GitHub
 | ||||
| 
 | ||||
|   return ( | ||||
|     <form onSubmit={handleSubmit(submitRepoHandler)}> | ||||
|       <div className="mb-2"> | ||||
| @ -135,10 +132,7 @@ const CreateRepo = () => { | ||||
|             name="account" | ||||
|             control={control} | ||||
|             render={({ field }) => ( | ||||
|               <AsyncSelect | ||||
|                 {...field} | ||||
|                 label={!field.value ? 'Select an account / Organization' : ''} | ||||
|               > | ||||
|               <AsyncSelect {...field}> | ||||
|                 {gitAccounts.map((account, key) => ( | ||||
|                   <Option key={key} value={account}> | ||||
|                     ^ {account} | ||||
|  | ||||
| @ -6,7 +6,7 @@ import { DateTime } from 'luxon'; | ||||
|  * @param {string} time - The input time in ISO 8601 format. | ||||
|  * @returns {string} - A human-readable relative time string. | ||||
|  */ | ||||
| export const relativeTime = (time: string) => { | ||||
| export const relativeTimeISO = (time: string) => { | ||||
|   return DateTime.fromISO(time).toRelative(); | ||||
| }; | ||||
| 
 | ||||
| @ -17,5 +17,5 @@ export const relativeTime = (time: string) => { | ||||
|  * @returns {string} - A human-readable relative time string. | ||||
|  */ | ||||
| export const relativeTimeMs = (time: string) => { | ||||
|   return relativeTime(new Date(Number(time)).toISOString()); | ||||
|   return relativeTimeISO(new Date(Number(time)).toISOString()); | ||||
| }; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user