1
0

API-first with openapiprocessor and modelmapper

This commit is contained in:
Michael Hoennig
2022-08-08 16:54:35 +02:00
parent 80f342eeae
commit eeab68d63a
14 changed files with 602 additions and 62 deletions

View File

@ -0,0 +1,27 @@
package net.hostsharing.hsadminng;
import org.modelmapper.ModelMapper;
import java.util.List;
import java.util.stream.Collectors;
/**
* A nicer API for ModelMapper.
*
* MOst
*/
public class Mapper {
private final static ModelMapper modelMapper = new ModelMapper();
public static <S, T> List<T> mapList(final List<S> source, final Class<T> targetClass) {
return source
.stream()
.map(element -> modelMapper.map(element, targetClass))
.collect(Collectors.toList());
}
public static <S, T> T map(final S source, final Class<T> targetClass) {
return modelMapper.map(source, targetClass);
}
}

View File

@ -1,16 +1,22 @@
package net.hostsharing.hsadminng.hs.hscustomer;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.generated.api.v1.api.CustomersApi;
import net.hostsharing.hsadminng.generated.api.v1.model.CustomerResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import javax.transaction.Transactional;
import java.util.List;
import java.util.UUID;
import static net.hostsharing.hsadminng.Mapper.map;
import static net.hostsharing.hsadminng.Mapper.mapList;
@RestController
public class CustomerController {
public class CustomerController implements CustomersApi {
@Autowired
private Context context;
@ -18,34 +24,40 @@ public class CustomerController {
@Autowired
private CustomerRepository customerRepository;
@GetMapping(value = "/api/customers")
@Override
@Transactional
public List<CustomerEntity> listCustomers(
@RequestHeader(value = "current-user") String userName,
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles,
@RequestParam(required = false) String prefix
public ResponseEntity<List<CustomerResource>> listCustomers(
String userName,
String assumedRoles,
String prefix
) {
context.setCurrentUser(userName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
return customerRepository.findCustomerByOptionalPrefixLike(prefix);
return ResponseEntity.ok(
mapList(
customerRepository.findCustomerByOptionalPrefixLike(prefix),
CustomerResource.class));
}
@PostMapping(value = "/api/customers")
@Override
@Transactional
public CustomerEntity addCustomer(
@RequestHeader(value = "current-user") String userName,
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles,
@RequestBody CustomerEntity customer
) {
context.setCurrentUser(userName);
public ResponseEntity<CustomerResource> addCustomer(
final String currentUser,
final String assumedRoles,
final CustomerResource customer) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
if (customer.getUuid() == null) {
customer.setUuid(UUID.randomUUID());
}
return customerRepository.save(customer);
return ResponseEntity.ok(
map(
customerRepository.save(map(customer, CustomerEntity.class)),
CustomerResource.class));
}
}

View File

@ -1,14 +1,19 @@
package net.hostsharing.hsadminng.hs.hspackage;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.generated.api.v1.api.PackagesApi;
import net.hostsharing.hsadminng.generated.api.v1.model.PackageResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import javax.transaction.Transactional;
import java.util.List;
import static net.hostsharing.hsadminng.Mapper.mapList;
@RestController
public class PackageController {
public class PackageController implements PackagesApi {
@Autowired
private Context context;
@ -16,18 +21,19 @@ public class PackageController {
@Autowired
private PackageRepository packageRepository;
@RequestMapping(value = "/api/packages", method = RequestMethod.GET)
@Override
@Transactional
public List<PackageEntity> listPackages(
@RequestHeader(value = "current-user") String userName,
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles,
@RequestParam(required = false) String name
public ResponseEntity<List<PackageResource>> listPackages(
String userName,
String assumedRoles,
String name
) {
context.setCurrentUser(userName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
return packageRepository.findAllByOptionalNameLike(name);
final var result = packageRepository.findAllByOptionalNameLike(name);
return ResponseEntity.ok(mapList(result, PackageResource.class));
}
}

View File

@ -1,16 +1,20 @@
package net.hostsharing.hsadminng.rbac.rbacrole;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.generated.api.v1.api.RbacrolesApi;
import net.hostsharing.hsadminng.generated.api.v1.model.RbacRoleResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import javax.transaction.Transactional;
import java.util.List;
import static net.hostsharing.hsadminng.Mapper.mapList;
@RestController
public class RbacRoleController {
public class RbacRoleController implements RbacrolesApi {
@Autowired
private Context context;
@ -18,17 +22,13 @@ public class RbacRoleController {
@Autowired
private RbacRoleRepository rbacRoleRepository;
@GetMapping(value = "/api/rbacroles")
@Override
@Transactional
public Iterable<RbacRoleEntity> listCustomers(
@RequestHeader(value = "current-user") String userName,
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles
) {
context.setCurrentUser(userName);
public ResponseEntity<List<RbacRoleResource>> listRoles(final String currentUser, final String assumedRoles) {
context.setCurrentUser(currentUser);
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
return rbacRoleRepository.findAll();
return ResponseEntity.ok(mapList(rbacRoleRepository.findAll(), RbacRoleResource.class));
}
}

View File

@ -6,14 +6,20 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.generated.api.v1.api.RbacusersApi;
import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserPermissionResource;
import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
import java.util.List;
import static net.hostsharing.hsadminng.Mapper.mapList;
@RestController
public class RbacUserController {
public class RbacUserController implements RbacusersApi {
@Autowired
private Context context;
@ -21,18 +27,9 @@ public class RbacUserController {
@Autowired
private RbacUserRepository rbacUserRepository;
@GetMapping(value = "/api/rbacusers")
@Operation(description = "List accessible RBAC users with optional filter by name.",
responses = {
@ApiResponse(responseCode = "200",
content = @Content(array = @ArraySchema(
schema = @Schema(implementation = RbacUserEntity.class)))),
@ApiResponse(responseCode = "401",
description = "if the 'current-user' cannot be identified"),
@ApiResponse(responseCode = "403",
description = "if the 'current-user' is not allowed to assume any of the roles from 'assumed-roles'") })
@Override
@Transactional
public List<RbacUserEntity> listUsers(
public ResponseEntity<List<RbacUserResource>> listUsers(
@RequestHeader(name = "current-user") String currentUserName,
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles,
@RequestParam(name="name", required = false) String userName
@ -41,19 +38,12 @@ public class RbacUserController {
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
return rbacUserRepository.findByOptionalNameLike(userName);
return ResponseEntity.ok(mapList(rbacUserRepository.findByOptionalNameLike(userName), RbacUserResource.class));
}
@GetMapping(value = "/api/rbacuser/{userName}/permissions")
@Operation(description = "List all visible permissions granted to the given user; reduced ", responses = {
@ApiResponse(responseCode = "200",
content = @Content(array = @ArraySchema( schema = @Schema(implementation = RbacUserPermission.class)))),
@ApiResponse(responseCode = "401",
description = "if the 'current-user' cannot be identified"),
@ApiResponse(responseCode = "403",
description = "if the 'current-user' is not allowed to view permissions of the given user") })
@Override
@Transactional
public List<RbacUserPermission> listUserPermissions(
public ResponseEntity<List<RbacUserPermissionResource>> listUserPermissions(
@RequestHeader(name = "current-user") String currentUserName,
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles,
@PathVariable(name= "userName") String userName
@ -62,6 +52,6 @@ public class RbacUserController {
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
return rbacUserRepository.findPermissionsOfUser(userName);
return ResponseEntity.ok(mapList(rbacUserRepository.findPermissionsOfUser(userName), RbacUserPermissionResource.class));
}
}

View File

@ -0,0 +1,306 @@
openapi: 3.0.1
info:
title: Hostsharing hsadmin-ng API
version: v0
servers:
- url: http://localhost:8080
description: Local development default URL.
paths:
/api/customers:
get:
summary: Returns a list of (optionally filtered) customers.
description: Returns the list of (optionally filtered) customers which are visible to the current user or any of it's assumed roles.
tags:
- customers
operationId: listCustomers
parameters:
- $ref: '#/components/parameters/currentUser'
- $ref: '#/components/parameters/assumedRoles'
- name: prefix
in: query
required: false
schema:
type: string
description: Customer-prefix to filter the results.
responses:
"200":
description: OK
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/Customer'
"401":
description: Not Authorized
"403":
description: Forbidden
post:
summary: Adds a new customer.
tags:
- customers
operationId: addCustomer
parameters:
- $ref: '#/components/parameters/currentUser'
- $ref: '#/components/parameters/assumedRoles'
requestBody:
content:
'application/json':
schema:
$ref: '#/components/schemas/Customer'
required: true
responses:
"200":
description: OK
content:
'application/json':
schema:
$ref: '#/components/schemas/Customer'
/api/rbac-users:
get:
tags:
- rbacusers
description: List accessible RBAC users with optional filter by name.
operationId: listUsers
parameters:
- $ref: '#/components/parameters/currentUser'
- $ref: '#/components/parameters/assumedRoles'
- name: name
in: query
required: false
schema:
type: string
responses:
"200":
description: OK
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/RbacUser'
"401":
description: if the 'current-user' cannot be identified
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/RbacUser'
"403":
description: if the 'current-user' is not allowed to assume any of the roles
from 'assumed-roles'
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/RbacUser'
/api/rbac-users/{userName}/permissions:
get:
tags:
- rbacusers
description: 'List all visible permissions granted to the given user; reduced '
operationId: listUserPermissions
parameters:
- $ref: '#/components/parameters/currentUser'
- $ref: '#/components/parameters/assumedRoles'
- name: userName
in: path
required: true
schema:
type: string
responses:
"200":
description: OK
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/RbacUserPermission'
"401":
$ref: '#/components/responses/Unauthorized'
"403":
$ref: '#/components/responses/Forbidden'
/api/rbac-roles:
get:
tags:
- rbacroles
operationId: listRoles
parameters:
- $ref: '#/components/parameters/currentUser'
- $ref: '#/components/parameters/assumedRoles'
responses:
"200":
description: OK
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/RbacRole'
/api/ping:
get:
tags:
- test
operationId: ping
responses:
"200":
description: OK
content:
'application/json':
schema:
type: string
/api/packages:
get:
tags:
- packages
operationId: listPackages
parameters:
- $ref: '#/components/parameters/currentUser'
- $ref: '#/components/parameters/assumedRoles'
- name: name
in: query
required: false
schema:
type: string
responses:
"200":
description: OK
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/Package'
components:
parameters:
currentUser:
name: current-user
in: header
required: true
schema:
type: string
description: Identifying name of the currently logged in user.
assumedRoles:
name: assumed-roles
in: header
required: false
schema:
type: string
description: Semicolon-separated list of roles to assume. The current user needs to have the right to assume these roles.
responses:
NotFound:
description: The specified was not found.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: The current user is unknown or not authorized.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Forbidden:
description: The current user or none of the assumed or roles is granted access to the .
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
Customer:
type: object
properties:
uuid:
type: string
format: uuid
prefix:
type: string
reference:
type: integer
format: int32
adminUserName:
type: string
RbacUser:
type: object
properties:
uuid:
type: string
format: uuid
name:
type: string
RbacUserPermission:
type: object
properties:
objectUuid:
type: string
format: uuid
objectTable:
type: string
objectIdName:
type: string
roleName:
type: string
roleUuid:
type: string
format: uuid
permissionUuid:
type: string
format: uuid
op:
type: string
RbacRole:
type: object
properties:
uuid:
type: string
format: uuid
objectUuid:
type: string
format: uuid
objectTable:
type: string
objectIdName:
type: string
roleType:
type: string
enum:
- owner
- admin
- tenant
roleName:
type: string
Package:
type: object
properties:
uuid:
type: string
format: uuid
name:
type: string
customer:
$ref: '#/components/schemas/Customer'
Error:
type: object
properties:
code:
type: string
message:
type: string
required:
- code
- message

View File

@ -0,0 +1,14 @@
openapi-processor-mapping: v2
options:
package-name: net.hostsharing.hsadminng.generated.api.v1
model-name-suffix: Resource
map:
result: org.springframework.http.ResponseEntity
types:
- type: array => java.util.List
- type: string:uuid => java.util.UUID

View File

@ -36,7 +36,7 @@ class RbacRoleControllerRestTest {
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/rbacroles")
.get("/api/rbac-roles")
.header("current-user", "mike@hostsharing.net")
.accept(MediaType.APPLICATION_JSON))

View File

@ -12,6 +12,8 @@ import org.springframework.orm.jpa.JpaSystemException;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import java.util.List;
import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;
@ -160,7 +162,7 @@ class RbacRoleRepositoryIntegrationTest {
assertThat(context.getAssumedRoles()).as("precondition").containsExactly(assumedRoles.split(";"));
}
void exactlyTheseRbacRolesAreReturned(final Iterable<RbacRoleEntity> actualResult, final String... expectedRoleNames) {
void exactlyTheseRbacRolesAreReturned(final List<RbacRoleEntity> actualResult, final String... expectedRoleNames) {
assertThat(actualResult)
.extracting(RbacRoleEntity::getRoleName)
.containsExactlyInAnyOrder(expectedRoleNames);

View File

@ -39,7 +39,7 @@ class RbacUserControllerRestTest {
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/rbacusers")
.get("/api/rbac-users")
.header("current-user", "mike@hostsharing.net")
.accept(MediaType.APPLICATION_JSON))
@ -59,7 +59,7 @@ class RbacUserControllerRestTest {
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/rbacusers")
.get("/api/rbac-users")
.param("name", "admin@aaa")
.header("current-user", "mike@hostsharing.net")
.accept(MediaType.APPLICATION_JSON))