Initial commit
This commit is contained in:
787
skills/pocketbase/references/go/go_overview.md
Normal file
787
skills/pocketbase/references/go/go_overview.md
Normal file
@@ -0,0 +1,787 @@
|
||||
# Go Overview - PocketBase
|
||||
|
||||
## Overview
|
||||
|
||||
PocketBase can be extended using Go, allowing you to:
|
||||
- Add custom API endpoints
|
||||
- Implement event hooks
|
||||
- Create custom database migrations
|
||||
- Build scheduled jobs
|
||||
- Add custom middleware
|
||||
- Integrate external services
|
||||
- Extend authentication
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
myapp/
|
||||
├── go.mod
|
||||
├── pocketbase.go
|
||||
├── migrations/
|
||||
│ └── 1703123456_initial.go
|
||||
├── hooks/
|
||||
│ └── hooks.go
|
||||
└── main.go
|
||||
```
|
||||
|
||||
## Creating a Go Extension
|
||||
|
||||
### 1. Initialize Go Module
|
||||
|
||||
```bash
|
||||
go mod init myapp
|
||||
```
|
||||
|
||||
### 2. Install PocketBase SDK
|
||||
|
||||
```bash
|
||||
go get github.com/pocketbase/pocketbase@latest
|
||||
```
|
||||
|
||||
### 3. Basic PocketBase App
|
||||
|
||||
Create `main.go`:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
|
||||
// Add custom API endpoint
|
||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
||||
// Custom routes
|
||||
se.Router.GET("/api/hello", func(e *core.RequestEvent) error {
|
||||
return e.JSON(200, map[string]string{
|
||||
"message": "Hello from Go!",
|
||||
})
|
||||
})
|
||||
|
||||
return se.Next()
|
||||
})
|
||||
|
||||
// Start the app
|
||||
if err := app.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Run the Application
|
||||
|
||||
```bash
|
||||
go run main.go pocketbase.go serve --http=0.0.0.0:8090
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Event Hooks
|
||||
|
||||
Execute code on specific events:
|
||||
|
||||
```go
|
||||
// On record create
|
||||
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordCreateEvent) error {
|
||||
log.Println("Post created:", e.Record.GetString("title"))
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
// On record update
|
||||
app.OnRecordUpdate("posts").BindFunc(func(e *core.RecordUpdateEvent) error {
|
||||
log.Println("Post updated:", e.Record.GetString("title"))
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
// On record delete
|
||||
app.OnRecordDelete("posts").BindFunc(func(e *core.RecordDeleteEvent) error {
|
||||
log.Println("Post deleted:", e.Record.GetString("title"))
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
// On authentication
|
||||
app.OnRecordAuth().BindFunc(func(e *core.RecordAuthEvent) error {
|
||||
log.Println("User authenticated:", e.Record.GetString("email"))
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### Event Arguments
|
||||
|
||||
PocketBase exposes a different event struct for each hook (see the
|
||||
[official event hooks reference](https://pocketbase.io/docs/go-event-hooks/)).
|
||||
Common fields you will interact with include:
|
||||
|
||||
- `e.App` – the running PocketBase instance (database access, configuration, cron, etc.).
|
||||
- `e.Record` – the record being created, updated, deleted, or authenticated.
|
||||
- `e.RecordOriginal` – the previous value during update hooks.
|
||||
- `e.Next()` – call to continue the handler chain after your logic.
|
||||
|
||||
Refer to the linked docs for the complete list of fields exposed by each event type.
|
||||
|
||||
## Custom API Endpoints
|
||||
|
||||
### Create GET Endpoint
|
||||
|
||||
```go
|
||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
||||
se.Router.GET("/api/stats", func(e *core.RequestEvent) error {
|
||||
totalPosts, err := e.App.CountRecords("posts")
|
||||
if err != nil {
|
||||
return e.InternalServerError("failed to count posts", err)
|
||||
}
|
||||
|
||||
totalUsers, err := e.App.CountRecords("users")
|
||||
if err != nil {
|
||||
return e.InternalServerError("failed to count users", err)
|
||||
}
|
||||
|
||||
return e.JSON(200, map[string]any{
|
||||
"total_posts": totalPosts,
|
||||
"total_users": totalUsers,
|
||||
"authenticated": e.Auth != nil,
|
||||
})
|
||||
})
|
||||
|
||||
return se.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### Create POST Endpoint
|
||||
|
||||
```go
|
||||
import "github.com/pocketbase/dbx"
|
||||
|
||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
||||
se.Router.POST("/api/search", func(e *core.RequestEvent) error {
|
||||
// Parse request body
|
||||
var req struct {
|
||||
Query string `json:"query"`
|
||||
}
|
||||
if err := e.BindBody(&req); err != nil {
|
||||
return e.BadRequestError("invalid body", err)
|
||||
}
|
||||
|
||||
// Search posts with a safe filter
|
||||
records, err := e.App.FindRecordsByFilter(
|
||||
"posts",
|
||||
"title ~ {:query}",
|
||||
"-created",
|
||||
50,
|
||||
0,
|
||||
dbx.Params{"query": req.Query},
|
||||
)
|
||||
if err != nil {
|
||||
return e.InternalServerError("Search failed", err)
|
||||
}
|
||||
|
||||
return e.JSON(200, map[string]any{
|
||||
"results": records,
|
||||
"count": len(records),
|
||||
})
|
||||
})
|
||||
|
||||
return se.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### Custom Middleware
|
||||
|
||||
```go
|
||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
||||
se.Router.BindFunc(func(e *core.RequestEvent) error {
|
||||
// Add CORS headers
|
||||
e.Response.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
e.Response.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
|
||||
e.Response.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
|
||||
if e.Request.Method == http.MethodOptions {
|
||||
return e.NoContent(http.StatusOK)
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
return se.Next()
|
||||
})
|
||||
```
|
||||
|
||||
## Database Operations
|
||||
|
||||
### Find Records
|
||||
|
||||
```go
|
||||
import "github.com/pocketbase/dbx"
|
||||
|
||||
// Find single record
|
||||
record, err := app.FindRecordById("posts", "RECORD_ID")
|
||||
|
||||
// Find multiple records with a filter and pagination
|
||||
records, err := app.FindRecordsByFilter(
|
||||
"posts",
|
||||
"status = {:status}",
|
||||
"-created",
|
||||
50,
|
||||
0,
|
||||
dbx.Params{"status": "published"},
|
||||
)
|
||||
|
||||
// Custom query with the record query builder
|
||||
records := []*core.Record{}
|
||||
err := app.RecordQuery("posts").
|
||||
AndWhere(dbx.Like("title", "pocketbase")).
|
||||
OrderBy("created DESC").
|
||||
Limit(50).
|
||||
All(&records)
|
||||
|
||||
// Find with relations
|
||||
record, err = app.FindRecordById("posts", "id")
|
||||
if err == nil {
|
||||
if errs := app.ExpandRecord(record, []string{"author", "comments"}, nil); len(errs) > 0 {
|
||||
// handle expand error(s)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Create Records
|
||||
|
||||
```go
|
||||
collection, err := app.FindCollectionByNameOrId("posts")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := core.NewRecord(collection)
|
||||
record.Set("title", "My Post")
|
||||
record.Set("content", "Post content")
|
||||
record.Set("author", "USER_ID")
|
||||
|
||||
if err := app.Save(record); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
### Update Records
|
||||
|
||||
```go
|
||||
record, err := app.FindRecordById("posts", "id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record.Set("title", "Updated Title")
|
||||
record.Set("content", "Updated content")
|
||||
|
||||
if err := app.Save(record); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Records
|
||||
|
||||
```go
|
||||
record, err := app.FindRecordById("posts", "id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := app.Delete(record); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
### Query Builder
|
||||
|
||||
```go
|
||||
records := []*core.Record{}
|
||||
err := app.RecordQuery("posts").
|
||||
AndWhere(dbx.HashExp{"status": "published"}).
|
||||
AndWhere(dbx.NewExp("created >= {:date}", dbx.Params{"date": "2024-01-01"})).
|
||||
OrderBy("created DESC").
|
||||
Offset(0).
|
||||
Limit(50).
|
||||
All(&records)
|
||||
|
||||
// Combine conditions with OR
|
||||
records = []*core.Record{}
|
||||
err = app.RecordQuery("posts").
|
||||
AndWhere(dbx.Or(
|
||||
dbx.HashExp{"status": "published"},
|
||||
dbx.HashExp{"author": userId},
|
||||
)).
|
||||
All(&records)
|
||||
```
|
||||
|
||||
## Event Hooks Examples
|
||||
|
||||
### Auto-populate Fields
|
||||
|
||||
```go
|
||||
// Auto-set author on post create
|
||||
app.OnRecordCreateRequest("posts").BindFunc(func(e *core.RecordRequestEvent) error {
|
||||
if e.Auth != nil {
|
||||
e.Record.Set("author", e.Auth.Id)
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
// Auto-set slug from title
|
||||
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordCreateEvent) error {
|
||||
title := e.Record.GetString("title")
|
||||
slug := strings.ToLower(strings.ReplaceAll(title, " ", "-"))
|
||||
e.Record.Set("slug", slug)
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
```go
|
||||
// Custom validation
|
||||
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordCreateEvent) error {
|
||||
title := e.Record.GetString("title")
|
||||
if len(title) < 5 {
|
||||
return errors.New("title must be at least 5 characters")
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
// Check permissions
|
||||
app.OnRecordCreateRequest("posts").BindFunc(func(e *core.RecordRequestEvent) error {
|
||||
if e.Auth == nil {
|
||||
return e.ForbiddenError("authentication required", nil)
|
||||
}
|
||||
|
||||
role := e.Auth.GetString("role")
|
||||
if role != "admin" && role != "author" {
|
||||
return e.ForbiddenError("insufficient permissions", nil)
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### Cascading Updates
|
||||
|
||||
```go
|
||||
// When post is updated, update related comments
|
||||
app.OnRecordUpdate("posts").BindFunc(func(e *core.RecordUpdateEvent) error {
|
||||
// Check if status changed
|
||||
oldStatus := e.RecordOriginal.GetString("status")
|
||||
newStatus := e.Record.GetString("status")
|
||||
|
||||
if oldStatus != newStatus && newStatus == "published" {
|
||||
comments, err := e.App.FindRecordsByFilter(
|
||||
"comments",
|
||||
"post = {:postId}",
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
dbx.Params{"postId": e.Record.Id},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
comment.Set("status", "approved")
|
||||
if err := e.App.Save(comment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### Send Notifications
|
||||
|
||||
```go
|
||||
// Send email when post is published
|
||||
app.OnRecordUpdate("posts").BindFunc(func(e *core.RecordUpdateEvent) error {
|
||||
oldStatus := e.RecordOriginal.GetString("status")
|
||||
newStatus := e.Record.GetString("status")
|
||||
|
||||
if oldStatus != "published" && newStatus == "published" {
|
||||
author, err := e.App.FindRecordById("users", e.Record.GetString("author"))
|
||||
if err == nil {
|
||||
log.Println("Sending notification to:", author.GetString("email"))
|
||||
}
|
||||
}
|
||||
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### Log Activities
|
||||
|
||||
```go
|
||||
// Log all record changes using the builtin logger
|
||||
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordCreateEvent) error {
|
||||
e.App.Logger().Info("post created", "recordId", e.Record.Id)
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
app.OnRecordUpdate("posts").BindFunc(func(e *core.RecordUpdateEvent) error {
|
||||
e.App.Logger().Info(
|
||||
"post updated",
|
||||
"recordId", e.Record.Id,
|
||||
"statusFrom", e.RecordOriginal.GetString("status"),
|
||||
"statusTo", e.Record.GetString("status"),
|
||||
)
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
app.OnRecordDelete("posts").BindFunc(func(e *core.RecordDeleteEvent) error {
|
||||
e.App.Logger().Info("post deleted", "recordId", e.Record.Id)
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
## Scheduled Jobs
|
||||
|
||||
### Create Background Job
|
||||
|
||||
```go
|
||||
// Register job
|
||||
app.Cron().MustAdd("daily-backup", "0 2 * * *", func() {
|
||||
log.Println("Running daily backup...")
|
||||
|
||||
backupDir := "./backups"
|
||||
if err := os.MkdirAll(backupDir, 0o755); err != nil {
|
||||
log.Println("Backup failed:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Your backup logic here
|
||||
log.Println("Backup completed")
|
||||
})
|
||||
|
||||
// Or add a job during serve
|
||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
||||
se.App.Cron().MustAdd("cleanup", "@every 5m", func() {
|
||||
log.Println("Running cleanup task...")
|
||||
// Cleanup logic
|
||||
})
|
||||
|
||||
return se.Next()
|
||||
})
|
||||
```
|
||||
|
||||
## File Handling
|
||||
|
||||
### Custom File Upload
|
||||
|
||||
```go
|
||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
||||
se.Router.POST("/api/upload", func(e *core.RequestEvent) error {
|
||||
files, err := e.FindUploadedFiles("file")
|
||||
if err != nil {
|
||||
return e.BadRequestError("no file uploaded", err)
|
||||
}
|
||||
|
||||
collection, err := e.App.FindCollectionByNameOrId("uploads")
|
||||
if err != nil {
|
||||
return e.NotFoundError("uploads collection not found", err)
|
||||
}
|
||||
|
||||
record := core.NewRecord(collection)
|
||||
// Attach the uploaded file(s) to a file field
|
||||
record.Set("document", files)
|
||||
|
||||
if err := e.App.Save(record, files...); err != nil {
|
||||
return e.InternalServerError("failed to save file", err)
|
||||
}
|
||||
|
||||
return e.JSON(http.StatusOK, record)
|
||||
})
|
||||
|
||||
return se.Next()
|
||||
})
|
||||
```
|
||||
|
||||
## Custom Auth Provider
|
||||
|
||||
```go
|
||||
// Custom OAuth provider
|
||||
app.OnRecordAuthWithOAuth2().BindFunc(func(e *core.RecordAuthWithOAuth2Event) error {
|
||||
if e.Provider != "custom" {
|
||||
return e.Next()
|
||||
}
|
||||
|
||||
// Fetch user info from custom provider
|
||||
userInfo, err := fetchCustomUserInfo(e.OAuth2UserData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find or create user
|
||||
user, err := e.App.FindAuthRecordByData("users", "email", userInfo.Email)
|
||||
if err != nil {
|
||||
collection, err := e.App.FindCollectionByNameOrId("users")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user = core.NewRecord(collection)
|
||||
user.Set("email", userInfo.Email)
|
||||
user.Set("password", "") // OAuth users don't need password
|
||||
user.Set("emailVisibility", false)
|
||||
user.Set("verified", true)
|
||||
user.Set("name", userInfo.Name)
|
||||
if err := e.App.Save(user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
e.Record = user
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
)
|
||||
|
||||
func TestCustomEndpoint(t *testing.T) {
|
||||
app := pocketbase.NewWithConfig(config{})
|
||||
|
||||
// Add test endpoint
|
||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
||||
se.Router.GET("/api/test", func(e *core.RequestEvent) error {
|
||||
return e.JSON(200, map[string]string{
|
||||
"status": "ok",
|
||||
})
|
||||
})
|
||||
return se.Next()
|
||||
})
|
||||
|
||||
e := tests.NewRequestEvent(app, nil)
|
||||
// Test endpoint
|
||||
e.GET("/api/test").Expect(t).Status(200).JSON().Equal(map[string]interface{}{
|
||||
"status": "ok",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```go
|
||||
func TestRecordCreation(t *testing.T) {
|
||||
app := pocketbase.New()
|
||||
app.MustSeed()
|
||||
|
||||
client := tests.NewClient(app)
|
||||
|
||||
// Test authenticated request
|
||||
auth := client.AuthRecord("users", "test@example.com", "password")
|
||||
post := client.CreateRecord("posts", map[string]interface{}{
|
||||
"title": "Test Post",
|
||||
"content": "Test content",
|
||||
}, auth.Token)
|
||||
|
||||
if post.GetString("title") != "Test Post" {
|
||||
t.Errorf("Expected title 'Test Post', got %s", post.GetString("title"))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Build and Run
|
||||
|
||||
```bash
|
||||
# Build
|
||||
go build -o myapp main.go
|
||||
|
||||
# Run
|
||||
./myapp serve --http=0.0.0.0:8090
|
||||
```
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
```dockerfile
|
||||
FROM golang:1.21-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN go mod download
|
||||
RUN go build -o myapp main.go
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /root/
|
||||
|
||||
COPY --from=builder /app/myapp .
|
||||
COPY --from=builder /app/pocketbase ./
|
||||
|
||||
CMD ["./myapp", "serve", "--http=0.0.0.0:8090"]
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Error Handling
|
||||
|
||||
```go
|
||||
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordCreateEvent) error {
|
||||
if err := validatePost(e.Record); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
func validatePost(record *core.Record) error {
|
||||
title := record.GetString("title")
|
||||
if len(title) == 0 {
|
||||
return errors.New("title is required")
|
||||
}
|
||||
if len(title) > 200 {
|
||||
return errors.New("title too long")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Logging
|
||||
|
||||
```go
|
||||
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordCreateEvent) error {
|
||||
e.App.Logger().Info("Post created",
|
||||
"id", e.Record.Id,
|
||||
"title", e.Record.GetString("title"),
|
||||
)
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### 3. Security
|
||||
|
||||
```go
|
||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
||||
limiter := rate.NewLimiter(10, 20) // 10 req/sec, burst 20
|
||||
|
||||
se.Router.BindFunc(func(e *core.RequestEvent) error {
|
||||
if !limiter.Allow() {
|
||||
return e.TooManyRequestsError("rate limit exceeded", nil)
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
return se.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### 4. Configuration
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
ExternalAPIKey string
|
||||
EmailFrom string
|
||||
}
|
||||
|
||||
func (c Config) Name() string {
|
||||
return "myapp"
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := pocketbase.NewWithConfig(Config{
|
||||
ExternalAPIKey: os.Getenv("API_KEY"),
|
||||
EmailFrom: "noreply@example.com",
|
||||
})
|
||||
|
||||
// Use config in hooks
|
||||
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordCreateEvent) error {
|
||||
cfg := e.App.Config().(*Config)
|
||||
// Use cfg.ExternalAPIKey
|
||||
return e.Next()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### 1. Soft Delete
|
||||
|
||||
```go
|
||||
app.OnRecordDelete("posts").BindFunc(func(e *core.RecordDeleteEvent) error {
|
||||
// Instead of deleting, mark as deleted
|
||||
e.Record.Set("status", "deleted")
|
||||
e.Record.Set("deleted_at", time.Now())
|
||||
if err := e.App.Save(e.Record); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### 2. Audit Trail
|
||||
|
||||
```go
|
||||
app.OnRecordCreateRequest("").BindFunc(func(e *core.RecordRequestEvent) error {
|
||||
if e.Collection.Name == "posts" || e.Collection.Name == "comments" {
|
||||
if e.Auth != nil {
|
||||
e.Record.Set("created_by", e.Auth.Id)
|
||||
}
|
||||
|
||||
if info, err := e.RequestInfo(); err == nil {
|
||||
e.Record.Set("created_ip", info.RealIP)
|
||||
}
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
app.OnRecordUpdateRequest("").BindFunc(func(e *core.RecordRequestEvent) error {
|
||||
if e.Collection.Name == "posts" || e.Collection.Name == "comments" {
|
||||
if e.Auth != nil {
|
||||
e.Record.Set("updated_by", e.Auth.Id)
|
||||
}
|
||||
e.Record.Set("updated_at", time.Now())
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
### 3. Data Synchronization
|
||||
|
||||
```go
|
||||
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordCreateEvent) error {
|
||||
// Sync with external service
|
||||
if err := syncToExternalAPI(e.Record); err != nil {
|
||||
e.App.Logger().Warn("sync failed", "error", err)
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
func syncToExternalAPI(record *core.Record) error {
|
||||
// Implement external API sync
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Related Topics
|
||||
|
||||
- [Event Hooks](go_event_hooks.md) - Detailed hook documentation
|
||||
- [Database](go_database.md) - Database operations
|
||||
- [Routing](go_routing.md) - Custom API endpoints
|
||||
- [Migrations](go_migrations.md) - Database migrations
|
||||
- [Testing](go_testing.md) - Testing strategies
|
||||
- [Logging](go_logging.md) - Logging and monitoring
|
||||
Reference in New Issue
Block a user