Skip to content

Commit 8c0bcb7

Browse files
committed
init pocketbase app
1 parent 3fbe25d commit 8c0bcb7

File tree

8 files changed

+366
-51
lines changed

8 files changed

+366
-51
lines changed

app/pb/handlers/api.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package handlers
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/labstack/echo/v5"
7+
"github.com/pocketbase/pocketbase"
8+
"github.com/pocketbase/pocketbase/apis"
9+
"github.com/pocketbase/pocketbase/core"
10+
"github.com/pocketbase/pocketbase/models"
11+
)
12+
13+
// RegisterAPIRoutes registers custom API routes
14+
func RegisterAPIRoutes(app *pocketbase.PocketBase) {
15+
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
16+
// Custom API endpoint for health check
17+
e.Router.GET("/api/health", func(c echo.Context) error {
18+
return c.JSON(http.StatusOK, map[string]string{
19+
"status": "healthy",
20+
})
21+
})
22+
23+
// Example of protected endpoint
24+
e.Router.GET("/api/protected", func(c echo.Context) error {
25+
// Require authenticated user
26+
user, _ := c.Get(apis.ContextAuthRecordKey).(*models.Record)
27+
if user == nil {
28+
return apis.NewForbiddenError("Only authenticated users can access this endpoint", nil)
29+
}
30+
31+
return c.JSON(http.StatusOK, map[string]string{
32+
"message": "You have access to protected data",
33+
})
34+
}, apis.RequireAdminOrRecordAuth())
35+
36+
return nil
37+
})
38+
}

app/pb/hooks/record_hooks.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package hooks
2+
3+
import (
4+
"github.com/pocketbase/pocketbase"
5+
"github.com/pocketbase/pocketbase/core"
6+
)
7+
8+
// RegisterRecordHooks registers all record-related hooks
9+
func RegisterRecordHooks(app *pocketbase.PocketBase) {
10+
// Before create hook example
11+
app.OnRecordBeforeCreateRequest().Add(func(e *core.RecordCreateEvent) error {
12+
// Add custom validation or data modification before creation
13+
if e.Collection.Name == "posts" {
14+
// Example: Set default status for new posts
15+
e.Record.Set("status", "draft")
16+
}
17+
return nil
18+
})
19+
20+
// After create hook example
21+
app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error {
22+
// Perform actions after record creation
23+
if e.Collection.Name == "users" {
24+
// Example: Send welcome email or create related records
25+
}
26+
return nil
27+
})
28+
29+
// Before update hook
30+
app.OnRecordBeforeUpdateRequest().Add(func(e *core.RecordUpdateEvent) error {
31+
// Add validation or modification before update
32+
return nil
33+
})
34+
35+
// After delete hook
36+
app.OnRecordAfterDeleteRequest().Add(func(e *core.RecordDeleteEvent) error {
37+
// Clean up related data after deletion
38+
return nil
39+
})
40+
}

app/pb/pbserver.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
package main
22

3-
//https://github.com/pocketbase/pocketbase.git
4-
//https://github.com/pedrozadotdev/pocketblocks
5-
63
import (
7-
"github.com/pocketbase/pocketbase/core"
84
"log"
95
"os"
106

11-
"github.com/fluent-qa/qgops/app/pb/webhooks"
7+
"github.com/fluent-qa/qgops/app/pb/handlers"
8+
"github.com/fluent-qa/qgops/app/pb/hooks"
129
"github.com/pocketbase/pocketbase"
1310
"github.com/pocketbase/pocketbase/apis"
11+
"github.com/pocketbase/pocketbase/core"
12+
"github.com/pocketbase/pocketbase/plugins/migratecmd"
1413
)
1514

1615
func main() {
1716
app := pocketbase.New()
18-
webhooks.AttachWebhooks(app)
17+
18+
// Register migrations
19+
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
20+
Dir: "./pb_migrations",
21+
Automigrate: true,
22+
TemplateLang: "",
23+
})
24+
25+
// Register custom API routes
26+
handlers.RegisterAPIRoutes(app)
27+
28+
// Register record hooks
29+
hooks.RegisterRecordHooks(app)
30+
1931
// serves static files from the provided public dir (if exists)
2032
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
2133
e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS("./public"), false))

