Skip to content

API Docs

Hyeonu Yun edited this page Nov 26, 2020 · 24 revisions

API Documentation

Summary

Method URI Description
POST api/login 회원가입으로 가입된 로컬 회원의 로그인
GET api/login/github github OAuth를 활용한 회원의 로그인
GET api/login/apple apple OAuth를 활용한 회원의 로그인
POST api/signup 새로운 로컬 회원의 정보를 생성
POST api/signup/check-id 새로운 로컬 회원이 회원가입할 때 중복된 아이디가 존재하는지 여부 검사
Method URI Description
GET api/issue/?page= 특정 페이지의 이슈 목록을 불러온다. page=0 or undefined일 경우, 전체 이슈 목록을 불러온다.
GET api/issue/:id 특정 이슈를 불러온다.
POST api/issue 새로운 이슈를 생성한다.
PATCH api/issue/:id 특정 이슈를 수정한다.
DELETE api/issue/:id 특정 이슈를 삭제한다.
Method URI Description
GET api/label 레이블 목록을 불러온다.
GET api/label/:id 특정 레이블을 가져온다.
POST api/label 새로운 레이블을 생성한다.
PATCH api/label/:id 특정 레이블을 수정한다.
DELETE api/label/:id 특정 레이블을 삭제한다.
Method URI Description
GET api/milestone 마일스톤 목록을 불러온다.
GET api/milestone/:id 특정 마일스톤을 가져온다.
POST api/milestone 새로운 마일스톤을 생성한다.
PATCH api/milestone/:id 특정 마일스톤을 수정한다.
DELETE api/milestone/:id 특정 마일스톤을 삭제한다.
Method URI Description
POST api/comment 새로운 댓글을 생성한다.
PATCH api/comment/:id 특정 댓글을 수정한다.
DELETE api/comment/:id 특정 댓글을 삭제한다.
GET api/comment/:iid 특정 이슈에 대한 댓글을 모두 불러온다.
Method URI Description
GET api/user 유저 정보를 받아온다.
POST api/image/profile 유저의 이미지를 저장한다.

1. User

1-1. Local Login

Method URI description
POST api/login 회원가입으로 가입된 로컬 회원의 로그인

Request

{
    userId:"[입력한 유저 아이디]",
    password:"[입력한 유저 비밀번호]"
}

Response

{
    user: "[유저 정보 객체]",
    token: "[현재 로그인 성공한 유저의 토큰]"
}

// 예시 id: "test", password: 해쉬값

