/Teamble-Server

πŸ’›πŸ’›λŸ¬λΈ”λ¦¬ν•œ μ„œλ²„ νŒ€λΈ”λŸ¬πŸ’›πŸ’›

Primary LanguageJavaScript


μ„œλ‘œ λ‹€λ₯Έ μƒ‰μ˜ μš°λ¦¬κ°€ λ§Œλ‚˜λŠ” 곡간, νŒ€λΈ” πŸ’œ


# 우리_νŒ€λΈ”ν•˜μž!

KakaoTalk_20220107_191904466

22.01.02 ~ 22.01.22 - SOPT 29th APPJAM



Main Service

1. ν˜‘μ—… μ„±ν–₯ ν…ŒμŠ€νŠΈ

image πŸ‘‰ ν˜‘μ—… μ„±ν–₯ ν…ŒμŠ€νŠΈλ₯Ό 톡해 λ‚˜μ˜ ν˜‘μ—… μœ ν˜•μ„ μ•Œμ•„λ³΄κ³ , νƒœκ·Έμ— λ“±λ‘ν•˜μ—¬ λ‚˜μ™€ 잘 λ§žλŠ” νŒ€μ›λ“€μ„ μ°Ύμ•„λ³΄μž

2. 마이 ν”„λ‘œν•„ μ„€μ •

image πŸ‘‰ ν˜‘μ—… μ„±ν–₯ νƒœκ·Έμ™€ 관심 ν”„λ‘œμ νŠΈ λΆ„μ•Ό, ν˜‘μ—… ν¬μ§€μ…˜ μ„€μ •κ³Ό μžκΈ°μ†Œκ°œ 글을 톡해 λ‚˜λ₯Ό ν‘œν˜„ν•˜μž

3. ν”„λ‘œμ νŠΈ νŒ€ λ§Œλ“€κΈ°

image πŸ‘‰ λͺ¨μ§‘ ν¬μ§€μ…˜, μ„ ν˜Έ ν˜‘μ—… μ„±ν–₯ 및 ν”„λ‘œμ νŠΈ λΆ„μ•Ό νƒœκ·Έ 등을 선택해 λ‚˜μ™€ 잘 λ§žλŠ” νŒ€μ›λ“€μ„ κ΅¬ν•΄λ³΄μž

4. ν‚€μ›Œλ“œ νƒœκ·Έ ν•„ν„° 검색 (ν”„λ‘œμ νŠΈ & νŒ€μ›)

image image πŸ‘‰ ν‚€μ›Œλ“œ νƒœκ·Έ ν•„ν„° 검색을 톡해 λ‚˜μ™€ 잘 λ§žλŠ” ν”„λ‘œμ νŠΈμ™€ νŒ€μ›μ„ μ°Ύμ•„λ³΄μž

5. 콕 찌λ₯΄κΈ°

image πŸ‘‰ 콕 찌λ₯΄κΈ°λ₯Ό 톡해 ν•¨κ»˜ ν•˜κ³  싢은 ν”„λ‘œμ νŠΈ νŒ€μ›μ—κ²Œ 관심을 ν‘œν˜„ν•΄ 보자




Developers

λ°•ν˜„μ§€ λ¬Έκ·œμ›
dingding-21 MoonGyu1

Role

이름 μ—­ν• 
λ°•ν˜„μ§€ 초기 ν™˜κ²½ μ„ΈνŒ…, λ°μ΄ν„°λ² μ΄μŠ€ 섀계, API λͺ…μ„Έμ„œ μž‘μ„± 및 κ΅¬ν˜„
λ¬Έκ·œμ› README μž‘μ„±, λ°μ΄ν„°λ² μ΄μŠ€ 섀계, API λͺ…μ„Έμ„œ μž‘μ„± 및 κ΅¬ν˜„

IA

μ›Œλ„ˆλΉ„νŒ€ IA


Git Workflow

main ← develop ← name_feature/#issue

Click!

main은 λͺ¨λ“  μž‘μ—…μ΄ λλ‚œ ν›„ developμ—μ„œ Merge μ‹œν‚¨λ‹€.

β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”

  • main - 초기 μ„ΈνŒ… 쑴재

  • develop - local μž‘μ—… μ™„λ£Œ ν›„ Merge 브랜치

  • name_feature/#issue - 각자의 κΈ°λŠ₯ μΆ”κ°€ 브랜치

    ex) gyuwon_login/#1

