Add detailed API reference with params and response metadata
All checks were successful
build-and-deploy / build-deploy (push) Successful in 17s

This commit is contained in:
androidlover5842
2026-02-04 12:06:00 +05:30
parent aa319401d2
commit fdb6792018
3 changed files with 499 additions and 2 deletions

View File

@@ -205,7 +205,10 @@ Notes / constraints
- Checkout validation: nightly rate must be within +/-20% of room type default rate (when default rate exists), and minimum stay duration must be at least 1 hour.
- Room-type reservations: use booking room requests (`booking_room_request`) for quantity holds without room numbers; availability checks include active requests + occupied stays.
- Cancellation policy engine (advance bookings): policy per property with `freeDaysBeforeCheckin` + `penaltyMode` (`NO_CHARGE`, `ONE_NIGHT`, `FULL_STAY`). On cancel/no-show, penalty charge ledger rows are auto-created (`CANCELLATION_PENALTY` / `NO_SHOW_PENALTY`) when within penalty window.
- API documentation source of truth: `docs/API_CATALOG.md`. Any API addition, removal, path change, method change, or behavior-impacting request/response change must update this doc in the same commit.
- API documentation source of truth:
- `docs/API_CATALOG.md` (endpoint inventory)
- `docs/API_REFERENCE.md` (usage/params/body type/response type/behavior summary)
- Any API addition, removal, path/method change, or behavior-impacting request/response change must update both docs in the same commit.
Operational notes
- Payment provider migrated: PayU removed; Razorpay now used for settings, QR, payment links, and webhooks.
@@ -221,5 +224,5 @@ Access / ops notes (prod)
- DB (dev): PostgreSQL `trisolaris` on `192.168.1.53:5432` (see `application-dev.properties` in the repo).
- DB access (server): `sudo -u postgres psql -d trisolaris`.
- Workflow: Always run build, commit, and push for changes unless explicitly told otherwise.
- Workflow rule for API changes: after code changes, update `docs/API_CATALOG.md`, then build, commit, and push.
- Workflow rule for API changes: after code changes, run `python scripts/generate_api_docs.py`, verify `docs/API_CATALOG.md` and `docs/API_REFERENCE.md`, then build, commit, and push.
- Android workflow note: user always runs Shift+F10 in Android Studio to deploy updates.

146
docs/API_REFERENCE.md Normal file
View File

