Self-hosted Nostr indexer for NIP-35 torrent events with federated curation
Guide for contributing to Lighthouse development.
git clone https://github.com/gmonarque/lighthouse.git
cd lighthouse
make deps
This installs:
Run backend with hot reload:
make dev
Run frontend dev server (separate terminal):
make dev-frontend
Access at:
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 |
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!",
})
}
internal/api/router.go:r.Get("/example", handlers.GetExample)
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
);
internal/:mkdir internal/myfeature
// internal/myfeature/types.go
package myfeature
type MyType struct {
ID string
Name string
}
// internal/myfeature/service.go
package myfeature
func DoSomething(input string) (*MyType, error) {
// Implementation
}
// internal/myfeature/types_test.go
package myfeature
import "testing"
func TestMyType(t *testing.T) {
// Test implementation
}
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>
web/src/routes/+layout.svelte:const navItems = [
// ... existing items
{ href: '/example', label: 'Example', icon: SomeIcon },
];
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
gofmt for formattinggolint for lintingFollow conventional commits:
feat: add new feature
fix: fix bug
docs: update documentation
refactor: refactor code
test: add tests
chore: maintenance tasks
git checkout -b feature/my-featuremake testmake lintgit push origin feature/my-featureimport "log"
log.Printf("Debug: %v", value)
Or use Delve:
dlv debug ./cmd/lighthouse
console.log('Debug:', value);
Or use browser DevTools.
sqlite3 ./data/lighthouse.db
# View tables
.tables
# Query
SELECT * FROM torrents LIMIT 10;
// 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.
git tag v1.0.0git push origin v1.0.0MIT License - contributions are welcome under the same license.