More ktor parity work
This commit is contained in:
parent
e84dedf675
commit
eff486898f
35 changed files with 295 additions and 43 deletions
|
@ -1,23 +1,18 @@
|
|||
apply plugin: 'java'
|
||||
apply plugin: 'application'
|
||||
apply plugin: "io.spring.dependency-management"
|
||||
apply plugin: "org.springframework.boot"
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "io.spring.dependency-management"
|
||||
id "org.springframework.boot"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||
implementation "org.springframework.session:spring-session-jdbc"
|
||||
implementation "org.springframework.boot:spring-boot-starter-web"
|
||||
runtimeOnly "org.postgresql:postgresql:42.2.23"
|
||||
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-test"
|
||||
testImplementation "org.springframework.security:spring-security-test:5.1.5.RELEASE"
|
||||
testImplementation "org.springframework.security:spring-security-test"
|
||||
}
|
||||
|
||||
jar {
|
||||
description = "twigs"
|
||||
}
|
||||
|
||||
mainClassName = "com.wbrawner.twigs.TwigsServerApplication"
|
||||
|
||||
sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.wbrawner.twigs.session.SessionResponse;
|
|||
import com.wbrawner.twigs.session.UserSessionRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
|
@ -34,6 +35,7 @@ public class UserController {
|
|||
private final PasswordEncoder passwordEncoder;
|
||||
private final UserPermissionRepository userPermissionsRepository;
|
||||
private final UserSessionRepository userSessionRepository;
|
||||
private final UserService userService;
|
||||
private final DaoAuthenticationProvider authenticationProvider;
|
||||
|
||||
@Autowired
|
||||
|
@ -43,13 +45,14 @@ public class UserController {
|
|||
UserSessionRepository userSessionRepository,
|
||||
PasswordEncoder passwordEncoder,
|
||||
UserPermissionRepository userPermissionsRepository,
|
||||
DaoAuthenticationProvider authenticationProvider
|
||||
UserService userService, DaoAuthenticationProvider authenticationProvider
|
||||
) {
|
||||
this.budgetRepository = budgetRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.userSessionRepository = userSessionRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.userPermissionsRepository = userPermissionsRepository;
|
||||
this.userService = userService;
|
||||
this.authenticationProvider = authenticationProvider;
|
||||
}
|
||||
|
||||
|
@ -75,17 +78,13 @@ public class UserController {
|
|||
|
||||
@PostMapping(path = "/login", produces = {MediaType.APPLICATION_JSON_VALUE})
|
||||
ResponseEntity<SessionResponse> login(@RequestBody LoginRequest request) {
|
||||
var authReq = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword());
|
||||
Authentication auth;
|
||||
try {
|
||||
auth = authenticationProvider.authenticate(authReq);
|
||||
return ResponseEntity.ok(new SessionResponse(
|
||||
userService.login(request.getUsername(), request.getPassword())
|
||||
));
|
||||
} catch (AuthenticationException e) {
|
||||
return ResponseEntity.notFound().build();
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
||||
}
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
var user = Objects.requireNonNull(getCurrentUser());
|
||||
var session = userSessionRepository.save(new Session(user.getId()));
|
||||
return ResponseEntity.ok(new SessionResponse(session));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/me", produces = {MediaType.APPLICATION_JSON_VALUE})
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/twigs
|
||||
spring.datasource.username=twigs
|
||||
spring.datasource.password=twigs
|
||||
spring.datasource.url=jdbc:h2:./twigs.db
|
||||
spring.profiles.active=prod
|
||||
spring.session.jdbc.initialize-schema=always
|
||||
spring.datasource.testWhileIdle=true
|
||||
spring.datasource.timeBetweenEvictionRunsMillis=60000
|
||||
spring.datasource.validationQuery=SELECT 1
|
||||
twigs.cors.domains=*
|
||||
twigs.cors.domains=http://localhost:4200
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
|
|
25
app/build.gradle
Normal file
25
app/build.gradle
Normal file
|
@ -0,0 +1,25 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "io.spring.dependency-management"
|
||||
id "org.springframework.boot"
|
||||
id 'org.graalvm.buildtools.native' version '0.9.18'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation project(':api')
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||
implementation "org.springframework.boot:spring-boot-starter-web"
|
||||
implementation "org.springframework.session:spring-session-jdbc"
|
||||
runtimeOnly 'org.postgresql:postgresql:42.2.27'
|
||||
runtimeOnly 'com.h2database:h2:2.2.220'
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-test"
|
||||
}
|
||||
|
||||
jar {
|
||||
description = "twigs"
|
||||
}
|
||||
|
||||
mainClassName = "com.wbrawner.twigs.TwigsServerApplication"
|
|
@ -1,6 +1,6 @@
|
|||
package com.wbrawner.twigs.config;
|
||||
|
||||
import com.wbrawner.twigs.passwordresetrequest.PasswordResetRequestRepository;
|
||||
import com.wbrawner.twigs.user.PasswordResetRequestRepository;
|
||||
import com.wbrawner.twigs.session.UserSessionRepository;
|
||||
import com.wbrawner.twigs.user.UserRepository;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -11,7 +11,6 @@ import org.springframework.security.authentication.AuthenticationManager;
|
|||
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.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.JdbcUserDetailsManager;
|
||||
|
@ -80,10 +79,12 @@ public class SecurityConfig {
|
|||
return httpSecurity.authorizeHttpRequests((authz) -> {
|
||||
try {
|
||||
authz
|
||||
.requestMatchers("/api/users/register", "/api/users/login")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.requestMatchers(
|
||||
"/api/^(users/register|users/login)"
|
||||
)
|
||||
.authenticated()
|
||||
.anyRequest()
|
||||
.permitAll()
|
||||
.and()
|
||||
.httpBasic()
|
||||
.authenticationEntryPoint(new SilentAuthenticationEntryPoint())
|
||||
|
@ -111,9 +112,7 @@ public class SecurityConfig {
|
|||
.and()
|
||||
.csrf()
|
||||
.disable()
|
||||
.addFilter(new TokenAuthenticationFilter(authenticationManager))
|
||||
.sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||
.addFilter(new TokenAuthenticationFilter(authenticationManager));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.wbrawner.twigs.config;
|
||||
|
||||
import com.wbrawner.twigs.Utils;
|
||||
import com.wbrawner.twigs.session.UserSessionRepository;
|
||||
import com.wbrawner.twigs.user.UserRepository;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
|
@ -47,7 +48,7 @@ public class TokenAuthenticationProvider extends DaoAuthenticationProvider {
|
|||
new Thread(() -> {
|
||||
// Update the session on a background thread to avoid holding up the request longer than necessary
|
||||
var updatedSession = session.get();
|
||||
updatedSession.setExpiration(twoWeeksFromNow());
|
||||
updatedSession.setExpiration(Utils.twoWeeksFromNow());
|
||||
userSessionRepository.save(updatedSession);
|
||||
}).start();
|
||||
return new SessionAuthenticationToken(
|
22
app/src/main/java/com/wbrawner/twigs/config/WebConfig.java
Normal file
22
app/src/main/java/com/wbrawner/twigs/config/WebConfig.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
//package com.wbrawner.twigs.config;
|
||||
//
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
//import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
//
|
||||
//@Configuration
|
||||
//public class WebConfig implements WebMvcConfigurer {
|
||||
//
|
||||
// @Override
|
||||
// public void addViewControllers(ViewControllerRegistry registry) {
|
||||
// registry.addViewController("/").setViewName("forward:/index.html");
|
||||
// registry.addViewController("/{path:\\w*}").setViewName("forward:/index.html");
|
||||
// registry.addViewController("/(^api)/**").setViewName("forward:/index.html");
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// registry.addResourceHandler("/**").addResourceLocations("classpath:/webapp/");
|
||||
// }
|
||||
//}
|
12
core/build.gradle
Normal file
12
core/build.gradle
Normal file
|
@ -0,0 +1,12 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "io.spring.dependency-management"
|
||||
id "org.springframework.boot"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||
implementation "org.springframework.boot:spring-boot-starter-web"
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.wbrawner.twigs.budget;
|
||||
|
||||
import com.wbrawner.twigs.Utils;
|
||||
import com.wbrawner.twigs.category.Category;
|
||||
import com.wbrawner.twigs.transaction.Transaction;
|
||||
import jakarta.persistence.Entity;
|
||||
|
@ -15,7 +16,7 @@ import static com.wbrawner.twigs.Utils.randomId;
|
|||
@Entity
|
||||
public class Budget {
|
||||
@Id
|
||||
private String id = randomId();
|
||||
private String id = Utils.randomId();
|
||||
private String name;
|
||||
private String description;
|
||||
private String currencyCode;
|
|
@ -0,0 +1,127 @@
|
|||
package com.wbrawner.twigs.recurringtransaction;
|
||||
|
||||
import com.wbrawner.twigs.budget.Budget;
|
||||
import com.wbrawner.twigs.category.Category;
|
||||
import com.wbrawner.twigs.user.User;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import static com.wbrawner.twigs.Utils.randomId;
|
||||
|
||||
@Entity
|
||||
public class RecurringTransaction implements Comparable<RecurringTransaction> {
|
||||
@Id
|
||||
private final String id = randomId();
|
||||
@ManyToOne
|
||||
@JoinColumn(nullable = false)
|
||||
private final User createdBy;
|
||||
private String title;
|
||||
private String description;
|
||||
private Instant date;
|
||||
private Long amount;
|
||||
@ManyToOne
|
||||
private Category category;
|
||||
private Boolean expense;
|
||||
@ManyToOne
|
||||
@JoinColumn(nullable = false)
|
||||
private Budget budget;
|
||||
|
||||
public RecurringTransaction() {
|
||||
this(null, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public RecurringTransaction(
|
||||
String title,
|
||||
String description,
|
||||
Instant date,
|
||||
Long amount,
|
||||
Category category,
|
||||
Boolean expense,
|
||||
User createdBy,
|
||||
Budget budget
|
||||
) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.date = date;
|
||||
this.amount = amount;
|
||||
this.category = category;
|
||||
this.expense = expense;
|
||||
this.createdBy = createdBy;
|
||||
this.budget = budget;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
// This should only be set from Hibernate so it shouldn't actually be null ever
|
||||
//noinspection ConstantConditions
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Instant getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(Instant date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public Long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(Long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public Category getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(Category category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public Boolean getExpense() {
|
||||
return expense;
|
||||
}
|
||||
|
||||
public void setExpense(Boolean expense) {
|
||||
this.expense = expense;
|
||||
}
|
||||
|
||||
public User getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public Budget getBudget() {
|
||||
return budget;
|
||||
}
|
||||
|
||||
public void setBudget(Budget budget) {
|
||||
this.budget = budget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RecurringTransaction other) {
|
||||
return this.date.compareTo(other.date);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.wbrawner.twigs.recurringtransaction;
|
||||
|
||||
import com.wbrawner.twigs.budget.Budget;
|
||||
import com.wbrawner.twigs.category.Category;
|
||||
import com.wbrawner.twigs.transaction.Transaction;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface RecurringTransactionRepository extends CrudRepository<RecurringTransaction, String>,
|
||||
PagingAndSortingRepository<RecurringTransaction, String> {
|
||||
Optional<Transaction> findByIdAndBudgetIn(String id, List<Budget> budgets);
|
||||
|
||||
List<Transaction> findAllByBudgetInAndCategoryInAndDateGreaterThanAndDateLessThan(
|
||||
List<Budget> budgets,
|
||||
List<Category> categories,
|
||||
Instant start,
|
||||
Instant end,
|
||||
Pageable pageable
|
||||
);
|
||||
|
||||
List<Transaction> findAllByBudgetInAndDateGreaterThanAndDateLessThan(
|
||||
List<Budget> budgets,
|
||||
Instant start,
|
||||
Instant end,
|
||||
Pageable pageable
|
||||
);
|
||||
|
||||
List<Transaction> findAllByBudgetAndCategory(Budget budget, Category category);
|
||||
|
||||
void deleteAllByBudget(Budget budget);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.wbrawner.twigs.passwordresetrequest;
|
||||
package com.wbrawner.twigs.user;
|
||||
|
||||
import com.wbrawner.twigs.user.User;
|
||||
import jakarta.persistence.Entity;
|
|
@ -1,4 +1,4 @@
|
|||
package com.wbrawner.twigs.passwordresetrequest;
|
||||
package com.wbrawner.twigs.user;
|
||||
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
|
37
core/src/main/java/com/wbrawner/twigs/user/UserService.java
Normal file
37
core/src/main/java/com/wbrawner/twigs/user/UserService.java
Normal file
|
@ -0,0 +1,37 @@
|
|||
package com.wbrawner.twigs.user;
|
||||
|
||||
import com.wbrawner.twigs.session.Session;
|
||||
import com.wbrawner.twigs.session.UserSessionRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.wbrawner.twigs.Utils.getCurrentUser;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
private final DaoAuthenticationProvider authenticationProvider;
|
||||
private final UserRepository userRepository;
|
||||
private final UserSessionRepository userSessionRepository;
|
||||
|
||||
@Autowired
|
||||
public UserService(DaoAuthenticationProvider authenticationProvider, UserRepository userRepository, UserSessionRepository userSessionRepository) {
|
||||
this.authenticationProvider = authenticationProvider;
|
||||
this.userRepository = userRepository;
|
||||
this.userSessionRepository = userSessionRepository;
|
||||
}
|
||||
|
||||
public Session login(String username, String password) throws AuthenticationException {
|
||||
var authReq = new UsernamePasswordAuthenticationToken(username, password);
|
||||
Authentication auth = authenticationProvider.authenticate(authReq);
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
var user = Objects.requireNonNull(getCurrentUser());
|
||||
return userSessionRepository.save(new Session(user.getId()));
|
||||
}
|
||||
}
|
|
@ -3,4 +3,7 @@
|
|||
*/
|
||||
|
||||
rootProject.name = 'twigs'
|
||||
include ':api'
|
||||
include ':api'
|
||||
include 'app'
|
||||
include 'core'
|
||||
|
||||
|
|
Loading…
Reference in a new issue