Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

DOing

[팀 프로젝트1] MyTube 팀 프로젝트 회고 본문

항해99

[팀 프로젝트1] MyTube 팀 프로젝트 회고

mangdo 2021. 6. 11. 14:02

Made by 다현님

[ 기획 배경 ]

  미디어 시장을 장악하고 있는 유튜브는 남녀노소 할 것없이 모두가 이용하고 있습니다. 다양한 유튜브 채널이 생겨나고 유튜브에서는 사용자마다 구독할만한 채널들을 항상 추천해주고 있습니다. 하지만 정작 구독한 채널을 관리하는 기능이 없어 불편함을 느끼고 있었습니다. 저희 조원들은 모두 이를 공감하며 유튜브 채널을 카테고리별로 나누어 관리할 수 있는 MyTube웹사이트를 제작하게되었습니다.

 

쌓여만 가는 유튜브 구독 채널들 어떻게 관리하시나요?
이제는 음악, 운동, 요리, 먹방 등 카테고리 별로 나누어서 관리해보세요!

 

[ 초기 설계 과정 ]

1. 와이어프레임

 : 주어진 시간이 많지 않았던지라, 제가 예전에 사용했었던 '카카오 오븐'을 사용하여 빠르게 제작하였습니다.

2. API 설계

 

[ 담당 기능 ]

1. 웹 스크래핑

: 유튜브 URL을 입력받으면 유튜브 채널명, 섬네일 이미지, 채널 주소를 스크래핑합니다.

: 웹 스크래핑시에 생길 수 있는 여러 예외처리에 대해 신경쓰려고 노력하였습니다.

 

* 더보기를 누르시면 관련 코드를 보실 수 있습니다.

더보기

* 백엔드단의 예외처리

@app.route('/api/channel/insert', methods=['POST'])
def channel_save():
    token_receive = request.cookies.get('mytoken')
    url_receive = request.form['url']
    desc_receive = request.form['desc']
    category_receive = request.form['category']

    try:
        # 받은 url 으로 스크랩핑
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
        data = requests.get(url_receive, headers=headers)
        soup = BeautifulSoup(data.text, 'html.parser')
        ogTitle = soup.select_one('meta[property="og:title"]')['content']
        ogImage = soup.select_one('meta[property="og:image"]')['content']
        ogUrl = soup.select_one('meta[property="og:url"]')['content']
    except:
        return jsonify({'msg': '저장되지 않았습니다. URL을 다시 확인해주세요.'})

    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        doc = {'user_id': payload["id"],
               'channel_name': ogTitle,
               'channel_image': ogImage,
               'channel_url': ogUrl,
               'channel_category': category_receive,
               'channel_desc': desc_receive,
               'channel_star': False}
        db.channel.insert_one(doc)

    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))

    return jsonify({'msg': '채널이 저장되었습니다!'})

* 프론트단의 예외처리

// 입력 검사
if (!url.includes("youtube.com")) {
	alert("유튜브 URL을 입력해주세요!");
	return;
} else if (!(url.includes("youtube.com/channel/") || url.includes("youtube.com/c/")) ) {
	alert('유튜브 "채널" URL을 입력해주세요!');
	return;
}

 

유튜브 채널 스크래핑 후, 등록

2. 유튜브 채널 CRUD

 : JWT를 적용하여 사용자의 유튜브 채널을 등록, 조회, 수정, 삭제가 가능합니다.

 : 채널 수정, 삭제는 본인만 가능하며 mongoDB의 _id를 이용합니다.

 : 자신이 가진 카테고리의 채널만 조회하도록 하였습니다. (None에 대한 예외처리)

 

* 더보기를 누르시면 관련 코드를 보실 수 있습니다.

더보기

* 채널 페이지 조회 (페이지 반환)

# 채널 페이지로 이동
@app.route('/channel')
def channel_page():
    category_receive = request.args.get("category")
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        # 사용자가 가지고 있는 카테고리인가?
        is_category = bool(db.categories.find_one({'category': category_receive, 'id': payload['id']}))
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))

    return render_template('channel.html', category=category_receive, is_category=is_category)

 

 * 채널 수정, 삭제

