내일배움캠프 - 59stargram 개발일지

Dongwoo Kim·2022년 5월 15일
0

스파르타 코딩클럽

내일배움캠프 AI 웹개발자양성과정 2회차

0. 프로젝트 정보

1) 프로젝트 명

59stargram - 인스타그램 클론 코딩

2) 기간

2022.05.03~11.

3) 프로젝트 간단설명

인스타그램의 벡엔드 기능을 직접 구현하기 위한 프로젝트

4) 역할 분담

  • 김동우 : 유저페이지
  • 김희정 : 게시글 페이지
  • 원송희 : 메인페이지 - 테마 수정부터
  • 전진영 : 로그인/회원가입 페이지

1. 메인페이지

1) 검색창 활성화 기능

검색창을 클릭했을 때 활성화시키고, 다른 곳을 클릭하면 비활성화 되도록 구현

// 검색창 활성화시키는 함수
function active_search_input_box() {
    $('#search_icon').css("display", "none")
    $('#search_active_button').css("display", "none")
    $('#search_input_box').css("width", "90%")
    $('#search_input_box').focus()
}

$(function () {
    // 검색창에서 focus를 다른 곳으로 옮길 때
    $('#search_input_box').blur(()=>{
        $('#search_icon').css("display", "block")
        $('#search_active_button').css("display", "block")
        $('#search_input_box').css("width", "70%")
    })
  });

2) 스토리부분 슬라이드 기능

좌우 슬라이드 기능과 버튼 구현
끝에 도달하면 해당 방향의 버튼 사라지도록 구현

// 스토리 슬라이드 함수
// vector : 슬라이드 방향 - 0:왼쪽, 1:오른쪽
function move_story_slide(vector) {
    let margin_text = $('#stories').css('margin-left')
    let width_text = $('#stories').css('width')
    let box_text = $('#card').css('width')

    let margin_num = Number(margin_text.slice(0, -2))
    let width_num = Number(width_text.slice(0, -2))
    let box_num = Number(box_text.slice(0, -2))

    let max_margin = 0  // 최대 마진 값
    let move_margin = 150   // 한번 이동할 때 움직일 마진 값

    if (width_num > box_num) {
        max_margin = width_num - box_num
    }

    if (vector == 1) {
        if (margin_num == 0) {
            $('#bg_slide_button_left').css("opacity", "1")
            $('#bg_slide_button_left').css("display", "block")
        }
        margin_num = margin_num - move_margin
        if (margin_num < -1 * max_margin) {
            margin_num = -1 * max_margin
            $('#bg_slide_button_right').css("opacity", "0")
            $('#bg_slide_button_right').css("display", "none")
        }
    } else {
        if (margin_num == -1 * max_margin) {
            $('#bg_slide_button_right').css("opacity", "1")
            $('#bg_slide_button_right').css("display", "block")
        }
        margin_num = margin_num + move_margin
        if (margin_num > 0) {
            margin_num = 0
            $('#bg_slide_button_left').css("opacity", "0")
            $('#bg_slide_button_left').css("display", "none")
        }
    }
    let update_margin = (margin_num).toString() + "px"

    $('#stories').css('margin-left', update_margin)
}

3) 반응형 레이아웃

  • 화면 width가 변경되어도 서브부분의 위치가 고정되도록 구현
// profile 마진값 계산하는 함수
function update_profile_margin() {
    let screen_width = $(window).width()
    let self_width = $('#main_body').width()
    let margin = screen_width - self_width
    let margin_right = (margin / 2).toString()

    let str = margin_right + "px"
    $('#profile_box').css("right", str)
}

// 로딩시 profile 마진값 다시 계산
$(document).ready(function () {
    update_profile_margin()
})

$(function () {
  // 화면 사이즈 변경 시
  // profile 마진값 다시 계산
  $(window).resize(function () {
    update_profile_margin()
  });
});

  • 화면 width에 따른 검색창 사라지기, 서브부분 사라지기, 피드부분 width 변경
@media (max-width: 1125px){
    .header_body {
        width: 100%
    }

    #profile_box {
        display: none;
    }

    #main_body {
        width: 614px;
    }

    .post_box {
        margin-right: 0;
    }
}

@media (max-width: 906px) {
    .search_box {
        display: none;
    }
}

@media (max-width: 640px){
    #main_body {
        width: 100%;
    }

    .post_box {
        width: 100%;
    }
}

2. 유저 페이지

1) 유저 페이지 로딩

  • jinja2 문법을 이용한 유저 페이지 로딩

// 유저 페이지의 게시물 호버시 좋아요와 코멘트 수 보여줌
$('.user_post_img_box').hover(function () {
    $(this).children('.user_post_info_box').css('display', 'block')
}, function () {
    $(this).children('.user_post_info_box').css('display', 'none')
})

// 사용자 페이지 로딩시 posts 탭을 보여줌
$(document).ready(function () {
    $(`.user_post_menu[name='posts']`).css('border-top', '2px solid black');
    $(`.user_post_menu[name='posts']`).css('font-weight', 'bold');
    $(`.user_post_menu[name='posts']`).css('opacity', '1');

    let user_info = get_user_name()
    console.log(user_info)
})

// 메뉴 클릭시 클릭한 메뉴 활성화
// name : 클릭한 메뉴 태그의 name
function user_menu_on(name){
    $('.user_post_menu').css('border-top', '1px solid #e8e8e8');
    $('.user_post_menu').css('font-weight', '300');
    $('.user_post_menu').css('opacity', '0.5');

    $(`.user_post_menu[name=${name}]`).css('border-top', '2px solid black');
    $(`.user_post_menu[name=${name}]`).css('font-weight', 'bold');
    $(`.user_post_menu[name=${name}]`).css('opacity', '1');

    $('.user_post_users').toggle('show')
    $('.user_post_bookmarks').toggle('show')
}
# 유저 페이지 - 유저 정보 보여주기
@app.route('/user')
def user():
    user_name = request.args.get('user_name')
    if user_name is None:
        return redirect(url_for('home'))
    else:
        # 유저 정보 불러오기
        user_info = db.Users.find_one({"UserName": user_name})

        # 유저 프로필 사진 불러오기
        fs = gridfs.GridFS(db, 'Profile')
        profile_img = db.Profile.files.find_one({'filename': user_name})

        my_id = profile_img['_id']
        profile_img = fs.get(my_id).read()

        profile_img = base64.b64encode(profile_img)  # convert to base64 as bytes
        profile_img = profile_img.decode()  # convert bytes to string

        # 게시글 정보 불러오기
        posts = list(db.Posts.find({"UserName": user_name}))

        # 북마크 정보 불러오기
        bookmarks = list(db.Bookmarks.find({"UserName": user_name}))

        # 포스트 이미지 불러오기
        post_images = []
        fs = gridfs.GridFS(db, 'Post')
        for post in posts:
            data = db.Post.files.find_one({'filename': post['PostId']})
            my_id = data['_id']
            data = fs.get(my_id).read()

            data = base64.b64encode(data)
            data = data.decode()

            post_images.append(data)

        # 북마크 이미지 불러오기
        bookmark_images = []
        bookmark_posts = []
        fs = gridfs.GridFS(db, 'Post')
        for bookmark in bookmarks:
            post_id = bookmark['PostId']

            bookmark_post = db.Posts.find_one({"PostId": post_id})
            data = db.Post.files.find_one({'filename': post_id})

            my_id = data['_id']
            data = fs.get(my_id).read()

            data = base64.b64encode(data)  # convert to base64 as bytes
            data = data.decode()  # convert bytes to string

            bookmark_posts.append(bookmark_post)
            bookmark_images.append(data)

        # 현재 로그인한 유저인지 판단
        ## 현재 접속한 유저의 사용자 이름
        token_receive = request.cookies.get('mytoken')
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        user_info_temp = db.Users.find_one({"Email": payload['id']}, {'_id': False})
        current_user = user_info_temp['UserName']

        # 현재 로그인한 유저 프로필 사진
        fs = gridfs.GridFS(db, 'Profile')
        current_profile = db.Profile.files.find_one({'filename': current_user})

        my_id = current_profile['_id']
        current_profile = fs.get(my_id).read()

        current_profile = base64.b64encode(current_profile)  # convert to base64 as bytes
        current_profile = current_profile.decode()  # convert bytes to string

        if user_name == current_user:
            my_page = 1
        else:
            my_page = 0

        return render_template("user.html",
                               current_user=current_user, current_profile=current_profile,
                               user_info=user_info, profile_img=profile_img,
                               posts=posts, post_images=post_images,
                               bookmark_posts=bookmark_posts, bookmark_images=bookmark_images,
                               my_page=my_page)