@@ -0,0 +1,146 @@
# API Reference
Generated from controller source. Use this for usage, params, response type, and behavior.
- Total endpoints: **125**
- Auth: Firebase Bearer token unless endpoint is public.
- Regenerate: `python scripts/generate_api_docs.py`
## Usage Template
```bash
curl -X <METHOD> "https://api.hoteltrisolaris.in<PATH>" \
-H "Authorization: Bearer <FIREBASE_ID_TOKEN>" \
-H "Content-Type: application/json" \
-d '<REQUEST_BODY_JSON>'
```
Behavior notes in this file are handler summaries; strict business rules remain in controller code.
| Method | Path | Path Params | Query Params | Body Type | Response Type | Status | Behavior | Handler |
|---|---|---|---|---|---|---|---|---|
| `GET` | `/` | `-` | `-` | `-` | `Map<String, String>` | `200` | Root. | `src/main/kotlin/com/android/trisolarisserver/controller/system/Health.kt:14` (`root`) |
| `GET` | `/amenities` | `-` | `-` | `-` | `List<AmenityResponse>` | `200` | List resources (list amenities). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomAmenities.kt:40` (`listAmenities`) |
| `POST` | `/amenities` | `-` | `-` | `AmenityUpsertRequest` | `AmenityResponse` | `201` | Create resource (create amenity). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomAmenities.kt:49` (`createAmenity`) |
| `DELETE` | `/amenities/{amenityId}` | `amenityId:UUID` | `-` | `-` | `Unit` | `204` | Delete resource (delete amenity). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomAmenities.kt:92` (`deleteAmenity`) |
| `PUT` | `/amenities/{amenityId}` | `amenityId:UUID` | `-` | `AmenityUpsertRequest` | `AmenityResponse` | `200` | Update resource (update amenity). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomAmenities.kt:68` (`updateAmenity`) |
| `GET` | `/auth/me` | `-` | `-` | `-` | `ResponseEntity<AuthResponse>` | `200` | Me. | `src/main/kotlin/com/android/trisolarisserver/controller/auth/Auth.kt:44` (`me`) |
| `PUT` | `/auth/me` | `-` | `-` | `UpdateMeRequest` | `ResponseEntity<AuthResponse>` | `200` | Update resource (update me). | `src/main/kotlin/com/android/trisolarisserver/controller/auth/Auth.kt:54` (`updateMe`) |
| `POST` | `/auth/verify` | `-` | `-` | `-` | `ResponseEntity<AuthResponse>` | `200` | Verify. | `src/main/kotlin/com/android/trisolarisserver/controller/auth/Auth.kt:33` (`verify`) |
| `GET` | `/health` | `-` | `-` | `-` | `Map<String, String>` | `200` | Health. | `src/main/kotlin/com/android/trisolarisserver/controller/system/Health.kt:9` (`health`) |
| `GET` | `/icons/png` | `-` | `-` | `-` | `List<String>` | `200` | List resources (list png). | `src/main/kotlin/com/android/trisolarisserver/controller/assets/IconFiles.kt:23` (`listPng`) |
| `GET` | `/icons/png/{filename}` | `filename:String` | `-` | `-` | `ResponseEntity<FileSystemResource>` | `200` | Get resource (get png). | `src/main/kotlin/com/android/trisolarisserver/controller/assets/IconFiles.kt:39` (`getPng`) |
| `GET` | `/image-tags` | `-` | `-` | `-` | `List<RoomImageTagResponse>` | `200` | List resources (list tags). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImageTags.kt:35` (`listTags`) |
| `POST` | `/image-tags` | `-` | `-` | `RoomImageTagUpsertRequest` | `RoomImageTagResponse` | `201` | Create resource (create tag). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImageTags.kt:43` (`createTag`) |
| `DELETE` | `/image-tags/{tagId}` | `tagId:UUID` | `-` | `-` | `Unit` | `204` | Delete resource (delete tag). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImageTags.kt:74` (`deleteTag`) |
| `PUT` | `/image-tags/{tagId}` | `tagId:UUID` | `-` | `RoomImageTagUpsertRequest` | `RoomImageTagResponse` | `200` | Update resource (update tag). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImageTags.kt:56` (`updateTag`) |
| `GET` | `/properties` | `-` | `-` | `-` | `List<PropertyResponse>` | `200` | List resources (list properties). | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:92` (`listProperties`) |
| `POST` | `/properties` | `-` | `-` | `PropertyCreateRequest` | `PropertyResponse` | `201` | Create resource (create property). | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:53` (`createProperty`) |
| `POST` | `/properties/access-codes/join` | `-` | `-` | `PropertyAccessCodeJoinRequest` | `PropertyUserResponse` | `200` | Join with access code. | `src/main/kotlin/com/android/trisolarisserver/controller/property/PropertyAccessCodes.kt:91` (`joinWithAccessCode`) |
| `PUT` | `/properties/{propertyId}` | `propertyId:UUID` | `-` | `PropertyUpdateRequest` | `PropertyResponse` | `200` | Update resource (update property). | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:306` (`updateProperty`) |
| `POST` | `/properties/{propertyId}/access-codes` | `propertyId:UUID` | `-` | `PropertyAccessCodeCreateRequest` | `PropertyAccessCodeResponse` | `201` | Create resource (create access code). | `src/main/kotlin/com/android/trisolarisserver/controller/property/PropertyAccessCodes.kt:45` (`createAccessCode`) |
| `GET` | `/properties/{propertyId}/billing-policy` | `propertyId:UUID` | `-` | `-` | `PropertyBillingPolicyResponse` | `200` | Get resource (get billing policy). | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:118` (`getBillingPolicy`) |
| `PUT` | `/properties/{propertyId}/billing-policy` | `propertyId:UUID` | `-` | `PropertyBillingPolicyRequest` | `PropertyBillingPolicyResponse` | `200` | Update resource (update billing policy). | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:135` (`updateBillingPolicy`) |
| `GET` | `/properties/{propertyId}/bookings` | `propertyId:UUID` | `status:String? (optional)` | `-` | `List<BookingListItem>` | `200` | List resources (list bookings). | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:187` (`listBookings`) |
| `POST` | `/properties/{propertyId}/bookings` | `propertyId:UUID` | `-` | `BookingCreateRequest` | `BookingCreateResponse` | `201` | Create resource (create booking). | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:99` (`createBooking`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `BookingDetailResponse` | `200` | Get resource (get booking). | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:282` (`getBooking`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/balance` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `BookingBalanceResponse` | `200` | Get resource (get balance). | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingBalances.kt:32` (`getBalance`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/billing-policy` | `propertyId:UUID, bookingId:UUID` | `-` | `BookingBillingPolicyUpdateRequest` | `Unit` | `204` | Update resource (update billing policy). | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:476` (`updateBillingPolicy`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/cancel` | `propertyId:UUID, bookingId:UUID` | `-` | `BookingCancelRequest` | `Unit` | `204` | Cancel. | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:695` (`cancel`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/charges` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `List<ChargeResponse>` | `200` | List resources (list). | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Charges.kt:80` (`list`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/charges` | `propertyId:UUID, bookingId:UUID` | `-` | `ChargeCreateRequest` | `ChargeResponse` | `201` | Create resource (create). | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Charges.kt:44` (`create`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/check-in/bulk` | `propertyId:UUID, bookingId:UUID` | `-` | `BookingBulkCheckInRequest` | `Unit` | `201` | Bulk check in. | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:353` (`bulkCheckIn`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/check-out` | `propertyId:UUID, bookingId:UUID` | `-` | `BookingCheckOutRequest` | `Unit` | `204` | Check out. | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:527` (`checkOut`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/expected-dates` | `propertyId:UUID, bookingId:UUID` | `-` | `BookingExpectedDatesUpdateRequest` | `Unit` | `204` | Update resource (update expected dates). | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:430` (`updateExpectedDates`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/link-guest` | `propertyId:UUID, bookingId:UUID` | `-` | `BookingLinkGuestRequest` | `Unit` | `204` | Link guest. | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:326` (`linkGuest`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/no-show` | `propertyId:UUID, bookingId:UUID` | `-` | `BookingNoShowRequest` | `Unit` | `204` | No show. | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:720` (`noShow`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `List<PaymentResponse>` | `200` | List resources (list). | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Payments.kt:86` (`list`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments` | `propertyId:UUID, bookingId:UUID` | `-` | `PaymentCreateRequest` | `PaymentResponse` | `201` | Create resource (create). | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Payments.kt:47` (`create`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/close` | `propertyId:UUID, bookingId:UUID` | `-` | `RazorpayPaymentRequestCloseRequest` | `RazorpayPaymentRequestCloseResponse` | `200` | Close request. | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayPaymentRequestsController.kt:93` (`closeRequest`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/payment-link` | `propertyId:UUID, bookingId:UUID` | `-` | `RazorpayPaymentLinkCreateRequest` | `RazorpayPaymentLinkCreateResponse` | `200` | Create resource (create payment link). | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayPaymentLinksController.kt:47` (`createPaymentLink`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `List<RazorpayQrRecordResponse>` | `200` | List resources (list qr). | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:292` (`listQr`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr` | `propertyId:UUID, bookingId:UUID` | `-` | `RazorpayQrGenerateRequest` | `RazorpayQrGenerateResponse` | `200` | Create resource (create qr). | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:56` (`createQr`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/active` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `RazorpayQrGenerateResponse?` | `200` | Get resource (get active qr). | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:157` (`getActiveQr`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/close` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `RazorpayQrGenerateResponse?` | `200` | Close active qr. | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:180` (`closeActiveQr`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/{qrId}/close` | `propertyId:UUID, bookingId:UUID, qrId:String` | `-` | `-` | `RazorpayQrGenerateResponse?` | `200` | Close qr by id. | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:212` (`closeQrById`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/{qrId}/events` | `propertyId:UUID, qrId:String` | `-` | `-` | `List<RazorpayQrEventResponse>` | `200` | Qr events. | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:244` (`qrEvents`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/{qrId}/events/stream` | `propertyId:UUID, bookingId:UUID, qrId:String` | `-` | `-` | `SseEmitter` | `200` | Stream events/data (stream qr events). | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:273` (`streamQrEvents`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/refund` | `propertyId:UUID, bookingId:UUID` | `-` | `RazorpayRefundRequest` | `RazorpayRefundResponse` | `200` | Refund. | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayRefundsController.kt:42` (`refund`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/requests` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `List<RazorpayPaymentRequestResponse>` | `200` | List resources (list requests). | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayPaymentRequestsController.kt:45` (`listRequests`) |
| `DELETE` | `/properties/{propertyId}/bookings/{bookingId}/payments/{paymentId}` | `propertyId:UUID, bookingId:UUID, paymentId:UUID` | `-` | `-` | `Unit` | `204` | Delete resource (delete). | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Payments.kt:103` (`delete`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/room-requests` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `List<BookingRoomRequestResponse>` | `200` | List resources (list). | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingRoomRequests.kt:103` (`list`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/room-requests` | `propertyId:UUID, bookingId:UUID` | `-` | `BookingRoomRequestCreateRequest` | `BookingRoomRequestResponse` | `201` | Create resource (create). | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingRoomRequests.kt:48` (`create`) |
| `DELETE` | `/properties/{propertyId}/bookings/{bookingId}/room-requests/{requestId}` | `propertyId:UUID, bookingId:UUID, requestId:UUID` | `-` | `-` | `Unit` | `204` | Cancel. | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingRoomRequests.kt:121` (`cancel`) |
| `POST` | `/properties/{propertyId}/bookings/{bookingId}/room-stays/{roomStayId}/check-out` | `propertyId:UUID, bookingId:UUID, roomStayId:UUID` | `-` | `BookingCheckOutRequest` | `Unit` | `204` | Check out room stay. | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:583` (`checkOutRoomStay`) |
| `GET` | `/properties/{propertyId}/bookings/{bookingId}/stream` | `propertyId:UUID, bookingId:UUID` | `-` | `-` | `org.springframework.web.servlet.mvc.method.annotation.SseEmitter` | `200` | Stream events/data (stream booking). | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:302` (`streamBooking`) |
| `GET` | `/properties/{propertyId}/cancellation-policy` | `propertyId:UUID` | `-` | `-` | `CancellationPolicyResponse` | `200` | Get resource (get). | `src/main/kotlin/com/android/trisolarisserver/controller/property/CancellationPolicies.kt:34` (`get`) |
| `PUT` | `/properties/{propertyId}/cancellation-policy` | `propertyId:UUID` | `-` | `CancellationPolicyUpsertRequest` | `CancellationPolicyResponse` | `200` | Upsert. | `src/main/kotlin/com/android/trisolarisserver/controller/property/CancellationPolicies.kt:53` (`upsert`) |
| `GET` | `/properties/{propertyId}/code` | `propertyId:UUID` | `-` | `-` | `PropertyCodeResponse` | `200` | Get resource (get property code). | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:105` (`getPropertyCode`) |
| `GET` | `/properties/{propertyId}/guests/search` | `propertyId:UUID` | `phone:String? (optional), vehicleNumber:String? (optional)` | `-` | `List<GuestResponse>` | `200` | Search. | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:75` (`search`) |
| `GET` | `/properties/{propertyId}/guests/visit-count` | `propertyId:UUID` | `phone:String (required)` | `-` | `GuestVisitCountResponse` | `200` | Get resource (get visit count). | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:113` (`getVisitCount`) |
| `GET` | `/properties/{propertyId}/guests/{guestId}` | `propertyId:UUID, guestId:UUID` | `-` | `-` | `GuestResponse` | `200` | Get resource (get guest). | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:102` (`getGuest`) |
| `PUT` | `/properties/{propertyId}/guests/{guestId}` | `propertyId:UUID, guestId:UUID` | `-` | `GuestUpdateRequest` | `GuestResponse` | `200` | Update resource (update guest). | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:45` (`updateGuest`) |
| `GET` | `/properties/{propertyId}/guests/{guestId}/documents` | `propertyId:UUID, guestId:UUID` | `-` | `-` | `List<GuestDocumentResponse>` | `200` | List resources (list documents). | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:125` (`listDocuments`) |
| `POST` | `/properties/{propertyId}/guests/{guestId}/documents` | `propertyId:UUID, guestId:UUID` | `bookingId:UUID (required)` | `-` | `GuestDocumentResponse` | `201` | Upload document. | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:65` (`uploadDocument`) |
| `GET` | `/properties/{propertyId}/guests/{guestId}/documents/stream` | `propertyId:UUID, guestId:UUID` | `-` | `-` | `org.springframework.web.servlet.mvc.method.annotation.SseEmitter` | `200` | Stream events/data (stream documents). | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:138` (`streamDocuments`) |
| `DELETE` | `/properties/{propertyId}/guests/{guestId}/documents/{documentId}` | `propertyId:UUID, guestId:UUID, documentId:UUID` | `-` | `-` | `Unit` | `204` | Delete resource (delete document). | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:184` (`deleteDocument`) |
| `GET` | `/properties/{propertyId}/guests/{guestId}/documents/{documentId}/file` | `propertyId:UUID, guestId:UUID, documentId:UUID` | `token:String? (optional)` | `-` | `ResponseEntity<FileSystemResource>` | `200` | Download document. | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:152` (`downloadDocument`) |
| `GET` | `/properties/{propertyId}/guests/{guestId}/ratings` | `propertyId:UUID, guestId:UUID` | `-` | `-` | `List<GuestRatingResponse>` | `200` | List resources (list). | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestRatings.kt:79` (`list`) |
| `POST` | `/properties/{propertyId}/guests/{guestId}/ratings` | `propertyId:UUID, guestId:UUID` | `-` | `GuestRatingCreateRequest` | `GuestRatingResponse` | `201` | Create resource (create). | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestRatings.kt:42` (`create`) |
| `POST` | `/properties/{propertyId}/guests/{guestId}/signature` | `propertyId:UUID, guestId:UUID` | `-` | `-` | `GuestResponse` | `201` | Upload signature. | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:170` (`uploadSignature`) |
| `GET` | `/properties/{propertyId}/guests/{guestId}/signature/file` | `propertyId:UUID, guestId:UUID` | `-` | `-` | `ResponseEntity<FileSystemResource>` | `200` | Download signature. | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:194` (`downloadSignature`) |
| `POST` | `/properties/{propertyId}/guests/{guestId}/vehicles` | `propertyId:UUID, guestId:UUID` | `-` | `GuestVehicleRequest` | `GuestResponse` | `201` | Add vehicle. | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:130` (`addVehicle`) |
| `POST` | `/properties/{propertyId}/inbound-emails/manual` | `propertyId:UUID` | `file:MultipartFile (required)` | `-` | `ManualInboundResponse` | `201` | Upload manual pdf. | `src/main/kotlin/com/android/trisolarisserver/controller/email/InboundEmailManual.kt:41` (`uploadManualPdf`) |
| `GET` | `/properties/{propertyId}/inbound-emails/{emailId}/file` | `propertyId:UUID, emailId:UUID` | `-` | `-` | `ResponseEntity<FileSystemResource>` | `200` | Download email pdf. | `src/main/kotlin/com/android/trisolarisserver/controller/email/InboundEmails.kt:32` (`downloadEmailPdf`) |
| `GET` | `/properties/{propertyId}/rate-plans` | `propertyId:UUID` | `roomTypeCode:String? (optional)` | `-` | `List<RatePlanResponse>` | `200` | List resources (list). | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:79` (`list`) |
| `POST` | `/properties/{propertyId}/rate-plans` | `propertyId:UUID` | `-` | `RatePlanCreateRequest` | `RatePlanResponse` | `201` | Create resource (create). | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:51` (`create`) |
| `DELETE` | `/properties/{propertyId}/rate-plans/{ratePlanId}` | `propertyId:UUID, ratePlanId:UUID` | `-` | `-` | `Unit` | `204` | Delete resource (delete). | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:114` (`delete`) |
| `PUT` | `/properties/{propertyId}/rate-plans/{ratePlanId}` | `propertyId:UUID, ratePlanId:UUID` | `-` | `RatePlanUpdateRequest` | `RatePlanResponse` | `200` | Update resource (update). | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:95` (`update`) |
| `GET` | `/properties/{propertyId}/rate-plans/{ratePlanId}/calendar` | `propertyId:UUID, ratePlanId:UUID` | `from:String (required), to:String (required)` | `-` | `RateCalendarAverageResponse` | `200` | List resources (list calendar). | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:170` (`listCalendar`) |
| `POST` | `/properties/{propertyId}/rate-plans/{ratePlanId}/calendar` | `propertyId:UUID, ratePlanId:UUID` | `-` | `RateCalendarRangeUpsertRequest` | `List<RateCalendarResponse>` | `201` | Upsert calendar. | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:129` (`upsertCalendar`) |
| `DELETE` | `/properties/{propertyId}/rate-plans/{ratePlanId}/calendar/{rateDate}` | `propertyId:UUID, ratePlanId:UUID, rateDate:String` | `-` | `-` | `Unit` | `204` | Delete resource (delete calendar). | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:206` (`deleteCalendar`) |
| `GET` | `/properties/{propertyId}/razorpay-settings` | `propertyId:UUID` | `-` | `-` | `RazorpaySettingsResponse` | `200` | Get resource (get settings). | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpaySettingsController.kt:33` (`getSettings`) |
| `PUT` | `/properties/{propertyId}/razorpay-settings` | `propertyId:UUID` | `-` | `RazorpaySettingsUpsertRequest` | `RazorpaySettingsResponse` | `200` | Upsert settings. | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpaySettingsController.kt:57` (`upsertSettings`) |
| `POST` | `/properties/{propertyId}/razorpay/return/failure` | `propertyId:UUID` | `-` | `-` | `Unit` | `204` | Failure. | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayReturnController.kt:22` (`failure`) |
| `POST` | `/properties/{propertyId}/razorpay/return/success` | `propertyId:UUID` | `-` | `-` | `Unit` | `204` | Success. | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayReturnController.kt:16` (`success`) |
| `POST` | `/properties/{propertyId}/razorpay/webhook` | `propertyId:UUID` | `-` | `String?` | `Unit` | `204` | Capture. | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayWebhookCapture.kt:53` (`capture`) |
| `GET` | `/properties/{propertyId}/room-stays/active` | `propertyId:UUID` | `-` | `-` | `List<ActiveRoomStayResponse>` | `200` | List resources (list active room stays). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomStays.kt:42` (`listActiveRoomStays`) |
| `GET` | `/properties/{propertyId}/room-stays/cards/{cardIndex}` | `propertyId:UUID, cardIndex:Int` | `-` | `-` | `IssuedCardResponse` | `200` | Get resource (get card by index). | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:158` (`getCardByIndex`) |
| `POST` | `/properties/{propertyId}/room-stays/cards/{cardIndex}/revoke` | `propertyId:UUID, cardIndex:Int` | `-` | `-` | `CardRevokeResponse` | `200` | Revoke. | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:138` (`revoke`) |
| `GET` | `/properties/{propertyId}/room-stays/{roomStayId}/cards` | `propertyId:UUID, roomStayId:UUID` | `-` | `-` | `List<IssuedCardResponse>` | `200` | List resources (list). | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:125` (`list`) |
| `POST` | `/properties/{propertyId}/room-stays/{roomStayId}/cards` | `propertyId:UUID, roomStayId:UUID` | `-` | `IssueCardRequest` | `IssuedCardResponse` | `201` | Issue. | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:83` (`issue`) |
| `POST` | `/properties/{propertyId}/room-stays/{roomStayId}/cards/prepare` | `propertyId:UUID, roomStayId:UUID` | `-` | `CardPrepareRequest` | `CardPrepareResponse` | `201` | Prepare. | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:50` (`prepare`) |
| `POST` | `/properties/{propertyId}/room-stays/{roomStayId}/void` | `propertyId:UUID, roomStayId:UUID` | `-` | `RoomStayVoidRequest` | `Unit` | `200` | Void room stay. | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomStays.kt:78` (`voidRoomStay`) |
| `GET` | `/properties/{propertyId}/room-types` | `propertyId:UUID` | `-` | `-` | `List<RoomTypeResponse>` | `200` | List resources (list room types). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:49` (`listRoomTypes`) |
| `POST` | `/properties/{propertyId}/room-types` | `propertyId:UUID` | `-` | `RoomTypeUpsertRequest` | `RoomTypeResponse` | `201` | Create resource (create room type). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:107` (`createRoomType`) |
| `GET` | `/properties/{propertyId}/room-types/{roomTypeCode}/images` | `propertyId:UUID, roomTypeCode:String` | `-` | `-` | `List<RoomImageResponse>` | `200` | List resources (list by room type). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypeImages.kt:29` (`listByRoomType`) |
| `GET` | `/properties/{propertyId}/room-types/{roomTypeCode}/rate` | `propertyId:UUID, roomTypeCode:String` | `date:String (required), ratePlanCode:String? (optional)` | `-` | `RateResolveResponse` | `200` | Resolve rate. | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:60` (`resolveRate`) |
| `DELETE` | `/properties/{propertyId}/room-types/{roomTypeId}` | `propertyId:UUID, roomTypeId:UUID` | `-` | `-` | `Unit` | `204` | Delete resource (delete room type). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:189` (`deleteRoomType`) |
| `PUT` | `/properties/{propertyId}/room-types/{roomTypeId}` | `propertyId:UUID, roomTypeId:UUID` | `-` | `RoomTypeUpsertRequest` | `RoomTypeResponse` | `200` | Update resource (update room type). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:153` (`updateRoomType`) |
| `GET` | `/properties/{propertyId}/rooms` | `propertyId:UUID` | `-` | `-` | `List<RoomResponse>` | `200` | List resources (list rooms). | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:66` (`listRooms`) |
| `POST` | `/properties/{propertyId}/rooms` | `propertyId:UUID` | `-` | `RoomUpsertRequest` | `RoomResponse` | `201` | Create resource (create room). | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:278` (`createRoom`) |
| `GET` | `/properties/{propertyId}/rooms/availability` | `propertyId:UUID` | `-` | `-` | `List<RoomAvailabilityResponse>` | `200` | Room availability. | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:126` (`roomAvailability`) |
| `GET` | `/properties/{propertyId}/rooms/availability-range` | `propertyId:UUID` | `-` | `-` | `List<RoomAvailabilityRangeResponse>` | `200` | Room availability range. | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:184` (`roomAvailabilityRange`) |
| `GET` | `/properties/{propertyId}/rooms/available` | `propertyId:UUID` | `-` | `-` | `List<RoomResponse>` | `200` | Available rooms. | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:147` (`availableRooms`) |
| `GET` | `/properties/{propertyId}/rooms/available-range-with-rate` | `propertyId:UUID` | `-` | `-` | `List<RoomAvailabilityWithRateResponse>` | `200` | Available rooms with rate. | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:221` (`availableRoomsWithRate`) |
| `GET` | `/properties/{propertyId}/rooms/board` | `propertyId:UUID` | `-` | `-` | `List<RoomBoardResponse>` | `200` | Room board. | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:86` (`roomBoard`) |
| `GET` | `/properties/{propertyId}/rooms/board/stream` | `propertyId:UUID` | `-` | `-` | `SseEmitter` | `200` | Room board stream. | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:114` (`roomBoardStream`) |
| `GET` | `/properties/{propertyId}/rooms/by-type/{roomTypeCode}` | `propertyId:UUID, roomTypeCode:String` | `-` | `-` | `List<RoomResponse>` | `200` | Rooms by type. | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:160` (`roomsByType`) |
| `DELETE` | `/properties/{propertyId}/rooms/{roomId}` | `propertyId:UUID, roomId:UUID` | `-` | `-` | `Unit` | `204` | Delete resource (delete room). | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:367` (`deleteRoom`) |
| `PUT` | `/properties/{propertyId}/rooms/{roomId}` | `propertyId:UUID, roomId:UUID` | `-` | `RoomUpsertRequest` | `RoomResponse` | `200` | Update resource (update room). | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:323` (`updateRoom`) |
| `POST` | `/properties/{propertyId}/rooms/{roomId}/cards/prepare-temp` | `propertyId:UUID, roomId:UUID` | `-` | `-` | `CardPrepareResponse` | `201` | Prepare temporary. | `src/main/kotlin/com/android/trisolarisserver/controller/card/TemporaryRoomCards.kt:46` (`prepareTemporary`) |
| `POST` | `/properties/{propertyId}/rooms/{roomId}/cards/temp` | `propertyId:UUID, roomId:UUID` | `-` | `IssueTempCardRequest` | `IssuedCardResponse` | `201` | Issue temporary. | `src/main/kotlin/com/android/trisolarisserver/controller/card/TemporaryRoomCards.kt:74` (`issueTemporary`) |
| `GET` | `/properties/{propertyId}/rooms/{roomId}/images` | `propertyId:UUID, roomId:UUID` | `-` | `-` | `List<RoomImageResponse>` | `200` | List resources (list). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:55` (`list`) |
| `POST` | `/properties/{propertyId}/rooms/{roomId}/images` | `propertyId:UUID, roomId:UUID` | `file:MultipartFile (required), tagIds:List<UUID>? (optional)` | `-` | `RoomImageResponse` | `201` | Upload. | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:85` (`upload`) |
| `PUT` | `/properties/{propertyId}/rooms/{roomId}/images/reorder-room` | `propertyId:UUID, roomId:UUID` | `-` | `RoomImageReorderRequest` | `Unit` | `204` | Reorder room images. | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:199` (`reorderRoomImages`) |
| `PUT` | `/properties/{propertyId}/rooms/{roomId}/images/reorder-room-type` | `propertyId:UUID, roomId:UUID` | `-` | `RoomImageReorderRequest` | `Unit` | `204` | Reorder room type images. | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:227` (`reorderRoomTypeImages`) |
| `DELETE` | `/properties/{propertyId}/rooms/{roomId}/images/{imageId}` | `propertyId:UUID, roomId:UUID, imageId:UUID` | `-` | `-` | `Unit` | `204` | Delete resource (delete). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:129` (`delete`) |
| `GET` | `/properties/{propertyId}/rooms/{roomId}/images/{imageId}/file` | `propertyId:UUID, roomId:UUID, imageId:UUID` | `size:String (optional)` | `-` | `ResponseEntity<FileSystemResource>` | `200` | File. | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:257` (`file`) |
| `PUT` | `/properties/{propertyId}/rooms/{roomId}/images/{imageId}/tags` | `propertyId:UUID, roomId:UUID, imageId:UUID` | `-` | `RoomImageTagUpdateRequest` | `Unit` | `204` | Update resource (update tags). | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:180` (`updateTags`) |
| `GET` | `/properties/{propertyId}/transport-modes` | `propertyId:UUID` | `-` | `-` | `List<TransportModeStatusResponse>` | `200` | List resources (list). | `src/main/kotlin/com/android/trisolarisserver/controller/transport/TransportModes.kt:26` (`list`) |
| `GET` | `/properties/{propertyId}/users` | `propertyId:UUID` | `-` | `-` | `List<PropertyUserResponse>` | `200` | List resources (list property users). | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:165` (`listPropertyUsers`) |
| `GET` | `/properties/{propertyId}/users/search` | `propertyId:UUID` | `phone:String? (optional)` | `-` | `List<PropertyUserDetailsResponse>` | `200` | Search property users. | `src/main/kotlin/com/android/trisolarisserver/controller/property/UserDirectory.kt:50` (`searchPropertyUsers`) |
| `DELETE` | `/properties/{propertyId}/users/{userId}` | `propertyId:UUID, userId:UUID` | `-` | `-` | `Unit` | `204` | Delete resource (delete property user). | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:290` (`deletePropertyUser`) |
| `PUT` | `/properties/{propertyId}/users/{userId}/disabled` | `propertyId:UUID, userId:UUID` | `-` | `PropertyUserDisableRequest` | `PropertyUserResponse` | `200` | Update resource (update property user disabled). | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:240` (`updatePropertyUserDisabled`) |
| `PUT` | `/properties/{propertyId}/users/{userId}/roles` | `propertyId:UUID, userId:UUID` | `-` | `PropertyUserRoleRequest` | `PropertyUserResponse` | `200` | Upsert property user roles. | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:189` (`upsertPropertyUserRoles`) |
| `GET` | `/users` | `-` | `phone:String? (optional)` | `-` | `List<AppUserSummaryResponse>` | `200` | List resources (list app users). | `src/main/kotlin/com/android/trisolarisserver/controller/property/UserDirectory.kt:27` (`listAppUsers`) |

View File

@@ -0,0 +1,348 @@
#!/usr/bin/env python3
"""Generate API reference markdown from Spring controller annotations."""
from __future__ import annotations
import glob
import os
import re
from dataclasses import dataclass
from pathlib import Path
from typing import List
ROOT = Path(__file__).resolve().parents[1]
CONTROLLER_ROOT = ROOT / "src/main/kotlin/com/android/trisolarisserver/controller"
OUTPUT = ROOT / "docs/API_REFERENCE.md"
HTTP_BY_ANNOTATION = {
"GetMapping": "GET",
"PostMapping": "POST",
"PutMapping": "PUT",
"DeleteMapping": "DELETE",
"PatchMapping": "PATCH",
}
@dataclass
class Endpoint:
method: str
path: str
path_params: List[str]
query_params: List[str]
body_type: str
response_type: str
status: str
behavior: str
handler_file: str
handler_name: str
handler_line: int
def method_from_annotations(annotations: List[str]) -> List[str]:
for ann in annotations:
m = re.match(r"@(\w+)", ann.strip())
if not m:
continue
name = m.group(1)
if name in HTTP_BY_ANNOTATION:
return [HTTP_BY_ANNOTATION[name]]
if name == "RequestMapping":
methods = re.findall(r"RequestMethod\.(GET|POST|PUT|DELETE|PATCH)", ann)
return methods or ["ANY"]
return []
def mapping_path(annotations: List[str]) -> str:
for ann in annotations:
if "Mapping" in ann:
m = re.search(r'"([^"]*)"', ann)
if m:
return m.group(1)
return ""
def class_base_path(lines: List[str], class_line_index: int) -> str:
start = max(0, class_line_index - 50)
for i in range(start, class_line_index):
line = lines[i].strip()
if line.startswith("@RequestMapping"):
m = re.search(r'"([^"]+)"', line)
if m:
return m.group(1)
return ""
def split_params(param_blob: str) -> List[str]:
parts: List[str] = []
buf = []
angle = 0
paren = 0
bracket = 0
brace = 0
for ch in param_blob:
if ch == "<":
angle += 1
elif ch == ">":
angle = max(0, angle - 1)
elif ch == "(":
paren += 1
elif ch == ")":
paren = max(0, paren - 1)
elif ch == "[":
bracket += 1
elif ch == "]":
bracket = max(0, bracket - 1)
elif ch == "{":
brace += 1
elif ch == "}":
brace = max(0, brace - 1)
if ch == "," and angle == 0 and paren == 0 and bracket == 0 and brace == 0:
part = "".join(buf).strip()
if part:
parts.append(part)
buf = []
continue
buf.append(ch)
tail = "".join(buf).strip()
if tail:
parts.append(tail)
return parts
def extract_types_from_params(param_blob: str) -> tuple[list[str], list[str], str]:
path_params: List[str] = []
query_params: List[str] = []
body_type = "-"
for raw in split_params(param_blob):
segment = " ".join(raw.split())
name_match = re.search(r"(\w+)\s*:", segment)
param_name = name_match.group(1) if name_match else "param"
type_match = re.search(r":\s*([^=]+)", segment)
param_type = type_match.group(1).strip() if type_match else "Unknown"
if "@PathVariable" in segment:
path_params.append(f"{param_name}:{param_type}")
elif "@RequestParam" in segment:
required = "optional" if "required = false" in segment else "required"
query_params.append(f"{param_name}:{param_type} ({required})")
elif "@RequestBody" in segment:
body_type = param_type
return path_params, query_params, body_type
def default_status(method: str, response_type: str, explicit: str | None) -> str:
if explicit:
return explicit
return "200"
def explicit_status_from_annotations(annotations: List[str]) -> str | None:
for ann in annotations:
if ann.strip().startswith("@ResponseStatus"):
if "CREATED" in ann:
return "201"
if "NO_CONTENT" in ann:
return "204"
m = re.search(r"HttpStatus\.([A-Z_]+)", ann)
if m:
return m.group(1)
return None
def behavior_from_name(name: str) -> str:
words = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", name).lower()
if name.startswith("list"):
return f"List resources ({words})."
if name.startswith("create"):
return f"Create resource ({words})."
if name.startswith("update"):
return f"Update resource ({words})."
if name.startswith("delete"):
return f"Delete resource ({words})."
if name.startswith("get"):
return f"Get resource ({words})."
if name.startswith("stream"):
return f"Stream events/data ({words})."
return f"{words.capitalize()}."
def parse_endpoints(file_path: Path) -> List[Endpoint]:
text = file_path.read_text(encoding="utf-8")
if "@RestController" not in text and "@Controller" not in text:
return []
lines = text.splitlines()
class_line = None
for idx, line in enumerate(lines):
if re.search(r"\bclass\b", line):
class_line = idx
break
if class_line is None:
return []
base = class_base_path(lines, class_line)
endpoints: List[Endpoint] = []
pending_annotations: List[str] = []
i = 0
while i < len(lines):
stripped = lines[i].strip()
if stripped.startswith("@"):
pending_annotations.append(stripped)
i += 1
continue
if "fun " in stripped and pending_annotations:
method_names = method_from_annotations(pending_annotations)
if not method_names:
pending_annotations = []
i += 1
continue
fun_line = i + 1
signature = stripped
name_match = re.search(r"\bfun\s+(\w+)\s*\(", signature)
if not name_match:
pending_annotations = []
i += 1
continue
fun_name = name_match.group(1)
# Parse function parameters with parenthesis depth, so annotation arguments
# like @RequestParam(required = false) do not break parsing.
line_idx = i
line_pos = lines[i].find("(")
depth = 1
param_chars: List[str] = []
tail_chars: List[str] = []
while line_idx < len(lines):
current = lines[line_idx]
start = line_pos + 1 if line_idx == i else 0
cursor = start
while cursor < len(current):
ch = current[cursor]
if depth > 0:
if ch == "(":
depth += 1
param_chars.append(ch)
elif ch == ")":
depth -= 1
if depth > 0:
param_chars.append(ch)
else:
param_chars.append(ch)
else:
tail_chars.append(ch)
cursor += 1
if depth == 0:
# Capture any extra return type tokens from following lines until body opens.
if "{" not in "".join(tail_chars) and "=" not in "".join(tail_chars):
look = line_idx + 1
while look < len(lines):
nxt = lines[look].strip()
if nxt.startswith("{") or nxt.startswith("="):
break
if nxt.startswith("@"):
break
tail_chars.append(" ")
tail_chars.append(nxt)
if "{" in nxt or "=" in nxt:
break
look += 1
break
line_idx += 1
param_blob = "".join(param_chars).strip()
tail = "".join(tail_chars).strip()
path_params, query_params, body_type = extract_types_from_params(param_blob)
return_type = "Unit"
rmatch = re.search(r":\s*([^{=]+)", tail)
if rmatch:
return_type = rmatch.group(1).strip()
rel_path = mapping_path(pending_annotations)
full_path = "/" + "/".join([p.strip("/") for p in [base, rel_path] if p])
if full_path == "":
full_path = "/"
explicit_status = explicit_status_from_annotations(pending_annotations)
behavior = behavior_from_name(fun_name)
rel_file = os.path.relpath(file_path, ROOT)
for method in method_names:
endpoints.append(
Endpoint(
method=method,
path=full_path or "/",
path_params=path_params,
query_params=query_params,
body_type=body_type,
response_type=return_type,
status=default_status(method, return_type, explicit_status),
behavior=behavior,
handler_file=rel_file,
handler_name=fun_name,
handler_line=fun_line,
)
)
pending_annotations = []
i = line_idx + 1
continue
if stripped and not stripped.startswith("//"):
pending_annotations = []
i += 1
return endpoints
def main() -> None:
endpoints: List[Endpoint] = []
for file_name in sorted(glob.glob(str(CONTROLLER_ROOT / "**/*.kt"), recursive=True)):
endpoints.extend(parse_endpoints(Path(file_name)))
uniq = {}
for e in endpoints:
key = (e.method, e.path, e.handler_file, e.handler_name)
uniq[key] = e
ordered = sorted(uniq.values(), key=lambda e: (e.path, e.method, e.handler_file, e.handler_name))
lines = [
"# API Reference",
"",
"Generated from controller source. Use this for usage, params, response type, and behavior.",
"",
f"- Total endpoints: **{len(ordered)}**",
"- Auth: Firebase Bearer token unless endpoint is public.",
"- Regenerate: `python scripts/generate_api_docs.py`",
"",
"## Usage Template",
"",
"```bash",
"curl -X <METHOD> \"https://api.hoteltrisolaris.in<PATH>\" \\",
" -H \"Authorization: Bearer <FIREBASE_ID_TOKEN>\" \\",
" -H \"Content-Type: application/json\" \\",
" -d '<REQUEST_BODY_JSON>'",
"```",
"",
"Behavior notes in this file are handler summaries; strict business rules remain in controller code.",
"",
"| Method | Path | Path Params | Query Params | Body Type | Response Type | Status | Behavior | Handler |",
"|---|---|---|---|---|---|---|---|---|",
]
for e in ordered:
path_params = ", ".join(e.path_params) if e.path_params else "-"
query_params = ", ".join(e.query_params) if e.query_params else "-"
handler = f"`{e.handler_file}:{e.handler_line}` (`{e.handler_name}`)"
lines.append(
f"| `{e.method}` | `{e.path}` | `{path_params}` | `{query_params}` | `{e.body_type}` | `{e.response_type}` | `{e.status}` | {e.behavior} | {handler} |"
)
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
OUTPUT.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Wrote {OUTPUT} ({len(ordered)} endpoints)")
if __name__ == "__main__":
main()