[0520] TIL 27일차

nikevapormax·2022년 5월 21일
0

TIL

목록 보기
26/116
post-custom-banner

😂 거북이반 인스타 클론코딩

😭 게시글 수정 / 삭제

- authorize 수정

  • 이번 수업에서 다룬 내용들은 이전과 달리 user 이외의 인자들을 필요로 한다. 따라서 여러 값이 인자로 들어갈 수 있도록 조치해주어야 한다.
    • *args : list 형태인 argument는 아무거나 다 들어와도 된다는 뜻
    • **kwargs : a=b의 형태인 키워드들이 몇개가 들어와도 인식할 수 있다.
def authorize(f):
    @wraps(f)
    # argument와 key-word argument가 같이 들어가는 경우를 위해 작성
    # *과 ** 만 있다면 이름은 뒤에 아무거나 붙어도 무방은 함
    # *args: list 형태로 아무거나 다 들어와 된다.
    # **kwargs : a = b의 형태로 즉, 키워드의 형태로 몇개씩 들어와도 인식을 하겠다.
    def decorated_function(*args, **kwargs):
        # 만약 Authorization이 헤더 안에 없다면
        if not 'Authorization' in request.headers:
            abort(401)  # 401 에러를 반환
        # Authorization이 헤더 안에 있다면 'token' 변수에 저장
        token = request.headers['Authorization']
        try:
            # 토큰을 디코드한 값을 user에 저장
            # _id, email, exp가 들어있음
            user = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        except:
            abort(401)
        return f(user, *args, **kwargs)
    return decorated_function

- 게시글 수정

  • 백앤드 부분
    • /article/<article_id>에서 볼 수 있듯이, article_id 값을 꼭 붙여 어떤 게시글을 수정하는지 기입해야 한다.
    • 게시글을 수정하게 되면 제목내용을 수정하게 된다. 따라서 title과 content의 데이터를 프론트에서 받아 와야 한다.
    • 여기서 중요한 점은 게시글을 작성한 사용자를 식별하는 것이며, 해당 사용자가 작성한 게시글이 어떤 것이냐이다. 따라서 게시글의 _id를 사용해야 한다.
    • 게시글의 아이디는 json 형식으로 프론트에서 넘어오기 때문에 str 타입이고, 우리가 db에서 해당 아이디를 가지고 있는 게시글을 찾아내기 위해서는 ObjectId 값이 필요하다. 따라서 ObjectId(article_id)를 꼭 해주어야 한다.
    • postman으로 게시글의 정보를 확인해 보면, 우리는 게시글을 작성한 사용자의 아이디를 str 타입으로 db에 저장하였기 때문에 user의 id는 그대로 사용해 주면 된다.(여기서 user란 authorize 함수에서 디코딩되어 나오는 내용이다. id, email, exp가 들어있다.)
    • 여태까지와 다르게 우리는 게시글을 수정하는 것이기 때문에 update_one을 사용하게 되었다. 하나의 게시글만을 수정하는 것이기 때문에 위의 함수를 사용하면 되며, .matched_count를 사용해서 수정 여부를 알 수 있다.
      • .matched_count = 1 --> 수정 완료
        .matched_count = 0 --> 수정되지 않음
