Skip to content
Merged
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
7 changes: 7 additions & 0 deletions logs/api.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
2025-10-20 10:42:30,488 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: 00c9054d4b4dc08a1cd512fdc7bedeeb).
2025-10-20 10:43:12,883 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: 27e603dee3f998f774b30b7dc19fff29).
2025-10-20 10:47:31,256 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: 4596e2a8c1c163c50a0295b2b2d68e4a).
2025-10-20 10:48:18,455 [INFO] discord.gateway: Shard ID None has connected to Gateway (Session ID: af714c3798c54aece11d596133567db5).
2025-10-20 10:57:25,663 [INFO] AREA-API: App started in TEST mode (SQLite in-memory).
2025-10-20 10:59:21,018 [INFO] AREA-API: App started in TEST mode (SQLite in-memory).
2025-10-20 12:23:10,150 [INFO] AREA-API: App started in TEST mode (SQLite in-memory).
5 changes: 5 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[pytest]
pythonpath = server
testpaths = server/tests
python_files = test_*.py
addopts = -v
27 changes: 21 additions & 6 deletions server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,44 @@

logger = setup_logger()

def create_app():
def create_app(mode="default"):
app = Flask(__name__)
swagger = Swagger(app, config=swagger_config, template=template)
app.config.from_object(Config)

if app.config.get("TESTING") or mode == "test":
app.config.update({
"SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:",
"SQLALCHEMY_ENGINE_OPTIONS": {},
})
logger.info("App started in TEST mode (SQLite in-memory).")

db.init_app(app)
migrate.init_app(app)
jwt.init_app(app)

CORS(app)

register_routes(app)

if app.config.get("TESTING") or mode == "test":
with app.app_context():
db.create_all()
return app

wait_for_db(app)
with app.app_context():
db.create_all()
seed_services()

scheduler = BackgroundScheduler()
scheduler.add_job(lambda: check_hooks(app), "interval", seconds=app.config["SCHEDULER_INTERVAL"], id="check_hooks_job")
scheduler.add_job(
lambda: check_hooks(app),
"interval",
seconds=app.config["SCHEDULER_INTERVAL"],
id="check_hooks_job"
)
scheduler.start()
logger.info("FLASK successfully running")

logger.info("Flask successfully running")
return app


Expand Down
4 changes: 4 additions & 0 deletions server/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ APScheduler==3.10.4
flasgger
discord
cryptography
pytest
pytest-flask
pytest-mock

76 changes: 24 additions & 52 deletions server/routes/spotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,73 +12,45 @@
AUTH_URL = "https://accounts.spotify.com/authorize"
TOKEN_URL = "https://accounts.spotify.com/api/token"

@bp.route("/spotify/login", methods=["GET"])
import urllib.parse

@bp.route("/spotify/login")
def spotify_login():
"""
Redirige l'utilisateur vers Spotify pour se connecter à l'application
---
tags:
- Spotify
responses:
302:
description: Redirection vers la page d'autorisation Spotify
"""

frontend = request.args.get("frontend", "web")

scope = "user-read-currently-playing user-read-playback-state"
state = f"frontend:{frontend}"

params = {
"client_id": CLIENT_ID,
"response_type": "code",
"redirect_uri": REDIRECT_URI,
"scope": scope,
"show_dialog": "true"
"state": state,
}

url = f"{AUTH_URL}?{urllib.parse.urlencode(params)}"
return redirect(url)
auth_url = f"{AUTH_URL}?{urllib.parse.urlencode(params)}"
return redirect(auth_url)


@bp.route("/spotify/callback", methods=["GET"])
def spotify_callback():
"""
Callback Spotify après l'authentification
---
tags:
- Spotify
responses:
200:
description: Retourne les tokens d'accès Spotify
"""
if "error" in request.args:
return jsonify({"error": request.args["error"]}), 400

@bp.route("/spotify/callback")
def spotify_callback():
code = request.args.get("code")
if not code:
return jsonify({"error": "Missing authorization code"}), 400
state = request.args.get("state", "frontend:web")

data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
frontend = state.split(":")[1]

res = requests.post(TOKEN_URL, data=data)
if res.status_code != 200:
return jsonify({"error": res.text}), 400
tokens = {...}

tokens = res.json()
access_token = tokens.get("access_token")
refresh_token = tokens.get("refresh_token")
expires_in = tokens.get("expires_in")
if frontend == "mobile":
redirect_uri = "areaapp://auth/spotify/callback"
else:
redirect_uri = "http://localhost:5173/spotify/callback"

session["spotify_tokens"] = {
"access_token": access_token,
"refresh_token": refresh_token,
"expires_at": datetime.now().timestamp() + expires_in
}
redirect_url = (
f"{redirect_uri}?access_token={tokens['access_token']}"
f"&refresh_token={tokens.get('refresh_token','')}"
)
return redirect(redirect_url)

return jsonify({
"message": "Spotify connected successfully!",
"tokens": tokens
})
Empty file added tests/__init__.py
Empty file.
22 changes: 22 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from dotenv import load_dotenv
import os
import pytest

# Cherche le .env à la racine du projet ou dans /server
for path in [".env", "server/.env"]:
env_path = os.path.join(os.path.dirname(__file__), "..", path)
env_path = os.path.abspath(env_path)
if os.path.exists(env_path):
load_dotenv(dotenv_path=env_path)
break

from server.app import create_app

@pytest.fixture
def client():
app = create_app(mode="test")
app.config["TESTING"] = True

with app.test_client() as client:
yield client

14 changes: 14 additions & 0 deletions tests/test_about.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
def test_about_json(client):
"""Test the /about.json endpoint returns correct JSON structure."""

response = client.get("/about.json")
assert response.status_code == 200

data = response.get_json()

assert "server" in data
assert "client" in data

assert "services" in data["server"]
assert isinstance(data["server"]["services"], list)
assert any(s["name"] == "spotify" for s in data["server"]["services"])