diff --git a/README.md b/README.md index 51a0eb2..f206c1a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,12 @@ The API starts on `http://localhost:3333`. On first run, it automatically fetche - **BCB**: Selic, CDI, IPCA, USD/BRL, EUR/BRL (last ~3 years) - **CVM**: Company registry + IPE filings (current + previous year) +## API Documentation + +Visit **`/docs`** for interactive Swagger UI with full endpoint documentation. + +The OpenAPI 3.0 spec is also available at `/docs/openapi.yaml`. + ## Endpoints | Endpoint | Description | diff --git a/docs b/docs new file mode 120000 index 0000000..52d502b --- /dev/null +++ b/docs @@ -0,0 +1 @@ +internal/api/docs \ No newline at end of file diff --git a/internal/api/docs/openapi.yaml b/internal/api/docs/openapi.yaml new file mode 100644 index 0000000..e672e2b --- /dev/null +++ b/internal/api/docs/openapi.yaml @@ -0,0 +1,706 @@ +openapi: "3.0.3" +info: + title: Sentinela API + description: Brazilian Financial Data API — Real-time market data from BCB and CVM + version: 0.2.0 + contact: + name: Sentinela + url: https://git.ophion.com.br/rainbow/sentinela-go + license: + name: MIT + +servers: + - url: / + description: Current server + +tags: + - name: Health + description: Health check + - name: Companies + description: CVM registered companies + - name: Filings + description: CVM company filings + - name: Market Data + description: BCB market rates and indicators + - name: Search + description: Global search across all entities + +paths: + /health: + get: + tags: [Health] + summary: Health check + description: Returns API health status + responses: + "200": + description: API is healthy + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: ok + version: + type: string + example: "0.2.0" + + /api/v1/companies: + get: + tags: [Companies] + summary: List companies + description: Returns a paginated list of CVM registered companies + parameters: + - name: limit + in: query + schema: + type: integer + default: 20 + description: Number of results to return + - name: offset + in: query + schema: + type: integer + default: 0 + description: Offset for pagination + - name: status + in: query + schema: + type: string + description: Filter by registration status + - name: sector + in: query + schema: + type: string + description: Filter by sector + responses: + "200": + description: List of companies + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Company" + total: + type: integer + example: 842 + limit: + type: integer + example: 20 + offset: + type: integer + example: 0 + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/companies/search: + get: + tags: [Companies] + summary: Search companies + description: Full-text search across company names and documents + parameters: + - name: q + in: query + required: true + schema: + type: string + description: Search query + example: Petrobras + responses: + "200": + description: Search results + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Company" + total: + type: integer + "400": + $ref: "#/components/responses/BadRequest" + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/companies/{id}: + get: + tags: [Companies] + summary: Get company by ID + description: Returns a single company by its CVM code + parameters: + - name: id + in: path + required: true + schema: + type: string + description: Company CVM code + responses: + "200": + description: Company details + content: + application/json: + schema: + $ref: "#/components/schemas/Company" + "404": + $ref: "#/components/responses/NotFound" + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/companies/{id}/filings: + get: + tags: [Companies] + summary: Get company filings + description: Returns all filings for a specific company + parameters: + - name: id + in: path + required: true + schema: + type: string + description: Company CVM code + responses: + "200": + description: List of filings for the company + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Filing" + total: + type: integer + "404": + $ref: "#/components/responses/NotFound" + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/filings: + get: + tags: [Filings] + summary: List filings + description: Returns a paginated list of CVM filings + parameters: + - name: limit + in: query + schema: + type: integer + default: 20 + - name: offset + in: query + schema: + type: integer + default: 0 + - name: category + in: query + schema: + type: string + description: Filter by filing category + - name: from + in: query + schema: + type: string + format: date + description: Start date (YYYY-MM-DD) + - name: to + in: query + schema: + type: string + format: date + description: End date (YYYY-MM-DD) + responses: + "200": + description: List of filings + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Filing" + total: + type: integer + limit: + type: integer + offset: + type: integer + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/filings/search: + get: + tags: [Filings] + summary: Search filings + description: Full-text search across filings + parameters: + - name: q + in: query + required: true + schema: + type: string + example: fato relevante + responses: + "200": + description: Search results + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Filing" + total: + type: integer + "400": + $ref: "#/components/responses/BadRequest" + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/filings/recent: + get: + tags: [Filings] + summary: Recent filings + description: Returns the most recent CVM filings + responses: + "200": + description: Recent filings + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Filing" + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/filings/{id}: + get: + tags: [Filings] + summary: Get filing by ID + description: Returns a single filing by its ID + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Filing details + content: + application/json: + schema: + $ref: "#/components/schemas/Filing" + "404": + $ref: "#/components/responses/NotFound" + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/market/selic: + get: + tags: [Market Data] + summary: Selic rate history + description: Returns historical Selic (Brazil's base interest rate) data from BCB + parameters: + - $ref: "#/components/parameters/from" + - $ref: "#/components/parameters/to" + - $ref: "#/components/parameters/limit" + responses: + "200": + description: Selic rate history + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Rate" + example: + data: + - date: "2025-01-29" + value: 15.0 + - date: "2025-01-28" + value: 15.0 + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/market/selic/current: + get: + tags: [Market Data] + summary: Current Selic rate + description: Returns the current Selic rate + responses: + "200": + description: Current Selic rate + content: + application/json: + schema: + $ref: "#/components/schemas/Rate" + example: + date: "2025-01-29" + value: 15.0 + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/market/cdi: + get: + tags: [Market Data] + summary: CDI rate history + description: Returns historical CDI (interbank deposit rate) data + parameters: + - $ref: "#/components/parameters/from" + - $ref: "#/components/parameters/to" + - $ref: "#/components/parameters/limit" + responses: + "200": + description: CDI rate history + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Rate" + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/market/cdi/current: + get: + tags: [Market Data] + summary: Current CDI rate + description: Returns the current CDI rate + responses: + "200": + description: Current CDI rate + content: + application/json: + schema: + $ref: "#/components/schemas/Rate" + example: + date: "2025-01-29" + value: 14.9 + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/market/ipca: + get: + tags: [Market Data] + summary: IPCA inflation history + description: Returns historical IPCA (Brazilian consumer price index) data + parameters: + - $ref: "#/components/parameters/from" + - $ref: "#/components/parameters/to" + - $ref: "#/components/parameters/limit" + responses: + "200": + description: IPCA history + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Rate" + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/market/ipca/current: + get: + tags: [Market Data] + summary: Current IPCA rate + description: Returns the latest IPCA monthly rate + responses: + "200": + description: Current IPCA rate + content: + application/json: + schema: + $ref: "#/components/schemas/Rate" + example: + date: "2025-01-01" + value: 0.52 + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/market/fx: + get: + tags: [Market Data] + summary: FX rate history + description: Returns historical foreign exchange rate data + parameters: + - name: pair + in: query + schema: + type: string + default: USD/BRL + description: Currency pair (e.g., USD/BRL, EUR/BRL) + - $ref: "#/components/parameters/from" + - $ref: "#/components/parameters/to" + - $ref: "#/components/parameters/limit" + responses: + "200": + description: FX rate history + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/FXRate" + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/market/fx/current: + get: + tags: [Market Data] + summary: Current FX rate + description: Returns the current foreign exchange rate + responses: + "200": + description: Current FX rate + content: + application/json: + schema: + $ref: "#/components/schemas/FXRate" + example: + date: "2025-01-29" + pair: USD/BRL + buy: 5.19 + sell: 5.20 + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/market/overview: + get: + tags: [Market Data] + summary: Market overview + description: Returns a snapshot of all key market indicators + responses: + "200": + description: Market overview + content: + application/json: + schema: + type: object + properties: + selic: + $ref: "#/components/schemas/Rate" + cdi: + $ref: "#/components/schemas/Rate" + ipca: + $ref: "#/components/schemas/Rate" + fx: + $ref: "#/components/schemas/FXRate" + example: + selic: + date: "2025-01-29" + value: 15.0 + cdi: + date: "2025-01-29" + value: 14.9 + ipca: + date: "2025-01-01" + value: 0.52 + fx: + date: "2025-01-29" + pair: USD/BRL + buy: 5.19 + sell: 5.20 + "429": + $ref: "#/components/responses/RateLimited" + + /api/v1/search: + get: + tags: [Search] + summary: Global search + description: Search across companies, filings, and market data + parameters: + - name: q + in: query + required: true + schema: + type: string + description: Search query + example: Petrobras + responses: + "200": + description: Search results grouped by type + content: + application/json: + schema: + type: object + properties: + companies: + type: array + items: + $ref: "#/components/schemas/Company" + filings: + type: array + items: + $ref: "#/components/schemas/Filing" + "400": + $ref: "#/components/responses/BadRequest" + "429": + $ref: "#/components/responses/RateLimited" + +components: + parameters: + from: + name: from + in: query + schema: + type: string + format: date + description: Start date (YYYY-MM-DD) + to: + name: to + in: query + schema: + type: string + format: date + description: End date (YYYY-MM-DD) + limit: + name: limit + in: query + schema: + type: integer + default: 20 + description: Number of results to return + + schemas: + Company: + type: object + properties: + id: + type: string + example: "9512" + name: + type: string + example: "PETRÓLEO BRASILEIRO S.A. - PETROBRAS" + cnpj: + type: string + example: "33.000.167/0001-01" + status: + type: string + example: "ATIVO" + sector: + type: string + example: "Petróleo, Gás e Biocombustíveis" + + Filing: + type: object + properties: + id: + type: string + example: "123456" + company_id: + type: string + example: "9512" + company_name: + type: string + example: "PETRÓLEO BRASILEIRO S.A. - PETROBRAS" + category: + type: string + example: "Fato Relevante" + date: + type: string + format: date-time + example: "2025-01-29T18:30:00Z" + description: + type: string + example: "Descoberta de novo campo no pré-sal" + + Rate: + type: object + properties: + date: + type: string + format: date + example: "2025-01-29" + value: + type: number + format: double + example: 15.0 + + FXRate: + type: object + properties: + date: + type: string + format: date + example: "2025-01-29" + pair: + type: string + example: "USD/BRL" + buy: + type: number + format: double + example: 5.19 + sell: + type: number + format: double + example: 5.20 + + Error: + type: object + properties: + error: + type: string + example: "invalid request" + message: + type: string + example: "missing required parameter: q" + + responses: + BadRequest: + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + error: bad_request + message: "missing required parameter: q" + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + error: not_found + message: "resource not found" + RateLimited: + description: Rate limit exceeded + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + error: rate_limited + message: "too many requests" diff --git a/internal/api/server.go b/internal/api/server.go index ba3b1d0..671d550 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -19,6 +19,8 @@ func NewServer(cfg *config.Config, database *db.DB) *fiber.App { app.Use(recover.New()) app.Use(logger.New()) app.Use(cors.New()) + RegisterSwagger(app) + app.Use(middleware.NewRateLimiter(cfg.RateLimit)) if cfg.APIKey != "" { diff --git a/internal/api/swagger.go b/internal/api/swagger.go new file mode 100644 index 0000000..b903286 --- /dev/null +++ b/internal/api/swagger.go @@ -0,0 +1,46 @@ +package api + +import ( + _ "embed" + + "github.com/gofiber/fiber/v2" +) + +//go:embed docs/openapi.yaml +var openapiSpec []byte + +func RegisterSwagger(app *fiber.App) { + app.Get("/docs/openapi.yaml", func(c *fiber.Ctx) error { + c.Set("Content-Type", "application/yaml") + return c.Send(openapiSpec) + }) + + app.Get("/docs", func(c *fiber.Ctx) error { + c.Set("Content-Type", "text/html") + return c.SendString(swaggerHTML) + }) +} + +const swaggerHTML = ` + +
+