Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Insights tab to visualize user's Spotify insights #4

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# app/__init__.py
from flask import Flask, render_template
from authlib.integrations.flask_client import OAuth
from flask import Flask
from flask_login import LoginManager
from flask_migrate import Migrate

from app.database import db

migrate = Migrate()
login_manager = LoginManager()
oauth = OAuth()


def create_app(config_object="config"):
Expand All @@ -16,9 +18,14 @@ def create_app(config_object="config"):
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
oauth.init_app(app)

from app.blueprints.auth import auth_bp
from app.blueprints.auth import auth_bp, register_oauth
from app.blueprints.insights import insights_bp

register_oauth(app, oauth)

app.register_blueprint(auth_bp)
app.register_blueprint(insights_bp)

return app
1 change: 1 addition & 0 deletions app/blueprints/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
from . import routes, models, forms

auth_bp = routes.auth_bp
register_oauth = routes.register_oauth
11 changes: 4 additions & 7 deletions app/blueprints/auth/models.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import flask_login

from app.database import db


class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
class User(db.Model, flask_login.UserMixin):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
spotify_id = db.Column(db.String(120), unique=True, nullable=False)
# Add other necessary columns
access_token = db.Column(db.String(120), nullable=False)
refresh_token = db.Column(db.String(120), nullable=False)
# example `expires_at` value: 1679647406
expires_at = db.Column(db.Integer, nullable=False)
# Define relationships with other models (e.g., posts, groups)

# Flask-Login integration
Expand Down
101 changes: 46 additions & 55 deletions app/blueprints/auth/routes.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,74 @@
# app/blueprints/auth/routes.py
from flask import Blueprint, redirect, render_template, request, session, url_for
from authlib.integrations.flask_client import OAuth
from flask import Blueprint, Flask, flash, redirect, render_template, session, url_for
from flask_login import current_user, login_required, login_user, logout_user

from app import login_manager
from app import login_manager, oauth
from app.database import db

from .forms import RegistrationForm
from .models import User
from .providers import SpotifyAuthProvider

auth_bp = Blueprint("auth", __name__)


@login_manager.unauthorized_handler
def unauthorized_callback():
"""Redirect unauthorized users to Login page."""
return redirect(url_for("auth.unauth_home"))
def register_oauth(app: Flask, oauth: OAuth):
oauth.register(
name="spotify",
client_id=app.config["SPOTIFY_CLIENT_ID"],
client_secret=app.config["SPOTIFY_CLIENT_SECRET"],
access_token_url="https://accounts.spotify.com/api/token",
access_token_params=None,
authorize_url="https://accounts.spotify.com/authorize",
authorize_params=None,
api_base_url="https://api.spotify.com/v1/",
client_kwargs={
"scope": "user-library-read user-read-recently-played user-top-read user-read-currently-playing user-read-email"
},
)


@login_manager.user_loader
def load_user(user_id):
"""Check if user is logged-in on every page load."""
return User.query.get(int(user_id))


@auth_bp.route("/callback")
def spotify_callback():
code = request.args.get("code")

auth_provider = SpotifyAuthProvider()
auth_provider.get_access_token(code, check_cache=False)
me = auth_provider.me()

spotify_id = me["id"]
user = User.query.filter_by(spotify_id=spotify_id).first()
if user:
# User exists, log them in and redirect to authorized home page
login_user(user)
return redirect(url_for("auth.home"))
else:
# User does not exist, redirect to registration form
# Store the tokens in the session
session["spotify_id"] = spotify_id
cached_tokens = auth_provider.get_cached_tokens()
session["access_token"] = cached_tokens["access_token"]
session["refresh_token"] = cached_tokens["refresh_token"]
session["expires_at"] = cached_tokens["expires_at"]

return redirect(url_for("auth.register"))


@auth_bp.route("/register", methods=["GET", "POST"])
def register():
# If user is authenticated, redirect to /home
@auth_bp.route("/spotify/login")
def spotify_login():
"""Redirect user to Spotify OAuth page to login and authorize the app."""
if current_user.is_authenticated:
return redirect(url_for("auth.home"))
redirect_uri = url_for("auth.spotify_authorize", _external=True)
return oauth.spotify.authorize_redirect(redirect_uri)

form = RegistrationForm()

if form.validate_on_submit():
@auth_bp.route("/callback")
def spotify_authorize():
token = oauth.spotify.authorize_access_token() # Get access token
response = oauth.spotify.get("me") # Get user info
session["spotify_user_info"] = response.json() # Save user info to session
user = User.query.filter_by(spotify_id=session["spotify_user_info"]["id"]).first()
if not user:
# User doesn't exist, create a new user
user = User(
username=form.username.data,
email=form.email.data,
spotify_id=session["spotify_id"],
access_token=session["access_token"],
refresh_token=session["refresh_token"],
expires_at=session["expires_at"],
username=session["spotify_user_info"]["display_name"],
email=session["spotify_user_info"]["email"],
spotify_id=session["spotify_user_info"]["id"],
)
db.session.add(user)
db.session.commit()
flash("Account created!", "success")

