initial Release request #1
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ target/
|
|||||||
!.mvn/wrapper/maven-wrapper.jar
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
!**/src/main/**/target/
|
!**/src/main/**/target/
|
||||||
!**/src/test/**/target/
|
!**/src/test/**/target/
|
||||||
|
.env
|
||||||
|
|
||||||
### STS ###
|
### STS ###
|
||||||
.apt_generated
|
.apt_generated
|
||||||
|
|||||||
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal 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
7
dockerfile
Normal 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
10
pom.xml
@@ -10,9 +10,9 @@
|
|||||||
</parent>
|
</parent>
|
||||||
<groupId>de.zendric.app</groupId>
|
<groupId>de.zendric.app</groupId>
|
||||||
<artifactId>XpenselyServer</artifactId>
|
<artifactId>XpenselyServer</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>1.0.0</version>
|
||||||
<name>XpenselyServer</name>
|
<name>XpenselyServer</name>
|
||||||
<description>Demo project for Spring Boot</description>
|
<description>XpenselyServer used to handle the Xpensely App</description>
|
||||||
<url/>
|
<url/>
|
||||||
<licenses>
|
<licenses>
|
||||||
<license/>
|
<license/>
|
||||||
@@ -50,7 +50,11 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.cdimascio</groupId>
|
||||||
|
<artifactId>dotenv-java</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-devtools</artifactId>
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
|||||||
@@ -4,11 +4,20 @@ import org.springframework.boot.SpringApplication;
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
import io.github.cdimascio.dotenv.Dotenv;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
public class XpenselyServerApplication {
|
public class XpenselyServerApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
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);
|
SpringApplication.run(XpenselyServerApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package de.zendric.app.xpensely_server.controller;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
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 org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import de.zendric.app.xpensely_server.model.AppUser;
|
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;
|
import de.zendric.app.xpensely_server.services.UserService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -36,18 +37,32 @@ public class AppUserController {
|
|||||||
return userService.getUserByName(username);
|
return userService.getUserByName(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@GetMapping("/byGoogleId")
|
||||||
public ResponseEntity<AppUser> createUser(@RequestBody AppUser user) {
|
public ResponseEntity<AppUser> getUserByGoogleId(@RequestParam String id) {
|
||||||
AppUser appUser = userService.createUser(user);
|
try {
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(appUser);
|
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")
|
@PostMapping("/createUser")
|
||||||
public ResponseEntity<?> createUsername(@RequestBody AppUser user, Authentication authentication) {
|
public ResponseEntity<AppUser> createUser(@RequestBody AppUserCreateRequest userRequest) {
|
||||||
String googleUserId = authentication.getName();
|
try {
|
||||||
// Validate and store the username with googleUserId
|
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
|
@DeleteMapping
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class ExpenseListController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping("/create")
|
||||||
public ResponseEntity<ExpenseList> create(@RequestBody ExpenseList expenseList) {
|
public ResponseEntity<ExpenseList> create(@RequestBody ExpenseList expenseList) {
|
||||||
try {
|
try {
|
||||||
if (expenseList.getOwner() != null) {
|
if (expenseList.getOwner() != null) {
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
package de.zendric.app.xpensely_server.model;
|
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.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import jakarta.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
@@ -13,6 +20,7 @@ import lombok.Setter;
|
|||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Entity
|
@Entity
|
||||||
|
@EqualsAndHashCode
|
||||||
public class AppUser {
|
public class AppUser {
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@@ -21,4 +29,12 @@ public class AppUser {
|
|||||||
@Column(name = "username", nullable = false, unique = true)
|
@Column(name = "username", nullable = false, unique = true)
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private String googleId;
|
||||||
|
|
||||||
|
@Column(updatable = false)
|
||||||
|
@CreationTimestamp
|
||||||
|
@JsonIgnore
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,4 +10,8 @@ import de.zendric.app.xpensely_server.model.AppUser;
|
|||||||
@Repository
|
@Repository
|
||||||
public interface UserRepository extends JpaRepository<AppUser, Long> {
|
public interface UserRepository extends JpaRepository<AppUser, Long> {
|
||||||
Optional<AppUser> findByUsername(String username);
|
Optional<AppUser> findByUsername(String username);
|
||||||
|
|
||||||
|
Optional<AppUser> findByGoogleId(String id);
|
||||||
|
|
||||||
|
Boolean existsByUsername(String username);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,11 @@ public class SecurityConfig {
|
|||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.anyRequest().authenticated() // Require authentication for all requests
|
.anyRequest().authenticated())
|
||||||
)
|
|
||||||
.oauth2ResourceServer(oauth2 -> oauth2
|
.oauth2ResourceServer(oauth2 -> oauth2
|
||||||
.jwt(Customizer.withDefaults()) // Enable JWT validation
|
.jwt(Customizer.withDefaults()))
|
||||||
)
|
.oauth2Login(Customizer.withDefaults())
|
||||||
.oauth2Login(Customizer.withDefaults()) // Optional if you want OAuth2 login
|
.csrf().disable();
|
||||||
.csrf().disable(); // Disable CSRF for simplicity in APIs (consider enabling it for forms)
|
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import java.util.Optional;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import de.zendric.app.xpensely_server.model.AppUser;
|
import de.zendric.app.xpensely_server.model.AppUser;
|
||||||
|
import de.zendric.app.xpensely_server.model.UsernameAlreadyExistsException;
|
||||||
import de.zendric.app.xpensely_server.repo.UserRepository;
|
import de.zendric.app.xpensely_server.repo.UserRepository;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -21,6 +22,9 @@ public class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AppUser createUser(AppUser user) {
|
public AppUser createUser(AppUser user) {
|
||||||
|
if (Boolean.TRUE.equals(userRepository.existsByUsername(user.getUsername()))) {
|
||||||
|
throw new UsernameAlreadyExistsException("Username already exists");
|
||||||
|
}
|
||||||
return userRepository.save(user);
|
return userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,4 +53,12 @@ public class UserService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AppUser getUserByGoogleId(String id) {
|
||||||
|
Optional<AppUser> optUser = userRepository.findByGoogleId(id);
|
||||||
|
if (optUser.isPresent()) {
|
||||||
|
return optUser.get();
|
||||||
|
} else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,12 +3,14 @@ spring.application.name=XpenselyServer
|
|||||||
|
|
||||||
#Security
|
#Security
|
||||||
spring.security.enabled=false
|
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
|
# PostgreSQL Configuration
|
||||||
spring.datasource.url=jdbc:postgresql://localhost:5432/Xpensely
|
spring.datasource.url=jdbc:postgresql://${DB_DEV_CONTAINER}:5432/${DB_DEV_NAME}
|
||||||
spring.datasource.username=${XpenselyDBUser}
|
spring.datasource.username=${DB_USERNAME}
|
||||||
spring.datasource.password=${XpenselyDBPW}
|
spring.datasource.password=${DB_PASSWORD}
|
||||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
|
|
||||||
# Hibernate configuration
|
# Hibernate configuration
|
||||||
|
|||||||
Reference in New Issue
Block a user