first commit

This commit is contained in:
David 2025-08-03 02:39:47 +02:00
commit d46130d303
111 changed files with 4764 additions and 0 deletions

2
.gitattributes vendored Executable file
View File

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

140
.gitignore vendored Normal file
View File

@ -0,0 +1,140 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### MAC ###
.DS_Store
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
# ---> Java
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
*.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
.env
.idea/
.run/
**/static/images/uplaod

19
.mvn/wrapper/maven-wrapper.properties vendored Executable file
View File

@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

49
Dockerfile Normal file
View File

@ -0,0 +1,49 @@
# syntax=docker/dockerfile:1
FROM maven:3-eclipse-temurin-23-alpine AS dependency
WORKDIR /app
COPY pom.xml ./
COPY domain/pom.xml domain/
COPY domain/spi/pom.xml domain/spi/
COPY domain/api/pom.xml domain/api/
COPY domain/service/pom.xml domain/service/
COPY domain/data/pom.xml domain/data/
COPY application/pom.xml application/
COPY infrastructure/pom.xml infrastructure/
COPY bootstrap/pom.xml bootstrap/
COPY common/pom.xml common/
ARG XPEDITIS_PROFILE
RUN mvn -P${XPEDITIS_PROFILE} dependency:go-offline \
&& mvn dependency:get -Dartifact=net.bytebuddy:byte-buddy-agent:1.15.11 -B \
&& mvn dependency:get -Dartifact=org.mapstruct:mapstruct-processor:1.6.3 -B \
&& mvn dependency:get -Dartifact=org.projectlombok:lombok-mapstruct-binding:0.2.0 -B \
&& mvn dependency:get -Dartifact=com.mysql:mysql-connector-j:9.1.0 -B \
&& mvn dependency:get -Dartifact=org.flywaydb:flyway-core:10.20.1 -B \
&& mvn dependency:get -Dartifact=org.flywaydb:flyway-mysql:10.20.1 -B
FROM dependency AS builder
COPY domain/spi/src domain/spi/src
COPY domain/api/src domain/api/src
COPY domain/service/src domain/service/src
COPY domain/data/src domain/data/src
COPY application/src application/src
COPY infrastructure/src infrastructure/src
COPY bootstrap/src bootstrap/src
COPY common/src common/src
ARG XPEDITIS_PROFILE
RUN mvn -o clean package -P${XPEDITIS_PROFILE} -Dmaven.test.skip=true -DskipTests
#FROM builder AS test
#RUN mvn -o clean test
FROM eclipse-temurin:23-jdk-alpine AS production
WORKDIR /opt/app
EXPOSE 8080
COPY --from=builder /app/bootstrap/target/bootstrap*.jar xpeditis.jar
COPY wait-for-it.sh ./
RUN chmod +x wait-for-it.sh
COPY entrypoint.sh ./
RUN chmod +x entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]
#CMD ["java", "-jar", "-Dserver.port=8080", "leblr.jar"]
#HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ "curl", "--fail", "http://localhost:8080/actuator/health", "|| exit 1" ]

36
Dockerfile-springboot Normal file
View File

@ -0,0 +1,36 @@
# syntax=docker/dockerfile:1
FROM eclipse-temurin:23-jdk AS dependency
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw ./
COPY pom.xml ./
COPY domain/pom.xml domain/
COPY domain/spi/pom.xml domain/spi/
COPY domain/api/pom.xml domain/api/
COPY domain/service/pom.xml domain/service/
COPY domain/data/pom.xml domain/data/
COPY application/pom.xml application/
COPY infrastructure/pom.xml infrastructure/
COPY bootstrap/pom.xml bootstrap/
COPY common/pom.xml common/
ARG XPEDITIS_PROFILE
RUN ./mvnw -P${XPEDITIS_PROFILE} dependency:go-offline \
&& ./mvnw dependency:get -Dartifact=net.bytebuddy:byte-buddy-agent:1.15.11 -B \
&& ./mvnw dependency:get -Dartifact=org.mapstruct:mapstruct-processor:1.6.3 -B \
&& ./mvnw dependency:get -Dartifact=org.projectlombok:lombok-mapstruct-binding:0.2.0 -B
FROM dependency AS production
COPY domain/spi/src domain/spi/src
COPY domain/api/src domain/api/src
COPY domain/service/src domain/service/src
COPY domain/data/src domain/data/src
COPY application/src application/src
COPY infrastructure/src infrastructure/src
COPY bootstrap/src bootstrap/src
COPY common/src common/src
CMD ["./mvnw", "spring-boot:run"]

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# leblr-backend
Le BLR backend side<br>
SWAGGER UI : http://localhost:8080/swagger-ui.html<br>
<br>
.\mvnw clean install flyway:migrate -Pprod<br>
.\mvnw clean install flyway:migrate '-Dflyway.configFiles=flyway-h2.conf' -Pdev<br>

68
application/pom.xml Executable file
View File

