-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
feat: 新增 Swagger API 文件,定義 URL 縮短服務的各項 API 端點 #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
WalkthroughAdds a new swagger.json file defining an OpenAPI 3.0 specification for the Sink API at https://aiurl.tw, documenting endpoints for location, verification, link management, logs, and statistics. Introduces component schemas Link and Query with field constraints, parameters, and response structures for all listed API operations. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant API as Sink API
participant Store as Data Store
rect rgb(243,247,254)
note right of Client: Create Link
Client->>API: POST /api/link/create {Link}
API->>Store: validate & insert Link
Store-->>API: {link, shortLink}
API-->>Client: 201 {link, shortLink}
end
rect rgb(245,255,245)
note right of Client: Query Link
Client->>API: GET /api/link/query?slug=...
API->>Store: fetch by slug
Store-->>API: Link or 404
API-->>Client: 200 Link / 404
end
rect rgb(255,248,240)
note right of Client: Stats (Views)
Client->>API: GET /api/stats/views?unit=day&clientTimezone=Etc/UTC
API->>Store: aggregate views
Store-->>API: [{time, visits, visitors}]
API-->>Client: 200 array
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Pre-merge checks (3 passed)✅ Passed checks (3 passed)
Poem
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. ✨ Finishing Touches🧪 Generate unit tests
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (9)
swagger.json (9)
10-11: Fix domain inconsistency (aiurl.tw vs sink.cool).Server base is aiurl.tw, but example under /api/verify uses sink.cool. Align to a single canonical domain.
- "url": { - "type": "string", - "example": "https://sink.cool" - } + "url": { + "type": "string", + "example": "https://aiurl.tw" + }Also applies to: 58-61
162-200: Use conventional HTTP semantics for edit and delete.
- PUT returning 201 is unconventional for updates; prefer 200 (with body) or 204 (no body).
- Deleting via POST is surprising; prefer DELETE with path param.
@@ - "responses": { - "201": { - "description": "Link updated successfully", + "responses": { + "200": { + "description": "Link updated successfully", "content": { "application/json": { "schema": { @@ - "/api/link/delete": { - "post": { + "/api/link/{slug}": { + "delete": { "summary": "Delete a link", "description": "Delete a short link by slug", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "slug": { - "type": "string", - "description": "The slug of the link to delete" - } - } - } - } - } - }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { "type": "string" }, + "description": "The slug of the link to delete" + } + ], "responses": { "200": { "description": "Link deleted successfully" }, "403": { "description": "Preview mode cannot delete links" } } } },Also applies to: 201-230
1161-1215: Constrain Link.id, Link.slug, and timestamp fields.
- id looks like ULID length 26—add pattern and format.
- slug max 2048 is excessive; constrain to a short length with a safe pattern.
- Use int64 for timestamps.
"Link": { "type": "object", "properties": { "id": { "type": "string", "maxLength": 26, - "description": "Unique identifier for the link" + "description": "Unique identifier for the link (ULID)", + "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$" }, @@ "slug": { "type": "string", - "maxLength": 2048, - "description": "The short slug for the link" + "minLength": 1, + "maxLength": 64, + "pattern": "^[A-Za-z0-9_-]+$", + "description": "The short slug for the link" }, @@ "createdAt": { - "type": "integer", + "type": "integer", + "format": "int64", "description": "Creation timestamp" }, "updatedAt": { - "type": "integer", + "type": "integer", + "format": "int64", "description": "Last update timestamp" }, "expiration": { - "type": "integer", + "type": "integer", + "format": "int64", "description": "Expiration timestamp" },Confirm these constraints match existing data before enforcing.
1217-1271: Enrich Query schema types, formats, and descriptions.Clarify timestamps; add URI formats; cap limit; describe locale fields.
"Query": { "type": "object", "properties": { "id": { "type": "string", "description": "Link ID or workspace ID" }, - "startAt": { "type": "integer" }, - "endAt": { "type": "integer" }, - "url": { "type": "string" }, + "startAt": { "type": "integer", "format": "int64", "description": "Unix ms timestamp" }, + "endAt": { "type": "integer", "format": "int64", "description": "Unix ms timestamp" }, + "url": { "type": "string", "format": "uri" }, "slug": { "type": "string" }, - "referer": { "type": "string" }, + "referer": { "type": "string", "format": "uri" }, "country": { "type": "string" }, "region": { "type": "string" }, "city": { "type": "string" }, - "timezone": { "type": "string" }, - "language": { "type": "string" }, + "timezone": { "type": "string", "description": "IANA TZ (e.g., America/Los_Angeles)" }, + "language": { "type": "string", "description": "BCP 47 tag (e.g., en-US)" }, "os": { "type": "string" }, "browser": { "type": "string" }, "browserType": { "type": "string" }, "device": { "type": "string" }, "deviceType": { "type": "string" }, "limit": { "type": "integer", - "default": 20 + "default": 20, + "minimum": 1, + "maximum": 1024 } } }
15-41: Add operationId and tags to facilitate client generation and grouping.Helps SDKs and UI grouping; apply across all operations.
"get": { + "operationId": "getLocation", + "tags": ["Location"], "summary": "Get user location",
26-33: Constrain latitude/longitude ranges.Add min/max to prevent invalid coordinates.
"latitude": { - "type": "number", + "type": "number", + "minimum": -90, + "maximum": 90, "description": "Latitude coordinate" }, "longitude": { - "type": "number", + "type": "number", + "minimum": -180, + "maximum": 180, "description": "Longitude coordinate" }
831-837: Enumerate allowed metric types.Document concrete values to make the contract testable (e.g., "country","region","city","timezone","language","os","browser","browserType","device","deviceType","referer").
{ "name": "type", "in": "query", "required": true, "schema": { - "type": "string" + "type": "string", + "enum": ["country","region","city","timezone","language","os","browser","browserType","device","deviceType","referer","url","slug"] }, "description": "The type of metric to retrieve" },
1140-1142: Use RFC3339 timestamps and clarify timezone input.
- Add date-time format to views.time.
- Note that clientTimezone must be an IANA name.
{ "name": "clientTimezone", "in": "query", "schema": { "type": "string", "default": "Etc/UTC" - }, - "description": "Client timezone" + }, + "description": "Client timezone (IANA name, e.g., America/Los_Angeles)" }, @@ "time": { - "type": "string" + "type": "string", + "format": "date-time", + "description": "Bucket start timestamp (RFC3339)" },Also applies to: 1001-1007
263-294: Paginate /api/link/search to avoid unbounded scans.Introduce limit/cursor (same as list) and bound the array.
"/api/link/search": { "get": { "summary": "Search links", - "description": "Search and retrieve all links with their metadata", + "description": "Search and retrieve links with their metadata", + "parameters": [ + { "name": "q", "in": "query", "schema": { "type": "string", "maxLength": 256 }, "description": "Search query over slug/url/comment" }, + { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20, "minimum": 1, "maximum": 1024 } }, + { "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "Pagination cursor" } + ], "responses": { "200": { "description": "List of links", "content": { "application/json": { "schema": { "type": "array", + "minItems": 0, + "maxItems": 1024, "items": {Also applies to: 269-291
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
swagger.json(1 hunks)
🧰 Additional context used
🪛 Checkov (3.2.334)
swagger.json
[high] 1-1275: Ensure that the global security field has rules defined
(CKV_OPENAPI_4)
[high] 1-1275: Ensure that security operations is not empty.
(CKV_OPENAPI_5)
[medium] 102-106: Ensure that arrays have a maximum number of items
(CKV_OPENAPI_21)
| { | ||
| "openapi": "3.0.0", | ||
| "info": { | ||
| "title": "Sink API", | ||
| "version": "1.0.0", | ||
| "description": "URL shortening service API with analytics and real-time tracking" | ||
| }, | ||
| "servers": [ | ||
| { | ||
| "url": "https://aiurl.tw" | ||
| } | ||
| ], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add global security scheme and top-level security requirement.
Spec exposes write/read endpoints without any security; Checkov flags CKV_OPENAPI_4/5. Define a security scheme (e.g., bearer JWT) and apply it globally; override per-op for public routes.
"openapi": "3.0.0",
"info": {
"title": "Sink API",
"version": "1.0.0",
"description": "URL shortening service API with analytics and real-time tracking"
},
+ "security": [
+ { "bearerAuth": [] }
+ ],
"servers": [
@@
"components": {
- "schemas": {
+ "securitySchemes": {
+ "bearerAuth": {
+ "type": "http",
+ "scheme": "bearer",
+ "bearerFormat": "JWT",
+ "description": "Provide a valid JWT in the Authorization header: Bearer <token>."
+ }
+ },
+ "schemas": {Apply "security": [] to explicitly public ops if needed (e.g., GET /api/verify).
Also applies to: 1159-1274
🧰 Tools
🪛 Checkov (3.2.334)
[high] 1-1275: Ensure that the global security field has rules defined
(CKV_OPENAPI_4)
[high] 1-1275: Ensure that security operations is not empty.
(CKV_OPENAPI_5)
🤖 Prompt for AI Agents
In swagger.json around lines 1 to 12 (and also apply changes covering lines 1159
to 1274), the OpenAPI spec lacks a global security scheme and top-level security
requirement causing Checkov CKV_OPENAPI_4/5 failures; add a securitySchemes
entry (e.g., bearerAuth using http type "bearer" and scheme "bearer",
bearerFormat "JWT") under components, add a top-level "security" array that
requires bearerAuth for all operations by default, and then explicitly override
public endpoints (for example PUT/GET /api/verify) by adding "security": [] to
those operation objects to mark them as unauthenticated.
| "name": "limit", | ||
| "in": "query", | ||
| "schema": { | ||
| "type": "integer", | ||
| "default": 20, | ||
| "maximum": 1024 | ||
| }, | ||
| "description": "Number of links per page" | ||
| }, | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Tighten pagination contract and array bounds for /api/link/list.
Add a minimum for limit; align response array bounds; add 400 when limit is invalid.
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"default": 20,
- "maximum": 1024
+ "minimum": 1,
+ "maximum": 1024
},
"description": "Number of links per page"
},
@@
"links": {
"type": "array",
+ "minItems": 0,
+ "maxItems": 1024,
"items": {
"$ref": "#/components/schemas/Link"
}
},
@@
- }
+ },
+ "400": { "description": "Invalid pagination parameters" }Also applies to: 100-115
| "responses": { | ||
| "201": { | ||
| "description": "Link created successfully", | ||
| "content": { | ||
| "application/json": { | ||
| "schema": { | ||
| "type": "object", | ||
| "properties": { | ||
| "link": { | ||
| "$ref": "#/components/schemas/Link" | ||
| }, | ||
| "shortLink": { | ||
| "type": "string", | ||
| "example": "https://aiurl.tw/abc123" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "409": { | ||
| "description": "Link already exists" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Standardize error responses with a shared Error schema.
Most endpoints lack structured 4xx/5xx responses. Add a components.schemas.Error and reference it across endpoints for consistent clients and observability.
"responses": {
"201": {
"description": "Link created successfully",
@@
- "409": {
- "description": "Link already exists"
- }
+ "400": { "description": "Invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
+ "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
+ "403": { "description": "Forbidden", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
+ "409": { "description": "Link already exists", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
+ "429": { "description": "Rate limited", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
+ "500": { "description": "Server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
} "components": {
+ "schemas": {
+ "Error": {
+ "type": "object",
+ "required": ["code", "message"],
+ "properties": {
+ "code": { "type": "string", "description": "Machine-readable error code" },
+ "message": { "type": "string", "description": "Human-readable error message" },
+ "details": { "type": "object", "additionalProperties": true }
+ }
+ },
"Link": {📝 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.
| "responses": { | |
| "201": { | |
| "description": "Link created successfully", | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "type": "object", | |
| "properties": { | |
| "link": { | |
| "$ref": "#/components/schemas/Link" | |
| }, | |
| "shortLink": { | |
| "type": "string", | |
| "example": "https://aiurl.tw/abc123" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| "409": { | |
| "description": "Link already exists" | |
| } | |
| } | |
| "responses": { | |
| "201": { | |
| "description": "Link created successfully", | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "type": "object", | |
| "properties": { | |
| "link": { | |
| "$ref": "#/components/schemas/Link" | |
| }, | |
| "shortLink": { | |
| "type": "string", | |
| "example": "https://aiurl.tw/abc123" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| "400": { | |
| "description": "Invalid input", | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "$ref": "#/components/schemas/Error" | |
| } | |
| } | |
| } | |
| }, | |
| "401": { | |
| "description": "Unauthorized", | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "$ref": "#/components/schemas/Error" | |
| } | |
| } | |
| } | |
| }, | |
| "403": { | |
| "description": "Forbidden", | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "$ref": "#/components/schemas/Error" | |
| } | |
| } | |
| } | |
| }, | |
| "409": { | |
| "description": "Link already exists", | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "$ref": "#/components/schemas/Error" | |
| } | |
| } | |
| } | |
| }, | |
| "429": { | |
| "description": "Rate limited", | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "$ref": "#/components/schemas/Error" | |
| } | |
| } | |
| } | |
| }, | |
| "500": { | |
| "description": "Server error", | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "$ref": "#/components/schemas/Error" | |
| } | |
| } | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In swagger.json around lines 136 to 159, the 4xx/5xx responses are not
standardized; add a components.schemas.Error object under components.schemas
(e.g., { type: "object", properties: { code: { type: "string" }, message: {
type: "string" }, details: { type: "object", additionalProperties: true } } })
and then replace the bare 409 response with a structured response that
references this shared schema (use content -> application/json -> schema ->
$ref: "#/components/schemas/Error"); apply the same pattern to other error
responses across the file to ensure all client-facing errors consistently
reference the new Error schema.
| "requestBody": { | ||
| "required": true, | ||
| "content": { | ||
| "application/json": { | ||
| "schema": { | ||
| "type": "object", | ||
| "properties": { | ||
| "slug": { | ||
| "type": "string", | ||
| "description": "The slug of the link to delete" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make slug required in delete request body (if keeping POST).
If you keep POST deletion, the body schema should require slug; otherwise empty bodies pass validation.
"schema": {
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The slug of the link to delete"
}
- }
+ },
+ "required": ["slug"]
}📝 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.
| "requestBody": { | |
| "required": true, | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "type": "object", | |
| "properties": { | |
| "slug": { | |
| "type": "string", | |
| "description": "The slug of the link to delete" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| "requestBody": { | |
| "required": true, | |
| "content": { | |
| "application/json": { | |
| "schema": { | |
| "type": "object", | |
| "properties": { | |
| "slug": { | |
| "type": "string", | |
| "description": "The slug of the link to delete" | |
| } | |
| }, | |
| "required": ["slug"] | |
| } | |
| } | |
| } | |
| }, |
🤖 Prompt for AI Agents
In swagger.json around lines 205 to 220, the requestBody schema for the POST
delete endpoint does not mark "slug" as required, allowing empty bodies to pass
validation; update the schema to include a "required": ["slug"] array at the
object level so that "slug" is mandatory, and ensure any server-side
validation/handlers expect and validate the slug field accordingly.
| "type": "array", | ||
| "items": { | ||
| "type": "object", | ||
| "description": "Event log entry" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add maxItems to all array responses to satisfy policy and bound payloads.
Arrays in events, locations, metrics, and views lack maxItems. Set to the effective page size (e.g., the provided limit or a hard ceiling).
Also applies to: 649-667, 966-979, 1136-1151
🤖 Prompt for AI Agents
In swagger.json around lines 505-510 (and similarly at 649-667, 966-979,
1136-1151), array response schemas for events, locations, metrics, and views are
missing maxItems; update each array schema to include a maxItems property set to
the effective page size (use the API's limit parameter when available or a
reasonable hard ceiling, e.g., 1000) so responses are bounded; ensure the
maxItems value is documented/consistent with any limit/query parameters and add
it alongside the existing "type" and "items" fields for each array schema.
Summary by CodeRabbit
New Features
Documentation