diff --git a/README.md b/README.md index 12c3c87..8493498 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,31 @@

별 하나에 글 하나 🌟

- "내 삶의 반짝이는 기억들을 우주에 담아보세요" +

"내 삶의 반짝이는 기억들을 우주에 담아보세요"

-

3D 기반 웹 추억 저장 서비스

+3D 기반 웹 추억 저장 서비스 + +
+ +남겨두고 싶은 순간을 찍은 사진과, 그 순간을 떠올리며 적은 글을 별에 담습니다. + +기억을 담은 별들이 모여 나만의 은하가 만들어집니다. + +추억으로 가득 채워진 나만 우주를 소중한 사람들에게 공유해보세요 ❤️
+[wiki 바로가기](https://github.com/boostcampwm2023/web16-B1G1/wiki) +
# 목차 -### [프로젝트 소개](#%EF%B8%8F-프로젝트-소개) +### [1. 프로젝트 소개](#%EF%B8%8F-프로젝트-소개) - [<별 하나에 글 하나>를 만들게 된 계기](#별-하나에-글-하나를-만들게-된-계기) - [기능 설명](#기능-설명) @@ -25,8 +35,24 @@ ### [2. 기술 스택](#%EF%B8%8F-기술-스택) ### [3. 기술적 경험](#-기술적-경험) +- [FE](#FE) + - [R3F Camera](#r3f-camera) + - [성능 최적화](#성능-최적화) + - [FSD 아키텍처](#fsd-아키텍처) +- [BE](#BE) + - [TDD, e2e 및 유닛 테스트](#tdd-e2e-및-유닛-테스트) + - [인증/인가](#인증인가) + - [트랜잭션 제어, 쿼리 최적화](#트랜잭션-제어-쿼리-최적화) + - [NestJS Enhancers](#NestJS-Enhancers) + - [배포 및 자동화](#배포-및-자동화) + - [admin 페이지 구현](#admin-페이지-구현) ### [4. 팀원 소개](#%EF%B8%8F-팀원-소개) +- [J010 김가은](#-j010-김가은-fe) +- [J016 김동민](#-j016-김동민-fe) +- [J053 박재하](#-j053-박재하-be) +- [J073 송준섭](#-j073-송준섭-be) +- [J098 이백범](#-j098-이백범-fe) ### [5. 팀원 회고](#-팀원-회고) @@ -50,20 +76,13 @@ ## 기능 설명 -남겨두고 싶은 순간을 찍은 사진과, 그 순간을 떠올리며 적은 글을 별에 담습니다. - -기억을 담은 별들이 모여 나만의 은하가 만들어집니다. - -추억으로 가득 채워진 나의 우주를 소중한 사람들에게 공유해보세요 ❤️ - -
- ### [ 랜딩페이지 ] - 왼쪽 위의 버튼을 이용해 배경음악을 끄고 켤 수 있습니다. - 마우스의 움직임에 따라 배경의 은하가 움직입니다. +- f9를 눌러 전체화면으로 변경할 수 있습니다. ### [ 회원가입 ] @@ -82,6 +101,8 @@ ### [ 코치마크 ] + + - 사용자에게 기본적인 서비스 사용법을 알려주는 기능입니다. - 첫 로그인 시에는 기본으로 뜨며, 이후에는 하단바의 물음표 버튼을 눌러 다시 볼 수 있습니다. @@ -103,12 +124,13 @@ - 글은 마크다운 형식으로 작성할 수 있으며, Preview 버튼을 누르면 마크다운이 적용된 글을 미리 볼 수 있습니다. - 사진은 5장까지 첨부할 수 있습니다. - + - 글 작성하고 다음 버튼을 누르면 별을 커스텀할 수 있습니다. - 별의 양 옆에 있는 화살표 버튼을 통해 별의 모양을 변경할 수 있습니다. - 색상, 크기, 밝기를 조절할 수 있습니다. - 색상 추천 버튼을 누르면, CLOVA Sentiment api를 통해 글의 감정을 분석해 색상을 추천해줍니다. +- 글이 생성될 때와 삭제될 때 별에서 애니메이션이 발생합니다. ### [ 은하 수정 ] @@ -124,7 +146,7 @@ - 별의 밝기를 조절할 수 있습니다. - 블러효과를 주어 우주에 흐림 효과를 줄 수 있습니다. -- 마우스 휠 속도를 조저할 수 있습니다. +- 마우스 휠 속도를 조절할 수 있습니다. - 우주의 소유주와 관계없이 내가 보는 화면에만 적용되는 속성입니다. ### [ 은하 공유 ] @@ -132,7 +154,7 @@ - 체크박스를 통해 검색 허용 여부를 설정할 수 있습니다. -- 로그인하지 않은 사용자도 공유 링크를 통해서 은하에 접근할 수 있습니다. +- 로그인하지 않은 사용자도 공유 링크를 통해 은하에 접근할 수 있습니다. ### [ 은하 검색 ] @@ -174,6 +196,7 @@ yarn workspace server start:dev - [Yarn berry로 모노레포 구성하기](https://velog.io/@minboykim/Yarn-berry%EB%A1%9C-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0) - [Vite, 왜 쓰는거지?](https://velog.io/@minboykim/Vite-%EC%99%9C-%EC%93%B0%EB%8A%94%EA%B1%B0%EC%A7%80) - [기술스택 선정이유 (NestJS, TypeORM, Docker, GitHub Actions)](https://velog.io/@qkrwogk/%EA%B8%B0%EC%88%A0%EC%8A%A4%ED%83%9D-%EC%84%A0%EC%A0%95%EC%9D%B4%EC%9C%A0-NestJS-TypeORM-Docker-GitHub-Actions) +- [MySQL 선택 이유](https://velog.io/@songjseop/why-mysql)
@@ -181,35 +204,50 @@ yarn workspace server start:dev ## FE -- 작성 중 +프론트엔드의 주요 기술적 도전은 **Three.js + React-Three-Fiber(R3F)를 사용한 우주 공간 구현**이었습니다. +팀원 모두에게 생소한 기술이었기에 사용한 것 자체도 도전적인 경험이었지만, 그 중에서 특히 **사용자 경험 개선** 위주의 경험을 작성해보았습니다. -**Three.js + React-Three-Fiber를 사용한 우주 공간 구현** +먼저 아래는 Three.js와 R3F에 관련하여 팀원들이 작성한 기술블로그입니다. -### R3F Camera +- [Three.js와의 설레는 첫만남](https://velog.io/@greencloud/Three.js%EC%99%80%EC%9D%98-%EC%84%A4%EB%A0%88%EB%8A%94-%EC%B2%AB%EB%A7%8C%EB%82%A8-) +- [JS로 자전과 공전을 구현할 수 있다고?](https://velog.io/@greencloud/JS%EB%A1%9C-%EC%9E%90%EC%A0%84%EA%B3%BC-%EA%B3%B5%EC%A0%84%EC%9D%84-%EA%B5%AC%ED%98%84%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8B%A4%EA%B3%A0) +- [R3F Material 간단 정리](https://electric-period-6ff.notion.site/Material-2f0279fc0d104b4e852250d190908b8b) +- [너와의 추억을 우주의 별로 띄울게](https://velog.io/@greencloud/%EB%84%88%EC%99%80%EC%9D%98-%EC%B6%94%EC%96%B5%EC%9D%84-%EC%9A%B0%EC%A3%BC%EC%9D%98-%EB%B3%84%EB%A1%9C-%EB%9D%84%EC%9A%B8%EA%B2%8C) +- [React로 멋진 3D 은하 만들기(Feat.R3F)](https://velog.io/@minboykim/React%EB%A1%9C-%EB%A9%8B%EC%A7%84-3D-%EC%9D%80%ED%95%98-%EB%A7%8C%EB%93%A4%EA%B8%B0feat.-R3F) -3D 공간 상에서 카메라는 사용자의 시야와 같다보니, 자연스러운 카메라 움직임이 중요했습니다. +
-3D 공간 상에서 사용자가 움직여야 하다보니 자연스러운 카메라 움직임이 매우 중요했습니다. +### R3F Camera + +3D 공간 상에서 카메라는 사용자의 시야와 같습니다. +그래서 카메라 움직임은 사용자 경험에 직결됩니다. +저희는 `자연스러운 카메라 움직임`을 만들어내 사용자 경험을 향상시키기 위해 여러 과정을 거쳤습니다. -별 클릭 시 카메라의 움직임을 최초에는 `회전 운동`을 적용하여 구현했다가, `직선 운동`으로 변경, 최종적으로는 회전운동과 직선운동의 장점을 합친 `포물선 운동`을 적용해주었습니다. +저희 서비스에서 별을 클릭하면 해당 별을 바라보도록 해야 합니다. +처음에는 카메라의 위치는 그대로 둔 채 시야만 회전하도록 하는 `회전 운동`의 방식을 사용했습니다. +그 다음에는 해당 별로 가까워지는 `직선 운동`으로 변경했습니다. +결과적으로는 회전운동과 직선운동의 장점을 합친 `포물선 운동` 방식을 적용해 훨씬 자연스러운 구현해냈습니다. - 직선 운동하는 카메라 - + + - 포물선 운동하는 카메라 - -또한 카메라의 자연스러운 움직임을 위해 멀리 이동할 때는 좀더 빠르게, 가까이 이동할 때는 좀 더 느리게 이동하는 움직임도 구현해주었습니다. + -마지막으로 멀리 있는 물체의 경우 너무 작게 보이는 문제점이 존재하여 최소 크기를 설정, 멀리 있는 별의 크기가 너무 작아보이지 않게 해주는 효과도 적용해주었습니다. +하지만 아직도 문제가 남아있었습니다. +거리가 먼 별에 가까워지기까지 너무 많은 시간이 걸렸고, 멀리 있는 별은 너무 작게 보였습니다. +어찌보면 당연한 이야기일 수 있지만, 서비스 특성상 사용자 입장에서는 불편할 수 있는 요소였습니다. -이렇듯 저희 팀은 항상 **사용자 경험**을 향상시키기 위해 많은 방법들을 적용해보았으며,이를 위해 다양한 학습과 고민을 이어나갔습니다. +그래서 멀리 이동할 때는 좀 더 빠르게, 가까이 이동할 때는 좀 더 느리게 이동하는 움직임을 구현했습니다. +또 거리에 비해서 물체가 커 보이게 처리함으로써 멀리 있는 별도 너무 작아보이지 않게 했습니다.
### 성능 최적화 -한 화면에 매우 많은 오브젝트를 보여줘야 하는 프로젝트 특성상 실행 환경에 많은 영향을 받아 최적화가 필수적이었습니다. +한 화면에 매우 많은 오브젝트를 보여줘야 하는 프로젝트 특성상 실행 환경에 많은 영향을 받기 때문에 최적화가 필수적이었습니다. 이에 여러 최적화 방법 중 우리 프로젝트에 적용하기 가장 적합한 `Instancing`과 `Performace Moritoring`을 사용해 최적화를 진행했습니다. @@ -217,32 +255,117 @@ yarn workspace server start:dev CPU가 GPU에게 무엇을 어떻게 그릴지 지시하는 `Draw Call`은 단순해 보이지만 상당히 무거운 작업입니다. 일반적인 컴퓨터 환경에서 Draw Call이 대략 1000회 넘어가 프레임 드랍이 생긴다는 점을 고려하면, 배경별만 4000개인 우리 프로젝트는 이 `Draw Call`을 줄여줘야 했습니다. - 이를 위해 사용한 방식이 `Instancing`으로 동일한 오브젝트를 여러번 그리는 경우 이를 한번에 처리하도록 하는 방식입니다. 이 방식을 통해 배경별을 종류별로 묶어줌으로써 `Draw Call`에 의한 CPU 병목 현상을 해결했습니다. + 이를 위해 사용한 방식이 `Instancing`으로, 동일한 오브젝트를 여러 번 그리는 경우 이를 한번에 처리하도록 하는 방식입니다. 이 방식을 통해 배경별을 종류별로 묶어줌으로써 4000개의 오브젝트를 13개의 오브젝트로 줄였습니다. 이렇게 `Draw Call`에 의한 CPU 병목 현상을 해결했습니다. + + 하지만 금요일 프로젝트 현황 공유 시간 때 '처음으로 맥북 팬 소리를 들었어요', '컴터가 안좋아서 그런지 느려요ㅠㅜㅠ' 같은 피드백을 들으면서 추가적인 최적화 작업에 대한 필요성을 느꼈습니다. 2. Performance Monitoring - 위의 과정 이후 GPU 또한 최적화가 필요함을 발견했습니다. 다양한 최적화 방식이 있었으나 우리가 사용하는 대부분의 오브젝트가 매우 매우 단순한 형태라 그리 효과적이지 않았습니다. 이에 선택한 방법이 `Performance Monitoring`으로 실시간으로 웹의 퍼포먼스를 확인해 이를 반영하는 방식입니다. + 피드백을 받은 이후 선택한 것은 Performance Monitoring을 사용한 방식입니다. 다양한 최적화 방식이 있었으나 프로젝트에서 사용하는 대부분의 오브젝트가 매우 단순한 형태라 그리 효과적이지 않았습니다. 이에 선택한 방법이 `Performance Monitoring`으로, 실시간으로 웹의 퍼포먼스를 확인해 이를 반영하는 방식입니다. - 이를 통해 퍼포먼스가 좋지 않은 경우 `Device Pixel Ratio`을 최대 0.5까지 낮추도록 설정해주었습니다. + 퍼포먼스가 좋지 않은 경우 (프레임 드랍 시) `Device Pixel Ratio`을 최대 0.5까지 낮추어 은하의 해상도가 낮아집니다. 이렇게 CPU만 고려하던 1번 방식에서 나아가 GPU의 부담까지 덜어주는 방식을 추가함으로써 더 최적화된 서비스를 만들 수 있었습니다. -
+ 아래 사진 중 왼쪽은 최고 해상도인 경우이고, 오른쪽은 최저 해상도인 경우입니다. -### FSD 아키텍처 적용 + + -프로젝트가 진행됨에 따라, 파일들이 많아져 각각의 파일들이 명확하게 분리되어야 할 필요성이 느껴졌습니다. 이에 따라 새로운 폴더 구조를 적용해보기로 결심했고, 그중에 눈에 띄었던 [FSD](https://feature-sliced.design/)방식을 적용해보기로 했습니다. + 아래 사진은 메모리 사용량을 비교한 것으로, Performance Monitoring 최적화 전 13.46GB였던 메모리 사용량이 최적화 후 12.50GB까지 감소했습니다. -FSD 방식 아키텍쳐는 프로젝트 폴더를 6개의 `layer`로 구분하고, 각각의 레이어들을 다시 `slice`와 `segment`들로 분할하여 관리하는 디자인 입니다. 이를 적용해보는 것으로 각 파일들의 명확한 분리와 한눈에 들어오는 폴더 구조를 가질 수 있게 되었습니다. + +
-### 라우팅 +### FSD 아키텍처 + +프로젝트를 진행함에 따라 파일 분리와 폴더 구조에 대한 명확한 원칙이 필요해졌습니다. +그래서 팀원들이 함께 폴더 구조에 대해 조사해보았고, 결과적으로 [FSD(Feature-Sliced Design)](https://feature-sliced.design/) 아키텍처를 적용하게 되었습니다. + +규모가 작은 저희 프로젝트와 달리, FSD 방식은 폴더를 세세하게 나누는 만큼 규모가 큰 프로젝트에 적합하다는 생각도 했습니다. +하지만 해당 방식의 장점이 매력적으로 다가오기도 했고, 이 프로젝트는 학습의 목적이 크기 때문에 팀원들 모두 새로운 폴더구조를 적용해보고 싶어했습니다. + +_출처: https://feature-sliced.design/_ + +FSD 아키텍처는 app, pages, widgets, features, entities, shared라는 6개의 `Layer`로 이루어져있습니다. 그리고 각각의 `Layer`는 `Slice`들로 이루어져있고, 그 `Slice`는 `Segment`로 이루어져있습니다. +저희 팀은 이렇게 각자의 역할이 분명한 폴더구조를 적용해봄으로써 모듈을 만들 때 각 모듈의 역할을 명확히 정의하게 되었습니다. +또 독립적으로 기능하는 `Slice` 단위로 개발함으로써 유지보수성을 높였습니다. + +아래는 저희의 폴더구조입니다. + +``` +📦src + ┣ 📂app + ┃ ┣ 📜App.tsx + ┃ ┣ 📜Router.tsx + ┃ ┗ 📜global.css + ┣ 📂assets + ┃ ┣ 📂fonts + ┃ ┣ 📂icons + ┃ ┣ 📂logos + ┃ ┗ 📂musics + ┣ 📂entities + ┃ ┣ 📂like + ┃ ┣ 📂posts + ┃ ┗ 📜index.ts + ┣ 📂features + ┃ ┣ 📂audio + ┃ ┣ 📂backgroundStars + ┃ ┣ 📂coachMarker + ┃ ┣ 📂controls + ┃ ┣ 📂star + ┃ ┗ 📜index.ts + ┣ 📂pages + ┃ ┣ 📂Home + ┃ ┣ 📂Landing + ┃ ┗ 📜index.ts + ┣ 📂shared + ┃ ┣ 📂apis + ┃ ┣ 📂hooks + ┃ ┣ 📂lib + ┃ ┃ ┣ 📂constants + ┃ ┃ ┣ 📂types + ┃ ┃ ┗ 📜index.ts + ┃ ┣ 📂routes + ┃ ┣ 📂store + ┃ ┣ 📂styles + ┃ ┣ 📂ui + ┃ ┃ ┣ 📂alert + ┃ ┃ ┣ 📂alertDialog + ┃ ┃ ┣ 📂audioButton + ┃ ┃ ┣ 📂buttons + ┃ ┃ ┣ 📂inputBar + ┃ ┃ ┣ 📂modal + ┃ ┃ ┣ 📂search + ┃ ┃ ┣ 📂slider + ┃ ┃ ┣ 📂textArea + ┃ ┃ ┣ 📂toast + ┃ ┃ ┗ 📜index.ts + ┃ ┗ 📂utils + ┣ 📂widgets + ┃ ┣ 📂error + ┃ ┣ 📂galaxy + ┃ ┣ 📂galaxyCustomModal + ┃ ┣ 📂landingScreen + ┃ ┣ 📂loginModal + ┃ ┣ 📂logoAndStart + ┃ ┣ 📂nickNameSetModal + ┃ ┣ 📂postModal + ┃ ┣ 📂screen + ┃ ┣ 📂shareModal + ┃ ┣ 📂signupModal + ┃ ┣ 📂starCustomModal + ┃ ┣ 📂underBar + ┃ ┣ 📂upperBar + ┃ ┣ 📂warpScreen + ┃ ┣ 📂writingModal + ┗ 📜vite-env.d.ts +```
## BE -- 작성 중 - **테스트와 쿼리 로그 분석을 통한 이유 있는 코드 작성** ### TDD, e2e 및 유닛 테스트 @@ -255,15 +378,17 @@ FSD 방식 아키텍쳐는 프로젝트 폴더를 6개의 `layer`로 구분하 #### 학습 및 개발 기록 - [테스트 코드를 작성해야 하는 이유](https://www.notion.so/b091dfc8229e4943af4acef50a7a5b75) -- [NetsJS + Jest 환경 설정](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%A4%80%EC%84%AD%5D-1114(%ED%99%94)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D) +- [NetsJS + Jest 환경 설정]() - [NestJS, TDD로 개발하기](https://velog.io/@qkrwogk/NestJS-TDD%EB%A1%9C-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-cx211d14) - [2주차 멘토링 일지(BE) - TDD 관련](https://github.com/boostcampwm2023/web16-B1G1/wiki/2%EC%A3%BC%EC%B0%A8-%EB%A9%98%ED%86%A0%EB%A7%81-%EC%9D%BC%EC%A7%80#be) -- [TDD 기록 1](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%9E%AC%ED%95%98%5D-1115(%EC%88%98)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D) -- [TDD 기록 2](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%9E%AC%ED%95%98%5D-1116(%EB%AA%A9)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D) -- [TDD 기록 3](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%A4%80%EC%84%AD%5D-1116(%EB%AA%A9)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D) +- [TDD 기록 1]() +- [TDD 기록 2]() +- [TDD 기록 3]() - [NestJS e2e 테스트 (jest, supertest)](https://velog.io/@qkrwogk/NestJS-e2e-%ED%85%8C%EC%8A%A4%ED%8A%B8-jest-supertest) - [NestJS, 유닛 테스트 각종 mocking, e2e 테스트 폼데이터 및 파일첨부](https://velog.io/@qkrwogk/NestJS-%EC%9C%A0%EB%8B%9B-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%81%EC%A2%85-mocking-e2e-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%8F%BC%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%8F-%ED%8C%8C%EC%9D%BC%EC%B2%A8%EB%B6%80) +
+ ### 인증/인가 인증/인가에 대해 고민도 많이 하였습니다. @@ -273,10 +398,11 @@ Session vs JWT, Authorization Bearer vs Cookie, RefreshToken #### 학습 및 개발 기록 - [Redis 연결](https://velog.io/@songjseop/nestjs-redis) -- [RefreshToken 발급 및 Redis 저장](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%A4%80%EC%84%AD%5D-1121(%ED%99%94)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D-%E2%80%90-%08SignIn%EC%8B%9C-RefreshToken-%EB%B0%9C%EA%B8%89-%EB%B0%8F-Redis%EC%97%90-%EC%A0%80%EC%9E%A5) -- [커스텀 AuthGuard 작성](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%A4%80%EC%84%AD%5D-1121(%ED%99%94)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D-%E2%80%90-%EC%BB%A4%EC%8A%A4%ED%85%80-AuthGuard-%EC%9E%91%EC%84%B1) -- [OAuth(깃헙 로그인)](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%A4%80%EC%84%AD%5D-1122(%EC%88%98)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D-%E2%80%90-%EA%B9%83%ED%97%99-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84) +- [RefreshToken 발급 및 Redis 저장]() +- [커스텀 AuthGuard 작성]() +- [OAuth(깃헙 로그인)]() +
### 트랜잭션 제어, 쿼리 최적화 @@ -289,10 +415,12 @@ TypeORM 쿼리 로그를 통해 하나의 비즈니스 로직에서 복수개의 - [Transaction(트랜잭션)](https://velog.io/@qkrwogk/Transaction-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98) - [TypeORM 트랜잭션(Transaction) 제어 with Query Runner 1](https://velog.io/@qkrwogk/TypeORM-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98Transaction-%EC%A0%9C%EC%96%B4-with-Query-Runner-1%EC%9D%BC%EC%B0%A8) - [TypeORM 트랜잭션(Transaction) 제어 with Query Runner 2](https://velog.io/@qkrwogk/TypeORM-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98Transaction-%EC%A0%9C%EC%96%B4-with-Query-Runner-2%EC%9D%BC%EC%B0%A8) -- [NestJS Interceptor와 로거 - Transaction Interceptor](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%A4%80%EC%84%AD%5D-1130(%EB%AA%A9)-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EB%A1%9D-%E2%80%90-NestJS-Interceptor%EC%99%80-%EB%A1%9C%EA%B1%B0#transaction-interceptor) -- [transaction 제어 인터셉터 방식 -> 메소드 내부에서 수행하는 방식으로 변경](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%9E%AC%ED%95%98%5D-1207(%EB%AA%A9)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D#transaction-%EC%A0%9C%EC%96%B4-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0-%EB%B0%A9%EC%8B%9D---%EB%A9%94%EC%86%8C%EB%93%9C-%EB%82%B4%EB%B6%80%EC%97%90%EC%84%9C-%EC%88%98%ED%96%89%ED%95%98%EB%8A%94-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EB%B3%80%EA%B2%BD) +- [NestJS Interceptor와 로거 - Transaction Interceptor]() +- [transaction 제어 인터셉터 방식 -> 메소드 내부에서 수행하는 방식으로 변경]() - [TypeORM 쿼리 로그, MySQL 쿼리 플랜, Query Builder을 이용한 쿼리 최적화 with NestJS](https://velog.io/@qkrwogk/TypeORM-%EC%BF%BC%EB%A6%AC-%EB%A1%9C%EA%B7%B8-MySQL-%EC%BF%BC%EB%A6%AC-%ED%94%8C%EB%9E%9C-Query-Builder%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%BF%BC%EB%A6%AC-%EC%B5%9C%EC%A0%81%ED%99%94-with-NestJS) +
+ ### NestJS Enhancers NetsJS 자체에 대한 학습을 위해 NestJS의 Lifecycle과 각 Enhancer들에 대해서도 학습을 해보았습니다. @@ -300,9 +428,10 @@ Interceptor, Exception Filter 등 학습을 하고 백엔드 코드에 적용을 #### 학습 및 개발 기록 - - [NestJS Interceptor와 로거 -> Log Interceptor](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%A4%80%EC%84%AD%5D-1130(%EB%AA%A9)-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EB%A1%9D-%E2%80%90-NestJS-Interceptor%EC%99%80-%EB%A1%9C%EA%B1%B0) - - [Exception Filter](https://www.notion.so/NestJS-Exception-Filter-13906c848dfb45ffb2829e81e470c619?pvs=4) - +- [NestJS Interceptor와 로거 -> Log Interceptor]() +- [Exception Filter](https://www.notion.so/NestJS-Exception-Filter-13906c848dfb45ffb2829e81e470c619?pvs=4) + +
### 배포 및 자동화 @@ -315,17 +444,25 @@ Interceptor, Exception Filter 등 학습을 하고 백엔드 코드에 적용을 - [NGINX 설정](https://www.notion.so/NGINX-b03d0811b0884ca3b7f61ca35f2d7779?pvs=4) - [SSH 보안: Key Forwarding, Tunneling, 포트 변경](https://velog.io/@qkrwogk/SSH-%EB%B3%B4%EC%95%88-SSH-Key-Forwarding-SSH-Tunneling%EC%9D%84-%ED%86%B5%ED%95%B4-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C%EC%9D%98-private-instance%EC%97%90-%EC%A0%91%EA%B7%BC%ED%95%98%EB%8A%94-%EB%B2%95-SSH-%ED%8F%AC%ED%8A%B8-%EB%B3%80%EA%B2%BD) - [Kubernetes 기초(minikube), docker image 최적화(멀티스테이징)](https://velog.io/@qkrwogk/Kubernetes-%EA%B8%B0%EC%B4%88minikube-docker-image-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A9%80%ED%8B%B0%EC%8A%A4%ED%85%8C%EC%9D%B4%EC%A7%95-AWS-IAM-EC2) -- [NCP VPC&인스턴스 구성, MySQL, nginx, docker, docker-compose](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%9E%AC%ED%95%98%5D-1119(%EC%9D%BC)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D) +- [NCP VPC&인스턴스 구성, MySQL, nginx, docker, docker-compose]() - [Redis 연결 후 RedisRepository 작성](https://velog.io/@songjseop/nestjs-redis) -- [NCP Object Storage, HTTPS, GitHub Actions](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%9E%AC%ED%95%98%5D-1123(%EB%AA%A9)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D) -- [NAT Gateway, MongoDB](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%9E%AC%ED%95%98%5D-1126(%EC%9D%BC)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D#%EB%B0%B0%ED%8F%AC%EC%9A%A9-db-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EC%97%90-mongodb-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%99%B8%EB%B6%80%EC%97%B0%EB%8F%99) -- [플랫폼 종속성 문제 해결(Sharp), 쿼리 최적화](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%9E%AC%ED%95%98%5D-1128(%ED%99%94)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D) -- [docker 이미지 최적화](https://github.com/boostcampwm2023/web16-B1G1/wiki/%5B%EC%9E%AC%ED%95%98%5D-1203(%EC%9D%BC)-%EA%B0%9C%EB%B0%9C%EA%B8%B0%EB%A1%9D) - +- [NCP Object Storage, HTTPS, GitHub Actions]() +- [NAT Gateway, MongoDB]() +- [플랫폼 종속성 문제 해결(Sharp), 쿼리 최적화]() +- [docker 이미지 최적화]()
-### **💪🏻 자세한 프로젝트 진행과정은 [wiki](https://github.com/boostcampwm2023/web16-B1G1/wiki)를 참고해주세요!** +### admin 페이지 구현 + +리액트를 경험해보고 싶어서 Vite + React + TS를 활용해 간단한 admin 페이지를 만들어보았습니다. +admin용 계정 정보를 설정하고, 게시글 관리 및 컴퓨터 자원 사용량, 에러 로그의 차트를 볼 수 있습니다. + +![image](https://github.com/boostcampwm2023/web16-B1G1/assets/101378867/780802e8-94f2-427d-b5c5-96e4f03dea17) + +![image](https://github.com/boostcampwm2023/web16-B1G1/assets/101378867/94877604-5f54-4807-8570-d7d7aa699c65) + +
@@ -336,7 +473,7 @@ Interceptor, Exception Filter 등 학습을 하고 백엔드 코드에 적용을
- + diff --git a/packages/client/src/widgets/galaxy/lib/modules/instances.tsx b/packages/client/src/widgets/galaxy/lib/modules/instances.tsx index 8cb7486..b6b179d 100644 --- a/packages/client/src/widgets/galaxy/lib/modules/instances.tsx +++ b/packages/client/src/widgets/galaxy/lib/modules/instances.tsx @@ -82,7 +82,7 @@ export default function Instances({ count, size, color, isCustom }: PropsType) { instancedMeshRef.current.getMatrixAt(index, tempMatrix); tempObject.position.setFromMatrixPosition(tempMatrix); - tempObject.scale.set(scale, scale, scale); + if (scale > 0.5) tempObject.scale.set(scale, scale, scale); tempObject.updateMatrix(); instancedMeshRef.current.setMatrixAt(index++, tempObject.matrix); } diff --git a/packages/server/src/admin/admin.service.ts b/packages/server/src/admin/admin.service.ts index fd58e45..b53d64b 100644 --- a/packages/server/src/admin/admin.service.ts +++ b/packages/server/src/admin/admin.service.ts @@ -12,7 +12,6 @@ import { LogInterceptor } from '../interceptor/log.interceptor'; import { HttpExceptionFilter } from '../exception-filter/http.exception-filter'; import * as osUtils from 'os-utils'; import { exec } from 'child_process'; -import { decryptAes } from '../util/aes.util'; import { InjectModel } from '@nestjs/mongoose'; import { Exception } from '../exception-filter/exception.schema'; import { awsConfig, bucketName } from '../config/aws.config'; diff --git a/packages/server/src/board/dto/create-board.dto.ts b/packages/server/src/board/dto/create-board.dto.ts index 565f2b1..763206e 100644 --- a/packages/server/src/board/dto/create-board.dto.ts +++ b/packages/server/src/board/dto/create-board.dto.ts @@ -4,7 +4,7 @@ import { IsJSON, IsNotEmpty, IsString, MaxLength } from 'class-validator'; export class CreateBoardDto { @IsNotEmpty({ message: '게시글 제목은 필수 입력입니다.' }) @IsString({ message: '게시글 제목은 문자열로 입력해야 합니다.' }) - @MaxLength(255, { message: '게시글 제목은 255자 이내로 입력해야 합니다.' }) + @MaxLength(20, { message: '게시글 제목은 20자 이내로 입력해야 합니다.' }) @ApiProperty({ description: '게시글 제목', example: 'test title', diff --git a/packages/server/src/board/file.service.ts b/packages/server/src/board/file.service.ts index 3e882f0..40b1196 100644 --- a/packages/server/src/board/file.service.ts +++ b/packages/server/src/board/file.service.ts @@ -12,16 +12,26 @@ import * as sharp from 'sharp'; @Injectable() export class FileService { async uploadFile(file: Express.Multer.File): Promise { - if (!file.mimetype.includes('image')) { - throw new BadRequestException('not an image file'); + if ( + !file.mimetype.includes('image') || + !file.originalname.match(/\.(jpg|jpeg|png)$/) + ) { + throw new BadRequestException('not supported image files'); } const { buffer } = file; - const resized_buffer = await sharp(buffer) - .resize(500, 500, { fit: 'cover' }) - .toFormat('png', { quality: 100 }) - .toBuffer(); + let resized_buffer; + try { + resized_buffer = await sharp(buffer) + .resize(500, 500, { fit: 'cover' }) + .toFormat('png', { quality: 100 }) + .toBuffer(); + } catch (e) { + // sharp에서 지원하지 않는 파일 형식 등의 이유로 리사이징에 실패한 경우 에러 리턴 + Logger.error(e); + throw new InternalServerErrorException('image resize failed'); + } const filename = uuid(); diff --git a/packages/server/src/interceptor/transaction.interceptor.ts b/packages/server/src/interceptor/transaction.interceptor.ts index 412ea7a..4a99342 100644 --- a/packages/server/src/interceptor/transaction.interceptor.ts +++ b/packages/server/src/interceptor/transaction.interceptor.ts @@ -2,7 +2,6 @@ import { CallHandler, ExecutionContext, Injectable, - InternalServerErrorException, Logger, NestInterceptor, } from '@nestjs/common'; diff --git a/packages/server/src/star/dto/update-star.dto.ts b/packages/server/src/star/dto/update-star.dto.ts index 3581ce6..c2202b3 100644 --- a/packages/server/src/star/dto/update-star.dto.ts +++ b/packages/server/src/star/dto/update-star.dto.ts @@ -1,3 +1 @@ -import { PartialType } from '@nestjs/swagger'; - export class UpdateStarDto {}