WIL (Weekly I Learned) 220711 - 220717

BOSEUL KIM·2022년 7월 17일
1

TIL / WIL

목록 보기
1/9

항해99 부트캠프 시작 후 첫 주가 지났,,,
증말 아직 일주일 밖에 안지났다고는 믿기지 않는 느낌,,
그 짧은 일주일 사이에 너무 많은 것을 했기 때문일까(.◜◡◝)

목요일에 미니 프로젝트(힘듦은 미니가 아니었지만) 하나를 마치고 어제까지 알고리즘 문제풀이를 알차게 했더니 어느새 일주일이 휘리릭~~

일단 이번 WIL에서 회고해볼 것은 이번 주에 진행했던 저희 조의 프로젝트,,,!!
바로 바로 '우 아 해!! (우리 아파트 해결사!!)'
지우님께서 정해 준 우리 팀 프로젝트명,,,
너무 신박해서 모두 박수 ㄴ('o')/

그럼 우아해 프로젝트 회고 시작하겠습니다~~


일단 우리가 구현하기로 했던 API는

  • JWT 토큰을 사용한 로그인과 회원가입
    < 로그인 후에는 메인 페이지로 이동 >

  • 건의사항 버튼을 클릭해 글 등록 - 사진 업로드 가능 / 익명 처리 가능
    < 등록 후엔 메인 페이지에서 바로 등록한 글 확인 가능 >

  • 각 글을 클릭하거나 '자세히보기' 누를 시에 각 글의 디테일 페이지로 이동
    < 건의사항이 저장될 때 DB에 글 마다 고유의 번호를 부여해 그 번호를 가지고 이동 >

  • 디테일 페이지에서 댓글 등록
    < 댓글 등록 시 밑의 댓글 창에 바로 확인 가능 >

  • 로그아웃 가능

일단 크게 보면 이 정도로 나눌 수 있겠다
처음 내가 맡았던 부분은 회원가입 페이지였는데 알고보니 제일 쉬운 부분이라
로그인 페이지까지 금방 해버리고 다른 팀원들에게 붙어 이것저것 같이 해결해나갔던~

일단 JWT를 통한 로그인 API부터 떠올려보자면

  • JWT가 뭔데??
    JWT 는 JSON Web Token의 약자로 전자 서명 된 URL-safe (URL로 이용할 수있는 문자 만 구성된)의 JSON입니다.
    전자 서명은 JSON 의 변조를 체크 할 수 있게되어 있습니다.
    JWT는 속성 정보 (Claim)를 JSON 데이터 구조로 표현한 토큰으로 RFC7519 표준 입니다.
    JWT는 서버와 클라이언트 간 정보를 주고 받을 때 Http 리퀘스트 헤더에 JSON 토큰을 넣은 후 서버는 별도의 인증 과정없이 헤더에 포함되어 있는 JWT 정보를 통해 인증합니다.
    이때 사용되는 JSON 데이터는 URL-Safe 하도록 URL에 포함할 수 있는 문자만으로 만듭니다.
    JWT는 HMAC 알고리즘을 사용하여 비밀키 또는 RSA를 이용한 Public Key/ Private Key 쌍으로 서명할 수 있습니다.

