Guide for contributing to Lighthouse development.
- Go 1.22+
- Node.js 20+
- Make
- Git
git clone https://github.com/gmonarque/lighthouse.git
cd lighthousemake depsThis installs:
- Go dependencies
- Node.js packages
- Air (hot reload)
Run backend with hot reload:
make devRun frontend dev server (separate terminal):
make dev-frontendAccess at:
- Backend: http://localhost:9999
- Frontend Dev: http://localhost:5173 (proxies to backend)
lighthouse/
├── cmd/
│ └── lighthouse/
│ └── main.go # Entry point
├── internal/
│ ├── api/
│ │ ├── handlers/ # HTTP handlers
│ │ ├── middleware/ # Auth, logging
│ │ ├── router.go # Route definitions
│ │ └── static/ # Embedded frontend
│ ├── comments/ # Comment system
│ ├── config/ # Configuration
│ ├── curator/ # Curation engine
│ ├── database/
│ │ ├── database.go # SQLite connection
│ │ ├── migrations/ # SQL migrations
│ │ └── queries/ # SQL files
│ ├── decision/ # Verification decisions
│ ├── explorer/ # Relay exploration
│ ├── indexer/ # Core indexer
│ ├── models/ # Shared types
│ ├── moderation/ # Reports/appeals
│ ├── nostr/ # Nostr client
│ ├── relay/ # Relay server
│ ├── ruleset/ # Rule engine
│ ├── torznab/ # Torznab API
│ └── trust/ # Trust system
├── web/
│ ├── src/
│ │ ├── lib/
│ │ │ ├── api/ # API client
│ │ │ ├── components/ # Svelte components
│ │ │ └── stores/ # State management
│ │ └── routes/ # SvelteKit pages
│ ├── static/ # Static assets
│ └── package.json
├── docs/ # Documentation
├── Makefile
├── go.mod
└── config.yaml
| Command | Description |
|---|---|
make deps |
Install all dependencies |
make build |
Production build |
make dev |
Backend with hot reload |
make dev-frontend |
Frontend dev server |
make test |
Run all tests |
make lint |
Run linters |
make clean |
Clean build artifacts |
make docker |
Build Docker image |
- Create handler in
internal/api/handlers/:
// internal/api/handlers/example.go
package handlers
import (
"net/http"
)
func GetExample(w http.ResponseWriter, r *http.Request) {
respondJSON(w, http.StatusOK, map[string]string{
"message": "Hello, World!",
})
}- Register route in
internal/api/router.go:
r.Get("/example", handlers.GetExample)- Create SQL file in
internal/database/migrations/:
-- internal/database/migrations/003_example.sql
CREATE TABLE IF NOT EXISTS examples (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);- Migrations run automatically on startup.
- Create directory under
internal/:
mkdir internal/myfeature- Create types:
// internal/myfeature/types.go
package myfeature
type MyType struct {
ID string
Name string
}- Create logic:
// internal/myfeature/service.go
package myfeature
func DoSomething(input string) (*MyType, error) {
// Implementation
}- Add tests:
// internal/myfeature/types_test.go
package myfeature
import "testing"
func TestMyType(t *testing.T) {
// Test implementation
}- Create route in
web/src/routes/:
<!-- web/src/routes/example/+page.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
let data = [];
onMount(async () => {
// Fetch data
});
</script>
<div class="page-header">
<h1>Example Page</h1>
</div>
<div class="page-content">
<!-- Content -->
</div>- Add to navigation in
web/src/routes/+layout.svelte:
const navItems = [
// ... existing items
{ href: '/example', label: 'Example', icon: SomeIcon },
];- Add to
web/src/lib/api/client.ts:
export interface ExampleResponse {
id: string;
name: string;
}
class APIClient {
// ... existing methods
async getExample(): Promise<ExampleResponse> {
return this.request<ExampleResponse>('/example');
}
async createExample(data: { name: string }): Promise<ExampleResponse> {
return this.request<ExampleResponse>('/example', {
method: 'POST',
body: JSON.stringify(data)
});
}
}Use Tailwind CSS classes. Custom styles in web/src/app.css.
Common patterns:
card- Card containerbtn-primary,btn-secondary- Buttonsinput- Form inputsmodal,modal-backdrop- Modalspage-header,page-content- Page layout
# All tests
make test
# Specific package
go test ./internal/ruleset/...
# With coverage
go test -cover ./...
# Verbose
go test -v ./...// internal/example/example_test.go
package example
import (
"testing"
)
func TestSomething(t *testing.T) {
// Arrange
input := "test"
// Act
result := DoSomething(input)
// Assert
if result != expected {
t.Errorf("expected %v, got %v", expected, result)
}
}
func TestTableDriven(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"case 1", "input1", "expected1"},
{"case 2", "input2", "expected2"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := DoSomething(tt.input)
if result != tt.expected {
t.Errorf("expected %v, got %v", tt.expected, result)
}
})
}
}cd web
npm run test- Follow standard Go conventions
- Use
gofmtfor formatting - Use
golintfor linting - Prefer explicit over implicit
- Handle errors properly
- Use TypeScript strict mode
- Follow Svelte 5 patterns (runes)
- Use Tailwind for styling
- Keep components small and focused
Follow conventional commits:
feat: add new feature
fix: fix bug
docs: update documentation
refactor: refactor code
test: add tests
chore: maintenance tasks
- Fork the repository
- Create branch:
git checkout -b feature/my-feature - Make changes
- Add tests
- Run tests:
make test - Run lints:
make lint - Commit with conventional commit message
- Push:
git push origin feature/my-feature - Create PR with description
- Tests pass
- Lints pass
- Documentation updated
- No breaking changes (or documented)
- Follows code style
import "log"
log.Printf("Debug: %v", value)Or use Delve:
dlv debug ./cmd/lighthouseconsole.log('Debug:', value);Or use browser DevTools.
sqlite3 ./data/lighthouse.db
# View tables
.tables
# Query
SELECT * FROM torrents LIMIT 10;- Handlers - HTTP request/response only
- Services - Business logic
- Storage - Database operations
- Types - Data structures
// Return errors, don't panic
func DoSomething() error {
if err := operation(); err != nil {
return fmt.Errorf("operation failed: %w", err)
}
return nil
}// Pass context for cancellation
func DoSomething(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
// Continue
}
}// DoSomething performs an important operation.
// It takes an input string and returns a result.
//
// Example:
//
// result, err := DoSomething("input")
// if err != nil {
// // handle error
// }
func DoSomething(input string) (string, error) {
// Implementation
}Update docs/api-reference.md for new endpoints.
Update relevant docs in docs/ folder.
- Update version in relevant files
- Update CHANGELOG.md
- Create git tag:
git tag v1.0.0 - Push tag:
git push origin v1.0.0 - GitHub Actions builds releases
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Nostr: Follow project npub
MIT License - contributions are welcome under the same license.