β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”

  1. 각 κΈ°λŠ₯이 좔가될 λ•Œλ§ˆλ‹€ Issue 및 Branch 생성
  2. local - name_feature/#issue μ—μ„œ 각자 κΈ°λŠ₯ μž‘μ—…
  3. μž‘μ—… μ™„λ£Œ ν›„ remote - develop 에 Pull Request
  4. μ½”λ“œ 리뷰 ν›„ Confirm λ°›κ³  Merge
  5. remote - develop 에 Merge 될 λ•Œ λ§ˆλ‹€ λͺ¨λ“  νŒ€μ› remote - develop pull λ°›μ•„ μ΅œμ‹  μƒνƒœ μœ μ§€

Commit Message Convention

Click!

νƒœκ·Έ μ„€λͺ…
[CHORE] μ½”λ“œ μˆ˜μ •, λ‚΄λΆ€ 파일 μˆ˜μ •
[FEAT] μƒˆλ‘œμš΄ κΈ°λŠ₯ κ΅¬ν˜„
[ADD] Feat μ΄μ™Έμ˜ λΆ€μˆ˜μ μΈ μ½”λ“œ μΆ”κ°€, 라이브러리 μΆ”κ°€, μƒˆλ‘œμš΄ 파일 생성 μ‹œ
[HOTFIX] issueλ‚˜, QAμ—μ„œ κΈ‰ν•œ 버그 μˆ˜μ •μ— μ‚¬μš©
[FIX] 버그, 였λ₯˜ ν•΄κ²°
[DEL] μ“Έλͺ¨μ—†λŠ” μ½”λ“œ μ‚­μ œ
[DOCS] READMEλ‚˜ WIKI λ“±μ˜ λ¬Έμ„œ κ°œμ •
[CORRECT] 주둜 λ¬Έλ²•μ˜ 였λ₯˜λ‚˜ νƒ€μž…μ˜ λ³€κ²½, 이름 λ³€κ²½ 등에 μ‚¬μš©
[MOVE] ν”„λ‘œμ νŠΈ λ‚΄ νŒŒμΌμ΄λ‚˜ μ½”λ“œμ˜ 이동
[RENAME] 파일 이름 변경이 μžˆμ„ λ•Œ μ‚¬μš©
[IMPROVE] ν–₯상이 μžˆμ„ λ•Œ μ‚¬μš©
[REFACTOR] μ „λ©΄ μˆ˜μ •μ΄ μžˆμ„ λ•Œ μ‚¬μš©
[MERGE] λ‹€λ₯Έ 브랜치λ₯Ό Merge ν•  λ•Œ μ‚¬μš©

Coding Convention

Click!

λͺ…λͺ…κ·œμΉ™(Naming Conventions)


  1. μ΄λ¦„μœΌλ‘œλΆ€ν„° μ˜λ„κ°€ μ½ν˜€μ§ˆ 수 있게 μ“΄λ‹€.
  • ex)

    // bad
    function q() {
      // ...stuff...
    }
    
    // good
    function query() {
      // ..stuff..
    }

  1. 였브젝트, ν•¨μˆ˜, 그리고 μΈμŠ€ν„΄μŠ€μ—λŠ” camelCaseλ₯Ό μ‚¬μš©ν•œλ‹€.
  • ex)

    // bad
    const OBJEcttsssss = {};
    const this_is_my_object = {};
    function c() {}
    
    // good
    const thisIsMyObject = {};
    function thisIsMyFunction() {}

  1. ν΄λž˜μŠ€λ‚˜ constructorμ—λŠ” PascalCaseλ₯Ό μ‚¬μš©ν•œλ‹€.
  • ex)

    // bad
    function user(options) {
      this.name = options.name;
    }
    
    const bad = new user({
      name: 'nope',
    });
    
    // good
    class User {
      constructor(options) {
        this.name = options.name;
      }
    }
    
    const good = new User({
      name: 'yup',
    });

  1. ν•¨μˆ˜ 이름은 동사 - λͺ…사 ν˜•νƒœλ‘œ μž‘μ„±ν•œλ‹€. ex) postUserInformation( )
  2. μ•½μ–΄ μ‚¬μš©μ€ μ΅œλŒ€ν•œ μ§€μ–‘ν•œλ‹€.

μ°Έμ‘°(References)


  1. λͺ¨λ“  μ°Έμ‘°λŠ” constλ₯Ό μ‚¬μš©ν•˜κ³ , varλŠ” μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€.

  2. μ°Έμ‘°λ₯Ό μž¬ν• λ‹Ή ν•΄μ•Όν•œλ‹€λ©΄ varλŒ€μ‹  let을 μ‚¬μš©ν•œλ‹€.

    μ΄λ•Œ, const와 let은 λΈ”λ‘μŠ€μ½”ν”„μž„μ„ 유의


