Skip to content

Commit

Permalink
Add PATCH API to set video URLs on proposals.
Browse files Browse the repository at this point in the history
Fixes #1306.
  • Loading branch information
lukegb authored and russss committed Jan 23, 2024
1 parent e876978 commit f0b02a8
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 1 deletion.
54 changes: 53 additions & 1 deletion apps/api/schedule.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from flask import request
from hmac import compare_digest
from functools import wraps

from flask import request, current_app as app
from flask_login import current_user
from flask_restful import Resource, abort

Expand All @@ -9,6 +12,54 @@
from models.admin_message import AdminMessage


def _require_video_api_key(func):
@wraps(func)
def wrapper(*args, **kwargs):
auth_header = request.headers.get("authorization", None)
if not auth_header or not auth_header.startswith("Bearer "):
abort(401)

bearer_token = auth_header.removeprefix("Bearer ")
if not compare_digest(bearer_token, app.config["VIDEO_API_KEY"]):
abort(401)

return func(*args, **kwargs)

return wrapper


class ProposalResource(Resource):
method_decorators = {"patch": [_require_video_api_key]}

def patch(self, proposal_id):
if not request.is_json:
abort(415)
proposal = Proposal.query.get_or_404(proposal_id)

payload = request.get_json()
if not payload:
abort(400)

ALLOWED_ATTRIBUTES = {"youtube_url", "thumbnail_url", "c3voc_url"}
if set(payload.keys()) - ALLOWED_ATTRIBUTES:
abort(400)

for attribute in ALLOWED_ATTRIBUTES:
if attribute in payload:
setattr(proposal, attribute, payload[attribute] or "")

db.session.add(proposal)
db.session.commit()

return {
"id": proposal.id,
"slug": proposal.slug,
"youtube_url": proposal.youtube_url,
"thumbnail_url": proposal.thumbnail_url,
"c3voc_url": proposal.c3voc_url,
}


class FavouriteProposal(Resource):
def get(self, proposal_id):
if not current_user.is_authenticated:
Expand Down Expand Up @@ -89,6 +140,7 @@ def get(self):
return messages


api.add_resource(ProposalResource, "/proposal/<int:proposal_id>")
api.add_resource(FavouriteProposal, "/proposal/<int:proposal_id>/favourite")
api.add_resource(FavouriteExternal, "/external/<int:event_id>/favourite")
api.add_resource(ScheduleMessage, "/schedule_messages")
2 changes: 2 additions & 0 deletions config/development-example.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ LISTMONK_LISTS = {
MAIL_SERVER = "localhost"
MAIL_BACKEND = "console"

VIDEO_API_KEY = "video-api-token"

# Feature flags
BANK_TRANSFER = True
BANK_TRANSFER_EURO = True
Expand Down
108 changes: 108 additions & 0 deletions tests/test_api_proposals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import pytest

from models.cfp import TalkProposal, Proposal


@pytest.fixture(scope="module")
def proposal(db, user):
proposal = TalkProposal()
proposal.title = "Title"
proposal.description = "Description"
proposal.user = user

db.session.add(proposal)
db.session.commit()

return proposal


def test_denies_request_without_api_key(client, app, proposal):
app.config.update(
{
"VIDEO_API_KEY": "api-key",
}
)

rv = client.patch(
f"/api/proposal/{proposal.id}",
json={
"youtube_url": "https://example.com/youtube.com",
"thumbnail_url": "https://example.com/thumbnail",
"c3voc_url": "https://example.com/media.ccc.de",
},
)
assert rv.status_code == 401


def test_can_set_video_urls(client, app, proposal):
app.config.update(
{
"VIDEO_API_KEY": "api-key",
}
)

rv = client.patch(
f"/api/proposal/{proposal.id}",
json={
"youtube_url": "https://example.com/youtube.com",
"thumbnail_url": "https://example.com/thumbnail",
"c3voc_url": "https://example.com/media.ccc.de",
},
headers={
"Authorization": "Bearer api-key",
},
)
assert rv.status_code == 200

proposal = Proposal.query.get(proposal.id)
assert proposal.youtube_url == "https://example.com/youtube.com"
assert proposal.thumbnail_url == "https://example.com/thumbnail"
assert proposal.c3voc_url == "https://example.com/media.ccc.de"


def test_clearing_video_url(client, app, db, proposal):
app.config.update(
{
"VIDEO_API_KEY": "api-key",
}
)

proposal.youtube_url = "https://example.com/youtube.com"
db.session.add(proposal)
db.session.commit()

rv = client.patch(
f"/api/proposal/{proposal.id}",
json={
"youtube_url": None,
},
headers={
"Authorization": "Bearer api-key",
},
)
assert rv.status_code == 200

proposal = Proposal.query.get(proposal.id)
assert proposal.youtube_url == ""


def test_rejects_disallowed_attributes(client, app, proposal):
app.config.update(
{
"VIDEO_API_KEY": "api-key",
}
)

rv = client.patch(
f"/api/proposal/{proposal.id}",
json={
"youtube_url": "https://example.com/youtube.com",
"thumbnail_url": "https://example.com/thumbnail",
"c3voc_url": "https://example.com/media.ccc.de",
"title": "Not allowed",
},
headers={
"Authorization": "Bearer api-key",
},
)
assert rv.status_code == 400

0 comments on commit f0b02a8

Please sign in to comment.