촬리의늘솔길

웹개발+4주차 본문

✍~2022/WEB

웹개발+4주차

리촬리 2021. 8. 1. 01:09

Bulma로 웹 사이트 꾸미기

: 부트스트랩과 비슷한 기능임

: 무료 css 프레임워크

: 순수 css로 이루어짐 -> 커스터마이징이 쉬움

: 사용법이 부트스트랩보다 훨씬 직관적이고, 시간 단축가능

: 부트스트랩의기본 모양보다 벌마의 기본모양이 이쁨

 

https://bulma.io/documentation/

 

Bulma: Free, open source, and modern CSS framework based on Flexbox

Bulma is a free, open source CSS framework based on Flexbox and built with Sass. It's 100% responsive, fully modular, and available for free.

bulma.io

 

Hero 배너 : 전면타이틀

<section class="hero is-primary is-bold is-medium">
    <div class="hero-body">
        <div class="container">
            <h1 class="title">
                Hero title
            </h1>
            <h2 class="subtitle">
                Hero subtitle
            </h2>
        </div>
    </div>
</section>

Section : 영역을 나눠줌 ,css요소를 추가해줄 수 있음

<section class="section">
    <div class="container">
        <h1 class="title">Section</h1>
        <h2 class="subtitle">
            A simple container to divide your page into <strong>sections</strong>, like the one you're
            currently reading
        </h2>
    </div>
</section>
.section {
    width: 600px;
    max-width: 100vw;
    margin: auto;
}

Box와 Media : media는 개별 피드를 나타내는 클래스, box는 테두리와 그림자를 이용해서 하나의 카드를 만듦

<div class="box">
    <article class="media">
        <div class="media-left">
            <figure class="image is-64x64">
                <img src="https://bulma.io/images/placeholders/128x128.png" alt="Image">
            </figure>
        </div>
        <div class="media-content">
            <div class="content">
                <p>
                    <strong>John Smith</strong> <small>@johnsmith</small> <small>31m</small>
                    <br>
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean efficitur sit
                    amet massa fringilla egestas. Nullam condimentum luctus turpis.
                </p>
            </div>
            <nav class="level is-mobile">
                <div class="level-left">
                    <a class="level-item" aria-label="reply">
                            <span class="icon is-small">
                              <i class="fas fa-reply" aria-hidden="true"></i>
                            </span>
                    </a>
                    <a class="level-item" aria-label="retweet">
                            <span class="icon is-small">
                              <i class="fas fa-retweet" aria-hidden="true"></i>
                            </span>
                    </a>
                    <a class="level-item" aria-label="like">
                            <span class="icon is-small">
                              <i class="fas fa-heart" aria-hidden="true"></i>
                            </span>
                    </a>
                </div>
            </nav>
        </div>
    </article>
</div>

Button

<button class="button is-primary is-outlined is-large is-fullwidth is-loading"></button>

회원가입 기능!

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

  • 우리가 회원가입에 사용할 해시함수 SHA256은 어떤 길이의 입력값을 넣어도 항상 256바이트의 결과값이 나옵니다!
  • 추가적으로 동일한 입력값은 항상 같은 결과값이 나오고, 입력값은 조금이라도 달라지면 완전히 다른 값이 나오게 됩니다. 그리고 결과값을 통해 입력값을 알아내는 것이 불가능하다는 세 가지 특징이 있습니다!
  • 복호화가 쉽지않다는 소리이군
  • pw가 저장된 db에 접근을 해도 암호화가 되어있어 알기어려움

 

 

robo3T에 저장된 변환된 pw 모습

 


로그인기능

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

  • 예를 들어, 로그인 기능을 생각해보면 사용자가 로그인하면 서버에서 회원임을 인증하는 토큰을 넘겨줌으로써 이후 회원만 접근할 수 있는 서비스 영역에서 신분을 확인하는 데 쓰일 수 있습니다.

 

request.cookies 에서 토큰을 가져옴

토큰 리시브 한것을 개인 키를 이용하여 jwt decode 즉 복호화를 해줌.

cookies라는 것이 무엇일까?

#################################
##  HTML을 주는 부분             ##
#################################
@app.route('/')
def home():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        user_info = db.user.find_one({"id": payload['id']})
        return render_template('index.html', nickname=user_info["nick"])
    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('/register')
def register():
    return render_template('register.html')

 


app.py에서 payload는 로그인 만료에 대한 기능임.

payload를 패스워드와 마찬가지로 암호화를 해줘야함.

암호화 될 때는 서버만의 특별한 키로 암호화를 해줌

서비스마다 비밀 키를 세팅 가능

복호화 키겠지?

클라이언트가 jwt토큰을 보여줄때 키를 이용해서 암호화 해독해서 아이디와 비번 확인.

 

# [로그인 API]
# id, pw를 받아서 맞춰보고, 토큰을 만들어 발급합니다.
@app.route('/api/login', methods=['POST'])
def api_login():
    id_receive = request.form['id_give']
    pw_receive = request.form['pw_give']

    # 회원가입 때와 같은 방법으로 pw를 암호화합니다.
    pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest()

    # id, 암호화된pw을 가지고 해당 유저를 찾습니다.
    result = db.user.find_one({'id': id_receive, 'pw': pw_hash})

    # 찾으면 JWT 토큰을 만들어 발급합니다.
    if result is not None:
        # JWT 토큰에는, payload와 시크릿키가 필요합니다.
        # 시크릿키가 있어야 토큰을 디코딩(=풀기) 해서 payload 값을 볼 수 있습니다.
        # 아래에선 id와 exp를 담았습니다. 즉, JWT 토큰을 풀면 유저ID 값을 알 수 있습니다.
        # exp에는 만료시간을 넣어줍니다. 만료시간이 지나면, 시크릿키로 토큰을 풀 때 만료되었다고 에러가 납니다.
        payload = {
            'id': id_receive,
            'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=5) #로그인 유효 정보
        }
        token = jwt.encode(payload, SECRET_KEY, algorithm='HS256').decode('utf-8')

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

login.html

아이디와 패스워드 값을 입력해서 서버로 post요청을 보냄

 <script>
            {% if msg %}
                alert("{{ msg }}")
            {% endif %}
            // ['쿠키'라는 개념에 대해 알아봅시다]
            // 로그인을 구현하면, 반드시 쿠키라는 개념을 사용합니다.
            // 페이지에 관계없이 브라우저에 임시로 저장되는 정보입니다. 키:밸류 형태(딕셔너리 형태)로 저장됩니다.
            // 쿠키가 있기 때문에, 한번 로그인하면 네이버에서 다시 로그인할 필요가 없는 것입니다.
            // 브라우저를 닫으면 자동 삭제되게 하거나, 일정 시간이 지나면 삭제되게 할 수 있습니다.
            function login() {
                $.ajax({
                    type: "POST",
                    url: "/api/login",
                    data: {id_give: $('#userid').val(), pw_give: $('#userpw').val()},
                    success: function (response) {
                        if (response['result'] == 'success') {
                            // 로그인이 정상적으로 되면, 토큰을 받아옵니다.
                            // 이 토큰을 mytoken이라는 키 값으로 쿠키에 저장합니다.
                            $.cookie('mytoken', response['token']);

                            alert('로그인 완료!')
                            window.location.href = '/'
                        } else {
                            // 로그인이 안되면 에러메시지를 띄웁니다.
                            alert(response['msg'])
                        }
                    }
                })
            }

        </script>

