Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,97 @@ Just add the dependency. Swagger UI automatically shows:

Works with JPA, MongoDB, and Predicate modules.

## Pagination, Sorting and Field Selection

The `page-sort` module provides annotations for pagination, sorting, and field selection.

```xml
<dependency>
<groupId>com.turkraft.springfilter</groupId>
<artifactId>page-sort</artifactId>
<version>3.2.2</version>
</dependency>
```

### Basic Usage

```java
@GetMapping("/cars")
Page<Car> search(@Filter Specification<Car> spec, @Page Pageable page) {
return repository.findAll(spec, page);
}
```

Usage: `?page=0&size=20&sort=-year` (prefix `-` for descending)

### Custom Parameter Names

```java
@GetMapping("/cars")
Page<Car> search(
@Page(pageParameter = "p", sizeParameter = "limit", sortParameter = "order") Pageable page) {
return repository.findAll(page);
}
```

Now use `?p=0&limit=50&order=-year`

### Sort Parameter

```java
@GetMapping("/cars")
List<Car> search(@Sort org.springframework.data.domain.Sort sort) {
return repository.findAll(sort);
}
```

Use `?sort=-year` or `?sort=-year,name`

### Field Selection

```java
@Fields
@GetMapping("/cars")
List<Car> search() {
return repository.findAll();
}
```

Use `?fields=id,brand.name,year` to return only specified fields. Uses Jackson's filtering internally.

```java
// Include specific fields
?fields= id,name,email

// Exclude fields
?fields= *,-password,-ssn

// Nested fields
?fields= id,brand.name,brand.country

// Wildcards
?fields= user.*
```

### Combined Example

```java
@Fields
@GetMapping("/cars")
Page<Car> search(
@Filter Specification<Car> spec,
@Page Pageable page) {
return repository.findAll(spec, page);
}
```

Use all features together:
```
/cars?filter=year>2020&page=0&size=20&sort=-year&fields=id,brand.name,year
```

The `openapi` module automatically generates documentation for these parameters when both dependencies are present.

## Frontend Integration

### JavaScript
Expand Down
6 changes: 6 additions & 0 deletions jpa-example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
<version>${revision}</version>
</dependency>

<dependency>
<groupId>com.turkraft.springfilter</groupId>
<artifactId>page-sort</artifactId>
<version>${revision}</version>
</dependency>

<dependency>
<groupId>com.turkraft.springfilter</groupId>
<artifactId>openapi</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.turkraft.springfilter.example;

import com.github.javafaker.Faker;
import com.turkraft.springfilter.boot.Fields;
import com.turkraft.springfilter.boot.Filter;
import com.turkraft.springfilter.boot.Page;
import com.turkraft.springfilter.converter.FilterSpecification;
import com.turkraft.springfilter.example.model.Address;
import com.turkraft.springfilter.example.model.Company;
Expand All @@ -28,6 +30,7 @@
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

Expand Down Expand Up @@ -59,19 +62,33 @@ public void run(String... args) {
List<Industry> industries = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Industry industry = new Industry();
industry.setName(faker.company().industry());
industry.setName(faker
.company()
.industry());
industries.add(industry);
}
industryRepository.saveAll(industries);

