Skip to content

Commit ae64fdb

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/npm_and_yarn-2697d138d9
2 parents 77a9d01 + 253556b commit ae64fdb

171 files changed

Lines changed: 3603 additions & 780 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.example.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ BASE_URL=http://localhost:3000
22
GO_ENV=development
33
DATABASE_URL=postgres://fider:fider_pw@localhost:5555/fider?sslmode=disable
44
JWT_SECRET=hsjl]W;&ZcHxT&FK;s%bgIQF:#ch=~#Al4:5]N;7V<qPZ3e9lT4'%;go;LIkc%k
5+
# ALLOW_ALLOWED_SCHEMES=false
56

67
LOG_LEVEL=DEBUG
78
LOG_CONSOLE=true

.github/workflows/locale.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ jobs:
4141
if: ${{ inputs.dry != 'true' }}
4242
uses: peter-evans/create-pull-request@v6
4343
with:
44+
base: ${{ github.ref_name }}
4445
commit-message: "Auto-translate missing keys in locales"
45-
title: "Auto-translate missing keys in locales"
46+
title: "[${{ github.head_ref || github.ref_name }}] Auto-translate missing keys in locales"
4647
body: "This PR adds machine-translated values for any missing locale keys."
4748
branch: auto/translate-missing-keys
4849
add-paths: |