cookie는 브라우저 자체 데이터 베이스이다. 

항상 쿠키값은 같이 보내짐

토큰이 있음 -> 로그인이 되었음을 확인함

클라이언트는 쿠키에 그 값을 저장해놓음.

 

쿠키라는것은 디바이스의 특정 브라우저에 저장되는 정보임.

쿠키를 통해 로그인이 되었다는 사실이 유지가 됨.

 

 

 

플라스크 서버에서 로그인 기능 구현하기

  • 로그인 시, 비밀번호를 같은 방법으로 암호화한 후, DB에서 해당 아이디와 비밀번호를 갖는 회원이 있는지 찾습니다. 회원 정보가 없는 경우 실패 메시지를 보내고, 찾은 경우 아이디와 토큰 만료 시간을 저장하는 토큰을 만들어 넘겨줍니다.
  • 로그인 성공 메시지를 받으면 건네받은 토큰을 쿠키로 저장하여 만료되기 전까지 갖고 있으면서, API 요청을 보낼 때마다 회원임을 확인받습니다. (마음을 확인받는 연인같다..ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ)
  • 로그아웃 시 해당 토큰을 삭제합니다.

 


Sweeter 페이지 만들어보깅

1. 

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

로그인 or 회원가입  누를 수 있도록하기

 

조건을 두어서 회원가입을 할 수 있도록

 

2.

 

모든사람들의 피드를 다 보여주기, 좋아요, 하트, 별 누르기, 로그인 날짜, 프로필사진

 

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

같은 로그인 페이지에서 

3. 작성페이지 구현하기

4. 프로필/ 프로필 수정 페이지 구현

 

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

5. 로그아웃 (쿠키제거)


1. 일단 로그인 배너  (login.html)

hero로 만들기 ( 벌마 )

 

<style>태그

body {
    background-color: RGBA(232, 52, 78, 0.2);
    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: #e8344e !important;
}

<body>태그

<section class="hero is-white">
    <div class="hero-body has-text-centered" style="padding-bottom:1rem;margin:auto;">
        <h1 class="title is-sparta">SWEETER</h1>
        <h3 class="subtitle is-sparta">세상을 달달하게</h3>
    </div>
</section>

구글폰트 추가

https://fonts.google.com/?subset=korean 

 

Google Fonts

Making the web more beautiful, fast, and open through great typography

fonts.google.com


로그인과 회원가입 박스 만들기

js를 이용해서 버튼을 눌렀을때 특정요소들이 보이게 세팅해보기

 

로그인css

.button.is-sparta {
    background-color: #e8344e;
    border-color: transparent;
    color: #fff !important;
}

.button.is-sparta.is-outlined {
    background-color: transparent;
    border-color: #e8344e;
    color: #e8344e !important;
}

.help {
    color: gray;
}

로그인 html

<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">
                                <button class="button is-sparta" onclick="check_dup()">중복확인</button>
                            </div>

                        </div>
                        <p id="help-id" class="help">아이디는 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">영문과 숫자 조합의 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">
                        <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">비밀번호를 다시 한 번 입력해주세요.</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>

 

 

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

 

    1. 로그인/회원가입 토글 기능 만들기
    • 이제 로그인 중인지, 회원가입 중인지 상황에 맞게 각 요소들을 숨겼다, 드러냈다 하는 기능을 만들어봅시다.
    • Bulma에서는 is-hidden이라는 클래스를 이용해서 요소를 숨길 수 있습니다. CSS로는 아래와 같이 정의되어있어요..
      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") }​
    • 이렇게 한 번에 토글할 수 있는 함수를 만들어 회원가입하기 버튼과 취소 버튼에 연결해주면 끝! 그런다음에 회원가입 버튼 누르면 숨겼던거 드러내기!
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")
}

 

회원가입 페이지 기능 만들기

    1. 회원가입 기능 만들기
    • 회원가입할 때는 입력 받은 값들이 형식에 맞는지 우선 확인해야겠죠?
      • 아이디: 영문과 숫자, 일부 특수문자(._-)만 사용 가능, 2-10자 길이. 영문 무조건 포함
      • 비밀번호: 영문, 숫자는 1개 씩 무조건 포함, 일부 특수문자 사용 가능, 8-20자 길이
      • 비밀번호 확인: 비밀번호와 일치
    • 이렇게 복잡한 조건을 확인할 때는 '정규표현식(Regular Expressions)'을 이용하여 비교하는 것이 좋습니다. 형식을 확인하여 결과를 참/거짓으로 반환하는 함수를 정의하면 편리하겠죠?
      • 아이디, 비밀번호 정규표현식
      • /^(?=.*[a-zA-Z])[-a-zA-Z0-9_.]{2,10}$/; 이부분이 정규표현식
      • 소괄호가 필수포함, 대괄호는 일반적인거
      •  
      • 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); }
    • 그리고 아이디는 다른 사람과 겹치면 안되기 때문에 중복확인을 해주어야겠죠? 서버로 POST 요청을 보내 아이디가 존재하는지 확인해주세요.
      • 아이디 중복확인 클라이언트
      • 중복여부를 확인하기 위해 help에다가 success클래스를 추가해주고, 나중에 회원가입 버튼을 눌렀을때 help에 해당 클래스가 있는지 여부 확인을 통해 없다면 중복확인 하라고 alert띄워줌
      • 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")
        
                }
            });
        }
      • 아이디 중복확인 서버
      • 존재하지 않다면 false값이 들어감 (bool이용했기 때문에)
      • @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})
    • 이제 이 조건들을 만족할 때만 회원가입 POST 요청을 보내도록 함수를 짜면 끝!
      •  회원가입 클라이언트
      • 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")
                }
            });
        
        }
      • 회원가입 서버
      • @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'})

오예 들어가있음

 


09. 로그인 페이지 기능 만들기

    1. 로그인 기능 만들기
    • 로그인 입력값 확인은 훨씬 간단합니다. 값을 입력했는지만 확인하고 바로 로그인 POST 요청을 보내주세요.
      • 로그인 클라이언트
      • 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'])
                    }
                }
            });
        }
      • 로그인 서버
        @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').decode('utf-8')
        
                return jsonify({'result': 'success', 'token': token})
            # 찾지 못하면
            else:
                return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})
    뒤의 기능들을 테스트하면서 개발하려면 한 아이디로 로그인해서 다른 사용자의 프로필 페이지를 봐야할 때가 있으니 테스트 용 아이디를 2개 이상 만들어주세요 ㅎㅎ

 

아이디 패스워드를 서버에 전달

서버는 매칭되는 유저 있는지 확인 (있으면 -> 토큰만들어서 클라이언트에 던져줌)

클라이언트는 이 토큰을 쿠키에 저장해서 로그인!

쿠키에는 키:밸류 형태로 저장됨

mytoken이라는 키가 실제 서버가 발행한 jwt토큰을 저장하고있다고 생각하고 , 이후부터는 서버한테 요청을 보낼때마다 쿠키가 딸려들어감

 

토큰확인법!


메인 페이지 모습 만들기

  • CSS 내용 중 다른 페이지에서도 쓸만한 것들은 static 폴더 안에 mystyle.css 파일을 만들어 옮겨주시고, head에는 링크를 달아주세요. 전체적으로 통일감 있는 웹사이트를 만들 수 있겠죠?
    • [코드스니펫] - CSS 파일 링크
      <link href="{{ url_for('static', filename='mystyle.css') }}" rel="stylesheet">
    • [코드스니펫] - CSS 파일 내용
      body {
          background-color: RGBA(232, 52, 78, 0.2);
          min-height: 100vh;
      		padding-top: 3.25rem;
      }
      
      .section {
          padding: 1rem 1.5rem;
          max-width: 750px;
          margin: auto;
      }
      
      .is-sparta {
          color: #e8344e !important;
      }
      
      .button.is-sparta {
          background-color: #e8344e;
          border-color: transparent;
          color: #fff !important;
      }
      
      .button.is-sparta.is-outlined {
          background-color: transparent;
          border-color: #e8344e;
          color: #e8344e !important;
      }
      
      .modal-content {
          width: 600px;
          max-width: 80%;
      }
      
      input::-webkit-calendar-picker-indicator {
          display: none;
      }
      
      .image img {
          object-fit:cover;
          width:100%;
          height:100%;
      }
    Bulma의 클래스들을 커스터마이징해서 쓰기 때문에 Bulma의 링크보다 아래에 넣어주어야합니다.

 

내비게이션 바

  • 로고를 클릭하면 메인 페이지로 갈 수 있는 내비게이션 바를 만들어봅시다.
  • [코드스니펫] - 내비게이션 바
    <nav class="navbar is-fixed-top is-white" role="navigation" aria-label="main navigation">
        <div class="navbar-brand">
            <a class="navbar-item" href="/">
                <img src="{{ url_for('static', filename='logo.png') }}">
                <strong class="is-sparta"
                        style="font-family: 'Stylish', sans-serif;font-size: larger;">SWEETER</strong>
            </a>
        </div>
    </nav>

포스팅 칸

<section class="section">
    <article class="media">
        <figure class="media-left" style="align-self: center">
            <a class="image is-32x32" href="#">
                <img class="is-rounded" src="{{ url_for("static", filename="profile_pics/profile_placeholder.png") }}">
            </a>
        </figure>
        <div class="media-content">
            <div class="field">
                <p class="control">
	                  <input id="input-post" class="input is-rounded" placeholder="무슨 생각을 하고 계신가요?"
	                         onclick='$("#modal-post").addClass("is-active")'>                </p>
            </div>
        </div>
    </article>
</section>

 

사용자정보 보내주기

user_info = db.users.find_one({"username": payload["id"]})
        return render_template('index.html', user_info=user_info)

실제 유저의정보 읽어와서 다시 클라이언트에게 던져주는거

그래서 클라이언트에서 진자템플릿 이용해서 유저 정보 마음껏 이용가능

 

 

포스팅모달

  • 실제로 글을 적을 수 있는 포스팅 모달은 우선 모습을 만들고 숨겨놓았다가 포스팅 칸을 클릭하면 나타납니다. 바깥 배경 영역이나 X표, 취소 버튼을 클릭하면 사라집니다. 나타나고 사라지는 것은 is-active 클래스를 이용해 제어가 가능합니다.
    • [코드스니펫] - 포스팅 모달
      <div class="modal" id="modal-post">
                      <div class="modal-background" onclick='$("#modal-post").removeClass("is-active")'></div>
                      <div class="modal-content">
                          <div class="box">
                              <article class="media">
                                  <div class="media-content">
                                      <div class="field">
                                          <p class="control">
                                              <textarea id="textarea-post" class="textarea"
                                                        placeholder="무슨 생각을 하고 계신가요?"></textarea>
                                          </p>
                                      </div>
                                      <nav class="level is-mobile">
                                          <div class="level-left">
      
                                          </div>
                                          <div class="level-right">
                                              <div class="level-item">
                                                  <a class="button is-sparta" onclick="post()">포스팅하기</a>
                                              </div>
                                              <div class="level-item">
                                                  <a class="button is-sparta is-outlined"
                                                     onclick='$("#modal-post").removeClass("is-active")'>취소</a>
                                              </div>
                                          </div>
                                      </nav>
                                  </div>
                              </article>
                          </div>
                      </div>
                      <button class="modal-close is-large" aria-label="close"
                              onclick='$("#modal-post").removeClass("is-active")'></button>
                  </div>

글쓰는 입력창이 생김 (이걸 모달이라고 함)

input(무슨생각 하고계신가요)창을누르면 onclick함수가 발동해서 modal에 isactive클래스 추가해줌

그래서 정상작동

 

 

포스팅카드

<section class="section">
    <div id="post-box" class="container">
        <div class="box">
            <article class="media">
                <div class="media-left">
                    <a class="image is-64x64" href="#">
                        <img class="is-rounded"
                             src={{ url_for("static", filename="profile_pics/profile_placeholder.png") }} alt="Image">
                    </a>
                </div>
                <div class="media-content">
                    <div class="content">
                        <p>
                            <strong>홍길동</strong> <small>@username</small> <small>10분 전</small>
                            <br>
                            글을 적는 칸
                        </p>
                    </div>
                    <nav class="level is-mobile">
                        <div class="level-left">
                            <a class="level-item is-sparta" aria-label="heart" onclick="toggle_like('', 'heart')">
                                <span class="icon is-small"><i class="fa fa-heart" aria-hidden="true"></i></span>&nbsp;<span class="like-num">2.7k</span>
                            </a>
                        </div>

                    </nav>
                </div>
            </article>
        </div>

    </div>
</section>

메인 페이지 기능 만들기 - 포스팅

글과 현재 시각을 문자열로 받아 POST 요청을 보내고, 저장에 성공하면 모달을 닫고 새로고침해줍니다.

서버에서는 글과 현재 시각을 받아 로그인한 사용자의 정보로부터 아이디, 이름, 프로필 사진을 같이 저장합니다.

 

포스트를 저장했으니 이번에는 받아와봅시다.

서버에서는 DB에서 최근 20개의 포스트를 받아와 리스트로 넘겨줍니다. 나중에 좋아요 기능을 쓸 때 각 포스트를 구분하기 위해서 MongoDB가 자동으로 만들어주는 _id 값을 이용할 것인데요, ObjectID라는 자료형이라 문자열로 변환해주어야합니다.

# 포스팅 목록 받아오기, 가장최근에 작성된거부터 들고옴 20개 리미트로
        posts = list(db.posts.find({}).sort("date", -1).limit(20))
        for post in posts:
            #_id는 각 포스트마다의 고유한 값
            post["_id"] = str(post["_id"])
        return jsonify({"result": "success", "msg": "포스팅을 가져왔습니다."})

 

 

