5/9일 항해를 시작하고 나서 첫 번째 주가 끝났다.
시작부터 웹페이지를 구상하라는 미션이 주어져서 굉장히 놀랬지만
정말 좋은 팀원들을 만나서 잘 끝낼 수 있었다. (사랑해요 우리 13조 :D!!!)
학교를 졸업하고 시간이 너무 지났기 때문에 머리 속에 남아있는 것들이
너무 희미해졌고, 사회생활하면서 간간히 공부했던 것들은 너무 가벼워서 사실상 백지와 다름없었다.
너무나도 다행인건 아직까지는 코드리딩이 됐기 때문에 구글링을 통해서 여러 코드를 보고 습득한 지식들이 있었다는 것? JWT의 개념과 원리를 이해했고, 코드로 활용하는 것까지 습득했다.
Header :
alg 서명 암호화할 방식
typ 토큰 유형
payload :
서버에 보낼 데이터, 일반적인 유저의 고유 ID 값, 유효기간 포함
즉, 서버와 클라이언트가 주고받는 시스템에서 실제로 사용될 유저의 정보를 의미
payload는 아래 세가지 방식으로 나뉨
[Registered claims / Public claims / Private claims]
(key-value 형식으로 이루어진 한 쌍의 정보를 Claim 으로 지칭)
Registered claims은 아래로 구성됨
payload에 정의된 key 값은 iss(issuer : 발행자) / exp(expireation time : 만료시간) / sub(subject : 제목) / iat(issued At : 발행시간) / jti(JWI ID)로 구성이 되어있다. 필수는 아니지만 exp/iat/jti는 기재되는 것이 좋음
Public은 사용자가 정의할 수 있는 클레임으로 공개용 정보전달에 사용되고
Private는 당사자들 간의 정보를 공유하기 위해 만들어진 사용자 지정 클레임, 외부에 공개되도 상관이 없지만 해당 유저를 특정할 수 있는 정보를 담음
우리는 미리 정의된 클레임(Registered claims)을 사용하였고
로그인 서버하시는 분이 클라에 적용하기 너무 쉽게 코드를 구상해주셨기 때문에
어렵지 않게 값을 적용할 수 있었다.
Signature :
로그인 정보는 헤더에 저장하게 되는데, 위 Signature이 헤더에 등록된 인코딩 값과 클라로부터 받아오는 인코딩 값을 합친 후 비밀키로 해쉬를 하여 생성한다.
사실 로그인서버는 팀원분이 맡아서 너무 훌륭하게 해주셨기 때문에
코드를 짜거나, 자세한 코드리딩은 아직 못해봤지만...(시간이 너무 촉박했어..)
이번 주 코테를 미리 풀고 자습기간에라도 뜯어봐서 TIL 꼭꼭 TIL하자
내 머리속에 있는 API개념은 남이 만들어놓은 기능을 가져다가 쓰는 것! 이미 만들어놓은 것을 활용하는것! 정도였는데 (ㅎ......)
서버까지 같이 구현하면서 구체적으로 API가 뭔지 알게되었다.
내가... API를 만들다니.......
프로젝트를 하면서 총 5개의 API를 만들었다.
이번에 SSR(server-side rendering)이라는 듣도보도 못한..(코린이) 기법으로 API와 클라를 구성해야해서 진짴ㅋㅋㅋ 너무 난감했다.
우선 내가 이해한 SSR기법은 클라에서 서버로 어떤 기능 또는 데이터를 요청하면 서버에서 데이터가공+html까지 랜더링을 해 클라로 반환하는 방식이여서, API를 작업할 때 데이터 이동과 가공을 최대한 고려해 만들었다. 출력하는 HTML을 Jinja 방식으로 데이터를 받아 작성한 후 페이지 이동을 하였음...
제대로 이해하는거.. 맞겠지...? :D 머리가 굳었나봐 딱딱해
나는 게시판 등록/수정/삭제를 맡았고, 사용자로부터 데이터를 입력 받는 페이지가 2개 필요했기 때문에 총5개의 API를 작성했다.
@app.route('/update')
@app.route('/update')
def view_update():
user_id = checklogin()
postings = list(db.dog.find({'user_id': user_id}, {'_id': False}))
my_dog=postings[0]['dog_name']
if user_id == 'logout':
return jsonify({'msg': '로그 아웃 되었습니다. '})
else:
return render_template("update.html", my_dog=my_dog)
@app.route('/modify/<key>')
def view_modify(key):
user_id = checklogin()
postings = list(db.board.find({'post_id': int(key)}, {'_id': False}))
# print(postings[0]["comment"])
if user_id == 'logout':
return jsonify({'msg': '로그 아웃 되었습니다. '})
else:
return render_template("modify.html", postings=postings)
처음에는 임의값으로 테스트를 진행했는데 JWT를 쓰면서 이렇게 간단하게 유저 정보를 받아올 수 있다니.... 신세계를 경험했다...
팀원분도 값을 잘 받아올 수 있게 함수를 잘 짜주셨어.. 행ㅋ벅ㅋ
페이지에 진입하면 사용자의 쿠키에 저장된 토큰이 있는지 비교해서 로그인시에만 글을 작성할 수 있도록 예외처리를 해주었고
로그인이 되어있는 User의 ID 값을 받아서 DB를 호출, 필요한 값을 리턴해줬다.
우리가 만든 웹페이지는 '내 강아지 자랑하기'가 테마이고
나는 글쓰기/수정하기 페이지를 로드할 때 사용자가 굳이 강아지 정보는 입력하지 않게, '내 강아지가 요기잉네?' 라는 느낌을 받게하고 싶었다.
페이지 로딩 시 사용자 데이터를 시각적으로 로드하는 것에 초점을 맞췄다. 그게 제일 애를 먹었고 ㅠㅠㅋㅋㅋㅋㅋ
db.dog.find({'user_id': user_id}, {'_id': False})
db.board.find({'post_id': int(key)}, {'_id': False})
DB 호출하는 함수에 너무 무지해서 처음에는 for문을 돌렸다....
아니 솔직히 있을 것 같았는데 귀찮았기도 했고.. 틀리면 어떡하나 겁도 났고
안전한 방법을 선택하고 싶었던 것 같음
기능을 생각보다 빨리 구현해서 코드를 좀 업데이트 해볼까면서 수정했는데
진짜 현타왔었다. 저거 배웠잖음
게으른 무식이 손들어요.... 너는 for문/if문과 작별할 필요가 좀 있어
아 맞네 그리고 수정은 홈에서 key값으로 수정페이지 로드하게 구상했음
요로케
{% if doglist.user_id == userid %}
<button onclick="modify_posting('{{ doglist.post_id }}')"
class="btn btn-outline-primary">수정하기
</button>
<button onclick="delete_posting('{{ doglist.post_id }}')"
class="btn btn-outline-primary">삭제하기
</button>
{% endif %}
진자놈... 부들부들.....
function modify_posting(post_id) {
window.location.href = `/modify/${post_id}`
}
사실 두 API는 거의 비슷하기 때문에...
등록 기준으로 기록하고 수정하기는 차이점만 넣자.
function post_update() {
let doggy_name = $('#dog_name').val()
let posting_doggy = $('#boast_doggy').val()
let form_data = new FormData()
let file = $('#file')[0].files[0]
if ($('#file')[0].files[0] == null) {
alert("첨부 파일은 필수!");
$("#isFile").focus();
return;
}
form_data.append("file_give", file)
form_data.append("doggyname_give", doggy_name)
form_data.append("postingdoggy_give", posting_doggy)
$.ajax({
type: "POST",
url: "/api/post/update",
data: form_data,
cache: false,
contentType: false,
processData: false,
success: function (response) {
alert(response["msg"])
window.location.href="/"
}
});
}
@app.route('/api/post/update', methods=['POST'])
def post_update():
user_id = checklogin()
doggyname_receive = request.form["doggyname_give"]
postingdoggy_receive = request.form["postingdoggy_give"]
file = request.files["file_give"]
extension = file.filename.split('.')[-1]
today = datetime.datetime.now()
mytime = today.strftime('%Y-%m-%d-%H-%M-%S')
reg_date = today.strftime('%Y-%m-%d')
post_id = today.strftime('%Y%m%d%H%M%S')
filename = f'file-{mytime}'
save_to = f'static/{filename}.{extension}'
file.save(save_to)
doc = {
'img1': f'{filename}.{extension}',
'comment': postingdoggy_receive,
'dog_name': doggyname_receive,
'user_id': user_id,
'likes_cnt': 0,
'post_id': int(post_id),
'reg_date': reg_date,
'mod_date': "",
}
if user_id == 'logout':
return jsonify({'msg': '로그 아웃 되었습니다. '})
else:
db.board.insert_one(doc)
return jsonify({'msg': '저장완료'})
등록하기는 post 형식으로 값을 받아서 ajax로 데이터를 서버로 넘겨주었다.
데이터 입/출력은 사전미션 때 해봐서 어렵지 않았는데
데이터 예외처리가 진짜 애를 먹었다 ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ
망할 메소드가 없습니다 에러...................!!!
그리고 아직까지도 해결하지 못했다 저건... 꼭 알아내고 말리라
결국에는
if ($('#file')[0].files[0] == null) {
alert("첨부 파일은 필수!");
$("#isFile").focus();
return;
}
아몰랑 안넣으면 못쓰게 할꺼양 ㅎㅋㅋㅋ
최악의 방법을 사용했다... 반성해라 나...
아! 그리고 사전미션때는 서버에 데이터 저장 시, 데이터이름 중복을 생각하지 않았다... 이번에 date를 이용해서 고유값을 만들도록 하였는데
이미지가 중복되었을 때에도 현업에서도 이런 방법을 사용하는지...? 오우 이거 기술 멘토때 물어볼껄 왜 이제와서 생각이 난거야! 찾아봐야겠다..
숙제가 참 많군..
db.board.update_one({'post_id': int(post_id_receive)},
{'$set': {'comment': posting_doggy_receive}})
db.board.update_one({'post_id': int(post_id_receive)},
{'$set': {'img1': f'{filename}.{extension}'}})
db.board.update_one({'post_id': int(post_id_receive)},
{'$set': {'mod_date': modi_date}})
수정은 update를 쓴게 차이점인데
post_id ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 날 undefined의 늪에서 허우적거리게 만들었다.
분명히 좋아요 기능 구현한 팀원분이랑 충분히 논의해서 int값으로 설정하고 "꼭 형변환하세요 ^.^" 라는 소리까지 했는데
빠가야... 왜 저걸 형변환 안해서 삽질을 내핵까지 하고 자빠졌니
#### 형변환!!!!! 자료형!!!!!!! 으악!!!!!!!!!!!! 진짜 이러지말자!!!!
4. 게시물 삭제
function delete_posting(post_id) {
let postid = post_id
$.ajax({
type: "POST",
url: '/api/post/delete',
data: {
post_id_give: postid
},
success: function (response) {
alert(response["msg"])
window.location.href = "/"
}
});
}
@app.route('/api/post/delete', methods=['POST'])
def post_delete():
post_id_receive = int(request.form["post_id_give"])
print(post_id_receive)
db.board.delete_one({'post_id': post_id_receive})
return jsonify({'result': 'success', 'msg': '게시물이 삭제되었습니다'})
삭제는 따로 HTML을 안만들고 그냥 홈에 있는 버튼액션을 통해서 바로 삭제되게끔 구현했다.
삭제는 어려운게 없어성... TIL에 남길게 없네..
이번 첫주는 사실 계란으로 바위치기여서 진짴ㅋㅋㅋ 너무 힘들었다
평균 4시간 잤나... 항해 끝날 때까지 화장하겠다는 내 각오가 3일만에 무너졌다.
화장이 뭐야 ㅎㅎㅎ 무슨 사치를 부리는거야 너는 ㅎㅎㅎㅎㅎ
항해 끝날 때까지 화장하기 -> 항해 끝날 때까지 머리감기로 바뀌었다.
사실 가이드가 구체적이지도 않고, 자바스크립트도 처음이고 파이썬도 처음이고 진자돜ㅋㅋㅋㅋㅋ 처음이여서 너무 난감했다.
팀원분들이랑도 회의하고 고민하고 난리도 아니였는데...
왠지 그래서 더 돈독해진 것 같구...♥
♥사랑해요 13조♥
이 난리통에서 그래도 일주일만에 이정도로 성장한거면 일취월장했지 장해
이렇게 빡세게 하니까 오히려 코테기간이 여유롭다.. 나....
다음주에 얼른 코테 마무리하고 자바스크립트 스터디나 빡시게 해야겠다.
우리 이뽀 이번주도 너무너무 고생했어 :D
잘 버티고 있고 열심히 해줘서 너무 예쁘고 고맙다 나 :)