# Remove the stored variables from the session
session.pop("spotify_id", None)
session.pop("access_token", None)
session.pop("refresh_token", None)
session.pop("expires_at", None)
# Remove the Spotify info from the session
session.pop("spotify_user_info", None)
login_user(user)
return redirect(url_for("auth.home"))

return redirect(url_for("auth.home"))

return render_template("register.html", form=form)
@login_manager.unauthorized_handler
def unauthorized_callback():
"""Redirect unauthorized users to Login page."""
return redirect(url_for("auth.unauth_home"))


@auth_bp.route("/")
Expand All @@ -86,9 +77,9 @@ def unauth_home():
if current_user.is_authenticated:
return redirect(url_for("auth.home"))
else:
auth_provider = SpotifyAuthProvider()
auth_url = auth_provider.get_authorize_url()
return render_template("unauth_home.html", auth_url=auth_url)
return render_template(
"unauth_home.html", spotify_auth_url=url_for("auth.spotify_login")
)


@auth_bp.route("/home")
Expand Down
4 changes: 4 additions & 0 deletions app/blueprints/insights/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# app/blueprints/insights/__init__.py
from . import routes, forms

insights_bp = routes.insights_bp
Empty file.
72 changes: 72 additions & 0 deletions app/blueprints/insights/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# app/blueprints/insights/routes.py
from flask import Blueprint, render_template
from pyecharts import options as opts
from pyecharts.charts import Bar
import json

insights_bp = Blueprint("insights", __name__)


@insights_bp.route("/insights")
def home():
import random
import datetime

import pyecharts.options as opts
from pyecharts.charts import Calendar

begin = datetime.date(2017, 1, 1)
end = datetime.date(2017, 12, 31)
data = [
[str(begin + datetime.timedelta(days=i)), random.randint(1000, 25000)]
for i in range((end - begin).days + 1)
]

chart = (
Calendar()
.add(
series_name="",
yaxis_data=data,
calendar_opts=opts.CalendarOpts(
pos_top="120",
pos_left="30",
pos_right="30",
range_="2017",
yearlabel_opts=opts.CalendarYearLabelOpts(is_show=False),
),
)
.set_global_opts(
title_opts=opts.TitleOpts(
pos_top="30", pos_left="center", title="2017年步数情况"
),
visualmap_opts=opts.VisualMapOpts(
max_=25000, min_=500, orient="horizontal", is_piecewise=False
),
)
.dump_options()
)

import random

from pyecharts import options as opts
from pyecharts.charts import HeatMap
from pyecharts.faker import Faker

value = [[i, j, random.randint(0, 80)] for i in range(24) for j in range(7)]
c = (
HeatMap()
.add_xaxis(Faker.clock)
.add_yaxis(
"series0",
[w.strip("day") for w in Faker.week_en],
value,
label_opts=opts.LabelOpts(is_show=True, position="inside"),
)
.set_global_opts(
title_opts=opts.TitleOpts(title="HeatMap-Label"),
visualmap_opts=opts.VisualMapOpts(),
)
.dump_options()
)

return render_template("insights.html", chart=chart)
7 changes: 4 additions & 3 deletions app/templates/base.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<!DOCTYPE html>
<!-- app/templates/base.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SongSwap</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.3/css/bulma.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
{% block styles %}{% endblock %}
</head>
<body>
Expand All @@ -16,7 +17,7 @@
</section>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.3/js/bulma.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
{% block scripts %}{% endblock %}
</body>
</html>
13 changes: 13 additions & 0 deletions app/templates/insights.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- app/templates/insights.html -->
{% extends "base.html" %}

{% block content %}
<div id="main" style="width:600px;height:400px;"></div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
<script type="text/javascript">
var chart = echarts.init(document.getElementById('main'));
var option = {{ chart| safe }};
chart.setOption(option);
</script>

{% endblock %}
2 changes: 1 addition & 1 deletion app/templates/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
{% if current_user.is_authenticated %}
<a class="navbar-item" href="{{ url_for('auth.home') }}">Notifications</a>
<a class="navbar-item" href="{{ url_for('auth.home') }}">Groups</a>
<a class="navbar-item" href="{{ url_for('auth.home') }}">Insights</a>
<a class="navbar-item" href="{{ url_for('insights.home') }}">Insights</a>
<a class="navbar-item" href="{{ url_for('auth.home') }}">Profile</a>
<a class="navbar-item" href="{{ url_for('auth.home') }}">Settings</a>
<a class="navbar-item" href="{{ url_for('auth.home') }}">Account</a>
Expand Down
12 changes: 6 additions & 6 deletions app/templates/unauth_home.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{% extends "base.html" %}

{% block content %}
<div class="text-center">
<h1>SongSwap</h1>
<p>Share and discover music with your friends.</p>
<a class="btn btn-primary" href="{{ auth_url }}">Login with Spotify</a>
</div>
{% endblock %}
<div class="column is-half is-offset-one-quarter text-center">
<h1>SongSwap</h1>
<p>Share and discover music with your friends.</p>
<a class="button" href="{{ spotify_auth_url }}">Login with Spotify</a>
</div>
{% endblock %}
Loading