First off, thank you for considering contributing to EmbedQA! 🎉
It's people like you that make EmbedQA such a great tool for API testing. This document provides guidelines and steps for contributing to the backend repository.
- Code of Conduct
- Getting Started
- How to Contribute
- Development Guidelines
- API Documentation
- Database Migrations
- Community
This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to [maintainer-akash.bhuiyann@gmail.com].
Before you begin, ensure you have the following installed:
- Java 17 or higher - Download
- Maven 3.8+ - Download
- MySQL 8.0+ or PostgreSQL 14+ - For database
- Git - Download
- IDE (recommended):
- IntelliJ IDEA (recommended)
- Eclipse
- VS Code with Java extensions
-
Fork the repository
Click the "Fork" button at the top right of the EmbedQA repository.
-
Clone your fork
git clone https://github.com/YOUR_USERNAME/embedqa.git cd embedqa -
Add upstream remote
git remote add upstream https://github.com/your-org/embedqa.git
-
Set up the database
MySQL:
CREATE DATABASE embedqa; CREATE USER 'embedqa'@'localhost' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON embedqa.* TO 'embedqa'@'localhost'; FLUSH PRIVILEGES;
PostgreSQL:
CREATE DATABASE embedqa; CREATE USER embedqa WITH PASSWORD 'your_password'; GRANT ALL PRIVILEGES ON DATABASE embedqa TO embedqa;
-
Configure application properties
cp src/main/resources/application.example.yml src/main/resources/application.yml
Update
application.ymlwith your database credentials:spring: datasource: url: jdbc:mysql://localhost:3306/embedqa username: embedqa password: your_password jpa: hibernate: ddl-auto: update show-sql: true
-
Build the project
mvn clean install
-
Run the application
mvn spring-boot:run
Or run from your IDE by executing the main class
EmbedQaApplication.java. -
Verify the setup
- Application starts at
http://localhost:8080 - Swagger UI available at
http://localhost:8080/swagger-ui.html - Health check:
curl http://localhost:8080/actuator/health
- Application starts at
Before creating bug reports, please check the existing issues to avoid duplicates.
When creating a bug report, please include:
- Clear title - Descriptive summary of the issue
- Environment details
- Java version
- Spring Boot version
- Database type and version
- Operating system
- Steps to reproduce - Detailed steps to reproduce the behavior
- Expected behavior - What you expected to happen
- Actual behavior - What actually happened
- Logs - Relevant stack traces or error logs
- Request/Response - API request and response (if applicable)
Example:
## Bug Report
**Title:** POST request body not sent when using FORM_DATA type
**Environment:**
- Java: 17.0.9
- Spring Boot: 3.2.1
- MySQL: 8.0.35
- OS: Ubuntu 22.04
**Steps to Reproduce:**
1. Send POST request via `/api/v1/execute`
2. Set bodyType to "FORM_DATA"
3. Include formData array with key-value pairs
4. Execute request
**Expected:** Form data should be sent as application/x-www-form-urlencoded
**Actual:** 415 Unsupported Media Type error
**Request:**
```json
{
"url": "https://httpbin.org/post",
"method": "POST",
"bodyType": "FORM_DATA",
"formData": [{"key": "name", "value": "test", "enabled": true}]
}Error Log:
org.springframework.web.client.HttpClientErrorException$UnsupportedMediaType: 415
### Suggesting Features
Feature requests are welcome! Please check existing issues and discussions before creating a new one.
**When suggesting a feature:**
1. **Use a clear title** - Start with "Feature:" prefix
2. **Describe the problem** - What problem does this solve?
3. **Describe the solution** - How should it work?
4. **API Design** - Proposed endpoints, request/response format
5. **Consider alternatives** - What alternatives have you considered?
### Your First Contribution
Looking for something to work on? Check out:
- Issues labeled [`good first issue`](https://github.com/your-org/embedqa/labels/good%20first%20issue) - Great for newcomers
- Issues labeled [`help wanted`](https://github.com/your-org/embedqa/labels/help%20wanted) - Extra attention needed
- Issues labeled [`documentation`](https://github.com/your-org/embedqa/labels/documentation) - Improve our docs
**First time contributing to open source?** Check out [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/).
### Pull Request Process
1. **Create a branch**
```bash
# Update your local main branch
git checkout main
git pull upstream main
# Create a feature branch
git checkout -b feature/your-feature-name
# Or for bug fixes
git checkout -b fix/issue-number-description
Branch naming conventions:
feature/description- New featuresfix/issue-number-description- Bug fixesdocs/description- Documentation changesrefactor/description- Code refactoringtest/description- Adding tests
-
Make your changes
- Write clean, readable code
- Follow the coding standards
- Add/update tests as needed
- Update documentation if necessary
-
Run tests and checks
# Run all tests mvn test # Run with coverage mvn test jacoco:report # Check code style (if Checkstyle is configured) mvn checkstyle:check
-
Commit your changes
git add . git commit -m "feat: add form data support in API executor"
See Commit Messages for format guidelines.
-
Keep your branch updated
git fetch upstream git rebase upstream/main
-
Push your branch
git push origin feature/your-feature-name
-
Create a Pull Request
- Go to your fork on GitHub
- Click "Compare & pull request"
- Fill in the PR template
- Link related issues using keywords (e.g., "Closes #123")
-
PR Review Process
- Maintainers will review your PR
- Address any requested changes
- Once approved, your PR will be merged
PR Requirements:
- All tests pass
- Code follows style guidelines
- New code has test coverage
- Documentation updated (if applicable)
- Commits follow conventional format
- PR description is complete
- No merge conflicts
We follow Google Java Style Guide with some modifications.
General Rules:
- Use 4 spaces for indentation (no tabs)
- Maximum line length: 120 characters
- Use meaningful variable and method names
- Write self-documenting code
- Add JavaDoc for public APIs
Naming Conventions:
| Type | Convention | Example |
|---------|-------------|----------------------|
| Classes | PascalCase | `ApiExecutorService` |
| Methods | camelCase | `executeRequest()` |
| Variables| camelCase | `requestBody` |
| Constants| UPPER_SNAKE_CASE | `MAX_TIMEOUT` |
| Packages | lowercase | `com.akash.embedqa` |
Code Organization:
public class MyService {
// 1. Static fields
private static final Logger log = LoggerFactory.getLogger(MyService.class);
// 2. Instance fields
private final MyRepository repository;
private final AnotherService anotherService;
// 3. Constructors
public MyService(MyRepository repository, AnotherService anotherService) {
this.repository = repository;
this.anotherService = anotherService;
}
// 4. Public methods
public MyDto findById(Long id) {
return repository.findById(id)
.map(this::mapToDto)
.orElseThrow(() -> new ResourceNotFoundException("Entity", id));
}
// 5. Private methods
private MyDto mapToDto(MyEntity entity) {
// mapping logic
}
}Layered Architecture:
┌─────────────────────────────────────┐
│ Controller Layer │ ← HTTP handling, validation
├─────────────────────────────────────┤
│ Service Layer │ ← Business logic
├─────────────────────────────────────┤
│ Repository Layer │ ← Data access
├─────────────────────────────────────┤
│ Database │
└─────────────────────────────────────┘
Controller Guidelines:
- Handle HTTP concerns only
- Validate input using
@Valid - Return consistent response format (
ApiResult<T>) - Use appropriate HTTP status codes
- Document with Swagger annotations
@RestController
@RequestMapping("/api/v1/collections")
@Tag(name = "Collections", description = "Collection management APIs")
@RequiredArgsConstructor
public class CollectionController {
private final CollectionService collectionService;
@GetMapping("/{id}")
@Operation(summary = "Get collection by ID")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Success"),
@ApiResponse(responseCode = "404", description = "Collection not found")
})
public ApiResult<CollectionDTO> getById(@PathVariable Long id) {
return ApiResult.success(collectionService.findById(id));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Create new collection")
public ApiResult<CollectionDTO> create(@Valid @RequestBody CreateCollectionDTO dto) {
return ApiResult.success(collectionService.create(dto));
}
}Service Guidelines:
- Contain business logic
- Use interfaces for abstraction
- Handle transactions with
@Transactional - Throw custom exceptions for error cases
public interface CollectionService {
CollectionDTO findById(Long id);
CollectionDTO create(CreateCollectionDTO dto);
CollectionDTO update(Long id, UpdateCollectionDTO dto);
void delete(Long id);
}
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class CollectionServiceImpl implements CollectionService {
private final CollectionRepository repository;
@Override
public CollectionDTO findById(Long id) {
log.debug("Finding collection by ID: {}", id);
return repository.findById(id)
.map(this::mapToDto)
.orElseThrow(() -> new ResourceNotFoundException("Collection", id));
}
@Override
@Transactional
public CollectionDTO create(CreateCollectionDTO dto) {
log.debug("Creating collection: {}", dto.getName());
ApiCollection collection = ApiCollection.builder()
.name(dto.getName())
.description(dto.getDescription())
.build();
return mapToDto(repository.save(collection));
}
}Entity Guidelines:
- Use Lombok annotations to reduce boilerplate
- Define relationships carefully
- Use
@Builderfor flexible object creation - Add audit fields (
createdAt,updatedAt)
@Entity
@Table(name = "api_collections")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ApiCollection {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String description;
@OneToMany(mappedBy = "collection", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<ApiRequest> requests = new ArrayList<>();
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
// Helper methods for bidirectional relationships
public void addRequest(ApiRequest request) {
requests.add(request);
request.setCollection(this);
}
public void removeRequest(ApiRequest request) {
requests.remove(request);
request.setCollection(null);
}
}DTO Guidelines:
- Separate DTOs for request and response
- Use validation annotations
- Use records for simple DTOs (Java 17+)
// Request DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CreateCollectionDTO {
@NotBlank(message = "Name is required")
@Size(max = 100, message = "Name must be less than 100 characters")
private String name;
@Size(max = 500, message = "Description must be less than 500 characters")
private String description;
}
// Response DTO (using record)
public record CollectionDTO(
Long id,
String name,
String description,
int requestCount,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {}We use Conventional Commits format:
<type>(<scope>): <subject>
[optional body]
[optional footer]
Types:
feat- New featurefix- Bug fixdocs- Documentation changesstyle- Code style changes (formatting, etc.)refactor- Code refactoringperf- Performance improvementstest- Adding or updating testschore- Maintenance tasks
Scope (optional): Module or area affected
executor,collection,request,environment,history,auth,config
Examples:
feat(executor): add form data support for POST requests
fix(history): resolve pagination returning incorrect total count
docs: add API documentation for environment endpoints
refactor(collection): extract mapping logic to separate class
test(executor): add integration tests for request execution
chore: upgrade Spring Boot to 3.2.1Rules:
- Use imperative mood ("add" not "added" or "adds")
- Don't capitalize first letter
- No period at the end
- Keep subject under 72 characters
- Reference issues in footer:
Closes #123
Test Structure:
src/test/java/com/akash/embedqa/
├── controller/ # Controller/Integration tests
├── service/ # Service unit tests
├── repository/ # Repository tests
Running Tests:
# Run all tests
mvn test
# Run specific test class
mvn test -Dtest=CollectionServiceTest
# Run with coverage report
mvn test jacoco:report
# Report at: target/site/jacoco/index.html
# Run integration tests only
mvn verify -P integration-testUnit Test Guidelines:
- Test one behavior per test method
- Use descriptive test names
- Follow Arrange-Act-Assert pattern
- Mock external dependencies
- Aim for 80%+ code coverage
Example Service Test:
@ExtendWith(MockitoExtension.class)
class CollectionServiceImplTest {
@Mock
private CollectionRepository repository;
@InjectMocks
private CollectionServiceImpl service;
@Test
@DisplayName("Should return collection when found by ID")
void findById_WhenExists_ReturnsCollection() {
// Arrange
Long id = 1L;
ApiCollection collection = ApiCollection.builder()
.id(id)
.name("Test Collection")
.build();
when(repository.findById(id)).thenReturn(Optional.of(collection));
// Act
CollectionDTO result = service.findById(id);
// Assert
assertThat(result).isNotNull();
assertThat(result.id()).isEqualTo(id);
assertThat(result.name()).isEqualTo("Test Collection");
verify(repository).findById(id);
}
@Test
@DisplayName("Should throw exception when collection not found")
void findById_WhenNotExists_ThrowsException() {
// Arrange
Long id = 999L;
when(repository.findById(id)).thenReturn(Optional.empty());
// Act & Assert
assertThatThrownBy(() -> service.findById(id))
.isInstanceOf(ResourceNotFoundException.class)
.hasMessageContaining("Collection")
.hasMessageContaining("999");
}
@Test
@DisplayName("Should create collection successfully")
void create_WithValidData_ReturnsCreatedCollection() {
// Arrange
CreateCollectionDTO dto = new CreateCollectionDTO("New Collection", "Description");
ApiCollection saved = ApiCollection.builder()
.id(1L)
.name(dto.getName())
.description(dto.getDescription())
.build();
when(repository.save(any())).thenReturn(saved);
// Act
CollectionDTO result = service.create(dto);
// Assert
assertThat(result.name()).isEqualTo("New Collection");
verify(repository).save(any(ApiCollection.class));
}
}We use Swagger/OpenAPI for API documentation.
Access Swagger UI:
- Local:
http://localhost:8085/swagger-ui.html - API Docs JSON:
http://localhost:8085/v3/api-docs
Adding Documentation:
@Operation(
summary = "Execute API request",
description = "Executes an HTTP request and returns the response"
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "Request executed successfully",
content = @Content(schema = @Schema(implementation = ApiResponseDTO.class))
),
@ApiResponse(
responseCode = "400",
description = "Invalid request parameters"
)
})
@PostMapping("/execute")
public ApiResult<ApiResponseDTO> execute(@Valid @RequestBody ExecuteRequestDTO request) {
// ...
}If using Flyway for database migrations:
Creating a migration:
# Location: src/main/resources/db/migration/
# Naming: V{version}__{description}.sql
# Example:
V1__create_collections_table.sql
V2__create_requests_table.sql
V3__add_environment_id_to_requests.sqlMigration file example:
-- V3__add_environment_id_to_requests.sql
ALTER TABLE api_requests
ADD COLUMN environment_id BIGINT,
ADD CONSTRAINT fk_request_environment
FOREIGN KEY (environment_id)
REFERENCES environments(id)
ON DELETE SET NULL;Running migrations:
# Migrations run automatically on startup
mvn spring-boot:run
# Or manually
mvn flyway:migrate- Discussions: GitHub Discussions
- Issues: GitHub Issues
If you need help:
- Check the documentation
- Check the API docs
- Search existing issues
- Ask in Discussions
- Create a new issue with the
questionlabel
Contributors are recognized in our README and release notes. We appreciate every contribution, big or small!
By contributing, you agree that your contributions will be licensed under the same license as the project.
Thank you for contributing to EmbedQA! 🙏
Happy coding! ☕