블둝(Blocks)


  1. λ³΅μˆ˜ν–‰μ˜ λΈ”λ‘μ—λŠ” μ€‘κ΄„ν˜Έ({})λ₯Ό μ‚¬μš©ν•œλ‹€.
  • ex)

    // bad
    if (test)
      return false;
    
    // good
    if (test) return false;
    
    // good
    if (test) {
      return false;
    }
    
    // bad
    function() { return false; }
    
    // good
    function() {
      return false;
    }

  1. λ³΅μˆ˜ν–‰ λΈ”λ‘μ˜ if 와 else λ₯Ό μ΄μš©ν•˜λŠ” 경우 else λŠ” if 블둝 끝의 μ€‘κ΄„ν˜Έ(})와 같은 행에 μœ„μΉ˜μ‹œν‚¨λ‹€.
  • ex)

    // bad
    if (test) {
      thing1();
      thing2();
    } else {
      thing3();
    }
    
    // good
    if (test) {
      thing1();
      thing2();
    } else {
      thing3();
    }

μ½”λ©˜νŠΈ(Comments)


  1. λ³΅μˆ˜ν˜•μ˜ μ½”λ©˜νŠΈλŠ” /** ... */ λ₯Ό μ‚¬μš©ν•œλ‹€.
  • ex)

    // good
    /**
     * @param {String} tag
     * @return {Element} element
     */
    function make(tag) {
      // ...stuff...
    
      return element;
    }

  1. λ‹¨μΌν–‰μ˜ μ½”λ©˜νŠΈμ—λŠ” // 을 μ‚¬μš©ν•˜κ³  μ½”λ©˜νŠΈλ₯Ό μΆ”κ°€ν•˜κ³  싢은 μ½”λ“œμ˜ 상뢀에 λ°°μΉ˜ν•œλ‹€. 그리고 μ½”λ©˜νŠΈμ˜ μ•žμ— λΉˆν–‰μ„ λ„£λŠ”λ‹€.
  • ex)

    // bad
    const active = true; // is current tab
    
    // good
    // is current tab
    const active = true;
    
    // good
    function getType() {
      console.log('fetching type...');
    
      // set the default type to 'no type'
      const type = this._type || 'no type';
    
      return type;
    }

곡백(Whitespace)


  1. μ£Όμš” μ€‘κ΄„ν˜Έ({}) μ•žμ—λŠ” 슀페이슀 1개λ₯Ό λ„£λŠ”λ‹€.
  • ex)

    // bad
    function test() {
      console.log('test');
    }
    
    // good
    function test() {
      console.log('test');
    }

  1. μ œμ–΄κ΅¬λ¬Έ (if λ¬Έμ΄λ‚˜ while λ¬Έ λ“±) 의 μ†Œκ΄„ν˜Έ (()) μ•žμ—λŠ” 슀페이슀λ₯Ό 1개 λ„£κ³  ν•¨μˆ˜ μ„ μ–Έμ΄λ‚˜ ν•¨μˆ˜ ν˜ΈμΆœμ‹œ 인수 리슀트의 μ•žμ—λŠ” 슀페이슀λ₯Ό 넣지 μ•ŠλŠ”λ‹€.
  • ex)

    // bad
    if (isJedi) {
      fight();
    }
    
    // good
    if (isJedi) {
      fight();
    }
    
    // bad
    function fight() {
      console.log('Swooosh!');
    }
    
    // good
    function fight() {
      console.log('Swooosh!');
    }

  1. μ—°μ‚°μž μ‚¬μ΄μ—λŠ” 슀페이슀λ₯Ό λ„£λŠ”λ‹€.
  • ex)

    // bad
    const x = y + 5;
    
    // good
    const x = y + 5;

콀마(Commas)


  1. μ„ λ‘μ˜ 콀마 BAD
  • ex)

    // bad
    const story = [once, upon, aTime];
    
    // good
    const story = [once, upon, aTime];

  1. 끝의 콀마 GOOD
  • ex)

    // bad
    const hero = {
      firstName: 'Dana',
      lastName: 'Scully',
    };
    
    // good
    const hero = {
      firstName: 'Dana',
      lastName: 'Scully',
    };

μ„Έλ―Έμ½œλ‘ (Seminolons)


  1. μ„Έλ―Έμ½œλ‘ μ„ μ‚¬μš©ν•œλ‹€.
  • ex)
    // bad
    (function () {
      const name = 'Skywalker';
      return name;
    })()(
      // good
      () => {
        const name = 'Skywalker';
        return name;
      }
    )();

