내일배움캠프 Node트랙 심화 프로젝트 역할 및 진행사항
이번 프로젝트는 팀 프로젝트로 Node트랙 심화 프로젝트를 진행하게 되었습니다. 프로젝트를 시작하며 팀에서 맡은 역할과 현재 진행사항에 대해 소개하겠습니다.
프로젝트 발표일까지 D-3
1/5(금) | 1/6(토) | 1/7(일) | 1/8(월) | 1/9(화) | 1/10(수) | 1/11(목) |
시작 ▶ | 🏃 | 🏃 | 🏃 | 완료 🚩 |
내일배움캠프 Node트랙 심화 프로젝트
🎯 프로젝트 주제 및 목표
이번 프로젝트는 "프로젝트 협업 도구"를 만드는 것입니다. 프로젝트 협업 도구 중 Trello 라는 서비스를 고르게 되었습니다. Trello는 칸반 보드 기반 서비스로 유명한 프로젝트 협업 도구로, Trello에서 제공하는 다양한 기능을 구현하는 것이 이번 프로젝트의 목표 입니다.
✅ 프로젝트 필수 구현 기능
Trello를 참고하여 프로젝트에 필수로 구현될 기능은 다음과 같습니다.
- 사용자 관리 기능 (로그인 / 회원가입 / 사용자 정보 수정 및 삭제)
- 보드 관리 기능 (보드 생성 / 보드 수정 / 보드 삭제 / 보드 초대)
- 컬럼 관리 기능 (컬럼 생성 / 컬럼 이름 수정 / 컬럼 삭제 / 컬럼 순서 이동)
- 카드 관리 기능 (카드 생성 / 카드 수정 / 카드 삭제 / 카드 이동)
- 카드 상세 기능 (댓글 달기 / 날짜 지정)
위 기능 중 필자는 보드 관리 기능 (팀에서는 보드 대신 워크스페이스라고 명칭을 바꿔 사용)을 담당하였습니다.
팀프로젝트 소스코드 확인시 위 링크를 참고바랍니다
👩💻 기술스택
- 프로그래밍 언어: TypeScript, JavaScript (Node.js)
- 프레임워크: Nest.js
- 데이터베이스: TypeORM, AWS RDS
- 버전 관리 시스템: Git
- 개발 도구: Visual Studio Code
- 배포 환경: GitHub
- 테스트 도구: Thunder Client
🗺️ 와이어프레임
📜 API 명세서
API의 경우 필자가 담당한 API 명세서만 넣었습니다.
📌 ERD
✍ 프로젝트 진행사항
현재 필자가 맡은 워크스페이스 생성,조회,삭제 및 멤버 추가 API 1차 구현 완료되었습니다. 하지만 사용자 정보에 대한 인가 부분이 아직 구현되지 않아 팀원이 기능 완성하는 데로 추가할 예정입니다.
또한, 멤버 추가하기 전 멤버 추가하려는 테이블에 추가하려는 데이터가 있는지 검증 후 insert 하는 로직도 추가할 예정입니다.
구현한 API 소스
workspaces.controller.ts
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { WorkspacesService } from './workspaces.service';
import { CreateWorkspaceDto } from './dto/create-workspace.dto';
import { MembersDto } from './dto/invite-member.dto';
@Controller('workspaces')
export class WorkspacesController {
constructor(private readonly workspacesService: WorkspacesService) {}
@Post()
async createWorkspace(@Body() createworkspaceDto: CreateWorkspaceDto) {
return await this.workspacesService.createWorkspace(createworkspaceDto);
}
@Get()
async findAllWorkspace() {
return await this.workspacesService.findAllWorkspace();
}
@Put(':workspaceId')
async updateWorkspace(@Param('workspaceId') workspaceId: number,@Body() createworkspaceDto: CreateWorkspaceDto) {
return await this.workspacesService.updateWorkspace(workspaceId,createworkspaceDto);
}
@Delete(':workspaceId')
async deleteWorkspace(@Param('workspaceId') workspaceId: number) {
return await this.workspacesService.deleteWorkspace(workspaceId);
}
@Post('invite')
async inviteMember(@Body() membersDto: MembersDto) {
return await this.workspacesService.inviteMembers(membersDto);
}
}
workspaces.service.ts
import { HttpException, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { DataSource, Repository, getConnection } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { WorkspacesModel } from './entities/workspaces.entity';
import { CreateWorkspaceDto } from './dto/create-workspace.dto';
import { ListsModel } from 'src/lists/entities/lists.entity';
import { UsersModel } from 'src/users/entities/users.entity';
import { MembersDto } from './dto/invite-member.dto';
@Injectable()
export class WorkspacesService {
constructor(
@InjectRepository(WorkspacesModel)
private workspaceRepository: Repository<WorkspacesModel>,
@InjectRepository(UsersModel)
private userRepository: Repository<UsersModel>,
private readonly dataSource: DataSource,
) {}
async createWorkspace(createworkspaceDto:CreateWorkspaceDto) {
const workspace = this.workspaceRepository.create({
name:createworkspaceDto.name,
description:createworkspaceDto.description,
color:createworkspaceDto.color
});
// TODO: worksapce 추가시 ownerId 추가 어떻게? 인가된 값으로 추가
// TODO: 작성자 멤버(users_model_workspaces_workspaces_model)도 owner에 저장하는 로직 추가!!!!
// workspace 정보 저장
await this.workspaceRepository.save(workspace);
return {
"message":"워크스페이스를 생성했습니다.",
"data": workspace
}
}
async findAllWorkspace() {
const workspaces = await this.workspaceRepository.find({
select:['id','name']
});
return {
"message":"워크스페이스를 조회했습니다.",
"data":workspaces
}
}
async updateWorkspace(workspaceId: number, createworkspaceDto:CreateWorkspaceDto) {
const workspace = await this.verifyWorkSpaceById(workspaceId);
await this.workspaceRepository.update({ id:workspaceId },
{
name:createworkspaceDto.name,
description: createworkspaceDto.description,
color:createworkspaceDto.color
});
return {
"message": "워크스페이스 정보를 수정했습니다."
}
}
async deleteWorkspace(workspaceId: number) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try{
await this.verifyWorkSpaceById(workspaceId);
await queryRunner.manager.delete(ListsModel,{workspaceId:workspaceId});
await queryRunner.manager.delete(WorkspacesModel,{id:workspaceId});
await queryRunner.commitTransaction();
return {
"message":"해당 워크스페이스 삭제되었습니다."
}
}catch(error){
await queryRunner.rollbackTransaction();
console.log(`error : ${error}`)
if (error instanceof HttpException) {
// HttpException을 상속한 경우(statusCode 속성이 있는 경우)
throw error;
} else {
// 그 외의 예외
throw new InternalServerErrorException('서버 에러가 발생했습니다.');
}
}finally{
await queryRunner.release();
}
}
async inviteMembers(membersDto:MembersDto) {
const members = membersDto.members;
const values = members.map(({ userId, workspaceId }) => ({
usersModelId: userId,
workspacesModelId: workspaceId,
}));
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try{
// TODO: 멤버 추가시 동일 워크스페이스에 기존 등록한 멤버 있는지 확인
await queryRunner.manager
.createQueryBuilder()
.insert()
.into('users_model_workspaces_workspaces_model')
.values(values)
.execute();
await queryRunner.commitTransaction();
return {
"message":"워크스페이스 멤버 추가했습니다."
}
}catch(error){
await queryRunner.rollbackTransaction();
if (error instanceof HttpException) {
// HttpException을 상속한 경우(statusCode 속성이 있는 경우)
throw error;
} else {
// 그 외의 예외
throw new InternalServerErrorException('서버 에러가 발생했습니다.');
}
}finally{
await queryRunner.release();
}
}
private async verifyWorkSpaceById(id: number) {
const workspace = await this.workspaceRepository.findOneBy({ id });
if (!workspace) {
throw new NotFoundException('존재하지 않는 워크스페이스입니다.');
}
return workspace;
}
private async findByEmail(email: string) {
const user = await this.userRepository.findOneBy({ email });
if (!user) {
throw new NotFoundException('존재하지 않는 사용자입니다.');
}
return user;
}
}
create-workspace.dto.ts
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateWorkspaceDto {
@IsString()
@IsNotEmpty({ message: '워크스페이스명을 입력해주세요.' })
name: string;
@IsString()
@IsNotEmpty({ message: '워크스페이스 내용를 입력해주세요.' })
description: string;
@IsString()
@IsNotEmpty({ message: '색상을 입력해주세요.' })
color: string;
}
invite-member.dto.ts
import { ArrayMinSize, ArrayNotEmpty, IsArray, IsInt, IsNotEmpty, IsObject, IsString } from 'class-validator';
export class MembersDto {
@IsArray()
@ArrayNotEmpty()
@ArrayMinSize(1)
members: MemberDto[];
}
export class MemberDto {
@IsObject()
@IsInt()
userId: number;
@IsObject()
@IsInt()
workspaceId: number;
}
workspaces.entity.ts
import { IsString } from 'class-validator';
import { BaseModel } from 'src/common/entities/base.entity';
import { ListsModel } from 'src/lists/entities/lists.entity';
import { UsersModel } from 'src/users/entities/users.entity';
import { Column, Entity, ManyToMany, ManyToOne, OneToMany } from 'typeorm';
@Entity()
export class WorkspacesModel extends BaseModel {
@Column()
@IsString()
name: string;
@Column()
@IsString()
description: string;
@Column()
@IsString()
color: string;
/**
* admin
*/
@ManyToOne(() => UsersModel, (user) => user.myWorkspaces)
owner: UsersModel;
/**
* 멤버
*/
@ManyToMany(() => UsersModel, (user) => user.workspaces)
users: UsersModel;
/**
* 리스트
*/
@OneToMany(() => ListsModel, (list) => list.workspace,{
onDelete:'CASCADE'
})
lists: ListsModel[];
}
앞으로의 계획
인가 기능 추가하여 테스트 한 후 프론트엔드 작업 및 다른 팀원의 미비된 기능을 도와줄 예정입니다. 기간 내에 팀에서 목표로한 모든 기능을 원활히 동작할 수 있도록 최선을 다할 예정입니다.
▼ 이전 진행한 프로젝트 ▼
'내일배움캠프' 카테고리의 다른 글
내일배움캠프 Node트랙 심화 프로젝트 진행사항 3 (0) | 2024.01.10 |
---|---|
내일배움캠프 Node트랙 심화 프로젝트 역할 및 진행사항 2 (0) | 2024.01.09 |
내일배움캠프 NestJS 프로젝트 코드리뷰 - 온라인 공연 예매 서비스 (1) | 2024.01.07 |
내일배움캠프 백오피스 프로젝트 - 펫시터 매칭 사이트 후기, 소감 (1) | 2023.12.18 |
내일배움캠프 개인 과제 피드백 - validate 구문은 리소스가 적게드는 로직부터 검사하기 (0) | 2023.11.20 |