코브라 스페이스는 코딩과 프로그래밍에 관한 주제로 된 라이브 채팅 커뮤니티입니다. 이 커뮤니티는 코딩 열정가들이 모여 정보를 공유하고 협력할 수 있는 플랫폼을 제공합니다.
"코브라"는 "COding BRAinstorm"의 줄임말입니다.
코브라 스페이스는 코딩과 프로그래밍 분야에서 아이디어를 공유하고 협력하는 공간을 상징합니다.
개발자들의 지식 공유, 네트워킹, 그리고 협력을 촉진하는 역할을 하며, 커뮤니티 내에서 새로운 아이디어와 프로젝트의 탄생을 돕습니다.
2023.08.17 ~ 2023.09.04
이승빈👑 | 심민정 | 박승원 | 이준태 | 이정효 | 김권이 |
---|---|---|---|---|---|
🌏sungbin-lee | 🌏MJ-SIM | 🌏Thisish0pe | 🌏ljt528 | 🌏jhstar2022 | 🌏Gonyim |
저희 코브라는 이스트소프트에서 주관하는 백엔드 개발 부트캠프 오르미 1기로 만나 5개월간 같이 성장해온 백엔드 주니어 개발자로 이루어진 팀 입니다. 실시간으로 채팅이 이루어지는 시스템에 대해 흥미가 생겨 코딩과 프로그래밍 분야에서 아이디어를 공유하고 협력하는 공간을 만들어 보자!라는 목표를 가지고 있습니다.
1. Notion 문서 바로가기
저희는 프로젝트의 진행 상황을 시각적으로 표현하기 위해 노션을 활용한 타임라인을 만들었습니다. 이 타임라인은 저희 프로젝트의 주요 이벤트와 마일스톤을 시각적으로 보여주며, 저희의 작업 일정과 진척 상황을 효과적으로 파악할 수 있도록 도와줬습니다.
2. SRS 문서 바로가기
저희 프로젝트의 성공적인 출발을 위해 프로젝트 시작단계에서 부터 프로젝트의 범위와 목표를 명확하게 이해하고자 시스템 요구 사항 (SRS) 문서와 유스케이스 다이어그램을 작성하였습니다. SRS 문서는 저희 프로젝트의 핵심이자 출발점입니다. SRS에는 프로젝트 범위, 목표, 요구사항, 사용사례, 기능적 요구사항을 포함하고 있습니다. 유스케이스 다이어그램으로 주요 사용사례를 시각적으로 나타내었습니다. 이 문서를 통해 프로젝트 팀원들 간의 공유된 비전을 구체화 하였습니다. 이러한 문서와 다이어그램은 혼동을 방지하고 팀원들 간의 공동 작업을 원활하게 합니다. 이는 데이터베이스 설계를 시작할 때 필요한 기반을 제공했습니다.
SRS에서 명시된 요구사항을 바탕으로 사용자 정보를 저장하는 데이터베이스 테이블을 설계하는 데 큰 도움이 되었습니다. 문서에서 정확한 요구 사항을 기술하였기 때문에 데이터베이스 테이블과 속성을 정의하고 관계를 설정하는 데 훨씬 수월하였습니다.
- 예를 들어, SRS에서 "사용자는 이름, 이메일, 비밀번호를 가지고 있어야 한다"라는 요구 사항을 보고 users 테이블의 속성을 정의하는 데 어려움이 없었습니다.
-
게시글 기능: 사용자들은 다양한 주제에 대한 게시글을 작성할 수 있습니다. 이 게시글은 프로그래밍과 관련된 다양한 주제를 다룰 수 있으며, 지식과 경험을 공유하고 질문을 할 수 있는 장을 제공합니다.
-
유저 기능: 사용자들은 개인 프로필을 만들고 자신의 코딩 역량과 관심사를 공유할 수 있습니다. 이를 통해 다른 사용자와 연결하고 커뮤니티 내에서 활발한 활동을 할 수 있습니다.
-
채팅 기능: 게시글의 주제에 따라 채팅방에 참여할 수 있습니다. 이렇게 하면 게시글 내용에 대한 토론을 더 쉽게 진행할 수 있으며, 사용자들은 관심 있는 주제에 대한 논의에 참여할 수 있습니다.
☑️기술:BackEnd
☑️ FE
제공자: 이승빈
레포지토리: https://github.com/ESTsoft-Ormi-1/cobra-space-fe
☑️ Version
django~=4.1.0
djangorestframework
channels[daphne]~=4.0.0
channels_redis~=4.0.0
redis!=4.4.0
django-bootstrap5
beautifulsoup4
django-cors-headers
djangorestframework-simplejwt
dj-rest-auth
django-allauth~=0.54.0
☑️ 개발환경
개발환경 | 선택 방식 |
---|---|
브렌치 전략 | Git-flow(chat/user/post/main) |
구조 관리 | UML, ERD |
디자인 | Bootstrap |
배포 환경 | Naver Cloud Platform, Nginx, Gunicorn, Daphne |
문서화 | Notion, Google Docs |
이슈 관리 | Git |
- URL:
/api/chat/message/
- Method: POST
- Description: 새로운 채팅 메세지를 생성합니다.
요청 예시:
{
"type": "chat.message",
"message": "Hello, World!"
}
- URL:
/api/chat/room/
- Method: POST
- Description: 새로운 대화방을 생성합니다.
요청 예시:
{
"name": "Chat Room"
}
- URL:
/api/chat/room/{room_id}/
- Method: POST
- Description: 특정 대화방을 조회합니다.
- 요청 파라미터:
{room_id}
- 조회할 방의 고유 식별자
요청 예시:
{
"id": 42,
"name": "Chat Room",
"chat_url": "/ws/chat/42/chat/"
}
- URL:
wss://example/ws/chat/{room_id}/chat/
- Description: 클라이언트는 이 엔드포인트를 통해 웹소켓 서버에 연결할 수 있습니다.
WebSocket 연결을 통해 주고받는 메시지는 다음과 같은 유형을 가질 수 있습니다:
chat.message
: 일반 채팅 메시지chat.user.join
: 사용자가 채팅방에 입장한 이벤트chat.user.leave
: 사용자가 채팅방에서 퇴장한 이벤트
클라이언트는 JSON 형식의 메시지를 WebSocket 서버로 전송할 수 있습니다. 메시지의 형식은 다음과 같습니다:
{
"type": "chat.message",
"message": "Hello, World!"
}
WebSocket 서버는 다른 클라이언트로부터 수신한 메시지를 현재 연결된 클라이언트에게 전달합니다.
{
"type": "chat.message",
"message": "Hello, World!",
"sender": "user@example.com",
"nickname": "John",
"profile_picture_url": "https://example.com/profile.jpg"
}
클라이언트는 chat.user.join 및 chat.user.leave 이벤트를 수신하여 사용자의 입장 및 퇴장을 처리할 수 있습니다.
{
"type": "chat.user.join",
"username": "John"
}
WebSocket API에서 오류가 발생할 경우, 클라이언트는 적절한 오류 메시지를 수신합니다. 오류 메시지의 형식은 다음과 같습니다:
{
"error_type": "invalid_request",
"message": "Invalid message format."
}
- URL: /user/registration/
- Method: POST
- Description: 사용자의 정보를 사용하여 회원가입을 진행합니다.
요청:
{
"email": "이메일 주소",
"password": "비밀번호",
...
}
응답:
{
"email": "이메일 주소",
"message": "회원가입이 완료되었습니다.",
"profile": {
... # 프로필 정보
}
}
- 회원가입된 이메일 입력시
응답 :
{
"email": [
"A user is already registered with this e-mail address."
]
}
- URL: /user/login/
- Method: POST
- Description: 사용자의 이메일과 비밀번호를 사용하여 로그인을 진행합니다.
요청:
{
"email": "이메일 주소",
"password": "비밀번호"
}
응답:
{
"message": "환영합니다.",
"profile": {
... # 프로필 정보
},
"refresh": "JWT Refresh 토큰",
"access": "JWT Access 토큰"
}
- URL: /user/logout/
- Method: POST
- Description: 로그아웃을 진행합니다.
응답:
{
"message": "로그아웃이 완료되었습니다."
}
- URL: /user/add-friend/
- Method: POST
- Description: 인증된 사용자 접근시 친구를 추가합니다.
요청:
{
"email": "친구의 이메일 주소"
}
응답:
{
"message": "친구 추가 완료 메시지"
}
- URL: /user/friends/
- Method: GET
- Description: 인증된 사용자 접근시 친구 목록을 조회합니다.
응답:
{
... # 친구 목록 정보
}
- URL: /user/friend-profile/int:friend_id/
- Method: GET
- Description: 인증된 사용자 접근시 특정 사용자의 프로필 정보를 조회합니다.
응답:
{
... # 친구의 프로필 정보
}
- URL: /user/search-friends/?keyword=[이메일 정보]
- Method: GET
- Description: 인증된 사용자 접근시 키워드를 기반으로 친구를 검색합니다. 키워드는 사용자의 이메일에 포함된 키워드여야 합니다.
쿼리 파라미터
{
"keyword": "검색할 키워드(이메일에 포함된 키워드)"
}
응답:
{
... # 검색된 사용자들의 정보
}
- URL: /user/delete-friend/
- Method: POST
- Description: 인증된 사용자 접근시 친구목록에 존재하면 친구를 삭제합니다.
요청:
{
"email": "삭제할 친구의 이메일 주소"
}
응답:
{
"message": "친구 삭제 완료 메시지"
}
- 친구목록에 없을 경우 응답
{
"detail": "user5@email.com님은 친구 목록에 없습니다."
}
URL: /mysite/user/v1/profile/ Method: GET Description:
응답
{
"id": "id번호"
"profile pictute": "프로필사진"
"contact number": "연락처"
"status": "상태"
"user": "user번호"
"is_private": "계정활성화/비활성화상태"
}
URL: /mysite/user/v1/profile/edit/ Method: PUT Description: 인증된 사용자가 프로필 수정 요청을 보내고 수정을 할 수 있습니다. 요청
{
"profile pictute": "변경할 프로필사진"
"contact number": "변경할 연락처"
"status": "변경할 상태"
}
**응답**
```json
{
"profile pictute": "변경된 프로필 사진"
"contact number": "변경된 연락처"
"status": "변경된 상태"
"is_private": "계정활성화/비활성화상태"
}
- URL: /api/post/
- Method: GET
- Description: 게시된 모든 게시물의 목록을 볼 수 있습니다.
응답
[
{
"id": 1,
"tags": [
{
"name": "{'name': '태그1 수정'}"
}
],
"title": "제목 수정1",
"content": "내용 수정",
"created_at": "2023-08-22T08:11:42.241124Z",
"updated_at": "2023-08-25T04:37:38.446555Z",
"hit": 1
}
]
- URL: /api/post/detail/1/
- Method: GET
- Description: 특정 게시물에 대한 내용, 작성자 및 기타 세부 정보를 확인할 수 있습니다.
응답
{
"post_id": 1,
"title": "제목 1",
"content": "내용",
"tags": [
{
"name": "{'name': '태그1'}"
}
],
"hit": 1
}
- URL: /api/post/write
- Method: POST
- Description: 제목, 내용 및 관련 정보를 제출하여 새 게시물을 만들 수 있습니다.
요청
{
"title": "제목",
"content": "내용",
"tags": [
{"name": "태그"},
]
}
응답
{
"id": 1,
"tags": [
{
"name": "{'name': '태그1'}"
}
],
"title": "제목 1",
"content": "내용 1",
"created_at": "2023-08-22T08:11:42.241124Z",
"updated_at": "2023-08-25T07:26:47.254487Z",
"hit": 0
}
- URL: /api/post/detail/1/edit/
- Method: GET, POST
- Description: 특정 게시물의 내용, 해시태그 및 기타 세부 정보를 수정할 수 있습니다.
요청
{
"title": "제목",
"content": "내용",
"tags": [
{"name": "태그"},
]
}
응답
{
"id": 1,
"tags": [
{
"name": "{'name': '태그1 수정'}"
},
{
"name": "{'name': '태그2 추가'}"
}
],
"title": "제목 수정1",
"content": "내용 수정1",
"created_at": "2023-08-22T08:11:42.241124Z",
"updated_at": "2023-08-25T07:26:47.254487Z",
"hit": 1
}
- URL: /api/post/detail/1/delete/
- Method: POST
- Description: 특정 게시물을 웹 사이트에서 제거할 수 있습니다.
응답
{
"msg": "Post deleted",
}
- URL: api/post/categories
- Method: POST
- Description: 존재하는 모든 카테고리의 목록을 볼 수 있습니다.
응답
{
"id": 1,
"name": "Programming"
},
{
"id": 2,
"name": "Ormi"
},
{
"id": 3,
"name": "ESTsoft"
}
- URL: api/post/?search=title
- Method: POST
- Description: 작성한 키워드에 해당하는 게시물의 목록을 볼 수 있습니다.
응답
{
"id": 2,
"tags": [],
"title": "Title2",
"content": "content2",
"created_at": "2023-08-30T16:46:04.899333+09:00",
"updated_at": "2023-08-30T16:58:01.008073+09:00",
"hit": 0,
"category": 1
},
{
"id": 3,
"tags": [],
"title": "New Post Title",
"content": "This is the content of the new post.",
"created_at": "2023-08-30T17:03:28.688333+09:00",
"updated_at": "2023-08-30T17:03:28.688333+09:00",
"hit": 0,
"category": 1
}
프로젝트의 구조를 간략하게 도식화하였습니다. 저희 프로젝트는 네이버 클라우드 플랫폼에서 호스팅되었으며, 아래와 같은 구조로 동작합니다.
- 클라이언트에서 HTTP 요청이나 WebSocket (WS) 요청을 보냅니다.
- 이러한 요청은 Nginx 서버로 전달됩니다. Nginx는 리버스 프록시 서버로 설정되어 있어 요청을 적절히 분배합니다.
- HTTP 요청은 Gunicorn 서버로 전달됩니다. Gunicorn 서버는 동적 컨텐츠를 처리하고 필요한 데이터와 로직을 활용하여 응답을 생성합니다.
- WS 요청은 Daphne 서버로 라우팅됩니다. Daphne 서버는 WebSocket 프로토콜을 이용하여 실시간 채팅과 같은 기능을 처리합니다.
- Nginx 서버는 정적 컨텐츠 (예: HTML, CSS, JavaScript)를 직접 제공하며, 이를 통해 빠른 응답을 보장합니다. 이러한 구조를 통해 정적 및 동적 컨텐츠를 효율적으로 처리하고, WebSocket을 활용하여 실시간 기능을 구현하였습니다.
0. 로그인 & 로그아웃 |
---|
1. 프로필 조회 |
---|
2. 게시글 조회 |
---|
3. 채팅 기능 |
---|
-.mov |
4. 게시글 작성 |
---|
default.mov |
🙅이승빈
GCP, AWS, 네이버 클라우드 플랫폼 등 여러 플랫폼에서 다양한 프로젝트들의 배포해 보았지만 역시 배포는 매번 진행할 때마다 어려운 것 같습니다. 배포 중 발생하는 문제나 복잡성을 이해하기위해 기초적인 부분에 공부의 필요성을 느낍니다.. 그리고 팀 프로젝트를 진행하면서 Git 브랜치 전략을 나름대로 구성해봤지만 더 자세한 계획과 전략이 필요하다고 느꼇습니다.
🙅심민정
팀 프로젝트는 처음이라 어디서부터 시작해야 할지 감을 잡기가 어려웠지만, 팀장님의 지도하에 타임라인 설정, UML, SRS 작성으로 점차 틀을 잡아나가다 보니 어려움보다 기대감이 커졌다.
하지만 팀 배분에 있어서 내가 할 수 있는 부분이 한정적이다 보니 다른 부분을 선택하고 싶었지만 제일 자신이 없던 user부분을 맡게 되었다.
user의 회원가입, 로그인 기능은 장고 기술 블로그 구현할 때 한번 해본 경험이 있지만, DRF와 JWT인증을 사용해서 구현하는 과제는 처음이라 나에게 너무 크게 다가왔다.
하지만 어떠한 일이 주어졌을 때 일단 부딪혀보는 성격이 강한 나는 못하더라도 아는 만큼 코드를 작성해 나갔고, 모르는 부분은 공부해가면서 기능을 구현하려고 노력했다.
그러던 중 장고 라이브러리 중 dj-rest-auth, allauth를 알게 되었고 이 라이브러리를 사용하면 회원가입, 로그인, 프로필 등의 기능들이 url, view를 작성하지 않아도 구현이 된다는 것을 알았다.
그래서 엄청 쉽게 기능 구현이 끝난 줄 알았으나 postman으로 api앤드포인트를 테스트하는데 내가 원하는 결괏값이 잘 나오지 않아 결국 커스텀의 필요성을 느껴 모델, url, view를 작성하기 시작했다.
여기서부터 수많은 오류를 만나게 되고 결국 회원가입, 로그인을 제외한 나머지 사용자(친구) 관련 view는 APIView로 작성하게 되었다.
그리고 한 가지 남은 과업이 JWT인증 방법이었는데 처음엔 JWT가 정확히 어떠한 역할을 하는지 잘 모르고 일단 토큰이 제대로 발급되고 인증이 되는지 테스트만 하면 되는 줄 알고 token/, jwt-test/ 이렇게 요청 응답으로 확인만 했었다.
그런데 생각해보니 jwt인증은 왜 사용하는 걸까? 라는 의구심과 함께 어디서 발급되어서 어디에 사용되어야 하는지 생각해본 결과 로그인할 때 발급되고 사용자가 토큰을 계속 사용하면 연장이 되도록 만료 시간 등을 설정해 주었다.
이 외에도 친구(사용자) 간의 연결, 사용자와 채팅과의 연결 같은 부분은 아직 공부가 많이 부족하다는 것을 느끼게 해주었다.
🙅박승원
코브라 스페이스는 인증방법으로 JWT를 사용하기 때문에, 게시글 작성자를 유저와 연결할 때 request.user는 APIView 내부 혹은 상속 받은 클래스에서만 사용 가능하다는 것을 배울 수 있었다. 때문에 context 등의 변수로 request를 같이 넘겨주었다.
이때 Serializer에서 writer (글쓴이)를 받아주는 필드를 추가로 생성해주어야 하는데 이 부분을 잘 해결하지 못하고 프로젝트를 마무리 지었다는 한계점이 있다.
현재의 Serialozer의 Meta 부분을 fields = 'all' 로 인해 전체 필드를 입력 받는 문제를 fields = ('title','content')와 같이 필요한 필드들을 넣어 수정하였다면, 유저가 게시글을 작성했을 때, 글쓴이가 자동적으로 유저의 email로 업로드될 수 있었을 것이다.
🙅이정효
입력 데이터의 검증 및 보안에 충분한 고려를 못한 점. 그리고 프로젝트의 사용자 인터페이스와 경험을 개선하는데 시간을 투자하지 못했고 테스트와 디버깅 부분에 대한 노력이 부족했던거 같습니다.
🙅김권이
기능들에 대한 선택과 구현에 있어서 많은 시간이 소요가 되고, 결국 처음에 구현하기로한 기능들을 마음먹은대로 진행할 수 없다는 것을 알았다.
🙅이준태
팀 프로젝트를 처음 해봤는데 서로 소통을 하는 것이 어려웠습니다.
코딩 능력이 아직 부족하다고 생각합니다.
🙋이승빈
개발에 대한 열정과 팀원들 간의 원활한 커뮤니케이션을 통해 프로젝트를 성공적으로 이끌어내기 위해 노력을 하였고 프로젝트의 기획, 개발, 배포 등 다양한 단계에서 팀원들과 협력하여 우리 프로젝트를 완성도 높은 솔루션으로 만들어내기 위해 최선을 다 했습니다! 믿고 따라와준 팀원들에게 감사드립니다! 채널스를 적용하면서 많은 공부가 되었습니다! 자세한글은 여기를 참고해주세요! https://github.com/sungbinlee/django-channels-practice
🙋심민정
일반적인 과제가 아닌 '결과물'이 나와야 한다는 압박감이 있어서 내가 잘못하면 전체 팀원들에게 영향을 끼치게 될까봐 잠을 줄이고 밥먹는 시간에도 컴퓨터를 붙잡고 있으니 체력적으로 한계가 왔다.
멘탈과 체력관리 또한 중요하다는 것을 이번 프로젝트를 통해 뼈저리게 느꼈다. 그리고 이번에 처음 팀원들과 git-flow 브렌치 전략을 세워서 수행했는데 처음하는 일이라 브렌치 생성, 포크, PR하는데 팀원들의 도움없이는 협업이 불가능 했을 것 같다.
시간이 조금 더 여유가 있었더라면 프론트엔드쪽도 해보고 싶고 친구(사용자)간의 연결, 채팅과의 연결 등과 같은 해보고 싶은게 너무 많았지만 욕심을 부리려다 시간이 촉박해져 시간에 맞는 계획과 실행의 필요성을 많이 느낄 수 있었다.
이번 프로젝트는 확실히 성장할 수 있는 계기가 되었고 내가 어느부분에 약하고 지식이 부족한지 알 수 있었다. 마지막으로 함께 밤새우며 노력해준 팀원들에게 모두 수고했다고 말하고 싶다.
🙋박승원
첫 팀 프로젝트인 만큼 PR과 같은 협동에 필요한 기본적인 부분 조차 익숙하지 않아 프로젝트가 더 낯설게 느껴졌다.
특히 코드 병합 후 포스트 작성 유저권한부여 등의 추가 구현을 할 때 다른 팀원의 코드를 건들이지 않는 선에서 코드를 수정하고 작성하는 것이 어려웠다. 함께하는 것인 만큼 시간 내에 프로젝트를 마치기 위해서 나 자신과 타협하고 많이 내려놓았던 이전과 달리, 끝까지 코드를 붙잡고 있어보는 값진 경험을 할 수 있었다.
이 과정에서 막연하게 '부족하다'가 아닌 '큰 틀에서 코드를 이해하지 못하고 있구나'를 깨달으며 내가 짜고 있는 코드가 왜 필요하며, 구현을 하기 위해서는 어떤 단계로 진행되어야 하는지 등 전체적으로 코드를 바라보는 시야를 키우려고 노력하였다.
이를 계기로 추후 공부의 방향성과 코드를 짤 때의 접근법을 다시 고민하는 시간을 가질 수 있었다. 매 프로젝트 때마다 부족함을 마주하지만 함께한 프로젝트인 만큼 나의 부족함으로 인한 피해를 우려하며 나의 미흡함을 더욱 뼈저리게 느끼고 대면하게 해준 프로젝트이다.
마무리 지을 수 있도록 함께 격려하고 노력해준 팀원들에게 고맙다는 말 꼭 전하고 싶다.
🙋이정효
새로운 기술과 도구를 배우고 개발 기술을 향상 시킬 수 있었습니다.
개발자로서 지속적인 학습과 성장이 중요하다는 것을 깨달았습니다. 그리고 팀 내에서의 협업과 의사 소통은 프로젝트의 성패에 큰 영향을 미치는 것을 알게 되었습니다.
🙋김권이
협업을 진행하면서, 스스로에 대한 부족함을 많이느껴서 좌절할 뻔 했지만, 팀원들의 응원에 마지막까지 함께할 수 있게되어서 굉장히 뿌듯함을 느꼈습니다. 다음 프로젝트때는 조금 더 체계적이고 계획적으로 접근해야겠다는 다짐을 하게되었습니다.
🙋이준태
각자의 능력과 기간에 맞춰서 프로젝트를 기획해야 된다는 것을 느꼈습니다. 협업을 해보면서 각자의 업무만 하는 것이 아니라 팀원들의 업무가 서로 연관되어 있는 것을 보며 다른 팀원의 업무도 파악해야 되는 것이 생각보다 어려웠습니다. 각자 분업을 할 때는 팀원 간의 의사소통이 많이 필요하다고 느꼈고 사람들이 회사에 출근하는 이유를 알 것 같습니다.