Skip to content

Rafal5671/MediQueue

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

MediQueue

Full-stack medical appointment booking system, built with Ruby on Rails 8. Role-based platform for patients, doctors and admins - with pessimistic locking against double-booking, Redis-cached slot availability, Sidekiq background jobs, and Stimulus.js interactivity.


Build Status

Platform Backend Database Cache Jobs Containerized Authentication Authorization Styling Language


Description

MediQueue is a full-stack medical appointment booking platform built to solve real-world engineering problems: concurrent slot booking, background job orchestration, Redis caching, and role-based access control.

Patients browse specialists, pick available time slots, book appointments and leave reviews. Doctors manage their schedules and view incoming bookings. Admins have full control over the system through a dedicated panel.

The main engineering focus was on correctness under concurrency - two patients trying to book the same slot at the same time should never both succeed, and the system should handle this gracefully without blocking.

Demo credentials:

Role Email Password
Patient patient@mediqueue.pl password123
Doctor kowalski@mediqueue.pl password123
Admin admin@mediqueue.pl password123

Features

  • User registration and login (Devise)
  • Role-based access - Patient, Doctor, Admin (Pundit)
  • Browse doctors by specialty with live filtering (Stimulus.js)
  • Book appointments from pre-generated time slots
  • Pessimistic locking - FOR UPDATE NOWAIT prevents double-booking under concurrent load
  • Appointment management - cancel with 24h policy, status tracking
  • Doctor panel - manage schedule, view appointments, edit profile
  • Admin panel - full CRUD on clinics, specialties, doctors, appointments
  • Patient reviews per appointment with star rating
  • Email notifications - confirmation, reminder, cancellation, weekly report
  • Background jobs - ReminderJob, CleanupJob, ReportJob, SlotGeneratorJob
  • Redis-cached slot availability (24h TTL, invalidated on schedule change)
  • Account settings - change name, email, password, delete account
  • Dockerized full-stack environment

Tech Stack

  • Framework: Ruby on Rails 8.1
  • Language: Ruby 3.4
  • Database: PostgreSQL 16
  • Cache / Queue: Redis 7
  • Background Jobs: Sidekiq
  • Authentication: Devise
  • Authorization: Pundit
  • Styling: Tailwind CSS
  • Testing: RSpec
  • Containerization: Docker, docker-compose
  • Email preview: letter_opener_web

Architecture Highlights

1. Pessimistic Locking - No Double Booking

Two users attempting to book the same slot simultaneously are handled via PostgreSQL row-level locking. The second transaction fails immediately rather than waiting - which gives the user instant feedback instead of a confusing delay.

Appointment
  .where(doctor: @doctor, scheduled_at: @scheduled_at)
  .where.not(status: :cancelled)
  .lock("FOR UPDATE NOWAIT")
  .first

Implemented in AppointmentBookingService which wraps the whole booking flow in a single transaction.

2. Redis-Cached Slot Availability

SlotGeneratorJob pre-computes available slots for 60 days ahead and caches them in Redis with a 24h TTL. Slot availability checks never hit PostgreSQL on read - the cache is invalidated and regenerated whenever a doctor changes their schedule.

REDIS.setex(
  "doctor:#{doctor_id}:available_slots",
  24.hours.to_i,
  available_slots.to_json
)

3. Background Job Pipeline (Sidekiq)

Job Description Schedule
ReminderJob Sends email 24h before appointment Every hour
CleanupJob Releases unconfirmed slots older than 15 min Every 15 minutes
ReportJob Weekly summary report for doctors Every Monday
SlotGeneratorJob Regenerates Redis cache on schedule change On demand

4. Performance Indexes

Composite PostgreSQL indexes on the most common query patterns:

idx_appointments_doctor_scheduled: (doctor_id, scheduled_at)
idx_appointments_status_created:   (status, created_at)
idx_schedules_doctor_day:          (doctor_id, day_of_week)
idx_notifications_user_read:       (user_id, read_at)

Project Structure

