개요: 추천하는 음악을 올리고 좋아요 버튼으로 다른 사람들의 반응을 볼 수 있는 사이트. 인기차트를 이용해 가장 추천수가 높은 5개를 보여준다. 카테고리를 이용해 각 장르 별로 목록을 볼 수 있다.
프로젝트 정보
- 프로젝트: SMM 프로젝트 (내일배움캠프 개강 전 미니 프로젝트)
- 개발기간: 2023.03.02 - 2023.03.09 ( 7일 )
- 역할
- 팀장
- Layout
- css
- 글쓰기
- 메인section 화면
- 좋아요
- 인기차트
- 사용 언어: HTML, JavaScript(Vanilla), Python
- 사용 라이브러리:
- HTML, JavaScript
- Jquery - 전체적인 동적 웹페이지 구현
- Bootstrap(프레임워크) - 각 개체들 구현
- Google Font - 웹페이지 폰트 변경
- Python
- Flask - 웹 개발 프레임워크 (import Flask, render_template, request, jsonify)
- pymongo - MongoDB로 DB구성
- bson - MongoDB Primary Key인 _id값(bson형식) 변환
더보기from bson.json_util import dumps / bson.objectid import ObjectId
- ytmusicapi - 유튜브 비공식 api, 크롤링에 사용
- 배포
- awsebcli - AWS Elastic Beanstalk 서비스로 웹사이트 배포
- HTML, JavaScript
프로젝트 구성
★ 파일구조
기본적인 구조는 Flask가 제공하는 템플릿이다
SMMproject ─┬─ app.py (백엔드)
├─ headers_auth.json (ytmusicapi의 유튜브 접속 json파일)
├─ venv (python 가상환경 폴더)
├─ template ─── index.html (프론트엔드)
└─ static ─── img ─┬─ thumbnail.png (meta data)
├─ thumbup.svg (좋아요 버튼)
└─ main_img.png (메인 이미지)
★ 화면구성 및 코드
(1) header
- css
/* index.html */
/* header css */
header {
width: 70%;
min-width: 1200px; /* 최소 너비 제한 */
height: 70px;
background-color: black;
border-bottom: 1px solid #333;
box-shadow: 0 0 3px #444;
margin: 0 auto;
}
.header-logo {
display: block;
line-height: 70px;
font-size: 24px;
font-weight: 800;
letter-spacing: 3px;
display: block;
margin-left: 20px;
text-decoration: none;
color: white;
float: left;
}
.header-btn {
display: block;
height: 20px;
right: 50px;
top: 50%;
margin-top: 15px;
margin-left: 20px;
margin-right: 20px;
float: right;
}
#logo-img {
height: 50px;
}
.loading {
z-index: 99999;
position: relative;
}
.youtubes {
z-index: 2000;
}
.youtube-cover {
float: left;
width:500px;
height:315px;
}
/* header css end */
상단 헤더쪽 css
width: 70%로 화면 전체 너비를 정해주고 min-width: 1200px로 최소 너비를 제한해 화면 크기를 고정
- 1. 로고
<!-- index.html -->
<!-- 로고 -->
<a onclick="location.href='/'"class="header-logo"> /* 클릭 시 메인페이지 불러오기 */
<img src="../static/img/main_img.png" id="logo-img">
</a>
<!-- 로고 end -->
로고 html
- 2. 글쓰기 버튼
<!-- index.html -->
<!-- 글쓰기 버튼 -->
<div class="header-btn">
<button type="button"
class="btn btn-primary"
data-bs-toggle="modal"
data-bs-target="#exampleModal"
data-bs-whatever="@mdo">글쓰기</button>
</div>
우측 상부 글쓰기 버튼 html
모달 구현은 부트스트랩을 사용했다
<!-- index.html -->
<!--글쓰기 버튼 누르면 나오는 모달창 html-->
<div class="modal fade"
id="exampleModal"
tabindex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="form-div" id="form">
<br>
<h1 style="text-align:center;">포스팅하기</h1>
<br>
<div class="input-group mb-3">
<input type="text"
class="form-control"
id="youtube_url"
placeholder="https://youtu.be/@@@@"
aria-label="youtube url"
aria-describedby="button-addon2" required>
<button class="btn btn-outline-secondary"
type="button"
id="button-addon2"
onclick="check_youtube_url()">확인</button>
</div>
<div class="youtube-cover">
<div class="d-flex justify-content-center loading">
<div class="spinner spinner-border text-primary" id="spinner" role="status"></div>
<span class="text-primary"
style="font-size:16px; margin-right:10px;
font-weight:bold; line-height:25px;
margin-left: 10px"
id="spinner_text">크롤링중...</span>
</div>
<div style="width:500px; height:280px" id="youtube_embed" class="youtubes">
</div>
</div>
<div class="input-group">
<span class="input-group-text">제목과 아티스트</span>
<input type="text"
aria-label="title"
class="form-control"
placeholder="곡 제목" id="title" required>
<input type="text"
aria-label="artist"
class="form-control"
placeholder="아티스트명" id="artist" required>
</div>
<div class="input-group mb-3">
<label class="input-group-text" for="genre">장르</label>
<select class="form-select" id="genre" required>
<option value="" selected>--선택하기--</option>
<option value="인디">인디 (Indipendent)</option>
<option value="발라드">발라드 (Ballad)</option>
<option value="댄스">댄스 (Dance)</option>
<option value="힙합">힙합 (HipHop)</option>
<option value="팝송">팝송 (Pop)</option>
<option value="락">락 (rock)</option>
</select>
</div>
<div class="input-group mb-3">
<label class="input-group-text" for="star">별점</label>
<select class="form-select" id="star" required>
<option value="" selected>--선택하기--</option>
<option value="1">⭐</option>
<option value="2">⭐⭐</option>
<option value="3">⭐⭐⭐</option>
<option value="4">⭐⭐⭐⭐</option>
<option value="5">⭐⭐⭐⭐⭐</option>
</select>
</div>
<div class="input-group">
<span class="input-group-text">코멘트</span>
<textarea class="form-control"
aria-label="comment"
placeholder="감상을 적어주세요!"
id="comment" required></textarea>
</div>
<br>
<div class="mybtns" style="display: inline-block; margin: 0 5px; float: right">
<button onclick="posting()" type="button" class="btn btn-dark">글쓰기</button>
<button type="button" class="btn btn-outline-dark" data-bs-dismiss="modal">돌아가기</button>
</div>
</div>
</div>
</div>
</div>
<!-- 모달창 html end -->
모달창에서 유튜브 링크를 입력한 후 확인 버튼을 누르면 실행되는 check_youtube_url()과 글 작성 완료 후 DB로 저장하기 위해 누르는 글쓰기 버튼의 posting()함수를 작성했다
/* 글쓰기 버튼 누르면 나오는 모달 창 스크립트 */
function posting() {
let youtube_url = $('#youtube_url').val()
let music = $('#title').val()
let artist = $('#artist').val()
let sort = $('#genre').val()
let star = $('#star').val()
let comment = $('#comment').val()
let formData = new FormData();
formData.append("youtube_url_give", youtube_url);
formData.append("music_give", music);
formData.append("artist_give", artist);
formData.append("sort_give", sort);
formData.append("star_give", star);
formData.append("comment_give", comment);
fetch('/write_main', { method: "POST", body: formData }).then((res) => res.json()).then((data) => {
alert(data['msg'])
/* 글쓰기가 완료되면 웹페이지 다시 리로드 */
window.location.reload()
})
}
/* 글쓰기 모달창에서 유튜브 주소 입력 후 확인 버튼 누르면 실행되는 check_youtube_url함수 */
function check_youtube_url() {
let url = $('#youtube_url').val()
let formData = new FormData();
/* GET요청동안 스피너 보이기 (로딩중...) */
spinner.style.visibility = 'visible';
spinner_text.style.visibility = 'visible';
formData.append("youtube_url_give", url);
fetch('/write', { method: "POST", body: formData }).then((res) => res.json()).then((data) => {
/* 유튜브 주소가 아닌 다른 주소 입력시 백엔드에서 'error'를 리턴*/
if (data['receive'] === 'error') {
alert('유튜브 주소를 다시 확인해주세요.\n공식 뮤비 주소여야 잘 돼요!');
spinner.style.visibility = 'hidden';
spinner_text.style.visibility = 'hidden';
} else{
html_cache = `
<iframe width="500" height="280"
src="//www.youtube.com/embed/${data['receive']['url']}"
title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay;
clipboard-write; encrypted-media; gyroscope;
picture-in-picture; web-share" allowfullscreen></iframe>`
$('#youtube_embed').empty() #appending전에 비워줌
$('#youtube_embed').append(html_cache)
/* 제목과 아티스트 입력 폼에 크롤링한 내용 삽입 */
document.getElementById("title").value = data['receive']['music'];
document.getElementById("artist").value = data['receive']['artist'];
/* 스피너 감추기 */
spinner.style.visibility = 'hidden';
spinner_text.style.visibility = 'hidden';
}
})
}
(2) Section
- css
/* index.html */
/* section css */
section {
width: 70%;
min-width: 1200px; /* 최소너비 제한 */
height: max-content;
margin: 0 auto;
}
.wrap {
width: 100%;
height: max-content;
}
.section {
width: 85%;
height: 820px;
margin: 0 auto 0 auto;
float: left;
justify-content: center;
align-items: center;
}
.title-cover {
width: 100%;
}
.card-title {
float: left;
width: 95%;
}
.artist-card {
float: left;
width: 100%;
height: 20px;
margin: 2px 0 0 2px;
color: #808080;
background-color: #FFF;
font-size: 17px;
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.mycards {
width: 90%;
margin: 30px auto;
}
.like-button {
width: 28px;
float: right;
display: block;
margin: 0 5px 10px 0;
}
.like-button:hover { /* 좋아요 버튼 hovering시 filter로 빨간색으로 반전 */
filter: invert(16%)
sepia(89%)
saturate(6054%)
hue-rotate(358deg)
brightness(97%)
contrast(113%);
}
.like {
float: right;
display: block;
margin: 0 10px 10px 0;
background-color: black;
border: 2px solid black;
border-radius: 10px;
width: 40px;
height: 30px;
text-align: center;
color: white;
}
/* section css end */
메인 섹션 페이지 css
헤더과 같이 width:70%, min-width: 1200px로 사이즈를 맞춤
각 컨텐츠 카드의 css와 내부 좋아요 버튼에 대한 css도 작성
- 3. 메인 카드 컨텐츠
<!-- index.html -->
<!-- contents card html -->
<div class="mycards">
<div class="row row-cols-1 row-cols-md-3 g-4 " id="cards-box">
</div>
</div>
<!-- contents card html end -->
메인 카드 컨텐츠 html
페이지 로딩 시 listing()함수를 호출해 fetch로 백엔드에 DB를 호출하고 cards-box에 호출된 데이터를 하나씩 appending함
/* index.html */
/* db 정보 가져와서 보여주는 listing함수(GET요청) */
function listing() {
fetch('/smm').then((res) => res.json()).then((data) => {
/* 백엔드에서 dumps()로 보낸 데이터를 JSON.parse로 감싸 json형식으로 만들어줌 */
let rows = JSON.parse(data['result'])
$('#cards-box').empty()
rows.reverse().forEach((a) => { // reverse()로 DB를 최근순으로 호출
let url = a['youtube_url']
let music = a['music'] //곡이름
let artist = a['artist']
let sort = a['sort'] //장르
let star = a['star']
let comment = a['comment']
let like = a['like']
let _id = a['_id']['$oid']
let star_repeat = '⭐'.repeat(star)
let cache_html = `<div class="col">
<div class="card h-100">
<iframe width=auto height=auto src="//www.youtube.com/embed/${url}"
title="YouTube video player"
frameborder="0"
allow="accelerometer;
autoplay;
clipboard-write; encrypted-media;
gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
<div class="card-body">
<h5 class="card-title"><b>${music}</b></h5>
<p class="artist-card">${artist}</p>
<p class="genre">${sort}</p>
<p>${star_repeat}</p>
<p>${comment}</p>
</div>
<div>
<p class="like">${like}</p>
<img class="like-button"
name="like_img"
id="${_id}"
src="../static/img/thumbup.svg">
</div>
</div>
</div>`
$('#cards-box').append(cache_html) /* 호출된 DB 데이터를 하나씩 appending */
})
})
}
/* 쿠키 관련 코드 */
// 해당 이름의 쿠키를 삭제한다.
function delCookie(_name) {
var expireDate = new Date();
//어제 날짜를 쿠키 소멸 날짜로 설정한다.
expireDate.setDate(expireDate.getDate() - 1);
document.cookie = _name + "= " + "; expires=" + expireDate.toGMTString() + "; path=/";
alert(document.cookie);
}
// 이름, 값, 시간(초)로 쿠키를 생성한다.
function setCookie(_name, _value, _time) {
var cookie = _name + "=" + escape(_value) + "; path=/;"
if (typeof _date != 'undefined') {
var todayDate = new Date();
//todayDate.setDate(todayDate.getDate() + _date); // 날짜
todayDate.setTime(todayDate.getTime() + 60 * 1000 * _time); // 시간 (milli seconds)
cookie += "expires=" + todayDate.toGMTString() + ";"
}
document.cookie = cookie;
return 1
}
// 해당 이름의 쿠키를 확인한다.
function getCookie(_name) {
try {
var nameOfCookie = _name + "="; // 'test='
var x = 0;
while (x <= document.cookie.length) {
var y = (x + nameOfCookie.length); // 시작값 + 쿠키이름 길이
if (document.cookie.substring(x, y) == nameOfCookie) // _name과 일치하는 경우
{
if ((endOfCookie = document.cookie.indexOf(";", y)) == -1) // 구분자(;)를 못 찾은 경우
endOfCookie = document.cookie.length;
return alert("이미 추천을 하셨습니다");
}
x = document.cookie.indexOf(" ", x) + 1; // 쿠키 구분자로 이동 (; 후 공백)
if (x == 0) break; // 이후 다른 쿠키가 없는 경우 종료
}
}
catch (err) {
alert("getCookie[" + err.description + "]");
}
return 1
}
/* 쿠키 관련 코드 end */
/* 추천 버튼 */
/* 추천 버튼이 같은 것이 여러 개 생성되므로 추천버튼을 클릭한 아들 요소의 class(like-button)를 찾는다 */
$(document).on('click', '.like-button', function (event) {
let id = event.target.id;
/* 생성된 쿠키가 없어서 getCookie()함수가 1를 리턴하면 setCookie()함수로 id이름의 새로운 쿠키 생성 */
if (getCookie(id) === 1) {
setCookie(id, "ok", 600);
let formData = new FormData();
formData.append("id_give", id);
fetch('/like', { method: "POST", body: formData }).then((res) => res.json()).then((data) => {
alert(data['msg'])
window.location.reload()
})
}
})
/* 추천 버튼 end */
app.py에서 db.music.find()로 mongoDB의 모든 데이터를 긁어오면 파이썬 객체이며 bson형식으로 들어오기 때문에 bson.json_util모듈의 dumps()함수로 감싸 json형식으로 변환한 뒤 다시 index.html로 보내준다
또한 좋아요 기능을 보면 mongoDB의 Primary Key인 _id는 bson형식이므로 bson.objectid모듈의 ObjectId()함수로 bson형식으로 변환해서 추천버튼을 누른 id값을 찾는다
찾은 뒤에 해당 id의 like값에 +1을 해서 다시 DB에 저장한다
마지막으로 좋아요를 중복으로 누르는 것을 방지하기 위해 쿠키를 사용했다.
# app.py
# db에 저장된 데이터 불러와서 index.html로 리턴
@app.route("/smm", methods=["GET"])
def smm_get():
all_data = list(db.music.find())
return jsonify({'result': dumps(all_data)})
# like기능
@app.route("/like", methods=["POST"])
def like_up():
id_receive = request.form['id_give']
# _id값 매치해서 like값 조회
present_like = db.music.find_one({"_id": ObjectId(id_receive)},{'_id':False, "like":True})
print(present_like['like'])
db.music.update_one({'_id': ObjectId(id_receive)},{'$set':{'like':present_like['like'] + 1}})
return jsonify({'msg': '추천완료!'})
(3) nav
- css
/* index.html */
/* nav css */
.nav {
width: 15%;
height: 820px;
}
/*sidebar css*/
.sidebar {
background-color: black;
width: 100%;
right: 32%;
height: 430px;
margin-top: 3%;
}
.sidebar div {
padding: 6px;
display: block;
text-decoration: none;
padding-bottom: 6px;
text-align: center;
}
.sidebar_title {
margin-top: 5%;
}
.sidebar_content {
border-radius: 5px;
border: 5px double red;
margin: 0 5px 0 5px;
}
.sidebar a:hover {
cursor: pointer;
opacity: .7;
}
/* rank css */
.ranking_system {
background-color: rgb(245, 245, 245);
width: 100%;
right: 32%;
height: 400px;
margin-top: 3%;
}
.rank_cover {
width: 100%;
height: 400px;
border: 2px solid black;
margin: 0 auto;
}
.rank_title {
background-color: black;
height: 40px;
text-align: center;
}
.rank_main {
height: 360px;
}
div.rank_list {
border: 1px black solid;
width: 100%;
height: 356px;
float: left;
display: inline;
}
.rank_each {
list-style: none;
height: 71px;
background-color: white;
}
.rank {
display: inline;
float: left;
width: 10%;
height: 70%;
margin: 9px 0 0 8px;
background-color: white;
color: grey;
}
.rank-top {
color: rgb(5, 175, 5);
font-size: 18px;
font-style: italic;
font-weight: bolder;
}
.rank-top-one {
color: rgb(253, 53, 97);
font-size: 24px;
font-style: italic;
font-weight: bolder;
}
.song-cover {
display: block;
float: left;
width: 60%;
height: 70%;
margin: 9px 0 0 8px;
}
.song {
float: left;
width: 100%;
height: 14px;
margin: 5px 0 0 5px;
font-size: 14px;
line-height: 14px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.artist {
float: left;
width: 100%;
height: 14px;
margin: 10px 0 0 2px;
color: #808080;
background-color: #FFF;
font-size: 13px;
line-height: 12px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.chart-like-cover {
display: block;
float: right;
width: 10%;
height: 70%;
margin: 9px 5px 0 0px;
}
.chart-like {
float: right;
display: block;
margin: 6px 0px;
background-color: black;
border: 1px solid black;
border-radius: 5px;
width: 24px;
height: 18px;
text-align: center;
color: white;
font-size: 12px;
}
/* rank css end */
/* nav css end */
- 4. 카테고리
이 부분은 팀원이 구현한 부분이다.
각 카테고리를 누르면 백엔드에 각 카테고리에 해당하는 장르만 find()함수로 sort해 다시 cards-box에 appending한다
<!-- index.html -->
<!-- categorizing -->
<div class="sidebar">
<div class="sidebar_title">
<h2 style="font-size: 26px; color:red">CATEGORY</h2>
</div>
<div class="sidebar_content">
<div style="font-size: 24px; color:#e6e6e6"><a onclick="recommend.run()">추천곡</div>
<div style="font-size: 24px; color:#e6e6e6"><a onclick="indi.run()">인디</a></div>
<div style="font-size: 24px; color:#e6e6e6"><a onclick="balad.run()">발라드</a></div>
<div style="font-size: 24px; color:#e6e6e6"><a onclick="dance.run()">댄스</a></div>
<div style="font-size: 24px; color:#e6e6e6"><a onclick="hiphop.run()">힙합</a></div>
<div style="font-size: 24px; color:#e6e6e6"><a onclick="pop.run()">팝송</a></div>
<div style="font-size: 24px; color:#e6e6e6"><a onclick="rock.run()">락</a></div>
</div>
</div>
<!-- categorizing end -->
onclick="###.run()"으로 클릭 시 각 객체의 run()함수를 호출해 sorting후 다시 메인페이지에 appending한다
/* Categorizing */
class Sort { /* Sort 클래스 생성 */
constructor(sort_name) {
this.sort_name = sort_name;
}
run() { /* run()함수 작성 */
fetch('/music').then((res) => res.json()).then((data) => {
let rows2 = data[this.sort_name];
$('#cards-box').empty()
rows2.forEach((a) => {
let url = a['youtube_url'];
let music = a['music'];
let artist = a['artist'];
let sort = a['sort'];
let star = a['star'];
let comment = a['comment'];
let like = a['like'];
let star_repeat = '⭐'.repeat(star);
let temp_html = `<div class="col">
<div class="card h-100">
<iframe width=auto height=auto src="//www.youtube.com/embed/${url}"
title="YouTube video player"
frameborder="0"
allow="accelerometer;
autoplay;
clipboard-write; encrypted-media;
gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
<div class="card-body">
<h5 class="card-title"><b>${music}</b></h5>
<p class="artist-card">${artist}</p>
<p class="genre">${sort}</p>
<p>${star_repeat}</p>
<p>${comment}</p>
</div>
<div>
<p class="like">${like}</p>
<img class="like-button"
name="like_img"
id="${_id}"
src="../static/img/thumbup.svg">
</div>
</div>
</div>`
$('#cards-box').append(temp_html)
})
})
}
}
/* 각 인스턴스를 생성한다 */
var recommend = new Sort('result')
var indi = new Sort('result2')
var balad = new Sort('result3')
var dance = new Sort('result4')
var hiphop = new Sort('result5')
var pop = new Sort('result6')
var rock = new Sort('result7')
/* Categorizing end */
기존의 팀원분께서 7개의 function으로 호출했던 부분을 내가 마지막에 class를 이용해 하나로 합쳤다
class를 적절히 사용해야 할 때를 알 수 있었다
# app.py
#카테고리별로 정보 내보내기
@app.route("/music", methods=["GET"])
def music_get():
all_musics = list(db.music.find({},{'_id':False}))
indi_musics = list(db.music.find({'sort':'인디'},{'_id':False}))
balad_musics = list(db.music.find({'sort':'발라드'},{'_id':False}))
dance_musics = list(db.music.find({'sort':'댄스'},{'_id':False}))
hip_musics = list(db.music.find({'sort':'힙합'},{'_id':False}))
pop_musics = list(db.music.find({'sort':'팝송'},{'_id':False}))
rock_musics = list(db.music.find({'sort':'락'},{'_id':False}))
return jsonify({'result': all_musics,
'result2':indi_musics,
'result3':balad_musics,
'result4':dance_musics,
'result5':hip_musics,
'result6':pop_musics,
'result7':rock_musics})
- 5. 인기차트
<!-- index.html -->
<!--Ranking System-->
<div class="ranking_system">
<div class="rank_cover">
<div class="rank_title">
<h2 style="font-size: 26px; color:#e6e6e6">인기차트</h2>
</div>
<div class="rank_main">
<div class="rank_list">
<li class="rank_each"><span class="rank rank-top-one">1</span>
<div class="song-cover">
<span class="song" id="music1"></span>
<span class="artist" id="artist1"></span>
</div>
<div class="chart-like-cover">
<span class="chart-like" id="like1"></span>
</div>
</li>
<li class="rank_each"><span class="rank rank-top">2</span>
<div class="song-cover">
<span class="song" id="music2"></span>
<span class="artist" id="artist2"></span>
</div>
<div class="chart-like-cover">
<span class="chart-like" id="like2"></span>
</div>
</li>
<li class="rank_each"><span class="rank rank-top">3</span>
<div class="song-cover">
<span class="song" id="music3"></span>
<span class="artist" id="artist3"></span>
</div>
<div class="chart-like-cover">
<span class="chart-like" id="like3"></span>
</div>
</li>
<li class="rank_each"><span class="rank">4</span>
<div class="song-cover">
<span class="song" id="music4"></span>
<span class="artist" id="artist4"></span>
</div>
<div class="chart-like-cover">
<span class="chart-like" id="like4"></span>
</div>
</li>
<li class="rank_each"><span class="rank">5</span>
<div class="song-cover">
<span class="song" id="music5"></span>
<span class="artist" id="artist5"></span>
</div>
<div class="chart-like-cover">
<span class="chart-like" id="like5"></span>
</div>
</li>
</div>
</div>
</div>
</div>
<!--Ranking System end -->
DB에 저장되어 있는 좋아요를 많은 순으로 sorting해 5개만 가져와서 보여주는 식으로 구현
/* index.html */
/* 인기차트 */
function chart() {
fetch('/chart').then((res) => res.json()).then((data) => {
let rows = data['result']
let i = 1
rows.forEach((a) => {
let url = a['youtube_url']
let music = a['music']
let artist = a['artist']
let like = a['like']
$(`#music${i}`).text(music);
$(`#artist${i}`).text(artist);
$(`#like${i}`).text(like);
i = i + 1
})
})
}
메인 페이지 로딩 시 위 함수를 실행해 백엔드에서 관련 데이터를 받아온다
# app.py
# 차트
@app.route("/chart", methods=["GET"])
def chart_get():
all_data = list(db.music.find({}, {'_id':False}).sort([("like", -1)]).limit(5))
return jsonify({'result': all_data})
pymongo의 find()함수로 _id값을 제외하고 모든 데이터를 가져온 뒤 sort()함수로 like순으로 내림차순으로 정렬한다.
그리고 limit()함수로 상위 5개만 뽑아서 all_data로 넘겨준다
결과물
http://smmproject.eba-smubgqvv.ap-northeast-2.elasticbeanstalk.com/
프로젝트 완료 후의 감상
인생 첫 프로젝트에서 팀장을 맡아 조금 정신 없었지만 재밌었다.
팀원들 간의 소통으로 더 나은 결과물을 만들 수 있었다.
더 다양한 프레임워크를 배워서 더 뛰어난 결과물을 만들고 싶다.
'프로그래밍 > 생각나는대로 프로젝트' 카테고리의 다른 글
[프로젝트] 06. DRF 경매 서비스 - 20세기 박물관 (0) | 2023.05.16 |
---|---|
[프로젝트] 05. Django SNS CaMu (0) | 2023.04.16 |
[프로젝트] 04. Django 무신사 재고 관리 시스템 (0) | 2023.04.10 |
[프로젝트] 03. Text RPG게임 냥이 키우기 (0) | 2023.04.03 |
[프로젝트] 02. 팀 소개 프로젝트 Brain-8 (0) | 2023.03.19 |