네 뭐 그렇다고 합니다 f(^_^;

그러니까 한마디로 로그인할 때 이 JWT를 발행해줘서 그 로그인한 사람을 서버가 기억하는 느낌?
뭐 로그인 유지, 아이디 기억하기 이런 곳에도 쓰이지 않을까?

국비 시절 JSP로 프로젝트를 진행할 때는 토큰이 아닌 세션으로 구현했었는데
비슷하게 쓰이는 것 같다

어쨋든 이 JWT를 로그인 API에서 발행해줘서 쿠키가 생성되고 정해놓은 시간까지는 쿠키가 생성되어있어 로그인이 유지되는 느낌!
그리고 로그아웃은 단순하게 이 발행해줬던 JWT 토큰을 삭제해주면 된다!

일단 우리가 작성했던 로그인 API 코드입니다,,!!

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

    pw_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
    result = db.users.find_one({'email': email_receive, 'password': pw_hash})

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

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

사실 레퍼런스에서 따온거라 저 부분의 모든 것을 이해할 수는 없지만 어쨋든 중요한 것은!!

우리는 회원가입할 때 ID 대신 E-Mail로 가입하고 로그인도 이메일로 한다!
그래서 LOGIN 화면에서 입력하여 넘겨준 email 값과 password를 받아 user DB에 있는 email과 password와 일치하면 로그인 성공!

아 처음 로그인 정보 없이 사이트에 들어가면 밑의 API가 작동되는데
로그인 없이는 메인 페이지를 확인할 수 없구요,,,
로그인이 되어서 토큰이 발행되었다면 등록되어있는 글을 DB에서 가져와 찍어줌다 o(*゚∇゚)ノ

@app.route('/')
def home():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        user_info = db.users.find_one({"email": payload["email"]})
        posts = list(db.posting.find({}, {'_id': False}))
        return render_template('main.html', user_info=user_info, posts=posts, name=user_info["name"])
    except jwt.ExpiredSignatureError:
        return redirect(url_for("login", msg="로그인 시간이 만료되었습니다."))
    except jwt.exceptions.DecodeError:
        return redirect(url_for("login", msg="로그인 정보가 존재하지 않습니다."))

우리 로그인 화면,,, 웹 퍼블리셔로 일하다 오신 지우님의 짬에서 나온 바이브로 너무 이쁨 \(☆o◎)/

밑에 있는 회원가입 버튼을 누르면

이런 식으로 바뀌는 데 시간이 더 있었다면 회원가입 페이지를 따로 만들었으면 좋았을 듯!!

어쨋든 여기서 로그인을 하게되면 메인 페이지로 이동합니다~~!

인터넷에서 긁어온 사진이라 혹시몰라 가림 ㅎㅎ

저렇게 DB에 저장된 글을 하나씩 보여줍니당
위에는 header로 만든 로고부분과 건의사항 / 로그아웃 버튼

건의사항을 누르면 글쓰기 페이지로 이동해서

이런 화면이 뜨도록!! 일단 저 이름과 동•호수 부분에는 유저 DB에서 가져온 정보를 바로 찍어줬다

@app.route('/write')
def write():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        user_info = db.users.find_one({"email": payload["email"]})
        return render_template('write.html', user_info=user_info)
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("main.html"))

방법이 맞는지는 모르겠지만 글 작성 API에서 토큰 발행할 때 user_info로 email값을 같이 넘겨주었고
글 작성 페이지에서 그 값을 받아 input 박스에 value값을 jinja2를 사용하여 찍어주었다

<div class="info">
  <div class="input-group mb-3">
    <span class="input-group-text" id="inputGroup-sizing-default">이름</span>
    <input type="text" class="form-control form-control-sm" id="writer" 
           aria-label=".form-control-lg example" value="{{ user_info.name }}">
  </div>
  <div class="input-group mb-3">
    <span class="input-group-text" id="inputGroup-sizing-default">동, 호수</span>
    <input class="form-control form-control-sm" id="address" type="text" 
			aria-label=".form-control-lg example" value="{{ user_info.address }}">
  </div>
</div>

글이 DB로 들어가는 API를 설계할 땐 조금 힘들었다,,, 종익님이 맡았던 부분이라 사진 파일을 업로드하는 부분까지 너무 잘해주셨는데 글 번호를 생성해서 DB에 같이 넣어주려고 하는 부분을 짤 때 머리가 터지는줄아랐다,,,
그러다 동민님이 찾아오신 github에 참고할 만한 부분이 있어서 해결~
이게 바로 협업?? \(╹◡╹\Ξ/╹◡╹)/

@app.route("/posting", methods=["POST"])
def web_posting_post():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        user_info = db.users.find_one({"email": payload["email"]})
        content_receive = request.form["content_give"]
        anonymous_receive = request.form['anonymous_give']
        writer_receive = request.form['writer_give']
        address_receive = request.form['address_give']
        file = request.files["file_give"]
        extension = file.filename.split('.')[-1]

        write_count = list(db.write_count.find({}, {'_id': False}))[0]['count'] + 1

        today = datetime.now()
        mytime = today.strftime('%Y년%m월%d일%H시%M분%S초')

        filename = f'file-{mytime}'

        save_to = f'static/pics/{filename}.{extension}'
        file.save(save_to)
        doc = {
            'num': write_count,
            'content': content_receive,
            'file': f'{filename}.{extension}',
            'anonymous': anonymous_receive,
            'writer': writer_receive,
            'address': address_receive,
        }

        db.posting.insert_one(doc)
        db.write_count.update_one({'count': int(write_count - 1)}, {'$set': {'count': write_count}})
        return jsonify({"result": "success", 'msg': '포스팅 성공'})

봐보자면 글이 DB로 들어갈 때 write_count라는 장소를 만들어 글 작성마다 num 값이 1씩 증가하게 설정해놓고 글이 저장되는 posting 장소에 num 값을 같이 들어가도록 설계했다

머리터져 머리~~

그리고 글 작성 페이지에서 익명 checkbox를 체크하면 anonymous라는 부분에 "on", "off"로 구분되어 DB에 들어가고 메인 페이지나 디테일 페이지에서 글을 불러올 때 작성자 부분과 동•호수 부분이 "익명"으로 나오도록 설정했다

이건 기술매니저님과 상담할 때 얻었던 팁으로 구현해냈다 ꒰⸝⸝•。•⸝⸝꒱

	if ($('#anonymous').is(':checked') == true) {
        anonymous = "on"
    } else {
        anonymous = "off"
    }

이렇게 넣어주고 찍어줄 땐 jinja2를 사용한 if문을 통해서 찍어주었당

디테일 화면으로 넘어가보자면

익명 설정을 하면 저런식으로 "익명"으로 찍히고

아닌 경우에는 이렇게 작성자와 주소가 찍힌다

그리고나서 댓글작성 부분을 구현하는 것도 머리가 터지도록~~~
이 부분또한 아까 페이지 이동할 때 쓰기위해 만든 write_count를 이용해서 해결했다!

html 부분에 - input type="hidden" - 을 만들고 그 안에 value 값을 jinja2로 글의 num 값을 넣어주고 javascript를 통해 댓글이 저장될 때 그 num 값을 같이 가지고 들어가게 했다

그래서 불러올 때도 글 마다 저장된 num 값과 댓글이 저장될 때 가지고갔던 num 값이 같은 것만 찍어주도록하니까 성공~~ 후,,

메인 페이지에서도 비슷한 방식으로 한 것 같다, 차이점은 find_one()이냐 find()냐의 차이? 물론 세세하게 생각하면 좀 다르겠지만서도,,,,

마지막으로 로그아웃은 javascript를 통해

function sign_out() {
    $.removeCookie('mytoken', {path: '/'});
    alert('로그아웃!')
    window.location.href = "/login"
}

쿠키를 삭제해주면서 완성~~

아 생각해보니까 회원가입할 때 중복아이디 체크라거나 정규식 사용해서 아이디 , 비밀번호 형식 체크하는 것도 했는데 안적었네,,, 힘들다,,

TIL이나 WIL은 그냥 짧게 꾸준히 적는 방식으로 하려고했는데 어쩌다보니 이번 프로젝트 하나하나 뜯어보고 있었네,,,

정말 프로젝트하는 기간 동안은 하루 15시간씩 컴퓨터 앞에 앉아서 보낸 것 같다 ( -̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥᷄ _ -̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥̥᷅ )

진짜 한번에 만들기 성공하는 API는 단 하나도 없었고 진짜 이렇게 많은 오류를 맞닥뜨린 건 처음 경험해보는 것 같다

그래도 하나씩 해결될 때 마다 느꼈던 성취감이란,,,, ◖|◔◡◉|◗

하루 15시간씩 할 수 있게 만들어주는 원동력??
ㅋㅋㅋㅋㅋ

구현해내지 못해서 아쉬웠던 부분도 많았는데

  • 수정하기 버튼 구현
  • 익명 설정처럼 비밀글 설정
  • 관리자 페이지

등등 있었다 시간이 아주 더 많았어야 구현할 수 있었겠지만 ㅎㅎㅎ

그래도 발표할 때 매니저님께 칭찬도 받고 너무너무 좋은 팀원들과 함께할 수 있었던 시간이었다
내 스스로 생각하기에도 어? 조금은 성장?한 거? 같은? 그런 느낌 ㅋ,,,,ㅎㅎㅎ

이번 WIL은 너무 길어져서 조금 힘드네요,,,
앞으로는 짧게 자주 적는 느낌으로 해보겠습니다~ 그럼 이만~~~ ʕ๑•̀ᴗ-ʔ

0개의 댓글