Moving Fider's content moderation feature from open source (AGPL) to commercial licensing using an "open core" model. The commercial code will reside in a commercial/ folder with a restrictive license, while the open source core provides infrastructure and gracefully degrades when commercial features aren't licensed.
- Update
webpack.config.jsto scan both folders:paths: [ ...glob.sync(`./public/**/*.{html,tsx}`, { nodir: true }), ...glob.sync(`./commercial/**/*.{html,tsx}`, { nodir: true }) ]
- Commercial code gets bundled but legal license prevents usage
- No complex conditional compilation needed
Handlers - Dynamic Route Registration
- Open source: Register stub routes returning "upgrade" messages
- Commercial: Override routes with real handlers via dynamic registration
- License validation controls active routes
Services - Stub + Override Pattern
- Open source: Register stub bus handlers returning "not licensed" errors
- Commercial: Override with real implementations via bus registration
- Same command/query types, different implementations
commercial/
├── LICENSE (restrictive commercial license)
├── pages/
│ └── Administration/
│ └── ContentModeration.page.tsx (~300 lines)
├── components/
│ └── ModerationIndicator.tsx
├── handlers/
│ ├── moderation.go (ModerationPage, GetModerationItems, GetModerationCount)
│ └── apiv1/
│ ├── moderation.go (all API endpoints)
│ └── moderation_test.go
├── services/
│ └── moderation.go (approve/decline business logic)
└── init.go (service/route registration)
- Complete moderation admin UI (ContentModeration.page.tsx + styles)
- Moderation indicator component showing pending counts
- All moderation-specific UI components
- All HTTP handlers (
ModerationPage,GetModerationItems,GetModerationCount) - All API endpoints (
/api/v1/admin/moderation/*routes) - Business logic implementations (approve/decline/verify/block functions)
- Route registrations for moderation endpoints
- Tests for commercial functionality
app/models/cmd/moderation.go- Command type definitionsapp/models/query/moderation.go- Query type definitions- Database schema, migrations (
is_moderation_enabled,is_approvedcolumns) - Entity definitions (
ModerationItemstructs)
- Privacy settings toggle (shows "upgrade" message if unlicensed)
- Basic tenant property (
isModerationEnabled) - License validation service
- Logic that marks content as needing approval
- Basic "content requires moderation" checks throughout codebase
- Content queuing when moderation is enabled
- All
moderation.*translation strings (used by upgrade messages too)
// Open source routes.go - stub routes
ui.Get("/admin/moderation", upgradeHandler("content-moderation"))
ui.Get("/_api/admin/moderation/items", upgradeHandler("content-moderation"))
// Commercial init.go - overrides routes
func init() {
if license.IsCommercialFeatureEnabled("content-moderation") {
web.RegisterRoute("GET", "/admin/moderation", handlers.ModerationPage())
web.RegisterRoute("GET", "/_api/admin/moderation/items", handlers.GetModerationItems())
// ... all other moderation routes
}
}// Open source postgres.go - register stubs
func (s *Service) Init() {
bus.AddHandler(approvePostStub) // Returns "feature not licensed"
bus.AddHandler(declinePostStub)
bus.AddHandler(getModerationItemsStub)
// ... other stubs
}
// Commercial service init - override with real handlers
func (cs *CommercialService) Init() {
if license.IsCommercialFeatureEnabled("content-moderation") {
bus.AddHandler(approvePost) // Real implementation
bus.AddHandler(declinePost)
bus.AddHandler(getModerationItems)
// ... other real handlers
}
}// app/services/license.go
type LicenseService interface {
IsCommercialFeatureEnabled(feature string) bool
}
// Implementation checks for valid commercial license
// Controls route registration and service overrides- Create
commercial/folder structure - Add restrictive LICENSE file to commercial folder
- Update webpack.config.js to scan commercial folder
- Create license validation service interface
- Move
ContentModeration.page.tsxtocommercial/pages/Administration/ - Move
ModerationIndicator.tsxtocommercial/components/ - Test that webpack builds both locations correctly
- Add license checks in components to show upgrade messages
- Create stub implementations for all moderation commands/queries
- Register stubs in open source postgres service
- Move real implementations to
commercial/services/moderation.go - Implement commercial service registration with license checks
- Replace direct handler calls with upgrade handlers in routes.go
- Move real handlers to
commercial/handlers/ - Implement dynamic route registration in commercial init
- Test route overriding works correctly
- Move tests to commercial folder
- Test open source build without commercial features
- Test commercial build with license validation
- Verify graceful degradation and upgrade messaging
- Test that forking open source works without commercial parts
- Recreating moderation requires rebuilding entire UI + API + business logic
- Substantial engineering effort (days/weeks) to replicate functionality
- Clear legal separation under different licenses
- Open source provides database infrastructure and settings
- Commercial enhances with actual moderation functionality
- No broken states - content flows normally when moderation disabled
- True open core model: commercial builds on open source foundation
- Single repository with clear folder separation
- Standard webpack build process
- License provides protection, not technical hiding
- Easy development workflow
- All existing moderation settings and data remain compatible
- Database schema stays in open source (infrastructure)
- Only the management interface becomes commercial
- With License: Full moderation functionality as before
- Without License: Moderation simply disabled, standard user flow
- Upgrade Path: Clear messaging and sales funnel for commercial features
- Developers can see all code (legal license controls usage)
- Standard build process works for both open source and commercial
- Clean separation makes it easy to add more commercial features
- Legal Protection: Someone forking open source cannot easily recreate moderation
- Functional Separation: Open source works perfectly without moderation
- Build Compatibility: Both open source and commercial versions build successfully
- Clean Boundaries: Clear understanding of what's core vs commercial
- Scalability: Pattern works for future commercial features
This plan provides genuine open core protection while maintaining clean architecture and manageable implementation complexity.