This repository is CCIP-App/CCIP-Server, the event-side API server for
OPass. Read this file before changing code so you keep the public OPass product
model, the event deployment model, and the local Python service boundaries
straight.
OPass is a shared open-source event check-in and event-information platform for Taiwan FLOSS conferences. The public site describes it as a way to check in, plan schedules, and receive announcements; it has repeatedly been used by SITCON, COSCUP, PyCon TW, g0v summit, HITCON, and related community events.
Useful public references:
- https://opass.app/ - product overview and GitHub org link.
- https://hackmd.io/@OPass/rJitRr3VS - OPass architecture and API flow.
- https://hackmd.io/@OPass/booth - puzzle / gamification operating model.
- https://github.com/CCIP-App/ - OPass source organization.
- https://github.com/CCIP-App/Portal - event list and event-detail configuration source.
- https://github.com/CCIP-App/schedule-json-generator - schedule JSON source pipeline mentioned by the architecture note.
Mental model:
Portalpublishes the list of available events and each event's static detail JSON. The mobile apps discover an event there.- The event detail JSON can include
server_base_url; that URL points at a deployment of this repository. - This server handles attendee token status, scenario usage, announcements, dashboards, and puzzle/gamification endpoints.
- Schedules are outside this server. They come from the event's
schedule_url. - Puzzle frontends are separate projects. This server stores identity, puzzle buckets, delivery permissions, status, and dashboards.
app/ccip.pyis the Flask app and all HTTP routes.app/import.pyimports attendee CSV rows into MongoDB and binds scenarios.app/models/contains MongoEngine documents:Attendeeand embeddedScenarioAnnouncementPuzzleStatusandPuzzleBucket
app/config-sample.pydocuments the runtimeconfig.pyshape.app/*-sample.*files are fixtures or examples only.Dockerfile,docker-compose.yml, andMakefileare the main deployment path.pyproject.tomlusesuv; runtime dependencies are Flask, MongoEngine, and Waitress. The dev dependency group currently only contains Flake8.
There are no tests in the repository at the time this file was written.
This service is configured by files inside app/ at container runtime. Several
of them are intentionally ignored by git because they are event-specific:
config.pyscenario*.jsonpuzzle-config.jsondelivery-permission.jsonreg.csv
Do not commit real event config, attendee CSVs, QR-code tokens, deliverer tokens, or MongoDB connection secrets.
config.py must define:
MONGODB_SETTINGS, passed tomongoengine.connect.SCENARIO_DEFS, mapping a role such asaudienceorstaffto a scenario definition JSON filename.ANNOUNCEMENT_DEFAULT_ROLE, used when/announcementis called without a valid attendee token.
Scenario definition JSON is loaded on process start. Historical sample shape includes keys like:
orderdisplay_textavailable_timeandexpire_time, parsed as%Y/%m/%d %H:%M %zcountdownlock_messagenot_lock_ruleshow_ruleattrrelated_scenariodeliver_puzzle
If you change scenario semantics, update both app/import.py and
app/ccip.py; import-time binding and request-time usage are split.
Attendee identity:
- Most attendee endpoints take
?token=<private attendee token>. Attendee.public_tokenissha1(token)and is used for puzzle sharing./statusrecordsfirst_useand initializes the attendee's puzzle bucket on first normal access. PassingStaffQuerychanges that behavior./use/<scenario_id>consumes a scenario, may unlock or disable related scenarios, may mark related scenarios used for staff scans, and may deliver puzzle pieces.
Puzzle endpoints:
/event/puzzle?token=<public_token>reads a puzzle bucket./event/puzzle/revoke?token=<private attendee token>invalidates the bucket and decrements current puzzle currency counters./event/puzzle/coupon?token=<private attendee token>marks coupon use./event/puzzle/deliverer?token=<deliverer token>maps a delivery token to a delivery slug fromdelivery-permission.json./event/puzzle/delivererslists delivery slugs.POST /event/puzzle/deliver?token=<deliverer token>expects formreceiver=<private attendee token>and delivers one puzzle piece./event/puzzle/dashboardreturns puzzle counters.
Announcements and dashboards:
GET /announcementreturns announcements for the attendee role if a valid token is provided; otherwise it usesANNOUNCEMENT_DEFAULT_ROLE.POST /announcementcreates an announcement from form fieldsmsg_zh,msg_en,uri, androle[]. There is no in-repo auth guard, so deployments must protect this endpoint externally if needed./dashboard,/dashboard/<role>,/roles, and/scenarios?role=...expose operational state.
Use the repository's uv setup for Python work:
uv sync --locked --all-groups
uv run ruff check app
uv run ruff format --check app
uv run python -m compileall app
uv run pre-commit run --all-filesRuff is the Python lint and format tool for this repo. The pre-commit hooks run
ruff check --fix and ruff format, so local commits should auto-fix routine
import ordering and formatting issues.
Run the service the same way the README describes:
make goThe expected successful container log includes Waitress serving on port 5000. Use:
make shellto enter the running backend container.
To import attendees, run from app/ or make sure relative config/scenario paths
resolve the same way they do in the container:
EVENT_ID=<event-id> uv run python import.py reg.csv audience
EVENT_ID=<event-id> uv run python import.py staff.csv staffapp/import.py requires EVENT_ID in the environment and a role that exists in
SCENARIO_DEFS.
- Keep changes small and explicit; this is a compact service with global module-load state.
- Be careful with process-start side effects.
app/ccip.pyreads config and optional puzzle files during import, and initializesPuzzleStatuswhen puzzle config exists. - Avoid importing the Flask app in ad hoc scripts unless the expected
config.py, scenario files, optional puzzle files, and MongoDB are available. - Preserve API compatibility with the mobile apps, Portal event config, and separate puzzle frontends. Existing endpoints are simple but externally coupled.
- Treat query parameter names and form field names as public contracts.
- When touching token or attendee flows, distinguish private attendee
token, public SHA-1public_token, and deliverer permission token. - GitHub Actions runs the same quality gate on pull requests and pushes to
master: locked uv sync, Ruff lint, Ruff format check, andcompileall. - If you add tests later, prefer focused Flask test-client coverage around
route behavior and MongoEngine model state. Consider
mongomockor a test MongoDB fixture instead of real event data. - Validate JSON and CSV fixture changes separately from Python linting.
- Do not modernize
app/webhook.shcasually. It is legacy KKTIX import tooling and includes environment-specific assumptions.
At minimum, run the cheapest applicable validation:
uv run ruff check app
uv run ruff format --check appBefore committing, prefer the full local gate:
uv run pre-commit run --all-files
uv run python -m compileall appIf you touched Docker, run or explicitly report why you could not run:
make buildIf you touched route behavior, exercise the relevant endpoint against a local container or Flask test client with non-real sample data.