feat: spaces - WIP

This commit is contained in:
Philipinho
2024-02-28 02:39:46 +00:00
parent 1d620eba49
commit 40251aef7d
32 changed files with 512 additions and 119 deletions

View File

@ -1,30 +0,0 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { TokenService } from '../services/token.service';
@Injectable()
export class JwtGuard implements CanActivate {
constructor(private tokenService: TokenService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token: string = await this.tokenService.extractTokenFromHeader(
request,
);
if (!token) {
throw new UnauthorizedException('Invalid jwt token');
}
try {
request['user'] = await this.tokenService.verifyJwt(token);
} catch (error) {
throw new UnauthorizedException('Could not verify jwt token');
}
return true;
}
}

View File

@ -8,6 +8,7 @@ import { AttachmentModule } from './attachment/attachment.module';
import { EnvironmentModule } from '../environment/environment.module';
import { CommentModule } from './comment/comment.module';
import { SearchModule } from './search/search.module';
import { SpaceModule } from './space/space.module';
@Module({
imports: [
@ -21,6 +22,7 @@ import { SearchModule } from './search/search.module';
AttachmentModule,
CommentModule,
SearchModule,
SpaceModule,
],
})
export class CoreModule {}

View File

@ -16,4 +16,7 @@ export class CreatePageDto {
@IsOptional()
@IsString()
parentPageId?: string;
@IsString()
spaceId: string;
}

View File

