mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-01 15:49:00 +08:00
b3ba6c00ff
* refactor(api)!: unify errors to AIP-193 + Page<T> pagination, enrich access log (#443) Settle the API consistency issues from #443 before SDKs ship. Breaking changes across the error envelope, list envelopes, and the generated Go client. Errors → AIP-193 google.rpc.Status (https://google.aip.dev/193): - every error body is now { error: { code, message, status, details:[ErrorInfo] } } - machine-readable, switchable key is details[0].reason (UPPER_SNAKE); status is the canonical google.rpc.Code; dynamic context lives in metadata (string→string) - built once in server/lib/http-errors.ts (buildErrorBody/ApiError/mapDomainError); inline handlers use apiError(c,status,msg,opts?); thrown errors flow through app.onError → renderError. Resolves #8 (one casing; no-storage 503 everywhere) and #9 (resource/maxBytes/conflictingName/licensing fields folded into metadata; featureGateErrorSchema removed) Pagination → Page<T> = { items, total, page, pageSize } via pageSchema + integer pageQuerySchema, applied to every list endpoint. image-hosting/images stays cursor (the one intentional exception). unreadCount moved out of the notifications list into /notifications/stats; entitlements drop the redundant orgId; team invitations use items. Access log: every 4xx/5xx carries reason + full message (set by apiError and renderError); a thrown domain error logs its mapped status (409, not 500); unhandled 500s log the full cause chain while the client gets a generic message. Frontend ApiError exposes reason/metadata/canonicalStatus; consumers updated. Go client regenerated from the new OpenAPI document. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(api): fix e2e name-conflict assertion + cover AIP-193 error branches - e2e/name-conflict.spec.ts: assert body.error.details[0].reason (AIP-193) instead of the removed top-level body.code - unit-test buildErrorBody, ApiError, and every mapDomainError branch (server/lib/http-errors.test.ts) and renderError + isHandledError (server/middleware/error-handler.test.ts) - integration-test the apiError error-branch guards the refactor touched: shares, redirect, site/invitations, objects, store/storefront, and the requirePermission middleware (authz) — restoring patch coverage above target Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(api): drop ad-hoc [spec:] breadcrumbs from new coverage tests lint:spec governs spec↔test traceability: a [spec: id] breadcrumb must map to a documented @id scenario in spec/**/*.feature. The added error-branch coverage tests are not Gherkin scenarios, so reference no spec id — use plain titles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(objects): allow the file-manager pageSize (500) on the objects list The shared pageQuerySchema caps pageSize at 100, but the file manager loads a whole folder client-side (FILES_PAGE_SIZE=500, transfer dialog 200) — the old z.string() query param was unbounded. With the cap, GET /api/objects?pageSize=500 returned 400, the file-manager list query errored and retried, and the toolbar / table never rendered (e2e: responsive @desktop + name-conflict table state). Raise just this list's ceiling to 1000 (default stays 20); other lists keep the 100 cap. Regression-tested: GET /api/objects?pageSize=500 → 200. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
import { release as osRelease } from 'node:os'
|
|
import { Hono } from 'hono'
|
|
import { constantTimeEqual } from '../lib/constant-time'
|
|
import type { Env } from '../middleware/platform'
|
|
import { getDeployPlatform } from '../runtime-platform'
|
|
import { INSTANCE_TELEMETRY_CRON, reportInstanceTelemetry } from '../usecases/site/instance-telemetry'
|
|
import { apiError } from './openapi'
|
|
|
|
const INTERNAL_API_TOKEN_ENV = 'ZPAN_INTERNAL_API_TOKEN'
|
|
|
|
const internal = new Hono<Env>()
|
|
|
|
function envAllowsIp(value: string | undefined): boolean {
|
|
return !['0', 'false', 'no', 'off'].includes(value?.trim().toLowerCase() ?? '')
|
|
}
|
|
|
|
internal.post('/instance-telemetry/report', async (c) => {
|
|
const platform = c.get('platform')
|
|
const token = platform.getEnv(INTERNAL_API_TOKEN_ENV)?.trim()
|
|
if (!token) return apiError(c, 404, 'Not found')
|
|
|
|
const auth = c.req.header('authorization') ?? ''
|
|
if (!constantTimeEqual(auth, `Bearer ${token}`)) return apiError(c, 401, 'Unauthorized')
|
|
|
|
const runtime = platform.getBinding('DB')
|
|
? {
|
|
runtime: 'workerd' as const,
|
|
platform: 'cloudflare-workers' as const,
|
|
}
|
|
: {
|
|
runtime: 'node' as const,
|
|
platform: getDeployPlatform() ?? 'node',
|
|
osPlatform: process.platform,
|
|
osArch: process.arch,
|
|
osRelease: osRelease(),
|
|
nodeVersion: process.version,
|
|
}
|
|
|
|
const result = await reportInstanceTelemetry(c.get('deps'), {
|
|
config: {
|
|
allowIp: envAllowsIp(platform.getEnv('ZPAN_TELEMETRY_ALLOW_IP')),
|
|
},
|
|
cron: INSTANCE_TELEMETRY_CRON,
|
|
trigger: 'deploy',
|
|
runtime,
|
|
})
|
|
|
|
return c.json(result)
|
|
})
|
|
|
|
export default internal
|