initial commit
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/restxlsx/restxlsx/internal/excel"
|
||||
)
|
||||
|
||||
//go:embed swagger-ui.html
|
||||
var swaggerHTML string
|
||||
|
||||
type OpenAPI struct {
|
||||
engine *excel.Engine
|
||||
debug bool
|
||||
}
|
||||
|
||||
func NewOpenAPI(engine *excel.Engine, debug bool) *OpenAPI {
|
||||
return &OpenAPI{engine: engine, debug: debug}
|
||||
}
|
||||
|
||||
func (o *OpenAPI) Spec() map[string]any {
|
||||
spec := map[string]any{
|
||||
"openapi": "3.0.3",
|
||||
"info": map[string]any{
|
||||
"title": "restxlsx",
|
||||
"description": "RESTful API over Excel tables. Supports REST CRUD, GraphQL, SQL, and DDL.",
|
||||
"version": "0.2.0",
|
||||
},
|
||||
"paths": o.buildPaths(),
|
||||
}
|
||||
|
||||
if o.debug {
|
||||
spec["servers"] = []map[string]any{
|
||||
{"url": "http://localhost:3000", "description": "Local dev"},
|
||||
}
|
||||
}
|
||||
|
||||
return spec
|
||||
}
|
||||
|
||||
func (o *OpenAPI) buildPaths() map[string]any {
|
||||
paths := make(map[string]any)
|
||||
|
||||
paths["/api/tables"] = map[string]any{
|
||||
"get": map[string]any{
|
||||
"summary": "List all tables",
|
||||
"operationId": "listTables",
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{
|
||||
"description": "List of table names",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"tables": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, name := range o.engine.ListTables() {
|
||||
t := o.engine.GetTable(name)
|
||||
tablePath := fmt.Sprintf("/api/%s", name)
|
||||
itemPath := fmt.Sprintf("/api/%s/{id}", name)
|
||||
|
||||
schema := tableSchema(t)
|
||||
|
||||
paths[tablePath] = map[string]any{
|
||||
"get": map[string]any{
|
||||
"summary": fmt.Sprintf("List all %s", name),
|
||||
"operationId": fmt.Sprintf("list%s", name),
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{
|
||||
"description": fmt.Sprintf("Array of %s", name),
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": map[string]any{
|
||||
"type": "array",
|
||||
"items": schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"post": map[string]any{
|
||||
"summary": fmt.Sprintf("Create a new %s", name),
|
||||
"operationId": fmt.Sprintf("create%s", name),
|
||||
"requestBody": map[string]any{
|
||||
"required": true,
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"201": map[string]any{
|
||||
"description": "Created",
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
paths[itemPath] = map[string]any{
|
||||
"get": map[string]any{
|
||||
"summary": fmt.Sprintf("Get %s by ID", name),
|
||||
"operationId": fmt.Sprintf("get%s", name),
|
||||
"parameters": []map[string]any{
|
||||
{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{
|
||||
"description": fmt.Sprintf("A single %s", name),
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
"404": map[string]any{"description": "Not found"},
|
||||
},
|
||||
},
|
||||
"put": map[string]any{
|
||||
"summary": fmt.Sprintf("Update %s by ID", name),
|
||||
"operationId": fmt.Sprintf("update%s", name),
|
||||
"parameters": []map[string]any{
|
||||
{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
|
||||
},
|
||||
"requestBody": map[string]any{
|
||||
"required": true,
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{"description": "Updated"},
|
||||
"404": map[string]any{"description": "Not found"},
|
||||
},
|
||||
},
|
||||
"delete": map[string]any{
|
||||
"summary": fmt.Sprintf("Delete %s by ID", name),
|
||||
"operationId": fmt.Sprintf("delete%s", name),
|
||||
"parameters": []map[string]any{
|
||||
{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"204": map[string]any{"description": "Deleted"},
|
||||
"404": map[string]any{"description": "Not found"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
paths["/api/sql"] = map[string]any{
|
||||
"post": map[string]any{
|
||||
"summary": "Execute a SQL query",
|
||||
"operationId": "execSQL",
|
||||
"requestBody": map[string]any{
|
||||
"required": true,
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"query": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{"description": "Query result"},
|
||||
"400": map[string]any{"description": "Bad request"},
|
||||
"403": map[string]any{"description": "Forbidden"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
paths["/api/ddl/sheets"] = map[string]any{
|
||||
"post": map[string]any{
|
||||
"summary": "Create a new sheet",
|
||||
"operationId": "createSheet",
|
||||
"requestBody": map[string]any{
|
||||
"required": true,
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"name": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"201": map[string]any{"description": "Created"},
|
||||
"403": map[string]any{"description": "Forbidden"},
|
||||
},
|
||||
},
|
||||
}
|
||||
paths["/api/ddl/sheets/{sheet}"] = map[string]any{
|
||||
"delete": map[string]any{
|
||||
"summary": "Delete a sheet",
|
||||
"operationId": "deleteSheet",
|
||||
"parameters": []map[string]any{
|
||||
{"name": "sheet", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{"description": "Deleted"},
|
||||
"403": map[string]any{"description": "Forbidden"},
|
||||
"404": map[string]any{"description": "Not found"},
|
||||
},
|
||||
},
|
||||
}
|
||||
paths["/api/ddl/tables"] = map[string]any{
|
||||
"post": map[string]any{
|
||||
"summary": "Create a table on a sheet",
|
||||
"operationId": "createTable",
|
||||
"requestBody": map[string]any{
|
||||
"required": true,
|
||||
"content": map[string]any{
|
||||
"application/json": map[string]any{
|
||||
"schema": map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"sheet": map[string]any{"type": "string"},
|
||||
"name": map[string]any{"type": "string"},
|
||||
"columns": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"201": map[string]any{"description": "Created"},
|
||||
"403": map[string]any{"description": "Forbidden"},
|
||||
},
|
||||
},
|
||||
}
|
||||
paths["/api/ddl/tables/{table}"] = map[string]any{
|
||||
"delete": map[string]any{
|
||||
"summary": "Drop a table",
|
||||
"operationId": "deleteTable",
|
||||
"parameters": []map[string]any{
|
||||
{"name": "table", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{"description": "Deleted"},
|
||||
"403": map[string]any{"description": "Forbidden"},
|
||||
"404": map[string]any{"description": "Not found"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
func tableSchema(t *excel.Table) map[string]any {
|
||||
props := make(map[string]any)
|
||||
required := make([]string, 0)
|
||||
|
||||
for _, col := range t.Columns {
|
||||
props[col] = map[string]any{"type": "string"}
|
||||
required = append(required, col)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"type": "object",
|
||||
"properties": props,
|
||||
"required": required,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OpenAPI) Handler(w http.ResponseWriter, r *http.Request) {
|
||||
spec := o.Spec()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
enc.Encode(spec)
|
||||
}
|
||||
|
||||
func (o *OpenAPI) SwaggerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
specBytes, _ := json.Marshal(o.Spec())
|
||||
specStr := strings.ReplaceAll(string(specBytes), `"`, `\"`)
|
||||
html := strings.Replace(swaggerHTML, "{{SPEC}}", specStr, 1)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(html))
|
||||
}
|
||||
Reference in New Issue
Block a user