@ -10,6 +10,7 @@ import {
import { Workspace } from '../../workspace/entities/workspace.entity';
import { Page } from './page.entity';
import { User } from '../../user/entities/user.entity';
import { Space } from '../../space/entities/space.entity';
@Entity('page_history')
export class PageHistory {
@ -48,6 +49,13 @@ export class PageHistory {
@JoinColumn({ name: 'lastUpdatedById' })
lastUpdatedBy: User;
@Column()
spaceId: string;
@ManyToOne(() => Space, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'spaceId' })
space: Space;
@Column()
workspaceId: string;

View File

@ -10,6 +10,7 @@ import {
DeleteDateColumn,
} from 'typeorm';
import { Workspace } from '../../workspace/entities/workspace.entity';
import { Space } from '../../space/entities/space.entity';
@Entity('page_ordering')
@Unique(['entityId', 'entityType'])
@ -23,9 +24,12 @@ export class PageOrdering {
@Column({ type: 'varchar', length: 50, nullable: false })
entityType: string;
@Column('uuid', { array: true })
@Column('uuid', { array: true, default: '{}' })
childrenIds: string[];
@Column('uuid')
workspaceId: string;
@ManyToOne(() => Workspace, (workspace) => workspace.id, {
onDelete: 'CASCADE',
})
@ -33,7 +37,13 @@ export class PageOrdering {
workspace: Workspace;
@Column('uuid')
workspaceId: string;
spaceId: string;
@ManyToOne(() => Space, (space) => space.id, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'spaceId' })
space: Space;
@DeleteDateColumn({ nullable: true })
deletedAt: Date;

View File

@ -14,6 +14,7 @@ import { User } from '../../user/entities/user.entity';
import { Workspace } from '../../workspace/entities/workspace.entity';
import { Comment } from '../../comment/entities/comment.entity';
import { PageHistory } from './page-history.entity';
import { Space } from '../../space/entities/space.entity';
@Entity('pages')
@Index('pages_tsv_idx', ['tsv'])
@ -82,6 +83,13 @@ export class Page {
@JoinColumn({ name: 'deletedById' })
deletedBy: User;
@Column()
spaceId: string;
@ManyToOne(() => Space, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'spaceId' })
space: Space;
@Column()
workspaceId: string;

View File

@ -9,7 +9,7 @@ import {
import { PageService } from './services/page.service';
import { CreatePageDto } from './dto/create-page.dto';
import { UpdatePageDto } from './dto/update-page.dto';
import { JwtGuard } from '../auth/guards/JwtGuard';
import { JwtGuard } from '../auth/guards/jwt.guard';
import { WorkspaceService } from '../workspace/services/workspace.service';
import { MovePageDto } from './dto/move-page.dto';
import { PageDetailsDto } from './dto/page-details.dto';
@ -71,39 +71,27 @@ export class PageController {
@HttpCode(HttpStatus.OK)
@Post('recent')
async getRecentWorkspacePages(@JwtUser() jwtUser) {
const workspaceId = (
await this.workspaceService.getUserCurrentWorkspace(jwtUser.id)
).id;
return this.pageService.getRecentWorkspacePages(workspaceId);
async getRecentSpacePages(@Body() { spaceId }) {
console.log(spaceId);
return this.pageService.getRecentSpacePages(spaceId);
}
@HttpCode(HttpStatus.OK)
@Post()
async getWorkspacePages(@JwtUser() jwtUser) {
const workspaceId = (
await this.workspaceService.getUserCurrentWorkspace(jwtUser.id)
).id;
return this.pageService.getSidebarPagesByWorkspaceId(workspaceId);
async getSpacePages(spaceId: string) {
return this.pageService.getSidebarPagesBySpaceId(spaceId);
}
@HttpCode(HttpStatus.OK)
@Post('ordering')
async getWorkspacePageOrder(@JwtUser() jwtUser) {
const workspaceId = (
await this.workspaceService.getUserCurrentWorkspace(jwtUser.id)
).id;
return this.pageOrderService.getWorkspacePageOrder(workspaceId);
async getSpacePageOrder(spaceId: string) {
return this.pageOrderService.getSpacePageOrder(spaceId);
}
@HttpCode(HttpStatus.OK)
@Post('tree')
async workspacePageTree(@JwtUser() jwtUser) {
const workspaceId = (
await this.workspaceService.getUserCurrentWorkspace(jwtUser.id)
).id;
return this.pageOrderService.convertToTree(workspaceId);
async spacePageTree(@Body() { spaceId }) {
return this.pageOrderService.convertToTree(spaceId);
}
@HttpCode(HttpStatus.OK)

View File

@ -2,7 +2,8 @@ import { MovePageDto } from './dto/move-page.dto';
import { EntityManager } from 'typeorm';
export enum OrderingEntity {
workspace = 'WORKSPACE',
workspace = 'SPACE',
space = 'SPACE',
page = 'PAGE',
}

View File

@ -18,6 +18,7 @@ export class PageRepository extends Repository<Page> {
'page.parentPageId',
'page.creatorId',
'page.lastUpdatedById',
'page.spaceId',
'page.workspaceId',
'page.isLocked',
'page.status',

View File

@ -34,7 +34,7 @@ export class PageOrderingService {
const movedPage = await manager
.createQueryBuilder(Page, 'page')
.where('page.id = :movedPageId', { movedPageId })
.select(['page.id', 'page.workspaceId', 'page.parentPageId'])
.select(['page.id', 'page.spaceId', 'page.parentPageId'])
.getOne();
if (!movedPage) throw new BadRequestException('Moved page not found');
@ -43,15 +43,15 @@ export class PageOrderingService {
if (movedPage.parentPageId) {
await this.removeFromParent(movedPage.parentPageId, dto.id, manager);
}
const workspaceOrdering = await this.getEntityOrdering(
movedPage.workspaceId,
OrderingEntity.workspace,
const spaceOrdering = await this.getEntityOrdering(
movedPage.spaceId,
OrderingEntity.space,
manager,
);
orderPageList(workspaceOrdering.childrenIds, dto);
orderPageList(spaceOrdering.childrenIds, dto);
await manager.save(workspaceOrdering);
await manager.save(spaceOrdering);
} else {
const parentPageId = dto.parentId;
@ -65,7 +65,7 @@ export class PageOrderingService {
parentPageOrdering = await this.createPageOrdering(
parentPageId,
OrderingEntity.page,
movedPage.workspaceId,
movedPage.spaceId,
manager,
);
}
@ -78,8 +78,8 @@ export class PageOrderingService {
// If movedPage didn't have a parent initially (was at root level), update the root level
if (!movedPage.parentPageId) {
await this.removeFromWorkspacePageOrder(
movedPage.workspaceId,
await this.removeFromSpacePageOrder(
movedPage.spaceId,
dto.id,
manager,
);
@ -95,36 +95,32 @@ export class PageOrderingService {
});
}
async addPageToOrder(
workspaceId: string,
pageId: string,
parentPageId?: string,
) {
async addPageToOrder(spaceId: string, pageId: string, parentPageId?: string) {
await this.dataSource.transaction(async (manager: EntityManager) => {
if (parentPageId) {
await this.upsertOrdering(
parentPageId,
OrderingEntity.page,
pageId,
workspaceId,
spaceId,
manager,
);
} else {
await this.addToWorkspacePageOrder(workspaceId, pageId, manager);
await this.addToSpacePageOrder(spaceId, pageId, manager);
}
});
}
async addToWorkspacePageOrder(
workspaceId: string,
async addToSpacePageOrder(
spaceId: string,
pageId: string,
manager: EntityManager,
) {
await this.upsertOrdering(
workspaceId,
OrderingEntity.workspace,
spaceId,
OrderingEntity.space,
pageId,
workspaceId,
spaceId,
manager,
);
}
@ -142,14 +138,14 @@ export class PageOrderingService {
);
}
async removeFromWorkspacePageOrder(
workspaceId: string,
async removeFromSpacePageOrder(
spaceId: string,
pageId: string,
manager: EntityManager,
) {
await this.removeChildFromOrdering(
workspaceId,
OrderingEntity.workspace,
spaceId,
OrderingEntity.space,
pageId,
manager,
);
@ -179,11 +175,7 @@ export class PageOrderingService {
if (page.parentPageId) {
await this.removeFromParent(page.parentPageId, page.id, manager);
} else {
await this.removeFromWorkspacePageOrder(
page.workspaceId,
page.id,
manager,
);
await this.removeFromSpacePageOrder(page.spaceId, page.id, manager);
}
}
@ -191,7 +183,7 @@ export class PageOrderingService {
entityId: string,
entityType: string,
childId: string,
workspaceId: string,
spaceId: string,
manager: EntityManager,
) {
let ordering = await this.getEntityOrdering(entityId, entityType, manager);
@ -200,7 +192,7 @@ export class PageOrderingService {
ordering = await this.createPageOrdering(
entityId,
entityType,
workspaceId,
spaceId,
manager,
);
}
@ -229,35 +221,35 @@ export class PageOrderingService {
async createPageOrdering(
entityId: string,
entityType: string,
workspaceId: string,
spaceId: string,
manager: EntityManager,
): Promise<PageOrdering> {
await manager.query(
`INSERT INTO page_ordering ("entityId", "entityType", "workspaceId", "childrenIds")
`INSERT INTO page_ordering ("entityId", "entityType", "spaceId", "childrenIds")
VALUES ($1, $2, $3, '{}')
ON CONFLICT ("entityId", "entityType") DO NOTHING`,
[entityId, entityType, workspaceId],
[entityId, entityType, spaceId],
);
return await this.getEntityOrdering(entityId, entityType, manager);
}
async getWorkspacePageOrder(workspaceId: string): Promise<PageOrdering> {
async getSpacePageOrder(spaceId: string): Promise<PageOrdering> {
return await this.dataSource
.createQueryBuilder(PageOrdering, 'ordering')
.select(['ordering.id', 'ordering.childrenIds', 'ordering.workspaceId'])
.where('ordering.entityId = :workspaceId', { workspaceId })
.select(['ordering.id', 'ordering.childrenIds', 'ordering.spaceId'])
.where('ordering.entityId = :spaceId', { spaceId })
.andWhere('ordering.entityType = :entityType', {
entityType: OrderingEntity.workspace,
entityType: OrderingEntity.space,
})
.getOne();
}
async convertToTree(workspaceId: string): Promise<TreeNode[]> {
const workspaceOrder = await this.getWorkspacePageOrder(workspaceId);
async convertToTree(spaceId: string): Promise<TreeNode[]> {
const spaceOrder = await this.getSpacePageOrder(spaceId);
const pageOrder = workspaceOrder ? workspaceOrder.childrenIds : undefined;
const pages = await this.pageService.getSidebarPagesByWorkspaceId(workspaceId);
const pageOrder = spaceOrder ? spaceOrder.childrenIds : undefined;
const pages = await this.pageService.getSidebarPagesBySpaceId(spaceId);
const pageMap: { [id: string]: PageWithOrderingDto } = {};
pages.forEach((page) => {

View File

@ -67,7 +67,7 @@ export class PageService {
page.lastUpdatedById = userId;
if (createPageDto.parentPageId) {
// TODO: make sure parent page belongs to same workspace and user has permissions
// TODO: make sure parent page belongs to same space and user has permissions
const parentPage = await this.pageRepository.findOne({
where: { id: createPageDto.parentPageId },
select: ['id'],
@ -79,7 +79,7 @@ export class PageService {
const createdPage = await this.pageRepository.save(page);
await this.pageOrderingService.addPageToOrder(
workspaceId,
createPageDto.spaceId,
createPageDto.id,
createPageDto.parentPageId,
);
@ -174,12 +174,7 @@ export class PageService {
const restoredPage = await manager
.createQueryBuilder(Page, 'page')
.where('page.id = :pageId', { pageId })
.select([
'page.id',
'page.title',
'page.workspaceId',
'page.parentPageId',
])
.select(['page.id', 'page.title', 'page.spaceId', 'page.parentPageId'])
.getOne();
if (!restoredPage) {
@ -188,7 +183,7 @@ export class PageService {
// add page back to its hierarchy
await this.pageOrderingService.addPageToOrder(
restoredPage.workspaceId,
restoredPage.spaceId,
pageId,
restoredPage.parentPageId,
);
@ -222,8 +217,8 @@ export class PageService {
return await this.pageRepository.findById(pageId);
}
async getSidebarPagesByWorkspaceId(
workspaceId: string,
async getSidebarPagesBySpaceId(
spaceId: string,
limit = 200,
): Promise<PageWithOrderingDto[]> {
const pages = await this.pageRepository
@ -234,12 +229,13 @@ export class PageService {
'ordering.entityId = page.id AND ordering.entityType = :entityType',
{ entityType: OrderingEntity.page },
)
.where('page.workspaceId = :workspaceId', { workspaceId })
.where('page.spaceId = :spaceId', { spaceId })
.select([
'page.id',
'page.title',
'page.icon',
'page.parentPageId',
'page.spaceId',
'ordering.childrenIds',
'page.creatorId',
'page.createdAt',
@ -251,14 +247,14 @@ export class PageService {
return transformPageResult(pages);
}
async getRecentWorkspacePages(
workspaceId: string,
async getRecentSpacePages(
spaceId: string,
limit = 20,
offset = 0,
): Promise<Page[]> {
const pages = await this.pageRepository
.createQueryBuilder('page')
.where('page.workspaceId = :workspaceId', { workspaceId })
.where('page.spaceId = :spaceId', { spaceId })
.select(this.pageRepository.baseFields)
.orderBy('page.updatedAt', 'DESC')
.offset(offset)

View File

@ -0,0 +1,12 @@
import { IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
export class CreateSpaceDto {
@MinLength(4)
@MaxLength(64)
@IsString()
name: string;
@IsOptional()
@IsString()
description?: string;
}

View File

@ -0,0 +1,6 @@
import { IsString } from 'class-validator';
export class DeleteSpaceDto {
@IsString()
spaceId: string;
}

View File

@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateSpaceDto } from './create-space.dto';
export class UpdateSpaceDto extends PartialType(CreateSpaceDto) {}

View File

@ -0,0 +1,46 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
JoinColumn,
Unique,
} from 'typeorm';
import { User } from '../../user/entities/user.entity';
import { Space } from './space.entity';
@Entity('space_users')
@Unique(['spaceId', 'userId'])
export class SpaceUser {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
userId: string;
@ManyToOne(() => User, (user) => user.spaceUsers, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'userId' })
user: User;
@Column()
spaceId: string;
@ManyToOne(() => Space, (space) => space.spaceUsers, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'spaceId' })
space: Space;
@Column({ length: 100, nullable: true })
role: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,54 @@
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { User } from '../../user/entities/user.entity';
import { Workspace } from '../../workspace/entities/workspace.entity';
import { SpaceUser } from './space-user.entity';
@Entity('spaces')
export class Space {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 255, nullable: true })
name: string;
@Column({ type: 'text', nullable: true })
description: string;
@Column({ length: 255, nullable: true })
icon: string;
@Column({ length: 255, nullable: true, unique: true })
hostname: string;
@Column()
creatorId: string;
@ManyToOne(() => User, (user) => user.spaces)
@JoinColumn({ name: 'creatorId' })
creator: User;
@Column()
workspaceId: string;
@ManyToOne(() => Workspace, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'workspaceId' })
workspace: Workspace;
@OneToMany(() => SpaceUser, (workspaceUser) => workspaceUser.space)
spaceUsers: SpaceUser[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,10 @@
import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { SpaceUser } from '../entities/space-user.entity';
@Injectable()
export class SpaceUserRepository extends Repository<SpaceUser> {
constructor(private dataSource: DataSource) {
super(SpaceUser, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1,14 @@
import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { Space } from '../entities/space.entity';
@Injectable()
export class SpaceRepository extends Repository<Space> {
constructor(private dataSource: DataSource) {
super(Space, dataSource.createEntityManager());
}
async findById(spaceId: string) {
return this.findOneBy({ id: spaceId });
}
}

View File

@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SpaceController } from './space.controller';
import { SpaceService } from './space.service';
describe('SpaceController', () => {
let controller: SpaceController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [SpaceController],
providers: [SpaceService],
}).compile();
controller = module.get<SpaceController>(SpaceController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,22 @@
import {
Controller,
HttpCode,
HttpStatus,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { FastifyRequest } from 'fastify';
import { JwtGuard } from '../auth/guards/jwt.guard';
import { SpaceService } from './space.service';
@UseGuards(JwtGuard)
@Controller('spaces')
export class SpaceController {
constructor(private readonly spaceService: SpaceService) {}
// get all spaces user is a member of
@HttpCode(HttpStatus.OK)
@Post('/')
async getUserSpaces(@Req() req: FastifyRequest) {}
}

View File

@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { SpaceService } from './space.service';
import { SpaceController } from './space.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Space } from './entities/space.entity';
import { AuthModule } from '../auth/auth.module';
import { SpaceUser } from './entities/space-user.entity';
import { SpaceRepository } from './repositories/space.repository';
import { SpaceUserRepository } from './repositories/space-user.repository';
@Module({
imports: [TypeOrmModule.forFeature([Space, SpaceUser]), AuthModule],
controllers: [SpaceController],
providers: [SpaceService, SpaceRepository, SpaceUserRepository],
exports: [SpaceService, SpaceRepository, SpaceUserRepository],
})
export class SpaceModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SpaceService } from './space.service';
describe('SpaceService', () => {
let service: SpaceService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [SpaceService],
}).compile();
service = module.get<SpaceService>(SpaceService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,69 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { CreateSpaceDto } from './dto/create-space.dto';
import { Space } from './entities/space.entity';
import { plainToInstance } from 'class-transformer';
import { SpaceRepository } from './repositories/space.repository';
import { SpaceUserRepository } from './repositories/space-user.repository';
import { SpaceUser } from './entities/space-user.entity';
@Injectable()
export class SpaceService {
constructor(
private spaceRepository: SpaceRepository,
private spaceUserRepository: SpaceUserRepository,
) {}
async create(userId: string, workspaceId, createSpaceDto?: CreateSpaceDto) {
let space: Space;
if (createSpaceDto) {
space = plainToInstance(Space, createSpaceDto);
} else {
space = new Space();
}
space.creatorId = userId;
space.workspaceId = workspaceId;
space.name = createSpaceDto?.name ?? 'untitled space';
space.description = createSpaceDto?.description ?? null;
space = await this.spaceRepository.save(space);
return space;
}
async addUserToSpace(
userId: string,
spaceId: string,
role: string,
): Promise<SpaceUser> {
const existingSpaceUser = await this.spaceUserRepository.findOne({
where: { userId: userId, spaceId: spaceId },
});
if (existingSpaceUser) {
throw new BadRequestException('User already added to this space');
}
const spaceUser = new SpaceUser();
spaceUser.userId = userId;
spaceUser.spaceId = spaceId;
spaceUser.role = role;
return this.spaceUserRepository.save(spaceUser);
}
async getUserSpacesInWorkspace(userId: string, workspaceId: string) {
const spaces = await this.spaceUserRepository.find({
relations: ['space'],
where: {
userId: userId,
space: {
workspaceId: workspaceId,
},
},
});
return spaces.map((userSpace: SpaceUser) => userSpace.space);
}
}

View File

@ -12,6 +12,8 @@ import { Workspace } from '../../workspace/entities/workspace.entity';
import { WorkspaceUser } from '../../workspace/entities/workspace-user.entity';
import { Page } from '../../page/entities/page.entity';
import { Comment } from '../../comment/entities/comment.entity';
import { Space } from '../../space/entities/space.entity';
import { SpaceUser } from '../../space/entities/space-user.entity';
@Entity('users')
export class User {
@ -66,6 +68,12 @@ export class User {
@OneToMany(() => Comment, (comment) => comment.creator)
comments: Comment[];
@OneToMany(() => Space, (space) => space.creator)
spaces: Space[];
@OneToMany(() => SpaceUser, (spaceUser) => spaceUser.user)
spaceUsers: SpaceUser[];
toJSON() {
delete this.password;
return this;

View File

@ -10,7 +10,7 @@ import {
Body,
} from '@nestjs/common';
import { UserService } from './user.service';
import { JwtGuard } from '../auth/guards/JwtGuard';
import { JwtGuard } from '../auth/guards/jwt.guard';
import { FastifyRequest } from 'fastify';
import { User } from './entities/user.entity';
import { Workspace } from '../workspace/entities/workspace.entity';

View File

@ -1,4 +1,4 @@
import { forwardRef, Module } from '@nestjs/common';
import { forwardRef, Global, Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
@ -7,6 +7,7 @@ import { UserRepository } from './repositories/user.repository';
import { AuthModule } from '../auth/auth.module';
import { WorkspaceModule } from '../workspace/workspace.module';
@Global()
@Module({
imports: [
TypeOrmModule.forFeature([User]),

View File

@ -31,16 +31,24 @@ export class UserService {
user = await this.userRepository.save(user);
//TODO: only create workspace if it is not a signup to an existing workspace
await this.workspaceService.create(user.id);
await this.workspaceService.createOrJoinWorkspace(user.id);
return user;
}
async getUserInstance(userId: string) {
const user: User = await this.findById(userId);
if (!user) {
throw new NotFoundException('User not found');
}
const workspace: Workspace =
await this.workspaceService.getUserCurrentWorkspace(userId);
if (!workspace) {
throw new NotFoundException('Workspace not found');
}
return { user, workspace };
}

View File

@ -11,7 +11,7 @@ import {
} from '@nestjs/common';
import { WorkspaceService } from '../services/workspace.service';
import { FastifyRequest } from 'fastify';
import { JwtGuard } from '../../auth/guards/JwtGuard';
import { JwtGuard } from '../../auth/guards/jwt.guard';
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
import { CreateWorkspaceDto } from '../dto/create-workspace.dto';
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
@ -24,6 +24,17 @@ import { AddWorkspaceUserDto } from '../dto/add-workspace-user.dto';
export class WorkspaceController {
constructor(private readonly workspaceService: WorkspaceService) {}
@HttpCode(HttpStatus.OK)
@Post('test')
async test(
@Req() req: FastifyRequest,
//@Body() createWorkspaceDto: CreateWorkspaceDto,
) {
//const jwtPayload = req['user'];
// const userId = jwtPayload.sub;
// return this.workspaceService.createOrJoinWorkspace();
}
@HttpCode(HttpStatus.OK)
@Post('create')
async createWorkspace(

View File

@ -14,12 +14,14 @@ import { generateHostname } from '../workspace.util';
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto';
import { SpaceService } from '../../space/space.service';
@Injectable()
export class WorkspaceService {
constructor(
private workspaceRepository: WorkspaceRepository,
private workspaceUserRepository: WorkspaceUserRepository,
private spaceService: SpaceService,
) {}
async findById(workspaceId: string): Promise<Workspace> {
@ -30,6 +32,45 @@ export class WorkspaceService {
return this.workspaceRepository.save(workspace);
}
async createOrJoinWorkspace(userId) {
// context:
// only create workspace if it is not a signup to an existing workspace
// OS version is limited to one workspace.
// if there is no existing workspace, create a new workspace
// and make first user owner/admin
// check if workspace already exists in the db
// if not create default, if yes add the user to existing workspace if signup is open.
const workspaceCount = await this.workspaceRepository.count();
if (workspaceCount === 0) {
// create first workspace
// add user to workspace as admin
const newWorkspace = await this.create(userId);
await this.addUserToWorkspace(userId, newWorkspace.id, 'owner');
// maybe create default space and add user to it too.
const newSpace = await this.spaceService.create(userId, newWorkspace.id);
await this.spaceService.addUserToSpace(userId, newSpace.id, 'owner');
} else {
//TODO: accept role as param
// if no role is passed use default new member role found in workspace settings
// fetch the oldest workspace and add user to it
const firstWorkspace = await this.workspaceRepository.find({
order: {
createdAt: 'ASC',
},
take: 1,
});
await this.addUserToWorkspace(userId, firstWorkspace[0].id, 'member');
// get workspace
// if there is a default space, we should add new users to it.
}
}
async create(
userId: string,
createWorkspaceDto?: CreateWorkspaceDto,
@ -50,7 +91,6 @@ export class WorkspaceService {
}
workspace = await this.workspaceRepository.save(workspace);
await this.addUserToWorkspace(userId, workspace.id, 'owner');
return workspace;
}
@ -151,6 +191,10 @@ export class WorkspaceService {
relations: ['workspace'],
});
if (!userWorkspace) {
throw new NotFoundException('No workspace found for this user');
}
return userWorkspace.workspace;
}

View File

@ -8,11 +8,13 @@ import { WorkspaceUser } from './entities/workspace-user.entity';
import { WorkspaceInvitation } from './entities/workspace-invitation.entity';
import { WorkspaceUserRepository } from './repositories/workspace-user.repository';
import { AuthModule } from '../auth/auth.module';
import { SpaceModule } from '../space/space.module';
@Module({
imports: [
TypeOrmModule.forFeature([Workspace, WorkspaceUser, WorkspaceInvitation]),
AuthModule,
SpaceModule,
],
controllers: [WorkspaceController],
providers: [WorkspaceService, WorkspaceRepository, WorkspaceUserRepository],