diff --git a/.gitignore b/.gitignore
index f8185d3..37fdfda 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
+.env
### STS ###
.apt_generated
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..59136de
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,30 @@
+version: "3"
+
+services:
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: XpenselyServer
+ ports:
+ - "8080:8080"
+ environment:
+ GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
+ GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
+ DB_PORT: 5432
+ DB_CONTAINER: ${DB_P_CONTAINER}
+ DB_NAME: ${DB_P_NAME}
+ DB_USERNAME: ${DB_USERNAME}
+ DB_PASSWORD: ${DB_PASSWORD}
+ depends_on:
+ - postgresdb
+
+ postgresdb:
+ image: postgres:14
+ container_name: postgresdb
+ ports:
+ - "5432:5432"
+ environment:
+ POSTGRES_DB: ${DB_P_NAME}
+ POSTGRES_USER: ${DB_USERNAME}
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
diff --git a/dockerfile b/dockerfile
new file mode 100644
index 0000000..f7e9a76
--- /dev/null
+++ b/dockerfile
@@ -0,0 +1,7 @@
+FROM openjdk:17-jdk-slim
+
+COPY ./target/*.jar app.jar
+
+EXPOSE 8080
+
+ENTRYPOINT ["java", "-jar", "app.jar"]
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 6abd81e..f7c275f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,9 +10,9 @@
de.zendric.app
XpenselyServer
- 0.0.1-SNAPSHOT
+ 1.0.0
XpenselyServer
- Demo project for Spring Boot
+ XpenselyServer used to handle the Xpensely App
@@ -50,7 +50,11 @@
org.springframework.boot
spring-boot-starter-web
-
+
+ io.github.cdimascio
+ dotenv-java
+ 3.1.0
+
org.springframework.boot
spring-boot-devtools
diff --git a/src/main/java/de/zendric/app/xpensely_server/XpenselyServerApplication.java b/src/main/java/de/zendric/app/xpensely_server/XpenselyServerApplication.java
index ae78652..d9678e8 100644
--- a/src/main/java/de/zendric/app/xpensely_server/XpenselyServerApplication.java
+++ b/src/main/java/de/zendric/app/xpensely_server/XpenselyServerApplication.java
@@ -4,11 +4,20 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
+import io.github.cdimascio.dotenv.Dotenv;
+
@SpringBootApplication
@EnableScheduling
public class XpenselyServerApplication {
public static void main(String[] args) {
+ Dotenv dotenv = Dotenv.load(); // Loads the .env file
+ System.setProperty("GOOGLE_CLIENT_ID", dotenv.get("GOOGLE_CLIENT_ID"));
+ System.setProperty("GOOGLE_CLIENT_SECRET", dotenv.get("GOOGLE_CLIENT_SECRET"));
+ System.setProperty("DB_USERNAME", dotenv.get("DB_USERNAME"));
+ System.setProperty("DB_PASSWORD", dotenv.get("DB_PASSWORD"));
+ System.setProperty("DB_DEV_CONTAINER", dotenv.get("DB_DEV_CONTAINER"));
+ System.setProperty("DB_DEV_NAME", dotenv.get("DB_DEV_NAME"));
SpringApplication.run(XpenselyServerApplication.class, args);
}
diff --git a/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java b/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java
index 4332c3c..e0b9994 100644
--- a/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java
+++ b/src/main/java/de/zendric/app/xpensely_server/controller/AppUserController.java
@@ -3,7 +3,6 @@ package de.zendric.app.xpensely_server.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
-import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -13,6 +12,8 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import de.zendric.app.xpensely_server.model.AppUser;
+import de.zendric.app.xpensely_server.model.AppUserCreateRequest;
+import de.zendric.app.xpensely_server.model.UsernameAlreadyExistsException;
import de.zendric.app.xpensely_server.services.UserService;
@RestController
@@ -36,18 +37,32 @@ public class AppUserController {
return userService.getUserByName(username);
}
- @PostMapping
- public ResponseEntity createUser(@RequestBody AppUser user) {
- AppUser appUser = userService.createUser(user);
- return ResponseEntity.status(HttpStatus.CREATED).body(appUser);
+ @GetMapping("/byGoogleId")
+ public ResponseEntity getUserByGoogleId(@RequestParam String id) {
+ try {
+ AppUser userByGoogleId = userService.getUserByGoogleId(id);
+ return new ResponseEntity<>(userByGoogleId, HttpStatus.OK);
+
+ } catch (IllegalArgumentException e) {
+ return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
+ } catch (Exception e) {
+ return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
}
@PostMapping("/createUser")
- public ResponseEntity> createUsername(@RequestBody AppUser user, Authentication authentication) {
- String googleUserId = authentication.getName();
- // Validate and store the username with googleUserId
+ public ResponseEntity createUser(@RequestBody AppUserCreateRequest userRequest) {
+ try {
+ AppUser convertedUser = userRequest.convertToAppUser();
+
+ AppUser nUser = userService.createUser(convertedUser);
+ return new ResponseEntity<>(nUser, HttpStatus.CREATED);
+ } catch (UsernameAlreadyExistsException e) {
+ return new ResponseEntity<>(null, HttpStatus.CONFLICT);
+ } catch (Exception e) {
+ return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
+ }
- return ResponseEntity.ok("Username created successfully");
}
@DeleteMapping
diff --git a/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java b/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java
index dfec7ae..3a407fb 100644
--- a/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java
+++ b/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java
@@ -95,7 +95,7 @@ class ExpenseListController {
}
}
- @PostMapping
+ @PostMapping("/create")
public ResponseEntity create(@RequestBody ExpenseList expenseList) {
try {
if (expenseList.getOwner() != null) {
diff --git a/src/main/java/de/zendric/app/xpensely_server/model/AppUser.java b/src/main/java/de/zendric/app/xpensely_server/model/AppUser.java
index 2b495e2..90a0cd9 100644
--- a/src/main/java/de/zendric/app/xpensely_server/model/AppUser.java
+++ b/src/main/java/de/zendric/app/xpensely_server/model/AppUser.java
@@ -1,10 +1,17 @@
package de.zendric.app.xpensely_server.model;
+import java.time.LocalDateTime;
+
+import org.hibernate.annotations.CreationTimestamp;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -13,6 +20,7 @@ import lombok.Setter;
@Setter
@NoArgsConstructor
@Entity
+@EqualsAndHashCode
public class AppUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -21,4 +29,12 @@ public class AppUser {
@Column(name = "username", nullable = false, unique = true)
private String username;
+ @JsonIgnore
+ private String googleId;
+
+ @Column(updatable = false)
+ @CreationTimestamp
+ @JsonIgnore
+ private LocalDateTime createdAt;
+
}
diff --git a/src/main/java/de/zendric/app/xpensely_server/model/AppUserCreateRequest.java b/src/main/java/de/zendric/app/xpensely_server/model/AppUserCreateRequest.java
new file mode 100644
index 0000000..3ae678d
--- /dev/null
+++ b/src/main/java/de/zendric/app/xpensely_server/model/AppUserCreateRequest.java
@@ -0,0 +1,21 @@
+package de.zendric.app.xpensely_server.model;
+
+import jakarta.persistence.Column;
+import lombok.Data;
+
+@Data
+public class AppUserCreateRequest {
+
+ @Column(name = "username", nullable = false, unique = true)
+ private String username;
+
+ private String googleId;
+
+ public AppUser convertToAppUser() {
+ AppUser appUser = new AppUser();
+ appUser.setGoogleId(googleId);
+ appUser.setUsername(username);
+
+ return appUser;
+ }
+}
diff --git a/src/main/java/de/zendric/app/xpensely_server/model/UsernameAlreadyExistsException.java b/src/main/java/de/zendric/app/xpensely_server/model/UsernameAlreadyExistsException.java
new file mode 100644
index 0000000..b0645d8
--- /dev/null
+++ b/src/main/java/de/zendric/app/xpensely_server/model/UsernameAlreadyExistsException.java
@@ -0,0 +1,11 @@
+package de.zendric.app.xpensely_server.model;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.CONFLICT)
+public class UsernameAlreadyExistsException extends RuntimeException {
+ public UsernameAlreadyExistsException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/zendric/app/xpensely_server/repo/UserRepository.java b/src/main/java/de/zendric/app/xpensely_server/repo/UserRepository.java
index 23bb4ee..a35ba4f 100644
--- a/src/main/java/de/zendric/app/xpensely_server/repo/UserRepository.java
+++ b/src/main/java/de/zendric/app/xpensely_server/repo/UserRepository.java
@@ -10,4 +10,8 @@ import de.zendric.app.xpensely_server.model.AppUser;
@Repository
public interface UserRepository extends JpaRepository {
Optional findByUsername(String username);
+
+ Optional findByGoogleId(String id);
+
+ Boolean existsByUsername(String username);
}
diff --git a/src/main/java/de/zendric/app/xpensely_server/security/SecurityConfig.java b/src/main/java/de/zendric/app/xpensely_server/security/SecurityConfig.java
index 72d7e71..b4883a3 100644
--- a/src/main/java/de/zendric/app/xpensely_server/security/SecurityConfig.java
+++ b/src/main/java/de/zendric/app/xpensely_server/security/SecurityConfig.java
@@ -10,28 +10,26 @@ import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
- // @Bean
- // public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
- // Exception {
- // http.authorizeHttpRequests(auth -> auth
- // .anyRequest().permitAll()).csrf().disable();
- // ;
+ // @Bean
+ // public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
+ // Exception {
+ // http.authorizeHttpRequests(auth -> auth
+ // .anyRequest().permitAll()).csrf().disable();
+ // ;
- // return http.build();
- // }
+ // return http.build();
+ // }
- @Bean
- public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
- http
- .authorizeHttpRequests(auth -> auth
- .anyRequest().authenticated() // Require authentication for all requests
- )
- .oauth2ResourceServer(oauth2 -> oauth2
- .jwt(Customizer.withDefaults()) // Enable JWT validation
- )
- .oauth2Login(Customizer.withDefaults()) // Optional if you want OAuth2 login
- .csrf().disable(); // Disable CSRF for simplicity in APIs (consider enabling it for forms)
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .authorizeHttpRequests(auth -> auth
+ .anyRequest().authenticated())
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(Customizer.withDefaults()))
+ .oauth2Login(Customizer.withDefaults())
+ .csrf().disable();
- return http.build();
- }
+ return http.build();
+ }
}
diff --git a/src/main/java/de/zendric/app/xpensely_server/services/UserService.java b/src/main/java/de/zendric/app/xpensely_server/services/UserService.java
index 6475a49..58298dc 100644
--- a/src/main/java/de/zendric/app/xpensely_server/services/UserService.java
+++ b/src/main/java/de/zendric/app/xpensely_server/services/UserService.java
@@ -6,6 +6,7 @@ import java.util.Optional;
import org.springframework.stereotype.Service;
import de.zendric.app.xpensely_server.model.AppUser;
+import de.zendric.app.xpensely_server.model.UsernameAlreadyExistsException;
import de.zendric.app.xpensely_server.repo.UserRepository;
@Service
@@ -21,6 +22,9 @@ public class UserService {
}
public AppUser createUser(AppUser user) {
+ if (Boolean.TRUE.equals(userRepository.existsByUsername(user.getUsername()))) {
+ throw new UsernameAlreadyExistsException("Username already exists");
+ }
return userRepository.save(user);
}
@@ -49,4 +53,12 @@ public class UserService {
return null;
}
+ public AppUser getUserByGoogleId(String id) {
+ Optional optUser = userRepository.findByGoogleId(id);
+ if (optUser.isPresent()) {
+ return optUser.get();
+ } else
+ return null;
+ }
+
}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 5591bdb..6983f6d 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -3,12 +3,14 @@ spring.application.name=XpenselyServer
#Security
spring.security.enabled=false
-#logging.level.org.springframework.security=TRACE
+spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
+spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
+spring.security.oauth2.resourceserver.jwt.issuer-uri=https://accounts.google.com
# PostgreSQL Configuration
-spring.datasource.url=jdbc:postgresql://localhost:5432/Xpensely
-spring.datasource.username=${XpenselyDBUser}
-spring.datasource.password=${XpenselyDBPW}
+spring.datasource.url=jdbc:postgresql://${DB_DEV_CONTAINER}:5432/${DB_DEV_NAME}
+spring.datasource.username=${DB_USERNAME}
+spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
# Hibernate configuration