Team Cannon Project

Fire Takes

Overview:

Fire Takes is the world's most popular album-review site, where users can share their opinions on today's hottest new releases and the greatest hits of yesterday. Users can search for albums to see current opinions, stream samples (or entire tracks, thanks to our partners over at Spotify), and add their own reviews. Registered users have a history of albums they have reviewed and are able to contribute to the growing conversation.


Meet Team Cannon!

Cainan Barboza

https://github.com/CainanB
Role: Lead back-end organization. API calls, database management, routing, and UI development.

Micah Peterson

https://github.com/petersonprojects
Role: Lead UI/UX development. HTML and CSS styling, animations, UI development, and functionality.

Dan Gelok

https://github.com/dgelok
Role: Project management and back-end support. Authentication, database setup and management, and design.

Tools used in the project:

Languages:

  • HTML
  • CSS
  • JavaScript (via Node.js)
  • SQL (via Sequelize)

Modules (for Node.js):

  • Sequelize
  • Express
  • EJS
  • bcrypt
  • bcrypt-js
  • body-parser
  • cookie-parser
  • multer
  • pg
  • pg-hstore

Other:

  • Node.js
  • Sequelize
  • GIMP
  • Postico
  • Postman
  • ElephantSQL

APIs

Base Objectives:

  • Users are able to register unique usernames, and sign in and out
  • Users are able to search for and access any album available via Spotify API
  • Authentication required for submitting new reviews
  • All reviews are stored in database and listed each time the corresponding album is searched
  • All reviews made by a single user are displayed on profile page
  • Navbar shifts between register/profile and login/logout, depending on status
  • Team goals: increased communication, smoother version control, more independent development

Stretch Goals Completed:

  • Users can upload a personal photo to their profile
  • Users can listen to 30-second snippets of album tracks via Spotify API - or full tracks, if signed in through Spotify

Stretch Goals Future

  • Create a cumulative average score from total user reviews
  • Place user photo by user reviews on album page
  • Enable 'follow' status for albums or artists, with alerts to new reviews
  • Provide chat room
  • Enable comment responses per review
  • Place most recent reviews on landing page
  • Place users with highest num of reviews on landing page

Challenges & Solutions:

Some of the biggest challenges we faced with this project build included:

Challenge: Simultaneous DB/API calls

Solution: Refactoring code allowed us to store necessary API data in our database, making only a single call necessary, instead of an initial call to our database and then a followup call to the Spotify API.

Challenge: More advanced design (compared to earlier projects)

Solution: Additional CSS development provided animations, working with GIMP provided a unique logo, and a series of authentication checks changed

Challenge: Photo uploads

Solution: Research led us to Multer middleware, and documentation research provided the required solution.

Challenge: We got sick

Solution: Naps.


Code Snippets:

This CSS snippet showcases custom-built hover animations for the navbar:

.nav-link:hover
{
    box-shadow:
        1px 1px rgb(255, 11, 11),
        2px 2px rgb(255, 11, 11),
        3px 3px rgb(255, 11, 11);

    -webkit-transform: translateX(-3px);
    transform: translateX(-3px);
    color: orange;
}

.custom-toggler.navbar-toggler {
    border-color: orange;
} 

.custom-toggler.navbar-toggler-icon {
    color: orange;
} 

.navbar-dark .custom-toggler .collapzoid:hover{
    color:pink;
}

.threed:hover{
    box-shadow:
        1px 1px rgb(255, 11, 11),
        2px 2px rgb(255, 11, 11),
        3px 3px rgb(255, 11, 11);

    -webkit-transform: translateX(-3px);
    transform: translateX(-3px);
}


This Jquery snippet is used to handle the registration of a new user:

$("#registerButton").click(async(e) => {
    e.preventDefault(); 
    let passw = $('#password').val()
    let passConf = $('#passwordConfirm').val()
    // console.log(passw)
    // console.log(passConf)

    if (passw != passConf) {
        $('#passwordFailMessage').show();
        $('#registerFailMessage').hide();
        // console.log(`${$('#password')}`)
        // $('#password').value = "";
        // $('#passwordConfirm').value = "";
    }
    else {
        fetch('/registration',{
            method: "POST",
            headers: {'Content-Type' : 'application/json'},
            body: JSON.stringify({
                username : $('#username').val(),
                password: $('#password').val()
                
            })
        })
        .then(results => results.json())
        .then(result => {
            // console.log(result) 
            if(result == "success"){
                $('#exampleModal').modal('toggle');
            }else{
                $('#registerFailMessage').show();
                $('#passwordFailMessage').hide();
            }
        })
    }
})

