본문 바로가기

TIL & WIL

WIL_220517_JWT, Ajax, Flask 통한 API구현, 미니프로젝트 회고

사실 저번주 일요일에 올렸어야 하는 글인데,

외부일정과 둥이 육아를 하니 하루가 끝나있었다.(솔직히 피곤하기도..)

부랴부랴 적어보는 뒤늦은 WIL...ㅎㅎ

 

5월 9일 시작된 항해99 부트캠프 7기가 어느덧 2주차에 접어들었다.

하루 약 15시간 정도씩 공부했던 것 같은데 첫 주차는 적응이 잘 안되더라.

오늘 공부한 시간(현재 14시간 44분 경과 중...)을 제외하고 벌써 104시간 26분을 

누적하여 공부중이다. 월, 화, 수, 목, 금, 토 그리고 월까지 만으로 7일동안

100시간이 넘게 공부를 하다니.. 고3때도 이렇게는 안했던 것 같은데 ㅋㅋ

 

뭐 사실 중간중간 점심먹고 저녁먹는 시간 빼야하니까 정확히 공부에 몰두하는 시간은
2시간정도는 빼야하지만 잠시 쉬는 동안에도 계속 머릿속에서 

"어떻게 알고리즘 문제를 풀까..", "그 개념은 뭐였더라.." 등등의 생각이 오고가니

뇌는 계속 움직이고 있었다고 봐도 되겠지..?ㅋㅋ

몸도 마음도 갑작스러운 변화에 적응하는 1주차였다.

 

그런들 저런들 부트캠프는 시작됐고,

캠프측에서 4명씩 조를 짜주어 나는 4조의 조장이 되었고

3명의 팀원분들과 함께 '미니 프로젝트' 구현을 시작했다.

 

조원분들과 구현할 아이디어에 대해 논의한 결과

우리는 '어쩔쓰레기'라는 서비스를 미니프로젝트로 런칭해보는 것으로 정했다.ㅋㅋ

요즘 어쩔티비... 등의 유행어와 쓰레기 마다의 알맞은 분리수거 방법을 합쳐본거였는데,

뭐.. 나름 뇌리에 박히는 네이밍이었다고 생각한다.ㅋㅋ

 


 

Python, JavaScript, JQuery, Ajax, Flask(파이썬 웹 프레임워크), MongoDB, JWT 등을 기초로

서버사이드 렌더링(SSR) 웹 사이트를 구현하였다.

 

- 로그인 및 회원가입(login.html)

- 아이템을 카드섹션 형태로 보여주는 분류페이지(index.html), 

- 아이템을 클릭했을 때 해당 아이템의 상세한 분리수거 방법과 이용자들의 댓글기능이 있는 상세페이지(detail.html)이

주된 구현 페이지였다.

 

월요일에 과제를 던져주고 목요일 오후 6시까지 제출해야하는 타이트한 일정이었는데,

사실 월요일은 부트캠프 시작날이라 이것저것 공지사항도 많고 멘토 및 매니저분들의 발제 및 줌 세션도 있고 하여

온전히 과제에 집중하기는 어려웠던 것 같다. 화요일부터 제대로 미니프로젝트를 시작했고 그렇게 약 2일 남짓 되는 기간 동안

주어진 강의를 참고하며 미니프로젝트 구현에 매진했다.

 

몇 페이지 되지는 않았지만, 로그인 시, Json Web Token(JWT)를 이용하여 토큰에 해당 이용자의 정보를 담아 쿠키를 발행해주고,

쿠키가 브라우저에 저장됐을 시에는 로그인 상태가 원하는 시간만큼 유지되는 코드를 배우고 적용하였는데,

상당히 간편하면서도 유용한 코드이고 실용적이었다고 생각한다.

비밀번호도 hashlib을 import하여 입력받은 암호를 암호화 시키는 것도 간편하게 구현할 수 있었다.

@app.route('/sign_in', methods=['POST'])
def sign_in():
    # 로그인
    username_receive = request.form['username_give']
    password_receive = request.form['password_give']

    pw_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest() # Hash함수를 이용해서 패스워드를 암호화
    result = db.users.find_one({'username': username_receive, 'password': pw_hash})

    if result is not None:
        payload = {
         'id': username_receive,
         'exp': datetime.utcnow() + timedelta(seconds=60 * 60 * 24)  # 로그인 24시간 유지
        }
        token = jwt.encode(payload, SECRET_KEY, algorithm='HS256').decode('utf-8')
        status = True

        return jsonify({'result': 'success', 'token': token, 'status': status})
    # 찾지 못하면
    else:
        return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})

 

