diff --git a/README.md b/README.md index 741efc55..2fe198be 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - Build packages ```bash - yarn build + yarn build --ignore frontend ``` - Change directory to `packages/backend` @@ -24,6 +24,12 @@ cd packages/backend ``` +- Load fixtures in database + + ```bash + yarn db:load:fixtures + ``` + - Start the server ```bash diff --git a/packages/backend/package.json b/packages/backend/package.json index 9e3dcbc4..b4c3910d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -29,7 +29,7 @@ "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", - "db:load:fixtures": "ts-node ./test/initialize-db.ts" + "db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts" }, "devDependencies": { "@types/fs-extra": "^11.0.4", diff --git a/packages/backend/test/fixtures/deployments.json b/packages/backend/test/fixtures/deployments.json new file mode 100644 index 00000000..73c869fa --- /dev/null +++ b/packages/backend/test/fixtures/deployments.json @@ -0,0 +1,92 @@ +[ + { + "projectIndex": 0, + "domainIndex":0, + "title": "nextjs-boilerplate-1", + "status": "Building", + "environment": "Production", + "isCurrent": false, + "branch": "prod", + "commitHash": "testXyz" + }, + { + "projectIndex": 0, + "domainIndex":1, + "title": "nextjs-boilerplate-2", + "status": "Ready", + "environment": "Preview", + "isCurrent": true, + "branch": "prod", + "commitHash": "testXyz" + }, + { + "projectIndex": 0, + "domainIndex":2, + "title": "nextjs-boilerplate-3", + "status": "Error", + "environment": "Development", + "isCurrent": false, + "branch": "prod", + "commitHash": "testXyz" + }, + { + "projectIndex": 1, + "domainIndex":3, + "title": "nextjs-boilerplate-1", + "status": "Building", + "environment": "Production", + "isCurrent": false, + "branch": "prod", + "commitHash": "testXyz" + }, + { + "projectIndex": 1, + "domainIndex":4, + "title": "nextjs-boilerplate-2", + "status": "Ready", + "environment": "Preview", + "isCurrent": true, + "branch": "prod", + "commitHash": "testXyz" + }, + { + "projectIndex": 1, + "domainIndex":5, + "title": "nextjs-boilerplate-3", + "status": "Error", + "environment": "Development", + "isCurrent": false, + "branch": "prod", + "commitHash": "testXyz" + }, + { + "projectIndex": 2, + "domainIndex":6, + "title": "nextjs-boilerplate-1", + "status": "Building", + "environment": "Production", + "isCurrent": false, + "branch": "prod", + "commitHash": "testXyz" + }, + { + "projectIndex": 2, + "domainIndex":7, + "title": "nextjs-boilerplate-2", + "status": "Ready", + "environment": "Preview", + "isCurrent": true, + "branch": "prod", + "commitHash": "testXyz" + }, + { + "projectIndex": 2, + "domainIndex":8, + "title": "nextjs-boilerplate-3", + "status": "Error", + "environment": "Development", + "isCurrent": false, + "branch": "prod", + "commitHash": "testXyz" + } +] diff --git a/packages/backend/test/fixtures/domains.json b/packages/backend/test/fixtures/domains.json new file mode 100644 index 00000000..0631d7cb --- /dev/null +++ b/packages/backend/test/fixtures/domains.json @@ -0,0 +1,56 @@ +[ + { + "name": "randomurl.snowballtools.xyz", + "status": "Live", + "isRedirected": false, + "branch": "test" + }, + { + "name": "saugatt.com", + "status": "Pending", + "isRedirected": false, + "branch": "test" + }, + { + "name": "www.saugatt.com", + "status": "Pending", + "isRedirected": true, + "branch": "test" + }, + { + "name": "randomurl.snowballtools.xyz", + "status": "Live", + "isRedirected": false, + "branch": "test" + }, + { + "name": "saugatt.com", + "status": "Pending", + "isRedirected": false, + "branch": "test" + }, + { + "name": "www.saugatt.com", + "status": "Pending", + "isRedirected": true, + "branch": "test" + }, + { + "name": "randomurl.snowballtools.xyz", + "status": "Live", + "isRedirected": false, + "branch": "test" + }, + { + "name": "saugatt.com", + "status": "Pending", + "isRedirected": false, + "branch": "test" + }, + { + "name": "www.saugatt.com", + "status": "Pending", + "isRedirected": true, + "branch": "test" + } +] diff --git a/packages/backend/test/fixtures/environment-variables.json b/packages/backend/test/fixtures/environment-variables.json new file mode 100644 index 00000000..02c95c49 --- /dev/null +++ b/packages/backend/test/fixtures/environment-variables.json @@ -0,0 +1,62 @@ +[ + { + "projectIndex": 0, + "key": "ABC", + "value": "ABC", + "environments": ["Production", "Preview"] + }, + { + "projectIndex": 0, + "key": "XYZ", + "value": "abc3", + "environments": ["Preview"] + }, + { + "projectIndex": 1, + "key": "ABC", + "value": "ABC", + "environments": ["Production", "Preview"] + }, + { + "projectIndex": 1, + "key": "XYZ", + "value": "abc3", + "environments": ["Preview"] + }, + { + "projectIndex": 2, + "key": "ABC", + "value": "ABC", + "environments": ["Production", "Preview"] + }, + { + "projectIndex": 2, + "key": "XYZ", + "value": "abc3", + "environments": ["Preview"] + }, + { + "projectIndex": 3, + "key": "ABC", + "value": "ABC", + "environments": ["Production", "Preview"] + }, + { + "projectIndex": 3, + "key": "XYZ", + "value": "abc3", + "environments": ["Preview"] + }, + { + "projectIndex": 4, + "key": "ABC", + "value": "ABC", + "environments": ["Production", "Preview"] + }, + { + "projectIndex": 4, + "key": "XYZ", + "value": "abc3", + "environments": ["Preview"] + } +] diff --git a/packages/backend/test/fixtures/project-members.json b/packages/backend/test/fixtures/project-members.json new file mode 100644 index 00000000..c4164b77 --- /dev/null +++ b/packages/backend/test/fixtures/project-members.json @@ -0,0 +1,12 @@ +[ + { + "memberIndex": 1, + "projectIndex": 0, + "permissions": ["View", "Edit"] + }, + { + "memberIndex": 2, + "projectIndex": 0, + "permissions": ["View", "Edit"] + } +] diff --git a/packages/backend/test/initialize-db.ts b/packages/backend/test/initialize-db.ts index 56a20ff9..4a0732bb 100644 --- a/packages/backend/test/initialize-db.ts +++ b/packages/backend/test/initialize-db.ts @@ -14,20 +14,37 @@ import { Deployment } from '../src/entity/Deployment'; const log = debug('snowball:initialize-database'); +const DB_PATH = '../db/snowball'; + const USER_DATA_PATH = './fixtures/users.json'; const PROJECT_DATA_PATH = './fixtures/projects.json'; const ORGANIZATION_DATA_PATH = './fixtures/organizations.json'; const USER_ORGANIZATION_DATA_PATH = './fixtures/user-orgnizations.json'; +const PROJECT_MEMBER_DATA_PATH = './fixtures/project-members.json'; +const DOMAIN_DATA_PATH = './fixtures/domains.json'; +const DEPLOYMENT_DATA_PATH = './fixtures/deployments.json'; +const ENVIRONMENT_VARIABLE_DATA_PATH = './fixtures/environment-variables.json'; -const loadAndSaveData = async (entityType: EntityTarget, dataSource: DataSource, filePath: string) => { +const loadAndSaveData = async (entityType: EntityTarget, dataSource: DataSource, filePath: string, relations?: any | undefined) => { const entitiesData = await fs.readFile(filePath, 'utf-8'); - const entities = JSON.parse(entitiesData) as DeepPartial[]; + const entities = JSON.parse(entitiesData); const entityRepository = dataSource.getRepository(entityType); const savedEntity:Entity[] = []; for (const entityData of entities) { - const entity = entityRepository.create(entityData); + let entity = entityRepository.create(entityData as DeepPartial); + + if (relations) { + for (const field in relations) { + const valueIndex = String(field + 'Index'); + + entity = { + ...entity, + [field]: relations[field][entityData[valueIndex]] + }; + } + } const dbEntity = await entityRepository.save(entity); savedEntity.push(dbEntity); } @@ -38,52 +55,69 @@ const loadAndSaveData = async (entityType: EntityT const generateTestData = async (dataSource: DataSource) => { const savedUsers = await loadAndSaveData(User, dataSource, path.resolve(__dirname, USER_DATA_PATH)); const savedOrgs = await loadAndSaveData(Organization, dataSource, path.resolve(__dirname, ORGANIZATION_DATA_PATH)); + const savedDomains = await loadAndSaveData(Domain, dataSource, path.resolve(__dirname, DOMAIN_DATA_PATH)); - const projectsData = await fs.readFile(path.resolve(__dirname, PROJECT_DATA_PATH), 'utf-8'); - const projects = JSON.parse(projectsData); - const projectRepository = dataSource.getRepository(Project); + const projectRelations = { + owner: savedUsers, + organization: savedOrgs + }; - for (const projectData of projects) { - const project = projectRepository.create(projectData as DeepPartial); - project.owner = savedUsers[projectData.ownerIndex]; - project.organization = savedOrgs[projectData.organizationIndex]; - await projectRepository.save(project); - } + const savedProjects = await loadAndSaveData(Project, dataSource, path.resolve(__dirname, PROJECT_DATA_PATH), projectRelations); - const userOrgData = await fs.readFile(path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH), 'utf-8'); - const userOrgs = JSON.parse(userOrgData); - const userOrgRepository = dataSource.getRepository(UserOrganization); + const userOrganizationRelations = { + member: savedUsers, + organization: savedOrgs + }; - for (const userOrgData of userOrgs) { - const userOrg = userOrgRepository.create(userOrgData as DeepPartial); - userOrg.member = savedUsers[userOrgData.memberIndex]; - userOrg.organization = savedOrgs[userOrgData.organizationIndex]; + await loadAndSaveData(UserOrganization, dataSource, path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH), userOrganizationRelations); - await userOrgRepository.save(userOrg); + const projectMemberRelations = { + member: savedUsers, + project: savedProjects + }; + + await loadAndSaveData(ProjectMember, dataSource, path.resolve(__dirname, PROJECT_MEMBER_DATA_PATH), projectMemberRelations); + + const deploymentRelations = { + project: savedProjects, + domain: savedDomains + }; + + await loadAndSaveData(Deployment, dataSource, path.resolve(__dirname, DEPLOYMENT_DATA_PATH), deploymentRelations); + + const environmentVariableRelations = { + project: savedProjects + }; + + await loadAndSaveData(EnvironmentVariable, dataSource, path.resolve(__dirname, ENVIRONMENT_VARIABLE_DATA_PATH), environmentVariableRelations); +}; + +const checkFileExists = async (filePath: string) => { + try { + await fs.access(filePath, fs.constants.F_OK); + return true; + } catch (err) { + log(err); + return false; } }; const main = async () => { - const dataSource = new DataSource({ - type: 'better-sqlite3', - database: 'db/snowball', - synchronize: true, - logging: true, - entities: [ - User, - Organization, - Project, - UserOrganization, - EnvironmentVariable, - Domain, - ProjectMember, - Deployment - ] - }); + const isDbPresent = await checkFileExists(path.resolve(__dirname, DB_PATH)); - await dataSource.initialize(); + if (!isDbPresent) { + const dataSource = new DataSource({ + type: 'better-sqlite3', + database: 'db/snowball', + synchronize: true, + logging: true, + entities: [path.join(__dirname, '../src/entity/*')] + }); - await generateTestData(dataSource); + await dataSource.initialize(); + + await generateTestData(dataSource); + } }; main().then(() => { diff --git a/packages/frontend/src/components/projects/project/settings/Domains.tsx b/packages/frontend/src/components/projects/project/settings/Domains.tsx index 1874a1e1..d8e8f9ec 100644 --- a/packages/frontend/src/components/projects/project/settings/Domains.tsx +++ b/packages/frontend/src/components/projects/project/settings/Domains.tsx @@ -14,19 +14,19 @@ const Domains = () => { const { projects } = useOutletContext(); - const currProject = useMemo(() => { + const currentProject = useMemo(() => { return projects.find((project) => { return project.id === id; }); }, [id, projects]); const linkedRepo = useMemo(() => { - return currProject?.repositories.find( - (repo: any) => repo.id === Number(currProject?.repositoryId), + return currentProject?.repositories.find( + (repo: any) => repo.id === Number(currentProject?.repositoryId), ); - }, [currProject]); + }, [currentProject]); - const domains = currProject?.deployments + const domains = currentProject?.deployments .filter((deployment) => { return deployment.domain != null; }) @@ -49,7 +49,7 @@ const Domains = () => { domain={domain} key={domain.id} repo={linkedRepo!} - project={currProject!} + project={currentProject!} /> ); })} diff --git a/packages/frontend/src/components/projects/project/settings/EnvironmentVariablesTabPanel.tsx b/packages/frontend/src/components/projects/project/settings/EnvironmentVariablesTabPanel.tsx index 548b90ea..7aba4622 100644 --- a/packages/frontend/src/components/projects/project/settings/EnvironmentVariablesTabPanel.tsx +++ b/packages/frontend/src/components/projects/project/settings/EnvironmentVariablesTabPanel.tsx @@ -38,7 +38,7 @@ export const EnvironmentVariablesTabPanel = () => { const { projects } = useOutletContext(); - const currProject = useMemo(() => { + const currentProject = useMemo(() => { return projects.find((project) => { return project.id === id; }); @@ -77,9 +77,9 @@ export const EnvironmentVariablesTabPanel = () => { }, [isSubmitSuccessful, reset]); const getEnvironmentVariable = useCallback((environment: Environments) => { - return (currProject?.environmentVariables as EnvironmentVariable[]).filter( - (item) => item.environments.includes(environment), - ); + return ( + currentProject?.environmentVariables as EnvironmentVariable[] + ).filter((item) => item.environments.includes(environment)); }, []); const isFieldEmpty = useMemo(() => { diff --git a/packages/frontend/src/components/projects/project/settings/GeneralTabPanel.tsx b/packages/frontend/src/components/projects/project/settings/GeneralTabPanel.tsx index d560e33f..13549862 100644 --- a/packages/frontend/src/components/projects/project/settings/GeneralTabPanel.tsx +++ b/packages/frontend/src/components/projects/project/settings/GeneralTabPanel.tsx @@ -15,7 +15,6 @@ import DeleteProjectDialog from './DeleteProjectDialog'; import ConfirmDialog from '../../../shared/ConfirmDialog'; import { ProjectsOutletContext } from '../../../../types/project'; -const PROJECT_ID = '62f87575-7a2b-4951-8156-9f9821j380d'; const TEAMS = ['Airfoil']; const DEFAULT_SELECT_TEAM = undefined; @@ -37,7 +36,7 @@ const GeneralTabPanel = () => { const { id } = useParams(); const { projects } = useOutletContext(); - const currProject = useMemo(() => { + const currentProject = useMemo(() => { return projects.find((project: any) => project.id === id); }, [id]); @@ -61,128 +60,132 @@ const GeneralTabPanel = () => { const { handleSubmit, register } = useForm({ defaultValues: { - appName: currProject?.name, - description: currProject?.description, + appName: currentProject?.name, + description: currentProject?.description, }, }); return ( <> -
{})}> - Project info - - App name - - - - Description (Optional) - - - - Project ID - - } - /> - -
-
- Transfer project - - Transfer this app to your personal account or a team you are a member - of. - - Learn more - - -
{ - handleTransferProjectDialog(); - })} - > - - Choose team - - ( - + + Description (Optional) + + + + Project ID + + } + /> + + +
+ Transfer project + + Transfer this app to your personal account or a team you are a + member of. + + Learn more + + +
{ + handleTransferProjectDialog(); + })} + > + + Choose team + + ( + + )} + /> + - - - - Upon confirmation, your project nextjs-boilerplate will be - transferred from saugat to Airfoil. - - -
-
- Delete project - - The project will be permanently deleted, including its deployments and - domains. This action is irreversible and can not be undone. - - - -
+ Transfer + + + + + Upon confirmation, your project nextjs-boilerplate will be + transferred from saugat to Airfoil. + + +
+
+ Delete project + + The project will be permanently deleted, including its deployments + and domains. This action is irreversible and can not be undone. + + + +
+ + )} ); }; diff --git a/packages/frontend/src/components/projects/project/settings/MembersTabPanel.tsx b/packages/frontend/src/components/projects/project/settings/MembersTabPanel.tsx index a7a05fe7..8436088d 100644 --- a/packages/frontend/src/components/projects/project/settings/MembersTabPanel.tsx +++ b/packages/frontend/src/components/projects/project/settings/MembersTabPanel.tsx @@ -18,12 +18,12 @@ const MembersTabPanel = () => { // @ts-expect-error create context type for projects const { projects } = useOutletContext(); - const currProject = useMemo(() => { + const currentProject = useMemo(() => { return projects.find((project: any) => project.id === id); }, [id]); const [updatedMembers, setUpdatedMembers] = useState([ - ...currProject?.members, + ...currentProject?.members, ]); const addMemberHandler = useCallback((member: Member) => { @@ -59,7 +59,7 @@ const MembersTabPanel = () => { member={member.member} key={member.id} isFirstCard={index === FIRST_MEMBER_CARD} - isOwner={member.member.id === currProject?.owner.id} + isOwner={member.member.id === currentProject?.owner.id} isPending={member.name === ''} permissions={member.permissions} handleDeletePendingMember={(id: number) => {