Skip to content

Conversation

@tbdavid2019
Copy link

@tbdavid2019 tbdavid2019 commented Sep 11, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a public API for URL shortening and analytics, including location, site verification, link management (list/create/edit/delete/query/search/upsert/AI slug), logs, and stats (counters, metrics, time-series views).
  • Documentation

    • Added an OpenAPI 3.0 specification for the API, enabling easy discovery and testing, with detailed operations and schemas for Link and Query.

@coderabbitai
Copy link

coderabbitai bot commented Sep 11, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
OpenAPI specification
swagger.json
New OpenAPI 3.0 spec documenting public endpoints: /api/location, /api/verify, /api/link/* (list, create, edit, delete, query, search, upsert, ai), /api/logs/* (events, locations), /api/stats/* (counters, metrics, views). Adds components.schemas: Link, Query with validations and response payloads.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: 新增 Swagger API 文件,定義 URL 縮短服務的各項 API 端點" clearly and concisely describes the primary change—adding a Swagger/OpenAPI specification that defines the URL-shortening service endpoints—and directly matches the PR contents, making it easy for teammates to understand the main purpose at a glance.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I twitch my ears at specs so fine,
New paths mapped out in tidy line.
Short links hop, stats burrow deep,
Logs like carrots, fresh to keep.
With schemas snug as burrow walls—
I thump approval in the halls! 🥕🐇

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.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

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
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between cf21a2d and 0ff42f6.

📒 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)

Comment on lines +1 to +12
{
"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"
}
],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment on lines +76 to +85
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"default": 20,
"maximum": 1024
},
"description": "Number of links per page"
},
{
Copy link

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

Comment on lines +136 to +159
"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"
}
}
Copy link

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.

Suggested change
"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.

Comment on lines +205 to +220
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The slug of the link to delete"
}
}
}
}
}
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
"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.

Comment on lines +505 to +510
"type": "array",
"items": {
"type": "object",
"description": "Event log entry"
}
}
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant