import { DataSource, DeepPartial } from 'typeorm'; import path from 'path'; import debug from 'debug'; import assert from 'assert'; import { DatabaseConfig } from './config'; import { User } from './entity/User'; import { Organization } from './entity/Organization'; import { UserOrganization } from './entity/UserOrganization'; import { Project } from './entity/Project'; import { Deployment, Environment } from './entity/Deployment'; import { ProjectMember } from './entity/ProjectMember'; import { EnvironmentVariable } from './entity/EnvironmentVariable'; const log = debug('snowball:database'); export class Database { private dataSource: DataSource; constructor ({ dbPath }: DatabaseConfig) { this.dataSource = new DataSource({ type: 'better-sqlite3', database: dbPath, entities: [path.join(__dirname, '/entity/*')], synchronize: true, logging: false }); } async init () : Promise { await this.dataSource.initialize(); log('database initialized'); } async getUser (userId: number) : Promise { const userRepository = this.dataSource.getRepository(User); const user = await userRepository.findOneBy({ id: userId }); return user; } async getOrganizationsByUserId (userId: number) : Promise { const userOrganizationRepository = this.dataSource.getRepository(UserOrganization); const userOrgs = await userOrganizationRepository.find({ relations: { member: true, organization: true }, where: { member: { id: userId } } }); const organizations = userOrgs.map(userOrg => userOrg.organization); return organizations; } async getProjectsByOrganizationId (organizationId: number): Promise { const projectRepository = this.dataSource.getRepository(Project); const projects = await projectRepository.find({ relations: { organization: true, owner: true }, where: { organization: { id: organizationId } } }); return projects; } async getProjectByProjectId (projectId: string): Promise { const projectRepository = this.dataSource.getRepository(Project); const project = await projectRepository.findOne({ relations: { organization: true, owner: true }, where: { id: projectId } }); return project; } async getDeploymentsByProjectId (projectId: string): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const deployments = await deploymentRepository.find({ relations: { project: true, domain: true }, where: { project: { id: projectId } } }); return deployments; } async getProjectMembersByProjectId (projectId: string): Promise { const projectMemberRepository = this.dataSource.getRepository(ProjectMember); const projectMembers = await projectMemberRepository.find({ relations: { project: true, member: true }, where: { project: { id: projectId } } }); return projectMembers; } async getEnvironmentVariablesByProjectId (projectId: string): Promise { const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); const environmentVariables = await environmentVariableRepository.find({ relations: { project: true }, where: { project: { id: projectId } } }); return environmentVariables; } async removeProjectMemberById (memberId: string): Promise { const projectMemberRepository = this.dataSource.getRepository(ProjectMember); const deleted = await projectMemberRepository.delete(memberId); if (deleted.affected) { return deleted.affected > 0; } else { return false; } } async addEnvironmentVariablesByProjectId (projectId: string, environmentVariables: any[]): Promise { const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); const projectRepository = this.dataSource.getRepository(Project); const project = await projectRepository.findOneBy({ id: projectId }); assert(project); const environmentVariablesPromises = environmentVariables.map(async environmentVariable => { const envVar = new EnvironmentVariable(); envVar.key = environmentVariable.key; envVar.value = environmentVariable.value; envVar.environments = environmentVariable.environments; envVar.project = project; return environmentVariableRepository.save(envVar); }); const savedEnvironmentVariables = await Promise.all(environmentVariablesPromises); return savedEnvironmentVariables.length > 0; } async getProjectMemberByMemberId (memberId: string): Promise { const projectMemberRepository = this.dataSource.getRepository(ProjectMember); const projectMemberWithProject = await projectMemberRepository.find({ relations: { project: { owner: true }, member: true }, where: { id: Number(memberId) } } ); if (projectMemberWithProject.length === 0) { throw new Error('Member does not exist'); } return projectMemberWithProject[0]; } async getProjectsBySearchText (userId: number, 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}%` }) .getMany(); return projects; } async updateDeploymentById (deploymentId: string, updates: DeepPartial): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const updateResult = await deploymentRepository.update({ id: Number(deploymentId) }, updates); if (updateResult.affected) { return updateResult.affected > 0; } else { return false; } } async updateProjectById (projectId: string, updates: DeepPartial): Promise { const projectRepository = this.dataSource.getRepository(Project); const updateResult = await projectRepository.update({ id: projectId }, updates); if (updateResult.affected) { return updateResult.affected > 0; } else { return false; } } async redeployToProdById (deploymentId: string): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const deployment = await deploymentRepository.findOne({ relations: { project: true, domain: true }, where: { id: Number(deploymentId) } }); if (deployment === null) { throw new Error('Deployment not found'); } const { id, createdAt, updatedAt, ...updatedDeployment } = deployment; if (updatedDeployment.environment === Environment.Production) { // TODO: Put isCurrent field in project updatedDeployment.isCurrent = true; } await deploymentRepository.update({ id: Number(deploymentId) }, { domain: null, isCurrent: false }); const savedUpdatedDeployment = await deploymentRepository.save(updatedDeployment); if (savedUpdatedDeployment) { return true; } else { return false; } } }