2) 팔로우정보 모달창 / 유저요약보기 모달창

  • 팔로워, 팔로잉 클릭시 해당 정보를 제공하는 API
const body = document.querySelector('body');
const modal_follow_outside = document.querySelector('.user_follower_box');
const modal_summary_outside = document.querySelector('.user_summary_body');
const modal_setting_outside = document.querySelector('.user_setting_modal_box');

// 팔로우 모달 API 전달하기
// type - 0:팔로워, 1:팔로잉
function user_follow_modal_on(type) {
    let title_text;
    if (type == 0) {
        title_text = '팔로워';
    } else if (type == 1) {
        title_text = '팔로잉';
    } else {
        return;
    }

    $('.user_follower_title_text').text(title_text);

    user_modal_list(type)

    modal_follow_outside.classList.toggle('show');
    if (modal_follow_outside.classList.contains('show')) {
        body.style.overflow = 'hidden';
    }
}

// 팔로우 모달창 리스트 업
function user_modal_list(type){
    $('.user_follower_list').empty()

    // 현재 로그인한 유저의 사용자이름 가져오기
    let user_info = get_user_name()
    let current_name = user_info['UserName']

    // 유저페이지의 사용자 이름
    let user_name = $('#user_name_title').text()

    let url = '/user/follow?user_name=' + user_name + '&type=' + type
    $.ajax({
        type: "GET",
        url: url,
        data: {},
        success: function (response) {
            let follows = response['users'];
            let user = user_name

            for (let i = 0; i < follows.length; i++) {
                let temp_html = `<div class="user_follower">
                                    <div class="user_follower_img">
                                        <img class="img_circle" width="30px" height="30px" 
                                             src="data:image/jpg;base64, ${follows[i]['ProfileImage']}"/>
                                    </div>
                                    <div class="user_follower_name_box">
                                        <div class="user_follower_username" name="${follows[i]['UserName']}">
                                            ${follows[i]['UserName']}
                                        </div>
                                        <div class="user_follower_name">
                                            ${follows[i]['Name']}
                                        </div>
                                    </div>
                                    <div class="user_follower_delete_box">
                                    </div>
                                </div>`

                $('.user_follower_list').append(temp_html);

                if (type == 1 && current_name == user_name) {
                    $('.user_follower_delete_box').empty();
                    let temp_html_2 = `<div class="user_follower_delete_btn"token interpolation">${user}', '${follows[i]['UserName']}')">
                                            삭제
                                        </div>`
                    $('.user_follower_delete_box').append(temp_html_2);
                }
            }
        }
    })
}

# 유저 페이지 - 팔로우정보 보여주기
@app.route('/user/follow', methods=['GET'])
def user_follow():
    user_name = request.args.get('user_name')

    # type - 0:팔로워 정보 / 1:팔로잉 정보
    follow_type = request.args.get('type')

    # 정보 불러오기
    follows = []
    users = []
    fs = gridfs.GridFS(db, 'Profile')
    if follow_type == '0':
        follows = list(db.Follows.find({"FollowingName": user_name}, {'_id': False}))
        msg = '팔로워 로딩'
        for follow in follows:
            follower = db.Users.find_one({"UserName": follow['UserName']}, {'_id': False})

            data = db.Profile.files.find_one({'filename': follower['UserName']})
            my_id = data['_id']
            data = fs.get(my_id).read()

            data = base64.b64encode(data)
            data = data.decode()

            follower_info = {
                'UserName': follower['UserName'],
                'Name': follower['Name'],
                'ProfileImage': data
            }

            users.append(follower_info)
    elif follow_type == '1':
        follows = list(db.Follows.find({"UserName": user_name}, {'_id': False}))
        msg = '팔로잉 로딩'
        for follow in follows:
            following = db.Users.find_one({"UserName": follow['FollowingName']}, {'_id': False})

            data = db.Profile.files.find_one({'filename': following['UserName']})
            my_id = data['_id']
            data = fs.get(my_id).read()

            data = base64.b64encode(data)
            data = data.decode()

            following_info = {
                'UserName': following['UserName'],
                'Name': following['Name'],
                'ProfileImage': data
            }
            users.append(following_info)
    else:
        msg = '로딩 실패!'

    return jsonify({'msg': msg, 'users': users})
  • 이후 리스트의 유저에 마우스를 올리면 해당 유저의 대한 정보를 요약해서 보여주는 API
