Files
b0esche_cloud/docs/DEVELOPMENT.md

672 lines
14 KiB
Markdown
Raw Permalink Normal View History

# b0esche.cloud Development Guide
This guide covers local development setup, coding conventions, and contribution guidelines.
## Prerequisites
### Required Software
| Software | Version | Installation |
|----------|---------|--------------|
| Go | 1.21+ | `brew install go` |
| Flutter | 3.10+ | [flutter.dev](https://flutter.dev/docs/get-started/install) |
| Docker | 24+ | [docker.com](https://docker.com) |
| PostgreSQL | 15+ | `brew install postgresql@15` or Docker |
| Git | 2.x | `brew install git` |
### Recommended Tools
- **VS Code** with extensions:
- Go
- Flutter
- Dart
- Docker
- GitLens
- **TablePlus** or **DBeaver** for database management
- **Postman** or **Bruno** for API testing
## Security Guidelines
### Code Security
- **Never log secrets**: Passwords, tokens, keys must never appear in logs
- **Validate all inputs**: Use `sanitizePath()` for file paths, validate UUIDs
- **Use structured errors**: Return safe error messages that don't leak internal details
- **HTTPS only**: All API calls must use HTTPS in production
- **Input sanitization**: All user inputs must be validated and sanitized
### Authentication
- **JWT tokens**: Use secure, short-lived tokens
- **Session validation**: Always validate sessions against database
- **Passkey security**: Follow WebAuthn best practices
### File Operations
- **Path validation**: Prevent directory traversal with proper path sanitization
- **Permission checks**: Verify user permissions before file operations
- **Scoped access**: Users can only access authorized files/orgs
### Development Security
- **Local secrets**: Use `.env` files, never commit secrets
- **Test with security**: Include security tests in development
- **Review code**: Security review for all changes
## Project Setup
### 1. Clone the Repository
```bash
git clone https://lab.b0esche.cloud/b0esche/b0esche_cloud.git
cd b0esche_cloud
```
### 2. Backend Setup
```bash
cd go_cloud
# Copy environment file
cp .env.example .env
# Edit .env with your local settings
# Key variables to set:
# - DATABASE_URL=postgres://user:pass@localhost:5432/b0esche_dev?sslmode=disable
# - JWT_SECRET=your-dev-secret
# - WEBAUTHN_RP_ID=localhost
# - WEBAUTHN_RP_ORIGIN=http://localhost:8080
```
#### Start PostgreSQL
**Option A: Using Docker (Recommended)**
```bash
docker run -d \
--name b0esche-postgres \
-e POSTGRES_USER=b0esche \
-e POSTGRES_PASSWORD=devpassword \
-e POSTGRES_DB=b0esche_dev \
-p 5432:5432 \
postgres:15-alpine
```
**Option B: Using local PostgreSQL**
```bash
createdb b0esche_dev
```
#### Run Migrations
```bash
# Install goose
go install github.com/pressly/goose/v3/cmd/goose@latest
# Run migrations
goose -dir migrations postgres "$DATABASE_URL" up
```
#### Start Backend
```bash
# Development mode with hot reload
go run ./cmd/api
# Or build and run
go build -o bin/api ./cmd/api
./bin/api
```
The backend will be available at `http://localhost:8080`.
### 3. Frontend Setup
```bash
cd b0esche_cloud
# Get dependencies
flutter pub get
# Run in Chrome (recommended for web development)
flutter run -d chrome
# Or run with specific port
flutter run -d chrome --web-port=3000
```
The frontend will be available at `http://localhost:3000` (or the port shown).
### 4. Quick Start Script
Use the provided development script:
```bash
./scripts/dev-all.sh
```
This starts all services in the correct order.
## Project Structure
### Backend (`go_cloud/`)
```
go_cloud/
├── cmd/
│ └── api/
│ └── main.go # Application entry point
├── internal/
│ ├── auth/
│ │ ├── auth.go # Authentication service
│ │ ├── passkey.go # WebAuthn implementation
│ │ └── auth_test.go # Tests
│ ├── config/
│ │ └── config.go # Configuration loading
│ ├── database/
│ │ └── database.go # Database connection
│ ├── files/
│ │ └── files.go # File operations
│ ├── http/
│ │ ├── routes.go # Route definitions
│ │ ├── server.go # HTTP server setup
│ │ └── wopi_handlers.go # WOPI protocol handlers
│ ├── middleware/
│ │ └── middleware.go # HTTP middleware
│ ├── models/
│ │ └── *.go # Data models
│ ├── org/
│ │ └── org.go # Organization logic
│ ├── storage/
│ │ ├── nextcloud.go # Nextcloud integration
│ │ └── webdav.go # WebDAV client
│ └── ...
├── migrations/
│ ├── 0001_initial.sql
│ ├── 0002_passkeys.sql
│ └── ...
├── pkg/
│ └── jwt/
│ └── jwt.go # JWT utilities
├── .env.example
├── Dockerfile
├── go.mod
└── Makefile
```
### Frontend (`b0esche_cloud/`)
```
b0esche_cloud/
├── lib/
│ ├── main.dart # App entry point
│ ├── injection.dart # Dependency injection
│ ├── blocs/
│ │ ├── auth/
│ │ │ ├── auth_bloc.dart
│ │ │ ├── auth_event.dart
│ │ │ └── auth_state.dart
│ │ ├── files/
│ │ └── org/
│ ├── models/
│ │ ├── user.dart
│ │ ├── file.dart
│ │ └── organization.dart
│ ├── pages/
│ │ ├── home_page.dart
│ │ ├── files_page.dart
│ │ ├── settings_page.dart
│ │ └── admin/
│ ├── repositories/
│ │ ├── auth_repository.dart
│ │ └── file_repository.dart
│ ├── services/
│ │ ├── api_client.dart
│ │ └── webauthn_service.dart
│ ├── theme/
│ │ └── app_theme.dart
│ └── widgets/
│ ├── file_list.dart
│ └── ...
├── web/
│ └── index.html
├── pubspec.yaml
└── analysis_options.yaml
```
## Coding Conventions
### Go Backend
#### Code Style
- Follow [Effective Go](https://golang.org/doc/effective_go)
- Use `gofmt` for formatting
- Use `golint` and `go vet` for linting
```bash
# Format code
gofmt -w .
# Lint
golint ./...
go vet ./...
```
#### Naming Conventions
- **Packages**: lowercase, single word (`auth`, `files`)
- **Exported functions**: PascalCase (`CreateUser`)
- **Private functions**: camelCase (`validateToken`)
- **Constants**: PascalCase (`DefaultTimeout`)
#### Error Handling
```go
// Always handle errors explicitly
user, err := s.GetUser(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
// Use custom error types for API errors
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
}
```
#### Project Patterns
```go
// Service pattern
type AuthService struct {
db *sqlx.DB
config *config.Config
}
func NewAuthService(db *sqlx.DB, config *config.Config) *AuthService {
return &AuthService{db: db, config: config}
}
// Handler pattern
func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) {
// Parse request
// Call service
// Return response
}
```
### Flutter Frontend
#### Code Style
- Follow [Effective Dart](https://dart.dev/guides/language/effective-dart)
- Use `dart format` for formatting
- Use `dart analyze` for linting
```bash
# Format code
dart format .
# Analyze
dart analyze
flutter analyze
```
#### Naming Conventions
- **Classes**: PascalCase (`AuthBloc`)
- **Files**: snake_case (`auth_bloc.dart`)
- **Variables/Functions**: camelCase (`getUserName`)
- **Constants**: camelCase or SCREAMING_CAPS
#### BLoC Pattern
```dart
// Events
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
final String username;
LoginRequested(this.username);
}
// States
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
final User user;
AuthAuthenticated(this.user);
}
class AuthError extends AuthState {
final String message;
AuthError(this.message);
}
// BLoC
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc() : super(AuthInitial()) {
on<LoginRequested>(_onLoginRequested);
}
Future<void> _onLoginRequested(
LoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading());
try {
final user = await _authRepository.login(event.username);
emit(AuthAuthenticated(user));
} catch (e) {
emit(AuthError(e.toString()));
}
}
}
```
#### Widget Structure
```dart
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<MyBloc, MyState>(
builder: (context, state) {
return switch (state) {
MyLoading() => const CircularProgressIndicator(),
MyLoaded(:final data) => _buildContent(data),
MyError(:final message) => Text('Error: $message'),
_ => const SizedBox.shrink(),
};
},
);
}
}
```
## Testing
### Backend Tests
```bash
cd go_cloud
# Run all tests
go test ./...
# Run with coverage
go test -cover ./...
# Run specific package
go test ./internal/auth/...
# Verbose output
go test -v ./...
```
Example test:
```go
func TestAuthService_Login(t *testing.T) {
// Setup
db := setupTestDB(t)
service := NewAuthService(db, testConfig)
// Test
user, err := service.Login(context.Background(), "testuser")
// Assert
assert.NoError(t, err)
assert.Equal(t, "testuser", user.Username)
}
```
### Frontend Tests
```bash
cd b0esche_cloud
# Run all tests
flutter test
# Run with coverage
flutter test --coverage
# Run specific test file
flutter test test/auth_bloc_test.dart
# Run integration tests
flutter test integration_test/
```
Example test:
```dart
void main() {
group('AuthBloc', () {
late AuthBloc authBloc;
late MockAuthRepository mockRepository;
setUp(() {
mockRepository = MockAuthRepository();
authBloc = AuthBloc(authRepository: mockRepository);
});
blocTest<AuthBloc, AuthState>(
'emits [AuthLoading, AuthAuthenticated] on successful login',
build: () => authBloc,
act: (bloc) => bloc.add(LoginRequested('testuser')),
expect: () => [
AuthLoading(),
isA<AuthAuthenticated>(),
],
);
});
}
```
## Database Migrations
### Creating a Migration
```bash
cd go_cloud
# Create new migration
goose -dir migrations create add_new_table sql
```
### Migration Best Practices
```sql
-- migrations/0005_add_feature.sql
-- +goose Up
-- Add new column with default
ALTER TABLE users ADD COLUMN new_field TEXT DEFAULT '';
-- Create index for performance
CREATE INDEX idx_users_new_field ON users(new_field);
-- +goose Down
DROP INDEX IF EXISTS idx_users_new_field;
ALTER TABLE users DROP COLUMN new_field;
```
### Running Migrations
```bash
# Apply all pending migrations
goose -dir migrations postgres "$DATABASE_URL" up
# Rollback last migration
goose -dir migrations postgres "$DATABASE_URL" down
# Check migration status
goose -dir migrations postgres "$DATABASE_URL" status
```
## Debugging
### Backend Debugging
**VS Code launch.json:**
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Go Backend",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/go_cloud/cmd/api",
"envFile": "${workspaceFolder}/go_cloud/.env"
}
]
}
```
**Logging:**
```go
import "log"
log.Printf("User login attempt: %s", username)
```
### Frontend Debugging
**Chrome DevTools:**
- Press F12 in Chrome
- Use the Flutter DevTools extension
**Debug print:**
```dart
debugPrint('Current state: $state');
```
**VS Code launch.json:**
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Flutter Web",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"deviceId": "chrome"
}
]
}
```
## Git Workflow
### Branch Naming
- `feature/description` - New features
- `fix/description` - Bug fixes
- `refactor/description` - Code refactoring
- `docs/description` - Documentation updates
### Commit Messages
Follow conventional commits:
```
type(scope): description
feat(auth): add passkey registration flow
fix(files): correct upload progress display
docs(readme): update deployment instructions
refactor(api): extract common error handling
```
### Pull Request Process
1. Create feature branch from `main`
2. Make changes with atomic commits
3. Run tests locally
4. Push and create PR
5. Wait for review
6. Squash and merge
## Troubleshooting
### Common Issues
#### Backend won't start
```bash
# Check if port is in use
lsof -i :8080
# Check database connection
psql $DATABASE_URL -c "SELECT 1"
# Check logs
go run ./cmd/api 2>&1 | head -50
```
#### Flutter build fails
```bash
# Clean and rebuild
flutter clean
flutter pub get
flutter run -d chrome
# Check for dependency issues
flutter pub deps
```
#### Database migration fails
```bash
# Check current status
goose -dir migrations postgres "$DATABASE_URL" status
# Force specific version
goose -dir migrations postgres "$DATABASE_URL" fix
```
#### WebAuthn not working locally
- WebAuthn requires HTTPS in production
- For localhost, use `WEBAUTHN_RP_ID=localhost`
- Chrome allows WebAuthn on localhost without HTTPS
## Environment Variables Reference
### Backend (.env)
```bash
# Server
SERVER_ADDR=:8080
DEV_MODE=true
# Database
DATABASE_URL=postgres://user:pass@localhost:5432/dbname?sslmode=disable
# Authentication
JWT_SECRET=your-secret-key
WEBAUTHN_RP_ID=localhost
WEBAUTHN_RP_NAME=b0esche.cloud
WEBAUTHN_RP_ORIGIN=http://localhost:8080
# External Services (optional for local dev)
NEXTCLOUD_BASE_URL=https://storage.b0esche.cloud
NEXTCLOUD_USERNAME=admin
NEXTCLOUD_PASSWORD=password
COLLABORA_BASE_URL=https://of.b0esche.cloud
```
### Frontend
API base URL is configured in `lib/services/api_client.dart`:
```dart
class ApiClient {
// For development
static const baseUrl = 'http://localhost:8080';
// For production (set via build args)
// static const baseUrl = String.fromEnvironment('API_URL');
}
```
## Resources
- [Go Documentation](https://golang.org/doc/)
- [Flutter Documentation](https://flutter.dev/docs)
- [WebAuthn Guide](https://webauthn.guide/)
- [BLoC Library](https://bloclibrary.dev/)
- [Chi Router](https://github.com/go-chi/chi)