Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
153e6a9
spec for moderation added
mattwoberts Jul 1, 2025
b40ef19
Generated back-end mostly code.
mattwoberts Jul 2, 2025
cd7ffbf
Post and comment moderation on the show post page seems to be working
mattwoberts Jul 2, 2025
16d58bc
Moderation admin
mattwoberts Jul 2, 2025
4b9b029
Making changes to support more moderation options.
mattwoberts Jul 3, 2025
4ba03a8
Fixed a few issues.
mattwoberts Jul 21, 2025
96792c6
Starting point for a moderation page.
mattwoberts Jul 22, 2025
9b0a2b9
Some more WIP towards an admin page for the moderation stuff.
mattwoberts Jul 23, 2025
b45b821
More work on the admin moderation page.
mattwoberts Jul 28, 2025
a9f55de
Made the UI more like the design
mattwoberts Jul 29, 2025
434a9db
More UI changes
mattwoberts Jul 29, 2025
bd630f0
Less custom CSS, more utility
mattwoberts Jul 29, 2025
2069c3b
WIP content moderation
mattwoberts Aug 15, 2025
26c57e5
Working on the moderation UI
mattwoberts Aug 20, 2025
d5a435c
More content moderation
mattwoberts Aug 20, 2025
b8cac19
Moderation UI changes.
mattwoberts Aug 20, 2025
2099114
More moderation stuff - decline and block
mattwoberts Sep 19, 2025
6ecc6e9
Show blocked users
mattwoberts Sep 19, 2025
7292e70
Approve and verify DONE
mattwoberts Sep 19, 2025
42afc49
Better user listing
mattwoberts Sep 19, 2025
41c83f2
More user listing improvements
mattwoberts Sep 21, 2025
5c043e1
More UI improvements for the user listing
mattwoberts Sep 22, 2025
28e7361
Re-work of member listing page.
mattwoberts Sep 22, 2025
2cb66ef
Show verified users
mattwoberts Sep 23, 2025
d7bb9a3
Plumbing in the moderation stuff.
mattwoberts Sep 24, 2025
2e36c9f
More backend logic for the content moderation.
mattwoberts Sep 24, 2025
57c1d65
Commercial features - phase 1
mattwoberts Sep 26, 2025
532f32c
Commercial features - phase 2
mattwoberts Sep 27, 2025
bd9269a
More work towards the commercial separation.
mattwoberts Sep 29, 2025
a4fcdb5
Working route resolution with a registry
mattwoberts Sep 30, 2025
57f62ce
.gitignore
mattwoberts Sep 30, 2025
3d1c094
.gitignore
mattwoberts Sep 30, 2025
354b3ad
Split db entities and the db handler functions
mattwoberts Sep 30, 2025
a44bfee
Starting to plumb in commercial toggle
mattwoberts Oct 3, 2025
8c73978
Don't serve stuff for non-commercial users
mattwoberts Oct 3, 2025
1d5e19d
Starting to remove more dbEntities into own folder and package
mattwoberts Oct 3, 2025
cff0911
Moved more entities to dbEntities
mattwoberts Oct 4, 2025
84e585b
Bring "commercial" to life
mattwoberts Oct 6, 2025
5895e66
Github actions should run all the tests.
mattwoberts Oct 6, 2025
56edcbe
Fixed warning
mattwoberts Oct 6, 2025
21542f9
Checking if commercial features are enabled
mattwoberts Oct 6, 2025
123754d
Removed console.log
mattwoberts Oct 7, 2025
228be70
Language strings (wip)
mattwoberts Oct 8, 2025
a0fa918
Fixed formatting
mattwoberts Oct 8, 2025
3cfefc7
Merge branch 'main' into content-moderation
mattwoberts Oct 10, 2025
859703e
For now, disable the google translate stuff
mattwoberts Oct 10, 2025
3e4f500
Merge branch 'main' into content-moderation
mattwoberts Nov 4, 2025
5d65669
Tidy up the content moderation scripts.
mattwoberts Nov 5, 2025
129cb2c
Missing columns.
mattwoberts Nov 5, 2025
83fba8d
Fixed issue with lingui not picking up strings from other folders lik…
mattwoberts Nov 5, 2025
a1997cf
UI tweaks and translation improvements.
mattwoberts Nov 6, 2025
8f74405
Verify is now Trust. Also some minor UI changes.
mattwoberts Nov 6, 2025
0dd4d03
More content moderation ui misc tweaks
mattwoberts Nov 6, 2025
ff47425
Merge main and fix a few issues.
mattwoberts Nov 17, 2025
8b08aba
Moderation items added to filter.
mattwoberts Nov 19, 2025
e6451f1
Language code tweaks
mattwoberts Nov 19, 2025
92304e6
Copy changes
mattwoberts Nov 19, 2025
19bd3fd
More language changes
mattwoberts Nov 19, 2025
72d0fdd
Pending items moved to a status filter.
mattwoberts Nov 21, 2025
731c7e5
First stab at stripe integration.
mattwoberts Nov 23, 2025
fa47df2
Better billing page.
mattwoberts Nov 24, 2025
f140f19
Removed paddle and trial logic
mattwoberts Nov 26, 2025
163383f
Trial notice removed
mattwoberts Nov 26, 2025
beeae4c
More stripe migration
mattwoberts Nov 28, 2025
d0c045c
Tests fixed.
mattwoberts Nov 28, 2025
d44c902
Determine if instance is commercial or "normal"
mattwoberts Nov 29, 2025
1d3c6c4
Upgrade path for paddle -> stripe
mattwoberts Nov 30, 2025
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
10 changes: 10 additions & 0 deletions .example.env
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@ EMAIL_SMTP_HOST=localhost
EMAIL_SMTP_PORT=1025
EMAIL_SMTP_USERNAME=
EMAIL_SMTP_PASSWORD=