// 팔로우 모달창에서 사용자이름 호버 이벤트 
$(document).on({
    // 호버 on:  유저요약보기 모달창 보여줌
    mouseenter: function () {
        let name = $(this).attr('name')
        let top = $(this).offset().top;
        let left = $(this).offset().left;
        user_summary_modal_on(name, top, left)
    },
    // 호버 off:  유저요약보기 모달창 보여줌
    mouseleave: function () {
        user_modal_quit(1);
    }
}, ".user_follower_username");

// 팔로우 모달창에서 사용자이름 클릭시 해당 유저페이지로 이동
$(document).on('click', ".user_follower_username", function () {
    let name = $(this).attr('name')
    document.location.href='/user?user_name='+name
});

// 유저 요약 모달 API 전달하기
function user_summary_modal_on(user_name, top, left) {
    let offset_top = $('.user_follower_body').offset().top + top + 50;
    let offset_left = $('.user_follower_body').offset().left + left + 50;

    offset_top = parseInt(offset_top).toString()+'px'
    offset_left = parseInt(offset_left).toString()+'px'

    $.ajax({
        type: "GET",
        url: '/user/summary?user_name=' + user_name,
        data: {},
        success: function (response) {
            let profile_img = response['profile_img']
            $('.user_summary_img').empty()
            let temp_html_2 = `<img class="img_circle img_cover" src="data:image/jpg;base64, ${profile_img}"/>`
            $('.user_summary_img').append(temp_html_2)


            $('.user_summary_username').text(response['user_info']['UserName'])
            $('.user_summary_name').text(response['user_info']['Name'])

            $('#user_summary_post_cnt').text(response['user_info']['PostCnt'])
            $('#user_summary_follower_cnt').text(response['user_info']['FollowerCnt'])
            $('#user_summary_following_cnt').text(response['user_info']['FollowingCnt'])

            $('.user_summary_posts').empty()

            let posts = response['post_images']
            for (let i = 0; i < posts.length; i++){
                let temp_html = `<img class="user_summary_post_img"
                                     src="data:image/jpg;base64, ${posts[i]}"/>`
                $('.user_summary_posts').append(temp_html)
            }
        }
    })

    modal_summary_outside.style.top = offset_top
    modal_summary_outside.style.left = offset_left
 	modal_summary_outside.classList.toggle('show');
}

# 유저 페이지 - 유저 정보 보여주기
@app.route('/user/summary', methods=['GET'])
def user_summary():
    user_name = request.args.get('user_name')

    # 유저 정보 불러오기
    user_info = db.Users.find_one({"UserName": user_name})

    new_user_info = {
        'UserName': user_info['UserName'],
        'Name': user_info['Name'],
        'PostCnt': user_info['PostCnt'],
        'FollowerCnt': user_info['FollowerCnt'],
        'FollowingCnt': user_info['FollowingCnt']
    }

    # 유저 프로필 사진 불러오기
    fs = gridfs.GridFS(db, 'Profile')
    profile_img = db.Profile.files.find_one({'filename': user_name})

    my_id = profile_img['_id']
    profile_img = fs.get(my_id).read()

    profile_img = base64.b64encode(profile_img)  # convert to base64 as bytes
    profile_img = profile_img.decode()  # convert bytes to string

    # 게시글 정보 불러오기
    posts = list(db.Posts.find({"UserName": user_name}).limit(3))

    # 포스트 이미지 불러오기
    post_images = []
    fs = gridfs.GridFS(db, 'Post')
    for post in posts:
        data = db.Post.files.find_one({'filename': post['PostId']})
        my_id = data['_id']
        data = fs.get(my_id).read()

        data = base64.b64encode(data)
        data = data.decode()

        post_images.append(data)

    return jsonify({'user_info': new_user_info,
                    'profile_img': profile_img,
                    'post_images': post_images})
  • 모달창 사라지기
