Initial commit
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package $package.presentation.rest;
|
||||
|
||||
$lombok_common_imports
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import $package.application.service.Create${entity}Service;
|
||||
import $package.application.service.Get${entity}Service;
|
||||
import $package.application.service.Update${entity}Service;
|
||||
import $package.application.service.Delete${entity}Service;
|
||||
import $package.application.service.List${entity}Service;
|
||||
import $package.presentation.dto.$EntityRequest;
|
||||
import $package.presentation.dto.$EntityResponse;
|
||||
import $package.presentation.dto.PageResponse;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
@RestController$controller_annotations_block
|
||||
@RequestMapping("$base_path")
|
||||
public class ${entity}Controller {
|
||||
|
||||
private final Create${entity}Service createService;
|
||||
private final Get${entity}Service getService;
|
||||
private final Update${entity}Service updateService;
|
||||
private final Delete${entity}Service deleteService;
|
||||
private final List${entity}Service listService;
|
||||
|
||||
$controller_constructor
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<$EntityResponse> create(@RequestBody @Valid $EntityRequest request) {
|
||||
$EntityResponse created = createService.create(request);
|
||||
return ResponseEntity.status(HttpStatus.CREATED)
|
||||
.header("Location", "$base_path/" + created.$id_name())
|
||||
.body(created);
|
||||
}
|
||||
|
||||
@GetMapping("/{${id_name_lower}}")
|
||||
public ResponseEntity<$EntityResponse> get(@PathVariable $id_type ${id_name_lower}) {
|
||||
return ResponseEntity.ok(getService.get(${id_name_lower}));
|
||||
}
|
||||
|
||||
@PutMapping("/{${id_name_lower}}")
|
||||
public ResponseEntity<$EntityResponse> update(@PathVariable $id_type ${id_name_lower},
|
||||
@RequestBody @Valid $EntityRequest request) {
|
||||
return ResponseEntity.ok(updateService.update(${id_name_lower}, request));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{${id_name_lower}}")
|
||||
public ResponseEntity<Void> delete(@PathVariable $id_type ${id_name_lower}) {
|
||||
deleteService.delete(${id_name_lower});
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<PageResponse<$EntityResponse>> list(Pageable pageable) {
|
||||
return ResponseEntity.ok(listService.list(pageable.getPageNumber(), pageable.getPageSize()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package $package.application.service;
|
||||
|
||||
$lombok_common_imports
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import $package.domain.model.$entity;
|
||||
import $package.domain.service.${entity}Service;
|
||||
import $package.application.mapper.${entity}Mapper;
|
||||
import $package.application.exception.${entity}ExistException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import $package.presentation.dto.$EntityRequest;
|
||||
import $package.presentation.dto.$EntityResponse;
|
||||
|
||||
@Service$service_annotations_block
|
||||
@Transactional
|
||||
public class Create${entity}Service {
|
||||
|
||||
private final ${entity}Service ${entity_lower}Service;
|
||||
private final ${entity}Mapper mapper;
|
||||
|
||||
$create_constructor
|
||||
|
||||
public $EntityResponse create($EntityRequest request) {
|
||||
try {
|
||||
$entity agg = mapper.toAggregate($create_id_arg, request);
|
||||
agg = ${entity_lower}Service.save(agg);
|
||||
return mapper.toResponse(agg);
|
||||
} catch (DataIntegrityViolationException ex) {
|
||||
throw new ${entity}ExistException("Duplicate $entity");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package $package.application.service;
|
||||
|
||||
$lombok_common_imports
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import $package.domain.service.${entity}Service;
|
||||
import $package.application.exception.${entity}NotFoundException;
|
||||
|
||||
@Service$service_annotations_block
|
||||
@Transactional
|
||||
public class Delete${entity}Service {
|
||||
|
||||
private final ${entity}Service ${entity_lower}Service;
|
||||
|
||||
$delete_constructor
|
||||
|
||||
public void delete($id_type $id_name) {
|
||||
if (!${entity_lower}Service.existsById($id_name)) {
|
||||
throw new ${entity}NotFoundException($id_name);
|
||||
}
|
||||
${entity_lower}Service.deleteById($id_name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package $package.domain.model;
|
||||
|
||||
$lombok_domain_imports
|
||||
$extra_imports
|
||||
|
||||
$lombok_domain_annotations_block
|
||||
public class $entity {
|
||||
$domain_fields_decls
|
||||
|
||||
private $entity($domain_ctor_params) {
|
||||
$domain_assigns
|
||||
}
|
||||
|
||||
public static $entity create($domain_ctor_params) {
|
||||
// TODO: add invariant checks
|
||||
return new $entity($all_names_csv);
|
||||
}
|
||||
|
||||
$domain_getters
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package $package.domain.repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import $package.domain.model.$entity;
|
||||
|
||||
public interface ${entity}Repository {
|
||||
$entity save($entity aggregate);
|
||||
Optional<$entity> findById($id_type $id_name);
|
||||
List<$entity> findAll(int page, int size);
|
||||
void deleteById($id_type $id_name);
|
||||
boolean existsById($id_type $id_name);
|
||||
long count();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package $package.domain.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
$lombok_common_imports
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import $package.domain.model.$entity;
|
||||
import $package.domain.repository.${entity}Repository;
|
||||
|
||||
@Service$service_annotations_block
|
||||
@Transactional
|
||||
public class ${entity}Service {
|
||||
|
||||
private final ${entity}Repository repository;
|
||||
|
||||
$domain_service_constructor
|
||||
|
||||
public $entity save($entity aggregate) {
|
||||
return repository.save(aggregate);
|
||||
}
|
||||
|
||||
public Optional<$entity> findById($id_type $id_name) {
|
||||
return repository.findById($id_name);
|
||||
}
|
||||
|
||||
public List<$entity> findAll(int page, int size) {
|
||||
return repository.findAll(page, size);
|
||||
}
|
||||
|
||||
public void deleteById($id_type $id_name) {
|
||||
repository.deleteById($id_name);
|
||||
}
|
||||
|
||||
public boolean existsById($id_type $id_name) {
|
||||
return repository.existsById($id_name);
|
||||
}
|
||||
|
||||
public long count() {
|
||||
return repository.count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package $package.presentation.dto;
|
||||
|
||||
$extra_imports
|
||||
|
||||
public record $EntityRequest($dto_request_components) { }
|
||||
@@ -0,0 +1,5 @@
|
||||
package $package.presentation.dto;
|
||||
|
||||
$extra_imports
|
||||
|
||||
public record $EntityResponse($dto_response_components) { }
|
||||
@@ -0,0 +1,35 @@
|
||||
package $package.presentation.rest;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import $package.application.exception.${entity}NotFoundException;
|
||||
import $package.application.exception.${entity}ExistException;
|
||||
import $package.presentation.dto.ErrorResponse;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ${entity}ExceptionHandler {
|
||||
|
||||
@ExceptionHandler(${entity}NotFoundException.class)
|
||||
public ResponseEntity<ErrorResponse> handleNotFound(${entity}NotFoundException ex, org.springframework.web.context.request.WebRequest request) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.NOT_FOUND.value(),
|
||||
"Not Found",
|
||||
ex.getMessage(),
|
||||
request.getDescription(false).replaceFirst("uri=", "")
|
||||
);
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
|
||||
}
|
||||
|
||||
@ExceptionHandler(${entity}ExistException.class)
|
||||
public ResponseEntity<ErrorResponse> handleExist(${entity}ExistException ex, org.springframework.web.context.request.WebRequest request) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.CONFLICT.value(),
|
||||
"Conflict",
|
||||
ex.getMessage(),
|
||||
request.getDescription(false).replaceFirst("uri=", "")
|
||||
);
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package $package.presentation.dto;
|
||||
|
||||
public record ErrorResponse(
|
||||
int status,
|
||||
String error,
|
||||
String message,
|
||||
String path
|
||||
) { }
|
||||
@@ -0,0 +1,10 @@
|
||||
package $package.application.exception;
|
||||
|
||||
public class ${entity}ExistException extends RuntimeException {
|
||||
public ${entity}ExistException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public ${entity}ExistException($id_type $id_name) {
|
||||
super("$entity already exists: " + $id_name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package $package.application.service;
|
||||
|
||||
$lombok_common_imports
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import $package.domain.service.${entity}Service;
|
||||
import $package.application.mapper.${entity}Mapper;
|
||||
import $package.application.exception.${entity}NotFoundException;
|
||||
import $package.presentation.dto.$EntityResponse;
|
||||
|
||||
@Service$service_annotations_block
|
||||
@Transactional(readOnly = true)
|
||||
public class Get${entity}Service {
|
||||
|
||||
private final ${entity}Service ${entity_lower}Service;
|
||||
private final ${entity}Mapper mapper;
|
||||
|
||||
$get_constructor
|
||||
|
||||
public $EntityResponse get($id_type $id_name) {
|
||||
return ${entity_lower}Service.findById($id_name)
|
||||
.map(mapper::toResponse)
|
||||
.orElseThrow(() -> new ${entity}NotFoundException($id_name));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package $package.presentation.rest;
|
||||
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
|
||||
import $package.presentation.dto.ErrorResponse;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ErrorResponse> handleValidationException(
|
||||
MethodArgumentNotValidException ex, org.springframework.web.context.request.WebRequest request) {
|
||||
String errors = ex.getBindingResult().getFieldErrors().stream()
|
||||
.map(f -> f.getField() + ": " + f.getDefaultMessage())
|
||||
.collect(java.util.stream.Collectors.joining(", "));
|
||||
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.BAD_REQUEST.value(),
|
||||
"Validation Error",
|
||||
"Validation failed: " + errors,
|
||||
request.getDescription(false).replaceFirst("uri=", "")
|
||||
);
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ResponseStatusException.class)
|
||||
public ResponseEntity<ErrorResponse> handleResponseStatusException(
|
||||
ResponseStatusException ex, org.springframework.web.context.request.WebRequest request) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
ex.getStatusCode().value(),
|
||||
ex.getStatusCode().toString(),
|
||||
ex.getReason(),
|
||||
request.getDescription(false).replaceFirst("uri=", "")
|
||||
);
|
||||
return new ResponseEntity<>(error, ex.getStatusCode());
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public ResponseEntity<ErrorResponse> handleDataIntegrityViolation(
|
||||
DataIntegrityViolationException ex, org.springframework.web.context.request.WebRequest request) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.CONFLICT.value(),
|
||||
"Conflict",
|
||||
ex.getMostSpecificCause() != null ? ex.getMostSpecificCause().getMessage() : ex.getMessage(),
|
||||
request.getDescription(false).replaceFirst("uri=", "")
|
||||
);
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package $package.infrastructure.persistence;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
$extra_imports
|
||||
$lombok_model_imports
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "$table_name")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public class ${entity}Entity {
|
||||
|
||||
$jpa_fields_decls
|
||||
|
||||
protected ${entity}Entity() { /* for JPA */ }
|
||||
|
||||
// Full constructor (optional, can be removed if not needed)
|
||||
public ${entity}Entity($jpa_ctor_params) {
|
||||
$jpa_assigns
|
||||
}
|
||||
|
||||
// Lombok generates getters and setters automatically
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package $package.application.service;
|
||||
|
||||
$lombok_common_imports
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import $package.domain.service.${entity}Service;
|
||||
import $package.application.mapper.${entity}Mapper;
|
||||
import $package.presentation.dto.$EntityResponse;
|
||||
import $package.presentation.dto.PageResponse;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service$service_annotations_block
|
||||
@Transactional(readOnly = true)
|
||||
public class List${entity}Service {
|
||||
|
||||
private final ${entity}Service ${entity_lower}Service;
|
||||
private final ${entity}Mapper mapper;
|
||||
|
||||
$list_constructor
|
||||
|
||||
public PageResponse<$EntityResponse> list(int page, int size) {
|
||||
List<$EntityResponse> content = ${entity_lower}Service.findAll(page, size)
|
||||
.stream()
|
||||
.map(mapper::toResponse)
|
||||
.collect(Collectors.toList());
|
||||
long total = ${entity_lower}Service.count();
|
||||
int totalPages = (int) Math.ceil(total / (double) size);
|
||||
return new PageResponse<>(content, page, size, total, totalPages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package $package.application.mapper;
|
||||
|
||||
import $package.domain.model.$entity;
|
||||
import $package.presentation.dto.$dto_request;
|
||||
import $package.presentation.dto.$dto_response;
|
||||
|
||||
public class ${entity}Mapper {
|
||||
|
||||
public $entity toAggregate($id_type id, $dto_request request) {
|
||||
return $entity.create($mapper_create_args);
|
||||
}
|
||||
|
||||
public $dto_response toResponse($entity a) {
|
||||
return new $dto_response($list_map_response_args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package $package.application.exception;
|
||||
|
||||
public class ${entity}NotFoundException extends RuntimeException {
|
||||
public ${entity}NotFoundException($id_type $id_name) {
|
||||
super("$entity not found: " + $id_name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package $package.presentation.dto;
|
||||
|
||||
public record PageResponse<T>(
|
||||
java.util.List<T> content,
|
||||
int page,
|
||||
int size,
|
||||
long totalElements,
|
||||
int totalPages
|
||||
) { }
|
||||
@@ -0,0 +1,54 @@
|
||||
package $package.infrastructure.persistence;
|
||||
|
||||
$lombok_common_imports
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
||||
import $package.domain.model.$entity;
|
||||
import $package.domain.repository.${entity}Repository;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component$adapter_annotations_block
|
||||
public class ${entity}RepositoryAdapter implements ${entity}Repository {
|
||||
|
||||
private final ${entity}JpaRepository jpa;
|
||||
|
||||
$adapter_constructor
|
||||
|
||||
@Override
|
||||
public $entity save($entity a) {
|
||||
${entity}Entity e = new ${entity}Entity($adapter_to_entity_args);
|
||||
e = jpa.save(e);
|
||||
return $entity.create($adapter_to_domain_args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<$entity> findById($id_type $id_name) {
|
||||
return jpa.findById($id_name).map(e -> $entity.create($adapter_to_domain_args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<$entity> findAll(int page, int size) {
|
||||
return jpa.findAll(PageRequest.of(page, size))
|
||||
.stream()
|
||||
.map(e -> $entity.create($adapter_to_domain_args))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById($id_type $id_name) {
|
||||
jpa.deleteById($id_name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsById($id_type $id_name) {
|
||||
return jpa.existsById($id_name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return jpa.count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# CRUD Generator Templates
|
||||
|
||||
You must provide all templates (.tpl) required by the generator; there is no fallback.
|
||||
|
||||
How it works:
|
||||
- The generator loads .tpl files from this directory (or a directory passed via --templates-dir).
|
||||
- It uses Python string.Template placeholders (e.g., $package, $entity, $id_type, $id_name, $id_name_lower, $base_path, $dto_request, $dto_response).
|
||||
- If any template is missing or fails to render, generation fails.
|
||||
|
||||
Required template filenames:
|
||||
- DomainModel.java.tpl
|
||||
- DomainRepository.java.tpl
|
||||
- JpaEntity.java.tpl
|
||||
- SpringDataRepository.java.tpl
|
||||
- PersistenceAdapter.java.tpl
|
||||
- CreateService.java.tpl
|
||||
- GetService.java.tpl
|
||||
- UpdateService.java.tpl
|
||||
- DeleteService.java.tpl
|
||||
- ListService.java.tpl
|
||||
- Mapper.java.tpl
|
||||
- DtoRequest.java.tpl
|
||||
- DtoResponse.java.tpl
|
||||
- PageResponse.java.tpl
|
||||
- ErrorResponse.java.tpl
|
||||
- Controller.java.tpl
|
||||
- GlobalExceptionHandler.java.tpl
|
||||
- EntityExceptionHandler.java.tpl
|
||||
- NotFoundException.java.tpl
|
||||
- ExistException.java.tpl
|
||||
|
||||
Tip: Start simple and expand over time; these files are your team’s baseline.
|
||||
|
||||
Conventions:
|
||||
- Base path is versioned: /v1/{resources}
|
||||
- POST returns 201 Created and sets Location: /v1/{resources}/{id}
|
||||
- GET collection supports pagination via Pageable in controller and returns PageResponse<T>
|
||||
- Application layer uses ${Entity}Mapper for DTO↔Domain and throws ${Entity}ExistException on duplicates
|
||||
- Exceptions are mapped by GlobalExceptionHandler and ${Entity}ExceptionHandler
|
||||
@@ -0,0 +1,5 @@
|
||||
package $package.infrastructure.persistence;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface ${entity}JpaRepository extends JpaRepository<${entity}Entity, $id_type> { }
|
||||
@@ -0,0 +1,32 @@
|
||||
package $package.application.service;
|
||||
|
||||
$lombok_common_imports
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import $package.domain.model.$entity;
|
||||
import $package.domain.service.${entity}Service;
|
||||
import $package.application.mapper.${entity}Mapper;
|
||||
import $package.application.exception.${entity}ExistException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import $package.presentation.dto.$EntityRequest;
|
||||
import $package.presentation.dto.$EntityResponse;
|
||||
|
||||
@Service$service_annotations_block
|
||||
@Transactional
|
||||
public class Update${entity}Service {
|
||||
|
||||
private final ${entity}Service ${entity_lower}Service;
|
||||
private final ${entity}Mapper mapper;
|
||||
|
||||
$update_constructor
|
||||
|
||||
public $EntityResponse update($id_type $id_name, $EntityRequest request) {
|
||||
try {
|
||||
$entity agg = mapper.toAggregate($id_name, request);
|
||||
agg = ${entity_lower}Service.save(agg);
|
||||
return mapper.toResponse(agg);
|
||||
} catch (DataIntegrityViolationException ex) {
|
||||
throw new ${entity}ExistException("Duplicate $entity");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user