Add fixtures for remaining entities in database initialization script (#36)

* Create fixture data for remaining entities and load it in db

* Rename currProject to currentProject in frontend package

* Handle review changes

* Update readme for loading fixtures

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
Nabarun Gogoi 2024-01-24 16:58:07 +05:30 committed by Ashwin Phatak
parent 890603061f
commit 2d7e56c0e1
11 changed files with 436 additions and 171 deletions

View File

@ -15,7 +15,7 @@
- Build packages - Build packages
```bash ```bash
yarn build yarn build --ignore frontend
``` ```
- Change directory to `packages/backend` - Change directory to `packages/backend`
@ -24,6 +24,12 @@
cd packages/backend cd packages/backend
``` ```
- Load fixtures in database
```bash
yarn db:load:fixtures
```
- Start the server - Start the server
```bash ```bash

View File

@ -29,7 +29,7 @@
"lint": "eslint .", "lint": "eslint .",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check .", "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": { "devDependencies": {
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",

View File

@ -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"
}
]

View File

@ -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"
}
]

View File

@ -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"]
}
]

View File

@ -0,0 +1,12 @@
[
{
"memberIndex": 1,
"projectIndex": 0,
"permissions": ["View", "Edit"]
},
{
"memberIndex": 2,
"projectIndex": 0,
"permissions": ["View", "Edit"]
}
]

View File

@ -14,20 +14,37 @@ import { Deployment } from '../src/entity/Deployment';
const log = debug('snowball:initialize-database'); const log = debug('snowball:initialize-database');
const DB_PATH = '../db/snowball';
const USER_DATA_PATH = './fixtures/users.json'; const USER_DATA_PATH = './fixtures/users.json';
const PROJECT_DATA_PATH = './fixtures/projects.json'; const PROJECT_DATA_PATH = './fixtures/projects.json';
const ORGANIZATION_DATA_PATH = './fixtures/organizations.json'; const ORGANIZATION_DATA_PATH = './fixtures/organizations.json';
const USER_ORGANIZATION_DATA_PATH = './fixtures/user-orgnizations.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 <Entity extends ObjectLiteral>(entityType: EntityTarget<Entity>, dataSource: DataSource, filePath: string) => { const loadAndSaveData = async <Entity extends ObjectLiteral>(entityType: EntityTarget<Entity>, dataSource: DataSource, filePath: string, relations?: any | undefined) => {
const entitiesData = await fs.readFile(filePath, 'utf-8'); const entitiesData = await fs.readFile(filePath, 'utf-8');
const entities = JSON.parse(entitiesData) as DeepPartial<Entity>[]; const entities = JSON.parse(entitiesData);
const entityRepository = dataSource.getRepository(entityType); const entityRepository = dataSource.getRepository(entityType);
const savedEntity:Entity[] = []; const savedEntity:Entity[] = [];
for (const entityData of entities) { for (const entityData of entities) {
const entity = entityRepository.create(entityData); let entity = entityRepository.create(entityData as DeepPartial<Entity>);
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); const dbEntity = await entityRepository.save(entity);
savedEntity.push(dbEntity); savedEntity.push(dbEntity);
} }
@ -38,52 +55,69 @@ const loadAndSaveData = async <Entity extends ObjectLiteral>(entityType: EntityT
const generateTestData = async (dataSource: DataSource) => { const generateTestData = async (dataSource: DataSource) => {
const savedUsers = await loadAndSaveData(User, dataSource, path.resolve(__dirname, USER_DATA_PATH)); 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 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 projectRelations = {
const projects = JSON.parse(projectsData); owner: savedUsers,
const projectRepository = dataSource.getRepository(Project); organization: savedOrgs
};
for (const projectData of projects) { const savedProjects = await loadAndSaveData(Project, dataSource, path.resolve(__dirname, PROJECT_DATA_PATH), projectRelations);
const project = projectRepository.create(projectData as DeepPartial<Project>);
project.owner = savedUsers[projectData.ownerIndex];
project.organization = savedOrgs[projectData.organizationIndex];
await projectRepository.save(project);
}
const userOrgData = await fs.readFile(path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH), 'utf-8'); const userOrganizationRelations = {
const userOrgs = JSON.parse(userOrgData); member: savedUsers,
const userOrgRepository = dataSource.getRepository(UserOrganization); organization: savedOrgs
};
for (const userOrgData of userOrgs) { await loadAndSaveData(UserOrganization, dataSource, path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH), userOrganizationRelations);
const userOrg = userOrgRepository.create(userOrgData as DeepPartial<UserOrganization>);
userOrg.member = savedUsers[userOrgData.memberIndex];
userOrg.organization = savedOrgs[userOrgData.organizationIndex];
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 main = async () => {
const isDbPresent = await checkFileExists(path.resolve(__dirname, DB_PATH));
if (!isDbPresent) {
const dataSource = new DataSource({ const dataSource = new DataSource({
type: 'better-sqlite3', type: 'better-sqlite3',
database: 'db/snowball', database: 'db/snowball',
synchronize: true, synchronize: true,
logging: true, logging: true,
entities: [ entities: [path.join(__dirname, '../src/entity/*')]
User,
Organization,
Project,
UserOrganization,
EnvironmentVariable,
Domain,
ProjectMember,
Deployment
]
}); });
await dataSource.initialize(); await dataSource.initialize();
await generateTestData(dataSource); await generateTestData(dataSource);
}
}; };
main().then(() => { main().then(() => {

View File

@ -14,19 +14,19 @@ const Domains = () => {
const { projects } = useOutletContext<ProjectsOutletContext>(); const { projects } = useOutletContext<ProjectsOutletContext>();
const currProject = useMemo(() => { const currentProject = useMemo(() => {
return projects.find((project) => { return projects.find((project) => {
return project.id === id; return project.id === id;
}); });
}, [id, projects]); }, [id, projects]);
const linkedRepo = useMemo(() => { const linkedRepo = useMemo(() => {
return currProject?.repositories.find( return currentProject?.repositories.find(
(repo: any) => repo.id === Number(currProject?.repositoryId), (repo: any) => repo.id === Number(currentProject?.repositoryId),
); );
}, [currProject]); }, [currentProject]);
const domains = currProject?.deployments const domains = currentProject?.deployments
.filter((deployment) => { .filter((deployment) => {
return deployment.domain != null; return deployment.domain != null;
}) })
@ -49,7 +49,7 @@ const Domains = () => {
domain={domain} domain={domain}
key={domain.id} key={domain.id}
repo={linkedRepo!} repo={linkedRepo!}
project={currProject!} project={currentProject!}
/> />
); );
})} })}