# Commercial License (Optional for self-hosted, Required for hosted multi-tenant)
# LICENSE_MASTER_SECRET: Only required for hosted Fider instances that sell Pro subscriptions
# - Used to generate and validate license keys for Pro customers
# - Not needed for self-hosted unless you want to validate a COMMERCIAL_KEY
# COMMERCIAL_KEY: For self-hosted Fider, set this to enable commercial features (content moderation)
# - Leave blank to run Fider without commercial features (free/open-source mode)
# - If you have a commercial license, set it here
# LICENSE_MASTER_SECRET=your-secret-key-here-change-in-production
# COMMERCIAL_KEY=FIDER-COMMERCIAL-123-1638360000-abc123...
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
- name: make test-server
run: |
mkdir ./dist
make test-server
make test-server SHORT=false
env:
BLOB_STORAGE_S3_ENDPOINT_URL: http://localhost:9000
DATABASE_URL: postgres://fider_ci:fider_ci_pw@localhost:5432/fider_ci?sslmode=disable
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/locale.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Auto-Translate Missing Keys

# Workflow automatic triggers disabled - only manual trigger is available
on:
workflow_dispatch:
inputs:
Expand All @@ -8,11 +9,10 @@ on:
required: false
default: "false"

push:
branches:
- main
paths:
- "locale/en/**.json"
# Automatic trigger temporarily disabled
# push:
# paths:
# - "locale/en/**.json"