@app.route('/article/<article_id>', methods=["PATCH"])
@authorize
def patch_article_detail(user, article_id):
    data = json.loads(request.data)

    # 게시글을 변경하게 되면 제목과 내용만 바뀌니까 그 둘을 data에서 뽑아온다.
    title = data.get("title")
    content = data.get("content")

    # filter 값으로 게시글의 고유값인 옵젝아이디, 그리고 해당 글을 작성한 유저를 찾아야 하므로 authorize의 user에 들어있는 id 값을 사용한다.
    # 여기서 잠깐! 왜 유저의 아이디는 옵젝아이디가 아니죠? -> article db에는 유저의 아이디가 string으로 저장되어 있다. 물론 user db에서 값을 빼온다하면 옵젝아이디로 빼야함!
    article = db.article.update_one(
        {"_id": ObjectId(article_id), "user": user["id"]}, {"$set": {"title": title, "content": content}})

    # 위의 업데이트가 성공적으로 이루어졌다면 1이 출력되고, 아니라면 0이 출력됨
    print(article.matched_count)

    # 이곳 POSTMAN 사용법 :
    # 해당 게시글의 아이디를 찾고, url에 넣어준다. 이러고 보내보면 오류가 난다. 왜냐? authorization에 글쓴이의 정보가 없기 때문이다.
    # 아예 _id 값이 불분명하다고 생각된다면 다음과 같이 처음부터 시작하자
    # /signin -> _id 추출 -> /get_articles -> 게시글의 _id 추출 -> /get_article_detail -> 게시글의  _id 값을 넣어 정보 확인
    # -> patch_article로 가 Headers에 Authorization 값을 넣어줌(사용자의 _id값) -> send -> 'msg': 'success' -> app.py로 와서 article.matched_count 확인ㄴ
    if article.matched_count:
        return jsonify({'msg': 'success'})
    else:
        return jsonify({'msg': 'fail'}), 403
  • 프론트앤드 부분
  • updateMode() 함수
    • update를 진행하는 버튼에 onclick으로 적용되어 있는 함수이다.
    • 수정하기 버튼을 클릭하게 되면 원래 값이 적혀있는 부분이 hidden으로 인해 가려지게 된다.
    • 수정할 부분을 작성할 textarea가 생성되면서 title과 content를 각각 작성하면 된다.
    • 현재 값이 가려져 있는 title과 content 변수에 방금 작성한 부분이 insertBefore 함수로 인해 들어가게 된다.
    • 그 후, 내가 방금 누른 update_button의 onclick 함수가 updateArticle()로 변하게 되며 해당 함수가 실행된다.
function updateMode() {
    const title = document.getElementById('title')
    const content = document.getElementById('content')
    // 수정하기 버튼을 누르면 title과 content가 숨겨짐
    title.style.visibility = "hidden";
    content.style.visibility = "hidden";

    const input_title = document.createElement("textarea")
    // id 값으로 input_title을 부여함
    input_title.setAttribute("id", "input_title")
    // 이 부분을 넣어줌으로써 body에 이 값을 집어 넣었을 때 textarea 안에 본래의 값이 들어가 있게 됨
    input_title.innerText = title.innerHTML

    const input_content = document.createElement("textarea")
    input_content.setAttribute("id", "input_content")
    input_content.innerText = content.innerHTML
    input_content.rows = 10

    // 위의 js에서 수정한 부분을 진짜로 html 안에다가 넣어주자
    const body = document.body
    body.insertBefore(input_title, title)
    body.insertBefore(input_content, content)

    const update_button = document.getElementById("update_button")
    update_button.setAttribute("onclick", "updateArticle()")
}
  • updateArticle() 함수
    • 위에서 작성된 input_titleinput_content가 해당 함수로 들어와 patchArticle()의 인자로 들어가게 된다. 아래에도 설명되겠지만 해당 함수로 인해 수정된 내용들이 백앤드 부분으로 넘어갈 수 있게 된다.
    • 위에서 작성된 body.insertBefore(input_title, title)에서 유추할 수 있듯이, 우리가 작성한 수정 부분은 이미 titlecontent 변수에게 값이 넘어간 상태이다. 그러므로 단지 textarea로서 값만을 보여주던 input_titleinput_content를 화면에서 지워도 상관이 없다. 또한 우리가 수정된 값을 보여주려면 textarea를 없애주어야 되기도 하기 때문에 remove하도록 하겠다.
    • 수정된 부분이 백앤드로 넘어가 db 상에서 수정이 다 되었기 때문에 titlecontent 변수의 값을 다시 사용자에게 보여주어도 된다.
    • 수정이 완료되기도 하였고, 우리가 수정하기 버튼을 누르게 되면서 해당 버튼의 함수를 updateArticle()로 변경했었기 때문에 사용자가 다시 또 수정을 할 수 있도록 원래의 함수인 updateMode()로 바꿔주었다.
    • 그리고 마지막에 loadArticles(article_id)를 통해 title과 content를 불러왔다.
