Skip to content

Commit 91aee14

Browse files
authored
Merge pull request #25 from Innovix-Matrix-Systems/dev
(feat): Database CLI seeder support
2 parents 3990d38 + 29f29bf commit 91aee14

File tree

11 files changed

+531
-2
lines changed

11 files changed

+531
-2
lines changed

README.md

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

2324
## Quick Start
2425

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

137139
## Project Structure

docs/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,16 @@ Guide for setting up Git hooks for code quality and automation:
187187
- Automated code quality checks
188188
- Integration with development workflow
189189

190+
### [Database Seeders Guide](seeders.md)
191+
192+
Complete guide for creating and managing database seeders:
193+
194+
- CLI seeder architecture and registration
195+
- Creating custom seeder functions
196+
- Running individual and batch seeders
197+
- Best practices for data generation
198+
- Integration with the factory system
199+
190200
## Advanced Topics
191201

192202
### [Dependency Injection Guide](dependency-injection.md)

docs/seeders.md

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# Database Seeders Guide
2+
3+
This document explains how to create and register new database seeders in the IMS PocketBase BaaS Starter.
4+
5+
## Overview
6+
7+
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:
8+
9+
1. **Migration Seeders** - Run automatically during database migrations (for essential data)
10+
2. **CLI Seeders** - Run manually via CLI commands (for test data and development)
11+
12+
This guide focuses on **CLI Seeders** which are designed for development and testing purposes.
13+
14+
## CLI Seeder Architecture
15+
16+
CLI Seeders follow a simple manual registration pattern:
17+
18+
- **Seeder Functions** - Individual functions that perform the seeding logic
19+
- **Registry** - Manual list of seeder functions in `internal/database/seeders/cli_seeder_registry.go`
20+
- **CLI Commands** - Commands to run individual or all seeders
21+
22+
## Creating a New CLI Seeder
23+
24+
### Step 1: Create the Seeder Function
25+
26+
Add your seeder function to an existing file (like `user_seeder.go`) or create a new seeder file:
27+
28+
```go
29+
// internal/database/seeders/user_seeder.go
30+
31+
// SeedUsersCLI seeds a specified number of fake users
32+
func SeedUsersCLI(app core.App, count int) error {
33+
log := logger.FromApp(app)
34+
35+
fmt.Printf("🌱 Seeding %d users...\n", count)
36+
37+
log.Info("Seeding users", "count", count)
38+
39+
userFactory := factories.NewUserFactory(app)
40+
41+
users, err := userFactory.GenerateMany(count)
42+
if err != nil {
43+
return fmt.Errorf("failed to generate users: %w", err)
44+
}
45+
46+
for i, user := range users {
47+
if err := app.Save(user); err != nil {
48+
return fmt.Errorf("failed to save user %d: %w", i+1, err)
49+
}
50+
log.Info("Created user", "name", user.GetString("name"), "email", user.GetString("email"))
51+
}
52+
53+
fmt.Printf("✅ Successfully seeded %d users\n", count)
54+
55+
log.Info("Successfully seeded users", "count", count)
56+
return nil
57+
}
58+
```
59+
60+
### Step 2: Register the Seeder Function
61+
62+
Add your seeder function to the registry in `internal/database/seeders/cli_seeder_registry.go`:
63+
64+
```go
65+
// GetAllCLISeederFunctions returns a list of all CLI seeder functions
66+
func GetAllCLISeederFunctions() []CLISeederFunction {
67+
return []CLISeederFunction{
68+
// Existing seeders...
69+
{
70+
Name: "UserSeeder[10]",
71+
Description: "Seeds 10 fake users",
72+
Function: func(app core.App) error {
73+
return SeedUsersCLI(app, 10)
74+
},
75+
},
76+
// Add your new seeder here:
77+
{
78+
Name: "CustomSeeder[50]",
79+
Description: "Seeds 50 custom records",
80+
Function: func(app core.App) error {
81+
return SeedCustomRecords(app, 50)
82+
},
83+
},
84+
}
85+
}
86+
```
87+
88+
### Step 3: (Optional) Create a Dedicated CLI Command
89+
90+
If you want to run your seeder individually, create a command handler in `internal/handlers/command/`:
91+
92+
```go
93+
// internal/handlers/command/custom_seeder_command.go
94+
95+
// HandleCustomSeederCommand handles the 'seed-custom' CLI command
96+
func HandleCustomSeederCommand(app *pocketbase.PocketBase, cmd *cobra.Command, args []string) {
97+
log := logger.GetLogger(app)
98+
99+
log.Info("Starting custom seeder process")
100+
fmt.Println("🌱 Starting custom seeder process...")
101+
102+
// Call the seeder function
103+
if err := seeders.SeedCustomRecords(app, 50); err != nil {
104+
log.Error("Failed to seed custom records", "error", err)
105+
fmt.Printf("❌ Error seeding custom records: %v\n", err)
106+
return
107+
}
108+
109+
log.Info("Custom seeder process completed successfully")
110+
fmt.Println("✅ Custom seeder completed successfully")
111+
}
112+
```
113+
114+
Then register it in `internal/commands/commands.go`. For detailed instructions on creating custom CLI commands, see the [CLI Commands Guide](cli-commands.md).
115+
116+
## Running Seeders
117+
118+
### Run All Registered Seeders
119+
120+
```bash
121+
./main db-seed
122+
```
123+
124+
This command runs all functions registered in `GetAllCLISeederFunctions()`.
125+
126+
### Run Individual Seeders
127+
128+
If you created a dedicated command:
129+
130+
```bash
131+
./main seed-custom
132+
```
133+
134+
## Best Practices
135+
136+
1. **Use Descriptive Names**: Give your seeders clear, descriptive names
137+
2. **Provide Useful Descriptions**: Help users understand what each seeder does
138+
3. **Handle Errors Gracefully**: Return meaningful error messages
139+
4. **Use the Logger**: Log important events with `logger.FromApp(app)`
140+
5. **Console Output**: Provide clear console feedback with emojis and formatting
141+
6. **Make Seeders Idempotent**: Design seeders so they can be run multiple times safely
142+
7. **Use Factories**: Leverage existing factories for generating fake data
143+
144+
## Example: Complete Custom Seeder
145+
146+
Here's a complete example of a custom seeder:
147+
148+
```go
149+
// internal/database/seeders/custom_seeder.go
150+
151+
// SeedProductsCLI seeds a specified number of fake products
152+
func SeedProductsCLI(app core.App, count int) error {
153+
log := logger.FromApp(app)
154+
155+
fmt.Printf("🌱 Seeding %d products...\n", count)
156+
157+
log.Info("Seeding products", "count", count)
158+
159+
// Get products collection
160+
productsCollection, err := app.FindCollectionByNameOrId("products")
161+
if err != nil {
162+
return fmt.Errorf("failed to find products collection: %w", err)
163+
}
164+
165+
// Generate fake products
166+
for i := 0; i < count; i++ {
167+
product := core.NewRecord(productsCollection)
168+
product.Set("name", faker.Word())
169+
product.Set("price", faker.Price(10, 1000))
170+
product.Set("description", faker.Sentence())
171+
product.Set("in_stock", faker.Bool())
172+
173+
if err := app.Save(product); err != nil {
174+
return fmt.Errorf("failed to save product %d: %w", i+1, err)
175+
}
176+
177+
log.Info("Created product", "name", product.GetString("name"))
178+
}
179+
180+
fmt.Printf("✅ Successfully seeded %d products\n", count)
181+
182+
log.Info("Successfully seeded products", "count", count)
183+
return nil
184+
}
185+
```
186+
187+
Register it in the registry:
188+
189+
```go
190+
{
191+
Name: "ProductSeeder[25]",
192+
Description: "Seeds 25 fake products",
193+
Function: func(app core.App) error {
194+
return SeedProductsCLI(app, 25)
195+
},
196+
},
197+
```
198+
199+
Now when you run `./main db-seed`, your new product seeder will be executed along with all other registered seeders.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/fatih/color v1.18.0 // indirect
2424
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
2525
github.com/ganigeorgiev/fexpr v0.5.0 // indirect
26+
github.com/go-faker/faker/v4 v4.6.1 // indirect
2627
github.com/go-logr/logr v1.4.3 // indirect
2728
github.com/go-logr/stdr v1.2.2 // indirect
2829
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBv
2323
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
2424
github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk=
2525
github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
26+
github.com/go-faker/faker/v4 v4.6.1 h1:xUyVpAjEtB04l6XFY0V/29oR332rOSPWV4lU8RwDt4k=
27+
github.com/go-faker/faker/v4 v4.6.1/go.mod h1:arSdxNCSt7mOhdk8tEolvHeIJ7eX4OX80wXjKKvkKBY=
2628
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
2729
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
2830
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=