jobs:
translate:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ tsconfig.tsbuildinfo
etc/*.pem
fider_schema.sql
fider.sql
.zed/debug.json
WARP.md
2 changes: 2 additions & 0 deletions .test.env
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ EMAIL_MAILGUN_DOMAIN=mydomain.com

USER_LIST_ENABLED=true
USER_LIST_APIKEY=abcdefg

LICENSE_MASTER_SECRET=test_master_secret_for_license_generation
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ The application includes pluggable services for:
- **Email**: SMTP, Mailgun, AWS SES
- **Blob Storage**: Filesystem, S3, SQL
- **OAuth**: Custom providers, GitHub, Google, etc.
- **Billing**: Paddle integration (optional)
- **Billing**: Stripe integration (optional)
- **Webhooks**: Outbound event notifications

## Development Setup Requirements
Expand Down Expand Up @@ -180,7 +180,7 @@ Fider uses BEM methodology combined with utility classes:
- **`c-<component_name>__<element>`** - Element classes (e.g., `c-toggle__label`)
- **`c-<component_name>--<state>`** - State modifiers (e.g., `c-toggle--checked`)
- **`is-<state>`, `has-<state>`** - Global state modifiers
- **Utility classes** - No prefix, used for common styling patterns. All utility classes are defined in public/assets/styles/utility/
- **Utility classes** - No prefix, used for common styling patterns. All utility classes are defined in public/assets/styles/utility

### General Principles

Expand Down
213 changes: 213 additions & 0 deletions COMMERCIAL_MODERATION_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Commercial Content Moderation Restructure Plan

## Overview

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.

## Key Architectural Decisions

### 1. Webpack Integration (Simple Solution)
- Update `webpack.config.js` to scan both folders:
```js
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

### 2. Backend Package Separation Strategy

**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

## What Moves to Commercial (Strong Legal Protection)

### Commercial Folder Structure
```
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)
```

### Frontend (Moves to Commercial)
- Complete moderation admin UI (ContentModeration.page.tsx + styles)
- Moderation indicator component showing pending counts
- All moderation-specific UI components

### Backend (Moves to Commercial)
- 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

## What Stays Open Source (Minimal Infrastructure)

### Database/Models (Cannot Move - Bus System Dependency)
- `app/models/cmd/moderation.go` - Command type definitions
- `app/models/query/moderation.go` - Query type definitions
- Database schema, migrations (`is_moderation_enabled`, `is_approved` columns)
- Entity definitions (`ModerationItem` structs)

### Settings Infrastructure
- Privacy settings toggle (shows "upgrade" message if unlicensed)
- Basic tenant property (`isModerationEnabled`)
- License validation service

### Content Flow Logic
- Logic that marks content as needing approval
- Basic "content requires moderation" checks throughout codebase
- Content queuing when moderation is enabled

### Locale Files
- All `moderation.*` translation strings (used by upgrade messages too)

## Technical Implementation Details

### 1. Handler Registration Pattern
```go
// 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
}
}
```

### 2. Service Registration Pattern
```go
// 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
}
}
```

### 3. License Validation Service
```go
// app/services/license.go
type LicenseService interface {
IsCommercialFeatureEnabled(feature string) bool
}

// Implementation checks for valid commercial license
// Controls route registration and service overrides
```

## Implementation Phases

### Phase 1: Setup Commercial Infrastructure
1. Create `commercial/` folder structure
2. Add restrictive LICENSE file to commercial folder
3. Update webpack.config.js to scan commercial folder
4. Create license validation service interface

### Phase 2: Move Frontend Components
1. Move `ContentModeration.page.tsx` to `commercial/pages/Administration/`
2. Move `ModerationIndicator.tsx` to `commercial/components/`
3. Test that webpack builds both locations correctly
4. Add license checks in components to show upgrade messages

### Phase 3: Implement Backend Service Separation
1. Create stub implementations for all moderation commands/queries
2. Register stubs in open source postgres service
3. Move real implementations to `commercial/services/moderation.go`
4. Implement commercial service registration with license checks

### Phase 4: Implement Route Separation
1. Replace direct handler calls with upgrade handlers in routes.go
2. Move real handlers to `commercial/handlers/`
3. Implement dynamic route registration in commercial init
4. Test route overriding works correctly

### Phase 5: Testing & Validation
1. Move tests to commercial folder
2. Test open source build without commercial features
3. Test commercial build with license validation
4. Verify graceful degradation and upgrade messaging
5. Test that forking open source works without commercial parts

## Key Benefits

### Strong Commercial Protection
- Recreating moderation requires rebuilding entire UI + API + business logic
- Substantial engineering effort (days/weeks) to replicate functionality
- Clear legal separation under different licenses

### Clean Architecture
- 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

### Simple Build Process
- Single repository with clear folder separation
- Standard webpack build process
- License provides protection, not technical hiding
- Easy development workflow

## Migration Considerations

### Existing Moderation Data
- All existing moderation settings and data remain compatible
- Database schema stays in open source (infrastructure)
- Only the management interface becomes commercial

### User Experience
- **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

### Development Workflow
- 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

## Success Metrics

1. **Legal Protection**: Someone forking open source cannot easily recreate moderation
2. **Functional Separation**: Open source works perfectly without moderation
3. **Build Compatibility**: Both open source and commercial versions build successfully
4. **Clean Boundaries**: Clear understanding of what's core vs commercial
5. **Scalability**: Pattern works for future commercial features

This plan provides genuine open core protection while maintaining clean architecture and manageable implementation complexity.
Binary file added CleanShot 2025-07-01 at [email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added CleanShot 2025-07-01 at [email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added CleanShot 2025-07-01 at [email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions MODERATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Moderation

We are going to implement the ability to moderate posts and comments added to fider. The idea is that when a new post is added, that post is then flagged as unmoderated. An admin will need to approve it.

This document explains everything that needs to change in Fider to facilitate this feature.

## Settings

This is a optional feature. Admins will be able to toggle this. This is done in the public/pages/Administration/pages/PrivacySettings.page.tsx page. Similar to how the other settings are controlled in this page. There needs to be a new column in the "tenants" database table called "is_moderation_enabled" to control this, so you're going to need a new migration file in migrations/

## New posts and comments

Posts and comments will need a new column "is_approved" to determine if the post or comment has been approved to be shown. Again, this will need adding to the migration.

When a new post or comment is added, if moderation is enabled then is_approved will be false, otherwise it will be true. New posts are added via public/pages/Home/components/ShareFeedback.tsx and comments via public/pages/ShowPost/components/CommentInput.tsx.

Once added, the post is only visible to the person who added it (and admins, see below). When you view the post (or the comment) via public/pages/ShowPost/ShowPost.page.tsx, there needs to be a message to tell you that it's awaiting moderation.

## Doing the moderation

The "admin" section of fider looks like this (public/pages/Administration/components/AdminBasePage.tsx):
![alt text](<CleanShot 2025-07-01 at [email protected]>)

There neeeds to be a new menu item on the left for "Moderation"

Clicking on that presents you with a tablular view of all non-moderated posts and comments.
For each row, display the following columns:
_ A checkbox to allow you to select multiple rows
_ User's name and date of post (e.g. "Matt, 10 minutes ago")
_ Wide column for the description.
_ If comment: "New comment: <comment>" (truncated to 200 chars)
_ If post: "New post: <post title>"
_ Thumbs up button to approve \* Thumbs down button to decline

If you click the description for a post or comment, it will take you to the post, and if you clicked a comment, will highlight the comment (this is already supporeted, see how the public/pages/ShowPost/ShowPost.page.tsx page highlights comments). When you are an admin, and it's a post that's awaiting moderation, the in place of the voting button, we need 2 buttons - one to approve, one to decline. The same is true for comments, there should be an approve / decline set of buttons udner the comment.

Declining a post or comment will delete it entirely. We should ask the user to confirm the action.

Approving should just set it as approved, and remove it from the list (might be easier to re-fetch the content for moderation)

Here is a screenshot showing the inspiration we found for the moderation page

![alt text](<CleanShot 2025-07-01 at [email protected]>)

You can see the bulk actions on this screenshot, we're interested in having bulk actions to approve or decline too to make it easier, plus a "select all" to highlight them all. The UI for the bulk actions should be like the "Sort by" options on the post listing in public/pages/Home/components/PostsSort.tsx

## Moderation Changes 1

We've decided to make some changes to the moderation admin:

1. Rather than have it part of the admin menu, remove the entry from the side menu in admin. Instead, we want an icon in the top Header (public/components/Header.tsx) that when clicked, takes you to the moderation admin page, without the side menu. Ideally the icon will have a little counter in the top-right of how many items are awaiting moderation.

2. We've decided to make the moderation less onourous to the admins by making some more changes:

2.1) As well as decline, you have another option - "decline and block". This option will decline the post or comment, and block the user who made it from posting again. We already have the ability to block users in Fider (see BlockUser in app/handlers/user.go), so we can hook into that. So if you had 1 user who had 5 posts and some comments, and you declined and blocked them, we would also decline all other posts and comments they made, and block them from posting again.

2.2) As well as "approve", you have another option - "approve and verify". This option will approve the post (or comment) and ALL other posts and comments from this user. It will also make the user "verified" for posting, meaning that any future posts or comments from that user can bypass moderation. To facilitate this, we're going to need a new column on the user, which will work similar to how blocking a user works, except that it will do the opposite. You can't be both blocked and verified, so if you "approve and verify" we'll need to unset any blocked status for that user.
Loading
Loading