{
  "user": {
    "uid": 1,
    "userId": "test",
    "nickname": "test_nickname",
    "OAuth": false,
    "resourceServer": "local"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIsInVzZXJJZCI6InRlc3RzZHNkczEyMTIiLCJwYXNzd29yZCI6IiQyYiQxMiRQQi5nV21oZFVMT0p1L0E3bGRmQnBPMGVRR0Ywcjk1cHRialR3bFdUWnBzZTRxZkIvWlFjbSIsIm5pY2tuYW1lIjoidGVzdF9uaWNrbmFtZSIsIk9BdXRoIjpmYWxzZSwicmVzb3VyY2VTZXJ2ZXIiOiJsb2NhbCIsImNyZWF0ZWRBdCI6IjIwMjAtMTAtMjlUMDU6NDk6MTcuMDAwWiIsInVwZGF0ZWRBdCI6IjIwMjAtMTAtMjlUMDU6NDk6MTcuMDAwWiIsImlhdCI6MTYwNDAyMTY5MH0.vTY-UuBtctMceP8xfnBbqZsDlg3kz-8rTyMkwCpIl4o"
}
Code Description
200 로그인 성공
400 로그인 실패

1-2. Github Login (web)

Method URI description
GET api/login/github github OAuth를 활용한 회원의 로그인

Request

{
   
}

Response

{

}
Code Description
200 로그인 성공
400 로그인 실패

1-3. Github Login (iOS)

Method URI description
GET api/login/github github OAuth를 활용한 회원의 로그인

Request

{
   "token": [accessToken]
}

Response

{
    "user": [유저의 정보],
    "token": [접근 토큰]
}

// 예시 id: "test", password: 해쉬값

{
  "user": {
    "uid": 1,
    "userId": "test",
    "password": null,
    "nickname": "test_nickname",
    "OAuth": true,
    "resourceServer": "github",
    "createdAt": "2020-10-29T05:49:17.000Z",
    "updatedAt": "2020-10-29T05:49:17.000Z"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIsInVzZXJJZCI6InRlc3RzZHNkczEyMTIiLCJwYXNzd29yZCI6IiQyYiQxMiRQQi5nV21oZFVMT0p1L0E3bGRmQnBPMGVRR0Ywcjk1cHRialR3bFdUWnBzZTRxZkIvWlFjbSIsIm5pY2tuYW1lIjoidGVzdF9uaWNrbmFtZSIsIk9BdXRoIjpmYWxzZSwicmVzb3VyY2VTZXJ2ZXIiOiJsb2NhbCIsImNyZWF0ZWRBdCI6IjIwMjAtMTAtMjlUMDU6NDk6MTcuMDAwWiIsInVwZGF0ZWRBdCI6IjIwMjAtMTAtMjlUMDU6NDk6MTcuMDAwWiIsImlhdCI6MTYwNDAyMTY5MH0.vTY-UuBtctMceP8xfnBbqZsDlg3kz-8rTyMkwCpIl4o"
}

Code Description
200 로그인 성공
400 로그인 실패

1-4. signup

Method URI description
POST api/signup 새로운 로컬 회원의 정보를 생성

Request

{
   userId:"[입력한 유저 아이디]",
   password:"[입력한 유저 비밀번호]"
}

Response

{
    user: "[유저 정보 객체]"
}
Code Description
200 회원가입 성공
400 회원가입 실패
  • 아이디의 글자수가 모자른 경우: 아이디는 6자 이상 적어주셔야 합니다.
  • 아이디의 글자수가 넘치는 경우: 아이디는 16자 이하로 적어주셔야 합니다.
  • 비밀번호의 글자수가 모자른 경우: 비밀번호는 6자 이상 적어주셔야 합니다.
  • 비밀번호의 글자수가 넘치는 경우: 12자 이하로 적어주셔야 합니다
  • 아이디가 형식에 맞지 않는 경우: 아이디는 영문과 숫자로만 적어주셔야 합니다.
  • 비밀번호가 형식에 맞지 않는 경우: 비밀번호는 영문자, 숫자, 특수문자로 구성되어야 합니다.

1-5. check-id

Method URI description
POST api/signup/check-id 새로운 로컬 회원이 회원가입할 때 중복된 아이디가 존재하는지 여부 검사

Request

{
   userId:"[입력한 유저 아이디]"
}

Response

{
    message: "[오류 내용]"
}
  • 중복된 아이디인 경우: 아이디가 이미 존재합니다.
  • 오류가 없을경우: controller로 넘어갑니다.
Code Description
200 로그인 성공
400 로그인 실패

이후 API는 Authenicate를 헤더에 추가해야한다.

Authorization: Bearer {authToken}


2.issue

2-1. 등록된 이슈 목록 불러오기

Method URI Description
GET api/issue 특정 페이지의 이슈 목록을 불러온다.

Request

{}

Response

{
    {
        "iid": "[이슈 아이디]",
        "isOpen": "[열려있는 이슈인지 여부]", 
        "title": "[이슈 제목]",
        "createdAt": "[이슈가 만들어진 일시]",
        "closedAt": null or "[이슈가 닫힌 일시]",
        "user": {
            "uid": "[이슈를 작성한 유저 식별 번호]",
            "userId": "[이슈를 작성한 유저 아이디]",
            "nickname": "[이슈를 작성한 유저 닉네임]",
            "image": "[이슈를 작성한 유저의 프로필사진 링크]"
        },
        "comments": [] or [
            {
                "cid": "[댓글 식별 번호]",
            },
            ...
        ],
        "assignees": [] or [
            {
                "uid": "[assignee로 지정된 유저 식별 번호]",
                "userId": "[assignee로 지정된 유저 아이디]",
                "nickname": "[assignee로 지정된 유저 닉네임]",
                "image": "[assignee로 지정된 유저의 프로필사진 링크]"
            },
            ...
        ],
        "labels": [] or [
            {
                "name": "[레이블 이름]",
                "color": "[레이블 색상]"
            },
            ...
        ],
        "milestone": null or {
            "mid": "[마일스톤 식별 번호]",
            "title": "[마일스톤 제목]",
            "issues": [
                {
                    "isOpen": "[할당되어 있는 이슈가 닫혀있는지 여부]"
                },
                ...
            ]
        }
    } 
}
Code Description
200 등록된 이슈 목록 불러오기 성공
400 등록된 이슈 목록 불러오기 실패

2-1. 등록된 필터링 이슈 목록 불러오기

Method URI Description
GET api/issue 특정 페이지의 이슈 목록을 불러온다.

Query

  • isOpen= true | false | undefined // 열림 / 닫힘 / 선택안함
  • isYours= true | undefined // 내가 쓴 이슈
  • mid= 마일스톤아이디 | undefined
  • assignees[]= 어사이니 목록 | undefined // 어사이니 목록 (or)
  • labels[]= 라벨 목록 | undefined // 선택한 라벨 목록 (or)
  • isComment= true | undefined // 내가 댓글을 작성한 이슈

Request

{}

Response

{
    {
        "iid": "[이슈 아이디]",
        "isOpen": "[열려있는 이슈인지 여부]", 
        "title": "[이슈 제목]",
        "createdAt": "[이슈가 만들어진 일시]",
        "closedAt": null or "[이슈가 닫힌 일시]",
        "user": {
            "uid": "[이슈를 작성한 유저 식별 번호]",
            "userId": "[이슈를 작성한 유저 아이디]",
            "nickname": "[이슈를 작성한 유저 닉네임]",
            "image": "[이슈를 작성한 유저의 프로필사진 링크]"
        },
        "comments": [] or [
            {
                "cid": "[댓글 식별 번호]",
                "uid": "[댓글을 작성한 유저 식별 번호]"
            },
            ...
        ],
        "assignees": [] or [
            {
                "uid": "[assignee로 지정된 유저 식별 번호]",
                "userId": "[assignee로 지정된 유저 아이디]",
                "nickname": "[assignee로 지정된 유저 닉네임]",
                "image": "[assignee로 지정된 유저의 프로필사진 링크]"
            },
            ...
        ],
        "labels": [] or [
            {
                "name": "[레이블 이름]",
                "color": "[레이블 색상]"
            },
            ...
        ],
        "milestone": null or {
            "mid": "[마일스톤 식별 번호]",
            "title": "[마일스톤 제목]",
            "issues": [
                {
                    "isOpen": "[할당되어 있는 이슈가 닫혀있는지 여부]"
                },
                ...
            ]
        }
    } 
}
Code Description
200 등록된 이슈 목록 불러오기 성공
400 등록된 이슈 목록 불러오기 실패

2-2. 원하는 이슈 하나 불러오기

Method URI Description
GET api/issue/:id 특정 번호의 이슈를 불러온다.

Request

{}

Response

{ 
    "iid": "[이슈 아이디]",
    "isOpen": "[열려있는 이슈인지 여부]", 
    "title": "[이슈 제목]",
    "content": null or "[이슈 내용]",
    "createdAt": "[이슈가 만들어진 일시]",
    "closedAt": null or "[이슈가 닫힌 일시]",
    "user": {
        "uid": "[이슈를 작성한 유저 식별 번호]",
        "userId": "[이슈를 작성한 유저 아이디]",
        "nickname": "[이슈를 작성한 유저 닉네임]",
        "image": "[이슈를 작성한 유저의 프로필사진 링크]"
    },
    "comments": [] or [
        {
            "cid": "[댓글 식별 번호]",
            "content": "[댓글 내용]",
            "createdAt": "[댓글이 작성된 일시]",
            "user": {
                "uid": "[댓글을 작성한 유저 식별 번호]",
                "userId": "[댓글을 작성한 유저 아이디]",
                "nickname": "[댓글을 작성한 유저 닉네임]",
                "image": "[댓글을 작성한 유저의 프로필사진 링크]"
            }
        },
        ...
    ],
    "assignees": [] or [
        {
            "uid": "[assignee로 지정된 유저 식별 번호]",
            "userId": "[assignee로 지정된 유저 아이디]",
            "nickname": "[assignee로 지정된 유저 닉네임]",
            "image": "[assignee로 지정된 유저의 프로필사진 링크]"
        },
        ...
    ],
    "labels": [] or [
        {
            "name": "[레이블 이름]",
            "color": "[레이블 색상]"
        },
        ...
    ],
    "milestone": null or {
        "mid": "[마일스톤 식별 번호]",
        "title": "[마일스톤 제목]",
        "issues": [
            {
                "isOpen": "[할당되어 있는 이슈가 닫혀있는지 여부]"
            },
            ...
        ]
    }
}

Code Description
200 원하는 이슈 하나 불러오기 성공
400 원하는 이슈 하나 불러오기 실패

2-3. 이슈 생성하기

Method URI Description
POST api/issue 새로운 이슈를 생성한다.

Request

{
    "title": "[이슈 타이틀]",
    "content": null or "[이슈 내용]",
    "uid": "[이슈를 작성한 사용자 식별 번호]",
    "labels": null or [
        {
            "name": "[레이블 이름]",
        },
        ...
    ],
    "mid": null or [마일스톤 번호],
    "assignees": null or [
        {
            "uid": [유저 식별번호]
        },
        ...
    ]
}

Response

// status 200
{}

// status 400
{
    "message": "오류 내용"
}
Code Description
200 이슈 생성 성공
400 이슈 생성 실패

2-4. 원하는 이슈 수정하기 (이슈 오픈/클로즈 포함)

Method URI Description
PATCH api/issue/:id 특정 이슈를 수정한다. (수정한 항목만 보낸다.)

Request

{
    "isOpen": "[이슈의 오픈 여부]" // true: open, false: close
    "title": "[변경할 이슈의 제목]"
    "content": "[변경할 이슈의 내용]",
    "labels": [
        {
            "name": "[레이블 이름]",
            "state": "[레이블이 생길 것인지, 없어질 것인지 여부]" // true: insert, false: delete
        },
        ...
    ],
    "assignees": [
        {
            "uid": "[assignee로 지정된 사용자 식별 번호]",
            "state": "[해당 사용자가 assignee에 추가될 것인지, assignee에서 빠질 것인지 여부]" // true: insert, false: delete
        },
        ...
    ]
}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 이슈 수정 성공
400 이슈 수정 실패

2-5. 원하는 이슈 삭제하기

Method URI Description
DELETE api/issue/:id 특정 이슈를 삭제한다.

Request

{}

Response

// status 200
{}

// status 400
{
    "message": "오류 내용"
}
Code Description
200 이슈 삭제 성공
400 이슈 삭제 실패

2-6. 이미지 업로드

Method URI Description
POST api/image/upload 이미지 URL을 반환한다.

Request

input type="file" name="img"

Response

// status 200
{
    "link": "image URL"
}
// status 400
{}
Code Description
200 이미지 주소 반환
400 이미지 업로드 실패

3. label

3-1. 등록된 레이블 목록 불러오기

Method URI Description
GET api/label 레이블 목록을 불러온다.

Request

{
    
}

Response

// status 200
{
    "labels" : [
        {
            "name": "이름1",
            "color": "색상1",
            "desc": "설명1",
            "createdAt": "처음 생성한 날짜",
            "updatedAt": "마지막 수정 날짜",
        },
        {
            "name": "이름2",
            "color": "색상2",
            "desc": "설명2",
            "createdAt": "처음 생성한 날짜",
            "updatedAt": "마지막 수정 날짜",
        }.
        ... ,
        {
            "name": "이름 마지막",
            "color": "색상 마지막",
            "desc": "설명 마지막",
            "createdAt": "처음 생성한 날짜",
            "updatedAt": "마지막 수정 날짜",
        }
    ]
}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 레이블 목록 불러오기 성공
400 레이블 목록 불러오기 실패

3-2. 원하는 레이블 불러오기

Method URI Description
GET api/label/:name 특정 레이블을 가져온다.

Request

{
    name: "[등록된 레이블 명]"
}

Response

// status 200
{
    "name": "이름1",
    "color": "색상1",
    "desc": "설명1",
    "createdAt": "처음 생성한 날짜",
    "updatedAt": "마지막 수정 날짜",
}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 특정 레이블 가져오기 성공
400 특정 레이블 가져오기 실패

3-3. 레이블 생성하기

Method URI Description
POST api/label 새로운 레이블을 생성한다.

Request

{
    name: "[현재 입력한 레이블 명]",
    desc: null or "[현재 입력한 레이블에 대한 설명]",
    color: "[레이블 색상]"
}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 레이블 생성 성공
400 레이블 생성 실패

3-4. 원하는 레이블 수정하기

Method URI Description
PATCH api/label/:name 특정 레이블을 수정한다.(params는 수정전 이름, request는 수정한 항목만 보낸다.)

Request

{
    "name": "[수정한 레이블 이름]",
    "color": "[수정한 레이블 색상]",
    "desc": "[수정한 레이블 설명]"
}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 레이블 수정 성공
400 레이블 수정 실패

3-5. 원하는 레이블 삭제하기

Method URI Description
DELETE api/label/:name 특정 레이블을 삭제한다.

Request

{}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 레이블 삭제 성공
400 레이블 삭제 실패

4. Milestone

4-1. 등록된 마일스톤 불러오기

Method URI Description
GET api/milestone 마일스톤 목록을 불러온다.

Request

{
    
}

Response

{
    "milestones": [
        {
            "mid": "[등록된 마일스톤 아이디]",
            "isOpen": "[마일스톤 오픈/클로즈 여부]",
            "title": "[마일스톤 제목]",
            "dueDate": null or "[마일스톤 마무리 날짜]",
            "content": null or "[마일스톤 내용]",
            "issues": {
                "open" : integer,
                "close" : integer,
            }
            "createdAt": "[마일스톤 처음 작성된 날짜]",
            "updatedAt": "[마일스톤 수정 날짜]",
            "closedAt": "[마일스톤 닫힌 날짜]"
        },
        {
            "mid": "[등록된 마일스톤 아이디]",
            "isOpen": "[마일스톤 오픈/클로즈 여부]",
            "title": "[마일스톤 제목]",
            "dueDate": null or "[마일스톤 마무리 날짜]",
            "content": null or "[마일스톤 내용]",
            "issues": {
                "open" : integer,
                "close" : integer,
            }
            "createdAt": "[마일스톤 처음 작성된 날짜]",
            "updatedAt": "[마일스톤 수정 날짜]", 
            "closedAt": "[마일스톤 닫힌 날짜]"
        },
        ...
    ]
}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 마일스톤 목록 호출 성공
400 마일스톤 목록 호출 실패

4-2. 원하는 마일스톤 가져오기

Method URI Description
GET api/milestone/:id 특정 마일스톤을 가져온다.

Request

{
    
}

Response

{
    "mid": "[등록된 마일스톤 아이디]",
    "isOpen": "[마일스톤 오픈/클로즈 여부]",
    "title": "[마일스톤 제목]",
    "dueDate": null or "[마일스톤 마무리 날짜]",
    "content": null or "[마일스톤 내용]",
    "issues": {
        [
            {
                "iid": int,
                "title": string,
                "isOpen": boolean
            },
            {
                "iid": int,
                "title": string,
                "isOpen": boolean
            }
        ]
    }
    "createdAt": "[마일스톤 처음 작성된 날짜]",
    "updatedAt": "[마일스톤 수정 날짜]", 
    "closedAt": "[마일스톤 닫힌 날짜]"
        
}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 원하는 마일스톤 한 개 가져오기 성공
400 원하는 마일스톤 한 개 가져오기 실패

4-3. 마일스톤 생성하기

Method URI Description
POST api/milestone 새로운 마일스톤을 생성한다.

Request

{
    "title": "제목",
    "dueDate": null or "마감일",
    "content": null or "내용"
}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 마일스톤 생성 성공
400 마일스톤 생성 실패

4-4. 원하는 마일스톤 수정하기

Method URI Description
PATCH api/milestone/:id 특정 마일스톤을 수정한다.

Request

// 제목은 필수, dueDate, content는 선택
// isOpen일경우 isOpen 하나만 주어도 된다.
{
    "title": "제목",
    "dueDate": null or "마감일",
    "content": null or "내용",
    "isOpen": true, false,
}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 마일스톤 수정 성공
400 마일스톤 수정 실패

4-5. 원하는 마일스톤 삭제하기

Method URI Description
DELETE api/milestone/:id 특정 마일스톤을 삭제한다.

Request

{
    
}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 마일스톤 삭제 성공
400 마일스톤 삭제 실패

5. Comment

5-1. 댓글 생성

Method URI Description
POST api/comment 새로운 댓글을 생성한다.

Request

{
    "iid": "[댓글을 작성할 issue 식별 번호]",
    "comment": "[입력한 댓글 내용]"
}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 댓글 생성 성공
400 댓글 생성 실패

5-2. 댓글 수정

Method URI Description
PATCH api/comment/:cid 특정 댓글을 수정한다.

Request

{
    comment: "[수정한 댓글 내용]"
}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 댓글 수정 성공
400 댓글 수정 실패

5-3. 댓글 삭제

Method URI Description
DELETE api/comment/:cid 특정 댓글을 삭제한다.

Request

{}

Response

// status 200
{}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 댓글 삭제 성공
400 댓글 삭제 실패

5-4. 댓글 얻어오기

Method URI Description
GET api/comment/:iid 특정 이슈에 대한 댓글을 모두 불러온다.

Request

{}

Response

// status 200
[
    {
        "cid": "댓글 아이디",
        "content": "댓글 내용",
        "createdAt": "댓글 처음 생성 일자",
        "user": {
            "uid": "댓글을 작성한 유저의 아이디 고유 번호",
            "userId": "댓글을 작성한 유저의 아이디",
            "nickname": "댓글을 작성한 유저의 닉네임",
            "image": null or "댓글을 작성한 유저의 이미지 URL 링크",
            }
      },
      ...
]
// status 400
{
    "message": "오류 내용"
}
Code Description
200 댓글 삭제 성공
400 댓글 삭제 실패

이미지 저장 방식

  1. 서버 내 폴더 안에 저장 후 URL을 넘겨줌
  2. imageur API를 찾아서 해당 기능을 사용
  • 그 후 저장된 이미지의 URL을 반환

6. User

6-1. 유저 목록 받아오기

Method URI Description
GET api/user 유저 정보를 받아온다.

Request

{}

Response

// status 200
{
    {
        "uid": "[사용자의 식별 번호]",
        "userId": "[사용자의 아이디]",
        "nickname": "[사용자의 닉네임]",
        "image": "[이미지 URL]"
    },
    ...
}
// status 400
{
    "message": "오류 내용"
}
Code Description
200 유저 목록 반환
400 오류 내용 반환

6-2. 유저 이미지

Method URI Description
POST api/image/profile 유저의 이미지를 저장한다.

Request

input type="file" name="img"

Response

// status 200
{
    "link": "image URL"
}
// status 400
{}
Code Description
200 이미지 주소 반환
400 이미지 업로드 실패

🎃 데일리 스크럼 🎃

Week 1

Week 2

Week 3

🎈 데일리 회고 🎈

Week 1

Week 2

Week 3

💻 주간 개발 진행 💻

Week 1

Week 2

Week 3

🎏 스프린트 계획 회의 🎏

🧑🏻‍🤝‍🧑🏻 피어세션 🧑🏻‍🤝‍🧑🏻

📚 위클리 팀 회고 📚



Clone this wiki locally