# 채널 삭제
@app.route('/api/channel/delete', methods=['POST'])
def channel_delete():
    channel_id = request.form['channel_id']
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        # 유저는 자신이 등록한 channel 만 삭제가능하다
        # pymongo _id의 타입인 objectId로 변환
        db.channel.delete_one({'_id': ObjectId(channel_id), 'user_id': payload["id"]})
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))

    return jsonify({'msg': '삭제되었습니다!'})

 

 

유튜브 채널의 수정, 삭제
존재하지 않는 카테고리 조회시 예외처리

3. 유튜브 채널 즐겨찾기

 : 조회시에는 즐겨찾기를 누른 채널이 가장 상단에 노출됩니다.

 : 즐겨찾기 여부는 boolean타입으로 저장해놓았기 때문에 조회시 이를 이용하여 정렬합니다.

 

* 더보기를 누르시면 관련 코드를 보실 수 있습니다.

더보기

* 채널 조회 (JSON 반환)

# 카테고리의 채널 조회
@app.route('/api/channel/list', methods=['GET'])
def channel_list():
    category_receive = request.args.get("category")
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        channels = list(db.channel.find({'user_id': payload["id"], 'channel_category': category_receive})
                        .sort("channel_star", -1))
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))

    # mongoDB의 _id 타입을 objectId 에서 문자열로 변환
    for channel in channels:
        channel["_id"] = str(channel["_id"])

    return jsonify({'channels': channels})

즐겨찾기한 채널은 조회시에 상단 노출

[ 배운점 ]

1. 웹 스크래핑

: 저는 이전 프로젝트들에서 한번도 웹 스크래핑을 이용한 경험이 없었습니다. 하지만 이번기회에 한번 도전해보고 싶다는 생각이 들어, 해당 기능을 담당하게되었습니다. 저는 이 과정에서 스크래핑이란 무엇인지, 또한 <meta:og>에 대해 자세히 공부해볼 수 있었습니다.

 

2. JWT

: 이전 프로젝트들에서는 모두 세션/쿠키 인증 방식만을 사용해왔었습니다. 기존 세션/쿠키 인증방식과 토큰인증방식의 차이점에 대해 알아보며 JWT에 대해 공부하였고 포스팅으로 작성하였습니다. JWT를 이용하여 사용자를 인증하고, 해당 사용자에 맞는 데이터를 조회, 수정, 삭제할 수 있게 구현하였습니다.

 

2021.06.11 - [WEB] - [WEB] HTTP 인증방식1_세션/쿠키

2021.06.12 - [WEB] - [WEB] HTTP 인증방식2_토큰기반 인증, JWT

 

[WEB] HTTP 인증방식2_토큰기반 인증, JWT

저번 포스팅에서 HTTP에서 인증이 필요한 이유와 인증방식 중 하나인 세션/쿠키기반 인증방식에 대해서 알아보았습니다. 세션/쿠키는 별도의 세션저장소를 두기때문에 DB의 부하가 생길수 있으

doing7.tistory.com

 

3. MongoDB

: RDBMS이 아닌, NoSQL인 MongoDB를 처음 다루어보았습니다. RDBMS(Oracle, MySQL)과 달리 NoSQL은 테이블간의 관계가 존재하지 않습니다. 기존 RDBMS는 id를 통해서 조인을 빈번하게 했는데 MongoDB에서는 조인도 없고, id도 알아서 만들어준다는 개념이 생소하게 느껴졌습니다. 만들어진 _id를 그대로 가져와서 사용하려고 했더니 MongoDB의 _id는 JSON이 아닌 BSON의 objectId라는 타입이기 때문에 이와 관련된 처리를 해줘야한다는 것을 알게되었습니다. 

  유연한 데이터구조를 가질 수 있다는 NoSQL의 대표적인 장점입니다. 이번 프로젝트에서 제가 직접 사용해보면서 느꼈던 점은 '빠르고 변동이 많은 개발중이라면 정말 편하겠구나' 라는 점이였습니다. 테이블 구조를 변경할 때, RDMS에서는 이를 ALTER TABLE로 고치고 기존 데이터에서 문제가 있을 수 있기때문에 DELETE로 모두 삭제하거나 테이블을 아예 새로 만들어야 했습니다. 하지만 MongoDB의 경우 이러한 과정이 필요없기 때문에 빠른 개발이 가능했습니다. 데이터타입을 무엇으로해야 좋을지에 대한 고민을 하지않아도 된다는 점 역시 빠른 개발을 가능하게 하였습니다.

 

