스파르타 코딩 클럽 이노베이션 캠프 1주 차 풀 스택 웹 개발로 프런트엔드와 백엔드의 두 가지 기술을 다양하게 사용해봄으로써 흰색 도화지에 선을 그리는 연습을 했다. 내가 이번 프로젝트에서 완벽히 마스터했다는 느낌보다는 아! 이런 게 코딩이고 이렇게 만들어서 네이버, 다음 등 웹사이트가 만들어지는구나라고 이해를 할 수 있었다.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
본론으로 넘어가서
이번 1주차에는 로그인, 회원가입 기능에 대해서 배워봤으며 JWT ( JSON Web Token)을 사용해보았다.
간략하게 JWT에 대해 설명을 하자면 서버와 클라이언트 간에 정보를 주고받을 때 서버는 별도의 인증 없이 Http 리퀘스트 헤더에 JSON 토큰을 넣은 후 서버는 별도의 인증 과정없이 헤더에 포함되어 있는 JWT 정보를 통해 인증한다.
간략한 JWT 의 진행 과정으로
1. 클라이언트 사용자가 아이디, 패스워드를 통하여 웹서비스를 인증.
2. 서버에 서명된 JWT를 생성하여 클라이언트에 응답
3. 클라이언트가 서버에 데이터를 추가적으로 요구할 경우 JWT를 HTTP Header에 첨부
4. 서버에서 클라이언트로부터 온 JWT를 검증한다.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
두 번째로 API , 1주 차 프로젝트 간에 ajax, jinja2를 사용하여 크롤링 기능을 구현했다.
Ajax & CSR (Client side rendering)
Asynchronous JavaScript and XML이라는 이름에서 보듯 '비동기적인' 방식으로 디스플레이(보이는 곳)의 뒤편에서 서버와 데이터를 주고받기 때문에 페이지를 다시 로딩하지 않고 변경이 이루어질 수 있다. DOM에 큰 뼈대인 테이블이 먼저 로드되고 Json 형식의 파일이 서버로부터 넘어와 나머지 부분들을 채워준다. HTML 파일 head부분에 데이터의 표현방식과 페이지 상에서의 위치를 명시해준다.
Jinja2 템플릿 엔진 & SSR(Server side rendering)
Python을 이용하는 웹템플릿 엔진으로 Django에서 영감을 받아 제작된 만큼 이와 높은 유사성을 보인다고 한다. 대신 그보다 더 나아가서 실행이 'sandbox'에서 이루어지도록 제작되어 보안성을 높였다. 템플릿 엔진을 사용하게 되면 코드가 간단하게 쓰일 수 있는데, html 파일에 template을 jinja 문법으로 구축하고 Flask 같은 서버 라이브러리로 데이터를 렌더링해 줘서 {{ }}안에 채워지면 되기 때문이다. 유연하고 재사용 가능한 템플릿 기반으로 동작하므로 블로그나 온라인 쇼핑몰 등을 만들 때 유용하게 쓰일 수 있다. HTML 파일 body에 데이터가 들어와야 하는 위치가 {{}}로 표시된다.
CSR : 사용자의 요청에 따라 필요한 부분만 응답받아 렌더링 하는 방식
SSR : 서버로부터 완전하게 만들어진 HTML 파일을 받아서 페이지 전체를 렌더링 하는 방식이다.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
다음으로 1주 차 프로젝트 간에 사용한 app.py, login.html. main_page.html 코드이다. \
from flask import Flask, render_template, request, jsonify, redirect, url_for
import jwt
import datetime
import hashlib
from werkzeug.utils import secure_filename
from datetime import datetime, timedelta
app = Flask(__name__)
app.config["TEMPLATES_AUTO_RELOAD"] = True
app.config['UPLOAD_FOLDER'] = "./static/profile_pics"
SECRET_KEY = 'SPARTA'
from pymongo import MongoClient
client = MongoClient('mongodb+srv://test:sparta@cluster0.gswdo8n.mongodb.net/Cluster0?retryWrites=true&w=majority')
db = client.dbsparta
db.devsbook_bestseller.drop()
db.devsbook_front.drop()
db.devsbook_back.drop()
import requests
from bs4 import BeautifulSoup
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
# 베스트 셀러 크롤링
data = requests.get(
'http://www.kyobobook.co.kr/bestSellerNew/bestseller.laf?mallGb=KOR&linkClass=33&range=0&kind=1&orderClick=DCb',
headers=headers)
bestseller_soup = BeautifulSoup(data.text, 'html.parser')
bestseller_books = bestseller_soup.select('#main_contents > ul > li')
for bestseller_book in bestseller_books:
booktitle = bestseller_book.select_one('div.detail > div.title > a > strong').text
price = bestseller_book.select_one('div.price > strong').text
image = bestseller_book.select_one('div.cover > a > img')
doc = {
'booktitle': booktitle,
'price': price,
'image': image['src']
}
db.devsbook_bestseller.insert_one(doc)
# 프론트엔드북 크롤링
front_data = requests.get(
'https://search.kyobobook.co.kr/web/search?vPstrKeyWord=react&searchPcondition=1&searchCategory=%EA%B5%AD%EB%82%B4%EB%8F%84%EC%84%9C@KORBOOK@@%EC%BB%B4%ED%93%A8%ED%84%B0/IT@33&collName=KORBOOK&from_CollName=%EC%A0%84%EC%B2%B4@UNION&vPstrTab=PRODUCT&from_coll=KORBOOK¤tPage=1&orderClick=LIZ',
headers=headers)
front_soup = BeautifulSoup(front_data.text, 'html.parser')
front_books = front_soup.select('#search_gallery > tr > td')
for front_book in front_books:
booktitle = front_book.select_one('div > div.title > a > strong').text
price = front_book.select_one('div > div.price > div.sell_price > strong').text
image = front_book.select_one('div > div.image > div.cover > a > img')
doc = {
'booktitle': booktitle,
'price': price,
'image': image['src']
}
db.devsbook_front.insert_one(doc)
back_data = requests.get(
'https://search.kyobobook.co.kr/web/search?vPstrKeyWord=spring&searchPcondition=1&searchCategory=%EA%B5%AD%EB%82%B4%EB%8F%84%EC%84%9C@KORBOOK@@%EC%BB%B4%ED%93%A8%ED%84%B0/IT@33&collName=KORBOOK&from_CollName=%EC%A0%84%EC%B2%B4@UNION&searchOrder=0&vPstrTab=PRODUCT&from_coll=KORBOOK¤tPage=1&orderClick=LIZ',
headers=headers)
back_soup = BeautifulSoup(back_data.text, 'html.parser')
back_books = back_soup.select('#search_gallery > tr > td')
# 백엔드북 크롤링
for back_book in back_books:
booktitle = back_book.select_one('div > div.title > a > strong').text
price = back_book.select_one('div > div.price > div.sell_price > strong').text
image = back_book.select_one('div > div.image > div.cover > a > img')
doc = {
'booktitle': booktitle,
'price': price,
'image': image['src']
}
db.devsbook_back.insert_one(doc)
@app.route('/')
def home():
token_receive = request.cookies.get('mytoken')
try:
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
bestseller_list = list(db.devsbook_bestseller.find({}, {'_id': False}))
front_list = list(db.devsbook_front.find({}, {'_id': False}))
back_list = list(db.devsbook_back.find({}, {'_id': False}))
return render_template('main_page.html',
bestseller_list=bestseller_list, front_list=front_list, back_list=back_list
)
except jwt.ExpiredSignatureError:
return redirect(url_for("login", msg="로그인 시간이 만료되었습니다."))
except jwt.exceptions.DecodeError:
return redirect(url_for("login", msg="로그인 정보가 존재하지 않습니다."))
@app.route('/login')
def login():
msg = request.args.get("msg")
return render_template('login.html', msg=msg)
@app.route('/user/<username>')
def user(username):
# 각 사용자의 프로필과 글을 모아볼 수 있는 공간
token_receive = request.cookies.get('mytoken')
try:
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
status = (username == payload["id"]) # 내 프로필이면 True, 다른 사람 프로필 페이지면 False
user_info = db.users.find_one({"username": username}, {"_id": False})
return render_template('user.html', user_info=user_info, status=status)
except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
return redirect(url_for("home"))
@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()
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')
return jsonify({'result': 'success', 'token': token})
# 찾지 못하면
else:
return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})
# 회원가입 서버
@app.route('/sign_up/save', methods=['POST'])
def sign_up():
username_receive = request.form['username_give']
password_receive = request.form['password_give']
password_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
doc = {
"username": username_receive, # 아이디
"password": password_hash, # 비밀번호
"profile_name": username_receive, # 프로필 이름 기본값은 아이디
"profile_pic": "", # 프로필 사진 파일 이름
"profile_pic_real": "profile_pics/profile_placeholder.png", # 프로필 사진 기본 이미지
"profile_info": "" # 프로필 한 마디
}
db.users.insert_one(doc)
return jsonify({'result': 'success'})
# 아이디 중복확인
@app.route('/sign_up/check_dup', methods=['POST'])
def check_dup():
username_receive = request.form['username_give']
exists = bool(db.users.find_one({"username": username_receive}))
return jsonify({'result': 'success', 'exists': exists})
@app.route('/update_profile', methods=['POST'])
def save_img():
token_receive = request.cookies.get('mytoken')
try:
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
# 프로필 업데이트
return jsonify({"result": "success", 'msg': '프로필을 업데이트했습니다.'})
except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
return redirect(url_for("home"))
@app.route('/posting', methods=['POST'])
def posting():
token_receive = request.cookies.get('mytoken')
try:
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
# 포스팅하기
return jsonify({"result": "success", 'msg': '포스팅 성공'})
except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
return redirect(url_for("home"))
@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'])
# 포스팅 목록 받아오기
return jsonify({"result": "success", "msg": "포스팅을 가져왔습니다."})
except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
return redirect(url_for("home"))
@app.route('/update_like', methods=['POST'])
def update_like():
token_receive = request.cookies.get('mytoken')
try:
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
# 좋아요 수 변경
return jsonify({"result": "success", 'msg': 'updated'})
except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
return redirect(url_for("home"))
@app.route("/comment", methods=["POST"])
def comment_post():
comment_receive = request.form['comment_give']
comment_list = list(db.comment.find({}, {'_id': False}))
count = len(comment_list) + 1
doc = {
'num': count,
'comment': comment_receive,
'done': 0
}
db.comment.insert_one(doc)
return jsonify({'msg': '저장 완료!'})
@app.route("/comment/done", methods=["POST"])
def comment_done():
num_receive = request.form['num_give']
db.comment.update_one({'num': int(num_receive)}, {'$set': {'done': 1}})
return jsonify({'msg': '삭제 완료!'})
@app.route("/comment", methods=["GET"])
def comment_get():
comment_list = list(db.comment.find({}, {'_id': False}))
return jsonify({'comments': comment_list})
if __name__ == '__main__':
app.run('0.0.0.0', port=5000, debug=True)
app.py
<!doctype html>
<html lang="en">
<head>
<!-- Webpage Title -->
<title>Dev's book</title>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bulma CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<!-- Font Awesome CSS -->
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
<!-- 배너 css-->
<style>
body {
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("https://images.unsplash.com/photo-1507842217343-583bb7270b66?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1590&q=80");
min-height: 100vh;
}
.section {
padding: 1rem 1.5rem;
max-width: 750px;
margin: auto;
}
.title {
font-weight: 800;
font-size: 5rem;
}
.subtitle {
font-size: 2rem;
}
.is-sparta {
color: #623306 !important;
}
/*로그인박스 CSS*/
.button.is-sparta {
background-color: #623306;
border-color: transparent;
color: #fff !important;
}
.button.is-sparta.is-outlined {
background-color: transparent;
border-color: #623306;
color: #623306 !important;
}
.help {
color: gray;
}
</style>
<script>
// {% if msg %}
// alert("{{ msg }}")
// {% endif %}
function sign_in() {
let username = $("#input-username").val()
let password = $("#input-password").val()
if (username == "") {
$("#help-id-login").text("아이디를 입력해주세요.")
$("#input-username").focus()
return;
} else {
$("#help-id-login").text("")
}
if (password == "") {
$("#help-password-login").text("비밀번호를 입력해주세요.")
$("#input-password").focus()
return;
} else {
$("#help-password-login").text("")
}
$.ajax({
type: "POST",
url: "/sign_in",
data: {
username_give: username,
password_give: password
},
success: function (response) {
if (response['result'] == 'success') {
$.cookie('mytoken', response['token'], {path: '/'});
window.location.replace("/")
} else {
alert(response['msg'])
}
}
});
}
//회원가입
function sign_up() {
let username = $("#input-username").val()
let password = $("#input-password").val()
let password2 = $("#input-password2").val()
console.log(username, password, password2)
if ($("#help-id").hasClass("is-danger")) {
alert("아이디를 다시 확인해주세요.")
return;
} else if (!$("#help-id").hasClass("is-success")) {
alert("아이디 중복확인을 해주세요.")
return;
}
if (password == "") {
$("#help-password").text("비밀번호를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
$("#input-password").focus()
return;
} else if (!is_password(password)) {
$("#help-password").text("비밀번호의 형식을 확인해주세요. 영문과 숫자 필수 포함, 특수문자(!@#$%^&*) 사용가능 8-20자").removeClass("is-safe").addClass("is-danger")
$("#input-password").focus()
return
} else {
$("#help-password").text("사용할 수 있는 비밀번호입니다.").removeClass("is-danger").addClass("is-success")
}
if (password2 == "") {
$("#help-password2").text("비밀번호를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
$("#input-password2").focus()
return;
} else if (password2 != password) {
$("#help-password2").text("비밀번호가 일치하지 않습니다.").removeClass("is-safe").addClass("is-danger")
$("#input-password2").focus()
return;
} else {
$("#help-password2").text("비밀번호가 일치합니다.").removeClass("is-danger").addClass("is-success")
}
$.ajax({
type: "POST",
url: "/sign_up/save",
data: {
username_give: username,
password_give: password
},
success: function (response) {
alert("회원가입을 축하드립니다!")
window.location.replace("/login")
}
});
}
function toggle_sign_up() {
$("#sign-up-box").toggleClass("is-hidden")
$("#div-sign-in-or-up").toggleClass("is-hidden")
$("#btn-check-dup").toggleClass("is-hidden")
$("#help-id").toggleClass("is-hidden")
$("#help-password").toggleClass("is-hidden")
$("#help-password2").toggleClass("is-hidden")
}
//정규표현식 추가
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}$/;
return regExp.test(asValue);
}
//아이디 중복확인 클라이언트
function check_dup() {
let username = $("#input-username").val()
console.log(username)
if (username == "") {
$("#help-id").text("아이디를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
$("#input-username").focus()
return;
}
if (!is_nickname(username)) {
$("#help-id").text("아이디의 형식을 확인해주세요. 영문과 숫자, 일부 특수문자(._-) 사용 가능. 2-10자 길이").removeClass("is-safe").addClass("is-danger")
$("#input-username").focus()
return;
}
$("#help-id").addClass("is-loading")
$.ajax({
type: "POST",
url: "/sign_up/check_dup",
data: {
username_give: username
},
success: function (response) {
if (response["exists"]) {
$("#help-id").text("이미 존재하는 아이디입니다.").removeClass("is-safe").addClass("is-danger")
$("#input-username").focus()
} else {
$("#help-id").text("사용할 수 있는 아이디입니다.").removeClass("is-danger").addClass("is-success")
}
$("#help-id").removeClass("is-loading")
}
});
}
</script>
</head>
<!-- 배너 -->
<body>
<section class="hero is-white">
<div class="hero-body has-text-centered" style="padding-bottom:1rem;margin:auto;" >
<h1 class="title is-sparta">DEV-BOOKS</h1>
<h3 class="subtitle is-sparta">코딩하는 사람들을 위한 도서 플랫폼</h3>
</div>
</section>
<!--로그인 박스-->
<section class="section">
<div class="container">
<div class="box" style="max-width: 480px;margin:auto">
<article class="media">
<div class="media-content">
<div class="content">
<div class="field has-addons">
<div class="control has-icons-left" style="width:100%">
<input id="input-username" class="input" type="text" placeholder="아이디">
<span class="icon is-small is-left"><i class="fa fa-user"></i></span>
</div>
<div id="btn-check-dup" class="control is-hidden">
<button class="button is-sparta" onclick="check_dup()">중복확인</button>
</div>
</div>
<p id="help-id" class="help is-hidden" a>아이디는 2-10자의 영문과 숫자와 일부 특수문자(._-)만 입력 가능합니다.</p>
<p id="help-id-login" class="help is-danger"></p>
<div class="field">
<div class="control has-icons-left">
<input id="input-password" class="input" type="password" placeholder="비밀번호">
<span class="icon is-small is-left"><i class="fa fa-lock"></i></span>
</div>
<p id="help-password" class="help is-hidden">영문과 숫자 조합의 8-20자의 비밀번호를 설정해주세요. 특수문자(!@#$%^&*)도
사용 가능합니다.</p>
</div>
</div>
<div id="div-sign-in-or-up" class="has-text-centered">
<nav class="level is-mobile">
<button class="level-item button is-sparta" onclick="sign_in()">
로그인
</button>
</nav>
<hr>
<h4 class="mb-3">아직 회원이 아니라면</h4>
<nav class="level is-mobile">
<button class="level-item button is-sparta is-outlined"
onclick="toggle_sign_up()">
회원가입하기
</button>
</nav>
</div>
<div id="sign-up-box" class="is-hidden">
<div class="mb-5">
<div class="field">
<div class="control has-icons-left" style="width:100%">
<input id="input-password2" class="input" type="password"
placeholder="비밀번호 재입력">
<span class="icon is-small is-left"><i class="fa fa-lock"></i></span>
</div>
<p id="help-password2" class="help is-hidden">비밀번호를 다시 한 번 입력해주세요.</p>
</div>
</div>
<nav class="level is-mobile">
<button class="level-item button is-sparta" onclick="sign_up()">
회원가입
</button>
<button class="level-item button is-sparta is-outlined" onclick="toggle_sign_up()">
취소
</button>
</nav>
</div>
</div>
</article>
</div>
</div>
</section>
</body>
</html>
login.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"></script>
<title>Dev's book</title>
<link href="https://fonts.googleapis.com/css2?family=Gowun+Dodum&display=swap" rel="stylesheet">
<style>
* {
font-family: 'Gowun Dodum', sans-serif;
}
a{
color: inherit;
}
/*main background box*/
.mytitle {
height: 250px;
width: 100%;
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("https://images.unsplash.com/photo-1507842217343-583bb7270b66?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1590&q=80");
background-position: center;
background-size: cover;
color: yellowgreen;
text-align: center;
}
/*title*/
.mytitle h1 a {
font-size: 60px;
font-weight: 700;
text-align: center;
color: yellowgreen;
line-height: 190px;
}
/*menu*/
.mytitle .nav-bar button {
width: 150px;
height: 50px;
font-size: 22px;
font-weight: 500;
overflow: hidden;
text-align: center;
background-color: transparent;
color: yellowgreen;
border: 2px solid yellowgreen;
float: right;
margin: 0px 10px;
}
.mytitle .nav-bar button:hover {
border: 2px solid yellowgreen;
}
/*section box*/
.book-list{
width: 100%;
overflow: hidden;
}
.best_box, .front_box, .back_box{
width: 99%;
overflow: hidden;
border: 3px solid yellowgreen;
overflow: hidden;
text-align: center;
line-height: 0.1px;
margin-top: 10px;
padding-left: 100px;
margin-left: 10px;
}
.card{
width: 230px;
overflow: hidden;
height: 420px;
float: left;
display: inline-block;
margin: 30px 20px;
text-align: center;
}
.card-body{
width: 100%;
height: 100px;
}
.card img{
width: 100%;
height: 300px;
}
.card-title{
font-size: 18px;
font-weight: 800;
padding-bottom: 10px;
}
.mypic > h1 {
font-size: 60px;
font-weight: 600;
text-align: center;
margin: 40px 0px;
color: yellowgreen;
}
.mybox {
width: 60%;
padding: 20px 20px 20px 20px;
box-shadow: 0 0 10px 0 gray;
margin: 30px auto;
}
.mycomment {
display: flex;
flex-direction: row;
align-items: center;
padding: 0px 0px 0px 0px;
}
.mycomment > input {
width: 70%;
}
.mybox > li {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-bottom: 10px;
min-height: 48px;
}
.mybox > li > h2 {
max-width: 75%;
font-size: 20px;
font-weight: 500;
margin-right: auto;
margin-bottom: 0;
}
.mybox > li > h2.done {
text-decoration: line-through
}
</style>
<script>
$(document).ready(function () {
show_comment();
if (sessionStorage.getItem("reload_comment") === "1") {
Click_community()
sessionStorage.clear()
}
});
function show_comment() {
$.ajax({
type: "GET",
url: "/comment",
data: {},
success: function (response) {
let rows = response['comments']
for (let i = 0; i < rows.length; i++) {
let comment = rows[i]['comment']
let num = rows[i]['num']
let done = rows[i]['done']
let temp_html = ``
if (done == 0) {
temp_html = `<li>
<h2> 📚${comment}</h2>
<button onclick="done_comment(${num})" type="button" class="btn btn-outline-success">삭제</button>
</li>`
}
$('#comment-list').append(temp_html)
}
}
});
}
function save_comment() {
let comment = $('#comment').val()
$.ajax({
type: "POST",
url: "/comment",
data: {comment_give: comment},
success: function (response) {
alert(response["msg"])
sessionStorage.setItem("reload_comment", "1")
window.location.reload()
}
});
}
function done_comment(num) {
$.ajax({
type: "POST",
url: "/comment/done",
data: {num_give: num},
success: function (response) {
sessionStorage.setItem("reload_comment", "1")
window.location.reload()
alert(response["msg"])
}
});
}
function Click_bestseller_button() {
$('#front_box').addClass('is-hidden')
$('#best_box').removeClass('is-hidden')
$('#back_box').addClass('is-hidden')
$('#community_box').addClass('is-hidden')
}
function Click_front_button() {
$('#front_box').removeClass('is-hidden')
$('#best_box').addClass('is-hidden')
$('#back_box').addClass('is-hidden')
$('#community_box').addClass('is-hidden')
}
function Click_back_button() {
$('#front_box').addClass('is-hidden')
$('#best_box').addClass('is-hidden')
$('#back_box').removeClass('is-hidden')
$('#community_box').addClass('is-hidden')
}
function Click_community() {
$('#front_box').addClass('is-hidden')
$('#best_box').addClass('is-hidden')
$('#back_box').addClass('is-hidden')
$('#community_box').removeClass('is-hidden')
}
</script>
</head>
<body>
<div class="mytitle">
<h1><a href="http://localhost:5000"> 📚Dev's BOOK📚</a></h1>
<div class="nav-bar">
<ul>
<li><button onclick="Click_bestseller_button()">bestseller</button></li>
<li><button onclick="Click_front_button()">F.E</button></li>
<li><button onclick="Click_back_button()">B.E</button></li>
<li><button onclick="Click_community()">Community</button></li>
</ul>
</div>
</div>
<div class="middle">
<div class="book-list">
<div class="best_box" id="best_box">
{% for bestseller_book in bestseller_list %}
{% set booktitle = bestseller_book['booktitle'] %}
{% set price = bestseller_book['price'] %}
{% set image = bestseller_book['image'] %}
<div class="col">
<div class="card">
<a href="http://www.kyobobook.co.kr/bestSellerNew/bestseller.laf?mallGb=KOR&linkClass=33&range=0&kind=1&orderClick=DCb">
<img src={{ image }} class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">{{ booktitle }}</h5>
<p class="card-text">{{ price }}</p>
</div>
</a>
</div>
</div>
<br>
{% endfor %}
</div>
<div class="front_box is-hidden" id="front_box">
{% for front_book in front_list %}
{% set booktitle = front_book['booktitle'] %}
{% set price = front_book['price'] %}
{% set image = front_book['image'] %}
<div class="col">
<div class="card">
<a href="https://search.kyobobook.co.kr/web/search?vPstrKeyWord=react&searchPcondition=1&searchCategory=%EA%B5%AD%EB%82%B4%EB%8F%84%EC%84%9C@KORBOOK@@%EC%BB%B4%ED%93%A8%ED%84%B0/IT@33&collName=KORBOOK&from_CollName=%EC%A0%84%EC%B2%B4@UNION&vPstrTab=PRODUCT&from_coll=KORBOOK¤tPage=1&orderClick=LIZ">
<img src={{ image }} class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">{{ booktitle }}</h5>
<p class="card-text">{{ price }}</p>
</div>
</a>
</div>
</div>
{% endfor %}
</div>
<div class="back_box is-hidden" id="back_box">
{% for back_book in back_list %}
{% set booktitle = back_book['booktitle'] %}
{% set price = back_book['price'] %}
{% set image = back_book['image'] %}
<div class="col">
<div class="card">
<a href="https://search.kyobobook.co.kr/web/search?vPstrKeyWord=spring&searchPcondition=1&searchCategory=%EA%B5%AD%EB%82%B4%EB%8F%84%EC%84%9C@KORBOOK@@%EC%BB%B4%ED%93%A8%ED%84%B0/IT@33&collName=KORBOOK&from_CollName=%EC%A0%84%EC%B2%B4@UNION&searchOrder=0&vPstrTab=PRODUCT&from_coll=KORBOOK¤tPage=1&orderClick=LIZ">
<img src={{ image }} class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">{{ booktitle }}}</h5>
<p class="card-text">{{ price }}</p>
</div>
</a>
</div>
</div>
{% endfor %}
</div>
<div class="community_box is-hidden" id="community_box">
<div class="mypic">
<h1>community</h1>
</div>
<div class="mybox">
<div class="mycomment">
<input id="comment" class="form-control" type="text" placeholder="책을 추천해 주세요.">
<button onclick="save_comment()" type="button" class="btn btn-outline-success" style="margin-left: 30px;">작성</button>
</div>
</div>
<div class="mybox" id="comment-list">
</div>
</div>
</div>
</div>
</body>
</html>
main_page.html
'이노베이션 캠프 WIL' 카테고리의 다른 글
6 주차 WIL (미니프로젝트, 협업 ) (0) | 2022.07.31 |
---|---|
5주차 WIL(CORS) (0) | 2022.07.24 |
4주차 WIL(ORM, SQL, MVC) (0) | 2022.07.17 |
3주차 WlL(DI, IoC, Bean) (0) | 2022.07.10 |
2주차 WIL (객체 지향 프로그래밍 , JVM) (0) | 2022.07.03 |
댓글