feat: openapi swagger ui
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
name: Deploy API Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'docs/openapi.yaml'
|
||||
- '.github/workflows/deploy-docs.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Generate Swagger UI
|
||||
uses: Legion2/swagger-ui-action@v1
|
||||
with:
|
||||
output: swagger-ui
|
||||
spec-file: docs/openapi.yaml
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: swagger-ui
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
1. **Ping the API**
|
||||
|
||||
- Endpoint: `GET /api/ping`
|
||||
- Payload: None
|
||||
- Response: 200
|
||||
- Response Example
|
||||
```json
|
||||
{
|
||||
"data": "pong"
|
||||
}
|
||||
```
|
||||
2. **Redirect by Slug**
|
||||
|
||||
- Endpoint: `GET /:slug`
|
||||
- Payload: None
|
||||
- Response: 301
|
||||
|
||||
3. **List All Links**
|
||||
|
||||
- Endpoint: `GET /api/links`
|
||||
- Headers: `X-Api-Key`
|
||||
- Query Parameters:
|
||||
- `limit` (optional): Number of results per page (default: 100)
|
||||
- `cursor` (optional): Pagination cursor from previous response
|
||||
- Response: 200
|
||||
- Response Example
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
||||
"refer": "http://localhost:4000/3wP4BQ",
|
||||
"origin": "https://monocuco.donado.co"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"has_more": true,
|
||||
"next": "75e0a7f4-9c5e-1235-b546-eb9c5e40f7ac"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **List link by ID**
|
||||
- Endpoint: `GET /api/links/:id`
|
||||
- Headers: `X-Api-Key`
|
||||
- Payload: None
|
||||
- Note: This endpoint returns up to 100 of the most recent clicks. For complete click history, use the `/api/links/:id/clicks` endpoint with pagination.
|
||||
- Response: 200
|
||||
- Response Example
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
||||
"refer": "http://localhost:4000/3wP4BQ",
|
||||
"origin": "https://monocuco.donado.co",
|
||||
"clicks": [
|
||||
{
|
||||
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||
"country": "DE",
|
||||
"browser": "Firefox",
|
||||
"os": "Mac OS X",
|
||||
"referer": "Direct",
|
||||
"created_at": "2024-07-12T19:25:22Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. **List Clicks for a Link**
|
||||
- Endpoint: `GET /api/links/:id/clicks`
|
||||
- Headers: `X-Api-Key`
|
||||
- Query Parameters:
|
||||
- `limit` (optional): Number of results per page (default: 100)
|
||||
- `cursor` (optional): Pagination cursor from previous response
|
||||
- Response: 200
|
||||
- Response Example
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||
"country": "DE",
|
||||
"browser": "Firefox",
|
||||
"os": "Mac OS X",
|
||||
"referer": "Direct",
|
||||
"created_at": "2024-07-12T19:25:22Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"has_more": true,
|
||||
"next": "629e3301-47f8-389b-b24c-f1c561df9825"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. **Create new link**
|
||||
- Endpoint: `POST /api/links`
|
||||
- Payload:
|
||||
```json
|
||||
{
|
||||
"url": "https://example.com"
|
||||
}
|
||||
```
|
||||
- Headers: `X-Api-Key`
|
||||
- Response: 201
|
||||
- Response Example:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
||||
"refer": "http://localhost:4000/3wP4BQ",
|
||||
"origin": "https://example.com",
|
||||
"clicks": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
7. **Update an existing link by ID**
|
||||
- Endpoint: `PUT /api/links/:id`
|
||||
- Payload:
|
||||
```json
|
||||
{
|
||||
"url": "https://newexample.com"
|
||||
}
|
||||
```
|
||||
- Headers: `X-Api-Key`
|
||||
- Response: 200
|
||||
- Response Example:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
||||
"refer": "http://localhost:4000/3wP4BQ",
|
||||
"origin": "https://newexample.com",
|
||||
"clicks": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
8. **Delete a link by ID**
|
||||
- Endpoint: `DELETE /api/links/:id`
|
||||
- Payload: None
|
||||
- Headers: `X-Api-Key`
|
||||
- Response: 204
|
||||
@@ -0,0 +1,496 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Bit - URL Shortener API
|
||||
description: A high-performance URL shortener service with click tracking and analytics
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: API Support
|
||||
url: https://github.com/sjdonado/bit
|
||||
|
||||
servers:
|
||||
- url: http://localhost:4000
|
||||
description: Development server
|
||||
- url: http://localhost:4001
|
||||
description: Benchmark server
|
||||
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
|
||||
paths:
|
||||
/api/ping:
|
||||
get:
|
||||
summary: Ping the API
|
||||
description: Health check endpoint to verify the API is running
|
||||
operationId: ping
|
||||
tags:
|
||||
- Health
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: API is healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: string
|
||||
example: pong
|
||||
|
||||
/{slug}:
|
||||
get:
|
||||
summary: Redirect by slug
|
||||
description: Redirects to the original URL and tracks the click asynchronously
|
||||
operationId: redirectBySlug
|
||||
tags:
|
||||
- Redirects
|
||||
security: []
|
||||
parameters:
|
||||
- name: slug
|
||||
in: path
|
||||
required: true
|
||||
description: The short URL slug
|
||||
schema:
|
||||
type: string
|
||||
example: 3wP4BQ
|
||||
- name: utm_source
|
||||
in: query
|
||||
required: false
|
||||
description: UTM source parameter for tracking
|
||||
schema:
|
||||
type: string
|
||||
example: email_campaign
|
||||
responses:
|
||||
'301':
|
||||
description: Redirect to original URL
|
||||
headers:
|
||||
Location:
|
||||
description: The original URL
|
||||
schema:
|
||||
type: string
|
||||
example: https://example.com
|
||||
X-Forwarded-For:
|
||||
description: Client IP address
|
||||
schema:
|
||||
type: string
|
||||
User-Agent:
|
||||
description: User agent string
|
||||
schema:
|
||||
type: string
|
||||
'404':
|
||||
description: Link not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/api/links:
|
||||
get:
|
||||
summary: List all links
|
||||
description: Retrieve all links for the authenticated user with pagination support
|
||||
operationId: listLinks
|
||||
tags:
|
||||
- Links
|
||||
parameters:
|
||||
- name: limit
|
||||
in: query
|
||||
description: Number of results per page
|
||||
schema:
|
||||
type: integer
|
||||
default: 100
|
||||
minimum: 1
|
||||
maximum: 1000
|
||||
- name: cursor
|
||||
in: query
|
||||
description: Pagination cursor from previous response
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: List of links
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/LinkSummary'
|
||||
pagination:
|
||||
$ref: '#/components/schemas/Pagination'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
post:
|
||||
summary: Create new link
|
||||
description: Create a new shortened link
|
||||
operationId: createLink
|
||||
tags:
|
||||
- Links
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- url
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
description: The URL to shorten
|
||||
example: https://example.com
|
||||
responses:
|
||||
'201':
|
||||
description: Link created successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/Link'
|
||||
'400':
|
||||
description: Bad request - invalid URL or missing field
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
examples:
|
||||
missingField:
|
||||
value:
|
||||
error: "url: Required field"
|
||||
invalidUrl:
|
||||
value:
|
||||
errors:
|
||||
url:
|
||||
- is invalid
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/api/links/{id}:
|
||||
get:
|
||||
summary: Get link by ID
|
||||
description: Retrieve a specific link with up to 100 most recent clicks. For complete click history, use /api/links/{id}/clicks
|
||||
operationId: getLink
|
||||
tags:
|
||||
- Links
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Link ID
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: Link details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/Link'
|
||||
'404':
|
||||
description: Link not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
put:
|
||||
summary: Update link
|
||||
description: Update the URL of an existing link
|
||||
operationId: updateLink
|
||||
tags:
|
||||
- Links
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Link ID
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- url
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
description: The new URL
|
||||
example: https://newexample.com
|
||||
responses:
|
||||
'200':
|
||||
description: Link updated successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/Link'
|
||||
'400':
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'403':
|
||||
description: Forbidden - link belongs to another user
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'404':
|
||||
description: Link not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
delete:
|
||||
summary: Delete link
|
||||
description: Delete a link and all its associated clicks
|
||||
operationId: deleteLink
|
||||
tags:
|
||||
- Links
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Link ID
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'204':
|
||||
description: Link deleted successfully
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'403':
|
||||
description: Forbidden - link belongs to another user
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'404':
|
||||
description: Link not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/api/links/{id}/clicks:
|
||||
get:
|
||||
summary: List clicks for a link
|
||||
description: Retrieve all clicks for a specific link with pagination support
|
||||
operationId: listClicks
|
||||
tags:
|
||||
- Clicks
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Link ID
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
- name: limit
|
||||
in: query
|
||||
description: Number of results per page
|
||||
schema:
|
||||
type: integer
|
||||
default: 100
|
||||
minimum: 1
|
||||
maximum: 1000
|
||||
- name: cursor
|
||||
in: query
|
||||
description: Pagination cursor from previous response
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: List of clicks
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Click'
|
||||
pagination:
|
||||
$ref: '#/components/schemas/Pagination'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'404':
|
||||
description: Link not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
ApiKeyAuth:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-Api-Key
|
||||
description: API key for authentication
|
||||
|
||||
schemas:
|
||||
LinkSummary:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Unique link identifier
|
||||
example: 1
|
||||
refer:
|
||||
type: string
|
||||
format: uri
|
||||
description: The shortened URL
|
||||
example: http://localhost:4000/3wP4BQ
|
||||
origin:
|
||||
type: string
|
||||
format: uri
|
||||
description: The original URL
|
||||
example: https://monocuco.donado.co
|
||||
|
||||
Link:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/LinkSummary'
|
||||
- type: object
|
||||
properties:
|
||||
clicks:
|
||||
type: array
|
||||
description: Array of click records (up to 100 most recent)
|
||||
items:
|
||||
$ref: '#/components/schemas/Click'
|
||||
|
||||
Click:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Unique click identifier
|
||||
example: 1
|
||||
user_agent:
|
||||
type: string
|
||||
description: User agent string
|
||||
example: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0
|
||||
country:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Country code (ISO 3166-1 alpha-2)
|
||||
example: US
|
||||
browser:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Browser name
|
||||
example: Firefox
|
||||
os:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Operating system
|
||||
example: Mac OS X
|
||||
referer:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Referer domain or utm_source
|
||||
example: Direct
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Click timestamp
|
||||
example: 2024-07-12T19:25:22Z
|
||||
|
||||
Pagination:
|
||||
type: object
|
||||
properties:
|
||||
has_more:
|
||||
type: boolean
|
||||
description: Whether there are more results
|
||||
example: true
|
||||
next:
|
||||
type: integer
|
||||
format: int64
|
||||
nullable: true
|
||||
description: Cursor for next page (link/click ID)
|
||||
example: 12
|
||||
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: Error message
|
||||
example: Resource not found
|
||||
required:
|
||||
- error
|
||||
|
||||
ValidationErrors:
|
||||
type: object
|
||||
properties:
|
||||
errors:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Field-level validation errors
|
||||
example:
|
||||
url:
|
||||
- is invalid
|
||||
|
||||
tags:
|
||||
- name: Health
|
||||
description: Health check endpoints
|
||||
- name: Redirects
|
||||
description: URL redirection and click tracking
|
||||
- name: Links
|
||||
description: Link management operations
|
||||
- name: Clicks
|
||||
description: Click analytics and tracking
|
||||
Reference in New Issue
Block a user