Decoupling audit: remove events API, update shared submodule
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m30s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m30s
- Delete events_api.py (dead internal API endpoint) - Remove registration from app.py - Update shared submodule Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
4
app.py
4
app.py
@@ -128,10 +128,6 @@ def create_app() -> "Quart":
|
||||
from bp.ticket_admin.routes import register as register_ticket_admin
|
||||
app.register_blueprint(register_ticket_admin())
|
||||
|
||||
# Internal API (server-to-server, CSRF-exempt)
|
||||
from events_api import register as register_events_api
|
||||
app.register_blueprint(register_events_api())
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
||||
198
events_api.py
198
events_api.py
@@ -1,198 +0,0 @@
|
||||
"""
|
||||
Internal JSON API for the events app.
|
||||
|
||||
These endpoints are called by other apps (cart) over HTTP.
|
||||
They are CSRF-exempt because they are server-to-server calls.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from quart import Blueprint, g, request, jsonify
|
||||
from sqlalchemy import select, update, func
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from models.calendars import CalendarEntry, Calendar, Ticket
|
||||
from shared.browser.app.csrf import csrf_exempt
|
||||
|
||||
|
||||
def register() -> Blueprint:
|
||||
bp = Blueprint("events_api", __name__, url_prefix="/internal/events")
|
||||
|
||||
@bp.get("/calendar-entries")
|
||||
@csrf_exempt
|
||||
async def calendar_entries():
|
||||
"""
|
||||
Return pending calendar entries for a user/session.
|
||||
Used by the cart app to display calendar items in the cart.
|
||||
|
||||
Query params: user_id, session_id, state (default: pending)
|
||||
"""
|
||||
user_id = request.args.get("user_id", type=int)
|
||||
session_id = request.args.get("session_id")
|
||||
state = request.args.get("state", "pending")
|
||||
|
||||
filters = [
|
||||
CalendarEntry.deleted_at.is_(None),
|
||||
CalendarEntry.state == state,
|
||||
]
|
||||
if user_id is not None:
|
||||
filters.append(CalendarEntry.user_id == user_id)
|
||||
elif session_id:
|
||||
filters.append(CalendarEntry.session_id == session_id)
|
||||
else:
|
||||
return jsonify([])
|
||||
|
||||
result = await g.s.execute(
|
||||
select(CalendarEntry)
|
||||
.where(*filters)
|
||||
.options(selectinload(CalendarEntry.calendar))
|
||||
.order_by(CalendarEntry.start_at.asc())
|
||||
)
|
||||
entries = result.scalars().all()
|
||||
|
||||
return jsonify([
|
||||
{
|
||||
"id": e.id,
|
||||
"name": e.name,
|
||||
"cost": float(e.cost) if e.cost else 0,
|
||||
"state": e.state,
|
||||
"start_at": e.start_at.isoformat() if e.start_at else None,
|
||||
"end_at": e.end_at.isoformat() if e.end_at else None,
|
||||
"calendar_name": e.calendar.name if e.calendar else None,
|
||||
"calendar_slug": e.calendar.slug if e.calendar else None,
|
||||
}
|
||||
for e in entries
|
||||
])
|
||||
|
||||
@bp.post("/adopt")
|
||||
@csrf_exempt
|
||||
async def adopt():
|
||||
"""
|
||||
Adopt anonymous calendar entries for a user.
|
||||
Called by the cart app after login.
|
||||
|
||||
Body: {"user_id": int, "session_id": str}
|
||||
"""
|
||||
data = await request.get_json() or {}
|
||||
user_id = data.get("user_id")
|
||||
session_id = data.get("session_id")
|
||||
|
||||
if not user_id or not session_id:
|
||||
return jsonify({"ok": False, "error": "user_id and session_id required"}), 400
|
||||
|
||||
# Soft-delete existing user entries
|
||||
await g.s.execute(
|
||||
update(CalendarEntry)
|
||||
.where(
|
||||
CalendarEntry.deleted_at.is_(None),
|
||||
CalendarEntry.user_id == user_id,
|
||||
)
|
||||
.values(deleted_at=func.now())
|
||||
)
|
||||
|
||||
# Adopt anonymous entries
|
||||
cal_result = await g.s.execute(
|
||||
select(CalendarEntry).where(
|
||||
CalendarEntry.deleted_at.is_(None),
|
||||
CalendarEntry.session_id == session_id,
|
||||
)
|
||||
)
|
||||
for entry in cal_result.scalars().all():
|
||||
entry.user_id = user_id
|
||||
|
||||
return jsonify({"ok": True})
|
||||
|
||||
@bp.get("/entry/<int:entry_id>")
|
||||
@csrf_exempt
|
||||
async def entry_detail(entry_id: int):
|
||||
"""
|
||||
Return entry details for order display.
|
||||
Called by the cart app when showing order items.
|
||||
"""
|
||||
result = await g.s.execute(
|
||||
select(CalendarEntry)
|
||||
.where(CalendarEntry.id == entry_id)
|
||||
.options(selectinload(CalendarEntry.calendar))
|
||||
)
|
||||
entry = result.scalar_one_or_none()
|
||||
|
||||
if not entry:
|
||||
return jsonify(None), 404
|
||||
|
||||
return jsonify({
|
||||
"id": entry.id,
|
||||
"name": entry.name,
|
||||
"cost": float(entry.cost) if entry.cost else 0,
|
||||
"state": entry.state,
|
||||
"start_at": entry.start_at.isoformat() if entry.start_at else None,
|
||||
"end_at": entry.end_at.isoformat() if entry.end_at else None,
|
||||
"calendar_name": entry.calendar.name if entry.calendar else None,
|
||||
"calendar_slug": entry.calendar.slug if entry.calendar else None,
|
||||
})
|
||||
|
||||
@bp.get("/tickets")
|
||||
@csrf_exempt
|
||||
async def tickets():
|
||||
"""
|
||||
Return tickets for a user/session.
|
||||
Query params: user_id, session_id, order_id, state
|
||||
"""
|
||||
user_id = request.args.get("user_id", type=int)
|
||||
session_id = request.args.get("session_id")
|
||||
order_id = request.args.get("order_id", type=int)
|
||||
state = request.args.get("state")
|
||||
|
||||
filters = []
|
||||
if order_id is not None:
|
||||
filters.append(Ticket.order_id == order_id)
|
||||
elif user_id is not None:
|
||||
filters.append(Ticket.user_id == user_id)
|
||||
elif session_id:
|
||||
filters.append(Ticket.session_id == session_id)
|
||||
else:
|
||||
return jsonify([])
|
||||
|
||||
if state:
|
||||
filters.append(Ticket.state == state)
|
||||
|
||||
result = await g.s.execute(
|
||||
select(Ticket)
|
||||
.where(*filters)
|
||||
.options(
|
||||
selectinload(Ticket.entry).selectinload(CalendarEntry.calendar),
|
||||
selectinload(Ticket.ticket_type),
|
||||
)
|
||||
.order_by(Ticket.created_at.desc())
|
||||
)
|
||||
tix = result.scalars().all()
|
||||
|
||||
return jsonify([
|
||||
{
|
||||
"id": t.id,
|
||||
"code": t.code,
|
||||
"state": t.state,
|
||||
"entry_name": t.entry.name if t.entry else None,
|
||||
"entry_start_at": t.entry.start_at.isoformat() if t.entry and t.entry.start_at else None,
|
||||
"calendar_name": t.entry.calendar.name if t.entry and t.entry.calendar else None,
|
||||
"ticket_type_name": t.ticket_type.name if t.ticket_type else None,
|
||||
"ticket_type_cost": float(t.ticket_type.cost) if t.ticket_type and t.ticket_type.cost else None,
|
||||
"checked_in_at": t.checked_in_at.isoformat() if t.checked_in_at else None,
|
||||
}
|
||||
for t in tix
|
||||
])
|
||||
|
||||
@bp.post("/tickets/<code>/checkin")
|
||||
@csrf_exempt
|
||||
async def checkin(code: str):
|
||||
"""
|
||||
Check in a ticket by code.
|
||||
Used by admin check-in interface.
|
||||
"""
|
||||
from .bp.tickets.services.tickets import checkin_ticket
|
||||
|
||||
success, error = await checkin_ticket(g.s, code)
|
||||
if not success:
|
||||
return jsonify({"ok": False, "error": error}), 400
|
||||
|
||||
return jsonify({"ok": True})
|
||||
|
||||
return bp
|
||||
2
shared
2
shared
Submodule shared updated: 7ee8638d6e...e83df2f742
Reference in New Issue
Block a user