@ -0,0 +1,68 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dh7789dev</groupId>
<artifactId>xpeditis</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>application</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.dh7789dev</groupId>
<artifactId>api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- spring-boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,83 @@
package com.dh7789dev.xpeditis.advice;
import com.dh7789dev.xpeditis.NlsService;
import com.dh7789dev.xpeditis.dto.CustomInternalExceptionResponse;
import com.dh7789dev.xpeditis.exception.CustomInternalException;
import com.dh7789dev.xpeditis.exception.GlobalNotFoundException;
import com.dh7789dev.xpeditis.exception.StorageException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.time.Instant;
@Slf4j
//@Order(1)
@RestControllerAdvice(basePackages = {"com.dh7789dev.xpeditis.controller"} )
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final String INTERNAL_ERROR = "internal.error";
private final NlsService nlsService;
@Autowired
public GlobalExceptionHandler(NlsService nlsService) {
this.nlsService = nlsService;
}
@ExceptionHandler(value = {IllegalArgumentException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleIllegalArgumentException(HttpServletRequest req, IllegalArgumentException ex) {
return nlsService.getMessage("error.invalid.argument");
}
@ExceptionHandler(StorageException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public CustomInternalExceptionResponse handleFileStorageException(HttpServletRequest req, StorageException ex) {
return new CustomInternalExceptionResponse(
(String) req.getAttribute("request_id"),
"StorageError",
ex.getMessage(),
req.getRequestURL().toString());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public CustomInternalExceptionResponse handleUnknownException(HttpServletRequest req, Exception ex) {
CustomInternalExceptionResponse response = new CustomInternalExceptionResponse();
response.setErrorCode(nlsService.getMessage(INTERNAL_ERROR));
response.setErrorMessage(ex.getMessage());
response.setRequestURL(req.getRequestURL().toString());
response.setTimestamp(Instant.now());
response.setId((String) req.getAttribute("request_id"));
return response;
}
@ExceptionHandler(CustomInternalException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public CustomInternalExceptionResponse handleCustomInternalException(HttpServletRequest req, CustomInternalException ex) {
CustomInternalExceptionResponse response = new CustomInternalExceptionResponse();
response.setErrorCode(nlsService.getMessage(ex.getNlsKey(), ex.getNlsParameters()));
response.setErrorMessage(ex.getMessage());
response.setRequestURL(req.getRequestURL().toString());
response.setTimestamp(Instant.now());
response.setId((String) req.getAttribute("request_id"));
return response;
}
@ExceptionHandler(GlobalNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
//@ResponseBody
public CustomInternalExceptionResponse globalNotFoundHandler(HttpServletRequest req, GlobalNotFoundException ex) {
return new CustomInternalExceptionResponse(
(String) req.getAttribute("request_id"),
ex.getErrorCode(),
ex.getMessage(),
req.getRequestURL().toString());
}
}

View File

@ -0,0 +1,35 @@
package com.dh7789dev.xpeditis.controller;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RestController
@RequestMapping(name = "/",
produces = APPLICATION_JSON_VALUE)
public class IndexRestController {
@Value("${spring.application.name}")
String name;
@Value("${spring.application.version}")
String version;
@Value("${spring.profiles.active}")
String springProfile;
public record ServiceInformation(String name,
String version,
@JsonProperty("spring profile") String springProfile) {
}
@GetMapping
public ResponseEntity<ServiceInformation> index() {
return ResponseEntity.ok(new ServiceInformation(name, version, springProfile));
}
}

View File

@ -0,0 +1,72 @@
package com.dh7789dev.xpeditis.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
@Controller
public class UploadController {
@Value("${file.upload-dir}")
String uploadDir;
private final EventService eventService;
private final GalleryService galleryService;
public UploadController(EventService eventService, GalleryService galleryService) {
this.eventService = eventService;
this.galleryService = galleryService;
}
@GetMapping("/uploadimage")
public String displayUploadForm() {
return "addImages";
}
@PostMapping(value = "/upload/{eventId}", consumes = MULTIPART_FORM_DATA_VALUE)
public String uploadImage(Model model,
@PathVariable long eventId,
@RequestParam("images") List<MultipartFile> images) {
eventService.addImages(eventId, images);
StringBuilder fileNames = new StringBuilder();
images.forEach(image -> fileNames.append(image.getOriginalFilename()).append(" "));
model.addAttribute("msg", "Uploaded images: " + fileNames);
return "addImages";
}
@PostMapping(value = "/upload/gallery/{galleryId}", consumes = MULTIPART_FORM_DATA_VALUE)
public String uploadImageToGallery(Model model,
@PathVariable long galleryId,
@RequestParam("images") List<MultipartFile> images) {
galleryService.addImages(galleryId, images);
StringBuilder fileNames = new StringBuilder();
images.forEach(image -> fileNames.append(image.getOriginalFilename()).append(" "));
model.addAttribute("msg", "Uploaded images: " + fileNames);
return "addImages";
}
@GetMapping("/addevent")
public String displayUploadFormToAddEvent() {
return "addEvent";
}
@PostMapping(value = "/upload", consumes = MULTIPART_FORM_DATA_VALUE)
public String addEvent(Model model,
@RequestPart("newEvent") Event newEvent,
@RequestPart("images") List<MultipartFile> images) {
eventService.addEvent(newEvent, images);
StringBuilder fileNames = new StringBuilder();
images.forEach(image -> fileNames.append(image.getOriginalFilename()).append(" "));
model.addAttribute("msg", "Uploaded images: " + fileNames);
return "addEvent";
}
}

View File

@ -0,0 +1,29 @@
package com.dh7789dev.xpeditis.controller.api.v1;
import com.dh7789dev.xpeditis.AuthenticationService;
import com.dh7789dev.xpeditis.dto.AuthenticationRequest;
import com.dh7789dev.xpeditis.dto.AuthenticationResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
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.RestController;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RestController
@RequestMapping(value = "${apiPrefix}/api/v1/auth",
produces = APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
public class AuthenticationRestController {
private final AuthenticationService service;
@PostMapping("/authenticate")
public ResponseEntity<AuthenticationResponse> authenticate(
@RequestBody @Valid AuthenticationRequest request) {
return ResponseEntity.ok(service.authenticate(request));
}
}

View File

@ -0,0 +1,38 @@
package com.dh7789dev.xpeditis.controller.api.v1;
import com.dh7789dev.xpeditis.UserService;
import com.dh7789dev.xpeditis.dto.ChangePasswordRequest;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@RestController
@Validated
@RequestMapping(value = "${apiPrefix}/api/v1/users",
produces = APPLICATION_JSON_VALUE)
public class UserRestController {
private final UserService service;
public UserRestController(UserService service) {
this.service = service;
}
@Operation(summary = "Change password of the connected user")
@PatchMapping("/password")
public ResponseEntity<Void> changePassword(
@RequestBody ChangePasswordRequest request,
Principal connectedUser) {
service.changePassword(request, connectedUser);
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@ -0,0 +1 @@
lombok.addLombokGeneratedAnnotation = true

128
bootstrap/pom.xml Executable file
View File

@ -0,0 +1,128 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dh7789dev</groupId>
<artifactId>xpeditis</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>bootstrap</artifactId>
<packaging>jar</packaging>
<description>Xpeditis</description>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.dh7789dev</groupId>
<artifactId>application</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.dh7789dev</groupId>
<artifactId>service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.dh7789dev</groupId>
<artifactId>infrastructure</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
<build>
<finalName>${project.name}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,27 @@
package com.dh7789dev.xpeditis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@ComponentScan(basePackages = {"com.dh7789dev.xpeditis"})
@EnableJpaRepositories("com.dh7789dev.xpeditis.dao")
@EntityScan("com.dh7789dev.xpeditis.entity")
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
@SpringBootApplication
public class XpeditisApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {
return applicationBuilder.sources(XpeditisApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(XpeditisApplication.class, args);
}
}

View File

@ -0,0 +1,95 @@
package com.dh7789dev.xpeditis.configuration;
import com.dh7789dev.xpeditis.dao.UserDao;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CommonsRequestLoggingFilter;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
@Configuration
@EnableTransactionManagement
@EnableAsync
public class GlobalConfiguration {
private static final String USER_NOT_FOUND_MSG = "User %s not found";
private final UserDao userDao;
public GlobalConfiguration(UserDao userDao) {
this.userDao = userDao;
}
@Bean
public CommonsRequestLoggingFilter loggingFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setIncludeClientInfo(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(false);
return filter;
}
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:language/i18n");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setDefaultLocale(Locale.FRANCE);
messageSource.setUseCodeAsDefaultMessage(true);
// Refresh cache once every hour
messageSource.setCacheSeconds(3600);
return messageSource;
}
@Bean
public LocaleResolver localeResolver() {
// Can be Fixed, AcceptHeader, Session or Cookie
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setDefaultLocale(Locale.FRANCE);
localeResolver.setDefaultTimeZone(TimeZone.getTimeZone("UTC"));
return localeResolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
// Example: ?lang=en
localeChangeInterceptor.setParamName("lang");
return localeChangeInterceptor;
}
@Bean
UrlBasedCorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
//configuration.setAllowCredentials(true);
configuration.setAllowedOrigins(List.of("http://localhost:8080", "http://localhost:3000",
"http://127.0.0.1:8080", "https://leblr.preprod.weworkstudio.fr","https://leblr.fr",
"https://api.leblr.preprod.weworkstudio.fr/","https://api.leblr.fr/", "https://www.leblr.fr"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowedHeaders(List.of("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public UserDetailsService userDetailsService() {
return username -> userDao.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, username)));
}
}

View File

@ -0,0 +1,31 @@
package com.dh7789dev.xpeditis.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {
private final LocaleChangeInterceptor localeChangeInterceptor;
private final RequestIdInterceptor requestIdInterceptor;
@Autowired
public InterceptorConfiguration(LocaleChangeInterceptor localeChangeInterceptor,
RequestIdInterceptor requestIdInterceptor) {
this.localeChangeInterceptor = localeChangeInterceptor;
this.requestIdInterceptor = requestIdInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
final String[] excluded = {"/error"};
registry.addInterceptor(localeChangeInterceptor)
.excludePathPatterns(excluded);
registry.addInterceptor(requestIdInterceptor)
.excludePathPatterns(excluded);
}
}

View File

@ -0,0 +1,81 @@
package com.dh7789dev.xpeditis.configuration;
import com.dh7789dev.xpeditis.dao.TokenDao;
import com.dh7789dev.xpeditis.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String AUTH_HEADER = "Authorization";
private static final String AUTH_TYPE = "Bearer ";
private final UserDetailsService userDetailsService;
private final JwtUtil jwtUtil;
private final TokenDao tokenDao;
public JwtAuthenticationFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil, TokenDao tokenDao) {
this.userDetailsService = userDetailsService;
this.jwtUtil = jwtUtil;
this.tokenDao = tokenDao;
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
final String jwt;
final String username;
if (request.getServletPath().contains("/api/v1/auth")) {
filterChain.doFilter(request, response);
return;
}
final String authHeader = request.getHeader(AUTH_HEADER);
if (authHeader == null || !authHeader.startsWith(AUTH_TYPE)) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(AUTH_TYPE.length()).trim();
username = jwtUtil.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
boolean isTokenValid = tokenDao.findByToken(jwt)
.map(t -> !t.isExpired() && !t.isRevoked())
.orElse(false);
if (isTokenValid && jwtUtil.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
} else {
throw new UsernameNotFoundException("Failed to authenticate with access token");
}
}
filterChain.doFilter(request, response);
}
}

View File

@ -0,0 +1,38 @@
package com.dh7789dev.xpeditis.configuration;
import com.dh7789dev.xpeditis.dao.TokenDao;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LogoutService implements LogoutHandler {
private final TokenDao tokenDao;
@Override
public void logout(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
final String authHeader = request.getHeader("Authorization");
final String jwt;
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return;
}
jwt = authHeader.substring(7);
var storedToken = tokenDao.findByToken(jwt)
.orElse(null);
if (storedToken != null) {
storedToken.setExpired(true);
storedToken.setRevoked(true);
tokenDao.save(storedToken);
SecurityContextHolder.clearContext();
}
}
}

View File

@ -0,0 +1,29 @@
package com.dh7789dev.xpeditis.configuration;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.UUID;
@Component
public class RequestIdInterceptor implements HandlerInterceptor {
private static final String REQUEST_ID = "request_id";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String requestId = UUID.randomUUID().toString();
MDC.put(REQUEST_ID, requestId);
request.setAttribute(REQUEST_ID, requestId);
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
MDC.clear();
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}

View File

@ -0,0 +1,128 @@
package com.dh7789dev.xpeditis.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
@Configuration
@EnableWebSecurity
//@EnableMethodSecurity
public class SecurityConfiguration {
@Value("${application.csrf.enabled}")
boolean csrfEnabled;
private static final String ADMIN_ROLE = "ADMIN";
private static final String API_V1_URI = "/api/v1/**";
private static final String[] WHITE_LIST_URL = {
"/api/v1/auth/**",
"/actuator/health/**"};
private static final String[] INTERNAL_WHITE_LIST_URL = {
"/v2/api-docs",
"/v3/api-docs",
"/v3/api-docs/**",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui/**",
"/webjars/**",
"/swagger-ui.html",
"/uploadimage",
"/upload/**",
"/addevent"};
private static final WebExpressionAuthorizationManager INTERNAL_ACCESS =
new WebExpressionAuthorizationManager("hasIpAddress('127.0.0.1') or hasIpAddress('0:0:0:0:0:0:0:1')");
private final UserDetailsService userDetailsService;
private final LogoutHandler logoutHandler;
@Autowired
public SecurityConfiguration(UserDetailsService userDetailsService, LogoutHandler logoutHandler) {
this.userDetailsService = userDetailsService;
this.logoutHandler = logoutHandler;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
final DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthFilter) throws Exception {
if (csrfEnabled) {
http.csrf(csrf -> csrf.ignoringRequestMatchers(antMatcher("/h2-console/**")));
} else {
// csrf is disabled for dev and test
http.csrf(AbstractHttpConfigurer::disable);
}
http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));
http.authorizeHttpRequests(auth ->
auth.requestMatchers(WHITE_LIST_URL).permitAll()
.requestMatchers(antMatcher(HttpMethod.GET, "/")).permitAll()
.requestMatchers(antMatcher(HttpMethod.GET, API_V1_URI)).permitAll()
.requestMatchers(antMatcher(HttpMethod.GET, "/images/**")).permitAll()
.requestMatchers(antMatcher(HttpMethod.GET, "/api/v1/reservations")).hasRole(ADMIN_ROLE)
.requestMatchers(antMatcher(HttpMethod.GET, "/api/v1/reservations/**")).hasRole(ADMIN_ROLE)
.requestMatchers(antMatcher(HttpMethod.PUT, API_V1_URI)).hasRole(ADMIN_ROLE)
.requestMatchers(antMatcher(HttpMethod.DELETE, API_V1_URI)).hasRole(ADMIN_ROLE)
.requestMatchers(antMatcher(HttpMethod.POST, "/api/v1/reservations")).permitAll()
.requestMatchers(antMatcher(HttpMethod.POST, API_V1_URI)).hasRole(ADMIN_ROLE)
.requestMatchers(antMatcher("/h2-console/**")).access(INTERNAL_ACCESS)
.requestMatchers(antMatcher("/actuator/**")).access(INTERNAL_ACCESS)
.requestMatchers(INTERNAL_WHITE_LIST_URL).access(INTERNAL_ACCESS)
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.logout(logout -> logout
.logoutUrl("/api/v1/auth/logout")
.addLogoutHandler(logoutHandler)
.logoutSuccessHandler((request,
response,
authentication) -> SecurityContextHolder.clearContext()));
return http.build();
}
}

View File

@ -0,0 +1,60 @@
---
spring:
h2:
console:
enabled: 'true'
datasource:
url: jdbc:h2:mem:xpeditis;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driverClassName: org.h2.Driver
username: sa
password: ''
sql:
init:
platform: h2
mode: always
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
# show_sql: true
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: update
# Just to load initial data for the demo. DO NOT USE IT IN PRODUCTION
defer-datasource-initialization: true
flyway: # flyway automatically uses the datasource from the application to connect to the DB
enabled: false # enables flyway database migration
mail:
host: sandbox.smtp.mailtrap.io
port: 2525
username: 2597bd31d265eb
password: cd126234193c89
properties:
mail:
smtp:
ssl:
trust:"*"
auth: true
starttls:
enable: true
connectiontimeout: 5000
timeout: 3000
writetimeout: 5000
application:
email:
from: randommailjf@gmail.com
csrf:
enabled: false
security:
jwt:
secret-key: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
expiration: 86400000 # a day
refresh-token:
expiration: 604800000 # 7 days

View File

@ -0,0 +1,66 @@
---
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
#hikari:
#schema: leblr
sql:
init:
platform: mysql
mode: never
#data-locations: import_users.sql
jpa:
# show-sql: true
properties:
hibernate:
format_sql: true
#show_sql: true
database: mysql
database-platform: org.hibernate.dialect.MySQLDialect
hibernate:
ddl-auto: validate
defer-datasource-initialization: false
#open-in-view: false
flyway: # flyway automatically uses the datasource from the application to connect to the DB
enabled: true # enables flyway database migration
locations: classpath:db/migration/structure, classpath:db/migration/data # the location where flyway should look for migration scripts
validate-on-migrate: true
baseline-on-migrate: true
baseline-version: 0
default-schema: leblr
mail:
protocol: smtp
host: ssl0.ovh.net
port: 587
username: contact@xpeditis.fr
password:
properties:
mail:
smtp:
auth: true
starttls:
enable: true
connectiontimeout: 5000
timeout: 3000
writetimeout: 5000
application:
email:
from: contact@leblr.fr
csrf:
enabled: false
security:
jwt:
secret-key: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
expiration: 86400000 # a day
refresh-token:
expiration: 604800000 # 7 days

View File

@ -0,0 +1,52 @@
server:
port: 8080
file:
upload-dir: /upload
spring:
http:
encoding:
charset: UTF-8
enabled: true
force: true
application:
name: '@project.description@'
version: '@project.version@'
profiles:
active: '@spring.profiles.active@'
banner:
location: 'classpath:banner.txt'
# jackson:
# date-format: yyyy-MM-dd HH:mm:ss
# time-zone: Europe/Paris
# messages:
# basename: language/i18n
servlet:
multipart:
enabled: true
max-file-size: 50MB
max-request-size: 50MB
#location: ${java.io.tmpdir}
logging:
level:
org:
springframework:
web:
filter:
CommonsRequestLoggingFilter: INFO
security:
config:
annotation:
authentication:
configuration:
InitializeUserDetailsBeanManagerConfigurer: ERROR
apiPrefix: ""

View File

@ -0,0 +1,17 @@
██╗ ██╗██████╗ ███████╗██████╗ ██╗████████╗██╗███████╗
╚██╗██╔╝██╔══██╗██╔════╝██╔══██╗██║╚══██╔══╝██║██╔════╝
╚███╔╝ ██████╔╝█████╗ ██║ ██║██║ ██║ ██║███████╗
██╔██╗ ██╔═══╝ ██╔══╝ ██║ ██║██║ ██║ ██║╚════██║
██╔╝ ██╗██║ ███████╗██████╔╝██║ ██║ ██║███████║
╚═╝ ╚═╝╚═╝ ╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝
⚓ MARITIME LOGISTICS & SHIPPING SOLUTIONS ⚓
🌊 Setting Sail for Excellence 🌊
═══════════════════════════════════════════════════════
Port: ${server.port:8080} | Version: ${spring.application.version}
═══════════════════════════════════════════════════════
🚢 Navigating the Digital Ocean 🚢
Ready to Ship Your Success!
Powered by Spring Boot ${spring-boot.version}

View File

@ -0,0 +1,28 @@
INSERT INTO users (username, email, password, role)
VALUES ('dbuser', 'dbuser@dev.ovh', '{bcrypt}$2y$10$.qkbukzzX21D.bqbI.B2R.tvWP90o/Y16QRWVLodw51BHft7ZWbc.', 'USER'),
('dbadmin', 'dbadmin@dev.ovh', '{bcrypt}$2y$10$kp1V7UYDEWn17WSK16UcmOnFd1mPFVF6UkLrOOCGtf24HOYt8p1iC', 'ADMIN');
INSERT INTO event (name, description, date)
VALUES ('toto', 'hello', '2025-01-01'),
('tata', 'hola', '2025-01-10');
INSERT INTO gallery (name, description)
VALUES ('gallery', '');
INSERT INTO menu_category (name)
VALUES ('STARTERS'),
('DISHES'),
('DESSERTS'),
('PIZZAS'),
('RED_WINES'),
('WHITE_WINES');
INSERT INTO product (name, description, price, category_id)
VALUES ('Coconut Cake', 'Fresh Coconut', 4.90, 3),
('Pasta', 'Fresh Pasta', 12.79, 2),
('Foie Gras', 'Fresh Pasta', 12.79, 1);
INSERT INTO selected_day (date)
VALUES ('2035-01-01'),
('2035-03-02'),
('2035-03-10');

View File

@ -0,0 +1,7 @@
internal.error=Unable to perform action
red.wines.name=RED WINES
white.wines.name=WHITE WINES
starters.name=STARTERS
dishes.name=DISHES
desserts.name=DESSERTS
pizzas.name=PIZZAS

View File

@ -0,0 +1,7 @@
internal.error=Impossible de réaliser l'action
red.wines.name=VINS ROUGES
white.wines.name=VINS BLANCS
starters.name=ENTREES
dishes.name=PLATS
desserts.name=DESSERTS
pizzas.name=PIZZAS

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<!--<configuration debug="true" scan="true" scanPeriod="30 seconds">-->
<configuration scan="true" scanPeriod="30 seconds">
<property name="XPEDITIS" value="XPEDITIS_" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%d{dd-MM-yyyy HH:mm:ss.SSS} [r:%X{request_id:--}] %magenta([%thread]) %contextName %highlight(%-5level) %yellow(%logger{36}.%M) - %msg%n
<!--%black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1.}): %msg%n%throwable-->
</pattern>
</layout>
</appender>
<springProfile name="dev">
<property name="XPEDITIS_LOGS_DEV" value="logs/dev" />
<root>
<level value="DEBUG"/>
<appender-ref ref="STDOUT"/>
</root>
</springProfile>
<springProfile name="prod">
<property name="XPEDITIS_LOGS_PROD" value="logs/prod" />
<!-- everything.log -->
<appender name="${XPEDITIS}prod" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${XPEDITIS_LOGS_PROD}/everything.log</file>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
%d{dd-MM-yyyy HH:mm:ss.SSS} [r:%X{request_id:--}] [%thread] %contextName %-5level %logger{36}.%M - %msg%n
<!--%d %p %C{1.} [%t] %m%n-->
</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily and when the file reaches 10 MegaBytes -->
<fileNamePattern>
${XPEDITIS_LOGS_PROD}/archived/everything_%d{dd-MM-yyyy}_%i.log
</fileNamePattern>
<maxFileSize>1MB</maxFileSize>
<maxHistory>10</maxHistory>
<totalSizeCap>10MB</totalSizeCap>
</rollingPolicy>
</appender>
<root>
<level value="INFO"/>
<appender-ref ref="STDOUT"/>
<appender-ref ref="${LEBLR}prod"/>
</root>
</springProfile>
</configuration>

View File

@ -0,0 +1,74 @@
<body>
<section class="my-5">
<div class="container">
<div class="row">
<div class="col-md-8 mx-auto">
<h2>Upload Image Example</h2>
<p th:text="${message}" th:if="${message ne null}" class="alert alert-primary"></p>
<form id="uploadForm" enctype="multipart/form-data">
<div class="form-group">
<label for="nameInput">Name:</label>
<input type="text" id="nameInput" name="name" class="form-control" required>
</div>
<div class="form-group">
<label for="descriptionInput">Description:</label>
<input type="text" id="descriptionInput" name="description" class="form-control" required>
</div>
<div class="form-group">
<label for="dateInput">Date:</label>
<input type="date" id="dateInput" name="date" class="form-control" required>
</div>
<div class="form-group">
<input type="file" name="images" accept="image/*" class="form-control-file" multiple>
</div>
<button type="button" class="btn btn-primary" onclick="submitForm()">Upload images</button>
</form>
<span th:if="${msg != null}" th:text="${msg}"></span>
</div>
</div>
</div>
</section>
<script>
function submitForm() {
var form = document.getElementById('uploadForm');
var formData = new FormData();
// Ajouter les champs de formulaire à formData
formData.append('name', document.getElementById('nameInput').value);
formData.append('description', document.getElementById('descriptionInput').value);
formData.append('date', document.getElementById('dateInput').value);
// Ajouter les images à formData
var images = form.querySelector('input[type="file"]').files;
for (var i = 0; i < images.length; i++) {
formData.append('images', images[i]);
}
// Créer un objet Blob pour newEvent
var eventData = {
name: formData.get('name'),
description: formData.get('description'),
date: formData.get('date')
};
var newEventBlob = new Blob([JSON.stringify(eventData)], { type: 'application/json' });
// Ajouter newEvent en tant que RequestPart séparé
formData.append('newEvent', newEventBlob);
// Envoyer la requête
fetch('/upload', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
alert(data);
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
</body>

View File

@ -0,0 +1,42 @@
<body>
<section class="my-5">
<div class="container">
<div class="row">
<div class="col-md-8 mx-auto">
<h2>Upload Image Example</h2>
<p th:text="${message}" th:if="${message ne null}" class="alert alert-primary"></p>
<form id="uploadForm" enctype="multipart/form-data">
<div class="form-group">
<label for="numberInput">Enter a Number:</label>
<input type="number" id="numberInput" name="number" class="form-control" required>
</div>
<div class="form-group">
<input type="file" name="images" accept="image/*" class="form-control-file" multiple>
</div>
<button type="button" class="btn btn-primary" onclick="submitForm1()">Upload images to event</button>
<button type="button" class="btn btn-primary" onclick="submitForm2()">Upload images to gallery</button>
</form>
<span th:if="${msg != null}" th:text="${msg}"></span>
</div>
</div>
</div>
</section>
<script>
function submitForm2() {
var number = document.getElementById('numberInput').value;
var form = document.getElementById('uploadForm');
form.action = '/upload/gallery/' + number;
form.method = 'post';
form.submit();
}
function submitForm1() {
var number = document.getElementById('numberInput').value;
var form = document.getElementById('uploadForm');
form.action = '/upload/' + number;
form.method = 'post';
form.submit();
}
</script>
</body>

View File

@ -0,0 +1,14 @@
package com.dh7789dev.xpeditis;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest
@ActiveProfiles("dev")
class LeBlrApplicationTests {
@Test
void contextLoads() {
}
}

16
common/pom.xml Executable file
View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<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>com.dh7789dev</groupId>
<artifactId>xpeditis</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>common</artifactId>
<packaging>jar</packaging>
</project>

View File

@ -0,0 +1,8 @@
package com.dh7789dev.xpeditis;
public class CommonUtil {
public void sayHello() {
System.out.println("Hello!");
}
}

79
compose.yml Normal file
View File

@ -0,0 +1,79 @@
services:
db:
image: mysql:latest
restart: always
ports:
- "3306:3306"
networks:
- xpeditis
environment:
MYSQL_DATABASE_FILE: /run/secrets/mysql-database
MYSQL_USER_FILE: /run/secrets/mysql-user
MYSQL_PASSWORD_FILE: /run/secrets/mysql-password
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql-root-password
# MYSQL_RANDOM_ROOT_PASSWORD: '1'
secrets:
- mysql-database
- mysql-user
- mysql-password
- mysql-root-password
volumes:
- type: bind
source: ../database/mysql
target: /var/lib/mysql
- type: bind
source: ../database/config/docker-fixes.cnf
target: /etc/mysql/conf.d/docker-fixes.cnf
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "10"
container_name: db
back:
image: leblr-backend
restart: always
build:
context: .
dockerfile: Dockerfile
args:
XPEDITIS_PROFILE: prod
secrets:
- mysql-user
- mysql-password
target: production
ports:
- "8081:8080"
networks:
- xpeditis
depends_on:
- db
environment:
SPRING_DATASOURCE_URL:
SPRING_DATASOURCE_USERNAME:
SPRING_DATASOURCE_PASSWORD:
volumes:
- ./logs/prod:/opt/app/logs/prod:rw
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "10"
container_name: leblr_backend
secrets:
mysql-user:
file: ../database/secrets/mysql-user.txt
mysql-database:
file: ../database/secrets/mysql-database.txt
mysql-password:
file: ../database/secrets/mysql-password.txt
mysql-root-password:
file: ../database/secrets/mysql-root-password.txt
networks:
leblr:
name: leblr
external: true

32
domain/api/pom.xml Executable file
View File

@ -0,0 +1,32 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dh7789dev</groupId>
<artifactId>domain</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>api</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.dh7789dev</groupId>
<artifactId>data</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,9 @@
package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.AuthenticationRequest;
import com.dh7789dev.xpeditis.dto.AuthenticationResponse;
public interface AuthenticationService {
AuthenticationResponse authenticate(AuthenticationRequest request);
}

View File

@ -0,0 +1,8 @@
package com.dh7789dev.xpeditis;
public interface EmailService {
void sendCustomerReservationEmail(Reservation reservation);
void sendAdminReservationEmail(Reservation reservation);
}

View File

@ -0,0 +1,17 @@
package com.dh7789dev.xpeditis;
public interface NlsService {
/**
* @param key nls message key
* @return the message corresponding to the key translated into the desired language
*/
String getMessage(String key);
/**
* @param key nls message key
* @param parameters parameters to use in the message
* @return the message corresponding to the key translated into the desired language
*/
String getMessage(String key, Object[] parameters);
}

View File

@ -0,0 +1,15 @@
package com.dh7789dev.xpeditis;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
public interface StorageService {
void init();
String upload(MultipartFile file, String newFileName);
Resource download(String fileName);
void delete(String fileName);
}

View File

@ -0,0 +1,10 @@
package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.ChangePasswordRequest;
import java.security.Principal;
public interface UserService {
void changePassword(ChangePasswordRequest request, Principal connectedUser);
}

57
domain/data/pom.xml Executable file
View File

@ -0,0 +1,57 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dh7789dev</groupId>
<artifactId>domain</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>data</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<!-- @Json -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
package com.dh7789dev.xpeditis.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
@Data
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
public class AuthenticationRequest {
@NotBlank
final String username;
@NotBlank
final String password;
}

View File

@ -0,0 +1,31 @@
package com.dh7789dev.xpeditis.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
public class AuthenticationResponse {
@JsonProperty("access_token")
String accessToken;
@JsonProperty("refresh_token")
String refreshToken;
@JsonProperty("created_at")
Date createdAt;
@JsonProperty("expires_at")
Date expiresAt;
}

View File

@ -0,0 +1,18 @@
package com.dh7789dev.xpeditis.dto;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
public class ChangePasswordRequest {
String currentPassword;
String newPassword;
String confirmationPassword;
}

View File

@ -0,0 +1,36 @@
package com.dh7789dev.xpeditis.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import java.time.Instant;
@Data
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class CustomInternalExceptionResponse {
String id;
Instant timestamp;
String errorCode;
String errorMessage;
String requestURL;
public CustomInternalExceptionResponse(String id, String errorCode, String errorMessage, String requestURL) {
this.id = id;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.requestURL = requestURL;
this.timestamp = Instant.now();
}
}

View File

@ -0,0 +1,35 @@
package com.dh7789dev.xpeditis.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.constraints.NotBlank;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Gallery {
Long id;
@NotBlank(message = "name cannot be empty")
String name;
String description;
List<Image> images;
@Override
public String toString() {
return "Gallery(" + String.format("name=%s, description=%s)", name, description);
}
}

View File

@ -0,0 +1,26 @@
package com.dh7789dev.xpeditis.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
public class Image {
Long id;
String name;
@JsonIgnore
String path;
String uri;
public Image(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,43 @@
package com.dh7789dev.xpeditis.exception;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
public class CustomInternalException extends RuntimeException {
private static final String INTERNAL_ERROR = "internal.error";
final String errorCode;
final String nlsKey;
final String[] nlsParameters;
public CustomInternalException(String message) {
super(message);
errorCode = "InternalError";
nlsKey = INTERNAL_ERROR;
nlsParameters = new String[]{};
}
public CustomInternalException(String errorCode, String message, String nlsKey) {
super(message);
this.errorCode = errorCode;
this.nlsKey = nlsKey;
this.nlsParameters= new String[]{};
}
public CustomInternalException(String errorCode, String message, String nlsKey, String[] nlsParameters) {
super(message);
this.errorCode = errorCode;
this.nlsKey = nlsKey;
this.nlsParameters = nlsParameters;
}
}

View File

@ -0,0 +1,21 @@
package com.dh7789dev.xpeditis.exception;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
public class GlobalNotFoundException extends RuntimeException {
final String errorCode;
public GlobalNotFoundException(String subject) {
super((subject + " Not Found"));
this.errorCode = subject + "NotFound";
}
}

View File

@ -0,0 +1,8 @@
package com.dh7789dev.xpeditis.exception;
public class NlsNotFoundException extends RuntimeException {
public NlsNotFoundException(String nlsKey, String locale) {
super("Nls message not found for nlsKey " + nlsKey + " and locale " + locale);
}
}

View File

@ -0,0 +1,12 @@
package com.dh7789dev.xpeditis.exception;
public class StorageException extends RuntimeException {
public StorageException(String message) {
super(message);
}
public StorageException(String message, Throwable cause) {
super(message, cause);
}
}

27
domain/pom.xml Executable file
View File

@ -0,0 +1,27 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dh7789dev</groupId>
<artifactId>xpeditis</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>domain</artifactId>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modules>
<module>data</module>
<module>api</module>
<module>spi</module>
<module>service</module>
</modules>
</project>

82
domain/service/pom.xml Executable file
View File

@ -0,0 +1,82 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dh7789dev</groupId>
<artifactId>domain</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>service</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.9.2</junit.version>
</properties>
<dependencies>
<!-- spring-boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.dh7789dev</groupId>
<artifactId>api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.dh7789dev</groupId>
<artifactId>spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,20 @@
package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.AuthenticationRequest;
import com.dh7789dev.xpeditis.dto.AuthenticationResponse;
import org.springframework.stereotype.Service;
@Service
public class AuthenticationServiceImpl implements AuthenticationService {
private final AuthenticationRepository authenticationRepository;
public AuthenticationServiceImpl(AuthenticationRepository authenticationRepository) {
this.authenticationRepository = authenticationRepository;
}
@Override
public AuthenticationResponse authenticate(AuthenticationRequest request) {
return authenticationRepository.authenticate(request);
}
}

View File

@ -0,0 +1,59 @@
package com.dh7789dev.xpeditis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.format.DateTimeFormatter;
@Service
public class EmailServiceImpl implements EmailService {
@Value("${application.email.from}")
String adminEmail;
private final EmailSenderRepository emailSenderRepository;
private final DateTimeFormatter outputFormatter;
@Autowired
public EmailServiceImpl(EmailSenderRepository emailSenderRepository) {
this.emailSenderRepository = emailSenderRepository;
this.outputFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy 'à' HH:mm");
}
protected String buildCustomerReservationEmail(String name, String date, int nbPerson) {
return "<div dir=\"ltr\" style=\"font-family: Arial, sans-serif; background-color: #f6f6f6; padding: 20px;\">\n" +
" <table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"max-width: 600px; margin: auto; background-color: #ffffff; border-radius: 8px; overflow: hidden;\">\n" +
" <tr>\n" +
" <td style=\"padding: 20px; text-align: center;\">\n" +
" <h1 style=\"color: #333;\">Bienvenue au Restaurant le BLR!</h1>\n" +
" <img src=\"https://demo.stripocdn.email/content/guids/04fb15d2-fa4d-45a6-9edf-63e2a4d6d00a/images/group_3.png\" alt=\"Restaurant le BLR\" width=\"100%\" style=\"max-width: 600px; border-radius: 8px;\">\n" +
" <hr style=\"border: none; border-bottom: 4px solid #ccad53; margin: 20px 0;\">\n" +
" <h2 style=\"color: #333;\">Bonjour " + name + ",</h2>\n" +
" <p style=\"color: #555; line-height: 1.6;\">\n" +
" Merci beaucoup pour votre réservation chez <strong>Le BLR</strong>.<br>\n" +
" Nous sommes ravis de vous avoir parmi nous !<br>\n" +
" Vous avez effectué une réservation pour <strong>" + nbPerson + " " + (nbPerson > 1 ? "personnes" : "personne") + "</strong>.<br>\n" +
" Nous avons hâte de vous accueillir dans notre restaurant le <strong>" + date + "</strong> et de vous offrir une expérience inoubliable !<br>\n" +
" À très bientôt,<br>\n" +
" L'équipe du <strong>Le BLR</strong>\n" +
" </p>\n" +
" </td>\n" +
" </tr>\n" +
" </table>\n" +
"</div>";
}
@Override
public void sendCustomerReservationEmail(Reservation reservation) {
emailSenderRepository.send(reservation.getEmail(),
buildCustomerReservationEmail(reservation.getName(), reservation.getDate().format(outputFormatter), reservation.getNbPerson()));
}
@Override
public void sendAdminReservationEmail(Reservation reservation) {
emailSenderRepository.send(adminEmail,
buildCustomerReservationEmail(reservation.getName(), reservation.getDate().format(outputFormatter), reservation.getNbPerson()));
}
}

View File

@ -0,0 +1,86 @@
package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.exception.StorageException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Slf4j
@Service
public class FileSystemStorageService implements StorageService {
@Value("${file.upload-dir}")
String uploadDir;
@Override
public void init() {
Path directory = Paths.get(uploadDir);
if (!Files.exists(directory)) {
try {
log.info("Create directory {} to store images", directory);
Files.createDirectories(directory);
} catch (IOException e) {
throw new StorageException("Failed to create directory " + uploadDir, e);
}
}
}
@Override
public String upload(MultipartFile file, String newFileName) {
if (!file.isEmpty()) {
Path filePath = Paths.get(uploadDir, newFileName);
log.info("File path: {}", filePath);
try {
Files.write(filePath, file.getBytes());
} catch (IOException e) {
throw new StorageException("Failed to store file", e);
}
log.info("save {} to {}", file.getOriginalFilename(), newFileName);
return filePath.toString();
} else {
throw new StorageException("Failed to store file");
}
}
@Override
public Resource download(String fileName) {
try {
Path filePath = Paths.get(uploadDir).resolve(fileName).normalize();
log.info("Download file path: {}", fileName);
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists() || !resource.isReadable()) {
throw new StorageException("Could not read file: " + fileName);
}
return resource;
} catch (IOException e) {
throw new StorageException("Failed to download file " + fileName, e);
}
}
@Override
public void delete(String fileName) {
try {
Path filePath = Paths.get(uploadDir).resolve(fileName).normalize();
log.info("Delete file path: {}", fileName);
if (Files.exists(filePath)) {
Files.delete(filePath);
} else {
throw new StorageException("File does not exist: " + fileName);
}
} catch (IOException e) {
throw new StorageException("Failed to delete file " + fileName, e);
}
}
}

View File

@ -0,0 +1,56 @@
package com.dh7789dev.xpeditis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.MessageFormat;
import java.util.Locale;
@Service
public class NlsServiceImpl implements NlsService {
private final NlsRepository nlsRepository;
@Autowired
public NlsServiceImpl(NlsRepository nlsRepository) {
this.nlsRepository = nlsRepository;
}
/**
* @param key nls message key
* @return the message corresponding to the key translated into the desired language
*/
@Override
public String getMessage(String key) {
return nlsRepository.getMessage(key);
}
/**
* @param key nls message key
* @param parameters parameters to use in the message
* @return the message corresponding to the key translated into the desired language
*/
@Override
public String getMessage(String key, Object[] parameters) {
String message = nlsRepository.getMessage(key, parameters);
// escape simple quotes: double them
message = message.replace("'", "''").replace("{", "'{");
// format message
return MessageFormat.format(message, parameters);
}
private Locale getLocale(final Locale locale) {
Locale locale1;
if (locale != null) {
// hack for simplified chinese
if (locale.getLanguage().equals("zh") && locale.getCountry().isEmpty()) {
locale1 = Locale.SIMPLIFIED_CHINESE;
} else {
locale1 = locale;
}
} else {
locale1 = Locale.getDefault();
}
return locale1;
}
}

View File

@ -0,0 +1,21 @@
package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.ChangePasswordRequest;
import org.springframework.stereotype.Service;
import java.security.Principal;
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public void changePassword(ChangePasswordRequest request, Principal connectedUser) {
userRepository.changePassword(request, connectedUser);
}
}

View File

@ -0,0 +1,35 @@
package com.dh7789dev.xpeditis;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class MenuServiceImplTest {
@Mock
private MenuRepository menuRepository;
@InjectMocks
private MenuServiceImpl menuService;
@Test
void given_whenGetCategories_then() {
// given
when(menuRepository.findAll()).thenReturn(List.of());
// when
List<MenuCategory> categories = menuService.getCategories();
// then
assertThat(categories).isEmpty();
}
}

28
domain/spi/pom.xml Executable file
View File

@ -0,0 +1,28 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dh7789dev</groupId>
<artifactId>domain</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spi</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.dh7789dev</groupId>
<artifactId>data</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,9 @@
package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.AuthenticationRequest;
import com.dh7789dev.xpeditis.dto.AuthenticationResponse;
public interface AuthenticationRepository {
AuthenticationResponse authenticate(AuthenticationRequest request);
}

View File

@ -0,0 +1,6 @@
package com.dh7789dev.xpeditis;
public interface EmailSenderRepository {
void send(String emailAddressTo, String emailContent);
}

View File

@ -0,0 +1,17 @@
package com.dh7789dev.xpeditis;
public interface NlsRepository {
/**
* @param key nls message key
* @return the message corresponding to the key translated into the desired language
*/
String getMessage(String key);
/**
* @param key nls message key
* @param parameters parameters to use in the message
* @return the message corresponding to the key translated into the desired language
*/
String getMessage(String key, Object[] parameters);
}

View File

@ -0,0 +1,10 @@
package com.dh7789dev.xpeditis;
import com.dh7789dev.xpeditis.dto.ChangePasswordRequest;
import java.security.Principal;
public interface UserRepository {
void changePassword(ChangePasswordRequest request, Principal connectedUser);
}

4
entrypoint.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
# ./wait-for-it.sh db:3306 --timeout=60 --strict --
java -jar -Dserver.port=8080 xpeditis.jar

View File

@ -0,0 +1,5 @@
flyway.user=user
flyway.password=LEBLR_p4ssw0rd_971
flyway.schemas=leblr
flyway.url=jdbc:mysql://localhost:3306/leblr
flyway.locations=classpath:db/migration/structure,classpath:db/migration/data

151
infrastructure/pom.xml Executable file
View File

@ -0,0 +1,151 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dh7789dev</groupId>
<artifactId>xpeditis</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>infrastructure</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.flywaydb.version>11.3.1</org.flywaydb.version>
<io.jsonwebtoken.version>0.12.6</io.jsonwebtoken.version>
</properties>
<dependencies>
<dependency>
<groupId>com.dh7789dev</groupId>
<artifactId>spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<!-- spring-boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</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-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${io.jsonwebtoken.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${io.jsonwebtoken.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${io.jsonwebtoken.version}</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</profile>
<profile>
<id>prod</id>
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.2.0</version>
<scope>runtime</scope>
</dependency>
<!-- flyway dependency -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${org.flywaydb.version}</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
<version>${org.flywaydb.version}</version>
</dependency>
</dependencies>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>${org.flywaydb.version}</version>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,33 @@
package com.dh7789dev.xpeditis.configuration;
import com.dh7789dev.xpeditis.entity.UserEntity;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component(value = "auditorProvider")
public class AuditorAwareImpl implements AuditorAware<String> {
private static final String DEFAULT_AUDITOR = "SYSTEM";
@Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder
.getContext()
.getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.empty();
} else if (authentication instanceof AnonymousAuthenticationToken) {
return Optional.of(DEFAULT_AUDITOR);
}
UserEntity userPrincipal = (UserEntity) authentication.getPrincipal();
return Optional.ofNullable(userPrincipal.getUsername());
}
}

View File

@ -0,0 +1,17 @@
package com.dh7789dev.xpeditis.dao;
import com.dh7789dev.xpeditis.entity.EventEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
public interface EventDao extends JpaRepository<EventEntity, Long> {
Optional<EventEntity> findByName(String name);
List<EventEntity> findAllByDate(Instant date);
List<EventEntity> findAllByOrderByDateDescNameAsc();
}

View File

@ -0,0 +1,7 @@
package com.dh7789dev.xpeditis.dao;
import com.dh7789dev.xpeditis.entity.GalleryEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface GalleryDao extends JpaRepository<GalleryEntity, Long> {
}

View File

@ -0,0 +1,7 @@
package com.dh7789dev.xpeditis.dao;
import com.dh7789dev.xpeditis.entity.ImageEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ImageDao extends JpaRepository<ImageEntity, Long> {
}

View File

@ -0,0 +1,8 @@
package com.dh7789dev.xpeditis.dao;
import com.dh7789dev.xpeditis.entity.MenuCategoryEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MenuDao extends JpaRepository<MenuCategoryEntity, Long> {
}

View File

@ -0,0 +1,8 @@
package com.dh7789dev.xpeditis.dao;
import com.dh7789dev.xpeditis.entity.ProductEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductDao extends JpaRepository<ProductEntity, Long> {
}

View File

@ -0,0 +1,18 @@
package com.dh7789dev.xpeditis.dao;
import com.dh7789dev.xpeditis.entity.ReservationEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.time.LocalDateTime;
import java.util.List;
public interface ReservationDao extends JpaRepository<ReservationEntity, Long> {
List<ReservationEntity> findByName(String name);
@Query("SELECT r FROM ReservationEntity r WHERE r.date >= :startOfDay AND r.date < :endOfDay")
List<ReservationEntity> findReservationsByDates(@Param("startOfDay") LocalDateTime startOfDay,
@Param("endOfDay") LocalDateTime endOfDay);
}

View File

@ -0,0 +1,14 @@
package com.dh7789dev.xpeditis.dao;
import com.dh7789dev.xpeditis.entity.SelectedDayEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.time.LocalDate;
import java.util.List;
public interface SelectedDayDao extends JpaRepository<SelectedDayEntity, Long> {
@Query("SELECT day FROM SelectedDayEntity day WHERE day.date >= :date")
List<SelectedDayEntity> findAllFromDate(LocalDate date);
}

View File

@ -0,0 +1,20 @@
package com.dh7789dev.xpeditis.dao;
import com.dh7789dev.xpeditis.entity.TokenEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
import java.util.Optional;
public interface TokenDao extends JpaRepository<TokenEntity, Integer> {
Optional<TokenEntity> findByToken(String token);
@Query(value = """
select t from TokenEntity t inner join UserEntity u\s
on t.user.id = u.id\s
where u.id = :userId and (t.expired = false or t.revoked = false)\s
""")
List<TokenEntity> findAllValidTokenByUserId(Long userId);
}

View File

@ -0,0 +1,11 @@
package com.dh7789dev.xpeditis.dao;
import com.dh7789dev.xpeditis.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserDao extends JpaRepository<UserEntity, Long> {
Optional<UserEntity> findByUsername(String username);
}

View File

@ -0,0 +1,48 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.io.Serializable;
import java.time.Instant;
@EntityListeners(AuditingEntityListener.class)
@Getter
@Setter
@MappedSuperclass
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class BaseEntity implements Serializable {
@Id
@Column(name = "id", updatable = false, nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
// @Version
// @Column(name = "version")
// int version;
@CreatedDate
@Column(name = "created_date", updatable = false, nullable = false, columnDefinition = "TIMESTAMP default CURRENT_TIMESTAMP")
Instant createdDate;
@LastModifiedDate
@Column(name = "modified_date", updatable = false, nullable = false, columnDefinition = "TIMESTAMP default CURRENT_TIMESTAMP")
Instant modifiedDate;
@CreatedBy
@Column(name = "created_by", updatable = false, nullable = false, columnDefinition = "varchar(255) default 'SYSTEM'")
String createdBy;
@LastModifiedBy
@Column(name = "modified_by")
String modifiedBy;
}

View File

@ -0,0 +1,34 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.*;
import lombok.*;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import java.time.LocalDate;
import java.util.List;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
@Table(name = "event", uniqueConstraints = {
@UniqueConstraint(columnNames = {"name"})
})
public class EventEntity extends BaseEntity {
@Column(nullable = false, unique = true)
String name;
@Column
String description;
@Column(nullable = false)
LocalDate date;
@OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ImageEntity> images;
}

View File

@ -0,0 +1,30 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.*;
import lombok.*;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import java.util.List;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
@Table(name = "gallery", uniqueConstraints = {
@UniqueConstraint(columnNames = {"name"})
})
public class GalleryEntity extends BaseEntity {
@Column(unique = true)
String name;
@Column
String description;
@OneToMany(mappedBy = "gallery", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ImageEntity> images;
}

View File

@ -0,0 +1,33 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
@Entity
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
@Table(name = "image", uniqueConstraints = {
@UniqueConstraint(columnNames = {"name", "path"})
})
public class ImageEntity extends BaseEntity {
@Column(unique = true)
String name;
@Column(unique = true)
String path;
@ManyToOne
@JoinColumn(name = "event_id")
EventEntity event;
@ManyToOne
@JoinColumn(name = "gallery_id")
GalleryEntity gallery;
}

View File

@ -0,0 +1,29 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@Table(name = "menu_category", uniqueConstraints = {
@UniqueConstraint(columnNames = {"name"})
})
public class MenuCategoryEntity extends BaseEntity {
@Enumerated(EnumType.STRING)
@Column(name = "name", nullable = false, unique = true)
private MenuCategory.Name name;
@OneToMany(mappedBy = "category")
private List<ProductEntity> products;
}

View File

@ -0,0 +1,20 @@
package com.dh7789dev.xpeditis.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum Permission {
ADMIN_READ("admin:read"),
ADMIN_UPDATE("admin:update"),
ADMIN_CREATE("admin:create"),
ADMIN_DELETE("admin:delete"),
MANAGER_READ("management:read"),
MANAGER_UPDATE("management:update"),
MANAGER_CREATE("management:create"),
MANAGER_DELETE("management:delete");
private final String permission;
}

View File

@ -0,0 +1,34 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.*;
import lombok.*;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import java.math.BigDecimal;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
@Table(name = "product", uniqueConstraints = {
@UniqueConstraint(columnNames = {"name"})
})
public class ProductEntity extends BaseEntity {
@Column(nullable = false, unique = true)
String name;
@Column
String description;
@Column(nullable = false)
BigDecimal price;
@ManyToOne
@JoinColumn(name = "category_id")
private MenuCategoryEntity category;
}

View File

@ -0,0 +1,35 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "reservation")
public class ReservationEntity extends BaseEntity {
@Column(nullable = false)
String name;
@Column(nullable = false)
String email;
@Column
String phone;
@Column(nullable = false)
int nbPerson;
@Column(nullable = false)
LocalDateTime date;
}

View File

@ -0,0 +1,45 @@
package com.dh7789dev.xpeditis.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.dh7789dev.xpeditis.entity.Permission.*;
@Getter
@RequiredArgsConstructor
public enum Role {
USER(Collections.emptySet()),
ADMIN(Set.of(
ADMIN_READ,
ADMIN_UPDATE,
ADMIN_DELETE,
ADMIN_CREATE,
MANAGER_READ,
MANAGER_UPDATE,
MANAGER_DELETE,
MANAGER_CREATE)
),
MANAGER(Set.of(
MANAGER_READ,
MANAGER_UPDATE,
MANAGER_DELETE,
MANAGER_CREATE));
private final Set<Permission> permissions;
public List<SimpleGrantedAuthority> getAuthorities() {
var authorities = getPermissions()
.stream()
.map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
.collect(Collectors.toList());
authorities.add(new SimpleGrantedAuthority("ROLE_" + this.name()));
return authorities;
}
}

View File

@ -0,0 +1,22 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.*;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import java.time.LocalDate;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Accessors(chain = true)
@Table(name = "selected_day")
public class SelectedDayEntity extends BaseEntity {
LocalDate date;
}

View File

@ -0,0 +1,38 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@Table(name = "token", uniqueConstraints = {
@UniqueConstraint(columnNames = {"token"})
})
public class TokenEntity extends BaseEntity {
public enum Type {
BEARER
}
@Column(unique = true)
private String token;
@Enumerated(EnumType.STRING)
private Type tokenType = Type.BEARER;
private boolean revoked;
private boolean expired;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private UserEntity user;
}

View File

@ -0,0 +1,87 @@
package com.dh7789dev.xpeditis.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.NaturalId;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Entity
@Getter
@Setter
@Table(name = "users", uniqueConstraints = {
@UniqueConstraint(columnNames = {"username", "email"})
})
public class UserEntity extends BaseEntity implements UserDetails {
@NaturalId
@Column(nullable = false, unique = true, length = 50)
private String username;
@Column(name = "first_name", length = 50)
private String firstName;
@Column(name = "last_name", length = 50)
private String lastName;
@Column(nullable = false, unique = true, length = 50)
private String email;
@Column(nullable = false)
private String password;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
@Column(name = "enabled", nullable = false, columnDefinition = "BOOLEAN DEFAULT TRUE NOT NULL")
private boolean enabled;
@OneToMany(mappedBy = "user")
private List<TokenEntity> tokens;
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return role.getAuthorities();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public String toString() {
return "UserEntity(" + super.toString() + String.format("username=%s, firstName=%s, lastName=%s, email=%s, role=%s)",
username, firstName, lastName, email, role.name());
}
}

View File

@ -0,0 +1,25 @@
package com.dh7789dev.xpeditis.mapper;
import com.dh7789dev.xpeditis.entity.EventEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(/*componentModel = MappingConstants.ComponentModel.SPRING,*/
uses = {ImageMapper.class})
public interface EventMapper {
EventMapper INSTANCE = Mappers.getMapper(EventMapper.class);
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "modifiedDate", ignore = true)
@Mapping(target = "createdBy", ignore = true)
@Mapping(target = "modifiedBy", ignore = true)
EventEntity eventToEventEntity(Event event);
Event eventEntityToEvent(EventEntity eventEntity);
List<Event> eventEntitiesToEvents(List<EventEntity> eventEntities);
}

View File

@ -0,0 +1,22 @@
package com.dh7789dev.xpeditis.mapper;
import com.dh7789dev.xpeditis.dto.Gallery;
import com.dh7789dev.xpeditis.entity.GalleryEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper(/*componentModel = MappingConstants.ComponentModel.SPRING,*/
uses = {ImageMapper.class})
public interface GalleryMapper {
GalleryMapper INSTANCE = Mappers.getMapper(GalleryMapper.class);
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "modifiedDate", ignore = true)
@Mapping(target = "createdBy", ignore = true)
@Mapping(target = "modifiedBy", ignore = true)
GalleryEntity galleryToGalleryEntity(Gallery gallery);
Gallery galleryEntityToGallery(GalleryEntity galleryEntity);
}

View File

@ -0,0 +1,25 @@
package com.dh7789dev.xpeditis.mapper;
import com.dh7789dev.xpeditis.dto.Image;
import com.dh7789dev.xpeditis.entity.ImageEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ImageMapper {
ImageMapper INSTANCE = Mappers.getMapper(ImageMapper.class);
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "modifiedDate", ignore = true)
@Mapping(target = "createdBy", ignore = true)
@Mapping(target = "modifiedBy", ignore = true)
@Mapping(target = "event", ignore = true)
ImageEntity imageToImageEntity(Image image);
@Mapping(target = "path", ignore = true)
@Mapping(target = "uri", expression = "java(\"/api/v1/images/\" + imageEntity.getId())")
Image imageEntityToImage(ImageEntity imageEntity);
}

View File

@ -0,0 +1,27 @@
package com.dh7789dev.xpeditis.mapper;
import com.dh7789dev.xpeditis.entity.MenuCategoryEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(/*componentModel = MappingConstants.ComponentModel.SPRING,*/
uses = {ProductMapper.class})
public interface MenuCategoryMapper {
MenuCategoryMapper INSTANCE = Mappers.getMapper(MenuCategoryMapper.class);
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "modifiedDate", ignore = true)
@Mapping(target = "createdBy", ignore = true)
@Mapping(target = "modifiedBy", ignore = true)
@Mapping(source = "menuItems", target = "products")
MenuCategoryEntity menuCategoryToMenuCategoryEntity(MenuCategory menuCategory);
@Mapping(source = "products", target = "menuItems")
MenuCategory menuCategoryEntityToMenuCategory(MenuCategoryEntity menuCategoryEntity);
List<MenuCategory> menuCategoryEntitiesToMenuCategories(List<MenuCategoryEntity> menuCategoryEntities);
}

View File

@ -0,0 +1,26 @@
package com.dh7789dev.xpeditis.mapper;
import com.dh7789dev.xpeditis.entity.ProductEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ProductMapper {
ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "modifiedDate", ignore = true)
@Mapping(target = "createdBy", ignore = true)
@Mapping(target = "modifiedBy", ignore = true)
@Mapping(target = "category", ignore = true)
ProductEntity menuItemToProductEntity(MenuItem menuItem);
MenuItem productEntityToMenuItem(ProductEntity productEntity);
List<MenuItem> productEntitiesToMenuItems(List<ProductEntity> productEntities);
}