List<Company> companies = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Company company = new Company();
company.setName(faker.company().name());
company.setIndustry(faker.options().nextElement(industries));
company.setName(faker
.company()
.name());
company.setIndustry(faker
.options()
.nextElement(industries));
company.setWebsites(
Stream.generate(() -> Map.entry(faker.company().buzzword(), faker.company().url()))
.limit(3).skip(faker.random().nextInt(0, 3))
Stream
.generate(() -> Map.entry(faker
.company()
.buzzword(), faker
.company()
.url()))
.limit(3)
.skip(faker
.random()
.nextInt(0, 3))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue)));
companies.add(company);
}
Expand All @@ -80,21 +97,48 @@ public void run(String... args) {
List<Employee> employees = new ArrayList<>();
for (int i = 0; i < 30; i++) {
Employee employee = new Employee();
employee.setFirstName(faker.name().firstName());
employee.setLastName(faker.name().lastName());
employee.setBirthDate(faker.date().birthday());
employee.setMaritalStatus(faker.options().option(MaritalStatus.class));
employee.setSalary(faker.random().nextInt(1000, 10000));
employee.setCompany(faker.options().nextElement(companies));
employee.setManager(employees.isEmpty() ? null : faker.options().nextElement(employees));
employee.setFirstName(faker
.name()
.firstName());
employee.setLastName(faker
.name()
.lastName());
employee.setBirthDate(faker
.date()
.birthday());
employee.setMaritalStatus(faker
.options()
.option(MaritalStatus.class));
employee.setSalary(faker
.random()
.nextInt(1000, 10000));
employee.setCompany(faker
.options()
.nextElement(companies));
employee.setManager(employees.isEmpty() ? null : faker
.options()
.nextElement(employees));
employee.setChildren(
Stream.generate(faker.name()::firstName).limit(5).skip(faker.random().nextInt(0, 5))
Stream
.generate(faker.name()::firstName)
.limit(5)
.skip(faker
.random()
.nextInt(0, 5))
.collect(Collectors.toList()));
employee.setAddress(new Address() {{
setCity(faker.address().city());
setCountry(faker.address().country());
setPostalCode(faker.address().zipCode());
setStreetAndNumber(faker.address().streetAddress());
setCity(faker
.address()
.city());
setCountry(faker
.address()
.country());
setPostalCode(faker
.address()
.zipCode());
setStreetAndNumber(faker
.address()
.streetAddress());
}});
employees.add(employee);
}
Expand All @@ -103,8 +147,12 @@ public void run(String... args) {
List<Payslip> payslips = new ArrayList<>();
for (int i = 0; i < 50; i++) {
Payslip payslip = new Payslip();
payslip.setEmployee(faker.options().nextElement(employees));
payslip.setDate(faker.date().past(360, TimeUnit.DAYS));
payslip.setEmployee(faker
.options()
.nextElement(employees));
payslip.setDate(faker
.date()
.past(360, TimeUnit.DAYS));
payslips.add(payslip);
}
payslipRepository.saveAll(payslips);
Expand All @@ -113,32 +161,49 @@ public void run(String... args) {

@Operation(hidden = true)
@GetMapping("/")
public void index(HttpServletResponse response) throws IOException {
public void index(HttpServletResponse response)
throws IOException {
response.sendRedirect("swagger-ui.html");
}

// With springfilter-openapi module, @Filter parameters are automatically documented!
// No need for manual @Operation and @Parameter annotations.
// The documentation, examples, and schema are generated automatically from the entity class.

@GetMapping(value = "industry")
public List<Industry> getIndustries(@Filter FilterSpecification<Industry> filter) {
return industryRepository.findAll(filter);
@Fields
public List<Industry> getIndustries(
@Filter FilterSpecification<Industry> filter,
@Page Pageable pageable) {
return industryRepository
.findAll(filter, pageable)
.getContent();
}

@GetMapping(value = "company")
public List<Company> getCompanies(@Filter FilterSpecification<Company> filter) {
return companyRepository.findAll(filter);
@Fields
public List<Company> getCompanies(
@Filter FilterSpecification<Company> filter,
@Page Pageable pageable) {
return companyRepository
.findAll(filter, pageable)
.getContent();
}

@GetMapping(value = "employee")
public List<Employee> getEmployees(@Filter FilterSpecification<Employee> filter) {
return employeeRepository.findAll(filter);
@Fields
public List<Employee> getEmployees(
@Filter FilterSpecification<Employee> filter,
@Page Pageable pageable) {
return employeeRepository
.findAll(filter, pageable)
.getContent();
}

@GetMapping(value = "payslip")
public List<Payslip> getPayslips(@Filter FilterSpecification<Payslip> filter) {
return payslipRepository.findAll(filter);
@Fields
public List<Payslip> getPayslips(
@Filter FilterSpecification<Payslip> filter,
@Page Pageable pageable) {
return payslipRepository
.findAll(filter, pageable)
.getContent();
}

}
6 changes: 6 additions & 0 deletions mongo-example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
<version>${revision}</version>
</dependency>

<dependency>
<groupId>com.turkraft.springfilter</groupId>
<artifactId>page-sort</artifactId>
<version>${revision}</version>
</dependency>

<dependency>
<groupId>com.turkraft.springfilter</groupId>
<artifactId>openapi</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.turkraft.springfilter.example;

import com.github.javafaker.Faker;
import com.turkraft.springfilter.boot.Fields;
import com.turkraft.springfilter.boot.Filter;
import com.turkraft.springfilter.boot.Page;
import com.turkraft.springfilter.example.model.Company;
import com.turkraft.springfilter.example.model.Employee;
import com.turkraft.springfilter.example.model.Employee.MaritalStatus;
import com.turkraft.springfilter.example.model.Industry;
import com.turkraft.springfilter.example.model.Payslip;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -24,8 +25,10 @@
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

Expand Down Expand Up @@ -177,27 +180,43 @@ public void index(HttpServletResponse response)
}

@GetMapping(value = "industry")
@Fields
public List<Industry> getIndustries(
@Parameter(hidden = true) @Filter(entityClass = Industry.class) Document filter) {
return mongoTemplate.find(new BasicQuery(filter), Industry.class);
@Filter(entityClass = Industry.class) Document filter,
@Page Pageable pageable) {
Query query = new BasicQuery(filter);
query.with(pageable);
return mongoTemplate.find(query, Industry.class);
}

@GetMapping(value = "company")
@Fields
public List<Company> getCompanies(
@Parameter(hidden = true) @Filter(entityClass = Company.class) Document filter) {
return mongoTemplate.find(new BasicQuery(filter), Company.class);
@Filter(entityClass = Company.class) Document filter,
@Page Pageable pageable) {
Query query = new BasicQuery(filter);
query.with(pageable);
return mongoTemplate.find(query, Company.class);
}

@GetMapping(value = "employee")
@Fields
public List<Employee> getEmployees(
@Parameter(hidden = true) @Filter(entityClass = Employee.class) Document filter) {
return mongoTemplate.find(new BasicQuery(filter), Employee.class);
@Filter(entityClass = Employee.class) Document filter,
@Page Pageable pageable) {
Query query = new BasicQuery(filter);
query.with(pageable);
return mongoTemplate.find(query, Employee.class);
}

@GetMapping(value = "payslip")
@Fields
public List<Payslip> getPayslips(
@Parameter(hidden = true) @Filter(entityClass = Payslip.class) Document filter) {
return mongoTemplate.find(new BasicQuery(filter), Payslip.class);
@Filter(entityClass = Payslip.class) Document filter,
@Page Pageable pageable) {
Query query = new BasicQuery(filter);
query.with(pageable);
return mongoTemplate.find(query, Payslip.class);
}

}
Loading