λ¬Έμžμ—΄(Strings)


  1. λ¬Έμžμ—΄μ—λŠ” μ‹±ν¬μΏΌνŠΈ '' λ₯Ό μ‚¬μš©ν•œλ‹€.
  • ex)

    ```jsx
    // bad
    const name = "Capt. Janeway";
    
    // good
    const name = 'Capt. Janeway';
    ```
    

  1. ν”„λ‘œκ·Έλž¨μ—μ„œ λ¬Έμžμ—΄μ„ μƒμ„±ν•˜λŠ” κ²½μš°λŠ” λ¬Έμžμ—΄ 연결이 μ•„λ‹Œ template stringsλ₯Ό μ΄μš©ν•œλ‹€.
  • ex)

    // bad
    function sayHi(name) {
      return 'How are you, ' + name + '?';
    }
    
    // bad
    function sayHi(name) {
      return ['How are you, ', name, '?'].join();
    }
    
    // good
    function sayHi(name) {
      return `How are you, ${name}?`;
    }

ν•¨μˆ˜(Functions)


  1. ν™”μ‚΄ν‘œ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•œλ‹€.
  • ex)

      ```jsx
      var arr1 = [1, 2, 3];
      var pow1 = arr.map(function (x) { // ES5 Not Good
        return x * x;
      });
    
      const arr2 = [1, 2, 3];
      const pow2 = arr.map(x => x * x); // ES6 Good
      ```
    

  1. ν•¨μˆ˜μ‹λ³΄λ‹€ ν•¨μˆ˜ 선언을 μ΄μš©ν•œλ‹€.
  • ex)

      ```jsx
      // bad
      const foo = function () {
      };
    
      // good
      function foo() {
      }
      ```
    

쑰건식과 등가식(Comparison Operators & Equality)


  1. == μ΄λ‚˜ != 보닀 === 와 !== 을 μ‚¬μš©ν•œλ‹€.

  1. λ‹¨μΆ•ν˜•μ„ μ‚¬μš©ν•œλ‹€.
  • ex)

    // bad
    if (name !== '') {
      // ...stuff...
    }
    
    // good
    if (name) {
      // ...stuff...
    }
    
    // bad
    if (collection.length > 0) {
      // ...stuff...
    }
    
    // good
    if (collection.length) {
      // ...stuff...
    }

  1. 비동기 ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•  λ•Œ Promiseν•¨μˆ˜μ˜ μ‚¬μš©μ€ μ§€μ–‘ν•˜κ³  async, awaitλ₯Ό 쓰도둝 ν•œλ‹€.


API

πŸ‘‰ λͺ…μ„Έμ„œ λ³΄λŸ¬κ°€κΈ°!