View File

@ -0,0 +1,25 @@
package com.dh7789dev.xpeditis.mapper;
import com.dh7789dev.xpeditis.entity.ReservationEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ReservationMapper {
ReservationMapper INSTANCE = Mappers.getMapper(ReservationMapper.class);
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "modifiedDate", ignore = true)
@Mapping(target = "createdBy", ignore = true)
@Mapping(target = "modifiedBy", ignore = true)
ReservationEntity reservationToReservationEntity(Reservation reservation);
Reservation reservationEntityToReservation(ReservationEntity reservationEntity);
List<Reservation> reservationEntitiesToReservation(List<ReservationEntity> reservationEntities);
}

View File

@ -0,0 +1,25 @@
package com.dh7789dev.xpeditis.mapper;
import com.dh7789dev.xpeditis.entity.SelectedDayEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface SelectedDayMapper {
SelectedDayMapper INSTANCE = Mappers.getMapper(SelectedDayMapper.class);
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "modifiedDate", ignore = true)
@Mapping(target = "createdBy", ignore = true)
@Mapping(target = "modifiedBy", ignore = true)
SelectedDayEntity selectedDayToSelectedDayEntity(SelectedDay selectedDay);
SelectedDay selectedDayEntityToSelectedDay(SelectedDayEntity selectedDayEntity);
List<SelectedDay> selectedDayEntitiesToSelectedDays(List<SelectedDayEntity> selectedDayEntities);
}