클라이언트가 볼 수 있는 View 페이지라고 해야할까. detail.html과 index.html에서는

Ajax의 GET, POST 요청과 JQuery 문법을 통해 app.py에 구현해놓은 서버사이드와 원활히 데이터를 주고 받을 수 있었고, MongoDB CRUD까지 app.py에서 한꺼번에 조작할 수 있었다.(API 구현!)

 

function get_posts() {
            $('#items').empty()
            $.ajax({
                type: "GET",
                url: "/get_posts",
                data: {},
                success: function (response) {
                    if (response["result"] == "success") {
                        let item_list = response["item_list"]
                        for (let i = 0; i < item_list.length; i++) {
                            let item = item_list[i]
                            let item_num = item["num"]
                            let item_name = item["name"]
                            let img_path = item["img"]
                            let intro = item["intro"]
                            let html_temp = `<div class="col" id=${item_name}>
                                                <div class="card">
                                                    <a href="/items/${item_num}">
                                                        <img src="${img_path}" class="card-img-top" alt="...">
                                                    </a>
                                                    <div class="card-body">
                                                        <h3 class="card-title">${item_name}</h3>
                                                        <p class="card-text">${intro}</p>
                                                    </div>
                                                </div>
                                            </div>`
                            $('#items').append(html_temp)
                        }

                    }
                }
            })

        }
# 중분류 아이템들 다 가져와서 던져주는 것.
@app.route("/get_posts", methods=['GET'])
def get_posts():
    item_list = list(db.items.find({}, {'_id': False}))
    return jsonify({'item_list': item_list, "result": "success"})

(솔직히 말해서 너무 편하더라..)

사실 Spring이나 Django와 같은 프레임워크들은 (적당히 배워봤지만..) 구조적으로 MVC, MVT을 통해 구현을 해야해서 

아무래도 이러한 미니프로젝트에는 조금 큰 덩치의 프레임워크인듯 하고 제대로 배우지않으면 접근이 쉽지않다.

 

Jinja2 템플릿 언어를 이용해서 서버사이드에서 렌더링해줄 때 받은 데이터를

{{ something }} 형식으로 html페이지에 쏙쏙 집어넣어

동적인 페이지를 구현하는 것도 상당히 재미있게 진행했던 부분이었다.

        // 페이지 로드 되자마자 댓글 및 아이템 정보들 가지고 오는 함수
        $(document).ready(function () {
            get_comments({{ item_info.num }})

            let temp_html = `{{ item_info.exp|tojson }}`
            $("#exp-list").append(temp_html.replaceAll("\"", ""))
        })

 

또한 JQuery toggelClass() 메소드를 통해 미리 만들어놓은 login.html 페이지의 로그인 및 회원가입 폼을

버튼 클릭으로 보였다가 안보였다가 할 수 있도록 하는 것을 강의를 보고 적용하였다.

보였다 안보였다 하게 하고싶은 html 태그에 is-hidden이라는 클래스를 적용시켜놓고

$('#해당 태그의 id값').toggleClass("is-hidden") 코드를 통해 구현하였다.

 

상세페이지에서의 하단부 댓글기능은 

- 로그인을 하였을 때만 작성이 가능하도록 구현하였고

- 로그인을 하지 않은 상태에서 다른 누군가가 쓴 댓글에 좋아요를 누르면 '로그인이 필요합니다' 라는 alert가 뜰 수 있도록 했다.

이 댓글(피드) 쪽이 또 재미있었는데, 특히 그 중에서도 1)Modal 부분과 2)좋아요 부분이 상당히 흥미로웠다.

 

Modal은 처음 써본 기능이었는데 일종의 부트스트랩 사이트와 같은 Bulma라는 사이트의 모달 기능을 가져왔다.

$('Modal-post').addClass("is-active")를 통해 버튼을 클릭했을 때 해당 모달이 화면에 뜨도록 설정했고

반대로 $('Modal-post').removeClass("is-active")를 통해 모달의 바깥쪽 검은 영역이나 X버튼, 그리고 취소 버튼을

