feat: VPN batch version#639
Conversation
📝 WalkthroughWalkthroughThis PR adds a batch endpoint ( ChangesBatch VPN/Proxy Detection Feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
|
||
| httpx.JSON(w, http.StatusOK, result) | ||
| })) | ||
| r.Post("/ip/vpn/batch", httpx.Guard(svc, func(w http.ResponseWriter, r *http.Request) { |
There was a problem hiding this comment.
| r.Post("/ip/vpn/batch", httpx.Guard(svc, func(w http.ResponseWriter, r *http.Request) { | ||
| var req BatchRequest | ||
|
|
||
| if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
There was a problem hiding this comment.
Use struct validation, do not manually validate here
|
@copilot resolve the merge conflicts in this pull request |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/services/networking/ip/vpn/service.go`:
- Around line 64-73: The current batch worker converts all errors from s.CheckIP
into a fallback item (item / IPCheckResponse), which swallows systemic context
errors; change the error handling in the goroutine that calls s.CheckIP so that
if err is context.Canceled or context.DeadlineExceeded (or ctx.Err() != nil) you
propagate that as a top-level error instead of sending an item fallback (e.g.,
return a sentinel via an error channel or set a shared error variable),
otherwise continue sending the fallback item for non-context errors; also, in
the fan-in/collector section that aggregates results, check ctx.Err() (or the
error channel) before returning the final success payload and surface ctx.Err()
as the returned error when present.
In `@apps/api/services/networking/ip/vpn/type.go`:
- Line 38: The struct tag on BatchRequest.IPs currently enforces per-item
validation (`dive,required,ip`) causing BindAndValidate to reject requests with
any malformed IP; update the tag to only validate the slice itself (e.g. change
`validate:"required,min=1,max=50,dive,required,ip"` to
`validate:"required,min=1,max=50,dive"`) so malformed or empty IP strings pass
initial binding and svc.CheckBatch can return per-item errors as intended;
modify the BatchRequest type's IPs field accordingly.
In `@apps/dashboard/config/api_docs/vpn-detection.yml`:
- Around line 145-193: The docs for the batch VPN detection response are missing
the data.total field returned by httpx.HandleBatch; update the response_example
JSON to include "total": <number> alongside "results" under "data", and add a
corresponding response_fields entry (name: total, type: integer, description:
Total number of input IPs / total results) so the OpenAPI example and field list
match the runtime behavior from HandleBatch.
In `@docs/apis/places/timezone.md`:
- Around line 166-189: The file contains duplicated sections titled "###
Response Fields" and "### Validation Rules" (including the table of fields like
`results`, `ip`, `is_vpn`, `score`, `fraud_score`, `asn_org`), causing MD024
lint warnings; remove the redundant duplicate block so only one copy of the "###
Response Fields" table and one "### Validation Rules" list remain, ensuring the
canonical section contains the full field list and validation bullets and that
any references to `results` or the validation constraints point to that single
section.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 01570c57-3064-4569-b0d3-414e680040a9
📒 Files selected for processing (8)
apps/api/services/networking/ip/vpn/service.goapps/api/services/networking/ip/vpn/service_test.goapps/api/services/networking/ip/vpn/transport_http.goapps/api/services/networking/ip/vpn/transport_http_test.goapps/api/services/networking/ip/vpn/type.goapps/dashboard/config/api_catalog.ymlapps/dashboard/config/api_docs/vpn-detection.ymldocs/apis/places/timezone.md
| result, err := s.CheckIP(ctx, ip) | ||
| if err != nil { | ||
| ch <- item{ | ||
| index: index, | ||
| result: IPCheckResponse{ | ||
| IP: raw, | ||
| }, | ||
| } | ||
| return | ||
| } |
There was a problem hiding this comment.
Don’t swallow systemic context errors in batch processing.
At Line 64-73, CheckIP errors are always converted to item fallbacks. For context.Canceled/context.DeadlineExceeded, this should return a top-level error instead of a successful batch payload.
🔧 Suggested adjustment
+import "errors"
...
- result, err := s.CheckIP(ctx, ip)
+ result, err := s.CheckIP(ctx, ip)
if err != nil {
+ if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+ ch <- item{
+ index: index,
+ result: IPCheckResponse{}, // marker slot; handled after fan-in
+ }
+ return
+ }
ch <- item{
index: index,
result: IPCheckResponse{
IP: raw,
},
}
return
}And in the fan-in section, surface ctx.Err() if set before returning success.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/services/networking/ip/vpn/service.go` around lines 64 - 73, The
current batch worker converts all errors from s.CheckIP into a fallback item
(item / IPCheckResponse), which swallows systemic context errors; change the
error handling in the goroutine that calls s.CheckIP so that if err is
context.Canceled or context.DeadlineExceeded (or ctx.Err() != nil) you propagate
that as a top-level error instead of sending an item fallback (e.g., return a
sentinel via an error channel or set a shared error variable), otherwise
continue sending the fallback item for non-context errors; also, in the
fan-in/collector section that aggregates results, check ctx.Err() (or the error
channel) before returning the final success payload and surface ctx.Err() as the
returned error when present.
| func (IPCheckResponse) IsData() {} | ||
|
|
||
| type BatchRequest struct { | ||
| IPs []string `json:"ips" validate:"required,min=1,max=50,dive,required,ip"` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify request validation currently enforces per-item IP format
rg -n 'type BatchRequest|validate:"' apps/api/services/networking/ip/vpn/type.go
# Verify tests/documentation expect partial success with invalid IP items
rg -n 'TestVPN_Batch_InvalidIPs|StatusOK|invalid-ip|Response preserves input order|Invalid IPs are included' \
apps/api/services/networking/ip/vpn/transport_http_test.go \
apps/api/services/networking/ip/vpn/service_test.go \
apps/dashboard/config/api_docs/vpn-detection.yml \
docs/apis/places/timezone.mdRepository: bobadilla-tech/requiems-api
Length of output: 1864
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the batch HTTP handler and its validation/binding logic
fd -a "transport_http.go" apps/api/services/networking/ip/vpn -t f | head -n 20
rg -n "Batch|CheckBatch|validate|validator|Bind|Decode|json.NewDecoder|httpx\." apps/api/services/networking/ip/vpn/transport_http.go
# Find the service method implementing batch partial-success behavior
rg -n "CheckBatch|BatchRequest|BatchResponse|Invalid IP|invalid-ip" apps/api/services/networking/ip/vpn -S
# Inspect the BatchRequest/BatchResponse helpers to see intended semantics
sed -n '1,220p' apps/api/services/networking/ip/vpn/type.goRepository: bobadilla-tech/requiems-api
Length of output: 4886
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the batch handler implementation in this file
sed -n '1,140p' apps/api/services/networking/ip/vpn/transport_http.go
sed -n '140,260p' apps/api/services/networking/ip/vpn/transport_http.go
# Inspect tests that cover invalid batch items (what HTTP status + payload they expect)
sed -n '240,420p' apps/api/services/networking/ip/vpn/transport_http_test.go
sed -n '80,260p' apps/api/services/networking/ip/vpn/service_test.go
# Locate httpx.HandleBatch implementation in the repo (or module cache if vendored)
# Prefer searching in repository first.
rg -n "func HandleBatch" -S . || true
rg -n "HandleBatch\\[" -S . || true
# If not found in-repo, try to locate in go/pkg/mod by searching workspace (may be slow but bounded by ripgrep).
rg -n "HandleBatch\\[" -S "$(go env GOPATH 2>/dev/null)/pkg/mod" 2>/dev/null | head -n 20 || trueRepository: bobadilla-tech/requiems-api
Length of output: 6772
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' apps/api/platform/httpx/handler.go
sed -n '220,420p' apps/api/platform/httpx/handler.go
rg -n "validator|validate:\"|go-playground/validator|Bind|Decode|Validate" apps/api/platform/httpx -S
ls -R apps/api/platform/httpx | head -n 50Repository: bobadilla-tech/requiems-api
Length of output: 11143
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,140p' apps/api/platform/httpx/validate.go
sed -n '1,220p' apps/api/platform/httpx/binding.go
sed -n '1,200p' apps/api/platform/httpx/errors.go
# Inspect how ValidationError maps to HTTP status/message
rg -n "func ValidationError" -S apps/api/platform/httpx
sed -n '1,120p' apps/api/platform/httpx/errors.goRepository: bobadilla-tech/requiems-api
Length of output: 5271
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect how test router is constructed / how HandleBatch is wired
sed -n '1,120p' apps/api/services/networking/ip/vpn/transport_http_test.go
# Inspect httpx.ValidationError mapping to status code
sed -n '1,120p' apps/api/platform/httpx/httpx.go
# Confirm BatchRequest validation tags in type.go (entire file small)
cat apps/api/services/networking/ip/vpn/type.goRepository: bobadilla-tech/requiems-api
Length of output: 7419
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Run only the VPN batch invalid-IP HTTP test to see actual status code
go test ./apps/api/services/networking/ip/vpn -run '^TestVPN_Batch_InvalidIPs$' -count=1
# Also run the service-level invalid IP test (should not depend on request validation)
go test ./apps/api/services/networking/ip/vpn -run '^TestService_CheckBatch_InvalidIPs$' -count=1Repository: bobadilla-tech/requiems-api
Length of output: 189
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls
find . -maxdepth 4 -name "go.mod" -o -name "go.work" | sed 's|^\./||'
find . -maxdepth 3 -type f -name "go.mod" -print -exec sh -c 'echo "----"; cat {} | head -n 20' \;Repository: bobadilla-tech/requiems-api
Length of output: 901
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cd apps/api
go test ./services/networking/ip/vpn -run '^TestVPN_Batch_InvalidIPs$' -count=1Repository: bobadilla-tech/requiems-api
Length of output: 1224
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cd apps/api
cat > /tmp/validate_batchreq.go <<'EOF'
package main
import (
"fmt"
"requiems-api/platform/httpx"
)
type BatchRequest struct {
IPs []string `json:"ips" validate:"required,min=1,max=50,dive,required,ip"`
}
func main() {
req := BatchRequest{
IPs: []string{"8.8.8.8", "invalid-ip", "", "999.999.999.999"},
}
err := httpx.Validate.Struct(req)
fmt.Printf("err=%T %v\n", err, err)
// If it's a validator.ValidationErrors, print details
type VErr interface {
error
}
_ = VErr(nil)
}
EOF
go run /tmp/validate_batchreq.goRepository: bobadilla-tech/requiems-api
Length of output: 366
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cd apps/api
go test ./services/networking/ip/vpn -run '^TestVPN_Batch_InvalidIPs$' -count=1 -vRepository: bobadilla-tech/requiems-api
Length of output: 359
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' apps/api/services/networking/ip/vpn/service.go
sed -n '220,420p' apps/api/services/networking/ip/vpn/service.goRepository: bobadilla-tech/requiems-api
Length of output: 1607
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cd apps/api
cat > /tmp/validate_http_batch.go <<'EOF'
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"requiems-api/platform/httpx"
vpn "requiems-api/services/networking/ip/vpn"
)
func main() {
h := httpx.HandleBatch(
func(ctx interface{}, req vpn.BatchRequest) (httpx.BatchResponse[vpn.IPCheckResponse], error) {
// Should never run if validation fails
return httpx.BatchResponse[vpn.IPCheckResponse]{Results: []vpn.IPCheckResponse{}}, nil
},
)
// ^ compile-time error because ctx type mismatch; rebuild below with correct signature.
_ = h
_ = json.Number("0")
_ = bytes.NewBuffer(nil)
fmt.Println("skip")
}
EOFRepository: bobadilla-tech/requiems-api
Length of output: 53
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cd apps/api
cat > /tmp/validate_http_batch.go <<'EOF'
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"requiems-api/platform/httpx"
vpn "requiems-api/services/networking/ip/vpn"
)
func main() {
h := httpx.HandleBatch(
func(ctx context.Context, req vpn.BatchRequest) (httpx.BatchResponse[vpn.IPCheckResponse], error) {
return httpx.BatchResponse[vpn.IPCheckResponse]{Results: []vpn.IPCheckResponse{}}, nil
},
)
body := []byte(`{
"ips": ["8.8.8.8","invalid-ip","","999.999.999.999"]
}`)
req := httptest.NewRequest(http.MethodPost, "/ip/vpn/batch", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
fmt.Println("status:", w.Code)
// Try decode error response (covers 422)
var er httpx.ErrorResponse
_ = json.NewDecoder(w.Body).Decode(&er)
fmt.Printf("body: error=%q message=%q fields=%d\n", er.Error, er.Message, len(er.Fields))
}
EOF
go run /tmp/validate_http_batch.goRepository: bobadilla-tech/requiems-api
Length of output: 135
🏁 Script executed:
#!/bin/bash
# Sanity-check current validation tags
rg -n 'type BatchRequest struct|validate:"required,min=1,max=50,dive,required,ip"' apps/api/services/networking/ip/vpn/type.go
# Ensure partial-success expectations exist (invalid IPs included, order preserved)
rg -n 'TestVPN_Batch_InvalidIPs|StatusOK|invalid-ip|Response preserves input order|Invalid IPs are included' \
apps/api/services/networking/ip/vpn/transport_http_test.go \
apps/api/services/networking/ip/vpn/service_test.go \
apps/dashboard/config/api_docs/vpn-detection.yml \
docs/apis/places/timezone.mdRepository: bobadilla-tech/requiems-api
Length of output: 1864
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cd apps/api
sed -n '90,220p' ../docs/apis/places/timezone.md
sed -n '130,290p' ../apps/dashboard/config/api_docs/vpn-detection.yml 2>/dev/null || trueRepository: bobadilla-tech/requiems-api
Length of output: 146
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cd apps/api
sed -n '100,220p' ../../docs/apis/places/timezone.md
sed -n '140,290p' ../../apps/dashboard/config/api_docs/vpn-detection.ymlRepository: bobadilla-tech/requiems-api
Length of output: 7056
Relax /ip/vpn/batch request validation to enable per-item partial success
apps/api/services/networking/ip/vpn/type.go sets BatchRequest.IPs to validate:"required,min=1,max=50,dive,required,ip". httpx.HandleBatch runs BindAndValidate before calling svc.CheckBatch, so any malformed/empty element causes the entire request to fail with a validation error (422), contradicting the documented behavior that invalid IPs are returned in-band with zeroed detection fields.
🔧 Proposed fix
type BatchRequest struct {
- IPs []string `json:"ips" validate:"required,min=1,max=50,dive,required,ip"`
+ IPs []string `json:"ips" validate:"required,min=1,max=50"`
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| IPs []string `json:"ips" validate:"required,min=1,max=50,dive,required,ip"` | |
| type BatchRequest struct { | |
| IPs []string `json:"ips" validate:"required,min=1,max=50"` | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/services/networking/ip/vpn/type.go` at line 38, The struct tag on
BatchRequest.IPs currently enforces per-item validation (`dive,required,ip`)
causing BindAndValidate to reject requests with any malformed IP; update the tag
to only validate the slice itself (e.g. change
`validate:"required,min=1,max=50,dive,required,ip"` to
`validate:"required,min=1,max=50,dive"`) so malformed or empty IP strings pass
initial binding and svc.CheckBatch can return per-item errors as intended;
modify the BatchRequest type's IPs field accordingly.
| response_example: | | ||
| { | ||
| "data": { | ||
| "results": [ | ||
| { | ||
| "ip": "8.8.8.8", | ||
| "is_vpn": false, | ||
| "is_proxy": false, | ||
| "is_tor": false, | ||
| "is_hosting": true, | ||
| "score": 1, | ||
| "threat": 1, | ||
| "fraud_score": 0, | ||
| "asn_org": "GOOGLE-ASN" | ||
| }, | ||
| { | ||
| "ip": "1.1.1.1", | ||
| "is_vpn": false, | ||
| "is_proxy": false, | ||
| "is_tor": false, | ||
| "is_hosting": true, | ||
| "score": 1, | ||
| "threat": 1, | ||
| "fraud_score": 0, | ||
| "asn_org": "CLOUDFLARE-ASN" | ||
| }, | ||
| { | ||
| "ip": "invalid-ip", | ||
| "is_vpn": false, | ||
| "is_proxy": false, | ||
| "is_tor": false, | ||
| "is_hosting": false, | ||
| "score": 0, | ||
| "threat": 0, | ||
| "fraud_score": 0, | ||
| "asn_org": "" | ||
| } | ||
| ] | ||
| }, | ||
| "metadata": { | ||
| "timestamp": "2026-01-01T00:00:00Z" | ||
| } | ||
| } | ||
|
|
||
| response_fields: | ||
| - name: results | ||
| type: array | ||
| description: List of IP detection results (preserves input order) | ||
|
|
There was a problem hiding this comment.
Batch response docs are missing data.total.
httpx.HandleBatch returns data.total alongside data.results. The new docs should include it in both response_example and response_fields to match runtime behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/dashboard/config/api_docs/vpn-detection.yml` around lines 145 - 193, The
docs for the batch VPN detection response are missing the data.total field
returned by httpx.HandleBatch; update the response_example JSON to include
"total": <number> alongside "results" under "data", and add a corresponding
response_fields entry (name: total, type: integer, description: Total number of
input IPs / total results) so the OpenAPI example and field list match the
runtime behavior from HandleBatch.
| ### Response Fields | ||
|
|
||
| | Field | Type | Description | | ||
| | ------------- | ------- | ------------------------------------------------------- | | ||
| | `results` | array | List of IP detection results (preserves input order) | | ||
| | `ip` | string | IP address analyzed | | ||
| | `is_vpn` | boolean | True if IP belongs to a VPN provider | | ||
| | `is_proxy` | boolean | True if IP is a proxy | | ||
| | `is_tor` | boolean | True if IP is a Tor exit node | | ||
| | `is_hosting` | boolean | True if IP belongs to a data-centre or hosting provider | | ||
| | `score` | integer | Threat score (Tor +3, VPN +2, Proxy +2, Hosting +1) | | ||
| | `threat` | integer | 0=None, 1=Low, 2-3=Medium, 4-5=High, 6+=Critical | | ||
| | `fraud_score` | integer | Fraud risk score from 0 to 100 | | ||
| | `asn_org` | string | ASN organization name | | ||
|
|
||
| --- | ||
|
|
||
| ### Validation Rules | ||
|
|
||
| - Maximum 50 IPs per request | ||
| - At least 1 IP required | ||
| - Invalid IPs do not fail the request | ||
| - Response preserves input order | ||
| - Each IP is evaluated independently |
There was a problem hiding this comment.
Duplicate sections should be removed (MD024 warning).
Line 166-189 repeats the same ### Response Fields and ### Validation Rules content, which triggers duplicate-heading lint warnings and adds noise.
🧹 Suggested cleanup
-### Response Fields
-
-| Field | Type | Description |
-| ------------- | ------- | ------------------------------------------------------- |
-| `results` | array | List of IP detection results (preserves input order) |
-| `ip` | string | IP address analyzed |
-| `is_vpn` | boolean | True if IP belongs to a VPN provider |
-| `is_proxy` | boolean | True if IP is a proxy |
-| `is_tor` | boolean | True if IP is a Tor exit node |
-| `is_hosting` | boolean | True if IP belongs to a data-centre or hosting provider |
-| `score` | integer | Threat score (Tor +3, VPN +2, Proxy +2, Hosting +1) |
-| `threat` | integer | 0=None, 1=Low, 2-3=Medium, 4-5=High, 6+=Critical |
-| `fraud_score` | integer | Fraud risk score from 0 to 100 |
-| `asn_org` | string | ASN organization name |
-
----
-
-### Validation Rules
-
-- Maximum 50 IPs per request
-- At least 1 IP required
-- Invalid IPs do not fail the request
-- Response preserves input order
-- Each IP is evaluated independently📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### Response Fields | |
| | Field | Type | Description | | |
| | ------------- | ------- | ------------------------------------------------------- | | |
| | `results` | array | List of IP detection results (preserves input order) | | |
| | `ip` | string | IP address analyzed | | |
| | `is_vpn` | boolean | True if IP belongs to a VPN provider | | |
| | `is_proxy` | boolean | True if IP is a proxy | | |
| | `is_tor` | boolean | True if IP is a Tor exit node | | |
| | `is_hosting` | boolean | True if IP belongs to a data-centre or hosting provider | | |
| | `score` | integer | Threat score (Tor +3, VPN +2, Proxy +2, Hosting +1) | | |
| | `threat` | integer | 0=None, 1=Low, 2-3=Medium, 4-5=High, 6+=Critical | | |
| | `fraud_score` | integer | Fraud risk score from 0 to 100 | | |
| | `asn_org` | string | ASN organization name | | |
| --- | |
| ### Validation Rules | |
| - Maximum 50 IPs per request | |
| - At least 1 IP required | |
| - Invalid IPs do not fail the request | |
| - Response preserves input order | |
| - Each IP is evaluated independently |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 166-166: Multiple headings with the same content
(MD024, no-duplicate-heading)
[warning] 183-183: Multiple headings with the same content
(MD024, no-duplicate-heading)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/apis/places/timezone.md` around lines 166 - 189, The file contains
duplicated sections titled "### Response Fields" and "### Validation Rules"
(including the table of fields like `results`, `ip`, `is_vpn`, `score`,
`fraud_score`, `asn_org`), causing MD024 lint warnings; remove the redundant
duplicate block so only one copy of the "### Response Fields" table and one "###
Validation Rules" list remain, ensuring the canonical section contains the full
field list and validation bullets and that any references to `results` or the
validation constraints point to that single section.
-add testing for service and transport_http
-add documentation
Summary by CodeRabbit
New Features
Documentation