View File

@ -38,7 +38,7 @@ export const EnvironmentVariablesTabPanel = () => {
const { projects } = useOutletContext<ProjectsOutletContext>(); const { projects } = useOutletContext<ProjectsOutletContext>();
const currProject = useMemo(() => { const currentProject = useMemo(() => {
return projects.find((project) => { return projects.find((project) => {
return project.id === id; return project.id === id;
}); });
@ -77,9 +77,9 @@ export const EnvironmentVariablesTabPanel = () => {
}, [isSubmitSuccessful, reset]); }, [isSubmitSuccessful, reset]);
const getEnvironmentVariable = useCallback((environment: Environments) => { const getEnvironmentVariable = useCallback((environment: Environments) => {
return (currProject?.environmentVariables as EnvironmentVariable[]).filter( return (
(item) => item.environments.includes(environment), currentProject?.environmentVariables as EnvironmentVariable[]
); ).filter((item) => item.environments.includes(environment));
}, []); }, []);
const isFieldEmpty = useMemo(() => { const isFieldEmpty = useMemo(() => {

View File

@ -15,7 +15,6 @@ import DeleteProjectDialog from './DeleteProjectDialog';
import ConfirmDialog from '../../../shared/ConfirmDialog'; import ConfirmDialog from '../../../shared/ConfirmDialog';
import { ProjectsOutletContext } from '../../../../types/project'; import { ProjectsOutletContext } from '../../../../types/project';
const PROJECT_ID = '62f87575-7a2b-4951-8156-9f9821j380d';
const TEAMS = ['Airfoil']; const TEAMS = ['Airfoil'];
const DEFAULT_SELECT_TEAM = undefined; const DEFAULT_SELECT_TEAM = undefined;
@ -37,7 +36,7 @@ const GeneralTabPanel = () => {
const { id } = useParams(); const { id } = useParams();
const { projects } = useOutletContext<ProjectsOutletContext>(); const { projects } = useOutletContext<ProjectsOutletContext>();
const currProject = useMemo(() => { const currentProject = useMemo(() => {
return projects.find((project: any) => project.id === id); return projects.find((project: any) => project.id === id);
}, [id]); }, [id]);
@ -61,12 +60,14 @@ const GeneralTabPanel = () => {
const { handleSubmit, register } = useForm({ const { handleSubmit, register } = useForm({
defaultValues: { defaultValues: {
appName: currProject?.name, appName: currentProject?.name,
description: currProject?.description, description: currentProject?.description,
}, },
}); });
return ( return (
<>
{currentProject && (
<> <>
<form onSubmit={handleSubmit(() => {})}> <form onSubmit={handleSubmit(() => {})}>
<Typography variant="h6">Project info</Typography> <Typography variant="h6">Project info</Typography>
@ -95,10 +96,10 @@ const GeneralTabPanel = () => {
<Input <Input
crossOrigin={undefined} crossOrigin={undefined}
variant="outlined" variant="outlined"
value={PROJECT_ID} value={currentProject.id}
size="md" size="md"
disabled disabled
icon={<CopyIcon value={PROJECT_ID} />} icon={<CopyIcon value={currentProject.id} />}
/> />
<Button type="submit" variant="gradient" size="sm" className="mt-1"> <Button type="submit" variant="gradient" size="sm" className="mt-1">
Save Save
@ -107,8 +108,8 @@ const GeneralTabPanel = () => {
<div className="mb-1"> <div className="mb-1">
<Typography variant="h6">Transfer project</Typography> <Typography variant="h6">Transfer project</Typography>
<Typography variant="small"> <Typography variant="small">
Transfer this app to your personal account or a team you are a member Transfer this app to your personal account or a team you are a
of. member of.
<Link to="" className="text-blue-500"> <Link to="" className="text-blue-500">
Learn more Learn more
</Link> </Link>
@ -166,8 +167,8 @@ const GeneralTabPanel = () => {
<div className="mb-1"> <div className="mb-1">
<Typography variant="h6">Delete project</Typography> <Typography variant="h6">Delete project</Typography>
<Typography variant="small"> <Typography variant="small">
The project will be permanently deleted, including its deployments and The project will be permanently deleted, including its deployments
domains. This action is irreversible and can not be undone. and domains. This action is irreversible and can not be undone.
</Typography> </Typography>
<Button <Button
variant="gradient" variant="gradient"
@ -184,6 +185,8 @@ const GeneralTabPanel = () => {
/> />
</div> </div>
</> </>
)}
</>
); );
}; };

View File

@ -18,12 +18,12 @@ const MembersTabPanel = () => {
// @ts-expect-error create context type for projects // @ts-expect-error create context type for projects
const { projects } = useOutletContext(); const { projects } = useOutletContext();
const currProject = useMemo(() => { const currentProject = useMemo(() => {
return projects.find((project: any) => project.id === id); return projects.find((project: any) => project.id === id);
}, [id]); }, [id]);
const [updatedMembers, setUpdatedMembers] = useState([ const [updatedMembers, setUpdatedMembers] = useState([
...currProject?.members, ...currentProject?.members,
]); ]);
const addMemberHandler = useCallback((member: Member) => { const addMemberHandler = useCallback((member: Member) => {
@ -59,7 +59,7 @@ const MembersTabPanel = () => {
member={member.member} member={member.member}
key={member.id} key={member.id}
isFirstCard={index === FIRST_MEMBER_CARD} isFirstCard={index === FIRST_MEMBER_CARD}
isOwner={member.member.id === currProject?.owner.id} isOwner={member.member.id === currentProject?.owner.id}
isPending={member.name === ''} isPending={member.name === ''}
permissions={member.permissions} permissions={member.permissions}
handleDeletePendingMember={(id: number) => { handleDeletePendingMember={(id: number) => {