59stargram - 인스타그램 클론 코딩
2022.05.03~11.
인스타그램의 벡엔드 기능을 직접 구현하기 위한 프로젝트
검색창을 클릭했을 때 활성화시키고, 다른 곳을 클릭하면 비활성화 되도록 구현
// 검색창 활성화시키는 함수
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%")
})
});
좌우 슬라이드 기능과 버튼 구현
끝에 도달하면 해당 방향의 버튼 사라지도록 구현
// 스토리 슬라이드 함수
// 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)
}
// 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()
});
});
@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%;
}
}
// 유저 페이지의 게시물 호버시 좋아요와 코멘트 수 보여줌
$('.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)
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})
// 팔로우 모달창에서 사용자이름 호버 이벤트
$(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)
}
});
// 팔로우 삭제
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})
https://kimphysicsman.notion.site/c5d75c7c019e457e9faabd431b2f0f5b
https://github.com/ai-web-9-team/59stargram