CLAUDE.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Common Development Commands
6+
7+
### Building
8+
9+
- `make build` - Build both server and UI
10+
- `make build-server` - Build Go server binary
11+
- `make build-ui` - Build UI assets with webpack
12+
- `make build-ssr` - Build SSR script and compile locales
13+
14+
### Running
15+
16+
- `make run` - Run Fider server (requires build first)
17+
- `make watch` - Start both server and UI in watch mode (recommended for development)
18+
- `make watch-server` - Run server in watch mode with air
19+
- `make watch-ui` - Run UI in watch mode with webpack
20+
- `make migrate` - Run database migrations
21+
22+
### Testing
23+
24+
- `make test` - Run both server and UI tests
25+
- `make test-server` - Run Go server tests (includes migration)
26+
- `make test-ui` - Run Jest tests for React components
27+
- `make coverage-server` - Run server tests with coverage
28+
- `make test-e2e-server` - Run E2E tests for server features
29+
- `make test-e2e-ui` - Run E2E tests for UI features
30+
31+
### Linting
32+
33+
- `make lint` - Lint both server and UI code
34+
- `make lint-server` - Run golangci-lint on Go code
35+
- `make lint-ui` - Run ESLint on TypeScript/React code
36+
37+
### Other
38+
39+
- `make clean` - Remove build artifacts
40+
- `make help` - Show all available make targets
41+
42+
## Project Architecture
43+
44+
### Backend (Go)
45+
46+
Fider uses a layered architecture with clean separation of concerns:
47+
48+
**Core Structure:**
49+
50+
- `main.go` - Entry point with command routing (ping, migrate, server)
51+
- `app/cmd/` - Command implementations and server bootstrap
52+
- `app/cmd/routes.go` - All routes are defined here
53+
- `app/handlers/` - HTTP handlers organized by functionality
54+
- `app/middlewares/` - HTTP middleware chain
55+
- `app/models/` - Data models (cmd, dto, entity, enum, query)
56+
- `app/services/` - Service implementations with dependency injection
57+
- `app/pkg/` - Reusable packages and utilities
58+
59+
**Key Patterns:**
60+
61+
- **Bus Architecture**: Uses `app/pkg/bus` for service registration and dispatch
62+
- **CQRS**: Commands and queries are separated in `app/models/`
63+
- **Service Layer**: All external services (email, blob storage, oauth) are abstracted
64+
- **Middleware Chain**: Authentication, tenant resolution, CORS, etc.
65+
66+
**Database:**
67+
68+
- PostgreSQL with custom migration system in `migrations/`
69+
- SQL-based data access through service interfaces
70+
- Tenant-aware data isolation
71+
72+
### Frontend (React/TypeScript)
73+
74+
Modern React application with TypeScript:
75+
76+
**Structure:**
77+
78+
- `public/index.tsx` - Application entry point with React 18
79+
- `public/components/` - Reusable UI components
80+
- `public/pages/` - Page-level components organized by feature
81+
- `public/services/` - Client-side services and utilities
82+
- `public/hooks/` - Custom React hooks
83+
84+
**Key Features:**
85+
86+
- **SSR Support**: Server-side rendering with hydration
87+
- **Internationalization**: LinguiJS for i18n with locale switching
88+
- **Component Library**: Extensive set of reusable components
89+
- **State Management**: React Context for global state
90+
- **Error Boundaries**: Comprehensive error handling
91+
92+
**Build System:**
93+
94+
- Webpack for bundling with CSS extraction
95+
- ESBuild for SSR compilation
96+
- SCSS for styling with utility classes
97+
- Asset optimization and code splitting
98+
99+
### API Design
100+
101+
RESTful API with multiple access levels:
102+
103+
- `/api/v1/` - Public API (no auth required)
104+
- Member API - Authenticated users
105+
- Staff API - Collaborators and administrators
106+
- Admin API - Administrators only
107+
108+
### Key Services
109+
110+
The application includes pluggable services for:
111+
112+
- **Email**: SMTP, Mailgun, AWS SES
113+
- **Blob Storage**: Filesystem, S3, SQL
114+
- **OAuth**: Custom providers, GitHub, Google, etc.
115+
- **Billing**: Paddle integration (optional)
116+
- **Webhooks**: Outbound event notifications
117+
118+
## Development Setup Requirements
119+
120+
1. **Go 1.22+** - Backend development
121+
2. **Node.js 21/22** - Frontend build tools and TypeScript
122+
3. **Docker** - PostgreSQL and local SMTP (MailHog)
123+
4. **Air** - Go hot reload: `go install github.com/cosmtrek/air`
124+
5. **Godotenv** - Environment loading: `go install github.com/joho/godotenv/cmd/godotenv`
125+
6. **golangci-lint** - Go linting: `go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.1`
126+
127+
**Environment Setup:**
128+
129+
- Copy `.example.env` to `.env` for local configuration
130+
- Run `docker compose up -d` for PostgreSQL and MailHog
131+
- MailHog UI available at http://localhost:8025
132+
133+
## Testing Strategy
134+
135+
**Backend Testing:**
136+
137+
- Unit tests alongside source files (`*_test.go`)
138+
- Integration tests with test database
139+
- E2E tests using Cucumber for server features
140+
141+
**Frontend Testing:**
142+
143+
- Jest for unit/component tests
144+
- Testing Library for React components
145+
- E2E tests using Cucumber + Playwright
146+
147+
**Test Environment:**
148+
149+
- Uses `.test.env` for test-specific configuration
150+
- Automated database migrations before test runs
151+
- Coverage reporting available
152+
153+
## Code Organization Principles
154+
155+
### Backend Model Naming Conventions
156+
157+
Follow these strict naming patterns for `app/models/`:
158+
159+
- **`action.<something>`** - User interactions for POST/PUT/PATCH requests, map 1-to-1 with Commands (e.g., `action.CreateNewUser`)
160+
- **`dto.<something>`** - Data transfer objects between packages/services (e.g., `dto.NewUserInfo`)
161+
- **`entity.<something>`** - Objects mapped to database tables (e.g., `entity.User`)
162+
- **`cmd.<something>`** - Commands that must be executed and potentially return values (e.g., `cmd.HttpRequest`, `cmd.LogDebug`, `cmd.SendMail`, `cmd.CreateNewUser`)
163+
- **`query.<something>`** - Queries to get information from somewhere (e.g., `query.GetUserById`, `query.GetAllPosts`)
164+
165+
### Frontend Structure Conventions
166+
167+
- **Page Organization**: Each page has its own folder under `public/pages/` with:
168+
- `index.ts` - Module exporter
169+
- `[PageName].page.tsx` - Main page component
170+
- `[PageName].page.scss` - Page-specific styles
171+
- `[PageName].page.spec.tsx` - Unit tests
172+
- `./components/` - Page-specific components
173+
174+
### CSS Naming Conventions
175+
176+
Fider uses BEM methodology combined with utility classes:
177+
178+
- **`p-<page_name>`** - HTML ID for each page component (e.g., `p-home`, `p-user-settings`)
179+
- **`c-<component_name>`** - Block class for components (e.g., `c-toggle`)
180+
- **`c-<component_name>__<element>`** - Element classes (e.g., `c-toggle__label`)
181+
- **`c-<component_name>--<state>`** - State modifiers (e.g., `c-toggle--checked`)
182+
- **`is-<state>`, `has-<state>`** - Global state modifiers
183+
- **Utility classes** - No prefix, used for common styling patterns. All utility classes are defined in public/assets/styles/utility/
184+
185+
### General Principles
186+
187+
**Backend:**
188+
189+
- Services are dependency-injected through the bus system
190+
- All external dependencies are abstracted behind interfaces
191+
- Database queries are centralized in query objects
192+
- Handlers focus on HTTP concerns, business logic in services
193+
194+
**Frontend:**
195+
196+
- Page components are lazy-loaded for performance
197+
- Shared components in `components/common/`
198+
- Business logic in services, not components
199+
- Type-safe API calls with proper error handling
200+
201+
## Build and Deployment
202+
203+
**Local Development:**
204+
205+
- `make watch` for development with hot reload
206+
- Webpack dev server for fast UI rebuilds
207+
- Air for Go server hot reload
208+
209+
**Production Build:**
210+
211+
- `make build` creates optimized binaries and assets
212+
- SSR compilation for better SEO and performance
213+
- Asset optimization and minification
214+
215+
This is a mature, production-ready feedback platform with comprehensive testing, i18n support, and a clean, maintainable architecture.

