Remove hasura and hasura-auth (#134)
* Remove hasura and hasura-auth * Move all models to prisma * Start implementing graphql * chore: clean package json * chore: make the code build * chore: get initial graphql.tsx file * feature: use typegql as qgl server * refactor: small refactoring * refactor: clean tests * bugfix: make all filters not case sensitive * chore: remove unused imports --------- Co-authored-by: Sammy Teillet <sammy.teillet@gmail.com>
This commit is contained in:
8125
server/package-lock.json
generated
8125
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -18,20 +18,26 @@
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"prisma:generate": "npx prisma generate",
|
||||
"prisma:migrate": "npx prisma migrate deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"@golevelup/nestjs-hasura": "^3.0.2",
|
||||
"@nestjs/apollo": "^11.0.5",
|
||||
"@nestjs/common": "^9.0.0",
|
||||
"@nestjs/core": "^9.0.0",
|
||||
"@nestjs/graphql": "^11.0.5",
|
||||
"@nestjs/platform-express": "^9.0.0",
|
||||
"@nestjs/serve-static": "^3.0.0",
|
||||
"@nestjs/terminus": "^9.2.2",
|
||||
"@prisma/client": "^4.13.0",
|
||||
"apollo-server-express": "^3.12.0",
|
||||
"graphql": "^16.6.0",
|
||||
"jest-mock-extended": "^3.0.4",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0",
|
||||
"typegraphql-nestjs": "^0.5.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -56,6 +62,7 @@
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.0.0",
|
||||
"tsconfig-paths": "4.1.0",
|
||||
"typegraphql-prisma": "^0.25.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"jest": {
|
||||
@ -76,6 +83,7 @@
|
||||
"testEnvironment": "node"
|
||||
},
|
||||
"prisma": {
|
||||
"schema": "src/database/schema.prisma"
|
||||
"schema": "src/database/schema.prisma",
|
||||
"seed": "ts-node src/database/seeds/index.ts"
|
||||
}
|
||||
}
|
||||
|
||||
45
server/src/api/api.module.ts
Normal file
45
server/src/api/api.module.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeGraphQLModule } from 'typegraphql-nestjs';
|
||||
import { ApolloDriver } from '@nestjs/apollo';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
import {
|
||||
CompanyCrudResolver,
|
||||
CompanyRelationsResolver,
|
||||
UserCrudResolver,
|
||||
UserRelationsResolver,
|
||||
PersonCrudResolver,
|
||||
PersonRelationsResolver,
|
||||
WorkspaceCrudResolver,
|
||||
WorkspaceRelationsResolver,
|
||||
WorkspaceMemberRelationsResolver,
|
||||
} from '@generated/type-graphql';
|
||||
|
||||
interface Context {
|
||||
prisma: PrismaClient;
|
||||
}
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeGraphQLModule.forRoot({
|
||||
driver: ApolloDriver,
|
||||
path: '/',
|
||||
validate: false,
|
||||
context: (): Context => ({ prisma }),
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
CompanyCrudResolver,
|
||||
CompanyRelationsResolver,
|
||||
UserCrudResolver,
|
||||
UserRelationsResolver,
|
||||
PersonCrudResolver,
|
||||
PersonRelationsResolver,
|
||||
WorkspaceCrudResolver,
|
||||
WorkspaceRelationsResolver,
|
||||
WorkspaceMemberRelationsResolver,
|
||||
],
|
||||
})
|
||||
export class ApiModule {}
|
||||
18
server/src/api/graphql/uuid.ts
Normal file
18
server/src/api/graphql/uuid.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { GraphQLScalarType } from 'graphql';
|
||||
|
||||
const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
function validate(uuid: unknown): string | never {
|
||||
if (typeof uuid !== 'string' || !regex.test(uuid)) {
|
||||
throw new Error('invalid uuid');
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
export const CustomUuidScalar = new GraphQLScalarType({
|
||||
name: 'uuid',
|
||||
description: 'A simple UUID parser',
|
||||
serialize: (value) => validate(value),
|
||||
parseValue: (value) => validate(value),
|
||||
parseLiteral: (ast) => validate(ast.kind === 'StringValue' ? ast.value : ''),
|
||||
});
|
||||
30
server/src/api/models/company.model.ts
Normal file
30
server/src/api/models/company.model.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Field, GraphQLISODateTime, Int, ObjectType } from '@nestjs/graphql';
|
||||
import { Company as CompanyDB } from '@prisma/client';
|
||||
import { CustomUuidScalar } from '../graphql/uuid';
|
||||
|
||||
@ObjectType()
|
||||
export class Company {
|
||||
@Field(() => CustomUuidScalar)
|
||||
id: CompanyDB[`id`];
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
createdAt: CompanyDB[`createdAt`];
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
updatedAt: CompanyDB[`updatedAt`];
|
||||
|
||||
@Field(() => GraphQLISODateTime, { nullable: true })
|
||||
deletedAt: CompanyDB[`deletedAt`];
|
||||
|
||||
@Field(() => String)
|
||||
name: CompanyDB[`name`];
|
||||
|
||||
@Field(() => String)
|
||||
domainName: CompanyDB[`domainName`];
|
||||
|
||||
@Field(() => String)
|
||||
address: CompanyDB[`address`];
|
||||
|
||||
@Field(() => Int)
|
||||
employees: CompanyDB[`employees`];
|
||||
}
|
||||
39
server/src/api/models/person.model.ts
Normal file
39
server/src/api/models/person.model.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { Field, GraphQLISODateTime, ObjectType } from '@nestjs/graphql';
|
||||
import { Person as PersonDB } from '@prisma/client';
|
||||
import { Company } from './company.model';
|
||||
|
||||
@ObjectType()
|
||||
export class Person {
|
||||
@Field(() => String)
|
||||
id: PersonDB[`id`];
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
createdAt: PersonDB[`createdAt`];
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
updatedAt: PersonDB[`updatedAt`];
|
||||
|
||||
@Field(() => GraphQLISODateTime, { nullable: true })
|
||||
deletedAt: PersonDB[`deletedAt`];
|
||||
|
||||
@Field(() => String)
|
||||
firstname: PersonDB[`firstname`];
|
||||
|
||||
@Field(() => String)
|
||||
lastname: PersonDB[`lastname`];
|
||||
|
||||
@Field(() => String)
|
||||
email: PersonDB[`email`];
|
||||
|
||||
@Field(() => String)
|
||||
phone: PersonDB[`phone`];
|
||||
|
||||
@Field(() => String)
|
||||
city: PersonDB[`city`];
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
companyId: PersonDB[`companyId`];
|
||||
|
||||
@Field(() => Company, { nullable: true })
|
||||
company: Company;
|
||||
}
|
||||
17
server/src/api/models/user.model.ts
Normal file
17
server/src/api/models/user.model.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Field, GraphQLISODateTime, ObjectType } from '@nestjs/graphql';
|
||||
import { User as UserDB } from '@prisma/client';
|
||||
|
||||
@ObjectType()
|
||||
export class User {
|
||||
@Field(() => String)
|
||||
id: UserDB[`id`];
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
createdAt: UserDB[`createdAt`];
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
updatedAt: UserDB[`updatedAt`];
|
||||
|
||||
@Field(() => GraphQLISODateTime, { nullable: true })
|
||||
deletedAt: UserDB[`deletedAt`];
|
||||
}
|
||||
17
server/src/api/models/workspace.model.ts
Normal file
17
server/src/api/models/workspace.model.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Field, GraphQLISODateTime, ObjectType } from '@nestjs/graphql';
|
||||
import { Workspace as WorkspaceDB } from '@prisma/client';
|
||||
|
||||
@ObjectType()
|
||||
export class Workspace {
|
||||
@Field(() => String)
|
||||
id: WorkspaceDB[`id`];
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
createdAt: WorkspaceDB[`createdAt`];
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
updatedAt: WorkspaceDB[`updatedAt`];
|
||||
|
||||
@Field(() => GraphQLISODateTime, { nullable: true })
|
||||
deletedAt: WorkspaceDB[`deletedAt`];
|
||||
}
|
||||
15
server/src/api/resolvers/company.resolver.ts
Normal file
15
server/src/api/resolvers/company.resolver.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Company } from '@generated/type-graphql';
|
||||
import { PrismaService } from '../../database/prisma.service';
|
||||
import { Resolver, Query } from 'type-graphql';
|
||||
|
||||
@Resolver(() => Company)
|
||||
export class CompanyResolver {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
@Query(() => [Company])
|
||||
async getCompaniesOfSammy(): Promise<Company[] | null> {
|
||||
return await this.prisma.company.findMany({
|
||||
where: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
22
server/src/api/resolvers/person.resolver.ts
Normal file
22
server/src/api/resolvers/person.resolver.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Parent, Query, ResolveField, Resolver } from '@nestjs/graphql';
|
||||
import { Person } from '../models/person.model';
|
||||
import { PersonRepository } from 'src/entities/person/person.repository';
|
||||
import { CompanyRepository } from 'src/entities/company/company.repository';
|
||||
|
||||
@Resolver(() => Person)
|
||||
export class PersonResolver {
|
||||
constructor(
|
||||
private readonly personRepository: PersonRepository,
|
||||
private readonly companyRepository: CompanyRepository,
|
||||
) {}
|
||||
|
||||
@Query(() => [Person])
|
||||
async getPeople() {
|
||||
return this.personRepository.findMany({});
|
||||
}
|
||||
|
||||
@ResolveField()
|
||||
company(@Parent() person: Person) {
|
||||
return this.companyRepository.findOne(person.companyId);
|
||||
}
|
||||
}
|
||||
0
server/src/api/resolvers/user.resolver.ts
Normal file
0
server/src/api/resolvers/user.resolver.ts
Normal file
14
server/src/api/resolvers/workspace.resolver.ts
Normal file
14
server/src/api/resolvers/workspace.resolver.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Parent, Query, ResolveField, Resolver } from '@nestjs/graphql';
|
||||
import { User } from '../models/user.model';
|
||||
import { WorkspaceRepository } from 'src/entities/workspace/workspace.repository';
|
||||
import { Workspace } from '../models/workspace.model';
|
||||
|
||||
@Resolver(() => Workspace)
|
||||
export class WorkspaceResolver {
|
||||
constructor(private readonly workspaceRepository: WorkspaceRepository) {}
|
||||
|
||||
@Query(() => [Workspace])
|
||||
async getWorkspaces() {
|
||||
return this.workspaceRepository.findMany({});
|
||||
}
|
||||
}
|
||||
@ -3,21 +3,10 @@ import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { HealthController } from './health.controller';
|
||||
import { TerminusModule } from '@nestjs/terminus';
|
||||
import { HasuraModule } from '@golevelup/nestjs-hasura';
|
||||
import { UserService } from './user/user.service';
|
||||
import { UserModule } from './user/user.module';
|
||||
import { ApiModule } from './api/api.module';
|
||||
@Module({
|
||||
imports: [
|
||||
UserModule,
|
||||
TerminusModule,
|
||||
HasuraModule.forRoot(HasuraModule, {
|
||||
webhookConfig: {
|
||||
secretFactory: process.env.HASURA_EVENT_HANDLER_SECRET_HEADER || '',
|
||||
secretHeader: 'secret-header',
|
||||
},
|
||||
}),
|
||||
],
|
||||
imports: [TerminusModule, ApiModule],
|
||||
controllers: [AppController, HealthController],
|
||||
providers: [AppService, UserService],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
103
server/src/database/migrations/20230523144316_init/migration.sql
Normal file
103
server/src/database/migrations/20230523144316_init/migration.sql
Normal file
@ -0,0 +1,103 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"lastSeen" TIMESTAMP(3),
|
||||
"disabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"displayName" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"avatarUrl" TEXT,
|
||||
"locale" TEXT NOT NULL,
|
||||
"phoneNumber" TEXT,
|
||||
"passwordHash" TEXT,
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"metadata" JSONB,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "workspaces" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"domainName" TEXT NOT NULL,
|
||||
"displayName" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "workspaces_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "workspace_members" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"userId" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "workspace_members_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "companies" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"name" TEXT NOT NULL,
|
||||
"domainName" TEXT NOT NULL,
|
||||
"address" TEXT NOT NULL,
|
||||
"employees" INTEGER NOT NULL,
|
||||
"accountOwnerId" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "companies_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "people" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
"firstname" TEXT NOT NULL,
|
||||
"lastname" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"phone" TEXT NOT NULL,
|
||||
"city" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"workspaceId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "people_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "workspaces_domainName_key" ON "workspaces"("domainName");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "workspace_members_userId_key" ON "workspace_members"("userId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "companies" ADD CONSTRAINT "companies_accountOwnerId_fkey" FOREIGN KEY ("accountOwnerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "companies" ADD CONSTRAINT "companies_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "people" ADD CONSTRAINT "people_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "people" ADD CONSTRAINT "people_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -0,0 +1,17 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "companies" DROP CONSTRAINT "companies_accountOwnerId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "people" DROP CONSTRAINT "people_companyId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "companies" ALTER COLUMN "accountOwnerId" DROP NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "people" ALTER COLUMN "companyId" DROP NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "companies" ADD CONSTRAINT "companies_accountOwnerId_fkey" FOREIGN KEY ("accountOwnerId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "people" ADD CONSTRAINT "people_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "companies" ALTER COLUMN "employees" DROP NOT NULL;
|
||||
3
server/src/database/migrations/migration_lock.toml
Normal file
3
server/src/database/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
@ -2,29 +2,96 @@ generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
generator typegraphql {
|
||||
provider = "typegraphql-prisma"
|
||||
output = "../../node_modules/@generated/type-graphql"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("SERVER_DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
lastSeen DateTime?
|
||||
disabled Boolean @default(false)
|
||||
displayName String
|
||||
email String @unique
|
||||
avatarUrl String?
|
||||
locale String
|
||||
phoneNumber String?
|
||||
passwordHash String?
|
||||
emailVerified Boolean @default(false)
|
||||
metadata Json?
|
||||
WorkspaceMember WorkspaceMember?
|
||||
companies Company[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Workspace {
|
||||
id String @id
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
domainName String @unique
|
||||
displayName String
|
||||
WorkspaceMember WorkspaceMember[]
|
||||
companies Company[]
|
||||
people Person[]
|
||||
|
||||
@@map("workspaces")
|
||||
}
|
||||
|
||||
model WorkspaceMember {
|
||||
id String @id
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
deleted_at DateTime?
|
||||
user_id String @unique
|
||||
workspace_id String
|
||||
id String @id
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
userId String @unique
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
workspaceId String
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id])
|
||||
|
||||
@@map("workspace_members")
|
||||
}
|
||||
|
||||
model Workspace {
|
||||
id String @id
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
deleted_at DateTime?
|
||||
domain_name String @unique
|
||||
display_name String
|
||||
model Company {
|
||||
id String @id
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
name String
|
||||
domainName String
|
||||
address String
|
||||
employees Int?
|
||||
accountOwnerId String?
|
||||
accountOwner User? @relation(fields: [accountOwnerId], references: [id])
|
||||
people Person[]
|
||||
workspaceId String
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id])
|
||||
|
||||
@@map("workspaces")
|
||||
@@map("companies")
|
||||
}
|
||||
|
||||
model Person {
|
||||
id String @id
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
firstname String
|
||||
lastname String
|
||||
email String
|
||||
phone String
|
||||
city String
|
||||
companyId String?
|
||||
company Company? @relation(fields: [companyId], references: [id])
|
||||
workspaceId String
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id])
|
||||
|
||||
@@map("people")
|
||||
}
|
||||
|
||||
150
server/src/database/seeds/companies.ts
Normal file
150
server/src/database/seeds/companies.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
export const seedCompanies = async (prisma: PrismaClient) => {
|
||||
await prisma.company.upsert({
|
||||
where: { id: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
name: 'Linkedin',
|
||||
domainName: 'linkedin.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
})
|
||||
await prisma.company.upsert({
|
||||
where: { id: '118995f3-5d81-46d6-bf83-f7fd33ea6102' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '118995f3-5d81-46d6-bf83-f7fd33ea6102',
|
||||
name: 'Facebook',
|
||||
domainName: 'facebook.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
})
|
||||
await prisma.company.upsert({
|
||||
where: { id: '04b2e9f5-0713-40a5-8216-82802401d33e' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '04b2e9f5-0713-40a5-8216-82802401d33e',
|
||||
name: 'Qonto',
|
||||
domainName: 'qonto.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
await prisma.company.upsert({
|
||||
where: { id: '460b6fb1-ed89-413a-b31a-962986e67bb4' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||
name: 'Microsoft',
|
||||
domainName: 'microsoft.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
await prisma.company.upsert({
|
||||
where: { id: '89bb825c-171e-4bcc-9cf7-43448d6fb278' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
name: 'Airbnb',
|
||||
domainName: 'airbnb.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
await prisma.company.upsert({
|
||||
where: { id: '0d940997-c21e-4ec2-873b-de4264d89025' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
name: 'Google',
|
||||
domainName: 'google.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
await prisma.company.upsert({
|
||||
where: { id: '1d3a1c6e-707e-44dc-a1d2-30030bf1a944' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '1d3a1c6e-707e-44dc-a1d2-30030bf1a944',
|
||||
name: 'Netflix',
|
||||
domainName: 'netflix.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
await prisma.company.upsert({
|
||||
where: { id: '7a93d1e5-3f74-492d-a101-2a70f50a1645' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '7a93d1e5-3f74-492d-a101-2a70f50a1645',
|
||||
name: 'Libeo',
|
||||
domainName: 'libeo.io',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.company.upsert({
|
||||
where: { id: '9d162de6-cfbf-4156-a790-e39854dcd4eb' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '9d162de6-cfbf-4156-a790-e39854dcd4eb',
|
||||
name: 'Claap',
|
||||
domainName: 'claap.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.company.upsert({
|
||||
where: { id: 'aaffcfbd-f86b-419f-b794-02319abe8637' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'aaffcfbd-f86b-419f-b794-02319abe8637',
|
||||
name: 'Hasura',
|
||||
domainName: 'hasura.io',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.company.upsert({
|
||||
where: { id: 'f33dc242-5518-4553-9433-42d8eb82834b' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'f33dc242-5518-4553-9433-42d8eb82834b',
|
||||
name: 'Wework',
|
||||
domainName: 'wework.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.company.upsert({
|
||||
where: { id: 'a7bc68d5-f79e-40dd-bd06-c36e6abb4678' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'a7bc68d5-f79e-40dd-bd06-c36e6abb4678',
|
||||
name: 'Samsung',
|
||||
domainName: 'samsung.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
await prisma.company.upsert({
|
||||
where: { id: 'a674fa6c-1455-4c57-afaf-dd5dc086361d' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'a674fa6c-1455-4c57-afaf-dd5dc086361d',
|
||||
name: 'Algolia',
|
||||
domainName: 'algolia.com',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
address: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
15
server/src/database/seeds/index.ts
Normal file
15
server/src/database/seeds/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { seedCompanies } from "./companies";
|
||||
import { seedWorkspaces } from "./workspaces";
|
||||
import { seedPeople } from "./people";
|
||||
|
||||
|
||||
const seed = async () => {
|
||||
const prisma = new PrismaClient()
|
||||
await seedWorkspaces(prisma)
|
||||
await seedCompanies(prisma)
|
||||
await seedPeople(prisma)
|
||||
await prisma.$disconnect()
|
||||
}
|
||||
|
||||
seed()
|
||||
182
server/src/database/seeds/people.ts
Normal file
182
server/src/database/seeds/people.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
export const seedPeople = async (prisma: PrismaClient) => {
|
||||
await prisma.person.upsert({
|
||||
where: { id: '86083141-1c0e-494c-a1b6-85b1c6fefaa5' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '86083141-1c0e-494c-a1b6-85b1c6fefaa5',
|
||||
firstname: 'Christoph',
|
||||
lastname: 'Callisto',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33789012345',
|
||||
city: 'Seattle',
|
||||
companyId: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
email: 'christoph.calisto@linkedin.com'
|
||||
},
|
||||
})
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: '0aa00beb-ac73-4797-824e-87a1f5aea9e0' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '0aa00beb-ac73-4797-824e-87a1f5aea9e0',
|
||||
firstname: 'Sylvie',
|
||||
lastname: 'Palmer',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33780123456',
|
||||
city: 'Los Angeles',
|
||||
companyId: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
email: 'sylvie.palmer@linkedin.com'
|
||||
},
|
||||
})
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: '93c72d2e-f517-42fd-80ae-14173b3b70ae' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '93c72d2e-f517-42fd-80ae-14173b3b70ae',
|
||||
firstname: 'Christopher',
|
||||
lastname: 'Gonzalez',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33789012345',
|
||||
city: 'Seattle',
|
||||
companyId: '04b2e9f5-0713-40a5-8216-82802401d33e',
|
||||
email: 'christopher.gonzalez@qonto.com'
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: 'eeeacacf-eee1-4690-ad2c-8619e5b56a2e' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'eeeacacf-eee1-4690-ad2c-8619e5b56a2e',
|
||||
firstname: 'Ashley',
|
||||
lastname: 'Parker',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33780123456',
|
||||
city: 'Los Angeles',
|
||||
companyId: '04b2e9f5-0713-40a5-8216-82802401d33e',
|
||||
email: 'ashley.parker@qonto.com'
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: '9b324a88-6784-4449-afdf-dc62cb8702f2' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '9b324a88-6784-4449-afdf-dc62cb8702f2',
|
||||
firstname: 'Nicholas',
|
||||
lastname: 'Wright',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33781234567',
|
||||
city: 'Seattle',
|
||||
companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||
email: 'nicholas.wright@microsoft.com'
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: '1d151852-490f-4466-8391-733cfd66a0c8' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '1d151852-490f-4466-8391-733cfd66a0c8',
|
||||
firstname: 'Isabella',
|
||||
lastname: 'Scott',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33782345678',
|
||||
city: 'New York',
|
||||
companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||
email: 'isabella.scott@microsoft.com'
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: '98406e26-80f1-4dff-b570-a74942528de3' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '98406e26-80f1-4dff-b570-a74942528de3',
|
||||
firstname: 'Matthew',
|
||||
lastname: 'Green',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33783456789',
|
||||
city: 'Seattle',
|
||||
companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||
email: 'matthew.green@microsoft.com'
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: 'a2e78a5f-338b-46df-8811-fa08c7d19d35' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'a2e78a5f-338b-46df-8811-fa08c7d19d35',
|
||||
firstname: 'Elizabeth',
|
||||
lastname: 'Baker',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33784567890',
|
||||
city: 'New York',
|
||||
companyId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
email: 'elizabeth.baker@airbnb.com'
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: 'ca1f5bf3-64ad-4b0e-bbfd-e9fd795b7016' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'ca1f5bf3-64ad-4b0e-bbfd-e9fd795b7016',
|
||||
firstname: 'Christopher',
|
||||
lastname: 'Nelson',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33785678901',
|
||||
city: 'San Francisco',
|
||||
companyId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
email: 'christopher.nelson@airbnb.com'
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: '56955422-5d54-41b7-ba36-f0d20e1417ae' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '56955422-5d54-41b7-ba36-f0d20e1417ae',
|
||||
firstname: 'Avery',
|
||||
lastname: 'Carter',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33786789012',
|
||||
city: 'New York',
|
||||
companyId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
email: 'avery.carter@airbnb.com'
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: '755035db-623d-41fe-92e7-dd45b7c568e1' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '755035db-623d-41fe-92e7-dd45b7c568e1',
|
||||
firstname: 'Ethan',
|
||||
lastname: 'Mitchell',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33787890123',
|
||||
city: 'Los Angeles',
|
||||
companyId: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
email: 'ethan.mitchell@google.com'
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.person.upsert({
|
||||
where: { id: '240da2ec-2d40-4e49-8df4-9c6a049190df' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '240da2ec-2d40-4e49-8df4-9c6a049190df',
|
||||
firstname: 'Madison',
|
||||
lastname: 'Perez',
|
||||
workspaceId: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
phone: '+33788901234',
|
||||
city: 'Seattle',
|
||||
companyId: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
email: 'madison.perez@google.com'
|
||||
},
|
||||
});
|
||||
}
|
||||
21
server/src/database/seeds/workspaces.ts
Normal file
21
server/src/database/seeds/workspaces.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
export const seedWorkspaces = async (prisma: PrismaClient) => {
|
||||
await prisma.workspace.upsert({
|
||||
where: { domainName: 'twenty.com' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||
displayName: 'Twenty',
|
||||
domainName: 'twenty.com',
|
||||
},
|
||||
})
|
||||
await prisma.workspace.upsert({
|
||||
where: { domainName: 'claap.com' },
|
||||
update: {},
|
||||
create: {
|
||||
id: '7ed9d212-1c25-4d02-bf25-6aeccf7ea420',
|
||||
displayName: 'Claap',
|
||||
domainName: 'claap.com',
|
||||
},
|
||||
})
|
||||
}
|
||||
10
server/src/entities/company/company.module.ts
Normal file
10
server/src/entities/company/company.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CompanyRepository } from './company.repository';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [CompanyRepository],
|
||||
exports: [CompanyRepository]
|
||||
})
|
||||
export class CompanyModule {}
|
||||
25
server/src/entities/company/company.repository.ts
Normal file
25
server/src/entities/company/company.repository.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Company, Prisma } from '@prisma/client';
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class CompanyRepository {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findMany(params: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
cursor?: Prisma.CompanyWhereUniqueInput;
|
||||
where?: Prisma.CompanyWhereInput;
|
||||
orderBy?: Prisma.CompanyOrderByWithRelationInput;
|
||||
}): Promise<Company[]> {
|
||||
const { skip, take, cursor, where, orderBy } = params;
|
||||
return this.prisma.company.findMany({ skip, take, cursor, where, orderBy });
|
||||
}
|
||||
|
||||
async findOne(id: string | null) {
|
||||
if (id === null) return null;
|
||||
const company = await this.prisma.company.findUnique({ where: { id } });
|
||||
return company;
|
||||
}
|
||||
}
|
||||
10
server/src/entities/person/person.module.ts
Normal file
10
server/src/entities/person/person.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PersonRepository } from './person.repository';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [PersonRepository],
|
||||
exports: [PersonRepository]
|
||||
})
|
||||
export class PersonModule {}
|
||||
19
server/src/entities/person/person.repository.ts
Normal file
19
server/src/entities/person/person.repository.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Person, Prisma } from '@prisma/client';
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class PersonRepository {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findMany(params: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
cursor?: Prisma.PersonWhereUniqueInput;
|
||||
where?: Prisma.PersonWhereInput;
|
||||
orderBy?: Prisma.PersonOrderByWithRelationInput;
|
||||
}): Promise<Person[]> {
|
||||
const { skip, take, cursor, where, orderBy } = params;
|
||||
return this.prisma.person.findMany({ skip, take, cursor, where, orderBy });
|
||||
}
|
||||
}
|
||||
10
server/src/entities/user/user.module.ts
Normal file
10
server/src/entities/user/user.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UserRepository } from './user.repository';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [UserRepository],
|
||||
exports: [UserRepository]
|
||||
})
|
||||
export class UserModule {}
|
||||
19
server/src/entities/user/user.repository.ts
Normal file
19
server/src/entities/user/user.repository.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { User, Prisma } from '@prisma/client';
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class UserRepository {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findMany(params: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
cursor?: Prisma.UserWhereUniqueInput;
|
||||
where?: Prisma.UserWhereInput;
|
||||
orderBy?: Prisma.UserOrderByWithRelationInput;
|
||||
}): Promise<User[]> {
|
||||
const { skip, take, cursor, where, orderBy } = params;
|
||||
return this.prisma.user.findMany({ skip, take, cursor, where, orderBy });
|
||||
}
|
||||
}
|
||||
10
server/src/entities/workspace/workspace.module.ts
Normal file
10
server/src/entities/workspace/workspace.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { WorkspaceRepository } from './workspace.repository';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [WorkspaceRepository],
|
||||
exports: [WorkspaceRepository]
|
||||
})
|
||||
export class WorkspaceModule {}
|
||||
19
server/src/entities/workspace/workspace.repository.ts
Normal file
19
server/src/entities/workspace/workspace.repository.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Workspace, Prisma } from '@prisma/client';
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceRepository {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findMany(params: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
cursor?: Prisma.WorkspaceWhereUniqueInput;
|
||||
where?: Prisma.WorkspaceWhereInput;
|
||||
orderBy?: Prisma.WorkspaceOrderByWithRelationInput;
|
||||
}): Promise<Workspace[]> {
|
||||
const { skip, take, cursor, where, orderBy } = params;
|
||||
return this.prisma.workspace.findMany({ skip, take, cursor, where, orderBy });
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const app = await NestFactory.create(AppModule, { cors: true });
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/database/prisma.module';
|
||||
import { UserRepository } from './user.repository';
|
||||
import { UserService } from './user.service';
|
||||
import { WorkspaceRepository } from './workspace.repository';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [UserRepository, UserService, WorkspaceRepository],
|
||||
exports: [UserService, UserRepository, WorkspaceRepository],
|
||||
})
|
||||
export class UserModule {}
|
||||
@ -1,26 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, WorkspaceMember } from '@prisma/client';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class UserRepository {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async upsertWorkspaceMember(params: { data: Prisma.WorkspaceMemberCreateInput }): Promise<WorkspaceMember> {
|
||||
const { data } = params;
|
||||
|
||||
return await this.prisma.workspaceMember.upsert({
|
||||
where: {
|
||||
user_id: data.user_id,
|
||||
},
|
||||
create: {
|
||||
id: data.id,
|
||||
user_id: data.user_id,
|
||||
workspace_id: data.workspace_id,
|
||||
},
|
||||
update: {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UserService } from './user.service';
|
||||
import { UserRepository } from './user.repository';
|
||||
import { WorkspaceRepository } from './workspace.repository';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
import {
|
||||
MockContext,
|
||||
createMockContext,
|
||||
} from '../database/client-mock/context';
|
||||
import { DeepMockProxy } from 'jest-mock-extended';
|
||||
|
||||
describe('UserService', () => {
|
||||
let mockCtx: MockContext;
|
||||
let service: UserService;
|
||||
let mockedPrismaService: DeepMockProxy<PrismaService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockCtx = createMockContext();
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
UserService,
|
||||
UserRepository,
|
||||
WorkspaceRepository,
|
||||
PrismaService,
|
||||
],
|
||||
})
|
||||
.overrideProvider(PrismaService)
|
||||
.useValue(mockCtx.prisma)
|
||||
.compile();
|
||||
|
||||
service = module.get<UserService>(UserService);
|
||||
mockedPrismaService =
|
||||
module.get<DeepMockProxy<PrismaService>>(PrismaService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
it('upsertWorkspaceMember should not upsert if email is absent', () => {
|
||||
service.handleUserCreated({
|
||||
event: {
|
||||
data: { new: { id: 1, email: ''}, old: null },
|
||||
session_variables: {},
|
||||
op: 'INSERT'
|
||||
},
|
||||
id: '1',
|
||||
table: { schema: 'auth', name: 'users' },
|
||||
trigger: { name: 'user-created' },
|
||||
delivery_info: { current_retry: 0, max_retries: 0},
|
||||
created_at: '2021-03-01T00:00:00.000Z',
|
||||
});
|
||||
expect(mockedPrismaService.workspace.findUnique).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('upsertWorkspaceMember should upsert if domain name is found from email', async () => {
|
||||
mockedPrismaService.workspace.findUnique.mockResolvedValue({
|
||||
id: 2,
|
||||
display_name: 'test',
|
||||
domain_name: 'domain.namexxx',
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
deleted_at: null,
|
||||
});
|
||||
|
||||
mockedPrismaService.workspaceMember.upsert.mockResolvedValue({
|
||||
id: 1,
|
||||
user_id: '1',
|
||||
workspace_id: 1,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
deleted_at: null,
|
||||
});
|
||||
|
||||
await service.handleUserCreated({
|
||||
event: {
|
||||
data: { new: { id: 1, email: 'test@domain.name' }, old: null },
|
||||
session_variables: {},
|
||||
op: 'INSERT',
|
||||
},
|
||||
id: '1',
|
||||
table: { schema: 'auth', name: 'users' },
|
||||
trigger: { name: 'user-created' },
|
||||
delivery_info: { current_retry: 0, max_retries: 0 },
|
||||
created_at: '2021-03-01T00:00:00.000Z',
|
||||
});
|
||||
expect(mockedPrismaService.workspace.findUnique).toHaveBeenCalledWith({
|
||||
where: { domain_name: 'domain.name' },
|
||||
});
|
||||
expect(mockedPrismaService.workspaceMember.upsert).toHaveBeenCalledWith(
|
||||
{
|
||||
where: {
|
||||
user_id: '1',
|
||||
},
|
||||
create: {
|
||||
user_id: '1',
|
||||
workspace_id: 2,
|
||||
},
|
||||
update: {},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,51 +0,0 @@
|
||||
import {
|
||||
HasuraInsertEvent,
|
||||
TrackedHasuraEventHandler,
|
||||
} from '@golevelup/nestjs-hasura';
|
||||
import { UserRepository } from './user.repository';
|
||||
import { Injectable, Response } from '@nestjs/common';
|
||||
import { WorkspaceRepository } from './workspace.repository';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
private repository: UserRepository,
|
||||
private workspaceRepository: WorkspaceRepository,
|
||||
) {}
|
||||
|
||||
@TrackedHasuraEventHandler({
|
||||
triggerName: 'user-created',
|
||||
tableName: 'users',
|
||||
schema: 'auth',
|
||||
definition: { type: 'insert' },
|
||||
})
|
||||
async handleUserCreated(evt: HasuraInsertEvent<User>) {
|
||||
const emailDomain = evt.event.data.new.email.split('@')[1];
|
||||
|
||||
if (!emailDomain) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = await this.workspaceRepository.findWorkspaceByDomainName({
|
||||
where: { domain_name: emailDomain },
|
||||
});
|
||||
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceMember = await this.repository.upsertWorkspaceMember({
|
||||
data: {
|
||||
id: v4(),
|
||||
user_id: String(evt.event.data.new.id),
|
||||
workspace_id: workspace.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, Workspace } from '@prisma/client';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceRepository {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findWorkspaceByDomainName(
|
||||
data: Prisma.WorkspaceFindUniqueArgs,
|
||||
): Promise<Workspace | null> {
|
||||
return await this.prisma.workspace.findUnique(data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user