View File

@ -0,0 +1,75 @@
package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.AuthenticationRepository;
import com.dh7789dev.xpeditis.dao.TokenDao;
import com.dh7789dev.xpeditis.dao.UserDao;
import com.dh7789dev.xpeditis.dto.AuthenticationRequest;
import com.dh7789dev.xpeditis.dto.AuthenticationResponse;
import com.dh7789dev.xpeditis.entity.TokenEntity;
import com.dh7789dev.xpeditis.entity.UserEntity;
import com.dh7789dev.xpeditis.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthenticationJwtRepository implements AuthenticationRepository {
private final AuthenticationManager authenticationManager;
private final UserDao userDao;
private final TokenDao tokenDao;
private final JwtUtil jwtUtil;
@Override
public AuthenticationResponse authenticate(AuthenticationRequest request) {
log.info("username: {}, password: {}", request.getUsername(), request.getPassword());
UsernamePasswordAuthenticationToken authToken = UsernamePasswordAuthenticationToken
.unauthenticated(request.getUsername(), request.getPassword());
Authentication authentication = authenticationManager.authenticate(authToken);
if (!authentication.isAuthenticated()) {
throw new UsernameNotFoundException("Failed to authenticate");
}
var userEntity = userDao.findByUsername(request.getUsername()).orElseThrow();
var jwtToken = jwtUtil.generateToken(userEntity);
var refreshToken = jwtUtil.generateRefreshToken(userEntity);
revokeAllUserTokens(userEntity);
saveUserToken(userEntity, jwtToken);
return new AuthenticationResponse()
.setAccessToken(jwtToken)
.setRefreshToken(refreshToken)
.setCreatedAt(jwtUtil.extractCreatedAt(jwtToken))
.setExpiresAt(jwtUtil.extractExpiration(jwtToken));
}
private void saveUserToken(UserEntity user, String jwtToken) {
var tokenEntity = new TokenEntity()
.setUser(user)
.setToken(jwtToken)
.setTokenType(TokenEntity.Type.BEARER)
.setExpired(false)
.setRevoked(false);
tokenDao.save(tokenEntity);
}
private void revokeAllUserTokens(UserEntity userEntity) {
var validUserTokens = tokenDao.findAllValidTokenByUserId(userEntity.getId());
if (validUserTokens.isEmpty()) return;
validUserTokens.forEach(token -> {
token.setExpired(true);
token.setRevoked(true);
});
tokenDao.saveAll(validUserTokens);
}
}

