Skip to content

Latest commit

 

History

52 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Parusya

QR Code-based attendance system for recurring communities.

Parusya enables organizations to register members, generate personal QR Codes, and record attendance via scan - replacing paper-and-pencil sign-in sheets with a real-time web platform. Built for weekly frequency, not one-off events.

🌐 Live: parusya.com.br


Overview

Three roles, one cohesive flow:

  • Participant - self-registers and receives a personal QR Code
  • EventStaff - scans QR Codes at the door using a mobile browser
  • Organizer - manages events, monitors attendance, and views participant profiles

Each role authenticates independently. Access is scoped to the group at the JWT level, so staff from one group cannot access another group's data.


Screenshots

Dashboard QR Code Scan Participant Profile
Dashboard Scan Profile

Architecture

┌─────────────────────────────────────────────────────────┐
│                        Frontend                         │
│           React + Vite  ·  Deployed on Vercel           │
└───────────────────────────┬─────────────────────────────┘
                            │ HTTPS / REST
┌───────────────────────────▼─────────────────────────────┐
│                        Backend                          │
│        Spring Boot 3  ·  Deployed on Railway            │
│                                                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │  /auth   │  │ /events  │  │ /checkins│  ...         │
│  └──────────┘  └──────────┘  └──────────┘              │
│                                                         │
│  Spring Security (JWT RS256)  ·  Flyway Migrations      │
└───────────────────────────┬─────────────────────────────┘
                            │ JDBC
┌───────────────────────────▼─────────────────────────────┐
│                      PostgreSQL                         │
│                  Hosted on Railway                      │
└─────────────────────────────────────────────────────────┘

Tech Stack

Backend

  • Java 21 + Spring Boot 3
  • Spring Security - JWT authentication with RS256 (asymmetric keys), three independent user roles resolved via a composite username strategy (email:ROLE)
  • Spring Data JPA + Hibernate - domain model with @UniqueConstraint, @Index, and lazy loading
  • Flyway - versioned database migrations
  • PostgreSQL - relational storage; native queries for time-series aggregations (5-minute interval bucketing via date_trunc)
  • ZXing - QR Code image generation (300×300 PNG, base64-encoded, generated on demand - never stored)
  • Apache POI - XLSX export with auto-filter and fixed column widths (headless-safe, no autoSizeColumn)

Frontend

  • React 18 + Vite
  • React Router v6 - protected routes per role
  • Recharts - attendance area charts with reference line for mean
  • html5-qrcode - camera-based QR Code scanner (environment-first, user-facing fallback)
  • React Hook Form + Zod - form validation
  • Axios - HTTP client with request/response interceptors (JWT injection, 401 redirect)

Infrastructure

  • Vercel - frontend hosting with automatic deploys from main
  • Railway - backend + PostgreSQL, automatic deploys from main
  • Cloudflare - DNS + domain (parusya.com.br)

Domain Model

Group
├── Organizers       (n)
├── EventStaff       (n)
├── Events           (n)
│   └── Tags         (n:m)
└── (scoped access for all entities above)

Participant          (global - not tied to a Group)
├── QrCodes          (1:n, nullable event_id for v1 global code)
└── CheckIns         (n) ──► Event, EventStaff

Key design decisions:

  • Participant is a global entity - one account works across multiple groups
  • QrCode stores only the encoded string; images are generated on demand
  • CheckIn has a UNIQUE(participant_id, event_id) constraint enforced at both the application and database layers
  • EventStaff references are nullified (not cascaded) on deletion to preserve check-in history
  • Tags are group-scoped and normalized to lowercase before persistence

API

All endpoints require a Bearer JWT except registration and login routes.

Method Path Role Description
POST /v1/auth/login/{organizer|staff|participant} Public Issue JWT
POST /v1/groups Public Create group + first organizer
POST /v1/participants/register Public Self-register + generate QR Code
GET /v1/participants/me/qrcode Participant Retrieve personal QR Code image
GET /v1/events/active EventStaff List active events for scanning
POST /v1/checkins/scan EventStaff Validate QR Code and record check-in
GET /v1/participants/ranking Organizer Paginated attendance ranking, with absent-only filter
GET /v1/participants/ranking/:id Organizer Full participant profile + attendance grid data
DELETE /v1/participants/ranking/:id Organizer Remove participant and all associated data
GET /v1/checkins/event/:id Organizer Paginated check-in log with name/staff/time filters
GET /v1/stats/events/:id Organizer Per-event stats: totals, 5-min distribution, staff breakdown
GET /v1/stats/events Organizer Aggregated stats across filtered events
GET /v1/export/xlsx Organizer Full data export (events, participants, check-ins)

Project Structure

backend/
└── src/main/java/com/parusya/
    ├── domain/
    │   ├── checkin/          # CheckIn entity, scan flow, event log, ranking queries
    │   ├── event/            # Event CRUD, dynamic Specification filters
    │   ├── export/           # XLSX generation (Apache POI)
    │   ├── group/            # Group + Organizer management
    │   ├── participant/      # Registration, QR Code, ranking, profile
    │   ├── staff/            # EventStaff management
    │   ├── stats/            # Aggregated and per-event statistics
    │   └── tag/              # Tag resolution and normalization
    └── infra/
        ├── exception/        # Global exception handler + error codes
        └── security/         # JWT (RS256), roles, SecurityConfig, SecurityUtils

frontend/
└── src/
    ├── api/                  # Axios instance + participants API
    ├── components/           # AttendanceGrid, LoginPage, PhoneInput, UI primitives
    ├── contexts/             # AuthContext (token + role persistence)
    ├── pages/
    │   ├── organizer/        # Dashboard, EventDetail, Participants, ParticipantProfile, Staff, Group
    │   ├── participant/      # QrCode, Register, Login
    │   └── staff/            # Scan
    └── routes/               # ProtectedRoute

Running Locally

Prerequisites

  • Java 21+
  • Maven 3.9+
  • Node.js 20+
  • PostgreSQL 15+ running locally

Backend

# Create the database
psql -U postgres -c "CREATE DATABASE parusya;"

# Generate RSA key pair for JWT
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

# Export environment variables
export DB_URL=jdbc:postgresql://localhost:5432/parusya
export DB_USERNAME=postgres
export DB_PASSWORD=your_password
export JWT_PRIVATE_KEY=$(cat private.pem | base64 | tr -d '\n')
export JWT_PUBLIC_KEY=$(cat public.pem | base64 | tr -d '\n')
export JWT_EXPIRATION_SECONDS=86400
export PORT=8080

cd backend
mvn spring-boot:run

Frontend

cd frontend
npm install

# Optional: point to local backend
echo "VITE_API_URL=http://localhost:8080/v1" > .env.local

npm run dev
# → http://localhost:3000

Roadmap

  • Unit and integration tests (Spring Boot Test + Vitest)
  • Absent member alerts (2+ consecutive misses → notify organizer)
  • Staff scan screen shows member name, age, and birthday flag
  • Engagement badges for members (streak tracking)
  • WhatsApp integration for outreach lists

Author

Vitor Azevedo Padovani linkedin.com/in/vitorpadovani · vitorpadovani.com.br

About

QR-based event check-in platform

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages