diff --git a/.gitignore b/.gitignore index c2658d7d..5bbf8847 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ node_modules/ +yarn-error.log +.yarnrc.yml +.yarn/ + +packages/backend/environments/local.toml \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..f3e08235 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + // IntelliSense for taiwind variants + "tailwindCSS.experimental.classRegex": [ + ["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] + ] +} diff --git a/README.md b/README.md index f37a2ae8..d75ccdfb 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,12 @@ cd packages/backend ``` +- Rename backend config file from [environments/local.toml.example](packages/backend/environments/local.toml.example) to `local.toml` + + ```bash + mv environments/local.toml.example environments/local.toml + ``` + - Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml) - Client ID and secret will be available after [creating an OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) - In "Homepage URL", type `http://localhost:3000` @@ -181,6 +187,12 @@ cd packages/frontend ``` +- Rename [.env.example](packages/frontend/.env.example) to `.env` + + ```bash + mv .env.example .env + ``` + - Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file ```env diff --git a/packages/backend/.eslintrc.json b/packages/backend/.eslintrc.json index da3b01c6..c7012cab 100644 --- a/packages/backend/.eslintrc.json +++ b/packages/backend/.eslintrc.json @@ -25,6 +25,9 @@ "allowArgumentsExplicitlyTypedAsAny": true } ], - "@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }] + "@typescript-eslint/no-unused-vars": [ + "error", + { "ignoreRestSiblings": true } + ] } } diff --git a/packages/backend/.gitignore b/packages/backend/.gitignore index b667a045..987e4095 100644 --- a/packages/backend/.gitignore +++ b/packages/backend/.gitignore @@ -1,2 +1,3 @@ db dist +environments/local.toml \ No newline at end of file diff --git a/packages/backend/environments/local.toml b/packages/backend/environments/local.toml.example similarity index 100% rename from packages/backend/environments/local.toml rename to packages/backend/environments/local.toml.example diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index d475a109..b4cdd8fc 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -24,7 +24,7 @@ export interface GitHubConfig { oAuth: { clientId: string; clientSecret: string; - } + }; } export interface RegistryConfig { @@ -39,7 +39,7 @@ export interface RegistryConfig { amount: string; denom: string; gas: string; - } + }; } export interface MiscConfig { diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index 82478770..1ec639c6 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -1,5 +1,6 @@ import process from 'process'; -export const DEFAULT_CONFIG_FILE_PATH = process.env.SNOWBALL_BACKEND_CONFIG_FILE_PATH || 'environments/local.toml'; +export const DEFAULT_CONFIG_FILE_PATH = + process.env.SNOWBALL_BACKEND_CONFIG_FILE_PATH || 'environments/local.toml'; export const DEFAULT_GQL_PATH = '/graphql'; diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 3324b2f7..a9ba5250 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -1,4 +1,10 @@ -import { DataSource, DeepPartial, FindManyOptions, FindOneOptions, FindOptionsWhere } from 'typeorm'; +import { + DataSource, + DeepPartial, + FindManyOptions, + FindOneOptions, + FindOptionsWhere +} from 'typeorm'; import path from 'path'; import debug from 'debug'; import assert from 'assert'; @@ -74,14 +80,18 @@ export class Database { return updateResult.affected > 0; } - async getOrganizations (options: FindManyOptions): Promise { + async getOrganizations ( + options: FindManyOptions + ): Promise { const organizationRepository = this.dataSource.getRepository(Organization); const organizations = await organizationRepository.find(options); return organizations; } - async getOrganization (options: FindOneOptions): Promise { + async getOrganization ( + options: FindOneOptions + ): Promise { const organizationRepository = this.dataSource.getRepository(Organization); const organization = await organizationRepository.findOne(options); @@ -123,7 +133,11 @@ export class Database { const project = await projectRepository .createQueryBuilder('project') - .leftJoinAndSelect('project.deployments', 'deployments', 'deployments.isCurrent = true') + .leftJoinAndSelect( + 'project.deployments', + 'deployments', + 'deployments.isCurrent = true' + ) .leftJoinAndSelect('deployments.createdBy', 'user') .leftJoinAndSelect('deployments.domain', 'domain') .leftJoinAndSelect('project.owner', 'owner') @@ -136,19 +150,29 @@ export class Database { return project; } - async getProjectsInOrganization (userId: string, organizationSlug: string): Promise { + async getProjectsInOrganization ( + userId: string, + organizationSlug: string + ): Promise { const projectRepository = this.dataSource.getRepository(Project); const projects = await projectRepository .createQueryBuilder('project') - .leftJoinAndSelect('project.deployments', 'deployments', 'deployments.isCurrent = true') + .leftJoinAndSelect( + 'project.deployments', + 'deployments', + 'deployments.isCurrent = true' + ) .leftJoinAndSelect('deployments.domain', 'domain') .leftJoin('project.projectMembers', 'projectMembers') .leftJoin('project.organization', 'organization') - .where('(project.ownerId = :userId OR projectMembers.userId = :userId) AND organization.slug = :organizationSlug', { - userId, - organizationSlug - }) + .where( + '(project.ownerId = :userId OR projectMembers.userId = :userId) AND organization.slug = :organizationSlug', + { + userId, + organizationSlug + } + ) .getMany(); return projects; @@ -157,7 +181,9 @@ export class Database { /** * Get deployments with specified filter */ - async getDeployments (options: FindManyOptions): Promise { + async getDeployments ( + options: FindManyOptions + ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const deployments = await deploymentRepository.find(options); @@ -182,7 +208,9 @@ export class Database { }); } - async getDeployment (options: FindOneOptions): Promise { + async getDeployment ( + options: FindOneOptions + ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const deployment = await deploymentRepository.findOne(options); @@ -210,8 +238,11 @@ export class Database { return deployment; } - async getProjectMembersByProjectId (projectId: string): Promise { - const projectMemberRepository = this.dataSource.getRepository(ProjectMember); + async getProjectMembersByProjectId ( + projectId: string + ): Promise { + const projectMemberRepository = + this.dataSource.getRepository(ProjectMember); const projectMembers = await projectMemberRepository.find({ relations: { @@ -228,8 +259,12 @@ export class Database { return projectMembers; } - async getEnvironmentVariablesByProjectId (projectId: string, filter?: FindOptionsWhere): Promise { - const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); + async getEnvironmentVariablesByProjectId ( + projectId: string, + filter?: FindOptionsWhere + ): Promise { + const environmentVariableRepository = + this.dataSource.getRepository(EnvironmentVariable); const environmentVariables = await environmentVariableRepository.find({ where: { @@ -244,9 +279,12 @@ export class Database { } async removeProjectMemberById (projectMemberId: string): Promise { - const projectMemberRepository = this.dataSource.getRepository(ProjectMember); + const projectMemberRepository = + this.dataSource.getRepository(ProjectMember); - const deleteResult = await projectMemberRepository.delete({ id: projectMemberId }); + const deleteResult = await projectMemberRepository.delete({ + id: projectMemberId + }); if (deleteResult.affected) { return deleteResult.affected > 0; @@ -255,37 +293,63 @@ export class Database { } } - async updateProjectMemberById (projectMemberId: string, data: DeepPartial): Promise { - const projectMemberRepository = this.dataSource.getRepository(ProjectMember); - const updateResult = await projectMemberRepository.update({ id: projectMemberId }, data); + async updateProjectMemberById ( + projectMemberId: string, + data: DeepPartial + ): Promise { + const projectMemberRepository = + this.dataSource.getRepository(ProjectMember); + const updateResult = await projectMemberRepository.update( + { id: projectMemberId }, + data + ); return Boolean(updateResult.affected); } - async addProjectMember (data: DeepPartial): Promise { - const projectMemberRepository = this.dataSource.getRepository(ProjectMember); + async addProjectMember ( + data: DeepPartial + ): Promise { + const projectMemberRepository = + this.dataSource.getRepository(ProjectMember); const newProjectMember = await projectMemberRepository.save(data); return newProjectMember; } - async addEnvironmentVariables (data: DeepPartial[]): Promise { - const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); - const savedEnvironmentVariables = await environmentVariableRepository.save(data); + async addEnvironmentVariables ( + data: DeepPartial[] + ): Promise { + const environmentVariableRepository = + this.dataSource.getRepository(EnvironmentVariable); + const savedEnvironmentVariables = + await environmentVariableRepository.save(data); return savedEnvironmentVariables; } - async updateEnvironmentVariable (environmentVariableId: string, data: DeepPartial): Promise { - const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); - const updateResult = await environmentVariableRepository.update({ id: environmentVariableId }, data); + async updateEnvironmentVariable ( + environmentVariableId: string, + data: DeepPartial + ): Promise { + const environmentVariableRepository = + this.dataSource.getRepository(EnvironmentVariable); + const updateResult = await environmentVariableRepository.update( + { id: environmentVariableId }, + data + ); return Boolean(updateResult.affected); } - async deleteEnvironmentVariable (environmentVariableId: string): Promise { - const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); - const deleteResult = await environmentVariableRepository.delete({ id: environmentVariableId }); + async deleteEnvironmentVariable ( + environmentVariableId: string + ): Promise { + const environmentVariableRepository = + this.dataSource.getRepository(EnvironmentVariable); + const deleteResult = await environmentVariableRepository.delete({ + id: environmentVariableId + }); if (deleteResult.affected) { return deleteResult.affected > 0; @@ -295,7 +359,8 @@ export class Database { } async getProjectMemberById (projectMemberId: string): Promise { - const projectMemberRepository = this.dataSource.getRepository(ProjectMember); + const projectMemberRepository = + this.dataSource.getRepository(ProjectMember); const projectMemberWithProject = await projectMemberRepository.find({ relations: { @@ -307,8 +372,7 @@ export class Database { where: { id: projectMemberId } - } - ); + }); if (projectMemberWithProject.length === 0) { throw new Error('Member does not exist'); @@ -317,34 +381,49 @@ export class Database { return projectMemberWithProject[0]; } - async getProjectsBySearchText (userId: string, searchText: string): Promise { + async getProjectsBySearchText ( + userId: string, + searchText: string + ): Promise { const projectRepository = this.dataSource.getRepository(Project); const projects = await projectRepository .createQueryBuilder('project') .leftJoinAndSelect('project.organization', 'organization') .leftJoin('project.projectMembers', 'projectMembers') - .where('(project.owner = :userId OR projectMembers.member.id = :userId) AND project.name LIKE :searchText', { - userId, - searchText: `%${searchText}%` - }) + .where( + '(project.owner = :userId OR projectMembers.member.id = :userId) AND project.name LIKE :searchText', + { + userId, + searchText: `%${searchText}%` + } + ) .getMany(); return projects; } - async updateDeploymentById (deploymentId: string, data: DeepPartial): Promise { + async updateDeploymentById ( + deploymentId: string, + data: DeepPartial + ): Promise { return this.updateDeployment({ id: deploymentId }, data); } - async updateDeployment (criteria: FindOptionsWhere, data: DeepPartial): Promise { + async updateDeployment ( + criteria: FindOptionsWhere, + data: DeepPartial + ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const updateResult = await deploymentRepository.update(criteria, data); return Boolean(updateResult.affected); } - async updateDeploymentsByProjectIds (projectIds: string[], data: DeepPartial): Promise { + async updateDeploymentsByProjectIds ( + projectIds: string[], + data: DeepPartial + ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const updateResult = await deploymentRepository @@ -378,9 +457,15 @@ export class Database { return projectRepository.save(newProject); } - async updateProjectById (projectId: string, data: DeepPartial): Promise { + async updateProjectById ( + projectId: string, + data: DeepPartial + ): Promise { const projectRepository = this.dataSource.getRepository(Project); - const updateResult = await projectRepository.update({ id: projectId }, data); + const updateResult = await projectRepository.update( + { id: projectId }, + data + ); return Boolean(updateResult.affected); } @@ -427,14 +512,20 @@ export class Database { return domain; } - async updateDomainById (domainId: string, data: DeepPartial): Promise { + async updateDomainById ( + domainId: string, + data: DeepPartial + ): Promise { const domainRepository = this.dataSource.getRepository(Domain); const updateResult = await domainRepository.update({ id: domainId }, data); return Boolean(updateResult.affected); } - async getDomainsByProjectId (projectId: string, filter?: FindOptionsWhere): Promise { + async getDomainsByProjectId ( + projectId: string, + filter?: FindOptionsWhere + ): Promise { const domainRepository = this.dataSource.getRepository(Domain); const domains = await domainRepository.find({ diff --git a/packages/backend/src/entity/Deployment.ts b/packages/backend/src/entity/Deployment.ts index baf5fe80..8e0a3246 100644 --- a/packages/backend/src/entity/Deployment.ts +++ b/packages/backend/src/entity/Deployment.ts @@ -27,26 +27,26 @@ export enum DeploymentStatus { } export interface ApplicationDeploymentRequest { - type: string - version: string - name: string - application: string - config: string, - meta: string + type: string; + version: string; + name: string; + application: string; + config: string; + meta: string; } export interface ApplicationRecord { type: string; - version:string - name: string - description?: string - homepage?: string - license?: string - author?: string - repository?: string[], - app_version?: string - repository_ref: string - app_type: string + version: string; + name: string; + description?: string; + homepage?: string; + license?: string; + author?: string; + repository?: string[]; + app_version?: string; + repository_ref: string; + app_type: string; } @Entity() diff --git a/packages/backend/src/entity/Domain.ts b/packages/backend/src/entity/Domain.ts index 29f189ff..c662daff 100644 --- a/packages/backend/src/entity/Domain.ts +++ b/packages/backend/src/entity/Domain.ts @@ -39,7 +39,7 @@ export class Domain { @ManyToOne(() => Domain) @JoinColumn({ name: 'redirectToId' }) - // eslint-disable-next-line no-use-before-define + // eslint-disable-next-line no-use-before-define redirectTo!: Domain | null; @Column({ diff --git a/packages/backend/src/entity/Organization.ts b/packages/backend/src/entity/Organization.ts index 15ff1011..3b15522d 100644 --- a/packages/backend/src/entity/Organization.ts +++ b/packages/backend/src/entity/Organization.ts @@ -27,8 +27,12 @@ export class Organization { @UpdateDateColumn() updatedAt!: Date; - @OneToMany(() => UserOrganization, userOrganization => userOrganization.organization, { - cascade: ['soft-remove'] - }) + @OneToMany( + () => UserOrganization, + (userOrganization) => userOrganization.organization, + { + cascade: ['soft-remove'] + } + ) userOrganizations!: UserOrganization[]; } diff --git a/packages/backend/src/entity/Project.ts b/packages/backend/src/entity/Project.ts index 4415476d..b46c219a 100644 --- a/packages/backend/src/entity/Project.ts +++ b/packages/backend/src/entity/Project.ts @@ -76,7 +76,7 @@ export class Project { @OneToMany(() => Deployment, (deployment) => deployment.project) deployments!: Deployment[]; - @OneToMany(() => ProjectMember, projectMember => projectMember.project, { + @OneToMany(() => ProjectMember, (projectMember) => projectMember.project, { cascade: ['soft-remove'] }) projectMembers!: ProjectMember[]; diff --git a/packages/backend/src/entity/ProjectMember.ts b/packages/backend/src/entity/ProjectMember.ts index d4e77edc..545a64db 100644 --- a/packages/backend/src/entity/ProjectMember.ts +++ b/packages/backend/src/entity/ProjectMember.ts @@ -15,7 +15,7 @@ import { User } from './User'; export enum Permission { View = 'View', - Edit = 'Edit' + Edit = 'Edit', } @Entity() diff --git a/packages/backend/src/entity/User.ts b/packages/backend/src/entity/User.ts index 1dadda86..69700fab 100644 --- a/packages/backend/src/entity/User.ts +++ b/packages/backend/src/entity/User.ts @@ -39,13 +39,17 @@ export class User { @CreateDateColumn() updatedAt!: Date; - @OneToMany(() => ProjectMember, projectMember => projectMember.project, { + @OneToMany(() => ProjectMember, (projectMember) => projectMember.project, { cascade: ['soft-remove'] }) projectMembers!: ProjectMember[]; - @OneToMany(() => UserOrganization, UserOrganization => UserOrganization.member, { - cascade: ['soft-remove'] - }) + @OneToMany( + () => UserOrganization, + (UserOrganization) => UserOrganization.member, + { + cascade: ['soft-remove'] + } + ) userOrganizations!: UserOrganization[]; } diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 79286c43..30785d2f 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -31,9 +31,16 @@ export const main = async (): Promise => { await db.init(); const registry = new Registry(registryConfig); - const service = new Service({ gitHubConfig: gitHub, registryConfig }, db, app, registry); + const service = new Service( + { gitHubConfig: gitHub, registryConfig }, + db, + app, + registry + ); - const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString(); + const typeDefs = fs + .readFileSync(path.join(__dirname, 'schema.gql')) + .toString(); const resolvers = await createResolvers(service); await createAndStartServer(server, typeDefs, resolvers, service); diff --git a/packages/backend/src/registry.ts b/packages/backend/src/registry.ts index f9840e8f..8a6e489b 100644 --- a/packages/backend/src/registry.ts +++ b/packages/backend/src/registry.ts @@ -6,7 +6,11 @@ import { DateTime } from 'luxon'; import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk'; import { RegistryConfig } from './config'; -import { ApplicationRecord, Deployment, ApplicationDeploymentRequest } from './entity/Deployment'; +import { + ApplicationRecord, + Deployment, + ApplicationDeploymentRequest +} from './entity/Deployment'; import { AppDeploymentRecord, PackageJSON } from './types'; const log = debug('snowball:registry'); @@ -20,9 +24,13 @@ export class Registry { private registry: LaconicRegistry; private registryConfig: RegistryConfig; - constructor (registryConfig : RegistryConfig) { + constructor (registryConfig: RegistryConfig) { this.registryConfig = registryConfig; - this.registry = new LaconicRegistry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId); + this.registry = new LaconicRegistry( + registryConfig.gqlEndpoint, + registryConfig.restEndpoint, + registryConfig.chainId + ); } async createApplicationRecord ({ @@ -32,24 +40,38 @@ export class Registry { appType, repoUrl }: { - appName: string, - packageJSON: PackageJSON - commitHash: string, - appType: string, - repoUrl: string - }): Promise<{applicationRecordId: string, applicationRecordData: ApplicationRecord}> { + appName: string; + packageJSON: PackageJSON; + commitHash: string; + appType: string; + repoUrl: string; + }): Promise<{ + applicationRecordId: string; + applicationRecordData: ApplicationRecord; + }> { // Use laconic-sdk to publish record // Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh // Fetch previous records - const records = await this.registry.queryRecords({ - type: APP_RECORD_TYPE, - name: packageJSON.name - }, true); + const records = await this.registry.queryRecords( + { + type: APP_RECORD_TYPE, + name: packageJSON.name + }, + true + ); // Get next version of record - const bondRecords = records.filter((record: any) => record.bondId === this.registryConfig.bondId); - const [latestBondRecord] = bondRecords.sort((a: any, b: any) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime()); - const nextVersion = semverInc(latestBondRecord?.attributes.version ?? '0.0.0', 'patch'); + const bondRecords = records.filter( + (record: any) => record.bondId === this.registryConfig.bondId + ); + const [latestBondRecord] = bondRecords.sort( + (a: any, b: any) => + new Date(b.createTime).getTime() - new Date(a.createTime).getTime() + ); + const nextVersion = semverInc( + latestBondRecord?.attributes.version ?? '0.0.0', + 'patch' + ); assert(nextVersion, 'Application record version not valid'); @@ -64,7 +86,12 @@ export class Registry { ...(packageJSON.description && { description: packageJSON.description }), ...(packageJSON.homepage && { homepage: packageJSON.homepage }), ...(packageJSON.license && { license: packageJSON.license }), - ...(packageJSON.author && { author: typeof packageJSON.author === 'object' ? JSON.stringify(packageJSON.author) : packageJSON.author }), + ...(packageJSON.author && { + author: + typeof packageJSON.author === 'object' + ? JSON.stringify(packageJSON.author) + : packageJSON.author + }), ...(packageJSON.version && { app_version: packageJSON.version }) }; @@ -84,11 +111,29 @@ export class Registry { const crn = this.getCrn(appName); log(`Setting name: ${crn} for record ID: ${result.data.id}`); - await this.registry.setName({ cid: result.data.id, crn }, this.registryConfig.privateKey, this.registryConfig.fee); - await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, this.registryConfig.privateKey, this.registryConfig.fee); - await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.repository_ref}` }, this.registryConfig.privateKey, this.registryConfig.fee); + await this.registry.setName( + { cid: result.data.id, crn }, + this.registryConfig.privateKey, + this.registryConfig.fee + ); + await this.registry.setName( + { cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, + this.registryConfig.privateKey, + this.registryConfig.fee + ); + await this.registry.setName( + { + cid: result.data.id, + crn: `${crn}@${applicationRecord.repository_ref}` + }, + this.registryConfig.privateKey, + this.registryConfig.fee + ); - return { applicationRecordId: result.data.id, applicationRecordData: applicationRecord }; + return { + applicationRecordId: result.data.id, + applicationRecordData: applicationRecord + }; } async createApplicationDeploymentRequest (data: { @@ -98,8 +143,8 @@ export class Registry { repository: string, environmentVariables: { [key: string]: string } }): Promise<{ - applicationDeploymentRequestId: string, - applicationDeploymentRequestData: ApplicationDeploymentRequest + applicationDeploymentRequestId: string; + applicationDeploymentRequestData: ApplicationDeploymentRequest; }> { const crn = this.getCrn(data.appName); const records = await this.registry.resolveNames([crn]); @@ -125,7 +170,9 @@ export class Registry { env: data.environmentVariables }), meta: JSON.stringify({ - note: `Added by Snowball @ ${DateTime.utc().toFormat('EEE LLL dd HH:mm:ss \'UTC\' yyyy')}`, + note: `Added by Snowball @ ${DateTime.utc().toFormat( + "EEE LLL dd HH:mm:ss 'UTC' yyyy" + )}`, repository: data.repository, repository_ref: data.deployment.commitHash }) @@ -143,21 +190,34 @@ export class Registry { log(`Application deployment request record published: ${result.data.id}`); log('Application deployment request data:', applicationDeploymentRequest); - return { applicationDeploymentRequestId: result.data.id, applicationDeploymentRequestData: applicationDeploymentRequest }; + return { + applicationDeploymentRequestId: result.data.id, + applicationDeploymentRequestData: applicationDeploymentRequest + }; } /** * Fetch ApplicationDeploymentRecords for deployments */ - async getDeploymentRecords (deployments: Deployment[]): Promise { + async getDeploymentRecords ( + deployments: Deployment[] + ): Promise { // Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments // TODO: Implement Laconicd GQL query to filter records by multiple values for an attribute - const records = await this.registry.queryRecords({ - type: APP_DEPLOYMENT_RECORD_TYPE - }, true); + const records = await this.registry.queryRecords( + { + type: APP_DEPLOYMENT_RECORD_TYPE + }, + true + ); // Filter records with ApplicationRecord ids - return records.filter((record: AppDeploymentRecord) => deployments.some(deployment => deployment.applicationRecordId === record.attributes.application)); + return records.filter((record: AppDeploymentRecord) => + deployments.some( + (deployment) => + deployment.applicationRecordId === record.attributes.application + ) + ); } getCrn (appName: string): string { diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 3eeace3f..e2e3339d 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -33,7 +33,10 @@ export const createResolvers = async (service: Service): Promise => { return service.getDeploymentsByProjectId(projectId); }, - environmentVariables: async (_: any, { projectId }: { projectId: string }) => { + environmentVariables: async ( + _: any, + { projectId }: { projectId: string } + ) => { return service.getEnvironmentVariablesByProjectId(projectId); }, @@ -45,14 +48,24 @@ export const createResolvers = async (service: Service): Promise => { return service.searchProjects(context.user, searchText); }, - domains: async (_:any, { projectId, filter }: { projectId: string, filter?: FindOptionsWhere }) => { + domains: async ( + _: any, + { + projectId, + filter + }: { projectId: string; filter?: FindOptionsWhere } + ) => { return service.getDomainsByProjectId(projectId, filter); } }, // TODO: Return error in GQL response Mutation: { - removeProjectMember: async (_: any, { projectMemberId }: { projectMemberId: string }, context: any) => { + removeProjectMember: async ( + _: any, + { projectMemberId }: { projectMemberId: string }, + context: any + ) => { try { return await service.removeProjectMember(context.user, projectMemberId); } catch (err) { @@ -61,12 +74,18 @@ export const createResolvers = async (service: Service): Promise => { } }, - updateProjectMember: async (_: any, { projectMemberId, data }: { - projectMemberId: string, - data: { - permissions: Permission[] + updateProjectMember: async ( + _: any, + { + projectMemberId, + data + }: { + projectMemberId: string; + data: { + permissions: Permission[]; + }; } - }) => { + ) => { try { return await service.updateProjectMember(projectMemberId, data); } catch (err) { @@ -75,13 +94,19 @@ export const createResolvers = async (service: Service): Promise => { } }, - addProjectMember: async (_: any, { projectId, data }: { - projectId: string, - data: { - email: string, - permissions: Permission[] + addProjectMember: async ( + _: any, + { + projectId, + data + }: { + projectId: string; + data: { + email: string; + permissions: Permission[]; + }; } - }) => { + ) => { try { return Boolean(await service.addProjectMember(projectId, data)); } catch (err) { @@ -90,25 +115,51 @@ export const createResolvers = async (service: Service): Promise => { } }, - addEnvironmentVariables: async (_: any, { projectId, data }: { projectId: string, data: { environments: string[], key: string, value: string}[] }) => { + addEnvironmentVariables: async ( + _: any, + { + projectId, + data + }: { + projectId: string; + data: { environments: string[]; key: string; value: string }[]; + } + ) => { try { - return Boolean(await service.addEnvironmentVariables(projectId, data)); + return Boolean( + await service.addEnvironmentVariables(projectId, data) + ); } catch (err) { log(err); return false; } }, - updateEnvironmentVariable: async (_: any, { environmentVariableId, data }: { environmentVariableId: string, data : DeepPartial}) => { + updateEnvironmentVariable: async ( + _: any, + { + environmentVariableId, + data + }: { + environmentVariableId: string; + data: DeepPartial; + } + ) => { try { - return await service.updateEnvironmentVariable(environmentVariableId, data); + return await service.updateEnvironmentVariable( + environmentVariableId, + data + ); } catch (err) { log(err); return false; } }, - removeEnvironmentVariable: async (_: any, { environmentVariableId }: { environmentVariableId: string}) => { + removeEnvironmentVariable: async ( + _: any, + { environmentVariableId }: { environmentVariableId: string } + ) => { try { return await service.removeEnvironmentVariable(environmentVariableId); } catch (err) { @@ -117,7 +168,11 @@ export const createResolvers = async (service: Service): Promise => { } }, - updateDeploymentToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => { + updateDeploymentToProd: async ( + _: any, + { deploymentId }: { deploymentId: string }, + context: any + ) => { try { return Boolean(await service.updateDeploymentToProd(context.user, deploymentId)); } catch (err) { @@ -126,7 +181,14 @@ export const createResolvers = async (service: Service): Promise => { } }, - addProject: async (_: any, { organizationSlug, data }: { organizationSlug: string, data: DeepPartial }, context: any) => { + addProject: async ( + _: any, + { + organizationSlug, + data + }: { organizationSlug: string; data: DeepPartial }, + context: any + ) => { try { return await service.addProject(context.user, organizationSlug, data); } catch (err) { @@ -135,7 +197,10 @@ export const createResolvers = async (service: Service): Promise => { } }, - updateProject: async (_: any, { projectId, data }: { projectId: string, data: DeepPartial }) => { + updateProject: async ( + _: any, + { projectId, data }: { projectId: string; data: DeepPartial } + ) => { try { return await service.updateProject(projectId, data); } catch (err) { @@ -144,7 +209,11 @@ export const createResolvers = async (service: Service): Promise => { } }, - redeployToProd: async (_: any, { deploymentId }: { deploymentId: string }, context: any) => { + redeployToProd: async ( + _: any, + { deploymentId }: { deploymentId: string }, + context: any + ) => { try { return Boolean(await service.redeployToProd(context.user, deploymentId)); } catch (err) { @@ -157,7 +226,8 @@ export const createResolvers = async (service: Service): Promise => { try { return await service.deleteProject(projectId); } catch (err) { - log(err); return false; + log(err); + return false; } }, @@ -170,7 +240,13 @@ export const createResolvers = async (service: Service): Promise => { } }, - rollbackDeployment: async (_: any, { projectId, deploymentId }: {deploymentId: string, projectId: string }) => { + rollbackDeployment: async ( + _: any, + { + projectId, + deploymentId + }: { deploymentId: string; projectId: string } + ) => { try { return await service.rollbackDeployment(projectId, deploymentId); } catch (err) { @@ -179,7 +255,10 @@ export const createResolvers = async (service: Service): Promise => { } }, - addDomain: async (_: any, { projectId, data }: { projectId: string, data: { name: string } }) => { + addDomain: async ( + _: any, + { projectId, data }: { projectId: string; data: { name: string } } + ) => { try { return Boolean(await service.addDomain(projectId, data)); } catch (err) { @@ -188,7 +267,10 @@ export const createResolvers = async (service: Service): Promise => { } }, - updateDomain: async (_: any, { domainId, data }: { domainId: string, data: DeepPartial}) => { + updateDomain: async ( + _: any, + { domainId, data }: { domainId: string; data: DeepPartial } + ) => { try { return await service.updateDomain(domainId, data); } catch (err) { @@ -197,7 +279,11 @@ export const createResolvers = async (service: Service): Promise => { } }, - authenticateGitHub: async (_: any, { code }: { code: string }, context: any) => { + authenticateGitHub: async ( + _: any, + { code }: { code: string }, + context: any + ) => { try { return await service.authenticateGitHub(code, context.user); } catch (err) { diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index 620dec72..c22db8db 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -188,10 +188,19 @@ type Query { type Mutation { addProjectMember(projectId: String!, data: AddProjectMemberInput): Boolean! - updateProjectMember(projectMemberId: String!, data: UpdateProjectMemberInput): Boolean! + updateProjectMember( + projectMemberId: String! + data: UpdateProjectMemberInput + ): Boolean! removeProjectMember(projectMemberId: String!): Boolean! - addEnvironmentVariables(projectId: String!, data: [AddEnvironmentVariableInput!]): Boolean! - updateEnvironmentVariable(environmentVariableId: String!, data: UpdateEnvironmentVariableInput!): Boolean! + addEnvironmentVariables( + projectId: String! + data: [AddEnvironmentVariableInput!] + ): Boolean! + updateEnvironmentVariable( + environmentVariableId: String! + data: UpdateEnvironmentVariableInput! + ): Boolean! removeEnvironmentVariable(environmentVariableId: String!): Boolean! updateDeploymentToProd(deploymentId: String!): Boolean! addProject(organizationSlug: String!, data: AddProjectInput): Project! diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index a6ec94d8..d204537c 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -23,8 +23,8 @@ const log = debug('snowball:service'); const GITHUB_UNIQUE_WEBHOOK_ERROR = 'Hook already exists on this repository'; interface Config { - gitHubConfig: GitHubConfig - registryConfig: RegistryConfig + gitHubConfig: GitHubConfig; + registryConfig: RegistryConfig; } export class Service { @@ -72,7 +72,9 @@ export class Service { }); if (deployments.length) { - log(`Found ${deployments.length} deployments in ${DeploymentStatus.Building} state`); + log( + `Found ${deployments.length} deployments in ${DeploymentStatus.Building} state` + ); // Fetch ApplicationDeploymentRecord for deployments const records = await this.registry.getDeploymentRecords(deployments); @@ -92,10 +94,12 @@ export class Service { /** * Update deployments with ApplicationDeploymentRecord data */ - async updateDeploymentsWithRecordData (records: AppDeploymentRecord[]): Promise { + async updateDeploymentsWithRecordData ( + records: AppDeploymentRecord[] + ): Promise { // Get deployments for ApplicationDeploymentRecords const deployments = await this.db.getDeployments({ - where: records.map(record => ({ + where: records.map((record) => ({ applicationRecordId: record.attributes.application })), order: { @@ -104,38 +108,46 @@ export class Service { }); // Get project IDs of deployments that are in production environment - const productionDeploymentProjectIds = deployments.reduce((acc, deployment): Set => { - if (deployment.environment === Environment.Production) { - acc.add(deployment.projectId); - } + const productionDeploymentProjectIds = deployments.reduce( + (acc, deployment): Set => { + if (deployment.environment === Environment.Production) { + acc.add(deployment.projectId); + } - return acc; - }, new Set()); + return acc; + }, + new Set() + ); // Set old deployments isCurrent to false - await this.db.updateDeploymentsByProjectIds(Array.from(productionDeploymentProjectIds), { isCurrent: false }); + await this.db.updateDeploymentsByProjectIds( + Array.from(productionDeploymentProjectIds), + { isCurrent: false } + ); - const recordToDeploymentsMap = deployments.reduce((acc: {[key: string]: Deployment}, deployment) => { - acc[deployment.applicationRecordId] = deployment; - return acc; - }, {}); + const recordToDeploymentsMap = deployments.reduce( + (acc: { [key: string]: Deployment }, deployment) => { + acc[deployment.applicationRecordId] = deployment; + return acc; + }, + {} + ); // Update deployment data for ApplicationDeploymentRecords const deploymentUpdatePromises = records.map(async (record) => { const deployment = recordToDeploymentsMap[record.attributes.application]; - await this.db.updateDeploymentById( - deployment.id, - { - applicationDeploymentRecordId: record.id, - applicationDeploymentRecordData: record.attributes, - url: record.attributes.url, - status: DeploymentStatus.Ready, - isCurrent: deployment.environment === Environment.Production - } - ); + await this.db.updateDeploymentById(deployment.id, { + applicationDeploymentRecordId: record.id, + applicationDeploymentRecordData: record.attributes, + url: record.attributes.url, + status: DeploymentStatus.Ready, + isCurrent: deployment.environment === Environment.Production + }); - log(`Updated deployment ${deployment.id} with URL ${record.attributes.url}`); + log( + `Updated deployment ${deployment.id} with URL ${record.attributes.url}` + ); }); await Promise.all(deploymentUpdatePromises); @@ -181,7 +193,10 @@ export class Service { async getOctokit (userId: string): Promise { const user = await this.db.getUser({ where: { id: userId } }); - assert(user && user.gitHubToken, 'User needs to be authenticated with GitHub token'); + assert( + user && user.gitHubToken, + 'User needs to be authenticated with GitHub token' + ); return new Octokit({ auth: user.gitHubToken }); } @@ -206,13 +221,19 @@ export class Service { return dbDeployments; } - async getEnvironmentVariablesByProjectId (projectId: string): Promise { - const dbEnvironmentVariables = await this.db.getEnvironmentVariablesByProjectId(projectId); + async getEnvironmentVariablesByProjectId ( + projectId: string + ): Promise { + const dbEnvironmentVariables = + await this.db.getEnvironmentVariablesByProjectId(projectId); return dbEnvironmentVariables; } - async getProjectMembersByProjectId (projectId: string): Promise { - const dbProjectMembers = await this.db.getProjectMembersByProjectId(projectId); + async getProjectMembersByProjectId ( + projectId: string + ): Promise { + const dbProjectMembers = + await this.db.getProjectMembersByProjectId(projectId); return dbProjectMembers; } @@ -221,20 +242,28 @@ export class Service { return dbProjects; } - async getDomainsByProjectId (projectId: string, filter?: FindOptionsWhere): Promise { + async getDomainsByProjectId ( + projectId: string, + filter?: FindOptionsWhere + ): Promise { const dbDomains = await this.db.getDomainsByProjectId(projectId, filter); return dbDomains; } - async updateProjectMember (projectMemberId: string, data: {permissions: Permission[]}): Promise { + async updateProjectMember ( + projectMemberId: string, + data: { permissions: Permission[] } + ): Promise { return this.db.updateProjectMemberById(projectMemberId, data); } - async addProjectMember (projectId: string, + async addProjectMember ( + projectId: string, data: { - email: string, - permissions: Permission[] - }): Promise { + email: string; + permissions: Permission[]; + } + ): Promise { // TODO: Send invitation let user = await this.db.getUser({ where: { @@ -279,29 +308,41 @@ export class Service { } } - async addEnvironmentVariables (projectId: string, data: { environments: string[], key: string, value: string}[]): Promise { - const formattedEnvironmentVariables = data.map((environmentVariable) => { - return environmentVariable.environments.map((environment) => { - return ({ - key: environmentVariable.key, - value: environmentVariable.value, - environment: environment as Environment, - project: Object.assign(new Project(), { - id: projectId - }) + async addEnvironmentVariables ( + projectId: string, + data: { environments: string[]; key: string; value: string }[] + ): Promise { + const formattedEnvironmentVariables = data + .map((environmentVariable) => { + return environmentVariable.environments.map((environment) => { + return { + key: environmentVariable.key, + value: environmentVariable.value, + environment: environment as Environment, + project: Object.assign(new Project(), { + id: projectId + }) + }; }); - }); - }).flat(); + }) + .flat(); - const savedEnvironmentVariables = await this.db.addEnvironmentVariables(formattedEnvironmentVariables); + const savedEnvironmentVariables = await this.db.addEnvironmentVariables( + formattedEnvironmentVariables + ); return savedEnvironmentVariables; } - async updateEnvironmentVariable (environmentVariableId: string, data : DeepPartial): Promise { + async updateEnvironmentVariable ( + environmentVariableId: string, + data: DeepPartial + ): Promise { return this.db.updateEnvironmentVariable(environmentVariableId, data); } - async removeEnvironmentVariable (environmentVariableId: string): Promise { + async removeEnvironmentVariable ( + environmentVariableId: string + ): Promise { return this.db.deleteEnvironmentVariable(environmentVariableId); } @@ -317,7 +358,10 @@ export class Service { throw new Error('Deployment does not exist'); } - const prodBranchDomains = await this.db.getDomainsByProjectId(oldDeployment.project.id, { branch: oldDeployment.project.prodBranch }); + const prodBranchDomains = await this.db.getDomainsByProjectId( + oldDeployment.project.id, + { branch: oldDeployment.project.prodBranch } + ); const octokit = await this.getOctokit(user.id); @@ -342,7 +386,9 @@ export class Service { recordData: { repoUrl?: string } = {} ): Promise { assert(data.project?.repository, 'Project repository not found'); - log(`Creating deployment in project ${data.project.name} from branch ${data.branch}`); + log( + `Creating deployment in project ${data.project.name} from branch ${data.branch}` + ); const [owner, repo] = data.project.repository.split('/'); const { data: packageJSONData } = await octokit.rest.repos.getContent({ @@ -362,18 +408,22 @@ export class Service { assert(packageJSON.name, "name field doesn't exist in package.json"); if (!recordData.repoUrl) { - const { data: repoDetails } = await octokit.rest.repos.get({ owner, repo }); + const { data: repoDetails } = await octokit.rest.repos.get({ + owner, + repo + }); recordData.repoUrl = repoDetails.html_url; } // TODO: Set environment variables for each deployment (environment variables can`t be set in application record) - const { applicationRecordId, applicationRecordData } = await this.registry.createApplicationRecord({ - appName: repo, - packageJSON, - appType: data.project!.template!, - commitHash: data.commitHash!, - repoUrl: recordData.repoUrl - }); + const { applicationRecordId, applicationRecordData } = + await this.registry.createApplicationRecord({ + appName: repo, + packageJSON, + appType: data.project!.template!, + commitHash: data.commitHash!, + repoUrl: recordData.repoUrl + }); // Update previous deployment with prod branch domain // TODO: Fix unique constraint error for domain @@ -439,7 +489,9 @@ export class Service { const octokit = await this.getOctokit(user.id); const [owner, repo] = project.repository.split('/'); - const { data: [latestCommit] } = await octokit.rest.repos.listCommits({ + const { + data: [latestCommit] + } = await octokit.rest.repos.listCommits({ owner, repo, sha: project.prodBranch, @@ -476,7 +528,10 @@ export class Service { owner, repo, config: { - url: new URL('api/github/webhook', this.config.gitHubConfig.webhookUrl).href, + url: new URL( + 'api/github/webhook', + this.config.gitHubConfig.webhookUrl + ).href, content_type: 'json' }, events: ['push'] @@ -484,9 +539,13 @@ export class Service { } catch (err) { // https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook--status-codes if ( - !(err instanceof RequestError && - err.status === 422 && - (err.response?.data as any).errors.some((err: any) => err.message === GITHUB_UNIQUE_WEBHOOK_ERROR)) + !( + err instanceof RequestError && + err.status === 422 && + (err.response?.data as any).errors.some( + (err: any) => err.message === GITHUB_UNIQUE_WEBHOOK_ERROR + ) + ) ) { throw err; } @@ -498,7 +557,9 @@ export class Service { async handleGitHubPush (data: GitPushEventPayload): Promise { const { repository, ref, head_commit: headCommit } = data; log(`Handling GitHub push event from repository: ${repository.full_name}`); - const projects = await this.db.getProjects({ where: { repository: repository.full_name } }); + const projects = await this.db.getProjects({ + where: { repository: repository.full_name } + }); if (!projects.length) { log(`No projects found for repository ${repository.full_name}`); @@ -510,23 +571,29 @@ export class Service { for await (const project of projects) { const octokit = await this.getOctokit(project.ownerId); - const [domain] = await this.db.getDomainsByProjectId(project.id, { branch }); + const [domain] = await this.db.getDomainsByProjectId(project.id, { + branch + }); // Create deployment with branch and latest commit in GitHub data - await this.createDeployment(project.ownerId, - octokit, - { - project, - branch, - environment: project.prodBranch === branch ? Environment.Production : Environment.Preview, - domain, - commitHash: headCommit.id, - commitMessage: headCommit.message - }); + await this.createDeployment(project.ownerId, octokit, { + project, + branch, + environment: + project.prodBranch === branch + ? Environment.Production + : Environment.Preview, + domain, + commitHash: headCommit.id, + commitMessage: headCommit.message + }); } } - async updateProject (projectId: string, data: DeepPartial): Promise { + async updateProject ( + projectId: string, + data: DeepPartial + ): Promise { return this.db.updateProjectById(projectId, data); } @@ -543,7 +610,9 @@ export class Service { }); if (domainsRedirectedFrom.length > 0) { - throw new Error('Cannot delete domain since it has redirects from other domains'); + throw new Error( + 'Cannot delete domain since it has redirects from other domains' + ); } return this.db.deleteDomainById(domainId); @@ -582,7 +651,10 @@ export class Service { return newDeployment; } - async rollbackDeployment (projectId: string, deploymentId: string): Promise { + async rollbackDeployment ( + projectId: string, + deploymentId: string + ): Promise { // TODO: Implement transactions const oldCurrentDeployment = await this.db.getDeployment({ relations: { @@ -600,16 +672,25 @@ export class Service { throw new Error('Current deployment doesnot exist'); } - const oldCurrentDeploymentUpdate = await this.db.updateDeploymentById(oldCurrentDeployment.id, { isCurrent: false, domain: null }); + const oldCurrentDeploymentUpdate = await this.db.updateDeploymentById( + oldCurrentDeployment.id, + { isCurrent: false, domain: null } + ); - const newCurrentDeploymentUpdate = await this.db.updateDeploymentById(deploymentId, { isCurrent: true, domain: oldCurrentDeployment?.domain }); + const newCurrentDeploymentUpdate = await this.db.updateDeploymentById( + deploymentId, + { isCurrent: true, domain: oldCurrentDeployment?.domain } + ); return newCurrentDeploymentUpdate && oldCurrentDeploymentUpdate; } - async addDomain (projectId: string, data: { name: string }): Promise<{ - primaryDomain: Domain, - redirectedDomain: Domain + async addDomain ( + projectId: string, + data: { name: string } + ): Promise<{ + primaryDomain: Domain; + redirectedDomain: Domain; }> { const currentProject = await this.db.getProjectById(projectId); @@ -634,12 +715,20 @@ export class Service { redirectTo: savedPrimaryDomain }; - const savedRedirectedDomain = await this.db.addDomain(redirectedDomainDetails); + const savedRedirectedDomain = await this.db.addDomain( + redirectedDomainDetails + ); - return { primaryDomain: savedPrimaryDomain, redirectedDomain: savedRedirectedDomain }; + return { + primaryDomain: savedPrimaryDomain, + redirectedDomain: savedRedirectedDomain + }; } - async updateDomain (domainId: string, data: DeepPartial): Promise { + async updateDomain ( + domainId: string, + data: DeepPartial + ): Promise { const domain = await this.db.getDomain({ where: { id: domainId @@ -680,7 +769,9 @@ export class Service { } if (redirectedDomain.redirectToId) { - throw new Error('Unable to redirect to the domain because it is already redirecting elsewhere. Redirects cannot be chained.'); + throw new Error( + 'Unable to redirect to the domain because it is already redirecting elsewhere. Redirects cannot be chained.' + ); } newDomain.redirectTo = redirectedDomain; diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index e0cef120..eae6929f 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -47,5 +47,5 @@ interface RegistryRecord { } export interface AppDeploymentRecord extends RegistryRecord { - attributes: AppDeploymentRecordAttributes + attributes: AppDeploymentRecordAttributes; } diff --git a/packages/backend/src/utils.ts b/packages/backend/src/utils.ts index f9d21e7a..693223bb 100644 --- a/packages/backend/src/utils.ts +++ b/packages/backend/src/utils.ts @@ -37,10 +37,15 @@ export const getEntities = async (filePath: string): Promise => { return entities; }; -export const loadAndSaveData = async (entityType: EntityTarget, dataSource: DataSource, entities: any, relations?: any | undefined): Promise => { +export const loadAndSaveData = async ( + entityType: EntityTarget, + dataSource: DataSource, + entities: any, + relations?: any | undefined +): Promise => { const entityRepository = dataSource.getRepository(entityType); - const savedEntity:Entity[] = []; + const savedEntity: Entity[] = []; for (const entityData of entities) { let entity = entityRepository.create(entityData as DeepPartial); diff --git a/packages/backend/test/delete-db.ts b/packages/backend/test/delete-db.ts index 9fd6a9ab..d984fc8c 100644 --- a/packages/backend/test/delete-db.ts +++ b/packages/backend/test/delete-db.ts @@ -18,4 +18,4 @@ const main = async () => { deleteFile(config.database.dbPath); }; -main().catch(err => log(err)); +main().catch((err) => log(err)); diff --git a/packages/backend/test/fixtures/deployments.json b/packages/backend/test/fixtures/deployments.json index c0536072..b73956f8 100644 --- a/packages/backend/test/fixtures/deployments.json +++ b/packages/backend/test/fixtures/deployments.json @@ -1,9 +1,9 @@ [ { "projectIndex": 0, - "domainIndex":0, + "domainIndex": 0, "createdByIndex": 0, - "id":"ffhae3zq", + "id": "ffhae3zq", "status": "Ready", "environment": "Production", "isCurrent": true, @@ -18,9 +18,9 @@ }, { "projectIndex": 0, - "domainIndex":1, + "domainIndex": 1, "createdByIndex": 0, - "id":"vehagei8", + "id": "vehagei8", "status": "Ready", "environment": "Preview", "isCurrent": false, @@ -35,9 +35,9 @@ }, { "projectIndex": 0, - "domainIndex":2, + "domainIndex": 2, "createdByIndex": 0, - "id":"qmgekyte", + "id": "qmgekyte", "status": "Ready", "environment": "Development", "isCurrent": false, @@ -54,7 +54,7 @@ "projectIndex": 0, "domainIndex": null, "createdByIndex": 0, - "id":"f8wsyim6", + "id": "f8wsyim6", "status": "Ready", "environment": "Production", "isCurrent": false, @@ -69,9 +69,9 @@ }, { "projectIndex": 1, - "domainIndex":3, + "domainIndex": 3, "createdByIndex": 1, - "id":"eO8cckxk", + "id": "eO8cckxk", "status": "Ready", "environment": "Production", "isCurrent": true, @@ -86,9 +86,9 @@ }, { "projectIndex": 1, - "domainIndex":4, + "domainIndex": 4, "createdByIndex": 1, - "id":"yaq0t5yw", + "id": "yaq0t5yw", "status": "Ready", "environment": "Preview", "isCurrent": false, @@ -103,9 +103,9 @@ }, { "projectIndex": 1, - "domainIndex":5, + "domainIndex": 5, "createdByIndex": 1, - "id":"hwwr6sbx", + "id": "hwwr6sbx", "status": "Ready", "environment": "Development", "isCurrent": false, @@ -120,9 +120,9 @@ }, { "projectIndex": 2, - "domainIndex":9, + "domainIndex": 9, "createdByIndex": 2, - "id":"ndxje48a", + "id": "ndxje48a", "status": "Ready", "environment": "Production", "isCurrent": true, @@ -137,9 +137,9 @@ }, { "projectIndex": 2, - "domainIndex":7, + "domainIndex": 7, "createdByIndex": 2, - "id":"gtgpgvei", + "id": "gtgpgvei", "status": "Ready", "environment": "Preview", "isCurrent": false, @@ -154,9 +154,9 @@ }, { "projectIndex": 2, - "domainIndex":8, + "domainIndex": 8, "createdByIndex": 2, - "id":"b4bpthjr", + "id": "b4bpthjr", "status": "Ready", "environment": "Development", "isCurrent": false, @@ -173,7 +173,7 @@ "projectIndex": 3, "domainIndex": 6, "createdByIndex": 2, - "id":"b4bpthjr", + "id": "b4bpthjr", "status": "Ready", "environment": "Production", "isCurrent": true, diff --git a/packages/backend/test/fixtures/project-members.json b/packages/backend/test/fixtures/project-members.json index 7b2661ea..84029c27 100644 --- a/packages/backend/test/fixtures/project-members.json +++ b/packages/backend/test/fixtures/project-members.json @@ -2,77 +2,55 @@ { "memberIndex": 1, "projectIndex": 0, - "permissions": [ - "View" - ], + "permissions": ["View"], "isPending": false }, { "memberIndex": 2, "projectIndex": 0, - "permissions": [ - "View", - "Edit" - ], + "permissions": ["View", "Edit"], "isPending": false }, { "memberIndex": 2, "projectIndex": 1, - "permissions": [ - "View" - ], + "permissions": ["View"], "isPending": false }, { "memberIndex": 0, "projectIndex": 2, - "permissions": [ - "View" - ], + "permissions": ["View"], "isPending": false }, { "memberIndex": 1, "projectIndex": 2, - "permissions": [ - "View", - "Edit" - ], + "permissions": ["View", "Edit"], "isPending": false }, { "memberIndex": 0, "projectIndex": 3, - "permissions": [ - "View" - ], + "permissions": ["View"], "isPending": false }, { "memberIndex": 2, "projectIndex": 3, - "permissions": [ - "View", - "Edit" - ], + "permissions": ["View", "Edit"], "isPending": false }, { "memberIndex": 1, "projectIndex": 4, - "permissions": [ - "View" - ], + "permissions": ["View"], "isPending": false }, { "memberIndex": 2, "projectIndex": 4, - "permissions": [ - "View", - "Edit" - ], + "permissions": ["View", "Edit"], "isPending": false } ] diff --git a/packages/backend/test/initialize-db.ts b/packages/backend/test/initialize-db.ts index 420ff03d..c4384294 100644 --- a/packages/backend/test/initialize-db.ts +++ b/packages/backend/test/initialize-db.ts @@ -10,7 +10,12 @@ import { EnvironmentVariable } from '../src/entity/EnvironmentVariable'; import { Domain } from '../src/entity/Domain'; import { ProjectMember } from '../src/entity/ProjectMember'; import { Deployment } from '../src/entity/Deployment'; -import { checkFileExists, getConfig, getEntities, loadAndSaveData } from '../src/utils'; +import { + checkFileExists, + getConfig, + getEntities, + loadAndSaveData +} from '../src/utils'; import { Config } from '../src/config'; import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants'; @@ -27,19 +32,34 @@ const ENVIRONMENT_VARIABLE_DATA_PATH = './fixtures/environment-variables.json'; const REDIRECTED_DOMAIN_DATA_PATH = './fixtures/redirected-domains.json'; const generateTestData = async (dataSource: DataSource) => { - const userEntities = await getEntities(path.resolve(__dirname, USER_DATA_PATH)); + const userEntities = await getEntities( + path.resolve(__dirname, USER_DATA_PATH) + ); const savedUsers = await loadAndSaveData(User, dataSource, userEntities); - const orgEntities = await getEntities(path.resolve(__dirname, ORGANIZATION_DATA_PATH)); - const savedOrgs = await loadAndSaveData(Organization, dataSource, orgEntities); + const orgEntities = await getEntities( + path.resolve(__dirname, ORGANIZATION_DATA_PATH) + ); + const savedOrgs = await loadAndSaveData( + Organization, + dataSource, + orgEntities + ); const projectRelations = { owner: savedUsers, organization: savedOrgs }; - const projectEntities = await getEntities(path.resolve(__dirname, PROJECT_DATA_PATH)); - const savedProjects = await loadAndSaveData(Project, dataSource, projectEntities, projectRelations); + const projectEntities = await getEntities( + path.resolve(__dirname, PROJECT_DATA_PATH) + ); + const savedProjects = await loadAndSaveData( + Project, + dataSource, + projectEntities, + projectRelations + ); const domainRepository = dataSource.getRepository(Domain); @@ -47,16 +67,30 @@ const generateTestData = async (dataSource: DataSource) => { project: savedProjects }; - const primaryDomainsEntities = await getEntities(path.resolve(__dirname, PRIMARY_DOMAIN_DATA_PATH)); - const savedPrimaryDomains = await loadAndSaveData(Domain, dataSource, primaryDomainsEntities, domainPrimaryRelations); + const primaryDomainsEntities = await getEntities( + path.resolve(__dirname, PRIMARY_DOMAIN_DATA_PATH) + ); + const savedPrimaryDomains = await loadAndSaveData( + Domain, + dataSource, + primaryDomainsEntities, + domainPrimaryRelations + ); const domainRedirectedRelations = { project: savedProjects, redirectTo: savedPrimaryDomains }; - const redirectDomainsEntities = await getEntities(path.resolve(__dirname, REDIRECTED_DOMAIN_DATA_PATH)); - await loadAndSaveData(Domain, dataSource, redirectDomainsEntities, domainRedirectedRelations); + const redirectDomainsEntities = await getEntities( + path.resolve(__dirname, REDIRECTED_DOMAIN_DATA_PATH) + ); + await loadAndSaveData( + Domain, + dataSource, + redirectDomainsEntities, + domainRedirectedRelations + ); const savedDomains = await domainRepository.find(); @@ -65,16 +99,30 @@ const generateTestData = async (dataSource: DataSource) => { organization: savedOrgs }; - const userOrganizationsEntities = await getEntities(path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH)); - await loadAndSaveData(UserOrganization, dataSource, userOrganizationsEntities, userOrganizationRelations); + const userOrganizationsEntities = await getEntities( + path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH) + ); + await loadAndSaveData( + UserOrganization, + dataSource, + userOrganizationsEntities, + userOrganizationRelations + ); const projectMemberRelations = { member: savedUsers, project: savedProjects }; - const projectMembersEntities = await getEntities(path.resolve(__dirname, PROJECT_MEMBER_DATA_PATH)); - await loadAndSaveData(ProjectMember, dataSource, projectMembersEntities, projectMemberRelations); + const projectMembersEntities = await getEntities( + path.resolve(__dirname, PROJECT_MEMBER_DATA_PATH) + ); + await loadAndSaveData( + ProjectMember, + dataSource, + projectMembersEntities, + projectMemberRelations + ); const deploymentRelations = { project: savedProjects, @@ -82,15 +130,29 @@ const generateTestData = async (dataSource: DataSource) => { createdBy: savedUsers }; - const deploymentsEntities = await getEntities(path.resolve(__dirname, DEPLOYMENT_DATA_PATH)); - await loadAndSaveData(Deployment, dataSource, deploymentsEntities, deploymentRelations); + const deploymentsEntities = await getEntities( + path.resolve(__dirname, DEPLOYMENT_DATA_PATH) + ); + await loadAndSaveData( + Deployment, + dataSource, + deploymentsEntities, + deploymentRelations + ); const environmentVariableRelations = { project: savedProjects }; - const environmentVariablesEntities = await getEntities(path.resolve(__dirname, ENVIRONMENT_VARIABLE_DATA_PATH)); - await loadAndSaveData(EnvironmentVariable, dataSource, environmentVariablesEntities, environmentVariableRelations); + const environmentVariablesEntities = await getEntities( + path.resolve(__dirname, ENVIRONMENT_VARIABLE_DATA_PATH) + ); + await loadAndSaveData( + EnvironmentVariable, + dataSource, + environmentVariablesEntities, + environmentVariableRelations + ); }; const main = async () => { diff --git a/packages/backend/test/initialize-registry.ts b/packages/backend/test/initialize-registry.ts index f25d79bc..ac818adc 100644 --- a/packages/backend/test/initialize-registry.ts +++ b/packages/backend/test/initialize-registry.ts @@ -21,12 +21,20 @@ async function main () { const bondId = await registry.getNextBondId(registryConfig.privateKey); log('bondId:', bondId); - await registry.createBond({ denom: DENOM, amount: BOND_AMOUNT }, registryConfig.privateKey, registryConfig.fee); + await registry.createBond( + { denom: DENOM, amount: BOND_AMOUNT }, + registryConfig.privateKey, + registryConfig.fee + ); for await (const name of authorityNames) { await registry.reserveAuthority({ name }, registryConfig.privateKey, registryConfig.fee); log('Reserved authority name:', name); - await registry.setAuthorityBond({ name, bondId }, registryConfig.privateKey, registryConfig.fee); + await registry.setAuthorityBond( + { name, bondId }, + registryConfig.privateKey, + registryConfig.fee + ); log(`Bond ${bondId} set for authority ${name}`); } } diff --git a/packages/backend/test/publish-deploy-records.ts b/packages/backend/test/publish-deploy-records.ts index 33b53d25..0819a91c 100644 --- a/packages/backend/test/publish-deploy-records.ts +++ b/packages/backend/test/publish-deploy-records.ts @@ -14,7 +14,11 @@ const log = debug('snowball:publish-deploy-records'); async function main () { const { registryConfig, database, misc } = await getConfig(DEFAULT_CONFIG_FILE_PATH); - const registry = new Registry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId); + const registry = new Registry( + registryConfig.gqlEndpoint, + registryConfig.restEndpoint, + registryConfig.chainId + ); const dataSource = new DataSource({ type: 'better-sqlite3', diff --git a/packages/frontend/.env.example b/packages/frontend/.env.example new file mode 100644 index 00000000..06d048c4 --- /dev/null +++ b/packages/frontend/.env.example @@ -0,0 +1,7 @@ +REACT_APP_SERVER_URL = 'http://localhost:8000' + +REACT_APP_GITHUB_CLIENT_ID = +REACT_APP_GITHUB_PWA_TEMPLATE_REPO = +REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = + +REACT_APP_WALLET_CONNECT_ID = \ No newline at end of file diff --git a/packages/frontend/.eslintrc.json b/packages/frontend/.eslintrc.json index e7f396b4..a06db087 100644 --- a/packages/frontend/.eslintrc.json +++ b/packages/frontend/.eslintrc.json @@ -16,5 +16,10 @@ "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" - ] + ], + "settings": { + "react": { + "version": "detect" + } + } } diff --git a/packages/frontend/.gitignore b/packages/frontend/.gitignore index 4d29575d..8692cf66 100644 --- a/packages/frontend/.gitignore +++ b/packages/frontend/.gitignore @@ -13,6 +13,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/packages/frontend/.vscode/settings.json b/packages/frontend/.vscode/settings.json new file mode 100644 index 00000000..3566c26b --- /dev/null +++ b/packages/frontend/.vscode/settings.json @@ -0,0 +1,39 @@ +{ + // eslint extension options + "eslint.enable": true, + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "css.customData": [".vscode/tailwind.json"], + // prettier extension setting + "editor.formatOnSave": true, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.rulers": [80], + "editor.codeActionsOnSave": [ + "source.addMissingImports", + "source.fixAll", + "source.organizeImports" + ], + // Show in vscode "Problems" tab when there are errors + "typescript.tsserver.experimental.enableProjectDiagnostics": true, + // Use absolute import for typescript files + "typescript.preferences.importModuleSpecifier": "non-relative", + // IntelliSense for taiwind variants + "tailwindCSS.experimental.classRegex": [ + ["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] + ] +} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 540b4476..5467e2a3 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -3,8 +3,17 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fontsource/inter": "^5.0.16", "@material-tailwind/react": "^2.1.7", "@tanstack/react-query": "^5.22.2", + "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-switch": "^1.0.3", + "@radix-ui/react-radio-group": "^1.1.3", + "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-toast": "^1.1.5", + "@radix-ui/react-tooltip": "^1.0.7", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -16,13 +25,15 @@ "@web3modal/wagmi": "^4.0.5", "assert": "^2.1.0", "axios": "^1.6.7", - "date-fns": "^3.0.1", + "clsx": "^2.1.0", + "date-fns": "^3.3.1", "downshift": "^8.2.3", "eslint-config-react-app": "^7.0.1", "gql-client": "^1.0.0", "luxon": "^3.4.4", "octokit": "^3.1.2", "react": "^18.2.0", + "react-calendar": "^4.8.0", "react-code-blocks": "^0.1.6", "react-day-picker": "^8.9.1", "react-dom": "^18.2.0", @@ -34,6 +45,7 @@ "react-scripts": "5.0.1", "react-timer-hook": "^3.0.7", "siwe": "^2.1.4", + "tailwind-variants": "^0.2.0", "typescript": "^4.9.5", "usehooks-ts": "^2.10.0", "vertical-stepper-nav": "^1.0.2", @@ -78,6 +90,6 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "prettier": "^3.1.0", - "tailwindcss": "^3.3.6" + "tailwindcss": "^3.4.1" } } diff --git a/packages/frontend/public/index.html b/packages/frontend/public/index.html index 271ccc60..dcd5ded0 100644 --- a/packages/frontend/public/index.html +++ b/packages/frontend/public/index.html @@ -3,18 +3,29 @@ - + - - - - - - + + + + + + Snowball diff --git a/packages/frontend/public/logo.svg b/packages/frontend/public/logo.svg new file mode 100644 index 00000000..3781af8a --- /dev/null +++ b/packages/frontend/public/logo.svg @@ -0,0 +1 @@ + diff --git a/packages/frontend/public/site.webmanifest b/packages/frontend/public/site.webmanifest index fd691d95..930233c3 100644 --- a/packages/frontend/public/site.webmanifest +++ b/packages/frontend/public/site.webmanifest @@ -1,19 +1,19 @@ { - "name": "Snowball Tools Dashboard", - "short_name": "snowball tools", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" + "name": "Snowball Tools Dashboard", + "short_name": "snowball tools", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" } diff --git a/packages/frontend/src/components/DatePicker.tsx b/packages/frontend/src/components/DatePicker.tsx index 59114bb0..45ad389f 100644 --- a/packages/frontend/src/components/DatePicker.tsx +++ b/packages/frontend/src/components/DatePicker.tsx @@ -126,7 +126,8 @@ const DatePicker = ({ crossOrigin={undefined} /> - + {/* TODO: Figure out what placeholder is for */} + {mode === 'single' && (
+ {/* TODO: Figure out what placeholder is for */} + {/* TODO: Figure out what placeholder is for */} diff --git a/packages/frontend/src/components/Sidebar.tsx b/packages/frontend/src/components/Sidebar.tsx index ded6f604..d8df365b 100644 --- a/packages/frontend/src/components/Sidebar.tsx +++ b/packages/frontend/src/components/Sidebar.tsx @@ -2,11 +2,13 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Link, NavLink, useNavigate, useParams } from 'react-router-dom'; import { Organization } from 'gql-client'; -import { Typography, Option } from '@material-tailwind/react'; +import { Option } from '@material-tailwind/react'; import { useDisconnect } from 'wagmi'; import { useGQLClient } from '../context/GQLClientContext'; import AsyncSelect from './shared/AsyncSelect'; +import { ChevronGrabberHorizontal, GlobeIcon } from './shared/CustomIcon'; +import { Tabs } from 'components/shared/Tabs'; const Sidebar = () => { const { orgSlug } = useParams(); @@ -33,61 +35,90 @@ const Sidebar = () => { }, [disconnect, navigate]); return ( -
+
-
- -

Snowball

- -
+ +
+ Snowball Logo + + Snowball + +
+ { setSelectedOrgSlug(value!); navigate(`/${value}`); }} selected={(_, index) => ( -
-
^
+
+ Application Logo
- {organizations[index!]?.name} - Organization +
+ {organizations[index!]?.name} +
+
Organization
)} + arrow={} > {/* TODO: Show label organization and manage in option */} {organizations.map((org) => ( ))} -
- (isActive ? 'text-blue-500' : '')} - > - Projects - -
-
- (isActive ? 'text-blue-500' : '')} - > - Settings - -
+ + + {['Projects', 'Settings'].slice(0, 2).map((title, index) => ( + + } value={title}> + {title} + + + ))} + +
-
- - Log Out - - Documentation - Support +
+ + {/* TODO: use proper link buttons */} + + } value=""> + + Log Out + + + } value=""> + Documentation + + } value=""> + Support + + +
); diff --git a/packages/frontend/src/components/projects/ProjectCard.tsx b/packages/frontend/src/components/projects/ProjectCard.tsx index 0d94a777..157a0ed4 100644 --- a/packages/frontend/src/components/projects/ProjectCard.tsx +++ b/packages/frontend/src/components/projects/ProjectCard.tsx @@ -21,11 +21,15 @@ const ProjectCard: React.FC = ({ project }) => { return (
- +
- {project.name} - + {project.name} + {project.deployments[0]?.domain?.name ?? 'No Production Deployment'} @@ -35,25 +39,27 @@ const ProjectCard: React.FC = ({ project }) => { - - ^ Project settings - ^ Delete project + + ^ Project settings + + ^ Delete project +
{project.deployments.length > 0 ? ( <> - + ^ {project.deployments[0].commitMessage} - + {relativeTimeMs(project.deployments[0].createdAt)} on ^  {project.deployments[0].branch} ) : ( - + No Production deployment )} diff --git a/packages/frontend/src/components/projects/ProjectSearchBar.tsx b/packages/frontend/src/components/projects/ProjectSearchBar.tsx index 6d695b66..56249356 100644 --- a/packages/frontend/src/components/projects/ProjectSearchBar.tsx +++ b/packages/frontend/src/components/projects/ProjectSearchBar.tsx @@ -71,12 +71,13 @@ const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => { className={`absolute w-1/2 max-h-52 -mt-1 overflow-y-auto ${ (!inputValue || !isOpen) && 'hidden' }`} + placeholder={''} > {items.length ? ( <>
- + Suggestions
@@ -84,19 +85,25 @@ const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => { - - + +
- + {item.name} {item.organization.name} @@ -106,7 +113,9 @@ const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => { ) : (
- ^ No projects matching this name + + ^ No projects matching this name +
)} diff --git a/packages/frontend/src/components/projects/create/ConnectAccount.tsx b/packages/frontend/src/components/projects/create/ConnectAccount.tsx index 11a5e5d2..ac22a0eb 100644 --- a/packages/frontend/src/components/projects/create/ConnectAccount.tsx +++ b/packages/frontend/src/components/projects/create/ConnectAccount.tsx @@ -46,9 +46,15 @@ const ConnectAccount = ({ onAuth: onToken }: ConnectAccountInterface) => { width={1000} height={1000} > - + {/* TODO: figure out what placeholder is for */} + - + {/* TODO: figure out what placeholder is for */} +
diff --git a/packages/frontend/src/components/projects/create/ConnectAccountTabPanel.tsx b/packages/frontend/src/components/projects/create/ConnectAccountTabPanel.tsx index d46d495a..37730d8d 100644 --- a/packages/frontend/src/components/projects/create/ConnectAccountTabPanel.tsx +++ b/packages/frontend/src/components/projects/create/ConnectAccountTabPanel.tsx @@ -5,11 +5,11 @@ import { Tabs, TabsHeader, Tab } from '@material-tailwind/react'; const ConnectAccountTabPanel = () => { return ( - - + + Import a repository - + Start with a template diff --git a/packages/frontend/src/components/projects/create/Deploy.tsx b/packages/frontend/src/components/projects/create/Deploy.tsx index a5222312..51845369 100644 --- a/packages/frontend/src/components/projects/create/Deploy.tsx +++ b/packages/frontend/src/components/projects/create/Deploy.tsx @@ -43,7 +43,12 @@ const Deploy = () => {
-
@@ -55,7 +60,7 @@ const Deploy = () => { handleConfirm={handleCancel} color="red" > - + This will halt the deployment and you will have to start the process from scratch. diff --git a/packages/frontend/src/components/projects/create/DeployStep.tsx b/packages/frontend/src/components/projects/create/DeployStep.tsx index ce466b32..1aae9c0c 100644 --- a/packages/frontend/src/components/projects/create/DeployStep.tsx +++ b/packages/frontend/src/components/projects/create/DeployStep.tsx @@ -62,7 +62,12 @@ const DeployStep = ({
{processLogs.map((log, key) => { return ( - + {log} ); @@ -75,6 +80,7 @@ const DeployStep = ({ toast.success('Logs copied'); }} color="blue" + placeholder={''} > ^ Copy log diff --git a/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx b/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx index 59a9d57d..c3e88b9a 100644 --- a/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx +++ b/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx @@ -66,7 +66,9 @@ const ProjectRepoCard: React.FC = ({ repository }) => { ) : (
- {'>'} + + {'>'} +
)}
diff --git a/packages/frontend/src/components/projects/create/RepositoryList.tsx b/packages/frontend/src/components/projects/create/RepositoryList.tsx index d0f360bb..8b29967e 100644 --- a/packages/frontend/src/components/projects/create/RepositoryList.tsx +++ b/packages/frontend/src/components/projects/create/RepositoryList.tsx @@ -136,11 +136,12 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => { ) : (
- No repository found + No repository found diff --git a/packages/frontend/src/components/projects/create/TemplateCard.tsx b/packages/frontend/src/components/projects/create/TemplateCard.tsx index bf6e7f29..3625b64b 100644 --- a/packages/frontend/src/components/projects/create/TemplateCard.tsx +++ b/packages/frontend/src/components/projects/create/TemplateCard.tsx @@ -18,11 +18,15 @@ interface TemplateCardProps { const CardDetails = ({ template }: { template: TemplateDetails }) => { return (
- + {template.icon} {template.name}
- + {'>'}
diff --git a/packages/frontend/src/components/projects/project/ActivityCard.tsx b/packages/frontend/src/components/projects/project/ActivityCard.tsx index ffc987d3..5fa5ae82 100644 --- a/packages/frontend/src/components/projects/project/ActivityCard.tsx +++ b/packages/frontend/src/components/projects/project/ActivityCard.tsx @@ -13,20 +13,29 @@ const ActivityCard = ({ activity }: ActivityCardProps) => { return (
- +
- {activity.commit.author?.name} - + {activity.commit.author?.name} + {relativeTimeISO(activity.commit.author!.date!)} ^{' '} {activity.branch.name} - + {activity.commit.message}
- + {'>'}
diff --git a/packages/frontend/src/components/projects/project/deployments/AssignDomainDialog.tsx b/packages/frontend/src/components/projects/project/deployments/AssignDomainDialog.tsx index 79dba9c4..4a4597fa 100644 --- a/packages/frontend/src/components/projects/project/deployments/AssignDomainDialog.tsx +++ b/packages/frontend/src/components/projects/project/deployments/AssignDomainDialog.tsx @@ -17,9 +17,9 @@ interface AssignDomainProps { const AssignDomainDialog = ({ open, handleOpen }: AssignDomainProps) => { return ( - - Assign Domain - + + Assign Domain + In order to assign a domain to your production deployments, configure it in the{' '} {/* TODO: Fix selection of project settings tab on navigation to domains */} @@ -36,12 +36,13 @@ const AssignDomainDialog = ({ open, handleOpen }: AssignDomainProps) => { theme={atomOneLight} /> - + diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx index f76f32e2..e191fe8c 100644 --- a/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx +++ b/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx @@ -93,10 +93,12 @@ const DeploymentDetailsCard = ({
{deployment.url && ( - {deployment.url} + + {deployment.url} + )}
- + {deployment.environment === Environment.Production ? `Production ${deployment.isCurrent ? '(Current)' : ''}` : 'Preview'} @@ -111,14 +113,16 @@ const DeploymentDetailsCard = ({ />
- ^ {deployment.branch} - + + ^ {deployment.branch} + + ^ {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '} {deployment.commitMessage}
- + ^ {relativeTimeMs(deployment.createdAt)} ^{' '} {formatAddress(deployment.createdBy.name ?? '')} @@ -128,18 +132,22 @@ const DeploymentDetailsCard = ({ - + - ^ Visit + + ^ Visit + setAssignDomainDialog(!assignDomainDialog)} + placeholder={''} > ^ Assign domain setChangeToProduction(!changeToProduction)} disabled={!(deployment.environment !== Environment.Production)} + placeholder={''} > ^ Change to production @@ -152,6 +160,7 @@ const DeploymentDetailsCard = ({ deployment.isCurrent ) } + placeholder={''} > ^ Redeploy to production @@ -162,6 +171,7 @@ const DeploymentDetailsCard = ({ deployment.environment !== Environment.Production || !Boolean(currentDeployment) } + placeholder={''} > ^ Rollback to this version @@ -180,17 +190,22 @@ const DeploymentDetailsCard = ({ }} >
- + Upon confirmation, this deployment will be changed to production. - + The new deployment will be associated with these domains: {prodBranchDomains.length > 0 && prodBranchDomains.map((value) => { return ( - + ^ {value.name} ); @@ -209,16 +224,16 @@ const DeploymentDetailsCard = ({ }} >
- + Upon confirmation, new deployment will be created with the same source code as current deployment. - + These domains will point to your new deployment: {deployment.domain?.name && ( - + {deployment.domain?.name} )} @@ -237,7 +252,7 @@ const DeploymentDetailsCard = ({ }} >
- + Upon confirmation, this deployment will replace your current deployment @@ -255,10 +270,10 @@ const DeploymentDetailsCard = ({ color: 'orange', }} /> - + These domains will point to your new deployment: - + ^ {currentDeployment.domain?.name}
diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx index 316551cb..a8b40b8b 100644 --- a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx +++ b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx @@ -20,7 +20,7 @@ const DeploymentDialogBodyCard = ({ deployment, }: DeploymentDialogBodyCardProps) => { return ( - + {chip && ( )} {deployment.url && ( - + {deployment.url} )} - + ^ {deployment.branch} ^{' '} {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '} {deployment.commitMessage} - + ^ {relativeTimeMs(deployment.createdAt)} ^{' '} {formatAddress(deployment.createdBy.name ?? '')} diff --git a/packages/frontend/src/components/projects/project/deployments/FilterForm.tsx b/packages/frontend/src/components/projects/project/deployments/FilterForm.tsx index 8846b47c..c28c6218 100644 --- a/packages/frontend/src/components/projects/project/deployments/FilterForm.tsx +++ b/packages/frontend/src/components/projects/project/deployments/FilterForm.tsx @@ -77,6 +77,7 @@ const FilterForm = ({ value, onChange }: FilterFormProps) => { onClick={() => setSelectedStatus(StatusOptions.ALL_STATUS)} className="rounded-full" size="sm" + placeholder={''} > X diff --git a/packages/frontend/src/components/projects/project/settings/AddEnvironmentVariableRow.tsx b/packages/frontend/src/components/projects/project/settings/AddEnvironmentVariableRow.tsx index 2d2a3bf9..cb93f260 100644 --- a/packages/frontend/src/components/projects/project/settings/AddEnvironmentVariableRow.tsx +++ b/packages/frontend/src/components/projects/project/settings/AddEnvironmentVariableRow.tsx @@ -21,7 +21,9 @@ const AddEnvironmentVariableRow = ({ return (
- Key + + Key +
- Value + + Value + onDelete()} disabled={isDeleteDisabled} + placeholder={''} > {'>'} diff --git a/packages/frontend/src/components/projects/project/settings/AddMemberDialog.tsx b/packages/frontend/src/components/projects/project/settings/AddMemberDialog.tsx index f631e4ce..cd564e81 100644 --- a/packages/frontend/src/components/projects/project/settings/AddMemberDialog.tsx +++ b/packages/frontend/src/components/projects/project/settings/AddMemberDialog.tsx @@ -61,23 +61,26 @@ const AddMemberDialog = ({ }, []); return ( - - + +
Add member
- - + + We will send an invitation link to this email address. - Email address + + Email address + - Permissions - + + Permissions + + You can change this later if required. - - diff --git a/packages/frontend/src/components/projects/project/settings/DeleteProjectDialog.tsx b/packages/frontend/src/components/projects/project/settings/DeleteProjectDialog.tsx index 5f603d4b..b7169d39 100644 --- a/packages/frontend/src/components/projects/project/settings/DeleteProjectDialog.tsx +++ b/packages/frontend/src/components/projects/project/settings/DeleteProjectDialog.tsx @@ -53,20 +53,21 @@ const DeleteProjectDialog = ({ }, [client, project, handleOpen]); return ( - - + +
Delete project?
- - + + Deleting your project is irreversible. Enter your project’s name  ({project.name}) @@ -80,12 +81,17 @@ const DeleteProjectDialog = ({ validate: (value) => value === project.name, })} /> - + ^ Deleting your project is irreversible. - - diff --git a/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx b/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx index 75ee3dad..20a70d8c 100644 --- a/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx +++ b/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx @@ -1,9 +1,8 @@ import React, { useState } from 'react'; import { Card, Collapse, Typography } from '@material-tailwind/react'; -import { Environment, EnvironmentVariable } from 'gql-client/dist/src/types'; - import EditEnvironmentVariableRow from './EditEnvironmentVariableRow'; +import { Environment, EnvironmentVariable } from 'gql-client'; interface DisplayEnvironmentVariablesProps { environment: Environment; @@ -30,11 +29,11 @@ const DisplayEnvironmentVariables = ({
{variables.length === 0 ? ( - - + + No environment variables added yet. - + Once you add them, they’ll show up here. diff --git a/packages/frontend/src/components/projects/project/settings/DomainCard.tsx b/packages/frontend/src/components/projects/project/settings/DomainCard.tsx index 4cfd4967..c5f657eb 100644 --- a/packages/frontend/src/components/projects/project/settings/DomainCard.tsx +++ b/packages/frontend/src/components/projects/project/settings/DomainCard.tsx @@ -68,7 +68,7 @@ const DomainCard = ({ <>
- + ^ {domain.name} - + { setEditDialogOpen((preVal) => !preVal); }} + placeholder={''} > ^ Edit domain setDeleteDialogOpen((preVal) => !preVal)} + placeholder={''} > ^ Delete domain @@ -127,7 +129,7 @@ const DomainCard = ({ }} color="red" > - + Once deleted, the project{' '} {project.name} @@ -140,15 +142,21 @@ const DomainCard = ({
- Production + + Production + {domain.status === DomainStatus.Pending && ( - + {refreshStatus === RefreshStatus.IDLE ? ( - + ^ Add these records to your domain and refresh to check ) : refreshStatus === RefreshStatus.CHECKING ? ( - + ^ Checking records for {domain.name} ) : ( diff --git a/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx b/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx index b9d2866c..b5984acb 100644 --- a/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx +++ b/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx @@ -122,27 +122,32 @@ const EditDomainDialog = ({ }, [domain]); return ( - - + +
Edit domain
- - Domain name + + + Domain name + - Redirect to + + Redirect to + ( - {redirectOptions.map((option, key) => ( - - diff --git a/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx b/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx index f8a87352..c15c142e 100644 --- a/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx +++ b/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx @@ -84,7 +84,9 @@ const EditEnvironmentVariableRow = ({ <>
- Key + + Key +
- Value + + Value + {'S'} @@ -125,6 +130,7 @@ const EditEnvironmentVariableRow = ({ reset(); setEdit((preVal) => !preVal); }} + placeholder={''} > {'C'} @@ -138,6 +144,7 @@ const EditEnvironmentVariableRow = ({ onClick={() => { setEdit((preVal) => !preVal); }} + placeholder={''} > {'E'} @@ -146,6 +153,7 @@ const EditEnvironmentVariableRow = ({ setDeleteDialogOpen((preVal) => !preVal)} + placeholder={''} > {'D'} @@ -162,7 +170,7 @@ const EditEnvironmentVariableRow = ({ handleConfirm={removeEnvironmentVariableHandler} color="red" > - + Are you sure you want to delete the variable  {variable.key}? diff --git a/packages/frontend/src/components/projects/project/settings/MemberCard.tsx b/packages/frontend/src/components/projects/project/settings/MemberCard.tsx index bd7e5c3b..715d8bf4 100644 --- a/packages/frontend/src/components/projects/project/settings/MemberCard.tsx +++ b/packages/frontend/src/components/projects/project/settings/MemberCard.tsx @@ -104,6 +104,7 @@ const MemberCard = ({ selected={(_, index) => ( {DROPDOWN_OPTIONS[index!]?.label} )} + placeholder={''} > {DROPDOWN_OPTIONS.map((permission, key) => (