클릭 했을 때 해당 모달 창이 꺼질 수 있도록 구현하였다.

 

좋아요 기능은 흔히 SNS의 피드에서 많이 볼 수 있는 기능인데, 아 이런식으로 구현이 되는구나라는 걸 알 수 있는 좋은 계기였다.

저장된 댓글들을 모두 불러오는 /get_commets 함수를 통해서 페이지가 로드 되자마자 DB에서 해당 댓글들을 Read하여

클라이언트 화면으로 뿌려줄 수 있도록 구현하였다.

(음...고민이었던) 각 상세페이지 별로 해당되는 댓글들이 다를 것이기 때문에 /get_comments?num_give=${item_num} 이런 방식으로 구현하여 해당 상세페이지에 맞는 댓글만 불러올 수 있도록 하였다.

처음부터 댓글을 DB에 저장할 때부터 해당 상세페이지(detail.html)의 item_num을 함께 저장하였기 때문에 가능한 일이었다.

 

좋아요가 몇개가 달렸는지는 해당 댓글의 post_id(고유값)을 str(post["_id"]) 형식으로 스트링 타입으로 변환했고, 

해당 post_id를 기준으로 좋아요 횟수가 저장되어있는 'likes' DB테이블에서 .count_documents 쿼리를 통해 가져올 수 있었다.

그리고 해당 댓글에 좋아요를 내가 눌렀는지 안눌렀는지도 중요한 기능이기 때문에

post["heart_by_me"] = bool(db.likes.find_one({"post_id": post["_id"], "type": "heart", "username": payload['id']}))

를 통해 Boolean 타입으로 가지고 올 수 있었다.

특히 여기서 중요한 것은 DB테이블에서 username을 조건으로 검색할 때 payload를 이용한다는 부분이었는데

이는 Json Web Token(JWT)에 로그인 시 담겨있는 payload 정보 중 'id' (=username_receive) 값을 기준으로

체크한다는 것이었다.

 


 

자, 그렇게 해서 완성된 우리 1주차 4조의 웹서비스. "어쩔쓰레기"

영상으로 해당 페이지의 기능들이 모두 잘 동작하는지 녹화하여 유튜브에 업로드를 하였다.

https://youtu.be/XKqAiiOFBBU

 

해당 프로젝트 폴더는 AWS의 EC2 서버컴퓨터 인스턴스를 만들어서 파일질라를 이용하여 업로드하였고,

미리 가비아에서 구매해둔 도메인과 AWS Public IP를 연결하였다.

 

EC2 서버컴퓨터에 접속하기 위해서는 Git Bash 터미널을 사용하였고,

- 원격 접속 방법 

: ssh -i /path(받은 키페어를 끌어다놓으면됨)/key-pair.pem ubuntu@aws public IP주소

 

- Server run with nohup(Git Bash꺼도 문제없이 돌아가도록 하는 명령어)
: nohup python app.py &

 

- Server kill
: ps -ef | grep 'python app.py' | awk '{print $2}' | xargs kill

 

이러한 명령어들을 이용하여 손쉽게 웹상에 서비스를 배포하고 또 종료할 수 있었다.

 


 

음... 회고해보면 배움이 정말 많았던 한 주 였다고 생각한다.

회사에서 만들어져있는 웹사이트를 관리해본적은 있어도

이렇게 처음부터 웹사이트를 기획하고 코드를 작성하여 서비스 배포까지 해본 경험은 없기 때문에

상당히 값진 경험이었다.(기억에도 많이 남고!)

 

그나저나.. 이번주는 알고리즘 주차이다.

프로그래머스 웹사이트에서 40개의 문제를 제시해주었고(하~ 중레벨)

그것을 하나 하나 풀어가고 있다.

조원들과 함께 각자 맡은 문제를 풀고 설명해주는 스터디 형식으로 진행 되는데

내가 맡은 문제 이외에도 다른 조원들이 푸는 문제도 공부하는 마음으로 다 풀어보고 있다.

지금은 40문제중에 37문제를 풀었고,

이제 3문제가 남았다. 

굳은 머리를 다시 말랑말랑하게 해주는 좋은 주간인듯..

 

오늘의 WIL은 여기서 마친다.

많이 썼다.