diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 73fc358b..e2e48c0c 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -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): Promise { 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; } diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index e54f6336..2020070b 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -19,10 +19,6 @@ export const main = async (): Promise => { // TODO: get config path using cli const { server, database, githubOauth } = await getConfig(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 => { 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); }; diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 9f1dc4e8..f10de5de 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -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 => { +export const createResolvers = async (service: Service): Promise => { 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; diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index 3dc835c4..fcb56278 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -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 { @@ -149,7 +147,7 @@ export class Service { return this.db.deleteEnvironmentVariable(environmentVariableId); } - async updateDeploymentToProd (deploymentId: string): Promise { + async updateDeploymentToProd (userId: string, deploymentId: string): Promise { 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): Promise { @@ -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): Promise { + return this.db.updateUser(userId, data); + } } diff --git a/packages/backend/test/fixtures/organizations.json b/packages/backend/test/fixtures/organizations.json index 1c61eeff..bf934e81 100644 --- a/packages/backend/test/fixtures/organizations.json +++ b/packages/backend/test/fixtures/organizations.json @@ -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" } ] diff --git a/packages/frontend/src/components/projects/ProjectCard.tsx b/packages/frontend/src/components/projects/ProjectCard.tsx index 91399452..ce78a64a 100644 --- a/packages/frontend/src/components/projects/ProjectCard.tsx +++ b/packages/frontend/src/components/projects/ProjectCard.tsx @@ -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 = ({ project }) => { {project.latestCommit.message} - {relativeTime(project.latestCommit.createdAt)} on{' '} + {relativeTimeISO(project.latestCommit.createdAt)} on{' '} {project.latestCommit.branch} diff --git a/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx b/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx index ac3556b0..3847d6ba 100644 --- a/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx +++ b/packages/frontend/src/components/projects/create/ProjectRepoCard.tsx @@ -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 = ({ 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 = ({ repository }) => { /> )} -

{repository.updated_at && relativeTime(repository.updated_at)}

+

{repository.updated_at && relativeTimeISO(repository.updated_at)}

{'>'} diff --git a/packages/frontend/src/components/projects/project/ActivityCard.tsx b/packages/frontend/src/components/projects/project/ActivityCard.tsx index b54d6510..a0e11c17 100644 --- a/packages/frontend/src/components/projects/project/ActivityCard.tsx +++ b/packages/frontend/src/components/projects/project/ActivityCard.tsx @@ -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) => {
{activity.commit.author?.name} - {relativeTime(activity.commit.author!.date!)} ^ {activity.branch.name} + {relativeTimeISO(activity.commit.author!.date!)} ^{' '} + {activity.branch.name} {activity.commit.message} diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx index c2aba303..1ae8aee0 100644 --- a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx +++ b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx @@ -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 = ({ /> )} - {deployment.title} + {deployment.url} ^ {deployment.branch} ^ {deployment.commitHash}{' '} {deployment.commit.message} - ^ {relativeTime(deployment.updatedAt)} ^ {deployment.author} + ^ {relativeTimeMs(deployment.createdAt)} ^ {deployment.createdBy.name} ); diff --git a/packages/frontend/src/constants.ts b/packages/frontend/src/constants.ts index f09a8853..79dfe16d 100644 --- a/packages/frontend/src/constants.ts +++ b/packages/frontend/src/constants.ts @@ -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'; diff --git a/packages/frontend/src/pages/org-slug/projects/create/Template.tsx b/packages/frontend/src/pages/org-slug/projects/create/Template.tsx index 098597eb..dda801f7 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/Template.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/Template.tsx @@ -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 = () => {
^
{template?.name}
- {/* TODO: Get template Git link from DB */} -
^snowball-tools/react-native-starter
+
diff --git a/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx index 50006ede..0e6fb3ff 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx @@ -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 (
@@ -135,10 +132,7 @@ const CreateRepo = () => { name="account" control={control} render={({ field }) => ( - + {gitAccounts.map((account, key) => (