Move backend files into a sub folder.
This commit is contained in:
60
backend/codiki-exposition/pom.xml
Normal file
60
backend/codiki-exposition/pom.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.codiki</groupId>
|
||||
<artifactId>codiki-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<artifactId>codiki-exposition</artifactId>
|
||||
|
||||
<name>codiki-exposition</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.codiki</groupId>
|
||||
<artifactId>codiki-application</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-data-jpa</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-security</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.postgresql</groupId>-->
|
||||
<!-- <artifactId>postgresql</artifactId>-->
|
||||
<!-- <scope>runtime</scope>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-test</artifactId>-->
|
||||
<!-- <scope>test</scope>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.security</groupId>-->
|
||||
<!-- <artifactId>spring-security-test</artifactId>-->
|
||||
<!-- <scope>test</scope>-->
|
||||
<!-- </dependency>-->
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.codiki.exposition.category;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.NO_CONTENT;
|
||||
import org.codiki.application.category.CategoryUseCases;
|
||||
import org.codiki.domain.category.model.Category;
|
||||
import org.codiki.exposition.category.model.CategoryDto;
|
||||
import org.codiki.exposition.category.model.CategoryEditionRequest;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/categories")
|
||||
public class CategoryController {
|
||||
private final CategoryUseCases categoryUseCases;
|
||||
|
||||
public CategoryController(CategoryUseCases categoryUseCases) {
|
||||
this.categoryUseCases = categoryUseCases;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(CREATED)
|
||||
public CategoryDto createCategory(@RequestBody CategoryEditionRequest request) {
|
||||
Category createdCategory = categoryUseCases.createCategory(request.name(), request.subCategoryIds());
|
||||
return new CategoryDto(createdCategory);
|
||||
}
|
||||
|
||||
@PutMapping("/{categoryId}")
|
||||
public CategoryDto updateCategory(
|
||||
@PathVariable("categoryId") UUID categoryId,
|
||||
@RequestBody CategoryEditionRequest request
|
||||
) {
|
||||
Category createdCategory = categoryUseCases.updateCategory(
|
||||
categoryId,
|
||||
request.name(),
|
||||
request.subCategoryIds()
|
||||
);
|
||||
return new CategoryDto(createdCategory);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{categoryId}")
|
||||
@ResponseStatus(NO_CONTENT)
|
||||
public void deleteCategory(@PathVariable("categoryId") UUID categoryId) {
|
||||
categoryUseCases.deleteCategory(categoryId);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<CategoryDto> getAllCategories() {
|
||||
return categoryUseCases.getAll()
|
||||
.stream()
|
||||
.map(CategoryDto::new)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.codiki.exposition.category.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.category.model.Category;
|
||||
|
||||
public record CategoryDto(
|
||||
UUID id,
|
||||
String name,
|
||||
List<CategoryDto> subCategories
|
||||
) {
|
||||
public CategoryDto(Category category) {
|
||||
this(
|
||||
category.id(),
|
||||
category.name(),
|
||||
category.subCategories()
|
||||
.stream()
|
||||
.map(CategoryDto::new)
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.codiki.exposition.category.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record CategoryEditionRequest(
|
||||
String name,
|
||||
List<UUID> subCategoryIds
|
||||
) {}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.codiki.exposition.configuration;
|
||||
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
|
||||
import org.codiki.domain.category.exception.CategoryDeletionException;
|
||||
import org.codiki.domain.category.exception.CategoryEditionException;
|
||||
import org.codiki.domain.category.exception.CategoryNotFoundException;
|
||||
import org.codiki.domain.exception.LoginFailureException;
|
||||
import org.codiki.domain.exception.RefreshTokenDoesNotExistException;
|
||||
import org.codiki.domain.exception.RefreshTokenExpiredException;
|
||||
import org.codiki.domain.exception.UserDoesNotExistException;
|
||||
import org.codiki.domain.picture.exception.PictureNotFoundException;
|
||||
import org.codiki.domain.picture.exception.PictureUploadException;
|
||||
import org.codiki.domain.publication.exception.NoPublicationSearchResultException;
|
||||
import org.codiki.domain.publication.exception.PublicationEditionException;
|
||||
import org.codiki.domain.publication.exception.PublicationNotFoundException;
|
||||
import org.codiki.domain.publication.exception.PublicationUpdateForbiddenException;
|
||||
import org.codiki.domain.user.exception.UserAlreadyExistsException;
|
||||
import org.codiki.domain.user.exception.UserCreationException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalControllerExceptionHandler extends ResponseEntityExceptionHandler {
|
||||
@ExceptionHandler({
|
||||
CategoryDeletionException.class,
|
||||
CategoryEditionException.class,
|
||||
CategoryNotFoundException.class,
|
||||
LoginFailureException.class,
|
||||
PublicationEditionException.class,
|
||||
PictureUploadException.class,
|
||||
UserAlreadyExistsException.class,
|
||||
UserCreationException.class
|
||||
})
|
||||
public ProblemDetail handleBadRequestExceptions(Exception exception) {
|
||||
return buildProblemDetail(BAD_REQUEST, exception);
|
||||
}
|
||||
|
||||
@ExceptionHandler({
|
||||
UserDoesNotExistException.class,
|
||||
RefreshTokenDoesNotExistException.class,
|
||||
PublicationNotFoundException.class,
|
||||
PictureNotFoundException.class,
|
||||
NoPublicationSearchResultException.class
|
||||
})
|
||||
public ProblemDetail handleNotFoundExceptions(Exception exception) {
|
||||
return buildProblemDetail(NOT_FOUND, exception);
|
||||
}
|
||||
|
||||
@ExceptionHandler({
|
||||
RefreshTokenExpiredException.class
|
||||
})
|
||||
public ProblemDetail handleUnauthorizedExceptions(Exception exception) {
|
||||
return buildProblemDetail(UNAUTHORIZED, exception);
|
||||
}
|
||||
|
||||
@ExceptionHandler({
|
||||
PublicationUpdateForbiddenException.class
|
||||
})
|
||||
public ProblemDetail handleForbiddenExceptions(Exception exception) {
|
||||
return buildProblemDetail(FORBIDDEN, exception);
|
||||
}
|
||||
|
||||
private static ProblemDetail buildProblemDetail(HttpStatus forbidden, Exception exception) {
|
||||
return ProblemDetail.forStatusAndDetail(forbidden, exception.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.codiki.exposition.configuration.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
|
||||
import static org.springframework.util.ObjectUtils.isEmpty;
|
||||
import org.codiki.application.security.JwtService;
|
||||
import org.codiki.application.security.model.CustomUserDetails;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
private static final String BEARER_PREFIX = "Bearer ";
|
||||
|
||||
private final JwtService jwtService;
|
||||
|
||||
public JwtAuthenticationFilter(JwtService jwtService) {
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain
|
||||
) throws ServletException, IOException {
|
||||
Optional.ofNullable(request.getHeader(AUTHORIZATION))
|
||||
.filter(authorizationHeader -> !isEmpty(authorizationHeader))
|
||||
.filter(authorizationHeader -> authorizationHeader.startsWith(BEARER_PREFIX))
|
||||
.map(authorizationHeader -> authorizationHeader.substring(BEARER_PREFIX.length()))
|
||||
.filter(token -> {
|
||||
String authorizationHeader = request.getHeader(AUTHORIZATION);
|
||||
return !isEmpty(authorizationHeader) && authorizationHeader.startsWith(BEARER_PREFIX);
|
||||
})
|
||||
.filter(jwtService::isValid)
|
||||
.flatMap(jwtService::extractUser)
|
||||
.map(CustomUserDetails::new)
|
||||
.map(userDetails -> new UsernamePasswordAuthenticationToken(
|
||||
userDetails,
|
||||
null,
|
||||
userDetails.getAuthorities()
|
||||
))
|
||||
.ifPresent(authenticationToken -> {
|
||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||
});
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.codiki.exposition.configuration.security;
|
||||
|
||||
import static org.springframework.http.HttpMethod.DELETE;
|
||||
import static org.springframework.http.HttpMethod.GET;
|
||||
import static org.springframework.http.HttpMethod.OPTIONS;
|
||||
import static org.springframework.http.HttpMethod.POST;
|
||||
import static org.springframework.http.HttpMethod.PUT;
|
||||
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
|
||||
import org.codiki.domain.user.model.UserRole;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
import static jakarta.servlet.DispatcherType.FORWARD;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class SecurityConfiguration {
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(
|
||||
HttpSecurity httpSecurity,
|
||||
JwtAuthenticationFilter jwtAuthenticationFilter
|
||||
) throws Exception {
|
||||
httpSecurity
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.exceptionHandling(configurer -> configurer
|
||||
.authenticationEntryPoint((request, response, authException) -> response.sendError(SC_UNAUTHORIZED))
|
||||
.accessDeniedHandler((request, response, accessDeniedException) -> response.sendError(SC_FORBIDDEN))
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.sessionManagement(customizer -> customizer.sessionCreationPolicy(STATELESS))
|
||||
.authorizeHttpRequests(requests -> requests
|
||||
.dispatcherTypeMatchers(FORWARD).permitAll()
|
||||
.requestMatchers(
|
||||
GET,
|
||||
"/api/health/check",
|
||||
"/api/categories",
|
||||
"/api/pictures/{pictureId}",
|
||||
"/api/publications/{publicationId}",
|
||||
"/api/publications",
|
||||
"/error"
|
||||
).permitAll()
|
||||
.requestMatchers(
|
||||
POST,
|
||||
"/api/users/login",
|
||||
"/api/users/refresh-token"
|
||||
).permitAll()
|
||||
.requestMatchers(
|
||||
POST,
|
||||
"/api/categories"
|
||||
).hasRole(UserRole.ADMIN.name())
|
||||
.requestMatchers(
|
||||
PUT,
|
||||
"/api/categories/{categoryId}"
|
||||
).hasRole(UserRole.ADMIN.name())
|
||||
.requestMatchers(
|
||||
DELETE,
|
||||
"/api/categories/{categoryId}"
|
||||
).hasRole(UserRole.ADMIN.name())
|
||||
.requestMatchers(OPTIONS).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
return httpSecurity.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.codiki.exposition.healthcheck;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/health")
|
||||
public class HealthCheckController {
|
||||
@GetMapping("/check")
|
||||
public String healthCheck() {
|
||||
return "Ok";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.codiki.exposition.picture;
|
||||
|
||||
import static java.util.Objects.isNull;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.tika.mime.MimeType;
|
||||
import org.apache.tika.mime.MimeTypeException;
|
||||
import org.apache.tika.mime.MimeTypes;
|
||||
import org.codiki.domain.picture.exception.PictureUploadException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Component
|
||||
public class MultipartFileConverter {
|
||||
private static final List<MimeType> ALLOWED_MIME_TYPES;
|
||||
|
||||
static {
|
||||
MimeTypes mimeTypes = MimeTypes.getDefaultMimeTypes();
|
||||
|
||||
try {
|
||||
ALLOWED_MIME_TYPES = List.of(
|
||||
mimeTypes.forName("image/png"),
|
||||
mimeTypes.forName("image/jpeg"),
|
||||
mimeTypes.forName("image/svg+xml")
|
||||
);
|
||||
} catch (MimeTypeException exception) {
|
||||
throw new RuntimeException("An error occurred while loading allowed mime types.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private final String tempPicturesFolderPath;
|
||||
|
||||
public MultipartFileConverter(@Value("${application.pictures.temp-path}") String tempPicturesFolderPath) {
|
||||
this.tempPicturesFolderPath = tempPicturesFolderPath;
|
||||
}
|
||||
|
||||
public File transformToFile(MultipartFile fileContent) {
|
||||
File pictureFile = new File(buildPicturePath(fileContent));
|
||||
try {
|
||||
fileContent.transferTo(pictureFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return pictureFile;
|
||||
}
|
||||
|
||||
private String buildPicturePath(MultipartFile fileContent) {
|
||||
checkMimeTypeIsAllowed(fileContent);
|
||||
return String.format(
|
||||
"%s/%s",
|
||||
tempPicturesFolderPath,
|
||||
UUID.randomUUID()
|
||||
);
|
||||
}
|
||||
|
||||
private void checkMimeTypeIsAllowed(MultipartFile fileContent) {
|
||||
MimeType result = null;
|
||||
try {
|
||||
result = MimeTypes.getDefaultMimeTypes()
|
||||
.forName(fileContent.getContentType());
|
||||
} catch (MimeTypeException exception) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
if (isNull(result) || !isAllowedMimeType(result)) {
|
||||
throw new PictureUploadException("Unable to upload the picture because its format is incorrect.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAllowedMimeType(MimeType mimeType) {
|
||||
return ALLOWED_MIME_TYPES.contains(mimeType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.codiki.exposition.picture;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||
import org.codiki.application.picture.PictureUseCases;
|
||||
import org.codiki.domain.picture.exception.PictureNotFoundException;
|
||||
import org.codiki.domain.picture.model.Picture;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/pictures")
|
||||
public class PictureController {
|
||||
private final MultipartFileConverter multipartFileConverter;
|
||||
private final PictureUseCases pictureUseCases;
|
||||
|
||||
public PictureController(
|
||||
MultipartFileConverter multipartFileConverter,
|
||||
PictureUseCases pictureUseCases
|
||||
) {
|
||||
this.multipartFileConverter = multipartFileConverter;
|
||||
this.pictureUseCases = pictureUseCases;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = MULTIPART_FORM_DATA_VALUE)
|
||||
public UUID uploadPicture(@RequestParam("file") MultipartFile fileContent) {
|
||||
File pictureFile = multipartFileConverter.transformToFile(fileContent);
|
||||
Picture newPicture = pictureUseCases.createPicture(pictureFile);
|
||||
return newPicture.id();
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{pictureId}", produces = APPLICATION_OCTET_STREAM_VALUE)
|
||||
public FileSystemResource loadPicture(@PathVariable("pictureId") UUID pictureId) {
|
||||
Picture picture = pictureUseCases.findById(pictureId)
|
||||
.orElseThrow(() -> new PictureNotFoundException(pictureId));
|
||||
return new FileSystemResource(picture.contentFile());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.codiki.exposition.publication;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.NO_CONTENT;
|
||||
import static org.springframework.util.ObjectUtils.isEmpty;
|
||||
import org.codiki.application.publication.PublicationUseCases;
|
||||
import org.codiki.domain.publication.exception.NoPublicationSearchResultException;
|
||||
import org.codiki.domain.publication.exception.PublicationNotFoundException;
|
||||
import org.codiki.domain.publication.model.Publication;
|
||||
import org.codiki.domain.publication.model.PublicationEditionRequest;
|
||||
import org.codiki.exposition.publication.model.PublicationDto;
|
||||
import org.codiki.exposition.publication.model.PublicationEditionRequestDto;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/publications")
|
||||
public class PublicationController {
|
||||
private final PublicationUseCases publicationUseCases;
|
||||
|
||||
public PublicationController(PublicationUseCases publicationUseCases) {
|
||||
this.publicationUseCases = publicationUseCases;
|
||||
}
|
||||
|
||||
@GetMapping("/{publicationId}")
|
||||
public PublicationDto getById(@PathVariable("publicationId") UUID publicationId) {
|
||||
return publicationUseCases.findById(publicationId)
|
||||
.map(PublicationDto::new)
|
||||
.orElseThrow(() -> new PublicationNotFoundException(publicationId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(CREATED)
|
||||
public PublicationDto createPublication(@RequestBody PublicationEditionRequestDto requestDto) {
|
||||
PublicationEditionRequest request = requestDto.toDomain();
|
||||
Publication newPublication = publicationUseCases.createPublication(request);
|
||||
return new PublicationDto(newPublication);
|
||||
}
|
||||
|
||||
@PutMapping("/{publicationId}")
|
||||
public PublicationDto updatePublication(
|
||||
@PathVariable("publicationId") UUID publicationId,
|
||||
@RequestBody PublicationEditionRequestDto requestDto
|
||||
) {
|
||||
PublicationEditionRequest request = requestDto.toDomain();
|
||||
Publication updatedPublication = publicationUseCases.updatePublication(publicationId, request);
|
||||
return new PublicationDto(updatedPublication);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{publicationId}")
|
||||
@ResponseStatus(NO_CONTENT)
|
||||
public void deletePublication(@PathVariable("publicationId") UUID publicationId) {
|
||||
publicationUseCases.deletePublication(publicationId);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<PublicationDto> searchPublications(@RequestParam("query") String searchQuery) {
|
||||
final List<PublicationDto> publications = publicationUseCases.searchPublications(searchQuery)
|
||||
.stream()
|
||||
.map(PublicationDto::new)
|
||||
.toList();
|
||||
|
||||
if (isEmpty(publications)) {
|
||||
throw new NoPublicationSearchResultException();
|
||||
}
|
||||
|
||||
return publications;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.codiki.exposition.publication.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.publication.model.Author;
|
||||
|
||||
public record AuthorDto(
|
||||
UUID id,
|
||||
String name,
|
||||
String image
|
||||
) {
|
||||
public AuthorDto(Author author) {
|
||||
this(
|
||||
author.id(),
|
||||
author.name(),
|
||||
author.image()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.codiki.exposition.publication.model;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.publication.model.Publication;
|
||||
import org.codiki.exposition.category.model.CategoryDto;
|
||||
|
||||
public record PublicationDto(
|
||||
UUID id,
|
||||
String key,
|
||||
String title,
|
||||
String text,
|
||||
String description,
|
||||
ZonedDateTime creationDate,
|
||||
UUID illustrationId,
|
||||
UUID categoryId,
|
||||
AuthorDto author
|
||||
) {
|
||||
public PublicationDto(Publication publication) {
|
||||
this(
|
||||
publication.id(),
|
||||
publication.key(),
|
||||
publication.title(),
|
||||
publication.text(),
|
||||
publication.description(),
|
||||
publication.creationDate(),
|
||||
publication.illustrationId(),
|
||||
publication.categoryId(),
|
||||
new AuthorDto(publication.author())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.codiki.exposition.publication.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.codiki.domain.publication.model.PublicationEditionRequest;
|
||||
|
||||
public record PublicationEditionRequestDto(
|
||||
String title,
|
||||
String text,
|
||||
String description,
|
||||
UUID illustrationId,
|
||||
UUID categoryId
|
||||
) {
|
||||
public PublicationEditionRequest toDomain() {
|
||||
return new PublicationEditionRequest(title, text, description, illustrationId, categoryId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.codiki.exposition.user;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import org.codiki.application.security.annotation.AllowedToAdmins;
|
||||
import org.codiki.application.security.annotation.AllowedToAnonymous;
|
||||
import org.codiki.application.user.UserUseCases;
|
||||
import org.codiki.domain.user.model.User;
|
||||
import org.codiki.domain.user.model.UserAuthenticationData;
|
||||
import org.codiki.exposition.user.model.LoginRequest;
|
||||
import org.codiki.exposition.user.model.LoginResponse;
|
||||
import org.codiki.exposition.user.model.RefreshTokenRequest;
|
||||
import org.codiki.exposition.user.model.SignInRequestDto;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
public class UserController {
|
||||
private final UserUseCases userUseCases;
|
||||
|
||||
public UserController(UserUseCases userUseCases) {
|
||||
this.userUseCases = userUseCases;
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
@AllowedToAnonymous
|
||||
public LoginResponse login(@RequestBody LoginRequest request) {
|
||||
UserAuthenticationData userAuthenticationData = userUseCases.authenticate(request.email(), request.password());
|
||||
return new LoginResponse(userAuthenticationData);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@AllowedToAdmins
|
||||
public List<User> findAll() {
|
||||
return userUseCases.findAll();
|
||||
}
|
||||
|
||||
@PostMapping("/refresh-token")
|
||||
public LoginResponse refreshToken(@RequestBody RefreshTokenRequest request) {
|
||||
UserAuthenticationData userAuthenticationData = userUseCases.authenticate(request.refreshTokenValue());
|
||||
return new LoginResponse(userAuthenticationData);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(CREATED)
|
||||
public void signIn(@RequestBody SignInRequestDto request) {
|
||||
userUseCases.createUser(request.pseudo(), request.email(), request.password());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.codiki.exposition.user.model;
|
||||
|
||||
public record LoginRequest(
|
||||
String email,
|
||||
String password
|
||||
) {}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.codiki.exposition.user.model;
|
||||
|
||||
import org.codiki.domain.user.model.UserAuthenticationData;
|
||||
|
||||
public record LoginResponse(
|
||||
String tokenType,
|
||||
String accessToken,
|
||||
String refreshToken
|
||||
) {
|
||||
public LoginResponse(UserAuthenticationData userAuthenticationData) {
|
||||
this(
|
||||
userAuthenticationData.tokenType(),
|
||||
userAuthenticationData.accessToken(),
|
||||
userAuthenticationData.refreshToken().value().toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.codiki.exposition.user.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record RefreshTokenRequest(
|
||||
UUID refreshTokenValue
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.codiki.exposition.user.model;
|
||||
|
||||
public record SignInRequestDto(
|
||||
String pseudo,
|
||||
String email,
|
||||
String password
|
||||
) {
|
||||
}
|
||||
Reference in New Issue
Block a user