app/actions/image.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package actions
2+
3+
import (
4+
"context"
5+
6+
"github.com/getfider/fider/app/models/dto"
7+
"github.com/getfider/fider/app/models/entity"
8+
"github.com/getfider/fider/app/pkg/validate"
9+
)
10+
11+
// UploadImage is used to upload an image without associating it with a post or comment
12+
type UploadImage struct {
13+
Image *dto.ImageUpload `json:"image"`
14+
}
15+
16+
// Initialize the model
17+
func (input *UploadImage) Initialize() {}
18+
19+
// IsAuthorized returns true if current user is authorized to perform this action
20+
func (input *UploadImage) IsAuthorized(ctx context.Context, user *entity.User) bool {
21+
return user != nil
22+
}
23+
24+
// Validate if current model is valid
25+
func (input *UploadImage) Validate(ctx context.Context, user *entity.User) *validate.Result {
26+
result := validate.Success()
27+
28+
messages, err := validate.ImageUpload(ctx, input.Image, validate.ImageUploadOpts{
29+
IsRequired: true,
30+
MinHeight: 50,
31+
MinWidth: 50,
32+
ExactRatio: false,
33+
MaxKilobytes: 5120,
34+
})
35+
36+
if err != nil {
37+
return validate.Error(err)
38+
}
39+
result.AddFieldFailure("image", messages...)
40+
return result
41+
}

app/actions/post.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,17 @@ func (input *CreateNewPost) OnPreExecute(ctx context.Context) error {
3737
if err := bus.Dispatch(ctx, getTag); err != nil {
3838
break
3939
}
40-
40+
4141
input.Tags = append(input.Tags, getTag.Result)
4242
}
4343
}
44-
44+
4545
return nil
4646
}
4747

4848
// IsAuthorized returns true if current user is authorized to perform this action
4949
func (action *CreateNewPost) IsAuthorized(ctx context.Context, user *entity.User) bool {
50+
5051
if user == nil {
5152
return false
5253
} else if env.Config.PostCreationWithTagsEnabled && !user.IsCollaborator() {
@@ -343,21 +344,18 @@ func (action *EditComment) Validate(ctx context.Context, user *entity.User) *val
343344
}
344345

345346
if len(action.Attachments) > 0 {
346-
getAttachments := &query.GetAttachments{Post: action.Post, Comment: action.Comment}
347-
err := bus.Dispatch(ctx, getAttachments)
348-
if err != nil {
349-
return validate.Error(err)
347+
348+
nonRemovedCount := 0
349+
for _, v := range action.Attachments {
350+
if !v.Remove {
351+
nonRemovedCount++
352+
}
350353
}
351354

352-
messages, err := validate.MultiImageUpload(ctx, getAttachments.Result, action.Attachments, validate.MultiImageUploadOpts{
353-
MaxUploads: 2,
354-
MaxKilobytes: 5120,
355-
ExactRatio: false,
356-
})
357-
if err != nil {
358-
return validate.Error(err)
355+
if nonRemovedCount > 2 {
356+
result.AddFieldFailure("content", i18n.T(ctx, "validation.custom.maxattachments", i18n.Params{"number": 2}))
359357
}
360-
result.AddFieldFailure("attachments", messages...)
358+
361359
}
362360

363361
return result

app/actions/signin.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,22 @@ func (action *SignInByEmail) Validate(ctx context.Context, user *entity.User) *v
5454
return result
5555
}
5656

57-
//GetEmail returns the email being verified
57+
// GetEmail returns the email being verified
5858
func (action *SignInByEmail) GetEmail() string {
5959
return action.Email
6060
}
6161

62-
//GetName returns empty for this kind of process
62+
// GetName returns empty for this kind of process
6363
func (action *SignInByEmail) GetName() string {
6464
return ""
6565
}
6666

67-
//GetUser returns the current user performing this action
67+
// GetUser returns the current user performing this action
6868
func (action *SignInByEmail) GetUser() *entity.User {
6969
return nil
7070
}
7171

72-
//GetKind returns EmailVerificationKindSignIn
72+
// GetKind returns EmailVerificationKindSignIn
7373
func (action *SignInByEmail) GetKind() enum.EmailVerificationKind {
7474
return enum.EmailVerificationKindSignIn
7575
}

app/actions/tenant.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ func (action *UpdateTenantSettings) Validate(ctx context.Context, user *entity.U
177177

178178
// UpdateTenantAdvancedSettings is the input model used to update tenant advanced settings
179179
type UpdateTenantAdvancedSettings struct {
180-
CustomCSS string `json:"customCSS"`
180+
CustomCSS string `json:"customCSS"`
181+
AllowedSchemes string `json:"allowedSchemes"`
181182
}
182183

183184
// IsAuthorized returns true if current user is authorized to perform this action

0 commit comments

Comments
 (0)