add-customer and introducing JpaAttempt test helper
This commit is contained in:
parent
03ee2cfd62
commit
2ac476d99b
38
README.md
38
README.md
@ -51,13 +51,20 @@ If you have at least Docker, the Java JDK and Gradle installed in appropriate ve
|
|||||||
|
|
||||||
# the following command should return a JSON array with just all packages visible for the admin of the customer aab:
|
# the following command should return a JSON array with just all packages visible for the admin of the customer aab:
|
||||||
curl \
|
curl \
|
||||||
-H 'current-user: mike@hostsharing.net' \
|
-H 'current-user: mike@hostsharing.net' -H 'assumed-roles: customer#aab.admin' \
|
||||||
-H 'assumed-roles: customer#aab.admin' \
|
|
||||||
http://localhost:8080/api/packages
|
http://localhost:8080/api/packages
|
||||||
|
|
||||||
The latter `curl` command actually goes through the database server.
|
# add a new customer
|
||||||
|
curl \
|
||||||
|
-H 'current-user: mike@hostsharing.net' -H "Content-Type: application/json" \
|
||||||
|
-d '{ "prefix":"baa", "reference":80001, "adminUserName":"admin@baa.example.com" }' \
|
||||||
|
-X POST http://localhost:8080/api/customers
|
||||||
|
|
||||||
<big>ⓘ</big>
|
<big>**ⓘ**</big>
|
||||||
|
'mike@hostsharing.net' and 'sven@hostsharing.net' are Hostsharing hostmaster accounts coming from the example data which is automatically inserted in Testcontainers and Development environments.
|
||||||
|
Also try for example 'admin@aaa.example.com' or 'unknown@example.org'.
|
||||||
|
|
||||||
|
<big>**ⓘ**</big>
|
||||||
If you want a formatted JSON output, you can pipe the result to `jq` or similar.
|
If you want a formatted JSON output, you can pipe the result to `jq` or similar.
|
||||||
|
|
||||||
If you still need to install some of these tools, find some hints in the next chapters.
|
If you still need to install some of these tools, find some hints in the next chapters.
|
||||||
@ -245,3 +252,26 @@ If the persistent database and the temporary database show different results, on
|
|||||||
e.g. from a previous run of tests or manually applied.
|
e.g. from a previous run of tests or manually applied.
|
||||||
It's best to run `pg-sql-reset && gw bootRun` before each test run, to have a clean database.
|
It's best to run `pg-sql-reset && gw bootRun` before each test run, to have a clean database.
|
||||||
|
|
||||||
|
## How to Amend Liquibase SQL Changesets?
|
||||||
|
|
||||||
|
Liquibase changesets are meant to be immutable and based on each other.
|
||||||
|
That means, once a changeset is written, it never changes, not even a whitespace or comment.
|
||||||
|
Liquibase is a *database migration tool*, not a *database initialization tool*.
|
||||||
|
|
||||||
|
This, if you need to add change a table, stored procedure or whatever,
|
||||||
|
create a new changeset and apply `ALTER`, `DROP`, `CREATE OR REPLACE` or whatever SQL commands to perform your changes.
|
||||||
|
These changes will be automatically applied once the application starts up again.
|
||||||
|
This way, any staging or production database will always match the application code.
|
||||||
|
|
||||||
|
But, during initial development that can be a big hassle because the database structure changes a lot in that stage.
|
||||||
|
Also, the actual structure of the database won't be easily recognized anymore through lots of migration changesets.
|
||||||
|
|
||||||
|
Therefore, during initial development, it's good approach just to amend the existing changesets and delete the database:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pg-sql-reset
|
||||||
|
gw bootRun
|
||||||
|
```
|
||||||
|
|
||||||
|
<big>**⚠**</big>
|
||||||
|
Just don't forget switching to the migration mode, once there is a production database!
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package net.hostsharing.hsadminng.errors;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.core.NestedExceptionUtils;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class RestResponseEntityExceptionHandler
|
||||||
|
extends ResponseEntityExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||||
|
protected ResponseEntity<CustomErrorResponse> handleConflict(
|
||||||
|
final RuntimeException exc, final WebRequest request) {
|
||||||
|
|
||||||
|
return new ResponseEntity<>(
|
||||||
|
new CustomErrorResponse(exc, HttpStatus.CONFLICT), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(JpaSystemException.class)
|
||||||
|
protected ResponseEntity<CustomErrorResponse> handleJpaExceptions(
|
||||||
|
final RuntimeException exc, final WebRequest request) {
|
||||||
|
|
||||||
|
return new ResponseEntity<>(
|
||||||
|
new CustomErrorResponse(exc, HttpStatus.FORBIDDEN), HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
class CustomErrorResponse {
|
||||||
|
|
||||||
|
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
|
||||||
|
private final LocalDateTime timestamp;
|
||||||
|
|
||||||
|
private final HttpStatus status;
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public CustomErrorResponse(final RuntimeException exc, final HttpStatus status) {
|
||||||
|
this.timestamp = LocalDateTime.now();
|
||||||
|
this.status = status;
|
||||||
|
this.message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstLine(final String message) {
|
||||||
|
return message.split("\\r|\\n|\\r\\n", 0)[0];
|
||||||
|
}
|
||||||
|
}
|
@ -2,16 +2,14 @@ package net.hostsharing.hsadminng.hscustomer;
|
|||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
|
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
|
||||||
@Controller
|
|
||||||
public class CustomerController {
|
public class CustomerController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -20,18 +18,35 @@ public class CustomerController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private CustomerRepository customerRepository;
|
private CustomerRepository customerRepository;
|
||||||
|
|
||||||
@ResponseBody
|
@GetMapping(value = "/api/customers")
|
||||||
@RequestMapping(value = "/api/customers", method = RequestMethod.GET)
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public List<CustomerEntity> listCustomers(
|
public List<CustomerEntity> listCustomers(
|
||||||
@RequestHeader(value = "current-user") String userName,
|
@RequestHeader(value = "current-user") String userName,
|
||||||
@RequestHeader(value="assumed-roles", required=false) String assumedRoles
|
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles
|
||||||
) {
|
) {
|
||||||
context.setCurrentUser(userName);
|
context.setCurrentUser(userName);
|
||||||
if ( assumedRoles != null && !assumedRoles.isBlank() ) {
|
if (assumedRoles != null && !assumedRoles.isBlank()) {
|
||||||
context.assumeRoles(assumedRoles);
|
context.assumeRoles(assumedRoles);
|
||||||
}
|
}
|
||||||
return customerRepository.findAll();
|
return customerRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/api/customers")
|
||||||
|
@ResponseStatus
|
||||||
|
@Transactional
|
||||||
|
public CustomerEntity addCustomer(
|
||||||
|
@RequestHeader(value = "current-user") String userName,
|
||||||
|
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles,
|
||||||
|
@RequestBody CustomerEntity customer
|
||||||
|
) {
|
||||||
|
context.setCurrentUser(userName);
|
||||||
|
if (assumedRoles != null && !assumedRoles.isBlank()) {
|
||||||
|
context.assumeRoles(assumedRoles);
|
||||||
|
}
|
||||||
|
if (customer.getUuid() == null) {
|
||||||
|
customer.setUuid(UUID.randomUUID());
|
||||||
|
}
|
||||||
|
return customerRepository.save(customer);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hscustomer;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -10,6 +11,7 @@ import java.util.UUID;
|
|||||||
@Entity
|
@Entity
|
||||||
@Table(name = "customer_rv")
|
@Table(name = "customer_rv")
|
||||||
@Getter
|
@Getter
|
||||||
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CustomerEntity {
|
public class CustomerEntity {
|
||||||
|
@ -210,7 +210,7 @@ create or replace function addCustomerNotAllowedForCurrentSubjects()
|
|||||||
language PLPGSQL
|
language PLPGSQL
|
||||||
as $$
|
as $$
|
||||||
begin
|
begin
|
||||||
raise exception 'add-customer not permitted for %', array_to_string(currentSubjects());
|
raise exception 'add-customer not permitted for %', array_to_string(currentSubjects(), ';', 'null');
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user