9시에 캠켰더니 다들 단체티 입고 온 날

4. 다양한 협업툴

: 팀원들과 온라인에서 협업을 해야했습니다. 팀 프로젝트는 무엇보다도 팀원들과의 소통이 중요했기때문에 다양한 협업툴을 활용하였습니다. 특히 게더와 구글 미트, 노션, git을 적극적으로 활용하며 팀원들과 소통하는 방법을 배웠습니다. 처음에는 온라인 화상회의가 어색하게만 느껴졌지만 하다보니 아침, 저녁, 새벽, 시간에 구애받지않을 수 있다는 점이 매력적으로 다가왔고 온라인에서도 원활한 소통이 가능하다는 것을 깨달았습니다.

  특히 저희 팀의 경우, 매일 아침 9시마다 각자의 진행상황을 공유하고 하루의 목표를 설정하였습니다. 오류가 생기면 화면 공유로 팀원들과 함께 해결하기도하고 git으로 서로의 코드를 리뷰하였습니다.

 

[ 아쉬운 점 ]

1. Git

  : git을 통한 협업이 미숙했던 점에 아쉬움이 남습니다. 팀프로젝트 진행 중에 Git 충돌이 크게 났었던 적이 있었습니다. 환경설정 파일을 잘못 올려져서 생긴 충돌이였습니다. 만약 프로젝트 초기에 제대로 gitignore을 설정했다면 이런 충돌이 없었을 것이라는 아쉬움이 남았습니다.

 

2. MongoDB 설계

  : MongoDB에 대한 설계가 아쉬움에 남습니다. 현 프로젝트에서는 사용자의 아이디를 카테고리 컬렉션에도, 채널 컬렉션에도 중복해서 들어가있습니다. 당시에 저는 유튜브 채널의 조회, 수정, 삭제는 해당 사용자만 가능하게 하고싶은 생각에 채널 컬렉션에도 사용자의 아이디를 넣었습니다. 하지만 프로젝트가 끝나고나서 생각해보니 데이터를 중복할 바에는 카테고리 컬렉션을 조회한 다음에 채널 컬렉션을 조회하는, 2개의 컬렉션을 잇따라 조회하는 법이 낫지 않았을까라는 생각이 들었습니다.

 


 

프로젝트 제출 완료하고 신나서 찍은 사진

마치며..

  프로젝트를 마치고 팀원들과 함께 가진 회고시간에서 팀원분께서 하신 말이 아직도 기억에 남습니다.

무엇보다도 웃으면서 프로젝트를 마칠수 있어서 좋았어요.

 

  개인적으로 이번 프로젝트를 마치고 나서 정말 좋은 프로젝트 경험이였다고 느끼고 있었는데 아마 이 이유였던것 같습니다. 새벽 5시까지 공부하던 팀원, 새벽 3시에 일어나는 팀원, 정말 다들 팀 프로젝트에 열심히 참여해서 저 역시도 많은 자극을 받을 수 있었습니다. 다들 체력적으로도 심적으로도 지칠 것같은 상황에서도 웃으면서 마지막까지 열심히 참여하는 팀원들 덕분에 프로젝트를 무사히 마칠 수 있었다고 생각합니다.

 

 

 

프로젝트 관련 자료

1. Github

  : https://github.com/HanghaeMytube/mytube

 

2. Youtube

  : https://youtu.be/K7LGtKgeIMI

 

3. Website (서버 내림)

  : http://firstquarter.shop/