13 KiB
13 KiB
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 |
| Docker | 24+ | 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
Project Setup
1. Clone the Repository
git clone https://lab.b0esche.cloud/b0esche/b0esche_cloud.git
cd b0esche_cloud
2. Backend Setup
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)
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
createdb b0esche_dev
Run Migrations
# Install goose
go install github.com/pressly/goose/v3/cmd/goose@latest
# Run migrations
goose -dir migrations postgres "$DATABASE_URL" up
Start Backend
# 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
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:
./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
- Use
gofmtfor formatting - Use
golintandgo vetfor linting
# 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
// 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
// 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
- Use
dart formatfor formatting - Use
dart analyzefor linting
# 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
// 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
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
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:
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
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:
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
cd go_cloud
# Create new migration
goose -dir migrations create add_new_table sql
Migration Best Practices
-- 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
# 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:
{
"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:
import "log"
log.Printf("User login attempt: %s", username)
Frontend Debugging
Chrome DevTools:
- Press F12 in Chrome
- Use the Flutter DevTools extension
Debug print:
debugPrint('Current state: $state');
VS Code launch.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 featuresfix/description- Bug fixesrefactor/description- Code refactoringdocs/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
- Create feature branch from
main - Make changes with atomic commits
- Run tests locally
- Push and create PR
- Wait for review
- Squash and merge
Troubleshooting
Common Issues
Backend won't start
# 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
# Clean and rebuild
flutter clean
flutter pub get
flutter run -d chrome
# Check for dependency issues
flutter pub deps
Database migration fails
# 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)
# 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:
class ApiClient {
// For development
static const baseUrl = 'http://localhost:8080';
// For production (set via build args)
// static const baseUrl = String.fromEnvironment('API_URL');
}