app/
β”œβ”€β”€ controllers/
β”‚   β”œβ”€β”€ admin/                          # Admin panel
β”‚   └── doctors/                        # Doctor panel
β”œβ”€β”€ services/
β”‚   └── appointment_booking_service.rb  # Pessimistic locking, booking flow
β”œβ”€β”€ jobs/
β”‚   β”œβ”€β”€ cleanup_job.rb                  # Release unconfirmed slots
β”‚   β”œβ”€β”€ reminder_job.rb                 # 24h email reminders
β”‚   β”œβ”€β”€ report_job.rb                   # Weekly doctor reports
β”‚   └── slot_generator_job.rb           # Cache slot availability in Redis
β”œβ”€β”€ policies/
β”‚   └── appointment_policy.rb           # Pundit authorization rules
└── mailers/
    └── appointment_mailer.rb           # Confirmation, reminder, cancellation

Data Model

User (Devise)
  β”œβ”€β”€ has_one :doctor_profile (Doctor)
  β”œβ”€β”€ has_many :appointments_as_patient
  └── has_many :appointments_as_doctor

Doctor
  β”œβ”€β”€ belongs_to :user
  β”œβ”€β”€ belongs_to :clinic
  β”œβ”€β”€ belongs_to :specialty
  β”œβ”€β”€ has_many :schedules
  └── has_many :reviews

Appointment
  β”œβ”€β”€ belongs_to :doctor (User)
  β”œβ”€β”€ belongs_to :patient (User)
  └── has_one :review

Schedule     # Weekly availability (Mon 9-17, etc.)
Specialty    # Cardiology, Dermatology, etc.
Clinic       # Multi-tenant data isolation
Review       # Patient reviews per appointment

User Roles

Role Capabilities
Patient Browse doctors, book appointments, cancel (more than 24h before), leave reviews
Doctor View appointments, manage weekly schedule, update profile
Admin Full CRUD on clinics, specialties, doctors, appointments

Getting Started

With Docker

git clone https://github.com/Rafal5671/MediQueue
cd MediQueue

cp .env.example .env
# Add your RAILS_MASTER_KEY to .env

docker-compose build
docker-compose up

# In a separate terminal
docker-compose exec web bin/rails db:create db:migrate db:seed

Visit http://127.0.0.1

Local Development

git clone https://github.com/Rafal5671/MediQueue
cd MediQueue
bundle install

rails db:create db:migrate db:seed

redis-server &
bundle exec sidekiq &
rails server

Running Tests

bundle exec rspec
bundle exec rspec --format documentation

Current coverage:

  • AppointmentBookingService - pessimistic locking, slot taken scenarios
  • CleanupJob - unconfirmed slot release logic
  • Model validations - User, Appointment, Schedule, Review, Clinic, Specialty
  • Mailer specs - AppointmentMailer (confirmation, reminder, cancellation)

Email Preview

All emails are captured by letter_opener_web and available at:

http://127.0.0.1/letter_opener

Emails sent by the system:

  • Appointment confirmation - immediately after booking
  • Appointment reminder - 24h before the appointment
  • Appointment cancellation - when cancelled by patient or doctor
  • Weekly report - every Monday, sent to doctors
  • Password reset, email changed, password changed - Devise security emails

Example Screenshots

Screenshot 2026-04-30 at 14-36-16 MediQueue Screenshot 2026-04-30 at 14-36-34 MediQueue Screenshot 2026-04-30 at 14-37-01 MediQueue Screenshot 2026-04-30 at 14-37-11 MediQueue Screenshot 2026-04-30 at 14-37-17 MediQueue Screenshot 2026-04-30 at 14-37-40 LetterOpenerWeb Screenshot 2026-04-30 at 14-38-02 MediQueue Screenshot 2026-04-30 at 14-38-16 MediQueue Screenshot 2026-04-30 at 14-38-37 MediQueue Screenshot 2026-04-30 at 14-38-43 MediQueue Screenshot 2026-04-30 at 14-38-49 MediQueue Screenshot 2026-04-30 at 14-39-08 MediQueue Screenshot 2026-04-30 at 14-39-20 MediQueue

Releases

No releases published

Packages

 
 
 

Contributors