포스팅시간

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()}일`
}

초 -> 분 -> 시간 -> 일 

60을 이용해서! 구함


좋아요 기능 만들기

- 어떤 사람이 좋아요

- 어떤 글을 좋아요

- 좋아요의 종류

- 끔/켬 확인

 

서버

DB에 저장할 때는 1) 누가 2) 어떤 포스트에 3) 어떤 반응을 남겼는지 세 정보만 넣으면 되고, 좋아요인지, 취소인지에 따라 해당 도큐먼트를 insert_one()을 할지 delete_one()을 할지 결정해주어야합니다.

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})

토큰에서 아이디 부분만 꺼내오면, 누가 좋아요 했는지 바로알 수 있음.

 

 

클라이언트

  • API에서 요구하는 데이터가 사용자 정보, 포스트 아이디, 좋아요/좋아요 취소, 아이콘 종류입니다.
  • 여기에서 하트를 누른 사람의 정보는 로그인 정보에서 받아왔으므로 나머지 3개만 데이터로 보내주면 됩니다.
  • 좋아요인지, 좋아요 취소인지는 아이콘의 클래스가 fa-heart인지 fa-heart-o인지로 알 수 있습니다.
  • 업데이트에 성공하면 아이콘의 클래스를 바꾸고 좋아요 숫자도 업데이트해줍니다.

 

좋아요 갯수 표시하기

    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": payload['id']}))
    '내'가 좋아요했는지 안했는지 여부

클라이언트에서는 이 정보를 받아 찬 하트("fa-heart")를 보여줄 것인지, 빈 하트("fa-heart-o")를 보여줄 것인지 결정합니다.

let class_heart = ""
if (post["heart_by_me"]) {
    class_heart = "fa-heart"
} else {
    class_heart = "fa-heart-o"
}

이걸 한줄로 처리할 수 있음. 삼항연산자

let class_heart = post['heart_by_me'] ? "fa-heart": "fa-heart-o"

삼항연산자 구조

변수 = 조건 ? 참일 때 값 : 거짓일 때 값

 


좋아요 숫자 표시 설정

function num2str(count) {
    if (count > 10000) {
        return parseInt(count / 1000) + "k"
    }
    if (count > 500) {
        return parseInt(count / 100) / 10 + "k"
    }
    if (count == 0) {
        return ""
    }
    return count
}

 

 

js따로 빼기

<script>
        $(document).ready(function () {
            get_posts()
        })
        {# 모달에 입력한 값을 불러옴 #}

        function toggle_like(post_id, type) {
            console.log(post_id, type)
            let $a_like = $(`#${post_id} a[aria-label='heart']`)
            let $i_like = $a_like.find("i")
            if ($i_like.hasClass("fa-heart")) {
                $.ajax({
                    type: "POST",
                    url: "/update_like",
                    data: {
                        post_id_give: post_id,
                        type_give: type,
                        action_give: "unlike"
                    },
                    success: function (response) {
                        console.log("unlike")
                        $i_like.addClass("fa-heart-o").removeClass("fa-heart")
                        $a_like.find("span.like-num").text(num2str(response["count"]))
                    }
                })
            } else {
                $.ajax({
                    type: "POST",
                    url: "/update_like",
                    data: {
                        post_id_give: post_id,
                        type_give: type,
                        action_give: "like"
                    },
                    success: function (response) {
                        console.log("like")
                        $i_like.addClass("fa-heart").removeClass("fa-heart-o")
                        $a_like.find("span.like-num").text(num2str(response["count"]))
                    }
                })

            }
        }

        function post() {
            let comment = $("#textarea-post").val()
            let today = new Date().toISOString()
            $.ajax({
                type: "POST",
                url: "/posting",
                data: {
                    comment_give: comment,
                    date_give: today
                },
                success: function (response) {
                    $("#modal-post").removeClass("is-active")
                    {# 모달을 닫는다 #}
                    window.location.reload()
                }
            })
        }

        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()}일`
        }

        function num2str(count) {
            if (count > 10000) {
                return parseInt(count / 1000) + "k"
            }
            if (count > 500) {
                return parseInt(count / 100) / 10 + "k"
            }
            if (count == 0) {
                return ""
            }
            return count
        }

        function get_posts(username) {
            if (username == undefined) {
                username = ""
            }
            $("#post-box").empty()
            $.ajax({
                type: "GET",
                url: "/get_posts",
                data: {},
                success: function (response) {
                    if (response["result"] == "success") {
                        let posts = response["posts"]
                        for (let i = 0; i < posts.length; i++) {
                            let post = posts[i]
                            let time_post = new Date(post["date"])
                            {# _id를 기억해야 제대로 불러옴 #}
                            let time_before = time2str(time_post)
                            let class_heart = post['heart_by_me'] ? "fa-heart" : "fa-heart-o"
                            let count_heart = post["count_heart"]
                            let html_temp = `<div class="box" id="${post["_id"]}">
                                        <article class="media">
                                            <div class="media-left">
                                                <a class="image is-64x64" href="/user/${post['username']}">
                                                    <img class="is-rounded" src="/static/${post['profile_pic_real']}"
                                                         alt="Image">
                                                </a>
                                            </div>
                                            <div class="media-content">
                                                <div class="content">
                                                    <p>
                                                        <strong>${post['profile_name']}</strong> <small>@${post['username']}</small> <small>${time_before}</small>
                                                        <br>
                                                        ${post['comment']}
                                                    </p>
                                                </div>
                                                <nav class="level is-mobile">
                                                    <div class="level-left">
                                                        <a class="level-item is-sparta" aria-label="heart" onclick="toggle_like('${post['_id']}', 'heart')">
                                                            <span class="icon is-small"><i class="fa ${class_heart}"
                                                                                           aria-hidden="true"></i></span>&nbsp;<span class="like-num">${num2str(post["count_heart"])}</span>
                                                        </a>
                                                    </div>

                                                </nav>
                                            </div>
                                        </article>
                                    </div>`
                            $("#post-box").append(html_temp)
                        }
                    }
                }
            })
        }


    </script>

프로필 페이지

기본 코드

<body class="has-navbar-fixed-top">
  <nav class="navbar is-fixed-top is-white" role="navigation" aria-label="main navigation">
      <div class="navbar-brand">
          <a class="navbar-item" href="/">
              <img src="{{ url_for('static', filename='logo.png') }}">
              <strong class="is-sparta"
                      style="font-family: 'Stylish', sans-serif;font-size: larger;">SWEETER</strong>
          </a>
      </div>
  </nav>
  <section class="section">
      <article class="media">
          <figure class="media-left" style="align-self: center">
              <a class="image is-32x32" href="/user/{{ user_info.username }}">
                  <img class="is-rounded" src="{{ url_for('static', filename=user_info.profile_pic_real) }}">
              </a>
          </figure>
          <div class="media-content">
              <div class="field">
                  <p class="control">
                      <input id="input-post" class="input is-rounded" placeholder="무슨 생각을 하고 계신가요?"
                             onclick='$("#modal-post").addClass("is-active")'>
                  </p>
              </div>
          </div>
      </article>
      <div class="modal" id="modal-post">
          <div class="modal-background" onclick='$("#modal-post").removeClass("is-active")'></div>
          <div class="modal-content">
              <div class="box">
                  <article class="media">
                      <div class="media-content">
                          <div class="field">
                              <p class="control">
                                  <textarea id="textarea-post" class="textarea"
                                            placeholder="무슨 생각을 하고 계신가요?"></textarea>
                              </p>
                          </div>
                          <nav class="level is-mobile">
                              <div class="level-left">

                              </div>
                              <div class="level-right">
                                  <div class="level-item">
                                      <a class="button is-sparta" onclick="post()">포스팅하기</a>
                                  </div>
                                  <div class="level-item">
                                      <a class="button is-sparta is-outlined"
                                         onclick='$("#modal-post").removeClass("is-active")'>취소</a>
                                  </div>
                              </div>
                          </nav>
                      </div>
                  </article>
              </div>
          </div>
          <button class="modal-close is-large" aria-label="close"
                  onclick='$("#modal-post").removeClass("is-active")'></button>
      </div>

  </section>
  <section class="section">
      <div id="post-box" class="container">
          <div class="box">
              <article class="media">
                  <div class="media-left">
                      <a class="image is-64x64" href="#">
                          <img class="is-rounded"
                               src={{ url_for("static", filename="profile_pics/profile_placeholder.png") }} alt="Image">
                      </a>
                  </div>
                  <div class="media-content">
                      <div class="content">
                          <p>
                              <strong>홍길동</strong> <small>@username</small> <small>10분 전</small>
                              <br>
                              글을 적는 칸
                          </p>
                      </div>
                      <nav class="level is-mobile">
                          <div class="level-left">
                              <a class="level-item is-sparta" aria-label="heart"
                                 onclick="toggle_like('', 'heart')">
                                  <span class="icon is-small"><i class="fa fa-heart"
                                                                 aria-hidden="true"></i></span>&nbsp;<span
                                      class="like-num">2.7k</span>
                              </a>
                          </div>

                      </nav>
                  </div>
              </article>
          </div>

      </div>
  </section>
</body>

 

프로필 수정 기능

<nav id="btns-me" class="level is-mobile" style="margin-top:2rem">
    <a class="button level-item has-text-centered is-sparta" aria-label="edit"
       onclick='$("#modal-edit").addClass("is-active")'>
        프로필 수정&nbsp;&nbsp;&nbsp;<span class="icon is-small"><i class="fa fa-pencil"
                                                               aria-hidden="true"></i></span>
    </a>

    <a class="button level-item has-text-centered is-sparta is-outlined" aria-label="logout"
       onclick="sign_out()">
        로그아웃&nbsp;&nbsp;&nbsp;<span class="icon is-small"><i class="fa fa-sign-out"
                                                             aria-hidden="true"></i></span>
    </a>
</nav>

 

저번과 마찬가지로 모달기능을 쓰는데 class의 형태가 isactive인지에 따라서 !

 

프로필 수정 모달도 추가해쥼

<div class="modal" id="modal-edit">
    <div class="modal-background" onclick='$("#modal-edit").removeClass("is-active")'></div>
    <div class="modal-content">
        <div class="box">
            <article class="media">
                <div class="media-content">
                    <div class="field">
                        <label class="label" for="input-name">이름</label>

                        <p class="control">

                            <input id="input-name" class="input"
                                   placeholder="홍길동" value="{{ user_info.profile_name }}">
                        </p>
                    </div>
                    <div class="field">
                        <label class="label" for="input-pic">프로필 사진</label>

                        <div class="control is-expanded">
                            <div class="file has-name">
                                <label class="file-label" style="width:100%">
                                    <input id="input-pic" class="file-input" type="file"
                                           name="resume">
                                    <span class="file-cta"><span class="file-icon"><i
                                            class="fa fa-upload"></i></span>
                                <span class="file-label">파일 선택</span>
                            </span>
                                    <span id="file-name" class="file-name"
                                          style="width:100%;max-width:100%">{{ user_info.profile_pic }}</span>
                                </label>
                            </div>

                        </div>
                    </div>
                    <div class="field">
                        <label class="label" for="textarea-about">나는 누구?</label>

                        <p class="control">

                        <textarea id="textarea-about" class="textarea"
                                  placeholder="자기소개하기">{{ user_info.profile_info }}</textarea>
                        </p>
                    </div>
                    <nav class="level is-mobile">
                        <div class="level-left">

                        </div>
                        <div class="level-right">
                            <div class="level-item">
                                <a class="button is-sparta" onclick="update_profile()">업데이트</a>
                            </div>
                            <div class="level-item">
                                <a class="button is-sparta is-outlined"
                                   onclick='$("#modal-edit").removeClass("is-active")'>취소</a>
                            </div>
                        </div>
                    </nav>
                </div>
            </article>
        </div>
    </div>
    <button class="modal-close is-large" aria-label="close"
            onclick='$("#modal-edit").removeClass("is-active")'></button>
</div>

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

ex :

{% if status %} <nav id="btns-me" class="level is-mobile" ...> <div class="modal" id="modal-edit" ...> {% endif %}

 

 

내 프로필 수정기능


완성!

더보기

myjs.js

function toggle_like(post_id, type) {
    console.log(post_id, type)
    let $a_like = $(`#${post_id} a[aria-label='${type}']`)
    let $i_like = $a_like.find("i")

    if ($i_like.hasClass("fa-heart")) {
        $.ajax({
            type: "POST",
            url: "/update_like",
            data: {
                post_id_give: post_id,
                type_give: type,
                action_give: "unlike"
            },
            success: function (response) {
                console.log("unlike")
                $i_like.addClass("fa-heart-o").removeClass("fa-heart")
                $a_like.find("span.like-num").text(num2str(response["count"]))
            }
        })
    } else if ($i_like.hasClass("fa-heart-o")) {
        $.ajax({
            type: "POST",
            url: "/update_like",
            data: {
                post_id_give: post_id,
                type_give: type,
                action_give: "like"
            },
            success: function (response) {
                console.log("like")
                $i_like.addClass("fa-heart").removeClass("fa-heart-o")
                $a_like.find("span.like-num").text(num2str(response["count"]))
            }
        })

    } else if ($i_like.hasClass("fa-star")) {
        $.ajax({
            type: "POST",
            url: "/update_like",
            data: {
                post_id_give: post_id,
                type_give: type,
                action_give: "unlike"
            },
            success: function (response) {
                console.log("like")
                $i_like.addClass("fa-star-o").removeClass("fa-star")
                $a_like.find("span.like-num").text(num2str(response["count"]))
            }
        })

    } else if ($i_like.hasClass("fa-star-o")) {
        $.ajax({
            type: "POST",
            url: "/update_like",
            data: {
                post_id_give: post_id,
                type_give: type,
                action_give: "like"
            },
            success: function (response) {
                console.log("unlike")
                $i_like.addClass("fa-star").removeClass("fa-star-o")
                $a_like.find("span.like-num").text(num2str(response["count"]))
            }
        })

    } else if ($i_like.hasClass("fa-thumbs-up")) {
        $.ajax({
            type: "POST",
            url: "/update_like",
            data: {
                post_id_give: post_id,
                type_give: type,
                action_give: "unlike"
            },
            success: function (response) {
                console.log("like")
                $i_like.addClass("fa-thumbs-o-up").removeClass("fa-thumbs-up")
                $a_like.find("span.like-num").text(num2str(response["count"]))
            }
        })

    } else if ($i_like.hasClass("fa-thumbs-o-up")) {
        $.ajax({
            type: "POST",
            url: "/update_like",
            data: {
                post_id_give: post_id,
                type_give: type,
                action_give: "like"
            },
            success: function (response) {
                console.log("unlike")
                $i_like.addClass("fa-thumbs-up").removeClass("fa-thumbs-o-up")
                $a_like.find("span.like-num").text(num2str(response["count"]))
            }
        })

    }
}