internal/commands/commands.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,30 @@ func RegisterCommands(app *pocketbase.PocketBase) error {
4343
Handler: command.HandleSyncPermissionsCommand,
4444
Enabled: true,
4545
},
46+
{
47+
ID: "db-seed",
48+
Use: "db-seed",
49+
Short: "Run all registered CLI seeders",
50+
Long: "Executes all CLI seeders that have been registered in the seeder registry",
51+
Handler: command.HandleDBSeedCommand,
52+
Enabled: true,
53+
},
54+
{
55+
ID: "seed-users",
56+
Use: "seed-users [count]",
57+
Short: "Seed fake users for testing",
58+
Long: "Creates a specified number of fake users (default 10) for development and testing purposes",
59+
Handler: command.HandleSeedUsersCommand,
60+
Enabled: true,
61+
},
62+
{
63+
ID: "seed-users-with-role",
64+
Use: "seed-users-with-role <count> <role-name>",
65+
Short: "Seed fake users with a specific role",
66+
Long: "Creates a specified number of fake users and assigns them to a specific role for testing",
67+
Handler: command.HandleSeedUsersWithRoleCommand,
68+
Enabled: true,
69+
},
4670
// Add more commands here as needed:
4771
// {
4872
// ID: "example",
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package factories
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/go-faker/faker/v4"
7+
"github.com/pocketbase/pocketbase/core"
8+
)
9+
10+
// UserFactory generates fake user data
11+
type UserFactory struct {
12+
app core.App
13+
}
14+
15+
// NewUserFactory creates a new instance of UserFactory
16+
func NewUserFactory(app core.App) *UserFactory {
17+
return &UserFactory{app: app}
18+
}
19+
20+
// Generate creates a single fake user record without saving it
21+
func (f *UserFactory) Generate() (*core.Record, error) {
22+
usersCollection, err := f.app.FindCollectionByNameOrId("users")
23+
if err != nil {
24+
return nil, fmt.Errorf("failed to find users collection: %w", err)
25+
}
26+
27+
email := faker.Email()
28+
name := faker.Name()
29+
password := faker.Password()
30+
31+
record := core.NewRecord(usersCollection)
32+
record.Set("email", email)
33+
record.Set("name", name)
34+
record.Set("verified", true) // Default to verified for test data
35+
record.Set("is_active", true) // Default to active
36+
record.SetPassword(password)
37+
38+
return record, nil
39+
}
40+
41+
// GenerateMany creates multiple fake user records without saving them
42+
func (f *UserFactory) GenerateMany(count int) ([]*core.Record, error) {
43+
records := make([]*core.Record, count)
44+
for i := 0; i < count; i++ {
45+
record, err := f.Generate()
46+
if err != nil {
47+
return nil, fmt.Errorf("failed to generate user %d: %w", i+1, err)
48+
}
49+
records[i] = record
50+
}
51+
return records, nil
52+
}

0 commit comments

Comments
 (0)