κΈ°λŠ₯ URI HTTP
λ©”μ„œλ“œ
μ„€λͺ… λ‹΄λ‹Ή μ™„λ£Œ
νšŒμ›κ°€μž…/
둜그인
/auth/signup POST μœ μ € νšŒμ›κ°€μž… κ·œμ› πŸ’œ
/auth/login POST μœ μ € 둜그인 ν˜„μ§€ πŸ’œ
/auth/login GET μœ μ € μžλ™ 둜그인 ν˜„μ§€ πŸ’œ
λžœλ”© νŽ˜μ΄μ§€ /project/top GET μ£Όλͺ©ν• λ§Œν•œ ν”„λ‘œμ νŠΈ 쑰회 κ·œμ› πŸ’œ
ν”„λ‘œμ νŠΈ /project/metadata GET ν”„λ‘œμ νŠΈ 생성 - 더미 데이터 λ°›κΈ° ν˜„μ§€ πŸ’œ
/project POST ν”„λ‘œμ νŠΈ 생성 ν˜„μ§€ πŸ’œ
/project/photo/:projectId POST ν”„λ‘œμ νŠΈ 생성 - 사진 μΆ”κ°€ ν˜„μ§€ πŸ’œ
/project/member POST ν”„λ‘œμ νŠΈ 생성 - νŒ€ ꡬ성원 μΆ”κ°€ ν˜„μ§€ πŸ’œ
/project/search/metadata GET ν”„λ‘œμ νŠΈ μ°ΎκΈ° - 더미 데이터 λ°›κΈ° ν˜„μ§€ πŸ’œ
/project/search POST ν”„λ‘œμ νŠΈ μ°ΎκΈ° - 쑰회 ν˜„μ§€ πŸ’œ
/project/:projectId GET ν”„λ‘œμ νŠΈ 상세뷰 쑰회 ν˜„μ§€ πŸ’œ
/project/:projectId DELETE ν”„λ‘œμ νŠΈ λͺ¨μ§‘ μ™„λ£Œ ν˜„μ§€ πŸ’œ
νŒ€μ› μ°ΎκΈ° /member/metadata GET νŒ€μ› μ°ΎκΈ° λ·° 더미 데이터 λ°›κΈ° κ·œμ› πŸ’œ
/member POST νŒ€μ› μ°ΎκΈ° 쑰회 κ·œμ› πŸ’œ
λ§ˆμ΄νŽ˜μ΄μ§€/
νŒ€μ› 상세뷰
/user/profile/:userId GET μœ μ € ν”„λ‘œν•„ κ°€μ Έμ˜€κΈ° κ·œμ› πŸ’œ
/user/profile/metadata GET μœ μ € ν”„λ‘œν•„ μˆ˜μ • 더미 데이터 λ°›κΈ° κ·œμ› πŸ’œ
/user/profile/:userId GET μœ μ € ν”„λ‘œν•„ μˆ˜μ • κ·œμ› πŸ’œ
/user/profile/photo
/:userId
POST μœ μ € ν”„λ‘œν•„ 사진 λ³€κ²½ κ·œμ› πŸ’œ
콕 찌λ₯΄κΈ°/
νŒ€ μ§€μ›ν•˜κΈ°
/user/poke-user POST μ½•μ°Œλ₯΄κΈ° κ·œμ› πŸ’œ
/user/poke-project POST νŒ€ μ§€μ›ν•˜κΈ° ν˜„μ§€ πŸ’œ
/user/poke-user/:userId GET λ‚˜λ₯Ό μ°”λŸ¬λ³Έ μ‚¬λžŒ κ·œμ› πŸ’œ
/user/poke-project/:userId GET λ‚΄ ν”„λ‘œμ νŠΈμ— μ§€μ›ν•œ μ‚¬λžŒ ν˜„μ§€ πŸ’œ
/user/poke-user
/:userId/:pokingUserId
DELETE λ‚˜λ₯Ό μ°”λŸ¬λ³Έ μ‚¬λžŒ μ‚­μ œ κ·œμ› πŸ’œ
/user/poke-project
/:projectId/:pokingUserId
DELETE λ‚΄ ν”„λ‘œμ νŠΈμ— μ§€μ›ν•œ μ‚¬λžŒ μ‚­μ œ ν˜„μ§€ πŸ’œ

ERD

ERD


Foldering

|-πŸ“‹ firebaserc
|-πŸ“‹ firebase.json
|-πŸ“‹ .gitignore
|-πŸ“ db*query
|-πŸ“ functions*
|- πŸ“‹ index.js
|- πŸ“‹ package.json
|- πŸ“‹ .gitignore
|- πŸ“‹ .env
|- πŸ“ api*
| |- πŸ“‹ index.js
| |- πŸ“ routes*
| |- πŸ“‹ index.js
| |- πŸ“ auth
| |- πŸ“ member
| |- πŸ“ project
| |- πŸ“ user
|
|- πŸ“ config*
| |- πŸ“‹ dbConfig.js
| |- πŸ“‹ firebaseClient.js
|
|- πŸ“ constants*
| |- πŸ“‹ jwt.js
| |- πŸ“‹ responseMessage.js
| |- πŸ“‹ statusCode.js
|
|- πŸ“ db*
| |- πŸ“‹ db.js
| |- πŸ“‹ index.js
|
|- πŸ“ lib*
| |- πŸ“‹ convertSnakeToCamel.js
| |- πŸ“‹ jwtHandlers.js
| |- πŸ“‹ util.js
|
|- πŸ“ middlewares\_
|- πŸ“‹ auth.js
|- πŸ“‹ uploadImage.js

Dependencies Module

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "lint": "eslint .",
    "serve": "cross-env NODE_ENV=development firebase emulators:start --only functions --project dev",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "cross-env NODE_ENV=production firebase deploy --only functions --project prod",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "16"
  },
  "main": "index.js",
  "dependencies": {
    "axios": "^0.25.0",
    "busboy": "^0.3.1",
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "cross-env": "^7.0.3",
    "dayjs": "^1.10.7",
    "dotenv": "^11.0.0",
    "eslint-config-prettier": "^8.3.0",
    "express": "^4.17.2",
    "firebase": "^9.6.2",
    "firebase-admin": "^9.8.0",
    "firebase-functions": "^3.14.1",
    "helmet": "^5.0.1",
    "hpp": "^0.2.3",
    "jsonwebtoken": "^8.5.1",
    "lodash": "^4.17.21",
    "pg": "^8.7.1"
  },
  "devDependencies": {
    "eslint": "^7.32.0",
    "eslint-config-google": "^0.14.0",
    "firebase-functions-test": "^0.2.0"
  },
  "private": true
}