{"id":48102157,"url":"https://github.com/ibalosh/parking-lot","last_synced_at":"2026-04-04T15:45:08.399Z","repository":{"id":324354906,"uuid":"1074123926","full_name":"ibalosh/parking-lot","owner":"ibalosh","description":null,"archived":false,"fork":false,"pushed_at":"2025-11-15T08:52:07.000Z","size":140,"stargazers_count":0,"open_issues_count":8,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-15T10:19:05.594Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ibalosh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-11T07:28:18.000Z","updated_at":"2025-11-15T08:52:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"7f5c9dbd-dd1d-4e0f-893e-e27ffecd7d2b","html_url":"https://github.com/ibalosh/parking-lot","commit_stats":null,"previous_names":["ibalosh/parking-lot"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/ibalosh/parking-lot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibalosh%2Fparking-lot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibalosh%2Fparking-lot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibalosh%2Fparking-lot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibalosh%2Fparking-lot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ibalosh","download_url":"https://codeload.github.com/ibalosh/parking-lot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ibalosh%2Fparking-lot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31403961,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-04T15:45:08.283Z","updated_at":"2026-04-04T15:45:08.386Z","avatar_url":"https://github.com/ibalosh.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🅿️ Parking Lot Management System\n\n[![CI](https://github.com/ibalosh/parking-lot/actions/workflows/ci.yml/badge.svg)](https://github.com/ibalosh/parking-lot/actions/workflows/ci.yml)\n\nA RESTful API for managing a parking lot with X amount of spaces.\n\n## Requirements\n\n- Ruby version in `.ruby-version` file\n- Rails 8+\n- Bundler\n- SQLite3\n\n## Setup\n\n```bash\nbundle install\nbin/rails db:setup\n```\n\nOnce the setup is complete, it will see the test data too. \nYou can start the Rails server:\n\n```\nrails server\n```\n\n## Running Tests\n\n```bash\nbundle exec rspec\n```\n\n## Documentation\n\n- **[API Endpoints](docs/API_ENDPOINTS.md)** - Detailed endpoint documentation with request/response examples\n- **[Database Schema](docs/DATABASE.md)** - Entity relationship diagram and table descriptions\n- **[Postman Collection](docs/Parking-Lot.postman_collection.json)** - Import this collection to test the API in Postman\n\n## Architecture\n\n### Models\n\n- **ParkingLotFacility** - Manages parking lot capacity (for this specific case - 54 spaces)\n- **Ticket** - Parking tickets with barcode, status (active/returned), and timestamps\n- **Payment** - Payment records linked to tickets\n- **Price** - Configurable pricing (€2/hour)\n- **Currency** - Multi-currency support\n\n### Key Features\n\n- **Race condition protection**: Pessimistic locking prevents duplicate payments and overbooking\n- **Data integrity**: validations and unique constraints\n- **Idempotent operations**: Multiple payment attempts return existing payment\n- **15-minute grace period**: Payment valid for 15 minutes after purchase\n- **Dynamic pricing**: Calculated per started hour based on entry time\n\n## Design Decisions\n\n1. **Pessimistic Locking** - Used in `TicketPaymentService` and `create_ticket!` to prevent race conditions in\n   high-concurrency scenarios (duplicate payments, overbooking).\n\n2. **Service Layer** - `PaymentService` extracts payment logic from the controller, making it easier to test and\n   maintain.\n\n3. **Active Tickets Scope** - Only active tickets count toward capacity; returned tickets free up spaces immediately.\n\n4. **Barcode as Identifier** - Tickets identified by barcode instead of database ID for better UX \n\n5. **Separate ID and Barcode Fields** - The tickets table uses both an auto-incrementing `id` (primary key) and a\n   `barcode` field (unique indexed string). While the barcode could technically serve as the primary key, keeping a\n   separate integer ID provides several benefits:\n   - **Performance**: Integer primary keys are more efficient for joins and foreign key relationships (e.g., payments\n     linking to tickets)\n   - **Flexibility**: If barcode format needs to change (e.g., from numeric to alphanumeric), it won't affect foreign\n     key relationships\n   - **Convention**: Follows Rails and ActiveRecord conventions, making the codebase more maintainable\n\n   The barcode serves as a natural key for API consumers and physical interactions, while the ID serves as a technical\n   key for internal database relationships.\n\n6. **Payment Expiration** - 15-minute window ensures customers don't pay and then park for hours before leaving.\n\n## Design Notes \u0026 Assumptions\n\n### Database \u0026 Multi-tenancy\n\n- **Single Parking Lot**: Currently, the API uses the first parking lot facility from the database. This can be easily\n  extended to support multiple parking lots by accepting a facility ID\n- **Database Choice**: SQLite for simplicity and ease of setup. Production deployments should use PostgreSQL or MySQL.\n\n### Pricing \u0026 Currency\n\n- **Multi-currency Support**: The system supports multiple currencies (Currency model), but each parking lot uses a\n  single currency defined in its Price configuration. Easily extendable this way\n- **Price History**: Tickets capture the price configuration at the time of entry (`price_at_entry`), ensuring\n  historical accuracy even if pricing changes later.\n- **Hourly Pricing**: Every started hour is billed as a full hour. For example, parking for 1 hour and 1 second costs\n  the same as 2 full hours. This can be made more granular (e.g., 15-minute increments) if needed.\n\n### Payment System\n\n- **Payment Methods**: Stored as denormalized string values (`credit_card`, `debit_card`, `cash`) with validation. No\n  separate payment method table was created for simplicity. This can be normalized if payment method metadata is needed\n  in the future.\n- **Multiple Payments**: The system allows multiple payment records per ticket (e.g., if payment expires after 15\n  minutes and customer pays again). The latest payment is used to determine ticket state.\n- **Idempotent Payments**: Attempting to pay an already-paid ticket returns the existing payment instead of creating a\n  duplicate (HTTP 200 instead of 201).\n\n### Ticket Lifecycle\n\n- **Ticket States**: Tickets have two states: `active` (default) and `returned`. Only active tickets count toward\n  parking capacity.\n- **Ticket Return**: The bonus endpoint (PUT `/api/tickets/{barcode}`) allows marking tickets as returned when cars\n  exit. This immediately frees up a parking space.\n- **No Authentication**: As per requirements, no authentication or authorization is implemented. In production, this\n  would be required.\n\n### API Design\n\n- **Versioning**: All endpoints are namespaced under `/api` to allow for future versioning (`/api/v2`, etc.) possibility.\n- **Error Handling**: Consistent error responses with appropriate HTTP status codes (404 for not found, 422 for\n  validation errors, 503 for parking lot full).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fibalosh%2Fparking-lot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fibalosh%2Fparking-lot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fibalosh%2Fparking-lot/lists"}