항해99_7기/1주차 풀스택 미니 프로젝트

[항해99] 웹개발 플러스 4주차

code10 2022. 5. 13. 17:06

스파르타코딩클럽 웹개발 플러스 4주차 

  1. Bulma로 예쁜 웹사이트를 만든다.
  2. 로그인 기능을 구현할 수 있다.

회원가입, 로그인, 패스워드, 패스워드 확인, 피드 올리기, 좋아요 별표 

프로필 클릭하면 프로필 수정, 로그아웃까지

 

4-2 Bulma로 웹사이트 꾸미기

Bulma: 무료 CSS 프레임워크

             순수 CSS>수정 쉬워, 직관적, 기본 모양이 예쁨

bulma 공식문서 링크↓

파이참, 필요 패키지: flask, pymongo, (certifi), pyjwt

4-3 회원가입 기능

해시함수란, 알고리즘의 한 종류로서 임의의 데이터를 입력 받아 항상 고정된 길이의 임의의 값으로 변환해주는 함수를 말합니다.

                   우리가 회원가입에 사용할 해시함수 SHA256은 어떤 길이의 입력값을 넣어도 항상 256바이트의 결과값이 나옴.

                   특징: 동일한 입력값은 항상 같은 결과값이 나오고,

                            입력값은 조금이라도 달라지면 완전히 다른 값이 나오고,

                            결과값을 통해 입력값을 알아내는 것이 불가능하다

 

# [회원가입 API]

# id, pw, nickname을 받아서, mongoDB에 저장합니다.

# 저장하기 전에, pw를 sha256 방법(=단방향 암호화. 풀어볼 수 없음)으로 암호화해서 저장합니다.

@app.route('/api/register', methods=['POST'])

def api_register():

    id_receive = request.form['id_give']
    pw_receive = request.form['pw_give']
    nickname_receive = request.form['nickname_give']
    pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest()

    db.user.insert_one({'id': id_receive, 'pw': pw_hash, 'nick': nickname_receive})
    return jsonify({'result': 'success'})
  • (내 컴 한정) 패키지 오류ㅣ pyjwt 1.7.1로 이전 버전 설치 필요. 

4-4 로그인 기능

JWT : JSON Web Token의 줄임말로, JSON 객체를 사용해 정보를 안정성 있게 전달하는 웹표준이에요!

 

['쿠키'라는 개념에 대해 알아봅시다]

// 브라우저 자체 데이터베이스.

// 로그인을 구현하면, 반드시 쿠키라는 개념을 사용합니다.

// 페이지에 관계없이 브라우저에 임시로 저장되는 정보입니다. 키:밸류 형태(딕셔너리 형태)로 저장됩니다.

// 쿠키가 있기 때문에, 한번 로그인하면 네이버에서 다시 로그인할 필요가 없는 것입니다.

// 브라우저를 닫으면 자동 삭제되게 하거나, 일정 시간이 지나면 삭제되게 할 수 있습니다.

 

$.ajax({
                    type: "POST",
                    url: "/sign_in",
                    data: {
                        username_give: username,
                        password_give: password
                    },
                    success: function (response) {
                        if (response['result'] == 'success') {
                            // 로그인이 정상적으로 되면, 토큰을 받아옵니다.
                            // 이 토큰을 mytoken이라는 키 값으로 쿠키에 저장합니다.
                            $.cookie('mytoken', response['token'], {path: '/'});
                            window.location.replace("/")
                        } else {
                            // 로그인이 안되면 에러메시지를 띄웁니다.
                            alert(response['msg'])
                        }
                    }
                });

4-5 프로젝트 4: Sweeter

로그인 & 회원가입 페이지 ▼

더보기
  1. 기본 화면으로 로그인 화면 보이기
  2. '회원가입하기' 버튼을 클릭하면 회원가입 화면으로 바뀌기
  3. '취소' 버튼을 클릭하면 로그인 화면으로 돌아오기
  4. 회원가입
    • 아이디 & 비밀번호 형식 확인
    • 아이디 중복 확인
    • DB에 아이디와 비밀번호 저장하여 회원가입 & 로그인 화면으로 전환
  5. 로그인
    • 아이디 & 비밀번호 입력 확인
    • 서버로 POST 요청을 보내 가입 정보가 존재하는지 확인
    • 회원일 경우 토큰 부여

메인 페이지 ▼