async function updateArticle() {
    var input_title = document.getElementById('input_title')
    var input_content = document.getElementById('input_content')
    console.log(input_title.value, input_content.value)

    // article_id는 전역변수로 맨 위에서 선언되어 사용 가능
    const article = await patchArticle(article_id, input_title.value, input_content.value)

    // 수정할 값을 넣은 후 '수정하기' 버튼을 누르면 값이 사라짐
    input_title.remove()
    input_content.remove()

    // 값이 사라지기만 하면은 안되니까 수정된 값을 다시 화면에 보여주기 위해 작업
    const title = document.getElementById('title')
    const content = document.getElementById('content')
    title.style.visibility = "visible";
    content.style.visibility = "visible";
    // 위에까지 하면은 '수정하기' 버튼을 누른 후 값이 화면에 다시 들어오기는 하지만 값은 변하지 않음
    // 그래서 변한 값을 보여주기 위해서 아래와 같이 작업
    // '수정하기' 버튼의 온클릭 함수를 다시 넣어준다. 위에 있는 updateMode()가 버튼을 누르면 다시 실행됨.
    update_button.setAttribute("onclick", "updateMode()")

    // 모든 작업이 끝난 뒤에 위에 있는 loadArticles() 함수를 실행해서 다시 db에서 값을 가지고 오도록 함
    loadArticles(article_id)
}
  • patchArticle 함수
    • 게시글에서 수정할 내용인 title과 content를 담은 articleData를 body에 담고, headers에 localstorage에 저장되어 있던 token 값과 함께 백앤드의 url을 가진 부분으로 fetch 해준다.
    • patchArticle 함수에 인자로 담긴 article_id와 회원가입이 완료되어 로그인하고 게시글을 작성한 user의 정보, 수정될 게시글의 내용이 백앤드로 전달되어 수정이 되고 db에 update되는 것이다.
async function patchArticle(article_id, title, content) {
    const articleData = {
        "title": title,
        "content": content
    }

    const response = await fetch(`${backend_base_url}/article/${article_id}`, {
        headers: {
            'Authorization': localStorage.getItem('token')
        },
        method: 'PATCH',
        body: JSON.stringify(articleData)
    })

    // 여기서 현재 로그인 하지 않은 아이디로 수정을 시도할 수 있다. 이러면 오류 뜨니까 꼭 로그인한 사용자로 하기!
    if (response.status == 200) {
        response_json = await response.json()
        return response_json
    } else {
        alert(response.status)
    }
}

- 삭제하기

  • 백앤드 부분
    • PATCH의 경우와 같이, /article/<article_id>를 작성해야 한다.
    • 함수의 인자로 user와 article_id가 들어간다. 이를 통해 글을 작성한 사용자만이 본인의 글을 삭제할 수 있도록 조치해 주는 것이다.
    • json 형식으로 넘어온 article_id를 ObjectId(article_id)로 변경해주어야 db에서 찾을 수 있으니 주의해야 한다.
      • 항상 하다가 헷갈리면 db를 확인하거나, postman을 통해 어떤 값들이 오고 가는지 확인해 보는것이 중요하다!
@app.route('/article/<article_id>', methods=["DELETE"])
@authorize
def delete_article_detail(user, article_id):
    article = db.article.delete_one(
        {"_id": ObjectId(article_id), "user": user['id']})

    # 위의 수정하기 경우와 똑같다. 게시글이 없거나 다른사람의 글을 지우려 하거나 한다면 0이 출력되고, 똑바로 접근했다면 1이 나온다.
    if article.deleted_count:
        return jsonify({'msg': 'success'})
    else:
        return jsonify({'msg': 'fail'}), 403
  • 프론트앤드 부분
  • removeArticle() 함수
    • 삭제하기 버튼을 누르게 되면 deleteArticle 함수가 실행되게 된다. 이때 인자로 article_id를 사용하게 된다.
async function removeArticle() {
    await deleteArticle(article_id)
}
  • deleteArticle() 함수
    • 위에서 받은 인자를 백앤드의 url에 포함시켜 DELETE 요청을 보내게 된다. 이때 token을 같이 보내 해당 글을 작성한 사용자가 맞는지에 대한 검사를 진행하게 된다.
    • 게시글의 삭제가 완료되면 메인 페이지로 이동할 수 있도록 조치하였다.
async function deleteArticle() {
    const response = await fetch(`${backend_base_url}/article/${article_id}`, {
        headers: { 'Authorization': localStorage.getItem('token') },
        method: 'DELETE'
    })

    if (response.status == 200) {
        window.location.replace(`${frontend_base_url}/`)
    } else {
        alert(response.status)
    }
}
profile
https://github.com/nikevapormax
post-custom-banner

0개의 댓글