A Go-based backend API for managing attendee access to Garba Night by GLA, including venue entry, refreshments, and dinner using QR code authentication.
This API manages event attendee access through a QR code system. Attendees receive JWT tokens encoded in QR codes that grant access to different parts of the event (dance, refreshments, dinner) based on their permissions.
The system supports different types of attendees with different access patterns:
-
Dance-only attendees (
has_dance_access: true, has_dinner_access: false):- Use
/venue/danceto enter the venue - Can claim refreshments via
/refreshment
- Use
-
Dinner-only attendees (
has_dance_access: false, has_dinner_access: true):- Use
/dinnerdirectly (automatically records venue entry)
- Use
-
Full access attendees (
has_dance_access: true, has_dinner_access: true):- Can enter via
/venue/danceearly for dancing and refreshments - Later use
/dinnerto claim dinner (venue entry already recorded) - Alternatively, can go directly to
/dinnerif arriving only for dinner (automatically records venue entry)
- Can enter via
Protected routes require a JWT token in the Authorization header:
Authorization: Bearer <jwt_token>
The JWT token contains:
attendee_id: Unique identifier for the attendeehas_dance_access: Boolean indicating dance access permissionhas_dinner_access: Boolean indicating dinner access permission
Admin routes require an admin secret in the X-Admin-Secret header:
X-Admin-Secret: <admin_secret>
Check if the API is running.
Authentication: None required
Response:
{
"message": "pong"
}cURL Example:
curl -X GET http://localhost:8080/pingAll entry routes require JWT authentication and are prefixed with /api.
Mark venue entry for attendees with dance access.
Authentication: JWT token required
Permissions: has_dance_access must be true
Response (Success):
{
"message": "Venue entry marked successfully",
"attendee_id": "A1B2C3D4E",
"attendee_name": "John Doe",
"venue_entry_at": "2025-09-12T15:30:00Z"
}Response (Error - No Dance Access):
{
"error": "Access denied: No dance access"
}Response (Error - Already Entered):
{
"error": "Already entered venue or attendee not found"
}cURL Example:
curl -X POST http://localhost:8080/api/venue/dance \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"Mark refreshment entry for attendees with dance access.
Authentication: JWT token required
Permissions: has_dance_access must be true
Response (Success):
{
"message": "Refreshment entry marked successfully",
"attendee_id": "A1B2C3D4E",
"attendee_name": "John Doe",
"refreshment_claimed_at": "2025-09-12T16:45:00Z"
}Response (Error - Not Eligible):
{
"error": "Not eligible for refreshments or already claimed"
}cURL Example:
curl -X POST http://localhost:8080/api/refreshment \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"Mark dinner entry for attendees with dinner access. This endpoint also automatically records venue entry if the attendee hasn't entered the venue yet.
Authentication: JWT token required
Permissions: has_dinner_access must be true
Response (Success):
{
"message": "Dinner entry marked successfully",
"attendee_id": "F5G6H7I8J",
"attendee_name": "Jane Smith",
"dinner_claimed_at": "2025-09-12T20:30:00Z",
"venue_entry_at": "2025-09-12T20:30:00Z"
}Response (Error - Not Eligible):
{
"error": "Not eligible for dinner or already claimed"
}cURL Example:
curl -X POST http://localhost:8080/api/dinner \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"All admin routes require admin secret authentication and are prefixed with /admin.
Generate a QR code containing JWT token for a specific attendee.
Authentication: Admin secret required
Parameters:
attendee_id(path): The unique identifier of the attendee
Response: PNG image (QR code)
Response Headers:
Content-Type: image/png
Content-Disposition: inline; filename=qr-{attendee_id}.png
Response (Error - Attendee Not Found):
{
"error": "Attendee not found"
}cURL Example:
# Save QR code to file
curl -X GET http://localhost:8080/admin/qr/A1B2C3D4E \
-H "X-Admin-Secret: your_admin_secret_here" \
-o qr-A1B2C3D4E.png
# View QR code info without saving
curl -X GET http://localhost:8080/admin/qr/A1B2C3D4E \
-H "X-Admin-Secret: your_admin_secret_here" \
-IGenerate a JWT token for a specific attendee and return it as JSON.
Authentication: Admin secret required
Parameters:
attendee_id(path): The unique identifier of the attendee
Response (Success):
{
"attendee_id": "A1B2C3D4E",
"attendee_name": "John Doe",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": "24h"
}Response (Error - Attendee Not Found):
{
"error": "Attendee not found"
}cURL Example:
curl -X GET http://localhost:8080/admin/token/A1B2C3D4E \
-H "X-Admin-Secret: your_admin_secret_here" \
-H "Content-Type: application/json"Create a new attendee in the database.
Authentication: Admin secret required
Request Body:
{
"id": "A1B2C3D4E",
"full_name": "John Doe",
"phone_number": "+1234567890",
"has_dance_access": true,
"has_dinner_access": false
}Required Fields:
id: Unique identifier for the attendeefull_name: Full name of the attendeephone_number: Contact phone number
Optional Fields:
has_dance_access: Boolean (default: false)has_dinner_access: Boolean (default: false)
Response (Success):
{
"message": "Attendee created successfully",
"attendee": {
"id": "A1B2C3D4E",
"full_name": "John Doe",
"phone_number": "+1234567890",
"has_dance_access": true,
"has_dinner_access": false,
"created_at": "2025-09-12T14:30:00Z",
"updated_at": "2025-09-12T14:30:00Z"
}
}Response (Error - Duplicate ID):
{
"error": "Attendee with this ID already exists"
}Response (Error - Missing Required Field):
{
"error": "Full name is required"
}cURL Example:
curl -X POST http://localhost:8080/admin/attendees \
-H "X-Admin-Secret: your_admin_secret_here" \
-H "Content-Type: application/json" \
-d '{
"id": "A1B2C3D4E",
"full_name": "John Doe",
"phone_number": "+1234567890",
"has_dance_access": true,
"has_dinner_access": false
}'200 OK: Request successful201 Created: Resource created successfully400 Bad Request: Invalid request format or missing required fields401 Unauthorized: Missing or invalid authentication403 Forbidden: Access denied (insufficient permissions)404 Not Found: Resource not found409 Conflict: Resource already exists or action not allowed500 Internal Server Error: Server error
{
"error": "Error message describing what went wrong"
}Some errors may include additional details:
{
"error": "Invalid JSON format",
"details": "json: cannot unmarshal string into Go struct field..."
}Required environment variables:
DATABASE_URL: PostgreSQL connection stringJWT_SECRET: Secret key for JWT token signing/verificationADMIN_SECRET: Secret key for admin route authenticationPORT(optional): Server port (default: 8080)
Example .env file:
DATABASE_URL=postgres://username:password@localhost:5432/garba_qrify
JWT_SECRET=your-super-secret-jwt-key-here
ADMIN_SECRET=your-admin-secret-here
PORT=8080- Create an attendee (Admin):
curl -X POST http://localhost:8080/admin/attendees \
-H "X-Admin-Secret: your_admin_secret_here" \
-H "Content-Type: application/json" \
-d '{
"id": "A1B2C3D4E",
"full_name": "John Doe",
"phone_number": "+1234567890",
"has_dance_access": true,
"has_dinner_access": true
}'- Generate QR code for attendee (Admin):
curl -X GET http://localhost:8080/admin/qr/A1B2C3D4E \
-H "X-Admin-Secret: your_admin_secret_here" \
-o john-doe-qr.png- Get JWT token for testing (Admin):
curl -X GET http://localhost:8080/admin/token/A1B2C3D4E \
-H "X-Admin-Secret: your_admin_secret_here"- Attendee enters venue for dance:
curl -X POST http://localhost:8080/api/venue/dance \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."- Attendee claims refreshments:
curl -X POST http://localhost:8080/api/refreshment \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."- Attendee claims dinner (automatically records venue entry if not already done):
curl -X POST http://localhost:8080/api/dinner \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."- JWT tokens have different expiration times:
- QR code tokens: 30 days
- API tokens: 24 hours
- Attendees can only perform actions they have access to (dance/dinner)
- Each action (venue entry, refreshments, dinner) can only be performed once per attendee
- Venue entry for dance is handled separately via
/venue/danceendpoint - Dinner entry automatically handles venue entry - no separate venue entry endpoint needed for dinner attendees
- All timestamps are returned in ISO 8601 format (UTC)