// 모달창 사라지기
// type - 0:팔로우 모달창, 1:유저요약 모달창, 2:유저설정 모달창
function user_modal_quit(type) {
    if (type == 0) {
        modal_follow_outside.classList.toggle('show');

        if (!modal_follow_outside.classList.contains('show')) {
            body.style.overflow = 'auto';
        }
    } else if (type == 1) {
        modal_summary_outside.classList.toggle('show');
    } else if (type == 2) {
        modal_setting_outside.classList.toggle('show');

        if (!modal_setting_outside.classList.contains('show')) {
            body.style.overflow = 'auto';
        }
    } else {
        return;
    }
}

// 모달 밖을 클릭하면 모달창 닫기
modal_follow_outside.addEventListener('click', (event) => {
    if (event.target === modal_follow_outside) {
        user_modal_quit(0)
    }
});
modal_setting_outside.addEventListener('click', (event) => {
    if (event.target === modal_setting_outside) {
        user_modal_quit(2)
    }
});

3) 팔로우 생성, 삭제 API

// 팔로우 삭제
function user_follow_delete(user_name, following_name) {
    $.ajax({
        type: "POST",
        url: '/user/follow/delete',
        data: {
            user_name: user_name,
            following_name: following_name
        },
        success: function (response) {
            user_modal_list(1)
            alert(response['msg'])
            window.location.reload()
        }
    })

}

// 팔로우 생성
function user_follow_create() {
    // 현재 로그인한 사용자 이름 가져오기
    let user_info = get_user_name()
    let user_name = user_info['UserName']

    let following_name = $('#user_name_title').text()

    $.ajax({
        type: "POST",
        url: '/user/follow/create',
        data: {
            user_name: user_name,
            following_name: following_name
        },
        success: function (response) {
            alert(response['msg'])
            window.location.reload()
        }
    })
}

# 유저 페이지 - 팔로우 삭제
@app.route('/user/follow/delete', methods=['POST'])
def user_follow_delete():
    user_name = request.form['user_name']
    following_name = request.form['following_name']

    try:
        db.Follows.delete_one({'UserName': user_name, 'FollowingName': following_name})
        msg = '삭제 완료'

        # 로그인한 유저의 팔로잉 숫자 업데이트
        # 현재 유저 정보에서 팔로잉 숫자 가져오기
        db.Users.update_one({'UserName': user_name}, {'$inc': {'FollowingCnt': -1}})

        # 팔로잉 유저의 팔로워 숫자 업데이트
        db.Users.update_one({'UserName': following_name}, {'$inc': {'FollowerCnt': -1}})

    except:
        msg = '삭제 실패'

    return jsonify({'msg': msg})


# 유저 페이지 - 팔로우 생성
@app.route('/user/follow/create', methods=['POST'])
def user_follow_create():
    user_name = request.form['user_name']
    following_name = request.form['following_name']

    follow_info = db.Follows.find_one({'UserName': user_name, 'FollowingName': following_name})

    if follow_info is None:
        try:
            db.Follows.insert_one({'UserName': user_name, 'FollowingName': following_name})
            msg = '팔로우 완료'

            # 로그인한 유저의 팔로잉 숫자 업데이트
            # 현재 유저 정보에서 팔로잉 숫자 가져오기
            db.Users.update_one({'UserName': user_name}, {'$inc': {'FollowingCnt': 1}})

            # 팔로잉 유저의 팔로워 숫자 업데이트
            db.Users.update_one({'UserName': following_name}, {'$inc': {'FollowerCnt': 1}})

        except:
            msg = '팔로우 실패'

        return jsonify({'msg': msg})
    else:
        msg = '이미 팔로우 한 상태입니다.'
        return jsonify({'msg': msg})

3. 기타 정보

1) 프로젝트 문서

https://kimphysicsman.notion.site/c5d75c7c019e457e9faabd431b2f0f5b

2) github

https://github.com/ai-web-9-team/59stargram

3) 발표영상

https://youtu.be/AtUV-gJGx4c

profile
kimphysicsman

0개의 댓글