Compare commits
8 Commits
basic-sear
...
develop
Author | SHA1 | Date |
---|---|---|
Jef Roosens | d5931d37b5 | |
Renovate Bot | 953e125bcd | |
Jef Roosens | 2d7827d8dc | |
Jef Roosens | 3ae4f9998c | |
Renovate Bot | 5cf4aa7ffe | |
Renovate Bot | b8b9b8c30d | |
Jef Roosens | 1865d8481f | |
Renovate Bot | 50e6e92421 |
|
@ -2,7 +2,7 @@ pipeline:
|
||||||
# =====TESTING=====
|
# =====TESTING=====
|
||||||
test-backend:
|
test-backend:
|
||||||
# Alpine version doesn't have make
|
# Alpine version doesn't have make
|
||||||
image: python:3.9
|
image: python:3.8
|
||||||
pull: true
|
pull: true
|
||||||
group: test
|
group: test
|
||||||
commands:
|
commands:
|
||||||
|
@ -23,7 +23,7 @@ pipeline:
|
||||||
|
|
||||||
# =====LINTING=====
|
# =====LINTING=====
|
||||||
lint-backend:
|
lint-backend:
|
||||||
image: python:3.9
|
image: python:3.8
|
||||||
group: lint
|
group: lint
|
||||||
commands:
|
commands:
|
||||||
- make lint
|
- make lint
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -3,7 +3,7 @@ PYTHON := python3
|
||||||
# This can't contain spaces (I think)
|
# This can't contain spaces (I think)
|
||||||
VENV := .venv
|
VENV := .venv
|
||||||
# Minimum % coverage for tests to succeed
|
# Minimum % coverage for tests to succeed
|
||||||
MIN_COV := 50
|
MIN_COV := 0
|
||||||
# Directory name for the frontend
|
# Directory name for the frontend
|
||||||
WEB_DIR := web
|
WEB_DIR := web
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ test: venv
|
||||||
## Starting the server
|
## Starting the server
|
||||||
### Run the Quart server
|
### Run the Quart server
|
||||||
run: venv
|
run: venv
|
||||||
@ QUART_ENV=development '$(VENV)'/bin/python app
|
@ '$(VENV)'/bin/python app
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
"""Entrypoint for the program."""
|
"""Main entrypoint for the program."""
|
||||||
from quart import Quart
|
from quart import Quart
|
||||||
from app.api import api_bp
|
|
||||||
|
|
||||||
app = Quart("jos", static_folder="web/dist", static_url_path="/")
|
app = Quart("jos", static_folder="web/dist", static_url_path="/")
|
||||||
app.register_blueprint(api_bp, url_prefix="/api")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", methods=["GET"], defaults={"path": ""})
|
@app.route("/", methods=["GET"], defaults={"path": ""})
|
||||||
|
@ -13,5 +11,4 @@ async def frontend(path):
|
||||||
return await app.send_static_file("index.html")
|
return await app.send_static_file("index.html")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
app.run(host="0.0.0.0")
|
||||||
app.run(host="0.0.0.0")
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
"""Module containing all Flask-related code."""
|
|
||||||
from quart import Blueprint
|
|
||||||
from .search import search_bp
|
|
||||||
|
|
||||||
|
|
||||||
# Main blueprint exposing entire API
|
|
||||||
api_bp = Blueprint("api", __name__)
|
|
||||||
api_bp.register_blueprint(search_bp, url_prefix="/search")
|
|
|
@ -1,11 +0,0 @@
|
||||||
"""Handles the search endpoint."""
|
|
||||||
from quart import Blueprint
|
|
||||||
|
|
||||||
|
|
||||||
search_bp = Blueprint("search", __name__)
|
|
||||||
|
|
||||||
|
|
||||||
@search_bp.route("/", methods=["GET"])
|
|
||||||
async def search_spotify():
|
|
||||||
"""Search the Spotify API."""
|
|
||||||
return "yeet"
|
|
|
@ -1,98 +0,0 @@
|
||||||
"""Handle connections with the Spotify API."""
|
|
||||||
import base64
|
|
||||||
import requests
|
|
||||||
import urllib
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
AUTH_URL = "https://accounts.spotify.com/api/token"
|
|
||||||
API_URL = "https://api.spotify.com/v1"
|
|
||||||
|
|
||||||
|
|
||||||
def requires_token(method: callable, retries: int = 2):
|
|
||||||
"""Decorator that handles refreshing the token.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
method: function to decorate
|
|
||||||
retries: how many times to retry creating the token
|
|
||||||
"""
|
|
||||||
|
|
||||||
def inner(ref, *args, **kwargs):
|
|
||||||
for _ in range(retries):
|
|
||||||
if ref._access_token and datetime.now() < ref._expiration_time:
|
|
||||||
break
|
|
||||||
|
|
||||||
ref._refresh_token()
|
|
||||||
|
|
||||||
method(ref, *args, **kwargs)
|
|
||||||
|
|
||||||
return inner
|
|
||||||
|
|
||||||
|
|
||||||
class CredentialError(Exception):
|
|
||||||
"""Thrown when invalid credentials are passed to Spotify API."""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Spotify:
|
|
||||||
"""Represents a connection object with the Spotify API."""
|
|
||||||
|
|
||||||
def __init__(self, client_id: str, client_secret: str):
|
|
||||||
"""Initialize a new Spotify object.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
client_id: client id for the API
|
|
||||||
client_secret: client secret for the API
|
|
||||||
"""
|
|
||||||
self._client_id = client_id
|
|
||||||
self._client_secret = client_secret
|
|
||||||
|
|
||||||
self._access_token = ""
|
|
||||||
self._token_type = ""
|
|
||||||
self._expiration_time = 0
|
|
||||||
self._scope = ""
|
|
||||||
|
|
||||||
def _refresh_token(self):
|
|
||||||
"""Refresh the current token, or create a new one."""
|
|
||||||
b64_encoded = base64.b64encode(
|
|
||||||
f"{self.client_id}:{self.client_secret}".encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
headers = {"Authorization": f"Basic {b64_encoded.decode('ascii')}"}
|
|
||||||
data = {"grant_type": "client_credentials"}
|
|
||||||
|
|
||||||
post = requests.post(AUTH_URL, headers=headers, data=data)
|
|
||||||
status_code = post.status_code
|
|
||||||
|
|
||||||
if status_code == 401:
|
|
||||||
raise CredentialError("Provided credentials are not correct.")
|
|
||||||
|
|
||||||
if status_code == 200:
|
|
||||||
self.access_token = post.json()["access_token"]
|
|
||||||
self.token_type = post.json()["token_type"]
|
|
||||||
self.expiration_time = datetime.now() + timedelta(seconds=post.json()["expires_in"])
|
|
||||||
self.scope = post.json()["scope"]
|
|
||||||
|
|
||||||
# TODO raise errors on failure
|
|
||||||
|
|
||||||
@requires_token
|
|
||||||
def search(self, search_term: str, types: List[str]):
|
|
||||||
"""Search the API for entries.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
search_term: the searm term to use
|
|
||||||
types: which object types to search for
|
|
||||||
"""
|
|
||||||
encoded_search_term = urllib.parse.quote(search_term)
|
|
||||||
types_str = ",".join(types)
|
|
||||||
|
|
||||||
r = requests.get(
|
|
||||||
f"{API_URL}/search?q={encoded_search_term}&type={types_str}",
|
|
||||||
headers={"Authorization": "Bearer " + self.access_token},
|
|
||||||
)
|
|
||||||
|
|
||||||
results = r.json()
|
|
||||||
|
|
||||||
return results
|
|
|
@ -1,12 +1,12 @@
|
||||||
[options]
|
[options]
|
||||||
install_requires =
|
install_requires =
|
||||||
quart==0.15.0
|
quart==0.15.1
|
||||||
|
|
||||||
[options.extras_require]
|
[options.extras_require]
|
||||||
# Used inside Tox for running tests
|
# Used inside Tox for running tests
|
||||||
test =
|
test =
|
||||||
pytest==6.2.4
|
pytest==6.2.4
|
||||||
pytest-cov==2.12.0
|
pytest-cov==2.12.1
|
||||||
# Worth testing out
|
# Worth testing out
|
||||||
pytest-tldr==0.2.4
|
pytest-tldr==0.2.4
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
def test_succeed():
|
||||||
|
"""Placeholder test to make CI succeed."""
|
||||||
|
pass
|
|
@ -26,10 +26,10 @@
|
||||||
"eslint-config-standard": "^16.0.2",
|
"eslint-config-standard": "^16.0.2",
|
||||||
"eslint-plugin-import": "^2.23.2",
|
"eslint-plugin-import": "^2.23.2",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^4.3.1",
|
"eslint-plugin-promise": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^7.9.0",
|
"eslint-plugin-vue": "^7.9.0",
|
||||||
"mocha": "^8.4.0",
|
"mocha": "^8.4.0",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "^4.1.3",
|
"typescript": "^4.1.3",
|
||||||
"vite": "^2.2.3",
|
"vite": "^2.2.3",
|
||||||
"vue-tsc": "^0.1.0"
|
"vue-tsc": "^0.1.0"
|
||||||
|
|
|
@ -222,6 +222,26 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
defer-to-connect "^1.0.1"
|
defer-to-connect "^1.0.1"
|
||||||
|
|
||||||
|
"@tsconfig/node10@^1.0.7":
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.7.tgz#1eb1de36c73478a2479cc661ef5af1c16d86d606"
|
||||||
|
integrity sha512-aBvUmXLQbayM4w3A8TrjwrXs4DZ8iduJnuJLLRGdkWlyakCf1q6uHZJBzXoRA/huAEknG5tcUyQxN3A+In5euQ==
|
||||||
|
|
||||||
|
"@tsconfig/node12@^1.0.7":
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.7.tgz#677bd9117e8164dc319987dd6ff5fc1ba6fbf18b"
|
||||||
|
integrity sha512-dgasobK/Y0wVMswcipr3k0HpevxFJLijN03A8mYfEPvWvOs14v0ZlYTR4kIgMx8g4+fTyTFv8/jLCIfRqLDJ4A==
|
||||||
|
|
||||||
|
"@tsconfig/node14@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.0.tgz#5bd046e508b1ee90bc091766758838741fdefd6e"
|
||||||
|
integrity sha512-RKkL8eTdPv6t5EHgFKIVQgsDapugbuOptNd9OOunN/HAkzmmTnZELx1kNCK0rSdUYGmiFMM3rRQMAWiyp023LQ==
|
||||||
|
|
||||||
|
"@tsconfig/node16@^1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1"
|
||||||
|
integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==
|
||||||
|
|
||||||
"@types/chai@^4.2.18":
|
"@types/chai@^4.2.18":
|
||||||
version "4.2.18"
|
version "4.2.18"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.18.tgz#0c8e298dbff8205e2266606c1ea5fbdba29b46e4"
|
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.18.tgz#0c8e298dbff8205e2266606c1ea5fbdba29b46e4"
|
||||||
|
@ -1305,10 +1325,10 @@ eslint-plugin-node@^11.1.0:
|
||||||
resolve "^1.10.1"
|
resolve "^1.10.1"
|
||||||
semver "^6.1.0"
|
semver "^6.1.0"
|
||||||
|
|
||||||
eslint-plugin-promise@^4.3.1:
|
eslint-plugin-promise@^5.0.0:
|
||||||
version "4.3.1"
|
version "5.1.0"
|
||||||
resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz#fb2188fb734e4557993733b41aa1a688f46c6f24"
|
||||||
integrity sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==
|
integrity sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==
|
||||||
|
|
||||||
eslint-plugin-vue@^7.9.0:
|
eslint-plugin-vue@^7.9.0:
|
||||||
version "7.9.0"
|
version "7.9.0"
|
||||||
|
@ -3484,11 +3504,15 @@ trough@^1.0.0:
|
||||||
resolved "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz"
|
resolved "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz"
|
||||||
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
||||||
|
|
||||||
ts-node@^9.1.1:
|
ts-node@^10.0.0:
|
||||||
version "9.1.1"
|
version "10.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"
|
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.0.0.tgz#05f10b9a716b0b624129ad44f0ea05dac84ba3be"
|
||||||
integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==
|
integrity sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg==
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@tsconfig/node10" "^1.0.7"
|
||||||
|
"@tsconfig/node12" "^1.0.7"
|
||||||
|
"@tsconfig/node14" "^1.0.0"
|
||||||
|
"@tsconfig/node16" "^1.0.1"
|
||||||
arg "^4.1.0"
|
arg "^4.1.0"
|
||||||
create-require "^1.1.0"
|
create-require "^1.1.0"
|
||||||
diff "^4.0.1"
|
diff "^4.0.1"
|
||||||
|
|
Reference in New Issue