7.3 KiB
Executable File
AGENTS.md
Build, Lint, and Test Commands
Backend (Rust)
cd backend && cargo build # Build project
cd backend && cargo test # Run all tests
cd backend && cargo test my_test # Run test matching pattern
cd backend && cargo test -- --exact # Run exact test name
cd backend && cargo test -- --test-threads=1 # Run tests sequentially
cd backend && cargo test -- --nocapture # Show test output
cd backend && cargo test --lib # Library tests only
cd backend && cargo fmt # Format code
cd backend && cargo fmt --check # Check formatting
cd backend && cargo clippy # Lint code
Frontend (React + TypeScript)
cd frontend && npm install # Install dependencies
cd frontend && npm run dev # Start dev server (port 3000)
cd frontend && npm run build # Build for production (tsc + vite)
cd frontend && npm run lint # Lint with ESLint
cd frontend && npx tsc --noEmit # Type check only
cd frontend && npm test # Run Jest tests
cd frontend && npm run test:watch # Run tests in watch mode
Docker
docker-compose up -d # Start all services
docker-compose logs -f backend # View backend logs
docker-compose logs -f frontend # View frontend build/logs
docker-compose down # Stop all services
docker cp frontend/dist/. sap-sync-frontend:/usr/share/nginx/html/ # Update frontend in container
docker exec sap-sync-frontend nginx -s reload # Reload nginx
Code Style Guidelines
Rust Backend
Imports — Alphabetical order, grouped with blank lines: std → external → local. Use full paths: crate::handlers::sync. Avoid wildcard imports.
Naming — Types: PascalCase. Functions: snake_case. Constants: SCREAMING_SNAKE_CASE. Modules: snake_case.
Error Handling — Use thiserror::Error for typed errors. Use ApiError in HTTP handlers. Log with tracing::{info, error} macros. Never use unwrap(); prefer ?, match, or unwrap_or_else(). Convert errors to String for ApiError::Database.
Types — i32 for DB IDs, f64 for decimals. String for owned text, &str for borrowed. Option<T> for nullable, Vec<T> for collections. Arc<T> for shared state.
Handlers — Return impl IntoResponse. Use build_response() helper for consistent JSON responses. Extract DB connections via state.pool.get() with error handling.
Database — Use r2d2 connection pooling. Parameterized queries ($1, $2). Always handle pool errors.
React Frontend
Imports — Alphabetical, grouped: external → lib → components → pages. Named imports preferred.
Formatting — 2-space indent, single quotes, no semicolons, 100-char line limit (Prettier).
Naming — Components: PascalCase. Hooks: useCamelCase. Variables: camelCase.
TypeScript — Strict mode enabled. Avoid any; use unknown or generics. Define interfaces for API responses. Prefer type over interface for unions.
Error Handling — Use try/catch for async ops. Log with logger.error(). Show user feedback via toast.success()/toast.error(). Use MUI Dialogs for confirmations, not window.confirm.
Components — Functional components with hooks. useCallback for handlers, useMemo for expensive calcs. Avoid inline functions in JSX props.
Project Structure
SAP-PLEX-SYNC/
├── backend/src/
│ ├── lib.rs # Library exports
│ ├── main.rs # Entry point (rouille HTTP server)
│ ├── handlers_sync.rs # Sync API handlers (axum - not currently used)
│ ├── handlers.rs # Other HTTP handlers (legacy)
│ ├── models.rs # Data models
│ ├── state.rs # AppState (r2d2 pool)
│ ├── errors.rs # ApiError enum
│ ├── response.rs # Response helpers
│ ├── sync.rs # Sync types & structs
│ ├── plesk_client.rs # Plesk API client
│ ├── sap_client.rs # SAP API client
│ ├── config.rs # Configuration
│ └── ...
├── frontend/src/
│ ├── pages/ # Page components
│ ├── components/ # Reusable components
│ ├── lib/api.ts # API client (apiJson)
│ ├── lib/hooks.ts # usePolling, formatDate
│ ├── lib/logger.ts # Logging utility
│ └── contexts/ # React contexts
├── database/ # Migrations and seeds
└── docker-compose.yml
Cursor Rules (.cursorrules)
- Follow
cargo fmtandcargo clippybefore committing - Use
anyhow::Resultfor error propagation,ApiErrorfor HTTP - Return
impl IntoResponsefor axum handlers (note: main server uses rouille) - Functional components with hooks; avoid inline JSX functions
- Use
useCallback/useMemofor performance - Test user interactions with React Testing Library, not implementation details
- Always check for unsafe
unwrap()calls and replace with proper error handling - Use proper logging instead of
console.login frontend - Keep functions small and focused
- Use meaningful variable and function names
- Add comments for complex logic
API Response Format
Backend returns flat JSON (no data wrapper):
{ "is_running": false, "stats": { "running": 0 }, "jobs": [...] }
For server listings, returns direct array: [{ "id": 1, "name": "test", ... }]
Frontend uses apiJson<T>() from lib/api.ts for typed API calls.
Frontend proxy: /api → http://localhost:3001 (configured in vite.config.ts).
Testing
Backend — Tests in #[cfg(test)] mod tests blocks. Use #[test] for sync, #[tokio::test] for async. Test both happy paths and error cases. Mock external deps with mockall.
To run a specific test: cd backend && cargo test test_function_name -- --exact
To run tests matching a pattern: cd backend && cargo test pattern_name
To run tests sequentially: cd backend && cargo test -- --test-threads=1
To see test output: cd backend && cargo test -- --nocapture
Frontend — Jest + React Testing Library. Use screen.getByRole/getByText. Test user interactions, not implementation. Mock API with jest.mock().
To run frontend tests: cd frontend && npm test
To run tests in watch mode: cd frontend && npm run test:watch
Git Workflow
Commits: type(scope): description (e.g., fix(handlers): resolve API mismatch).
Types: feat, fix, docs, refactor, chore.
Branches: feature/, bugfix/, hotfix/.
Key Reminders
- Build and lint before committing:
cargo build --lib,cargo fmt,cargo clippy,npm run build - Update Docker container after frontend changes:
docker cp frontend/dist/. sap-sync-frontend:/usr/share/nginx/html/ - Test API endpoints with
curl http://localhost/api/...to verify response format - Use
tracing::{info, error}for backend logging,logger.error()for frontend - Never use
unwrap()in production code; use?or proper error handling - Avoid
anyin TypeScript; useunknownor proper types - Use MUI Dialogs for confirmations, not
window.confirm - Use
toast.success()/toast.error()for user feedback, notalert() - Remember that the main HTTP server uses rouille (not axum) in
main.rs - Database column for passwords is named
password_hash, notpassword