+import functools
from dataclasses import dataclass
from datetime import datetime, timezone
bp = Blueprint("jams", __name__, url_prefix="/jams")
-@bp.get("/")
+def jam_owner_only(f):
+ @functools.wraps(f)
+ def _wrapper(jamid, *args, **kwargs):
+ row = db.query(
+ "SELECT * FROM jams WHERE jamid = ?", [jamid], expect_one=True)
+
+ if row["ownerid"] != g.userid:
+ abort(403) # Forbidden; cannot modify other user's jam
+
+ return f(jamid, *args, **kwargs)
+ return _wrapper
+
+
+@bp.get("")
def jams():
# Show a list of all jams: ongoing, upcoming, previous
rows = db.query(
@bp.post("/<int:jamid>/update")
@auth.requires_login
+@jam_owner_only
def update(jamid):
# Update a jam with the new form data, redirect to view page
title = request.form["title"]
@bp.get("/<int:jamid>/delete")
@auth.requires_login
+@jam_owner_only
def delete(jamid):
# Delete a jam, redirect to the jams list
row = db.query(
return redirect(url_for("jams.jams"))
-@bp.get("/<int:jamid>/events")
-def events(jamid):
- # Show a list of all events for the jam (current, upcoming, previous)
- ...
-
-
@bp.get("/<int:jamid>/events/create")
@auth.requires_login
-def events_create():
+@jam_owner_only
+def events_create(jamid):
# Create a new event and redirect to the edit form
...
@bp.get("/<int:jamid>/events/<int:eventid>")
-def events_view(eventid):
+def events_view(jamid, eventid):
# Show the event page
...
@bp.post("/<int:jamid>/events/<int:eventid>/update")
@auth.requires_login
-def events_update(jamid):
+@jam_owner_only
+def events_update(jamid, eventid):
# Update an event with the new form data
...
@bp.get("/<int:jamid>/events/<int:eventid>/delete")
@auth.requires_login
-def events_delete(jamid):
+@jam_owner_only
+def events_delete(jamid, eventid):
# Delete an event, redirect to list of all events
...
@classmethod
def from_row(cls, row):
- event_rows = db.query("SELECT * FROM jam_events WHERE jamid = ?", [row["jamid"]])
+ event_rows = db.query(
+ """
+ SELECT
+ e.eventid,
+ e.jamid,
+ e.title,
+ e.threadid,
+ e.created,
+ e.startdate,
+ e.enddate,
+ e.description,
+ j.title as jam_title,
+ u.username as jam_ownername
+ FROM jam_events as e
+ INNER JOIN jams as j on e.jamid = j.jamid
+ INNER JOIN users as u on j.ownerid = u.userid
+ WHERE e.jamid = ?
+ """, [row["jamid"]])
events = [JamEvent.from_row(r) for r in event_rows]
return cls(
jamid=row["jamid"],
startdate: datetime
enddate: datetime
description: str
+ jam_title: str
+ jam_ownername: str
# TODO: Comment object?
comments: list
startdate=datetime.fromisoformat(row["startdate"]),
enddate=datetime.fromisoformat(row["enddate"]),
description=sanitize_user_text(row["description"] or ""),
+ jam_title=row["jam_title"],
+ jam_ownername=row["jam_ownername"],
# TODO: Comment object?
comments=comments,
)
--- /dev/null
+{% macro jam_event_list(list_title, events) %}
+{% if events %}
+<h2>{{ list_title }}</h2>
+<div class="jam-event-list">
+ {% for event in events %}
+ <div class="jam-event-list-entry">
+ <span class="jam-event-list-title">
+ <a href="/jams/{{ event.jamid }}/events/{{ event.eventid }}">{{ event.title }}</a>
+ </span>
+ -
+ <span class="jam-event-list-jam-title">
+ <a href="/jams/{{ event.jamid }}">{{ event.jam_title }}</a>
+ </span>
+ <span class="jam-event-list-owner">
+ Hosted by <a href="/users/{{ event.jam_ownername }}" class="profile-link">{{ event.jam_ownername }}</a>
+ </span>
+ </div>
+ {% endfor %}
+</div>
+{% endif %}
+{% endmacro %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block title %}{{ event.title }} - {{ jam.title }}{% endblock %}
+
+{% block body %}
+
+<h1>{{ event.title }} - {{ jam.title }}</h1>
+
+<div>
+ <strong>Host:</strong>
+ <a href="/users/{{ jam.ownername }}" class="profile-link">{{ jam.ownername }}</a>
+ <br/>
+ <strong>Start Date:</strong>
+ {{ event.startdate.aslocaltime().strftime("%Y-%m-%d @ %H:%M") }}
+ <br/>
+ <strong>End Date:</strong>
+ {{ event.enddate.aslocaltime().strftime("%Y-%m-%d @ %H:%M") }}
+</div>
+
+<div class="jam-description">
+{{ event.description }}
+</div>
+
+<h2>About the Jam</h2>
+<div class="jam-description">
+{{ jam.description }}
+</div>
+
+<h2>Submissions</h2>
+<button class="button">Submit a Song</button>
+{% include "song-list.html" %}
+
+<h2>Comments</h2>
+{{ comment_thread(event.threadid, session['userid'], jam.ownerid, event.comments) }}
+
+{% endblock %}
<h1>{{ jam.title }}</h1>
<div>
- <span><strong>Jam Master:</strong></span> {{ jam.username }}
+ <strong>Host:</strong>
+ <a href="/users/{{ jam.ownername }}" class="profile-link">{{ jam.ownername }}</a>
</div>
-<div>
<h2>Description</h2>
+<div>
{{ jam.description }}
</div>
+{% from "jam-event-list.html" import jam_event_list %}
+{{ jam_event_list("Ongoing Events", ongoing) }}
+{{ jam_event_list("Upcoming Events", upcoming) }}
+{{ jam_event_list("Past Events", past) }}
+
{% endblock %}
<h1>Jams</h1>
-{% macro jam_list(list_title, jams) %}
-{% if jams %}
-<h2>{{ list_title }}</h2>
+{% from "jam-event-list.html" import jam_event_list %}
+{{ jam_event_list("Ongoing Events", ongoing) }}
+{{ jam_event_list("Upcoming Events", upcoming) }}
+{{ jam_event_list("Recent Events", recent) }}
+
+<h2>All Jams</h2>
<div class="jam-list">
{% for jam in jams %}
<div class="jam-list-entry">
</div>
{% endfor %}
</div>
-{% endif %}
-{% endmacro %}
-
-{{ jam_list("Ongoing Jams", ongoing) }}
-{{ jam_list("Upcoming Jams", upcoming) }}
-{{ jam_list("Past Jams", past) }}
{% endblock %}
assert b"New Jam" in response.data
def test_jams_list(client, user, jam):
- response = client.get("/jams/")
+ response = client.get("/jams")
assert response.status_code == 200
assert b"New Jam" in response.data
data={"title": "Coolest Jam", "description": "pb and jam"})
assert response.status_code == 404
+def test_update_other_users_jam(client, user, jam):
+ create_user(client, "otheruser", login=True)
+ response = client.post(
+ f"/jams/{jam}/update",
+ data={"title": "Coolest Jam", "description": "pb and jam"})
+ assert response.status_code == 403
+
def test_delete_jam(client, user, jam):
response = client.get(f"/jams/{jam}/delete", follow_redirects=True)
- assert response.request.path == "/jams/"
+ assert response.request.path == "/jams"
assert b"New Jam" not in response.data
response = client.get(f"/jams/{jam}")
def test_delete_invalid_jam(client, user):
response = client.get("/jams/1/delete")
assert response.status_code == 404
+
+def test_delete_other_users_jam(client, user, jam):
+ create_user(client, "otheruser", login=True)
+ response = client.get(f"/jams/{jam}/delete")
+ assert response.status_code == 403