initial Release request #1

Merged
Cedric merged 34 commits from dev into main 2025-01-12 10:46:30 +01:00
14 changed files with 168 additions and 38 deletions
Showing only changes of commit 5546b0ba3b - Show all commits

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
.env
### STS ###
.apt_generated

30
docker-compose.yml Normal file
View File

@@ -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}

7
dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM openjdk:17-jdk-slim
COPY ./target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

10
pom.xml
View File

@@ -10,9 +10,9 @@
</parent>
<groupId>de.zendric.app</groupId>
<artifactId>XpenselyServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>1.0.0</version>
<name>XpenselyServer</name>
<description>Demo project for Spring Boot</description>
<description>XpenselyServer used to handle the Xpensely App</description>
<url/>
<licenses>
<license/>
@@ -50,7 +50,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>

View File

@@ -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);
}

View File

@@ -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<AppUser> createUser(@RequestBody AppUser user) {
AppUser appUser = userService.createUser(user);
return ResponseEntity.status(HttpStatus.CREATED).body(appUser);
@GetMapping("/byGoogleId")
public ResponseEntity<AppUser> 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<AppUser> 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

View File

@@ -95,7 +95,7 @@ class ExpenseListController {
}
}
@PostMapping
@PostMapping("/create")
public ResponseEntity<ExpenseList> create(@RequestBody ExpenseList expenseList) {
try {
if (expenseList.getOwner() != null) {

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -10,4 +10,8 @@ import de.zendric.app.xpensely_server.model.AppUser;
@Repository
public interface UserRepository extends JpaRepository<AppUser, Long> {
Optional<AppUser> findByUsername(String username);
Optional<AppUser> findByGoogleId(String id);
Boolean existsByUsername(String username);
}

View File

@@ -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();
}
}

View File

@@ -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<AppUser> optUser = userRepository.findByGoogleId(id);
if (optUser.isPresent()) {
return optUser.get();
} else
return null;
}
}

View File

@@ -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