app/pb/webhooks/retry.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package webhooks
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
)
8+
9+
// RetryConfig defines the configuration for webhook retries
10+
type RetryConfig struct {
11+
MaxRetries int
12+
InitialWait time.Duration
13+
MaxWait time.Duration
14+
}
15+
16+
// DefaultRetryConfig provides default retry configuration
17+
var DefaultRetryConfig = RetryConfig{
18+
MaxRetries: 3,
19+
InitialWait: 5 * time.Second,
20+
MaxWait: 1 * time.Minute,
21+
}
22+
23+
// RetryableWebhook wraps a webhook with retry capability
24+
type RetryableWebhook struct {
25+
webhook Webhook
26+
config RetryConfig
27+
}
28+
29+
// NewRetryableWebhook creates a new RetryableWebhook instance
30+
func NewRetryableWebhook(webhook Webhook, config RetryConfig) *RetryableWebhook {
31+
return &RetryableWebhook{
32+
webhook: webhook,
33+
config: config,
34+
}
35+
}
36+
37+
// Send attempts to send the webhook with retries
38+
func (r *RetryableWebhook) Send(ctx context.Context, payload []byte) error {
39+
var lastErr error
40+
wait := r.config.InitialWait
41+
42+
for attempt := 0; attempt <= r.config.MaxRetries; attempt++ {
43+
if attempt > 0 {
44+
select {
45+
case <-ctx.Done():
46+
return fmt.Errorf("context cancelled during retry: %w", ctx.Err())
47+
case <-time.After(wait):
48+
// Exponential backoff
49+
wait *= 2
50+
if wait > r.config.MaxWait {
51+
wait = r.config.MaxWait
52+
}
53+
}
54+
}
55+
56+
err := sendWebhook(ctx, r.webhook, payload)
57+
if err == nil {
58+
return nil
59+
}
60+
61+
lastErr = err
62+
// Log retry attempt
63+
fmt.Printf("Webhook delivery failed (attempt %d/%d): %v\n",
64+
attempt+1, r.config.MaxRetries+1, err)
65+
}
66+
67+
return fmt.Errorf("webhook delivery failed after %d attempts: %w",
68+
r.config.MaxRetries+1, lastErr)
69+
}

app/pb/webhooks/router.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package webhooks
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"sync"
7+
8+
"github.com/pocketbase/pocketbase"
9+
)
10+
11+
// WebhookRouter handles the routing of incoming webhooks to their appropriate handlers
12+
type WebhookRouter struct {
13+
app *pocketbase.PocketBase
14+
handlers map[string][]WebhookHandler
15+
mu sync.RWMutex
16+
}
17+
18+
// WebhookHandler defines the interface for webhook handlers
19+
type WebhookHandler interface {
20+
// Handle processes the webhook payload
21+
Handle(payload *WebhookPayload) error
22+
// ShouldHandle determines if this handler should process the webhook
23+
ShouldHandle(payload *WebhookPayload) bool
24+
}
25+
26+
// NewWebhookRouter creates a new WebhookRouter instance
27+
func NewWebhookRouter(app *pocketbase.PocketBase) *WebhookRouter {
28+
return &WebhookRouter{
29+
app: app,
30+
handlers: make(map[string][]WebhookHandler),
31+
}
32+
}
33+
34+
// RegisterHandler registers a new webhook handler for a specific collection
35+
func (r *WebhookRouter) RegisterHandler(collection string, handler WebhookHandler) {
36+
r.mu.Lock()
37+
defer r.mu.Unlock()
38+
39+
if r.handlers[collection] == nil {
40+
r.handlers[collection] = make([]WebhookHandler, 0)
41+
}
42+
r.handlers[collection] = append(r.handlers[collection], handler)
43+
}
44+
45+
// Route processes an incoming webhook and routes it to appropriate handlers
46+
func (r *WebhookRouter) Route(payload *WebhookPayload) error {
47+
r.mu.RLock()
48+
handlers := r.handlers[payload.Collection]
49+
r.mu.RUnlock()
50+
51+
var errors []error
52+
for _, handler := range handlers {
53+
if handler.ShouldHandle(payload) {
54+
if err := handler.Handle(payload); err != nil {
55+
errors = append(errors, fmt.Errorf("handler error: %w", err))
56+
}
57+
}
58+
}
59+
60+
if len(errors) > 0 {
61+
return fmt.Errorf("webhook routing errors: %v", errors)
62+
}
63+
return nil
64+
}
65+
66+
// Example handler for logging webhooks
67+
type LoggingWebhookHandler struct{}
68+
69+
func (h *LoggingWebhookHandler) Handle(payload *WebhookPayload) error {
70+
// Log the webhook payload
71+
jsonData, _ := json.MarshalIndent(payload, "", " ")
72+
fmt.Printf("Received webhook: %s\n", string(jsonData))
73+
return nil
74+
}
75+
76+
func (h *LoggingWebhookHandler) ShouldHandle(payload *WebhookPayload) bool {
77+
return true // Handle all webhooks
78+
}
79+
80+
// Example handler for specific collection changes
81+
type CollectionChangeHandler struct {
82+
collection string
83+
action string
84+
}
85+
86+
func NewCollectionChangeHandler(collection, action string) *CollectionChangeHandler {
87+
return &CollectionChangeHandler{
88+
collection: collection,
89+
action: action,
90+
}
91+
}
92+
93+
func (h *CollectionChangeHandler) Handle(payload *WebhookPayload) error {
94+
// Handle the specific collection change
95+
fmt.Printf("Handling %s action for collection %s\n", payload.Action, payload.Collection)
96+
return nil
97+
}
98+
99+
func (h *CollectionChangeHandler) ShouldHandle(payload *WebhookPayload) bool {
100+
return payload.Collection == h.collection && payload.Action == h.action
101+
}

0 commit comments

Comments
 (0)