# 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 { AuthBloc() : super(AuthInitial()) { on(_onLoginRequested); } Future _onLoginRequested( LoginRequested event, Emitter 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( 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( 'emits [AuthLoading, AuthAuthenticated] on successful login', build: () => authBloc, act: (bloc) => bloc.add(LoginRequested('testuser')), expect: () => [ AuthLoading(), isA(), ], ); }); } ``` ## 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)