feat: multi-workspace followup (#4197)
* Seed UserWorkspace for existing demo/dev users * add workspaces field to currentUser * new token generation endpoint for switching workspace * lint fix * include dependency * requested fixes * resolver test pass * changing defaultWorkspace and workspaceMember when switching workspaces * tests fix * requested changes * delete user/workspace edge case handled * after merge * requested changes * :wq! * workspace manytoone relation * lint fix / import fix * gql codegen * Fix migrations and generateJWT * migration fix * relations fix --------- Co-authored-by: martmull <martmull@hotmail.fr>
This commit is contained in:
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -4,21 +4,21 @@
|
|||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true,
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.addMissingImports": "always"
|
"source.addMissingImports": "always"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true,
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.addMissingImports": "always"
|
"source.addMissingImports": "always"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true,
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.addMissingImports": "always"
|
"source.addMissingImports": "always"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -235,6 +235,7 @@ export type Mutation = {
|
|||||||
deleteUser: User;
|
deleteUser: User;
|
||||||
emailPasswordResetLink: EmailPasswordResetLink;
|
emailPasswordResetLink: EmailPasswordResetLink;
|
||||||
generateApiKeyToken: ApiKeyToken;
|
generateApiKeyToken: ApiKeyToken;
|
||||||
|
generateJWT: AuthTokens;
|
||||||
generateTransientToken: TransientToken;
|
generateTransientToken: TransientToken;
|
||||||
impersonate: Verify;
|
impersonate: Verify;
|
||||||
renewToken: AuthTokens;
|
renewToken: AuthTokens;
|
||||||
@ -289,6 +290,11 @@ export type MutationGenerateApiKeyTokenArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationGenerateJwtArgs = {
|
||||||
|
workspaceId: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationImpersonateArgs = {
|
export type MutationImpersonateArgs = {
|
||||||
userId: Scalars['String'];
|
userId: Scalars['String'];
|
||||||
};
|
};
|
||||||
@ -568,6 +574,7 @@ export type User = {
|
|||||||
createdAt: Scalars['DateTime'];
|
createdAt: Scalars['DateTime'];
|
||||||
defaultAvatarUrl?: Maybe<Scalars['String']>;
|
defaultAvatarUrl?: Maybe<Scalars['String']>;
|
||||||
defaultWorkspace: Workspace;
|
defaultWorkspace: Workspace;
|
||||||
|
defaultWorkspaceId: Scalars['String'];
|
||||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||||
disabled?: Maybe<Scalars['Boolean']>;
|
disabled?: Maybe<Scalars['Boolean']>;
|
||||||
email: Scalars['String'];
|
email: Scalars['String'];
|
||||||
@ -581,6 +588,7 @@ export type User = {
|
|||||||
supportUserHash?: Maybe<Scalars['String']>;
|
supportUserHash?: Maybe<Scalars['String']>;
|
||||||
updatedAt: Scalars['DateTime'];
|
updatedAt: Scalars['DateTime'];
|
||||||
workspaceMember?: Maybe<WorkspaceMember>;
|
workspaceMember?: Maybe<WorkspaceMember>;
|
||||||
|
workspaces: Array<UserWorkspace>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserEdge = {
|
export type UserEdge = {
|
||||||
@ -596,6 +604,18 @@ export type UserExists = {
|
|||||||
exists: Scalars['Boolean'];
|
exists: Scalars['Boolean'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UserWorkspace = {
|
||||||
|
__typename?: 'UserWorkspace';
|
||||||
|
createdAt: Scalars['DateTime'];
|
||||||
|
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||||
|
id: Scalars['ID'];
|
||||||
|
updatedAt: Scalars['DateTime'];
|
||||||
|
user: User;
|
||||||
|
userId: Scalars['String'];
|
||||||
|
workspace?: Maybe<Workspace>;
|
||||||
|
workspaceId: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
export type ValidatePasswordResetToken = {
|
export type ValidatePasswordResetToken = {
|
||||||
__typename?: 'ValidatePasswordResetToken';
|
__typename?: 'ValidatePasswordResetToken';
|
||||||
email: Scalars['String'];
|
email: Scalars['String'];
|
||||||
@ -916,7 +936,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
|||||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null } } };
|
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null } | null }> } };
|
||||||
|
|
||||||
export type ActivateWorkspaceMutationVariables = Exact<{
|
export type ActivateWorkspaceMutationVariables = Exact<{
|
||||||
input: ActivateWorkspaceInput;
|
input: ActivateWorkspaceInput;
|
||||||
@ -1854,6 +1874,14 @@ export const GetCurrentUserDocument = gql`
|
|||||||
workspaceId
|
workspaceId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
workspaces {
|
||||||
|
workspace {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
logo
|
||||||
|
domainName
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -36,6 +36,14 @@ export const GET_CURRENT_USER = gql`
|
|||||||
workspaceId
|
workspaceId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
workspaces {
|
||||||
|
workspace {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
logo
|
||||||
|
domainName
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||||
import { UserService } from 'src/core/user/services/user.service';
|
import { UserService } from 'src/core/user/services/user.service';
|
||||||
|
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
||||||
|
import { User } from 'src/core/user/user.entity';
|
||||||
|
|
||||||
import { AuthResolver } from './auth.resolver';
|
import { AuthResolver } from './auth.resolver';
|
||||||
|
|
||||||
@ -20,6 +22,10 @@ describe('AuthResolver', () => {
|
|||||||
provide: getRepositoryToken(Workspace, 'core'),
|
provide: getRepositoryToken(Workspace, 'core'),
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(User, 'core'),
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: AuthService,
|
provide: AuthService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
@ -32,6 +38,10 @@ describe('AuthResolver', () => {
|
|||||||
provide: UserService,
|
provide: UserService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: UserWorkspaceService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,8 @@ import { UpdatePasswordViaResetTokenInput } from 'src/core/auth/dto/update-passw
|
|||||||
import { EmailPasswordResetLink } from 'src/core/auth/dto/email-password-reset-link.entity';
|
import { EmailPasswordResetLink } from 'src/core/auth/dto/email-password-reset-link.entity';
|
||||||
import { InvalidatePassword } from 'src/core/auth/dto/invalidate-password.entity';
|
import { InvalidatePassword } from 'src/core/auth/dto/invalidate-password.entity';
|
||||||
import { EmailPasswordResetLinkInput } from 'src/core/auth/dto/email-password-reset-link.input';
|
import { EmailPasswordResetLinkInput } from 'src/core/auth/dto/email-password-reset-link.input';
|
||||||
|
import { GenerateJwtInput } from 'src/core/auth/dto/generate-jwt.input';
|
||||||
|
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
||||||
|
|
||||||
import { ApiKeyToken, AuthTokens } from './dto/token.entity';
|
import { ApiKeyToken, AuthTokens } from './dto/token.entity';
|
||||||
import { TokenService } from './services/token.service';
|
import { TokenService } from './services/token.service';
|
||||||
@ -49,6 +51,7 @@ export class AuthResolver {
|
|||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private tokenService: TokenService,
|
private tokenService: TokenService,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
|
private userWorkspaceService: UserWorkspaceService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Query(() => UserExists)
|
@Query(() => UserExists)
|
||||||
@ -128,6 +131,20 @@ export class AuthResolver {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => AuthTokens)
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
async generateJWT(
|
||||||
|
@AuthUser() user: User,
|
||||||
|
@Args() args: GenerateJwtInput,
|
||||||
|
): Promise<AuthTokens> {
|
||||||
|
const token = await this.tokenService.generateSwitchWorkspaceToken(
|
||||||
|
user,
|
||||||
|
args.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => AuthTokens)
|
@Mutation(() => AuthTokens)
|
||||||
async renewToken(@Args() args: RefreshTokenInput): Promise<AuthTokens> {
|
async renewToken(@Args() args: RefreshTokenInput): Promise<AuthTokens> {
|
||||||
if (!args.refreshToken) {
|
if (!args.refreshToken) {
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { ArgsType, Field } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
@ArgsType()
|
||||||
|
export class GenerateJwtInput {
|
||||||
|
@Field(() => String)
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
workspaceId: string;
|
||||||
|
}
|
||||||
@ -98,7 +98,7 @@ export class AuthService {
|
|||||||
where: {
|
where: {
|
||||||
email,
|
email,
|
||||||
},
|
},
|
||||||
relations: ['defaultWorkspace'],
|
relations: ['defaultWorkspace', 'workspaces', 'workspaces.workspace'],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(user, "This user doesn't exist", NotFoundException);
|
assert(user, "This user doesn't exist", NotFoundException);
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
|||||||
import { User } from 'src/core/user/user.entity';
|
import { User } from 'src/core/user/user.entity';
|
||||||
import { JwtAuthStrategy } from 'src/core/auth/strategies/jwt.auth.strategy';
|
import { JwtAuthStrategy } from 'src/core/auth/strategies/jwt.auth.strategy';
|
||||||
import { EmailService } from 'src/integrations/email/email.service';
|
import { EmailService } from 'src/integrations/email/email.service';
|
||||||
|
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
||||||
|
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||||
|
|
||||||
import { TokenService } from './token.service';
|
import { TokenService } from './token.service';
|
||||||
|
|
||||||
@ -33,6 +35,10 @@ describe('TokenService', () => {
|
|||||||
provide: EmailService,
|
provide: EmailService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: UserWorkspaceService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: getRepositoryToken(User, 'core'),
|
provide: getRepositoryToken(User, 'core'),
|
||||||
useValue: {},
|
useValue: {},
|
||||||
@ -41,6 +47,10 @@ describe('TokenService', () => {
|
|||||||
provide: getRepositoryToken(RefreshToken, 'core'),
|
provide: getRepositoryToken(RefreshToken, 'core'),
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Workspace, 'core'),
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import { assert } from 'src/utils/assert';
|
|||||||
import {
|
import {
|
||||||
ApiKeyToken,
|
ApiKeyToken,
|
||||||
AuthToken,
|
AuthToken,
|
||||||
|
AuthTokens,
|
||||||
PasswordResetToken,
|
PasswordResetToken,
|
||||||
} from 'src/core/auth/dto/token.entity';
|
} from 'src/core/auth/dto/token.entity';
|
||||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||||
@ -39,6 +40,8 @@ import { EmailService } from 'src/integrations/email/email.service';
|
|||||||
import { InvalidatePassword } from 'src/core/auth/dto/invalidate-password.entity';
|
import { InvalidatePassword } from 'src/core/auth/dto/invalidate-password.entity';
|
||||||
import { EmailPasswordResetLink } from 'src/core/auth/dto/email-password-reset-link.entity';
|
import { EmailPasswordResetLink } from 'src/core/auth/dto/email-password-reset-link.entity';
|
||||||
import { JwtData } from 'src/core/auth/types/jwt-data.type';
|
import { JwtData } from 'src/core/auth/types/jwt-data.type';
|
||||||
|
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
||||||
|
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TokenService {
|
export class TokenService {
|
||||||
@ -50,10 +53,16 @@ export class TokenService {
|
|||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
@InjectRepository(RefreshToken, 'core')
|
@InjectRepository(RefreshToken, 'core')
|
||||||
private readonly refreshTokenRepository: Repository<RefreshToken>,
|
private readonly refreshTokenRepository: Repository<RefreshToken>,
|
||||||
|
@InjectRepository(Workspace, 'core')
|
||||||
|
private readonly workspaceRepository: Repository<Workspace>,
|
||||||
private readonly emailService: EmailService,
|
private readonly emailService: EmailService,
|
||||||
|
private readonly userWorkspaceService: UserWorkspaceService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async generateAccessToken(userId: string): Promise<AuthToken> {
|
async generateAccessToken(
|
||||||
|
userId: string,
|
||||||
|
workspaceId?: string,
|
||||||
|
): Promise<AuthToken> {
|
||||||
const expiresIn = this.environmentService.getAccessTokenExpiresIn();
|
const expiresIn = this.environmentService.getAccessTokenExpiresIn();
|
||||||
|
|
||||||
assert(expiresIn, '', InternalServerErrorException);
|
assert(expiresIn, '', InternalServerErrorException);
|
||||||
@ -74,7 +83,7 @@ export class TokenService {
|
|||||||
|
|
||||||
const jwtPayload: JwtPayload = {
|
const jwtPayload: JwtPayload = {
|
||||||
sub: user.id,
|
sub: user.id,
|
||||||
workspaceId: user.defaultWorkspace.id,
|
workspaceId: workspaceId ? workspaceId : user.defaultWorkspace.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -232,6 +241,45 @@ export class TokenService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async generateSwitchWorkspaceToken(
|
||||||
|
user: User,
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<AuthTokens> {
|
||||||
|
const userExists = await this.userRepository.findBy({ id: user.id });
|
||||||
|
|
||||||
|
assert(userExists, 'User not found', NotFoundException);
|
||||||
|
|
||||||
|
const workspace = await this.workspaceRepository.findOne({
|
||||||
|
where: { id: workspaceId },
|
||||||
|
relations: ['workspaceUsers'],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(workspace, 'workspace doesnt exist', NotFoundException);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
workspace.workspaceUsers
|
||||||
|
.map((userWorkspace) => userWorkspace.userId)
|
||||||
|
.includes(user.id),
|
||||||
|
'user does not belong to workspace',
|
||||||
|
ForbiddenException,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.userRepository.save({
|
||||||
|
id: user.id,
|
||||||
|
defaultWorkspace: workspace,
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = await this.generateAccessToken(user.id, workspaceId);
|
||||||
|
const refreshToken = await this.generateRefreshToken(user.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
tokens: {
|
||||||
|
accessToken: token,
|
||||||
|
refreshToken,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async verifyRefreshToken(refreshToken: string) {
|
async verifyRefreshToken(refreshToken: string) {
|
||||||
const secret = this.environmentService.getRefreshTokenSecret();
|
const secret = this.environmentService.getRefreshTokenSecret();
|
||||||
const coolDown = this.environmentService.getRefreshTokenCoolDown();
|
const coolDown = this.environmentService.getRefreshTokenCoolDown();
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
Unique,
|
Unique,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
@ -22,15 +23,25 @@ export class UserWorkspace {
|
|||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
@Field(() => User)
|
||||||
|
@ManyToOne(() => User, (user) => user.workspaces, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
@JoinColumn({ name: 'userId' })
|
@JoinColumn({ name: 'userId' })
|
||||||
user: User;
|
user: User;
|
||||||
|
|
||||||
|
@Field({ nullable: false })
|
||||||
@Column()
|
@Column()
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
|
@Field(() => Workspace, { nullable: true })
|
||||||
|
@ManyToOne(() => Workspace, (workspace) => workspace.workspaceUsers, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
@JoinColumn({ name: 'workspaceId' })
|
@JoinColumn({ name: 'workspaceId' })
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
|
|
||||||
|
@Field({ nullable: false })
|
||||||
@Column()
|
@Column()
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
|
||||||
|
|||||||
@ -81,14 +81,6 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
|||||||
).length;
|
).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findUserWorkspaces(userId: string): Promise<UserWorkspace[]> {
|
|
||||||
return this.userWorkspaceRepository.find({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkUserWorkspaceExists(
|
async checkUserWorkspaceExists(
|
||||||
userId: string,
|
userId: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
|||||||
@ -83,12 +83,27 @@ export class UserService extends TypeOrmQueryService<User> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteUser(userId: string): Promise<User> {
|
async deleteUser(userId: string): Promise<User> {
|
||||||
const user = await this.userRepository.findOneBy({
|
const user = await this.userRepository.findOne({
|
||||||
|
where: {
|
||||||
id: userId,
|
id: userId,
|
||||||
|
},
|
||||||
|
relations: ['defaultWorkspace'],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(user, 'User not found');
|
assert(user, 'User not found');
|
||||||
|
|
||||||
|
const dataSourceMetadata =
|
||||||
|
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||||
|
user.defaultWorkspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const workspaceDataSource =
|
||||||
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
|
|
||||||
|
await workspaceDataSource?.query(
|
||||||
|
`DELETE FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId" = '${userId}'`,
|
||||||
|
);
|
||||||
|
|
||||||
await this.userRepository.delete(user.id);
|
await this.userRepository.delete(user.id);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { IDField } from '@ptc-org/nestjs-query-graphql';
|
|||||||
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||||
import { WorkspaceMember } from 'src/core/user/dtos/workspace-member.dto';
|
import { WorkspaceMember } from 'src/core/user/dtos/workspace-member.dto';
|
||||||
|
import { UserWorkspace } from 'src/core/user-workspace/user-workspace.entity';
|
||||||
|
|
||||||
@Entity({ name: 'user', schema: 'core' })
|
@Entity({ name: 'user', schema: 'core' })
|
||||||
@ObjectType('User')
|
@ObjectType('User')
|
||||||
@ -72,6 +73,10 @@ export class User {
|
|||||||
})
|
})
|
||||||
defaultWorkspace: Workspace;
|
defaultWorkspace: Workspace;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
@Column()
|
||||||
|
defaultWorkspaceId: string;
|
||||||
|
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
passwordResetToken: string;
|
passwordResetToken: string;
|
||||||
@ -87,4 +92,8 @@ export class User {
|
|||||||
|
|
||||||
@Field(() => WorkspaceMember, { nullable: true })
|
@Field(() => WorkspaceMember, { nullable: true })
|
||||||
workspaceMember: WorkspaceMember;
|
workspaceMember: WorkspaceMember;
|
||||||
|
|
||||||
|
@Field(() => [UserWorkspace])
|
||||||
|
@OneToMany(() => UserWorkspace, (userWorkspace) => userWorkspace.user)
|
||||||
|
workspaces: UserWorkspace[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import { UserResolver } from 'src/core/user/user.resolver';
|
|||||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||||
|
import { UserWorkspaceModule } from 'src/core/user-workspace/user-workspace.module';
|
||||||
|
import { UserWorkspace } from 'src/core/user-workspace/user-workspace.entity';
|
||||||
|
|
||||||
import { userAutoResolverOpts } from './user.auto-resolver-opts';
|
import { userAutoResolverOpts } from './user.auto-resolver-opts';
|
||||||
|
|
||||||
@ -19,13 +21,14 @@ import { UserService } from './services/user.service';
|
|||||||
imports: [
|
imports: [
|
||||||
NestjsQueryGraphQLModule.forFeature({
|
NestjsQueryGraphQLModule.forFeature({
|
||||||
imports: [
|
imports: [
|
||||||
NestjsQueryTypeOrmModule.forFeature([User], 'core'),
|
NestjsQueryTypeOrmModule.forFeature([User, UserWorkspace], 'core'),
|
||||||
TypeORMModule,
|
TypeORMModule,
|
||||||
],
|
],
|
||||||
resolvers: userAutoResolverOpts,
|
resolvers: userAutoResolverOpts,
|
||||||
}),
|
}),
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
FileModule,
|
FileModule,
|
||||||
|
UserWorkspaceModule,
|
||||||
],
|
],
|
||||||
exports: [UserService],
|
exports: [UserService],
|
||||||
providers: [UserService, UserResolver, TypeORMService],
|
providers: [UserService, UserResolver, TypeORMService],
|
||||||
|
|||||||
@ -23,8 +23,11 @@ import { assert } from 'src/utils/assert';
|
|||||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||||
import { User } from 'src/core/user/user.entity';
|
import { User } from 'src/core/user/user.entity';
|
||||||
import { WorkspaceMember } from 'src/core/user/dtos/workspace-member.dto';
|
import { WorkspaceMember } from 'src/core/user/dtos/workspace-member.dto';
|
||||||
|
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
||||||
|
|
||||||
import { UserService } from './services/user.service';
|
import { UserService } from './services/user.service';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
const getHMACKey = (email?: string, key?: string | null) => {
|
const getHMACKey = (email?: string, key?: string | null) => {
|
||||||
if (!email || !key) return null;
|
if (!email || !key) return null;
|
||||||
@ -38,15 +41,21 @@ const getHMACKey = (email?: string, key?: string | null) => {
|
|||||||
@Resolver(() => User)
|
@Resolver(() => User)
|
||||||
export class UserResolver {
|
export class UserResolver {
|
||||||
constructor(
|
constructor(
|
||||||
|
@InjectRepository(User, 'core')
|
||||||
|
private readonly userRepository: Repository<User>,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
|
private readonly userWorkspaceService: UserWorkspaceService,
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
private readonly fileUploadService: FileUploadService,
|
private readonly fileUploadService: FileUploadService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Query(() => User)
|
@Query(() => User)
|
||||||
async currentUser(@AuthUser() { id }: User) {
|
async currentUser(@AuthUser() { id }: User): Promise<User> {
|
||||||
const user = await this.userService.findById(id, {
|
const user = await this.userRepository.findOne({
|
||||||
relations: [{ name: 'defaultWorkspace', query: {} }],
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
relations: ['defaultWorkspace', 'workspaces', 'workspaces.workspace'],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(user, 'User not found');
|
assert(user, 'User not found');
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||||
import { WorkspaceManagerService } from 'src/workspace/workspace-manager/workspace-manager.service';
|
import { WorkspaceManagerService } from 'src/workspace/workspace-manager/workspace-manager.service';
|
||||||
|
import { UserWorkspace } from 'src/core/user-workspace/user-workspace.entity';
|
||||||
|
import { User } from 'src/core/user/user.entity';
|
||||||
import { BillingService } from 'src/core/billing/billing.service';
|
import { BillingService } from 'src/core/billing/billing.service';
|
||||||
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
||||||
|
|
||||||
@ -19,6 +21,14 @@ describe('WorkspaceService', () => {
|
|||||||
provide: getRepositoryToken(Workspace, 'core'),
|
provide: getRepositoryToken(Workspace, 'core'),
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(UserWorkspace, 'core'),
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(User, 'core'),
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: WorkspaceManagerService,
|
provide: WorkspaceManagerService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { WorkspaceManagerService } from 'src/workspace/workspace-manager/workspa
|
|||||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||||
import { User } from 'src/core/user/user.entity';
|
import { User } from 'src/core/user/user.entity';
|
||||||
import { ActivateWorkspaceInput } from 'src/core/workspace/dtos/activate-workspace-input';
|
import { ActivateWorkspaceInput } from 'src/core/workspace/dtos/activate-workspace-input';
|
||||||
|
import { UserWorkspace } from 'src/core/user-workspace/user-workspace.entity';
|
||||||
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
import { UserWorkspaceService } from 'src/core/user-workspace/user-workspace.service';
|
||||||
import { BillingService } from 'src/core/billing/billing.service';
|
import { BillingService } from 'src/core/billing/billing.service';
|
||||||
|
|
||||||
@ -17,6 +18,10 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
private readonly workspaceRepository: Repository<Workspace>,
|
private readonly workspaceRepository: Repository<Workspace>,
|
||||||
|
@InjectRepository(UserWorkspace, 'core')
|
||||||
|
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||||
|
@InjectRepository(User, 'core')
|
||||||
|
private readonly userRepository: Repository<User>,
|
||||||
private readonly workspaceManagerService: WorkspaceManagerService,
|
private readonly workspaceManagerService: WorkspaceManagerService,
|
||||||
private readonly userWorkspaceService: UserWorkspaceService,
|
private readonly userWorkspaceService: UserWorkspaceService,
|
||||||
private readonly billingService: BillingService,
|
private readonly billingService: BillingService,
|
||||||
@ -49,6 +54,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
|
|
||||||
assert(workspace, 'Workspace not found');
|
assert(workspace, 'Workspace not found');
|
||||||
|
|
||||||
|
await this.userWorkspaceRepository.delete({ workspaceId: id });
|
||||||
await this.billingService.deleteSubscription(workspace.id);
|
await this.billingService.deleteSubscription(workspace.id);
|
||||||
|
|
||||||
await this.workspaceManagerService.delete(id);
|
await this.workspaceManagerService.delete(id);
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import Stripe from 'stripe';
|
|||||||
import { User } from 'src/core/user/user.entity';
|
import { User } from 'src/core/user/user.entity';
|
||||||
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||||
import { BillingSubscription } from 'src/core/billing/entities/billing-subscription.entity';
|
import { BillingSubscription } from 'src/core/billing/entities/billing-subscription.entity';
|
||||||
|
import { UserWorkspace } from 'src/core/user-workspace/user-workspace.entity';
|
||||||
|
|
||||||
@Entity({ name: 'workspace', schema: 'core' })
|
@Entity({ name: 'workspace', schema: 'core' })
|
||||||
@ObjectType('Workspace')
|
@ObjectType('Workspace')
|
||||||
@ -55,6 +56,11 @@ export class Workspace {
|
|||||||
@OneToMany(() => User, (user) => user.defaultWorkspace)
|
@OneToMany(() => User, (user) => user.defaultWorkspace)
|
||||||
users: User[];
|
users: User[];
|
||||||
|
|
||||||
|
@OneToMany(() => UserWorkspace, (userWorkspace) => userWorkspace.workspace, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
workspaceUsers: UserWorkspace[];
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
@Column({ default: true })
|
@Column({ default: true })
|
||||||
allowImpersonation: boolean;
|
allowImpersonation: boolean;
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import { WorkspaceManagerModule } from 'src/workspace/workspace-manager/workspac
|
|||||||
import { WorkspaceResolver } from 'src/core/workspace/workspace.resolver';
|
import { WorkspaceResolver } from 'src/core/workspace/workspace.resolver';
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||||
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||||
|
import { UserWorkspace } from 'src/core/user-workspace/user-workspace.entity';
|
||||||
|
import { User } from 'src/core/user/user.entity';
|
||||||
import { UserWorkspaceModule } from 'src/core/user-workspace/user-workspace.module';
|
import { UserWorkspaceModule } from 'src/core/user-workspace/user-workspace.module';
|
||||||
import { BillingModule } from 'src/core/billing/billing.module';
|
import { BillingModule } from 'src/core/billing/billing.module';
|
||||||
|
|
||||||
@ -24,7 +26,7 @@ import { WorkspaceService } from './services/workspace.service';
|
|||||||
BillingModule,
|
BillingModule,
|
||||||
FileModule,
|
FileModule,
|
||||||
NestjsQueryTypeOrmModule.forFeature(
|
NestjsQueryTypeOrmModule.forFeature(
|
||||||
[Workspace, FeatureFlagEntity],
|
[User, Workspace, UserWorkspace, FeatureFlagEntity],
|
||||||
'core',
|
'core',
|
||||||
),
|
),
|
||||||
UserWorkspaceModule,
|
UserWorkspaceModule,
|
||||||
|
|||||||
@ -9,6 +9,10 @@ import {
|
|||||||
deleteWorkspaces,
|
deleteWorkspaces,
|
||||||
} from 'src/database/typeorm-seeds/core/demo/workspaces';
|
} from 'src/database/typeorm-seeds/core/demo/workspaces';
|
||||||
import { deleteFeatureFlags } from 'src/database/typeorm-seeds/core/demo/feature-flags';
|
import { deleteFeatureFlags } from 'src/database/typeorm-seeds/core/demo/feature-flags';
|
||||||
|
import {
|
||||||
|
deleteUserWorkspaces,
|
||||||
|
seedUserWorkspaces,
|
||||||
|
} from 'src/database/typeorm-seeds/core/demo/userWorkspaces';
|
||||||
|
|
||||||
export const seedCoreSchema = async (
|
export const seedCoreSchema = async (
|
||||||
workspaceDataSource: DataSource,
|
workspaceDataSource: DataSource,
|
||||||
@ -18,6 +22,7 @@ export const seedCoreSchema = async (
|
|||||||
|
|
||||||
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
||||||
await seedUsers(workspaceDataSource, schemaName, workspaceId);
|
await seedUsers(workspaceDataSource, schemaName, workspaceId);
|
||||||
|
await seedUserWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteCoreSchema = async (
|
export const deleteCoreSchema = async (
|
||||||
@ -26,6 +31,7 @@ export const deleteCoreSchema = async (
|
|||||||
) => {
|
) => {
|
||||||
const schemaName = 'core';
|
const schemaName = 'core';
|
||||||
|
|
||||||
|
await deleteUserWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
||||||
await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId);
|
await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId);
|
||||||
await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
||||||
// deleteWorkspaces should be last
|
// deleteWorkspaces should be last
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
|
||||||
|
const tableName = 'userWorkspace';
|
||||||
|
|
||||||
|
export enum DemoSeedUserIds {
|
||||||
|
Noah = '20202020-9e3b-46d4-a556-88b9ddc2b035',
|
||||||
|
Hugo = '20202020-3957-4908-9c36-2929a23f8358',
|
||||||
|
Julia = '20202020-7169-42cf-bc47-1cfef15264b9',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const seedUserWorkspaces = async (
|
||||||
|
workspaceDataSource: DataSource,
|
||||||
|
schemaName: string,
|
||||||
|
workspaceId: string,
|
||||||
|
) => {
|
||||||
|
await workspaceDataSource
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(`${schemaName}.${tableName}`, ['userId', 'workspaceId'])
|
||||||
|
.orIgnore()
|
||||||
|
.values([
|
||||||
|
{
|
||||||
|
userId: DemoSeedUserIds.Noah,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: DemoSeedUserIds.Hugo,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: DemoSeedUserIds.Julia,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.execute();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteUserWorkspaces = async (
|
||||||
|
workspaceDataSource: DataSource,
|
||||||
|
schemaName: string,
|
||||||
|
workspaceId: string,
|
||||||
|
) => {
|
||||||
|
await workspaceDataSource
|
||||||
|
.createQueryBuilder()
|
||||||
|
.delete()
|
||||||
|
.from(`${schemaName}.${tableName}`)
|
||||||
|
.where(`"${tableName}"."workspaceId" = :workspaceId`, {
|
||||||
|
workspaceId,
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
};
|
||||||
@ -12,6 +12,10 @@ import {
|
|||||||
seedFeatureFlags,
|
seedFeatureFlags,
|
||||||
deleteFeatureFlags,
|
deleteFeatureFlags,
|
||||||
} from 'src/database/typeorm-seeds/core/feature-flags';
|
} from 'src/database/typeorm-seeds/core/feature-flags';
|
||||||
|
import {
|
||||||
|
deleteUserWorkspaces,
|
||||||
|
seedUserWorkspaces,
|
||||||
|
} from 'src/database/typeorm-seeds/core/userWorkspaces';
|
||||||
|
|
||||||
export const seedCoreSchema = async (
|
export const seedCoreSchema = async (
|
||||||
workspaceDataSource: DataSource,
|
workspaceDataSource: DataSource,
|
||||||
@ -21,6 +25,7 @@ export const seedCoreSchema = async (
|
|||||||
|
|
||||||
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
||||||
await seedUsers(workspaceDataSource, schemaName, workspaceId);
|
await seedUsers(workspaceDataSource, schemaName, workspaceId);
|
||||||
|
await seedUserWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
||||||
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,6 +35,7 @@ export const deleteCoreSchema = async (
|
|||||||
) => {
|
) => {
|
||||||
const schemaName = 'core';
|
const schemaName = 'core';
|
||||||
|
|
||||||
|
await deleteUserWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
||||||
await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId);
|
await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId);
|
||||||
await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
||||||
// deleteWorkspaces should be last
|
// deleteWorkspaces should be last
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
|
||||||
|
// import { SeedWorkspaceId } from 'src/database/typeorm-seeds/core/workspaces';
|
||||||
|
|
||||||
|
const tableName = 'userWorkspace';
|
||||||
|
|
||||||
|
export enum SeedUserIds {
|
||||||
|
Tim = '20202020-9e3b-46d4-a556-88b9ddc2b034',
|
||||||
|
Jony = '20202020-3957-4908-9c36-2929a23f8357',
|
||||||
|
Phil = '20202020-7169-42cf-bc47-1cfef15264b8',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const seedUserWorkspaces = async (
|
||||||
|
workspaceDataSource: DataSource,
|
||||||
|
schemaName: string,
|
||||||
|
workspaceId: string,
|
||||||
|
) => {
|
||||||
|
await workspaceDataSource
|
||||||
|
.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(`${schemaName}.${tableName}`, ['userId', 'workspaceId'])
|
||||||
|
.orIgnore()
|
||||||
|
.values([
|
||||||
|
{
|
||||||
|
userId: SeedUserIds.Tim,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: SeedUserIds.Jony,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: SeedUserIds.Phil,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.execute();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteUserWorkspaces = async (
|
||||||
|
workspaceDataSource: DataSource,
|
||||||
|
schemaName: string,
|
||||||
|
workspaceId: string,
|
||||||
|
) => {
|
||||||
|
await workspaceDataSource
|
||||||
|
.createQueryBuilder()
|
||||||
|
.delete()
|
||||||
|
.from(`${schemaName}.${tableName}`)
|
||||||
|
.where(`"${tableName}"."workspaceId" = :workspaceId`, {
|
||||||
|
workspaceId,
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
};
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class UpdateUserWorkspace1709314035408 implements MigrationInterface {
|
||||||
|
name = 'UpdateUserWorkspace1709314035408';
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE "core"."userWorkspace"
|
||||||
|
ADD CONSTRAINT "FK_37fdc7357af701e595c5c3a9bd6"
|
||||||
|
FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id")
|
||||||
|
ON DELETE CASCADE ON UPDATE NO ACTION
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE "core"."userWorkspace"
|
||||||
|
ADD CONSTRAINT "FK_cb488f32c6a0827b938edadf221"
|
||||||
|
FOREIGN KEY ("userId") REFERENCES "core"."user"("id")
|
||||||
|
ON DELETE CASCADE ON UPDATE NO ACTION
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."userWorkspace" DROP CONSTRAINT "FK_cb488f32c6a0827b938edadf221"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."userWorkspace" DROP CONSTRAINT "FK_37fdc7357af701e595c5c3a9bd6"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
|||||||
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||||
import { BillingSubscription } from 'src/core/billing/entities/billing-subscription.entity';
|
import { BillingSubscription } from 'src/core/billing/entities/billing-subscription.entity';
|
||||||
import { BillingSubscriptionItem } from 'src/core/billing/entities/billing-subscription-item.entity';
|
import { BillingSubscriptionItem } from 'src/core/billing/entities/billing-subscription-item.entity';
|
||||||
|
import { UserWorkspace } from 'src/core/user-workspace/user-workspace.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||||
@ -26,6 +27,7 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
|||||||
entities: [
|
entities: [
|
||||||
User,
|
User,
|
||||||
Workspace,
|
Workspace,
|
||||||
|
UserWorkspace,
|
||||||
RefreshToken,
|
RefreshToken,
|
||||||
FeatureFlagEntity,
|
FeatureFlagEntity,
|
||||||
BillingSubscription,
|
BillingSubscription,
|
||||||
|
|||||||
Reference in New Issue
Block a user