View File

@ -0,0 +1,44 @@
package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.EmailSenderRepository;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Repository;
@Slf4j
@Repository
public class JavaMailSenderRepository implements EmailSenderRepository {
@Value("${application.email.from}")
String emailAddressFrom;
private final JavaMailSender emailSender;
@Autowired
public JavaMailSenderRepository(JavaMailSender emailSender) {
this.emailSender = emailSender;
}
@Async
@Override
public void send(String emailAddressTo, String emailContent) {
try {
MimeMessage mimeMessage = emailSender.createMimeMessage();
MimeMessageHelper helperMessage = new MimeMessageHelper(mimeMessage, "utf-8");
helperMessage.setText(emailContent, true);
helperMessage.setTo(emailAddressTo);
helperMessage.setSubject("Reservation Confirmation");
helperMessage.setFrom(emailAddressFrom);
emailSender.send(mimeMessage);
} catch (MessagingException e) {
log.error("failed to send email", e);
throw new IllegalStateException("failed to send email");
}
}
}

View File

@ -0,0 +1,46 @@
package com.dh7789dev.xpeditis.repository;
import com.dh7789dev.xpeditis.NlsRepository;
import com.dh7789dev.xpeditis.exception.NlsNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Repository;
@Slf4j
@Repository
public class NlsCatalogRepository implements NlsRepository {
private final MessageSource messageSource;
@Autowired
public NlsCatalogRepository(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* @param key nls message key
* @return the message corresponding to the key translated into the desired language
*/
@Override
public String getMessage(String key) {
return this.getMessage(key, null);
}
/**
* @param key nls message key
* @param parameters parameters to use in the message
* @return the message corresponding to the key translated into the desired language
*/
@Override
public String getMessage(String key, Object[] parameters) {
try {
return messageSource.getMessage(key, parameters, LocaleContextHolder.getLocale());
} catch (NoSuchMessageException e) {
log.warn(e.getMessage());
throw new NlsNotFoundException(key, LocaleContextHolder.getLocale().toString());
}
}
}

Some files were not shown because too many files have changed in this diff Show More