[항해99] 웹개발 플러스 4주차
스파르타코딩클럽 웹개발 플러스 4주차
- Bulma로 예쁜 웹사이트를 만든다.
- 로그인 기능을 구현할 수 있다.
회원가입, 로그인, 패스워드, 패스워드 확인, 피드 올리기, 좋아요 별표
프로필 클릭하면 프로필 수정, 로그아웃까지
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
로그인 & 회원가입 페이지 ▼
- 기본 화면으로 로그인 화면 보이기
- '회원가입하기' 버튼을 클릭하면 회원가입 화면으로 바뀌기
- '취소' 버튼을 클릭하면 로그인 화면으로 돌아오기
- 회원가입
- 아이디 & 비밀번호 형식 확인
- 아이디 중복 확인
- DB에 아이디와 비밀번호 저장하여 회원가입 & 로그인 화면으로 전환
- 로그인
- 아이디 & 비밀번호 입력 확인
- 서버로 POST 요청을 보내 가입 정보가 존재하는지 확인
- 회원일 경우 토큰 부여
메인 페이지 ▼
- 모든 사람의 포스트를 시간 역순으로 보여주기
- 각 포스트에 좋아요/좋아요 취소 가능
- 좋아요 누른 포스트는 찬 하트로 보여주기
- 포스팅 칸에 내 프로필 사진 보여주기
- 프로필 사진 누르면 프로필 페이지로 이동
- 포스팅 칸 클릭하면 포스팅 모달 띄우기
- 포스팅하기 버튼 클릭하면 포스트 DB에 저장
- 새로고침하여 포스트 목록 다시 띄우기
프로필 페이지 ▼
- 해당 사용자의 포스트만 시간 역순으로 보여주기
- 내 프로필이라면 프로필 수정 & 로그아웃 버튼 보여주기
- 내 프로필일 때만 포스팅 칸 보여주기
- 프로필 수정 버튼 클릭하면 프로필 수정 모달 보여주기
- 기존의 저장되어 있는 값 보여주기
- 수정 시 DB에 업데이트하고 새로고침해서 변경사항 적용
- 로그아웃 버튼 클릭하면 토큰 삭제하고 로그인 페이지로 이동
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 프로필 페이지 모습 만들기 - 기능 만들기
- 내 프로필에서만 프로필 수정기능 보이게 하기
프로필 수정 & 로그아웃 버튼은 내 프로필에 들어갔을 때만 보여야겠죠? 서버에서 보내준 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에 링크 첨부.