function post() {
    let comment = $("#textarea-post").val()
    let today = new Date().toISOString()
    $.ajax({
        type: "POST",
        url: "/posting",
        data: {
            comment_give: comment,
            date_give: today
        },
        success: function (response) {
            $("#modal-post").removeClass("is-active")

            window.location.reload()
        }
    })
}

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()}일`
}

function num2str(count) {
    if (count > 10000) {
        return parseInt(count / 1000) + "k"
    }
    if (count > 500) {
        return parseInt(count / 100) / 10 + "k"
    }
    if (count == 0) {
        return ""
    }
    return count
}


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") {
                let posts = response["posts"]
                for (let i = 0; i < posts.length; i++) {
                    let post = posts[i]
                    let time_post = new Date(post["date"])

                    let time_before = time2str(time_post)
                    let class_heart = post['heart_by_me'] ? "fa-heart" : "fa-heart-o"
                    let count_heart = post['count_heart']
                    let class_star = post['star_by_me'] ? "fa-star" : "fa-star-o"
                    let count_star = post['count_star']
                    let class_thumbs = post['thumbs-up_by_me'] ? "fa-thumbs-up" : "fa-thumbs-o-up"
                    let count_thumbs = post['count_thumbs-up']
                    let html_temp = `<div class="box" id="${post["_id"]}">
                                        <article class="media">
                                            <div class="media-left">
                                                <a class="image is-64x64" href="/user/${post['username']}">
                                                    <img class="is-rounded" src="/static/${post['profile_pic_real']}"
                                                         alt="Image">
                                                </a>
                                            </div>
                                            <div class="media-content">
                                                <div class="content">
                                                    <p>
                                                        <strong>${post['profile_name']}</strong> <small>@${post['username']}</small> <small>${time_before}</small>
                                                        <br>
                                                        ${post['comment']}
                                                    </p>
                                                </div>
                                                <nav class="level is-mobile">
                                                    <div class="level-left">
                                                        <a class="level-item is-sparta" aria-label="heart" onclick="toggle_like('${post['_id']}', 'heart')">
                                                            <span class="icon is-small"><i class="fa ${class_heart}"
                                                                                           aria-hidden="true"></i></span>&nbsp;<span class="like-num">${num2str(post["count_heart"])}</span>
                                                        </a>
                                                        <a class="level-item is-sparta" aria-label="star" onclick="toggle_like('${post['_id']}', 'star')">
                                                            <span class="icon is-small"><i class="fa ${class_star}"
                                                                                           aria-hidden="true"></i></span>&nbsp;<span class="like-num">${num2str(post["count_star"])}</span>
                                                        </a>
                                                        <a class="level-item is-sparta" aria-label="thumbs-up" onclick="toggle_like('${post['_id']}', 'thumbs-up')">
                                                            <span class="icon is-small"><i class="fa ${class_thumbs}"
                                                                                           aria-hidden="true"></i></span>&nbsp;<span class="like-num">${num2str(post["count_thumbs-up"])}</span>
                                                        </a>
                                                    </div>

                                                </nav>
                                            </div>
                                        </article>
                                    </div>`
                    $("#post-box").append(html_temp)
                }
            }
        }
    })
}

app.py

from pymongo import MongoClient
import jwt
import datetime
import hashlib
from flask import Flask, render_template, jsonify, request, redirect, url_for
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'

client = MongoClient('3.36.119.178', 27017, username="test", password="test")
db = client.dbsparta_plus_week4


@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({"username": payload["id"]})
        return render_template('index.html', user_info=user_info)

    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').decode('utf-8')

        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}))
    # 존재하지 않다면 false값이 들어감 (bool이용했기 때문에)
    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'])
        username = payload["id"]
        name_receive = request.form["name_give"]
        about_receive = request.form["about_give"]
        new_doc = {
            "profile_name": name_receive,
            "profile_info": about_receive
        }
        if 'file_give' in request.files:
            file = request.files["file_give"]
            #이미지의 경로만 db에 저장하고 실직적은 이미지 파일은 static에 저장
            filename = secure_filename(file.filename)
            extension = filename.split(".")[-1]
            file_path = f"profile_pics/{username}.{extension}"
            file.save("./static/"+file_path)
            new_doc["profile_pic"] = filename
            new_doc["profile_pic_real"] = file_path
        db.users.update_one({'username': payload['id']}, {'$set':new_doc})
        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'])
        user_info = db.users.find_one({"username": payload["id"]})
        comment_receive = request.form["comment_give"]
        date_receive = request.form["date_give"]
        doc = {
            "username": user_info["username"],
            "profile_name": user_info["profile_name"],
            "profile_pic_real": user_info["profile_pic_real"],
            "comment": comment_receive,
            "date": date_receive
        }
        db.posts.insert_one(doc)
        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'])
        # 포스팅 목록 받아오기, 가장최근에 작성된거부터 들고옴 20개 리미트로
        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:
            #_id는 각 포스트마다의 고유한 값
            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": payload['id']}))
            post["count_star"] = db.likes.count_documents({"post_id": post["_id"], "type": "star"})
            post["star_by_me"] = bool(db.likes.find_one({"post_id": post["_id"], "type": "star", "username": payload['id']}))
            post["count_thumbs-up"] = db.likes.count_documents({"post_id": post["_id"], "type": "thumbs-up"})
            post["thumbs-up_by_me"] = bool(db.likes.find_one({"post_id": post["_id"], "type": "thumbs-up", "username": payload['id']}))

        return jsonify({"result": "success", "msg": "포스팅을 가져왔습니다.", "posts": posts})
    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'])
        # 좋아요 수 변경
        user_info = db.users.find_one({"username": payload["id"]})
        #좋아요 토큰 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})

    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))


if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)

index.html

<!doctype html>
<html lang="en">
<head>

    <!-- Webpage Title -->
    <title>Home | SWEETER</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">
    <link href="{{ url_for('static', filename='mystyle.css') }}" rel="stylesheet">
    <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
    <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
    <meta property="og:title" content="스위터 - 세상을 달달하게"/>
    <meta property="og:description" content="mini project for Web Plus"/>
    <meta property="og:image" content="{{ url_for('static', filename='ogimg.png') }}"/>
    <!-- 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>
    <script src="{{ url_for('static', filename='myjs.js') }}"></script>
    <script>
        $(document).ready(function () {
            get_posts()
        })

    </script>

</head>
<body>
<nav class="navbar is-fixed-top is-white" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
        <a class="navbar-item" href="/">
            <img src="{{ url_for('static', filename='logo.png') }}">
            <strong class="is-sparta"
                    style="font-family: 'Stylish', sans-serif;font-size: larger;">SWEETER</strong>
        </a>
    </div>
</nav>

{#    입력하기#}
<section class="section">
    <article class="media">
        <figure class="media-left" style="align-self: center">
            {# 로그인한 유저의 프로필로 페이지로가겠다! #}
            <a class="image is-32x32" href="/user/{{ user_info.username }}">
                {# 유저마다 다른 프로필 #}
                <img class="is-rounded" src="{{ url_for('static', filename=user_info.profile_pic_real) }}">
            </a>
        </figure>
        <div class="media-content">
            <div class="field">
                <p class="control">
                    <input id="input-post" class="input is-rounded" placeholder="무슨 생각을 하고 계신가요?"
                           onclick='$("#modal-post").addClass("is-active")'></p>
            </div>
        </div>
    </article>
    <div class="modal" id="modal-post">
        {# 바깥쪽 클릭 #}
        <div class="modal-background" onclick='$("#modal-post").removeClass("is-active")'></div>
        <div class="modal-content">
            <div class="box">
                <article class="media">
                    <div class="media-content">
                        <div class="field">
                            <p class="control">
                                        <textarea id="textarea-post" class="textarea"
                                                  placeholder="무슨 생각을 하고 계신가요?"></textarea>
                            </p>
                        </div>
                        <nav class="level is-mobile">
                            <div class="level-left">

                            </div>
                            <div class="level-right">
                                <div class="level-item">
                                    <a class="button is-sparta" onclick="post()">포스팅하기</a>
                                </div>
                                <div class="level-item">
                                    <a class="button is-sparta is-outlined"
                                            {# 모달 취소버튼 #}
                                       onclick='$("#modal-post").removeClass("is-active")'>취소</a>
                                </div>
                            </div>
                        </nav>
                    </div>
                </article>
            </div>
        </div>
        <button class="modal-close is-large" aria-label="close"
                onclick='$("#modal-post").removeClass("is-active")'></button>
    </div>
</section>
<section class="section">
    <div id="post-box" class="container">
        <div class="box">
            <article class="media">
                <div class="media-left">
                    <a class="image is-64x64" href="#">
                        <img class="is-rounded"
                             src={{ url_for("static", filename="profile_pics/profile_placeholder.png") }} alt="Image">
                    </a>
                </div>
                <div class="media-content">
                    <div class="content">
                        <p>
                            <strong>홍길동</strong> <small>@username</small> <small>10분 전</small>
                            <br>
                            글을 적는 칸
                        </p>
                    </div>
                    <nav class="level is-mobile">
                        <div class="level-left">
                            <a class="level-item is-sparta" aria-label="heart" onclick="toggle_like('', 'heart')">
                                <span class="icon is-small"><i class="fa fa-heart"
                                                               aria-hidden="true"></i></span>&nbsp;<span
                                    class="like-num">2.7k</span>
                            </a>
                        </div>

                    </nav>
                </div>
            </article>
        </div>

    </div>
</section>
</body>
</html>

login.html

<!doctype html>
<html lang="en">
<head>

    <!-- Webpage Title -->
    <title>Log In | SWEETER</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">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Gowun+Batang:wght@700&family=Sunflower:wght@500&display=swap"
          rel="stylesheet">
    <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
    <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
    <meta property="og:title" content="스위터 - 세상을 달달하게"/>
    <meta property="og:description" content="mini project for Web Plus"/>
    <meta property="og:image" content="{{ url_for('static', filename='ogimg.png') }}"/>
    <!-- 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>
    <style>
        body {
            background-color: RGBA(232, 52, 78, 0.2);
            min-height: 100vh;
        }

        .section {
            padding: 1rem 1.5rem;
            max-width: 750px;
            margin: auto;
        }

        .title {
            font-weight: 800;
            font-size: 5rem;

            font-family: 'Sunflower', sans-serif;
        }

        .subtitle {
            font-size: 2rem;
            font-family: 'Gowun Batang', serif;
        }

        .is-sparta {
            color: #e8344e !important;
        }

        .button.is-sparta {
            background-color: #e8344e;
            border-color: transparent;
            color: #fff !important;
        }

        .button.is-sparta.is-outlined {
            background-color: transparent;
            border-color: #e8344e;
            color: #e8344e !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)

            {# help에 issuccess클래스가 있는지 확인~! #}
            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")
                }
            });

        }

        {#jquery  toggle class사용함#}

        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;
            }

            {#true가 아니면 이프문 돌아감#}
            if (!is_nickname(username)) {
                $("#help-id").text("아이디의 형식을 확인해주세요. 영문과 숫자, 일부 특수문자(._-) 사용 가능. 2-10자 길이").removeClass("is-safe").addClass("is-danger")
                $("#input-username").focus()
                return;
            }

            {# 서버에 중복된 자료가 있는디 db확인 요청 #}
            $("#help-id").addClass("is-loading")
            $.ajax({
                type: "POST",
                url: "/sign_up/check_dup",
                data: {
                    username_give: username
                },
                success: function (response) {
                    {# 서버에서 받은 exists값. 만약 false라면 else문이겠지 #}
                    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에다가 success클래스를 추가해주고, 나중에 회원가입 버튼을 눌렀을때 help에 해당 클래스가 있는지 여부 확인을 통해 없다면 중복확인 하라고  alert띄워줌#}
                    $("#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">SWEETER</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">아이디는 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>

user.html

<!doctype html>
<html lang="en">
<head>

    <!-- Webpage Title -->
    <title>{{ user_info.name }} | SWEETER</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">
    <link href="{{ url_for('static', filename='mystyle.css') }}" rel="stylesheet">
    <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
    <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
    <meta property="og:title" content="스위터 - 세상을 달달하게"/>
    <meta property="og:description" content="mini project for Web Plus"/>
    <meta property="og:image" content="{{ url_for('static', filename='ogimg.png') }}"/>
    <!-- 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>
    <script src="{{ url_for('static', filename='myjs.js') }}"></script>


    <script>

        $(document).ready(function () {
            get_posts('{{ user_info.username }}')
        })

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

        function update_profile() {
            let name = $('#input-name').val()
            let file = $('#input-pic')[0].files[0]
            let about = $("#textarea-about").val()
            let form_data = new FormData()
            form_data.append("file_give", file)
            form_data.append("name_give", name)
            form_data.append("about_give", about)
            console.log(name, file, about, form_data)

            $.ajax({
                type: "POST",
                url: "/update_profile",
                data: form_data,
                cache: false,
                contentType: false,
                processData: false,
                success: function (response) {
                    if (response["result"] == "success") {
                        alert(response["msg"])
                        window.location.reload()

                    }
                }
            });
        }
    </script>

</head>
<body class="has-navbar-fixed-top">
<nav class="navbar is-fixed-top is-white" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
        <a class="navbar-item" href="/">
            <img src="{{ url_for('static', filename='logo.png') }}">
            <strong class="is-sparta"
                    style="font-family: 'Stylish', sans-serif;font-size: larger;">SWEETER</strong>
        </a>
    </div>
</nav>
<section class="hero is-white">
    <div class="hero-body" style="padding-bottom:1rem;margin:auto;min-width: 400px">
        <article class="media">

            <figure class="media-left" style="align-self: center">
                <a class="image is-96x96" href="#">
                    <img class="is-rounded" src="{{ url_for('static', filename=user_info.profile_pic_real) }}">
                </a>
            </figure>
            <div class="media-content">
                <div class="content">
                    <p>
                        <strong>{{ user_info.profile_name }}</strong> <small>@{{ user_info.username }}</small>
                        <br>
                        {{ user_info.profile_info }}
                    </p>
                </div>

            </div>
        </article>
        {% if status %}
            <nav id="btns-me" class="level is-mobile" style="margin-top:2rem">
                <a class="button level-item has-text-centered is-sparta" aria-label="edit"
                   onclick='$("#modal-edit").addClass("is-active")'>
                    프로필 수정&nbsp;&nbsp;&nbsp;<span class="icon is-small"><i class="fa fa-pencil"
                                                                           aria-hidden="true"></i></span>
                </a>

                <a class="button level-item has-text-centered is-sparta is-outlined" aria-label="logout"
                   onclick="sign_out()">
                    로그아웃&nbsp;&nbsp;&nbsp;<span class="icon is-small"><i class="fa fa-sign-out"
                                                                         aria-hidden="true"></i></span>
                </a>
            </nav>
            <div class="modal" id="modal-edit">
                <div class="modal-background" onclick='$("#modal-edit").removeClass("is-active")'></div>
                <div class="modal-content">
                    <div class="box">
                        <article class="media">
                            <div class="media-content">
                                <div class="field">
                                    <label class="label" for="input-name">이름</label>

                                    <p class="control">

                                        <input id="input-name" class="input"
                                               placeholder="홍길동" value="{{ user_info.profile_name }}">
                                    </p>
                                </div>
                                <div class="field">
                                    <label class="label" for="input-pic">프로필 사진</label>

                                    <div class="control is-expanded">
                                        <div class="file has-name">
                                            <label class="file-label" style="width:100%">
                                                <input id="input-pic" class="file-input" type="file"
                                                       name="resume">
                                                <span class="file-cta"><span class="file-icon"><i
                                                        class="fa fa-upload"></i></span>
                                <span class="file-label">파일 선택</span>
                            </span>
                                                <span id="file-name" class="file-name"
                                                      style="width:100%;max-width:100%">{{ user_info.profile_pic }}</span>
                                            </label>
                                        </div>

                                    </div>
                                </div>
                                <div class="field">
                                    <label class="label" for="textarea-about">나는 누구?</label>

                                    <p class="control">

                        <textarea id="textarea-about" class="textarea"
                                  placeholder="자기소개하기">{{ user_info.profile_info }}</textarea>
                                    </p>
                                </div>
                                <nav class="level is-mobile">
                                    <div class="level-left">

                                    </div>
                                    <div class="level-right">
                                        <div class="level-item">
                                            <a class="button is-sparta" onclick="update_profile()">업데이트</a>
                                        </div>
                                        <div class="level-item">
                                            <a class="button is-sparta is-outlined"
                                               onclick='$("#modal-edit").removeClass("is-active")'>취소</a>
                                        </div>
                                    </div>
                                </nav>
                            </div>
                        </article>
                    </div>
                </div>
                <button class="modal-close is-large" aria-label="close"
                        onclick='$("#modal-edit").removeClass("is-active")'></button>
            </div>
        {% endif %}
    </div>
</section>
{% if status %}
    <section class="section">
        <article class="media">
            <figure class="media-left" style="align-self: center">
                <a class="image is-32x32" href="/user/{{ user_info.username }}">
                    <img class="is-rounded" src="{{ url_for('static', filename=user_info.profile_pic_real) }}">
                </a>
            </figure>
            <div class="media-content">
                <div class="field">
                    <p class="control">
                        <input id="input-post" class="input is-rounded" placeholder="무슨 생각을 하고 계신가요?"
                               onclick='$("#modal-post").addClass("is-active")'>
                    </p>
                </div>
            </div>
        </article>
        <div class="modal" id="modal-post">
            <div class="modal-background" onclick='$("#modal-post").removeClass("is-active")'></div>
            <div class="modal-content">
                <div class="box">
                    <article class="media">
                        <div class="media-content">
                            <div class="field">
                                <p class="control">
                                  <textarea id="textarea-post" class="textarea"
                                            placeholder="무슨 생각을 하고 계신가요?"></textarea>
                                </p>
                            </div>
                            <nav class="level is-mobile">
                                <div class="level-left">

                                </div>
                                <div class="level-right">
                                    <div class="level-item">
                                        <a class="button is-sparta" onclick="post()">포스팅하기</a>
                                    </div>
                                    <div class="level-item">
                                        <a class="button is-sparta is-outlined"
                                           onclick='$("#modal-post").removeClass("is-active")'>취소</a>
                                    </div>
                                </div>
                            </nav>
                        </div>
                    </article>
                </div>
            </div>
            <button class="modal-close is-large" aria-label="close"
                    onclick='$("#modal-post").removeClass("is-active")'></button>
        </div>

    </section>
{% endif %}
<section class="section">
    <div id="post-box" class="container">
        <div class="box">
            <article class="media">
                <div class="media-left">
                    <a class="image is-64x64" href="#">
                        <img class="is-rounded"
                             src={{ url_for("static", filename="profile_pics/profile_placeholder.png") }} alt="Image">
                    </a>
                </div>
                <div class="media-content">
                    <div class="content">
                        <p>
                            <strong>홍길동</strong> <small>@username</small> <small>10분 전</small>
                            <br>
                            글을 적는 칸
                        </p>
                    </div>
                    <nav class="level is-mobile">
                        <div class="level-left">
                            <a class="level-item is-sparta" aria-label="heart"
                               onclick="toggle_like('', 'heart')">
                                  <span class="icon is-small"><i class="fa fa-heart"
                                                                 aria-hidden="true"></i></span>&nbsp;<span
                                    class="like-num">2.7k</span>
                            </a>
                        </div>

                    </nav>
                </div>
            </article>
        </div>

    </div>
</section>
</body>
</html>

 

 

http://3.36.119.178

 

 

 

728x90

'✍~2022 > WEB' 카테고리의 다른 글

네이버 부스트코스 풀스택 학습일지 (1)  (0) 2022.02.06
드디어 ! . 완주  (0) 2021.08.01
3주차 개발일지~!  (0) 2021.07.26
웹개발+/2주차 필기,개발일지  (0) 2021.07.18
WEB 개발+ / 1주차 필기, 개발일지  (0) 2021.07.12