Skip to content

Commit

Permalink
✨ Feat: collection crud
Browse files Browse the repository at this point in the history
#
  • Loading branch information
ks1ksi committed Sep 5, 2023
1 parent c294c00 commit 5c6aa6e
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PrismaModule } from './prisma/prisma.module';
import { PrismaService } from './prisma/prisma.service';
import { UserModule } from './user/user.module';
import { TagModule } from './tag/tag.module';
import { CollectionModule } from './collection/collection.module';

@Module({
imports: [
Expand All @@ -17,6 +18,7 @@ import { TagModule } from './tag/tag.module';
DocumentModule,
UserModule,
TagModule,
CollectionModule,
],
controllers: [AppController],
providers: [AppService, PrismaService],
Expand Down
66 changes: 66 additions & 0 deletions src/collection/collection.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
Body,
Controller,
DefaultValuePipe,
Get,
Param,
ParseIntPipe,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { CollectionService } from './collection.service';
import { AuthGuard } from '@nestjs/passport';
import { ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
import { GetUid } from '../decorators/get-uid.decorator';
import { CollectionDto } from './dto/response/collection.dto';
import { CreateCollectionDto } from './dto/request/create-collection.dto';

@Controller('collection')
@UseGuards(AuthGuard('jwt'))
@ApiBearerAuth()
export class CollectionController {
constructor(private readonly collectionService: CollectionService) {}

@Get()
@ApiQuery({
name: 'limit',
required: false,
type: Number,
description: '한 번에 받을 태그의 개수. 최대 20개까지 가능. 기본값은 20.',
})
@ApiQuery({
name: 'offset',
required: false,
type: Number,
description: '몇 번째 태그부터 검색 결과에 포함할지. 기본값은 0.',
})
getCollections(
@GetUid() uid: string,
@Query('limit', new DefaultValuePipe(20), ParseIntPipe) limit: number,
@Query('offset', new DefaultValuePipe(0), ParseIntPipe) offset: number,
): Promise<CollectionDto[]> {
return this.collectionService.findAll(uid, limit, offset);
}

@Post()
createCollection(
@GetUid() uid: string,
@Body() createCollectionDto: CreateCollectionDto,
): Promise<CollectionDto> {
return this.collectionService.create(uid, createCollectionDto);
}

@Get('find/:name')
findByName(
@GetUid() uid: string,
@Param('name') name: string,
): Promise<CollectionDto> {
return this.collectionService.findOne(uid, name);
}

@Post('delete/:name')
delete(@GetUid() uid: string, @Param('name') name: string): Promise<void> {
return this.collectionService.deleteOne(uid, name);
}
}
11 changes: 11 additions & 0 deletions src/collection/collection.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { CollectionService } from './collection.service';
import { CollectionController } from './collection.controller';
import { UserModule } from '../user/user.module';

@Module({
imports: [UserModule],
controllers: [CollectionController],
providers: [CollectionService],
})
export class CollectionModule {}
122 changes: 122 additions & 0 deletions src/collection/collection.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
BadRequestException,
Injectable,
Logger,
UnauthorizedException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CollectionDto } from './dto/response/collection.dto';
import { UserService } from '../user/user.service';
import { CreateCollectionDto } from './dto/request/create-collection.dto';

@Injectable()
export class CollectionService {
private readonly logger: Logger = new Logger(CollectionService.name);

constructor(
private readonly prisma: PrismaService,
private readonly userService: UserService,
) {}

async findAll(
uid: string,
limit: number,
offset: number,
): Promise<CollectionDto[]> {
limit = limit > 20 ? 20 : limit;
const collections = await this.prisma.collection.findMany({
include: {
_count: { select: { documents: true } },
},
where: { user: { uid } },
orderBy: [{ documents: { _count: 'desc' } }, { name: 'asc' }],
take: limit,
skip: offset,
});

this.logger.log(`getTags: ${JSON.stringify(collections)}`);

return collections.map((collection) => ({
name: collection.name,
description: collection.description,
count: collection._count.documents,
}));
}

async create(
uid: string,
createCollectionDto: CreateCollectionDto,
): Promise<CollectionDto> {
const user = await this.userService.findByUid(uid);

const documents = await this.prisma.document.findMany({
select: { id: true },
where: {
userId: user.id,
docId: { in: createCollectionDto.docIds ?? [] },
},
});

// TODO: CreateCollectionDto에 docIds가 너무 많을 경우 처리

try {
const collection = await this.prisma.collection.create({
data: {
name: createCollectionDto.name,
description: createCollectionDto.description,
user: { connect: { id: user.id } },
documents: {
connect: documents.map((doc) => ({ id: doc.id })),
},
},
});

return {
name: collection.name,
description: collection.description,
count: documents.length,
};
} catch (error) {
if (error.code === 'P2002') {
// "Unique constraint failed on the {constraint}" 에러
throw new BadRequestException('Collection already exists');
}
}
}

async deleteOne(uid: string, name: string): Promise<void> {
const user = await this.userService.findByUid(uid);
const collection = await this.prisma.collection.findUnique({
where: { userId_name: { userId: user.id, name } },
include: { user: true },
});

if (!collection) {
throw new BadRequestException('Collection does not exist');
}

if (collection.user.uid !== uid) {
throw new UnauthorizedException('Unauthorized');
}

await this.prisma.collection.delete({ where: { id: collection.id } });
}

async findOne(uid: string, name: string) {
const user = await this.userService.findByUid(uid);
const collection = await this.prisma.collection.findUnique({
where: { userId_name: { userId: user.id, name } },
include: { _count: { select: { documents: true } } },
});

if (!collection) {
throw new BadRequestException('Collection does not exist');
}

return {
name: collection.name,
description: collection.description,
count: collection._count.documents,
};
}
}
19 changes: 19 additions & 0 deletions src/collection/dto/request/create-collection.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class CreateCollectionDto {
@IsString()
@IsNotEmpty()
name: string;

@IsString()
description: string;

@IsOptional()
@IsString({ each: true })
@ApiProperty({
description: '컬렉션에 속한 문서의 uuid',
required: false,
})
docIds?: string[];
}
21 changes: 21 additions & 0 deletions src/collection/dto/response/collection.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';

export class CollectionDto {
@ApiProperty({
description: '컬렉션 이름',
example: '백엔드',
})
name: string;

@ApiProperty({
description: '컬렉션 설명',
example: '백엔드 개발과 관련된 블로그 글 모음',
})
description: string;

@ApiProperty({
description: '컬렉션에 속한 문서의 개수',
example: 1,
})
count: number;
}

0 comments on commit 5c6aa6e

Please sign in to comment.