Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ A production-ready Backend-as-a-Service (BaaS) starter kit that extends PocketBa
- 🪝 **Event Hooks System** - Comprehensive event hook management with organized handlers for records, collections, requests, mailer, and realtime events
- ⚡ **Go Cache with TTL** - High-performance in-memory caching with Time-To-Live support for improved application performance
- ⏰ **Cron Jobs & Job Queue** - Scheduled tasks and dynamic job processing with concurrent workers
- 📊 **Future-Proof Migrations** - Automated database setup, seeding, and schema evolution
- 💻 **CLI Command Support** - Command-line interface support for custom scripts and tasks
- 📈 **Metrics & Observability** - Comprehensive monitoring with Prometheus metrics and OpenTelemetry support for performance tracking and system insights
- 📧 **Email Integration** - SMTP configuration with MailHog for development
- 📚 **Auto API Documentation** - Interactive auto generated API Docs: Scalar, Swagger UI, ReDoc, OpenAPI JSON with Postman compatibility
- 📚 **Auto API Documentation** - Interactive auto generated API Docs: Scalar, Swagger UI, ReDoc, OpenAPI JSON with Postman and Bruno compatibility
- 🌱 **Database Seeders** - CLI-based database seeding system for test data and development
- 🐳 **Docker Support** - Production and development environments
- 🔄 **Hot Reload** - Development environment with automatic code reloading
- ⚙️ **Environment Configuration** - Flexible configuration via environment variables
- 📊 **Future-Proof Migrations** - Automated database setup, seeding, and schema evolution

## Quick Start

