항해99/웹개발종합반PLUS

웹개발종합반PLUS 4주차-스위터 완성본

숲별 2022. 9. 22. 14:02
728x90

이걸 토대로 미니프로젝트를 만들어보면 좋을 것 같아서 조원과 공유하려고 깃헙에 올렸지만

일단 여기에도 올려본다.

 

------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'

import certifi

ca = certifi.where()
client = MongoClient('mongodb+srv://test:sparta@cluster0.bqbsmxx.mongodb.net/Cluster0?retryWrites=true&w=majority',
                     tlsCAFile=ca)
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')

        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}))
    # print(value_receive, type_receive, exists)
    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"]
            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"]
        print(type(date_receive))
        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'])
        my_username = payload["id"]
        username_receive = request.args.get("username_give")
        if username_receive=="":
            posts = list(db.posts.find({}).sort("date", -1).limit(20))
        else:
            posts = list(db.posts.find({"username":username_receive}).sort("date", -1).limit(20))

        for post in posts:
            post["_id"] = str(post["_id"])

            post["count_heart"] = db.likes.count_documents({"post_id": post["_id"], "type": "heart"})
            post["heart_by_me"] = bool(db.likes.find_one({"post_id": post["_id"], "type": "heart", "username": my_username}))

            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": my_username}))

            post["count_like"] = db.likes.count_documents({"post_id": post["_id"], "type": "like"})
            post["like_by_me"] = bool(db.likes.find_one({"post_id": post["_id"], "type": "like", "username": my_username}))

        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"]})
        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})
        print(count)
        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>
        <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') }}"/>
        <!-- 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.gstatic.com">
        <link href="https://fonts.googleapis.com/css2?family=Gamja+Flower&family=Stylish&display=swap" rel="stylesheet">
        <link href="{{ url_for('static', filename='mystyle.css') }}" rel="stylesheet">

        <!-- JS -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
        <script src="{{ url_for('static', filename='myjs.js') }}"></script>


        <script>
            $(document).ready(function () {
                get_posts()
            })

        </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="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>

 

 

------user.html

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

        <!-- Webpage Title -->
        <title>{{ user_info.profile_name }} | SWEETER</title>
        <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') }}"/>
        <!-- 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.gstatic.com">
        <link href="https://fonts.googleapis.com/css2?family=Gamja+Flower&family=Stylish&display=swap" rel="stylesheet">
        <link href="{{ url_for('static', filename='mystyle.css') }}" rel="stylesheet">

        <!-- JS -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
        <script src="{{ url_for('static', filename='myjs.js') }}"></script>


        <script>
            $(document).ready(function () {
                get_posts("{{ user_info.username }}")
            })

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

                        }
                    }
                });
            }

            function sign_out() {
                $.removeCookie('mytoken', {path: '/'});
                alert('로그아웃!')
                window.location.href = "/login"
            }
        </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 id="section-post" 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>
        </section>

    </body>
</html>

 

-------login.html

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

        <!-- Webpage Title -->
        <title>Log In | SWEETER</title>
        <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') }}"/>
        <!-- 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.gstatic.com">
        <link href="https://fonts.googleapis.com/css2?family=Gamja+Flower&family=Stylish&display=swap" rel="stylesheet">
        <link href="{{ url_for('static', filename='mystyle.css') }}" rel="stylesheet">

        <!-- JS -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>


        <style>


            .title {
                font-weight: 800;
                font-size: 5rem;
                font-family: 'Stylish', sans-serif;
            }

            .subtitle {
                font-family: 'Gamja Flower', cursive;
                font-size: 2rem;

            }

            .help {
                color: gray;
            }
        </style>
        <script>
            // {% if msg %}
                //    alert("{{ msg }}")
                // {% endif %}

            function toggle_sign_up() {
                $("#sign-up-box").toggleClass("is-hidden")
                $("#div-sign-in-or-up").toggleClass("is-hidden")
                $("#btn-check-dup").toggleClass("is-hidden")
                $("#help-id").toggleClass("is-hidden")
                $("#help-password").toggleClass("is-hidden")
                $("#help-password2").toggleClass("is-hidden")
            }

            function is_nickname(asValue) {
                var regExp = /^(?=.*[a-zA-Z])[-a-zA-Z0-9_.]{2,10}$/;
                return regExp.test(asValue);
            }

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

            function check_dup() {
                let username = $("#input-username").val()
                console.log(username)
                if (username == "") {
                    $("#help-id").text("아이디를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
                    $("#input-username").focus()
                    return;
                }
                if (!is_nickname(username)) {
                    $("#help-id").text("아이디의 형식을 확인해주세요. 영문과 숫자, 일부 특수문자(._-) 사용 가능. 2-10자 길이").removeClass("is-safe").addClass("is-danger")
                    $("#input-username").focus()
                    return;
                }
                $("#help-id").addClass("is-loading")
                $.ajax({
                    type: "POST",
                    url: "/sign_up/check_dup",
                    data: {
                        username_give: username
                    },
                    success: function (response) {

                        if (response["exists"]) {
                            $("#help-id").text("이미 존재하는 아이디입니다.").removeClass("is-safe").addClass("is-danger")
                            $("#input-username").focus()
                        } else {
                            $("#help-id").text("사용할 수 있는 아이디입니다.").removeClass("is-danger").addClass("is-success")
                        }
                        $("#help-id").removeClass("is-loading")

                    }
                });
            }

            function sign_up() {
                let username = $("#input-username").val()
                let password = $("#input-password").val()
                let password2 = $("#input-password2").val()
                console.log(username, password, password2)


                if ($("#help-id").hasClass("is-danger")) {
                    alert("아이디를 다시 확인해주세요.")
                    return;
                } else if (!$("#help-id").hasClass("is-success")) {
                    alert("아이디 중복확인을 해주세요.")
                    return;
                }

                if (password == "") {
                    $("#help-password").text("비밀번호를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
                    $("#input-password").focus()
                    return;
                } else if (!is_password(password)) {
                    $("#help-password").text("비밀번호의 형식을 확인해주세요. 영문과 숫자 필수 포함, 특수문자(!@#$%^&*) 사용가능 8-20자").removeClass("is-safe").addClass("is-danger")
                    $("#input-password").focus()
                    return
                } else {
                    $("#help-password").text("사용할 수 있는 비밀번호입니다.").removeClass("is-danger").addClass("is-success")
                }
                if (password2 == "") {
                    $("#help-password2").text("비밀번호를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
                    $("#input-password2").focus()
                    return;
                } else if (password2 != password) {
                    $("#help-password2").text("비밀번호가 일치하지 않습니다.").removeClass("is-safe").addClass("is-danger")
                    $("#input-password2").focus()
                    return;
                } else {
                    $("#help-password2").text("비밀번호가 일치합니다.").removeClass("is-danger").addClass("is-success")
                }
                $.ajax({
                    type: "POST",
                    url: "/sign_up/save",
                    data: {
                        username_give: username,
                        password_give: password
                    },
                    success: function (response) {
                        alert("회원가입을 축하드립니다!")
                        window.location.replace("/login")
                    }
                });

            }

            function 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'])
                        }
                    }
                });
            }


        </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>
                                    <p id="help-password-login" class="help is-danger"></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>

 

 

--------mystyle.css

body {
    background-color: RGBA(232, 52, 78, 0.2);
    min-height: 100vh;
}

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

 

 

 

-----myjs.js

body {
    background-color: RGBA(232, 52, 78, 0.2);
    min-height: 100vh;
}

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

 

 

 

폴더 구성
패키지구성도 일단 급한대로