Merge pull request #68 from twentyhq/cbo-add-user-to-workspaces-on-signup
Assign user to workspace on signin
This commit is contained in:
@ -30,5 +30,8 @@ export const refreshAccessToken = async () => {
|
||||
if (response.ok) {
|
||||
const { accessToken } = await response.json();
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
} else {
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('accessToken');
|
||||
}
|
||||
};
|
||||
|
||||
@ -120,3 +120,24 @@ array_relationships:
|
||||
table:
|
||||
name: user_providers
|
||||
schema: auth
|
||||
- name: workspace_member
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: user_id
|
||||
table:
|
||||
name: workspace_members
|
||||
schema: public
|
||||
event_triggers:
|
||||
- name: user-created
|
||||
definition:
|
||||
enable_manual: false
|
||||
insert:
|
||||
columns: '*'
|
||||
retry_conf:
|
||||
interval_sec: 10
|
||||
num_retries: 0
|
||||
timeout_sec: 60
|
||||
webhook: '{{HASURA_EVENT_HANDLER_URL}}'
|
||||
headers:
|
||||
- name: secret-header
|
||||
value: secret
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
table:
|
||||
name: workspace_members
|
||||
schema: public
|
||||
@ -8,4 +8,5 @@
|
||||
- "!include auth_users.yaml"
|
||||
- "!include public_companies.yaml"
|
||||
- "!include public_people.yaml"
|
||||
- "!include public_workspace_members.yaml"
|
||||
- "!include public_workspaces.yaml"
|
||||
|
||||
10
hasura/migrations/default/1682338781740_squashed/down.sql
Normal file
10
hasura/migrations/default/1682338781740_squashed/down.sql
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
alter table "public"."workspaces" rename column "domain_name" to "name";
|
||||
|
||||
alter table "public"."workspaces" add constraint "workspaces_domain_name_key" unique (domain_name);
|
||||
alter table "public"."workspaces" alter column "domain_name" drop not null;
|
||||
alter table "public"."workspaces" add column "domain_name" text;
|
||||
|
||||
|
||||
|
||||
DROP TABLE "public"."workspace_members";
|
||||
55
hasura/migrations/default/1682338781740_squashed/up.sql
Normal file
55
hasura/migrations/default/1682338781740_squashed/up.sql
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
CREATE TABLE "public"."workspace_members" ("id" serial NOT NULL, "user_id" uuid NOT NULL, "workspace_id" integer NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("user_id"));
|
||||
|
||||
alter table "public"."workspace_members" add column "created_at" timestamptz
|
||||
not null default now();
|
||||
|
||||
alter table "public"."workspace_members" add column "updated_at" timestamptz
|
||||
not null default now();
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
_new record;
|
||||
BEGIN
|
||||
_new := NEW;
|
||||
_new."updated_at" = NOW();
|
||||
RETURN _new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER "set_public_workspace_members_updated_at"
|
||||
BEFORE UPDATE ON "public"."workspace_members"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||
COMMENT ON TRIGGER "set_public_workspace_members_updated_at" ON "public"."workspace_members"
|
||||
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||
|
||||
alter table "public"."workspace_members" add column "deleted_at" timestamptz
|
||||
null;
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_deleted_at"()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
_new record;
|
||||
BEGIN
|
||||
_new := NEW;
|
||||
_new."deleted_at" = NOW();
|
||||
RETURN _new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER "set_public_workspace_members_deleted_at"
|
||||
BEFORE UPDATE ON "public"."workspace_members"
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE "public"."set_current_timestamp_deleted_at"();
|
||||
COMMENT ON TRIGGER "set_public_workspace_members_deleted_at" ON "public"."workspace_members"
|
||||
IS 'trigger to set value of column "deleted_at" to current timestamp on row update';
|
||||
|
||||
alter table "public"."workspaces" add column "domain_name" text
|
||||
null unique;
|
||||
|
||||
alter table "public"."workspaces" drop column "domain_name" cascade;
|
||||
|
||||
alter table "public"."workspaces" rename column "name" to "domain_name";
|
||||
|
||||
alter table "public"."workspaces" add column "deleted_at" Timestamp
|
||||
null;
|
||||
@ -1,6 +1,20 @@
|
||||
HASURA_GRAPHQL_METADATA_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/hasura
|
||||
HASURA_GRAPHQL_PG_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/default
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: secret
|
||||
HASURA_GRAPHQL_JWT_SECRET: '{"type":"HS256", "key": "jwt-very-long-hard-to-guess-secret"}'
|
||||
HASURA_EVENT_HANDLER_URL: http://twenty-server:3000/hasura/events
|
||||
|
||||
HASURA_AUTH_SERVER_URL: http://localhost:4000
|
||||
HASURA_AUTH_CLIENT_URL: http://localhost:3001/auth/callback
|
||||
HASURA_AUTH_PROVIDER_GOOGLE_CLIENT_ID: REPLACE_ME
|
||||
HASURA_AUTH_PROVIDER_GOOGLE_CLIENT_SECRET: REPLACE_ME
|
||||
HASURA_AUTH_GRAPHQL_URL: http://twenty-hasura:8080/v1/graphql
|
||||
|
||||
FRONT_REACT_APP_API_URL=http://localhost:8080
|
||||
FRONT_REACT_APP_AUTH_URL=http://localhost:4000
|
||||
FRONT_REACT_APP_AUTH_URL=http://localhost:4000
|
||||
|
||||
SERVER_HASURA_EVENT_HANDLER_SECRET_HEADER: secret
|
||||
SERVER_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/default
|
||||
SERVER_HASURA_EVENT_HANDLER_SECRET_HEADER: secret
|
||||
|
||||
POSTGRES_PASSWORD=postgrespassword
|
||||
@ -28,22 +28,24 @@ services:
|
||||
- "postgres"
|
||||
restart: always
|
||||
environment:
|
||||
HASURA_GRAPHQL_METADATA_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/hasura
|
||||
HASURA_GRAPHQL_PG_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/default
|
||||
HASURA_GRAPHQL_METADATA_DATABASE_URL: ${HASURA_GRAPHQL_METADATA_DATABASE_URL}
|
||||
HASURA_GRAPHQL_PG_DATABASE_URL: ${HASURA_GRAPHQL_PG_DATABASE_URL}
|
||||
HASURA_GRAPHQL_ENABLE_CONSOLE: "false"
|
||||
HASURA_GRAPHQL_DEV_MODE: "true"
|
||||
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: secret
|
||||
HASURA_GRAPHQL_JWT_SECRET: '{"type":"HS256", "key": "jwt-very-long-hard-to-guess-secret"}'
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_GRAPHQL_ADMIN_SECRET}
|
||||
HASURA_GRAPHQL_JWT_SECRET: ${HASURA_GRAPHQL_JWT_SECRET}
|
||||
HASURA_EVENT_HANDLER_URL: ${HASURA_EVENT_HANDLER_URL}
|
||||
hasura-auth:
|
||||
image: nhost/hasura-auth:0.19.1
|
||||
ports:
|
||||
- "4000:4000"
|
||||
environment:
|
||||
HASURA_GRAPHQL_JWT_SECRET: '{"type":"HS256", "key": "jwt-very-long-hard-to-guess-secret"}'
|
||||
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/default
|
||||
HASURA_GRAPHQL_GRAPHQL_URL: http://twenty-hasura:8080/v1/graphql
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: secret
|
||||
HASURA_GRAPHQL_JWT_SECRET: ${HASURA_GRAPHQL_JWT_SECRET}
|
||||
HASURA_GRAPHQL_DATABASE_URL: ${HASURA_GRAPHQL_PG_DATABASE_URL}
|
||||
HASURA_GRAPHQL_GRAPHQL_URL: ${HASURA_AUTH_GRAPHQL_URL}
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_GRAPHQL_ADMIN_SECRET}
|
||||
AUTH_JWT_CUSTOM_CLAIMS: '{"workspace-id":"workspace_member.workspace_id"}'
|
||||
npm_package_version: '0'
|
||||
AUTH_SMTP_HOST: mailhog
|
||||
AUTH_SMTP_PORT: 1025
|
||||
@ -78,6 +80,9 @@ services:
|
||||
volumes:
|
||||
- ../../server:/app/server
|
||||
- twenty_node_modules_server:/app/server/node_modules
|
||||
environment:
|
||||
HASURA_EVENT_HANDLER_SECRET_HEADER: ${SERVER_HASURA_EVENT_HANDLER_SECRET_HEADER}
|
||||
SERVER_DATABASE_URL: ${SERVER_DATABASE_URL}
|
||||
depends_on:
|
||||
- postgres
|
||||
postgres:
|
||||
@ -85,7 +90,7 @@ services:
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgrespassword
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
|
||||
@ -13,4 +13,4 @@ RUN npm install
|
||||
COPY ../../server .
|
||||
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
CMD ["npm", "run", "start:dev"]
|
||||
1160
server/package-lock.json
generated
1160
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,11 +21,14 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@golevelup/nestjs-hasura": "^3.0.2",
|
||||
"@nestjs/common": "^9.0.0",
|
||||
"@nestjs/core": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.0.0",
|
||||
"@nestjs/serve-static": "^3.0.0",
|
||||
"@nestjs/terminus": "^9.2.2",
|
||||
"@prisma/client": "^4.13.0",
|
||||
"jest-mock-extended": "^3.0.4",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0"
|
||||
@ -45,6 +48,7 @@
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "28.1.3",
|
||||
"prettier": "^2.3.2",
|
||||
"prisma": "^4.13.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "28.0.8",
|
||||
@ -69,5 +73,8 @@
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
},
|
||||
"prisma": {
|
||||
"schema": "src/database/schema.prisma"
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,9 +14,7 @@ describe('AppController', () => {
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.health()).toBe('Healthy!');
|
||||
});
|
||||
it('should be defined', () => {
|
||||
expect(appController).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,9 +4,4 @@ import { AppService } from './app.service';
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get('/health')
|
||||
health(): string {
|
||||
return this.appService.health();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,21 @@ 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';
|
||||
@Module({
|
||||
imports: [TerminusModule],
|
||||
imports: [
|
||||
UserModule,
|
||||
TerminusModule,
|
||||
HasuraModule.forRoot(HasuraModule, {
|
||||
webhookConfig: {
|
||||
secretFactory: process.env.HASURA_EVENT_HANDLER_SECRET_HEADER,
|
||||
secretHeader: 'secret-header',
|
||||
},
|
||||
}),
|
||||
],
|
||||
controllers: [AppController, HealthController],
|
||||
providers: [AppService],
|
||||
providers: [AppService, UserService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
16
server/src/database/client-mock/context.ts
Normal file
16
server/src/database/client-mock/context.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { mockDeep, DeepMockProxy } from 'jest-mock-extended';
|
||||
|
||||
export type Context = {
|
||||
prisma: PrismaClient;
|
||||
};
|
||||
|
||||
export type MockContext = {
|
||||
prisma: DeepMockProxy<PrismaClient>;
|
||||
};
|
||||
|
||||
export const createMockContext = (): MockContext => {
|
||||
return {
|
||||
prisma: mockDeep<PrismaClient>(),
|
||||
};
|
||||
};
|
||||
8
server/src/database/prisma.module.ts
Normal file
8
server/src/database/prisma.module.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
15
server/src/database/prisma.service.ts
Normal file
15
server/src/database/prisma.service.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async enableShutdownHooks(app: INestApplication) {
|
||||
this.$on('beforeExit', async () => {
|
||||
await app.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
30
server/src/database/schema.prisma
Normal file
30
server/src/database/schema.prisma
Normal file
@ -0,0 +1,30 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("SERVER_DATABASE_URL")
|
||||
}
|
||||
|
||||
model WorkspaceMember {
|
||||
id Int @id @default(autoincrement())
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
deleted_at DateTime?
|
||||
user_id String @unique
|
||||
workspace_id Int
|
||||
|
||||
@@map("workspace_members")
|
||||
}
|
||||
|
||||
model Workspace {
|
||||
id Int @id @default(autoincrement())
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
deleted_at DateTime?
|
||||
domain_name String @unique
|
||||
display_name String
|
||||
|
||||
@@map("workspaces")
|
||||
}
|
||||
12
server/src/user/user.module.ts
Normal file
12
server/src/user/user.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
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 {}
|
||||
24
server/src/user/user.repository.ts
Normal file
24
server/src/user/user.repository.ts
Normal file
@ -0,0 +1,24 @@
|
||||
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: {
|
||||
user_id: data.user_id,
|
||||
workspace_id: data.workspace_id,
|
||||
},
|
||||
update: {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
104
server/src/user/user.service.spec.ts
Normal file
104
server/src/user/user.service.spec.ts
Normal file
@ -0,0 +1,104 @@
|
||||
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: {},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
50
server/src/user/user.service.ts
Normal file
50
server/src/user/user.service.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
HasuraInsertEvent,
|
||||
TrackedHasuraEventHandler,
|
||||
} from '@golevelup/nestjs-hasura';
|
||||
import { UserRepository } from './user.repository';
|
||||
import { Injectable, Response } from '@nestjs/common';
|
||||
import { WorkspaceRepository } from './workspace.repository';
|
||||
import { response } from 'express';
|
||||
|
||||
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: {
|
||||
user_id: String(evt.event.data.new.id),
|
||||
workspace_id: workspace.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
14
server/src/user/workspace.repository.ts
Normal file
14
server/src/user/workspace.repository.ts
Normal file
@ -0,0 +1,14 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -12,10 +12,10 @@
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user