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

@ -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);
}
}