This snippet, found in our routes, handles a POST request and allows for the creation of a new review, thus enabling us to tether information from Spotify API into our database:

router.post('/albums', async (req, res) => {
    // let username = req.session.username;
    let userID = parseInt(req.session.userID)
    let review = req.body.reviewText
    if(req.body.rating == 'undefined' || req.body.rating == undefined)
    {
        req.body.rating = 5;
    }
    let rating =  parseInt(req.body.rating);
    let albumID = req.body.albumID
    let albumTitle = req.body.albumName;
    let aristName = req.body.artistName;
    let albumURL = req.body.albumArt;

  
    // console.log(`userID: ${userID}, review: ${review}, rating: ${rating}, albumID: ${albumID}`);

    db.reviews.create({
        authorID: userID,
        stars: rating,
        text: review,
        albumID: albumID,
        aristName: aristName,
        albumTitle: albumTitle,
        albumURL: albumURL

    })
    .then(user =>{
        // console.log("review inserted succesfully")
        res.redirect('/')
    })
    .catch(error =>{
        console.log(error)
    })
})

This JS snippet is used to fetch user review data from the database, then populate the profile page:

const getUserInfo = async () =>{
    $("#userReviewsBlock").html("")
    const reviews = await fetch('/userInfo')
    let userReviews = await reviews.json();
    userReviews.reverse();  // REVERSE THE RESULTS SO THE LATEST REVIEW MADE SHOWS FIRST
    userReviews.forEach((review,i)=>{ // LOOP THROUGH EACH REVIEW AND CREATE HTML ELEMENTS
        let starHTML = '';
        for(let i = 0; i < review.stars;i++)
        {
            starHTML += '<span class="one fa fa-star fa-2x checked"></span>'
        }
        let htmlEl = `
        <div id="${review.id}Container">
            <div class="row">
                <h1 class="ml-2"> ${review.albumTitle} </h1>
            </div>
            <div class="row ml-2">
                <h3> ${review.aristName} </h3>
            </div>
                <div class="row ml-2 mt-2">
                <div class="col-xl-3 mt-1 pl-0 pr-0">
                    <a href="/albums"><img class="cover mr-5" src="${review.albumURL}" height="200" width="200" alt=""></a>
                </div>
                <div class="col-xl-8 ml-xl-3 mt-1 pl-0 d-flex justify-content-start">
                    <blockquote id="${review.id}currentReviewText" class="lead blockquote text-left ml-0 mt-0 pt-1 h-100 w-100">
                        ${review.text}
                    </blockquote>
                </div>
            </div>
            <div id="stars" class="col-2 pt-1 mt-1  ml-0 d-flex justify-content-start">
                ${starHTML}
            </div>
            
            <a href="#" id="${review.id}" class="edit ml-2 mt-4" style="display:inline-block;"><i class="editReviewButton fa fa-pencil-square-o" aria-hidden="true"></i> Edit Review</a>
            <a href="#" id="${review.id}" class="delete edit ml-2 mt-4" style="display:inline-block;"><i class="deleteReviewButton fa fa-trash-o" aria-hidden="true"></i> Delete Review</a>
            <hr style="height: 1px;
            background-color: orangered;
            border: none;margin-top:0.5rem">
            </div>
        `
        $("#userReviewsBlock").append(htmlEl)

        // SAVE A UNIQUE REVIEW EDIT FORM FOR EACH REVIEW AND PUSH TO ARRAY
        reviewForms.push({
            id: review.id,
            html: `<form id="${review.id}form">
            <input id="${review.id}hiddenInput" type="hidden" name="albumID" value="${review.id}">
            <textarea id="${review.id}editedReviewText" name="editedReviewText">${review.text}</textarea>
            <input class="btn btn-danger editedReviewSubmitButton" type="submit" id="${review.id}">
            </form>`
        })
        
    });
}