Remove usages of connectToDataSource and use workspaceDataSource (#11873)
In this PR we are 1. cleaning typeORM service by removing connectToDataSource method 2. using workspaceDataSource instead of mainDataSource when possible, and replacing raw SQL with workspaceRepository methods to use
This commit is contained in:
@ -100,7 +100,9 @@ export class GoogleAPIsService {
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId);
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const scopes = getGoogleApisOauthScopes();
|
||||
|
||||
|
||||
@ -104,7 +104,9 @@ export class MicrosoftAPIsService {
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId);
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const scopes = getMicrosoftApisOauthScopes();
|
||||
|
||||
|
||||
@ -14,9 +14,7 @@ describe('JwtAuthStrategy', () => {
|
||||
let workspaceRepository: any;
|
||||
let userWorkspaceRepository: any;
|
||||
let userRepository: any;
|
||||
let dataSourceService: any;
|
||||
let typeORMService: any;
|
||||
|
||||
let twentyORMGlobalManager: any;
|
||||
const jwt = {
|
||||
sub: 'sub-default',
|
||||
jti: 'jti-default',
|
||||
@ -38,6 +36,12 @@ describe('JwtAuthStrategy', () => {
|
||||
extractJwtFromRequest: jest.fn(() => () => 'token'),
|
||||
};
|
||||
|
||||
twentyORMGlobalManager = {
|
||||
getRepositoryForWorkspace: jest.fn(async () => ({
|
||||
findOne: jest.fn(async () => ({ id: 'api-key-id', revokedAt: null })),
|
||||
})),
|
||||
};
|
||||
|
||||
// first we test the API_KEY case
|
||||
it('should throw AuthException if type is API_KEY and workspace is not found', async () => {
|
||||
const payload = {
|
||||
@ -50,10 +54,8 @@ describe('JwtAuthStrategy', () => {
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
{} as any,
|
||||
userWorkspaceRepository,
|
||||
@ -77,19 +79,15 @@ describe('JwtAuthStrategy', () => {
|
||||
findOneBy: jest.fn(async () => new Workspace()),
|
||||
};
|
||||
|
||||
dataSourceService = {
|
||||
getLastDataSourceMetadataFromWorkspaceIdOrFail: jest.fn(async () => ({})),
|
||||
};
|
||||
|
||||
typeORMService = {
|
||||
connectToDataSource: jest.fn(async () => {}),
|
||||
twentyORMGlobalManager = {
|
||||
getRepositoryForWorkspace: jest.fn(async () => ({
|
||||
findOne: jest.fn(async () => null),
|
||||
})),
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
{} as any,
|
||||
userWorkspaceRepository,
|
||||
@ -113,21 +111,15 @@ describe('JwtAuthStrategy', () => {
|
||||
findOneBy: jest.fn(async () => new Workspace()),
|
||||
};
|
||||
|
||||
const mockDataSource = {
|
||||
query: jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ id: 'api-key-id', revokedAt: null }]),
|
||||
twentyORMGlobalManager = {
|
||||
getRepositoryForWorkspace: jest.fn(async () => ({
|
||||
findOne: jest.fn(async () => ({ id: 'api-key-id', revokedAt: null })),
|
||||
})),
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(typeORMService, 'connectToDataSource')
|
||||
.mockResolvedValue(mockDataSource as any);
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
{} as any,
|
||||
userWorkspaceRepository,
|
||||
@ -140,7 +132,6 @@ describe('JwtAuthStrategy', () => {
|
||||
});
|
||||
|
||||
// second we test the ACCESS cases
|
||||
|
||||
it('should throw AuthExceptionCode if type is ACCESS, no jti, and user not found', async () => {
|
||||
const payload = {
|
||||
sub: 'sub-default',
|
||||
@ -156,10 +147,8 @@ describe('JwtAuthStrategy', () => {
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
@ -194,10 +183,8 @@ describe('JwtAuthStrategy', () => {
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
@ -235,10 +222,8 @@ describe('JwtAuthStrategy', () => {
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
|
||||
@ -5,7 +5,6 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Strategy } from 'passport-jwt';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
@ -15,20 +14,17 @@ import {
|
||||
JwtPayload,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { ApiKeyWorkspaceEntity } from 'src/modules/api-key/standard-objects/api-key.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly jwtWrapperService: JwtWrapperService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(User, 'core')
|
||||
@ -37,7 +33,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||
) {
|
||||
const jwtFromRequestFunction = jwtWrapperService.extractJwtFromRequest();
|
||||
const secretOrKeyProviderFunction = async (request, rawJwtToken, done) => {
|
||||
const secretOrKeyProviderFunction = async (_request, rawJwtToken, done) => {
|
||||
try {
|
||||
const decodedToken = jwtWrapperService.decode(
|
||||
rawJwtToken,
|
||||
@ -75,20 +71,20 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
);
|
||||
}
|
||||
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
const apiKeyRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ApiKeyWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'apiKey',
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
const res = await workspaceDataSource?.query(
|
||||
`SELECT * FROM ${dataSourceMetadata.schema}."apiKey" WHERE id = $1`,
|
||||
[payload.jti],
|
||||
);
|
||||
|
||||
apiKey = res?.[0];
|
||||
apiKey = await apiKeyRepository.findOne({
|
||||
where: {
|
||||
id: payload.jti,
|
||||
},
|
||||
});
|
||||
|
||||
if (!apiKey || apiKey.revokedAt) {
|
||||
throw new AuthException(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
@ -13,7 +13,6 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
@ -27,7 +26,6 @@ describe('UserWorkspaceService', () => {
|
||||
let userWorkspaceRepository: Repository<UserWorkspace>;
|
||||
let userRepository: Repository<User>;
|
||||
let objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
||||
let dataSourceService: DataSourceService;
|
||||
let typeORMService: TypeORMService;
|
||||
let workspaceInvitationService: WorkspaceInvitationService;
|
||||
let workspaceEventEmitter: WorkspaceEventEmitter;
|
||||
@ -71,7 +69,7 @@ describe('UserWorkspaceService', () => {
|
||||
{
|
||||
provide: TypeORMService,
|
||||
useValue: {
|
||||
connectToDataSource: jest.fn(),
|
||||
getMainDataSource: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -116,7 +114,6 @@ describe('UserWorkspaceService', () => {
|
||||
objectMetadataRepository = module.get(
|
||||
getRepositoryToken(ObjectMetadataEntity, 'metadata'),
|
||||
);
|
||||
dataSourceService = module.get<DataSourceService>(DataSourceService);
|
||||
typeORMService = module.get<TypeORMService>(TypeORMService);
|
||||
workspaceInvitationService = module.get<WorkspaceInvitationService>(
|
||||
WorkspaceInvitationService,
|
||||
@ -179,12 +176,9 @@ describe('UserWorkspaceService', () => {
|
||||
defaultAvatarUrl: 'avatar-url',
|
||||
locale: 'en',
|
||||
} as User;
|
||||
const dataSourceMetadata = {
|
||||
schema: 'public',
|
||||
} as DataSourceEntity;
|
||||
const workspaceDataSource = {
|
||||
const mainDataSource = {
|
||||
query: jest.fn(),
|
||||
};
|
||||
} as unknown as DataSource;
|
||||
const workspaceMember = [
|
||||
{
|
||||
id: 'workspace-member-id',
|
||||
@ -197,17 +191,16 @@ describe('UserWorkspaceService', () => {
|
||||
const objectMetadata = {
|
||||
nameSingular: 'workspaceMember',
|
||||
} as ObjectMetadataEntity;
|
||||
const workspaceMemberRepository = {
|
||||
insert: jest.fn(),
|
||||
find: jest.fn().mockResolvedValue(workspaceMember),
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(
|
||||
dataSourceService,
|
||||
'getLastDataSourceMetadataFromWorkspaceIdOrFail',
|
||||
)
|
||||
.mockResolvedValue(dataSourceMetadata);
|
||||
.spyOn(typeORMService, 'getMainDataSource')
|
||||
.mockReturnValue(mainDataSource);
|
||||
jest
|
||||
.spyOn(typeORMService, 'connectToDataSource')
|
||||
.mockResolvedValue(workspaceDataSource as any);
|
||||
workspaceDataSource.query
|
||||
.spyOn(mainDataSource, 'query')
|
||||
.mockResolvedValueOnce(undefined)
|
||||
.mockResolvedValueOnce(workspaceMember);
|
||||
jest
|
||||
@ -217,15 +210,23 @@ describe('UserWorkspaceService', () => {
|
||||
.spyOn(workspaceEventEmitter, 'emitDatabaseBatchEvent')
|
||||
.mockImplementation();
|
||||
|
||||
jest
|
||||
.spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace')
|
||||
.mockResolvedValue(workspaceMemberRepository as any);
|
||||
|
||||
await service.createWorkspaceMember(workspaceId, user);
|
||||
|
||||
expect(
|
||||
dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail,
|
||||
).toHaveBeenCalledWith(workspaceId);
|
||||
expect(typeORMService.connectToDataSource).toHaveBeenCalledWith(
|
||||
dataSourceMetadata,
|
||||
);
|
||||
expect(workspaceDataSource.query).toHaveBeenCalledTimes(2);
|
||||
expect(workspaceMemberRepository.insert).toHaveBeenCalledWith({
|
||||
name: {
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
},
|
||||
colorScheme: 'System',
|
||||
userId: user.id,
|
||||
userEmail: user.email,
|
||||
locale: 'en',
|
||||
avatarUrl: 'avatar-url',
|
||||
});
|
||||
expect(objectMetadataRepository.findOneOrFail).toHaveBeenCalledWith({
|
||||
where: {
|
||||
nameSingular: 'workspaceMember',
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@ -69,33 +69,35 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
}
|
||||
|
||||
async createWorkspaceMember(workspaceId: string, user: User) {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
const workspaceMemberRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
await workspaceMemberRepository.insert({
|
||||
name: {
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
},
|
||||
colorScheme: 'System',
|
||||
userId: user.id,
|
||||
userEmail: user.email,
|
||||
avatarUrl: user.defaultAvatarUrl ?? '',
|
||||
locale: (user.locale ?? SOURCE_LOCALE) as keyof typeof APP_LOCALES,
|
||||
});
|
||||
|
||||
await workspaceDataSource?.query(
|
||||
`INSERT INTO ${dataSourceMetadata.schema}."workspaceMember"
|
||||
("nameFirstName", "nameLastName", "colorScheme", "userId", "userEmail", "avatarUrl", "locale")
|
||||
VALUES ($1, $2, 'System', $3, $4, $5, $6)`,
|
||||
[
|
||||
user.firstName,
|
||||
user.lastName,
|
||||
user.id,
|
||||
user.email,
|
||||
user.defaultAvatarUrl ?? '',
|
||||
user.locale ?? SOURCE_LOCALE,
|
||||
],
|
||||
);
|
||||
const workspaceMember = await workspaceDataSource?.query(
|
||||
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId"='${user.id}'`,
|
||||
);
|
||||
const workspaceMember = await workspaceMemberRepository.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
assert(
|
||||
workspaceMember.length === 1,
|
||||
workspaceMember?.length === 1,
|
||||
`Error while creating workspace member ${user.email} on workspace ${workspaceId}`,
|
||||
);
|
||||
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
|
||||
|
||||
@ -6,13 +6,11 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||
@ -38,13 +36,11 @@ export class UserService extends TypeOrmQueryService<User> {
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly userRoleService: UserRoleService,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {
|
||||
super(userRepository);
|
||||
}
|
||||
@ -88,17 +84,16 @@ export class UserService extends TypeOrmQueryService<User> {
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
const workspaceMemberRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
const workspaceMembers = await workspaceDataSource?.query(
|
||||
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember"`,
|
||||
);
|
||||
const workspaceMembers = await workspaceMemberRepository.find();
|
||||
|
||||
if (workspaceMembers.length > 1) {
|
||||
const userWorkspace =
|
||||
@ -119,9 +114,7 @@ export class UserService extends TypeOrmQueryService<User> {
|
||||
|
||||
assert(workspaceMember, 'WorkspaceMember not found');
|
||||
|
||||
await workspaceDataSource?.query(
|
||||
`DELETE FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId" = '${userId}'`,
|
||||
);
|
||||
await workspaceMemberRepository.delete({ userId });
|
||||
|
||||
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
|
||||
Reference in New Issue
Block a user