더보기
  1. 모든 사람의 포스트를 시간 역순으로 보여주기
  2. 각 포스트에 좋아요/좋아요 취소 가능
    • 좋아요 누른 포스트는 찬 하트로 보여주기
  3. 포스팅 칸에 내 프로필 사진 보여주기
    • 프로필 사진 누르면 프로필 페이지로 이동
  4. 포스팅 칸 클릭하면 포스팅 모달 띄우기
    • 포스팅하기 버튼 클릭하면 포스트 DB에 저장
    • 새로고침하여 포스트 목록 다시 띄우기

프로필 페이지 ▼

더보기
  1. 해당 사용자의 포스트만 시간 역순으로 보여주기
  2. 내 프로필이라면 프로필 수정 & 로그아웃 버튼 보여주기
  3. 내 프로필일 때만 포스팅 칸 보여주기
  4. 프로필 수정 버튼 클릭하면 프로필 수정 모달 보여주기
    • 기존의 저장되어 있는 값 보여주기
    • 수정 시 DB에 업데이트하고 새로고침해서 변경사항 적용
  5. 로그아웃 버튼 클릭하면 토큰 삭제하고 로그인 페이지로 이동

4-6 프로젝트 세팅

4-7 로그인&회원가입 페이지 모습 만들기

모든 요소를 넣은 다음에, 로그인할 때는 파란색 요소들을 숨기고, 회원가입할 때는 초록색 요소들을 숨기면 되겠군요! 주황색 도움말 요소들도 숨겨져있다가 회원가입할 때 나타나야합니다.

 

로그인/회원가입 토글 기능 만들기

Bulma에서 is-hidden이라는 클래스 이용

.is-hidden {
    display: none!important;
}

숨겨져 있으면 드러내고, 드러나 있으면 숨겨주는 함수 만들기: sign-up-box div에 적용

function toggle_sign_up() {
    if ($("#sign-up-box").hasClass("is-hidden")) {
        $("#sign-up-box").removeClass("is-hidden")
    } else {
        $("#sign-up-box").addClass("is-hidden")
    }
}

jQuery에는 이것을 더 간단하게 도와주는 함수가 있음, 바로 toggleClass()

function toggle_sign_up() {  
    $("#sign-up-box").toggleClass("is-hidden")
}

4-8 회원가입 페이지 기능 만들기

아이디, 비밀번호 정규표현식

//- 아이디: 영문과 숫자, 일부 특수문자(._-)만 사용 가능, 2-10자 길이. 영문 무조건 포함
//- 비밀번호: 영문, 숫자는 1개 씩 무조건 포함, 일부 특수문자 사용 가능, 8-20자 길이
//- 비밀번호 확인: 비밀번호와 일치
function is_nickname(asValue) {
    var regExp = /^(?=.*[a-zA-Z])[-a-zA-Z0-9_.]{2,10}$/;
    return regExp.test(asValue);
}