Expand Down Expand Up @@ -132,6 +133,7 @@ For a complete list of commands and usage examples, see the [Makefile Commands G
- **Caching** - High-performance TTL cache system. See [Caching Guide](docs/caching.md)
- **Metrics & Observability** - Prometheus metrics and OpenTelemetry support. See [Metrics Guide](docs/metrics.md)
- **CLI Commands** - Command-line interface for administrative tasks including permission sync and health checks. See [CLI Commands Guide](docs/cli-commands.md)
- **Database Seeders** - CLI-based database seeding for test data and development. See [Seeders Guide](docs/seeders.md)
- **Migration CLI** - Generate migrations with `make migrate-gen name=your_migration`

## Project Structure
Expand Down
10 changes: 10 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ Guide for setting up Git hooks for code quality and automation:
- Automated code quality checks
- Integration with development workflow

### [Database Seeders Guide](seeders.md)

Complete guide for creating and managing database seeders:

- CLI seeder architecture and registration
- Creating custom seeder functions
- Running individual and batch seeders
- Best practices for data generation
- Integration with the factory system

## Advanced Topics

### [Dependency Injection Guide](dependency-injection.md)
Expand Down
199 changes: 199 additions & 0 deletions docs/seeders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# Database Seeders Guide

This document explains how to create and register new database seeders in the IMS PocketBase BaaS Starter.

## Overview

The database seeder system allows you to populate your database with test data or initial data for development and production environments. Seeders are organized into two categories:

1. **Migration Seeders** - Run automatically during database migrations (for essential data)
2. **CLI Seeders** - Run manually via CLI commands (for test data and development)

This guide focuses on **CLI Seeders** which are designed for development and testing purposes.

## CLI Seeder Architecture

CLI Seeders follow a simple manual registration pattern:

- **Seeder Functions** - Individual functions that perform the seeding logic
- **Registry** - Manual list of seeder functions in `internal/database/seeders/cli_seeder_registry.go`
- **CLI Commands** - Commands to run individual or all seeders

## Creating a New CLI Seeder

### Step 1: Create the Seeder Function

Add your seeder function to an existing file (like `user_seeder.go`) or create a new seeder file:

```go
// internal/database/seeders/user_seeder.go

// SeedUsersCLI seeds a specified number of fake users
func SeedUsersCLI(app core.App, count int) error {
log := logger.FromApp(app)

fmt.Printf("🌱 Seeding %d users...\n", count)

log.Info("Seeding users", "count", count)

userFactory := factories.NewUserFactory(app)

users, err := userFactory.GenerateMany(count)
if err != nil {
return fmt.Errorf("failed to generate users: %w", err)
}

for i, user := range users {
if err := app.Save(user); err != nil {
return fmt.Errorf("failed to save user %d: %w", i+1, err)
}
log.Info("Created user", "name", user.GetString("name"), "email", user.GetString("email"))
}

fmt.Printf("✅ Successfully seeded %d users\n", count)

log.Info("Successfully seeded users", "count", count)
return nil
}
```

### Step 2: Register the Seeder Function

Add your seeder function to the registry in `internal/database/seeders/cli_seeder_registry.go`:

```go
// GetAllCLISeederFunctions returns a list of all CLI seeder functions
func GetAllCLISeederFunctions() []CLISeederFunction {
return []CLISeederFunction{
// Existing seeders...
{
Name: "UserSeeder[10]",
Description: "Seeds 10 fake users",
Function: func(app core.App) error {
return SeedUsersCLI(app, 10)
},
},
// Add your new seeder here:
{
Name: "CustomSeeder[50]",
Description: "Seeds 50 custom records",
Function: func(app core.App) error {
return SeedCustomRecords(app, 50)
},
},
}
}
```

### Step 3: (Optional) Create a Dedicated CLI Command

If you want to run your seeder individually, create a command handler in `internal/handlers/command/`:

```go
// internal/handlers/command/custom_seeder_command.go

// HandleCustomSeederCommand handles the 'seed-custom' CLI command
func HandleCustomSeederCommand(app *pocketbase.PocketBase, cmd *cobra.Command, args []string) {
log := logger.GetLogger(app)

log.Info("Starting custom seeder process")
fmt.Println("🌱 Starting custom seeder process...")

// Call the seeder function
if err := seeders.SeedCustomRecords(app, 50); err != nil {
log.Error("Failed to seed custom records", "error", err)
fmt.Printf("❌ Error seeding custom records: %v\n", err)
return
}

log.Info("Custom seeder process completed successfully")
fmt.Println("✅ Custom seeder completed successfully")
}
```

Then register it in `internal/commands/commands.go`. For detailed instructions on creating custom CLI commands, see the [CLI Commands Guide](cli-commands.md).

## Running Seeders

### Run All Registered Seeders

```bash
./main db-seed
```

This command runs all functions registered in `GetAllCLISeederFunctions()`.

### Run Individual Seeders

If you created a dedicated command:

```bash
./main seed-custom
```

## Best Practices

1. **Use Descriptive Names**: Give your seeders clear, descriptive names
2. **Provide Useful Descriptions**: Help users understand what each seeder does
3. **Handle Errors Gracefully**: Return meaningful error messages
4. **Use the Logger**: Log important events with `logger.FromApp(app)`
5. **Console Output**: Provide clear console feedback with emojis and formatting
6. **Make Seeders Idempotent**: Design seeders so they can be run multiple times safely
7. **Use Factories**: Leverage existing factories for generating fake data

## Example: Complete Custom Seeder

Here's a complete example of a custom seeder:

```go
// internal/database/seeders/custom_seeder.go

// SeedProductsCLI seeds a specified number of fake products
func SeedProductsCLI(app core.App, count int) error {
log := logger.FromApp(app)

fmt.Printf("🌱 Seeding %d products...\n", count)

log.Info("Seeding products", "count", count)

// Get products collection
productsCollection, err := app.FindCollectionByNameOrId("products")
if err != nil {
return fmt.Errorf("failed to find products collection: %w", err)
}

// Generate fake products
for i := 0; i < count; i++ {
product := core.NewRecord(productsCollection)
product.Set("name", faker.Word())
product.Set("price", faker.Price(10, 1000))
product.Set("description", faker.Sentence())
product.Set("in_stock", faker.Bool())

if err := app.Save(product); err != nil {
return fmt.Errorf("failed to save product %d: %w", i+1, err)
}

log.Info("Created product", "name", product.GetString("name"))
}

fmt.Printf("✅ Successfully seeded %d products\n", count)

log.Info("Successfully seeded products", "count", count)
return nil
}
```

Register it in the registry:

```go
{
Name: "ProductSeeder[25]",
Description: "Seeds 25 fake products",
Function: func(app core.App) error {
return SeedProductsCLI(app, 25)
},
},
```

Now when you run `./main db-seed`, your new product seeder will be executed along with all other registered seeders.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/ganigeorgiev/fexpr v0.5.0 // indirect
github.com/go-faker/faker/v4 v4.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBv
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk=
github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/go-faker/faker/v4 v4.6.1 h1:xUyVpAjEtB04l6XFY0V/29oR332rOSPWV4lU8RwDt4k=
github.com/go-faker/faker/v4 v4.6.1/go.mod h1:arSdxNCSt7mOhdk8tEolvHeIJ7eX4OX80wXjKKvkKBY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
Expand Down
24 changes: 24 additions & 0 deletions internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,30 @@ func RegisterCommands(app *pocketbase.PocketBase) error {
Handler: command.HandleSyncPermissionsCommand,
Enabled: true,
},
{
ID: "db-seed",
Use: "db-seed",
Short: "Run all registered CLI seeders",
Long: "Executes all CLI seeders that have been registered in the seeder registry",
Handler: command.HandleDBSeedCommand,
Enabled: true,
},
{
ID: "seed-users",
Use: "seed-users [count]",
Short: "Seed fake users for testing",
Long: "Creates a specified number of fake users (default 10) for development and testing purposes",
Handler: command.HandleSeedUsersCommand,
Enabled: true,
},
{
ID: "seed-users-with-role",
Use: "seed-users-with-role <count> <role-name>",
Short: "Seed fake users with a specific role",
Long: "Creates a specified number of fake users and assigns them to a specific role for testing",
Handler: command.HandleSeedUsersWithRoleCommand,
Enabled: true,
},
// Add more commands here as needed:
// {
// ID: "example",
Expand Down
52 changes: 52 additions & 0 deletions internal/database/factories/user_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package factories

import (
"fmt"

"github.com/go-faker/faker/v4"
"github.com/pocketbase/pocketbase/core"
)

// UserFactory generates fake user data
type UserFactory struct {
app core.App
}

// NewUserFactory creates a new instance of UserFactory
func NewUserFactory(app core.App) *UserFactory {
return &UserFactory{app: app}
}

// Generate creates a single fake user record without saving it
func (f *UserFactory) Generate() (*core.Record, error) {
usersCollection, err := f.app.FindCollectionByNameOrId("users")
if err != nil {
return nil, fmt.Errorf("failed to find users collection: %w", err)
}

email := faker.Email()
name := faker.Name()
password := faker.Password()

record := core.NewRecord(usersCollection)
record.Set("email", email)
record.Set("name", name)
record.Set("verified", true) // Default to verified for test data
record.Set("is_active", true) // Default to active
record.SetPassword(password)

return record, nil
}

// GenerateMany creates multiple fake user records without saving them
func (f *UserFactory) GenerateMany(count int) ([]*core.Record, error) {
records := make([]*core.Record, count)
for i := 0; i < count; i++ {
record, err := f.Generate()
if err != nil {
return nil, fmt.Errorf("failed to generate user %d: %w", i+1, err)
}
records[i] = record
}
return records, nil
}
Loading
Loading