Repository navigation
feat(properties): health check, tracing, injection hardening and pagination for the Property API - #1473
Open
sadeeq6400 wants to merge 2 commits into
Conversation
…nation Operability and input hardening for the Property API (`src/properties`), following the Users API pass in MettaChain#1457-MettaChain#1463. - MettaChain#1375: GET /properties/health answers 200 when the database can read the properties table and 503 when it cannot or does not answer within 2s. The probe is a findFirst({ select: { id: true } }) so it is the cheapest read that still proves the connection, pool and table are usable; the timeout timer is always cleared. PropertiesHealthController is registered before PropertiesController so `GET /properties/health` is not captured by `GET /properties/:id`. No error detail is returned. docs/api/properties-health.md - MettaChain#1376: PropertiesTraceInterceptor logs one line per handler call with the trace id from TraceInterceptor (or x-request-id, or `untraced`), the handler, the outcome and the duration. Bodies, headers, addresses and personal data are never logged. docs/api/properties-tracing.md - MettaChain#1377: PropertyInjectionGuardPipe screens the write bodies and the search query for `$`-prefixed or dotted keys at any depth, NUL bytes, SQL comment/stacked-statement markers in the free-text fields, and payloads nested deeper than 8 levels. This is not redundant with the global ValidationPipe: `whitelist`/`forbidNonWhitelisted` only inspect the top level of a body, not keys reached inside a nested object or an array element. docs/api/properties-injection-hardening.md - MettaChain#1378: GET /properties accepts page (>=1) and limit (1-100, default 20). With neither parameter the response is still the plain array it always was; with either it becomes { data, meta: { page, limit, total, totalPages, hasNext } }. The total comes from a count() on the same where clause, issued in the same $transaction as the rows so the two cannot disagree. `findAll` is unchanged for existing callers and `findAllPaginated` was added alongside it; the shared where-clause and include payload were hoisted so the two cannot drift apart. GET /properties/search already paged and already capped limit at 100, so it is untouched. docs/api/properties-pagination.md One behaviour change worth calling out: because GET /properties now takes a validated query DTO, an unknown query parameter there is rejected with 400. That matches GET /properties/search and the global forbidNonWhitelisted setting. The existing integration spec asserts `GET /properties` returns a bare array, which the no-parameters path still does. Nothing was installed, built, linted or tested; please verify in CI. Closes MettaChain#1375 Closes MettaChain#1376 Closes MettaChain#1377 Closes MettaChain#1378
|
@sadeeq6400 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Operability and input hardening for the Property API (
src/properties), following the Users API pass in #1457–#1463.#1375 — Health check
GET /properties/health(unauthenticated) answers200when the database can read thepropertiestable, and503when it cannot or does not answer within 2 seconds.{ "status": "up", "checkedAt": "…", "checks": { "database": { "status": "up", "latencyMs": 3 } } }findFirst({ select: { id: true } })— the cheapest read available that still proves the connection, the pool and the table are all usable.Promise.raceand the timer is always cleared in afinally, so a slow database cannot leak a handle on every probe.PropertiesHealthControlleris registered beforePropertiesControllerin the module, soGET /properties/healthis matched by the probe and not swallowed byGET /properties/:id. This ordering is easy to get wrong and the Users API had to solve the same problem.Separate from the platform-wide
/healthzand/readyz, which report the process as a whole.→
docs/api/properties-health.md#1376 — Tracing
PropertiesTraceInterceptorlogs exactly one line per handler call:The trace id is the one set by the platform
TraceInterceptor, else the inboundx-request-id, elseuntraced— so a single listing call is followable across the log stream with no client-side correlation work. Success islog, failure iswarnwith status and error class, so failures can be alerted on without parsing messages.Bodies, headers, addresses and personal data are never logged. A listing create carries an owner's identity and a full address; that should not end up in log aggregation.
→
docs/api/properties-tracing.md#1377 — Injection hardening
PropertyInjectionGuardPipescreens the write bodies (create, update, status transition, bulk, agent assignment, amenities) and theGET /properties/searchquery, answering400for:$or containing.at any depth,--,/*,; drop|delete|update|insert|alter|truncate) in the free-text fields,Why this is not already covered: the global
ValidationPiperuns withwhitelist: true, forbidNonWhitelisted: true, but that only rejects unknown keys at the top level of a body. It does not descend into values — a$-prefixed key inside a nested object or a dotted key inside an array element is not whatwhitelistinspects. The pipe walks the whole structure, array elements included (propertyIds[0],tags[2]).Search is the highest-value target: it is the one unauthenticated route whose values build predicates, and
location,categoryandtagall end up incontains/hasfilters.Prisma parameterises every query, so a payload that got through would be bound rather than concatenated. This is the earlier layer, not a replacement.
→
docs/api/properties-injection-hardening.md#1378 — Pagination
GET /propertiesacceptspage(>= 1) andlimit(1–100, default 20).{ data, meta: { page, limit, total, totalPages, hasNext } }.totalcomes from acount()over the samewhereclause as the page, issued in the same$transactionas the rows, so the count and the page cannot disagree.findAllis left unchanged for existing callers andfindAllPaginatedis a new method alongside it; the shared "active and not deleted" filter and the owner/agent include payload were hoisted so the two cannot drift apart.GET /properties/searchalready paged and already cappedlimitat 100, so it is untouched and keeps its existing response shape.One behaviour change to be aware of: because the handler now takes a validated query DTO, an unknown query parameter on
GET /propertiesis rejected with400. That is consistent withGET /properties/searchand with the globalforbidNonWhitelisted: true, but it is new for this route.The existing integration spec asserts
GET /propertiesreturns a bare array, which the no-parameters path still does.→
docs/api/properties-pagination.mdScope
Module-local additions plus one doc per issue. Nothing was installed, built, linted or tested — please verify in CI.
Closes #1375
Closes #1376
Closes #1377
Closes #1378