function is_password(asValue) {
    var regExp = /^(?=.*\d)(?=.*[a-zA-Z])[0-9a-zA-Z!@#$%^&*]{8,20}$/;//숫자 포함 \d 
    return regExp.test(asValue);
}

 

중복확인했는지 여부 판단

Else 에서 .addClass("is-success")

회원가입 누를 때 체크했는지 판단

 

Request.form으로 유저네임 받아서, db.user ~ find. One 한다면 존재. 아니면, bool 해서 존재 하지 않는 것. 

4-9 로그인 페이지 기능 만들기

로그인 후 토큰 저장 여부 확인하는 방법

개발자 도구 > cookies >> 

4-10 메인 페이지 모습 만들기

css 파일 분리하고, index.html에 css 파일 링크 넣어줄 때, bulma css 보다 아래 위치해야 함. 내가 하는 코드가 bulma css를 수정하는 방향으로 가야 하므로.

 

실제로 글을 적을 수 있는 포스팅 모달은 우선 모습을 만들고 숨겨놓았다가 포스팅 칸을 클릭하면 나타납니다. 바깥 배경 영역이나 X표, 취소 버튼을 클릭하면 사라집니다. 나타나고 사라지는 것은 is-active 클래스를 이용해 제어가 가능합니다.

 

modal-background, modal-content의 취소 버튼, moda-close 등 3곳에 모달 기능 넣기

onclick='$("#modal-post").removeClass("is-active")

4-11 메인 페이지 모습 만들기

posts = list(db.posts.find({}).sort("date", -1).limit(20))

for post in posts:

    post["_id"] = str(post["_id"])

 

-1 내림차순으로 , 가장 최신글이 위에 오게 정렬가능. limit은 최대 20개까지만 불러오기

고유식별자는 _id , 포스트 고유값. objectID 타입으로 되어 있어서 -> 항상 문자열(string) 변경해줘야

 

function get_posts(username){ (...)
let html_temp = `<div class="box" id="${post["_id"]}"> // 항상 id를 기억하고 있음

자바스크립트의 Date 오브젝트 간의 빼기의 결과는 밀리초로 주어집니다.

function time2str(date) {
    let today = new Date()
    let time = (today - date) / 1000 / 60  // 분

    if (time < 60) {
        return parseInt(time) + "분 전"
    }
    time = time / 60  // 시간
    if (time < 24) {
        return parseInt(time) + "시간 전"
    }
    time = time / 24
    if (time < 7) {
        return parseInt(time) + "일 전"
    }
    return `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일`
}

4-12 메인 페이지 기능 만들기 - 좋아요

좋아요 업데이트 API

user_info = db.users.find_one({"username": payload["id"]})
post_id_receive = request.form["post_id_give"]
type_receive = request.form["type_give"]
action_receive = request.form["action_give"]
doc = {
    "post_id": post_id_receive,
    "username": user_info["username"],
    "type": type_receive
}
if action_receive =="like":
    db.likes.insert_one(doc)
else:
    db.likes.delete_one(doc)
count = db.likes.count_documents({"post_id": post_id_receive, "type": type_receive})
return jsonify({"result": "success", 'msg': 'updated', "count": count})

JS 파일 분리하기 : static 폴더에 js 파일 생성, <script></script> 부분 옮기기. js 파일에서는 양 끝 script표기 없어야 함.

4-13 프로필 페이지 모습 만들기 - 전체

4-14 프로필 페이지 모습 만들기 - 프로필 수정

1. 프로필 수정&로그아웃 버튼 만들기

로그아웃 기능

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

 

2. 프로필 수정 모달 만들기

4-15 프로필 페이지 모습 만들기 - 기능 만들기

  1. 내 프로필에서만 프로필 수정기능 보이게 하기

프로필 수정 & 로그아웃 버튼은 내 프로필에 들어갔을 때만 보여야겠죠? 서버에서 보내준 status 파라미터를 이용해 내 프로필일 때만 해당 부분을 그리도록 jinja2 문법을 씁니다.

{% if status %}

<nav id="btns-me" class="level is-mobile" ...>
<div class="modal" id="modal-edit" ...>

{% endif %}

글을 적는 포스팅 칸과 모달도 내 프로필에서만 보이게 해줍니다.

{% if status %}

<section id="section-post" class="section" ...>

{% endif %}

 

2. 프로필 수정 기능 만들기

프로필 수정 모달에서 이름을 바꾸거나 새 프로필 사진을 업로드하는 경우 파일을 받아 저장해야 함. 프로필 업데이트 후 페이지 새로고침.

 

3. 해당 사용자 글만 보이게 하기

-포스팅 카드들 중에 해당 사용자 글만 보이게.

-아까 만든 get_posts() 함수에 username을 변수로 받도록 바꾸기.

function get_posts(username) {
    if (username==undefined) {  //작성하지 않았다면
        username=""  // 그 유저네임 그대로 쓰겠다
    }
    $("#post-box").empty()
    $.ajax({
        type: "GET",
        url: `/get_posts?username_give=${username}`,
        data: {},
        success: function (response) {
            if (response["result"] == "success") {
                ...
            }
        }
    })
}

이제 서버 쪽에서 username을 받아 해당 사용자의 글만 가져오도록 바꾸기.

@app.route("/get_posts", methods=['GET'])
def get_posts():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        username_receive = request.args.get("username_give")
        if username_receive=="": //빈칸이면 했던 방식 그대로
            posts = list(db.posts.find({}).sort("date", -1).limit(20))
        else: //그렇지 않으면 유저네임 검색
            posts = list(db.posts.find({"username":username_receive}).sort("date", -1).limit(20))
        for post in posts:
            post["_id"] = str(post["_id"])
            post["count_heart"] = db.likes.count_documents({"post_id": post["_id"], "type": "heart"})
            post["heart_by_me"] = bool(db.likes.find_one({"post_id": post["_id"], "type": "heart", "username": my_username}))        return jsonify({"result": "success", "msg": "포스팅을 가져왔습니다.", "posts": posts})
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))

4. og태그, favicon 넣기

        마지막 웹서비스도 Open Graph 태그와 favicon을 넣어서 완성해줍시다. ogimg는 로그인화면 배너를 스크린샷을 찍고 favicon은 아래 파일을 다운 받아 static 폴더에 넣어줄게요.
        HTML 파일들의 head에 링크 첨부.