프로그래밍/생각나는대로 프로젝트

[프로젝트] 01. 음악 추천 서비스 SMM

Churnobyl 2023. 3. 11. 00:21
728x90
반응형

SMMproject

개요: 추천하는 음악을 올리고 좋아요 버튼으로 다른 사람들의 반응을 볼 수 있는 사이트. 인기차트를 이용해 가장 추천수가 높은 5개를 보여준다. 카테고리를 이용해 각 장르 별로 목록을 볼 수 있다.

 

 


프로젝트 정보


  • 프로젝트: SMM 프로젝트 (내일배움캠프 개강 전 미니 프로젝트)
  • 개발기간: 2023.03.02 - 2023.03.09 ( 7일 )
  • 역할
    • 팀장
    • Layout
    • css
    • 글쓰기
    • 메인section 화면
    • 좋아요
    • 인기차트
  • 사용 언어: HTML, JavaScript(Vanilla), Python
  • 사용 라이브러리:
    • HTML, JavaScript
      1. Jquery - 전체적인 동적 웹페이지 구현
      2. Bootstrap(프레임워크) -  각 개체들 구현
      3. Google Font - 웹페이지 폰트 변경
    • Python
      1. Flask - 웹 개발 프레임워크 (import Flask, render_template, request, jsonify)
      2. pymongo - MongoDB로 DB구성
      3. bson - MongoDB Primary Key인 _id값(bson형식) 변환
        더보기
        from bson.json_util import dumps / bson.objectid import ObjectId
      4. ytmusicapi - 유튜브 비공식 api, 크롤링에 사용
    • 배포
      • awsebcli - AWS Elastic Beanstalk 서비스로 웹사이트 배포

 

 


프로젝트 구성


파일구조

기본적인 구조는 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 (메인 이미지)

 

 

★ 화면구성 및 코드

 

SMMproject 화면 구성

 


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

 

Share My Music

내일배움캠프 시작 전 6조 첫번째 프로젝트

smmproject.eba-smubgqvv.ap-northeast-2.elasticbeanstalk.com

 

 


프로젝트 완료 후의 감상

인생 첫 프로젝트에서 팀장을 맡아 조금 정신 없었지만 재밌었다.

팀원들 간의 소통으로 더 나은 결과물을 만들 수 있었다.

더 다양한 프레임워크를 배워서 더 뛰어난 결과물을 만들고 싶다.

 

반응형