feat(swagger): add Swagger UI for API documentation
* Create index.html for Swagger UI integration * Link to Swagger UI CSS and JS from CDN * Configure Swagger UI to load API specification from api.json
This commit is contained in:
@@ -131,65 +131,6 @@
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.endpoints {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 30px;
|
||||
text-align: left;
|
||||
animation: fadeInUp 0.8s ease 1s both;
|
||||
}
|
||||
|
||||
.endpoints h2 {
|
||||
color: #1a237e;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.endpoint-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.endpoint-group h3 {
|
||||
color: #1e88e5;
|
||||
font-size: 1em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
background: white;
|
||||
padding: 12px 15px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Monaco', 'Courier New', monospace;
|
||||
font-size: 0.85em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.endpoint-method {
|
||||
background: #1e88e5;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 0.85em;
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.endpoint-method.post {
|
||||
background: #4caf50;
|
||||
}
|
||||
|
||||
.endpoint-method.delete {
|
||||
background: #f44336;
|
||||
}
|
||||
|
||||
.endpoint-path {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
padding-top: 30px;
|
||||
@@ -265,11 +206,6 @@
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -311,258 +247,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoints">
|
||||
<h2>Available API Endpoints</h2>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>📊 Status & Health</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method">GET</span>
|
||||
<span class="endpoint-path">/health</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🔌 Webhooks</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method">GET</span>
|
||||
<span class="endpoint-path">/api/hooks</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/hooks/add</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method delete">DELETE</span>
|
||||
<span class="endpoint-path">/api/hooks/remove</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>👤 Accounts</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method">GET</span>
|
||||
<span class="endpoint-path">/api/accounts</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/accounts/add</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/accounts/update</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/accounts/disable</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/accounts/enable</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method delete">DELETE</span>
|
||||
<span class="endpoint-path">/api/accounts/remove</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>💬 Send Messages</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/image</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/video</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/document</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/audio</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/sticker</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/location</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/contacts</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/interactive</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/template</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/flow</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/reaction</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/messages/read</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>📄 Templates</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/templates</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/templates/upload</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/templates/delete</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🔄 Flows</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/create</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/get</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/upload</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/publish</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/deprecate</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/flows/delete</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>📞 Phone Numbers</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/phone-numbers</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/phone-numbers/request-code</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/phone-numbers/verify-code</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🏪 Catalog / Commerce</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/catalogs</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/catalogs/products</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/catalog</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/product</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/send/product-list</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🏢 Business Profile</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/business-profile</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/business-profile/update</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🗑️ Media Management</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/media/upload</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/media-delete</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>💾 Message Cache</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method">GET</span>
|
||||
<span class="endpoint-path">/api/cache</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method">GET</span>
|
||||
<span class="endpoint-path">/api/cache/stats</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/api/cache/replay</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint-group">
|
||||
<h3>🔔 WhatsApp Business API</h3>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method">GET</span>
|
||||
<span class="endpoint-path">/webhooks/whatsapp/{account_id}</span>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<span class="endpoint-method post">POST</span>
|
||||
<span class="endpoint-path">/webhooks/whatsapp/{account_id}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Need help? Check out the <a href="https://git.warky.dev/wdevs/whatshooked" target="_blank">documentation</a> · <a href="/privacy-policy">Privacy Policy</a> · <a href="/terms-of-service">Terms of Service</a></p>
|
||||
<p style="margin-top: 10px;">Made by Warky Devs</p>
|
||||
|
||||
@@ -390,7 +390,11 @@ func (m *Manager) sendToHook(ctx context.Context, hook config.Hook, payload inte
|
||||
}
|
||||
parsedURL.RawQuery = query.Encode()
|
||||
|
||||
req, err := http.NewRequestWithContext(hookCtx, hook.Method, parsedURL.String(), bytes.NewReader(data))
|
||||
method := hook.Method
|
||||
if method == "" {
|
||||
method = http.MethodPost
|
||||
}
|
||||
req, err := http.NewRequestWithContext(hookCtx, method, parsedURL.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
logging.Error("Failed to create request", "hook_id", hook.ID, "error", err)
|
||||
m.eventBus.Publish(events.HookFailedEvent(eventCtx, hook.ID, hook.Name, err))
|
||||
@@ -413,8 +417,14 @@ func (m *Manager) sendToHook(ctx context.Context, hook config.Hook, payload inte
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
logging.Warn("Hook returned non-success status", "hook_id", hook.ID, "status", resp.StatusCode)
|
||||
m.eventBus.Publish(events.HookFailedEvent(eventCtx, hook.ID, hook.Name, fmt.Errorf("status code %d", resp.StatusCode)))
|
||||
errBody, _ := io.ReadAll(resp.Body)
|
||||
logging.Warn("Hook returned non-success status",
|
||||
"hook_id", hook.ID,
|
||||
"status", resp.StatusCode,
|
||||
"status_text", resp.Status,
|
||||
"body", string(errBody),
|
||||
)
|
||||
m.eventBus.Publish(events.HookFailedEvent(eventCtx, hook.ID, hook.Name, fmt.Errorf("%s: %s", resp.Status, string(errBody))))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
1704
pkg/serverembed/dist/api.json
vendored
Normal file
1704
pkg/serverembed/dist/api.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
29
pkg/serverembed/dist/swagger/index.html
vendored
Normal file
29
pkg/serverembed/dist/swagger/index.html
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WhatsHooked API</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
||||
<script>
|
||||
SwaggerUIBundle({
|
||||
url: "../api.json",
|
||||
dom_id: "#swagger-ui",
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset,
|
||||
],
|
||||
layout: "BaseLayout",
|
||||
deepLinking: true,
|
||||
tryItOutEnabled: true,
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/utils"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/whatsapp"
|
||||
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
|
||||
"github.com/google/uuid"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
)
|
||||
|
||||
@@ -463,6 +464,15 @@ func (wh *WhatsHooked) StartAPIServer(ctx context.Context) error {
|
||||
// Mark database as ready for account/hook loading
|
||||
wh.dbReady = true
|
||||
|
||||
// Persist hook events to the event_log table
|
||||
eventLogRepo := storage.NewEventLogRepository(db)
|
||||
wh.eventBus.Subscribe(events.EventHookFailed, func(event events.Event) {
|
||||
wh.persistEventLog(eventLogRepo, event, false)
|
||||
})
|
||||
wh.eventBus.Subscribe(events.EventHookSuccess, func(event events.Event) {
|
||||
wh.persistEventLog(eventLogRepo, event, true)
|
||||
})
|
||||
|
||||
// Sync config.json hooks and accounts into the database so they are never silently ignored
|
||||
logging.Info("Syncing config to database")
|
||||
if err := wh.syncConfigToDatabase(ctx); err != nil {
|
||||
@@ -507,6 +517,33 @@ func (wh *WhatsHooked) StopAPIServer(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// persistEventLog writes a hook event to the public.event_log table.
|
||||
func (wh *WhatsHooked) persistEventLog(repo *storage.EventLogRepository, event events.Event, success bool) {
|
||||
hookID, _ := event.Data["hook_id"].(string)
|
||||
|
||||
errStr := ""
|
||||
if !success {
|
||||
errStr, _ = event.Data["error"].(string)
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(event.Data)
|
||||
|
||||
entry := models.ModelPublicEventLog{
|
||||
ID: resolvespec_common.NewSqlString(uuid.New().String()),
|
||||
EventType: resolvespec_common.NewSqlString(string(event.Type)),
|
||||
EntityType: resolvespec_common.NewSqlString("hook"),
|
||||
EntityID: resolvespec_common.NewSqlString(hookID),
|
||||
Data: resolvespec_common.NewSqlString(string(data)),
|
||||
Error: resolvespec_common.NewSqlString(errStr),
|
||||
Success: success,
|
||||
CreatedAt: resolvespec_common.NewSqlTimeStamp(event.Timestamp),
|
||||
}
|
||||
|
||||
if err := repo.Create(context.Background(), &entry); err != nil {
|
||||
logging.Error("Failed to persist event log entry", "event_type", event.Type, "hook_id", hookID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleHookResponse processes hook success events for two-way communication
|
||||
func (wh *WhatsHooked) handleHookResponse(event events.Event) {
|
||||
// Use event context for sending message
|
||||
|
||||
Reference in New Issue
Block a user