Refactored API organization, added functionality for password resets.
This commit is contained in:
parent
d60f7142e8
commit
5d18da6ebe
|
@ -4,8 +4,18 @@ An HTTP/REST API powered by Java and Spring Boot. This API serves as the main en
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
To get started, follow these steps:
|
||||||
|
1. Clone the repository, and open this project it in your editor. (Open the pom.xml file if you're using a maven-aware editor).
|
||||||
|
2. Run `./gen_keys.d` to generate the keys that will be used by this application for signing JWT tokens. (Requires a D lang installation).
|
||||||
|
3. Make sure the *gymboard-cdn* service is running locally.
|
||||||
|
4. Boot up the project.
|
||||||
|
|
||||||
|
### Sample Data
|
||||||
|
|
||||||
To ease development, `nl.andrewlalis.gymboard_api.util.SampleDataLoader` will run on startup and populate the database with some sample entities. You can regenerate this data by manually deleting the database, and deleting the `.sample_data` marker file that's generated in the project directory.
|
To ease development, `nl.andrewlalis.gymboard_api.util.SampleDataLoader` will run on startup and populate the database with some sample entities. You can regenerate this data by manually deleting the database, and deleting the `.sample_data` marker file that's generated in the project directory.
|
||||||
|
|
||||||
|
You should have the *gymboard-cdn* project running when starting up this API, since the sample data includes videos that will be uploaded as part of some sample submissions.
|
||||||
|
|
||||||
## ULIDs
|
## ULIDs
|
||||||
|
|
||||||
For entities that don't need a human-readable primary key (or keys), we choose to use [ULID](https://github.com/ulid/spec) strings, which are like UUIDs, but use a timestamp based preamble such that their values are monotonically increasing, and lexicographically ordered by creation time. The result is a pseudorandom string of 26 characters which appears random to a human, yet is efficient as a primary key. It's also near-impossible for automated systems to guess previous/next ids.
|
For entities that don't need a human-readable primary key (or keys), we choose to use [ULID](https://github.com/ulid/spec) strings, which are like UUIDs, but use a timestamp based preamble such that their values are monotonically increasing, and lexicographically ordered by creation time. The result is a pseudorandom string of 26 characters which appears random to a human, yet is efficient as a primary key. It's also near-impossible for automated systems to guess previous/next ids.
|
||||||
|
|
|
@ -77,8 +77,20 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<!-- TODO: Change this to "test" once the SampleDataLoader is refactored to the tests. -->
|
<scope>test</scope>
|
||||||
<scope>compile</scope>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>5.1.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>2.1.214</version>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,8 @@ public class SecurityConfig {
|
||||||
"/exercises",
|
"/exercises",
|
||||||
"/leaderboards",
|
"/leaderboards",
|
||||||
"/gyms/**",
|
"/gyms/**",
|
||||||
"/submissions/**"
|
"/submissions/**",
|
||||||
|
"/auth/reset-password"
|
||||||
).permitAll()
|
).permitAll()
|
||||||
.requestMatchers(// Allow the following POST endpoints to be public.
|
.requestMatchers(// Allow the following POST endpoints to be public.
|
||||||
HttpMethod.POST,
|
HttpMethod.POST,
|
||||||
|
@ -54,7 +55,8 @@ public class SecurityConfig {
|
||||||
"/gyms/*/submissions/upload",
|
"/gyms/*/submissions/upload",
|
||||||
"/auth/token",
|
"/auth/token",
|
||||||
"/auth/register",
|
"/auth/register",
|
||||||
"/auth/activate"
|
"/auth/activate",
|
||||||
|
"/auth/reset-password"
|
||||||
).permitAll()
|
).permitAll()
|
||||||
// Everything else must be authenticated, just to be safe.
|
// Everything else must be authenticated, just to be safe.
|
||||||
.anyRequest().authenticated();
|
.anyRequest().authenticated();
|
||||||
|
|
|
@ -6,9 +6,9 @@ import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import nl.andrewlalis.gymboard_api.dao.auth.UserRepository;
|
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.TokenAuthentication;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.TokenAuthentication;
|
||||||
import nl.andrewlalis.gymboard_api.service.auth.TokenService;
|
import nl.andrewlalis.gymboard_api.domains.auth.service.TokenService;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
|
||||||
|
|
||||||
public record TokenResponse(String token) {}
|
|
|
@ -1,3 +0,0 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
|
||||||
|
|
||||||
public record UserActivationPayload(String code) {}
|
|
|
@ -1,7 +1,7 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller;
|
package nl.andrewlalis.gymboard_api.domains.api.controller;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseResponse;
|
||||||
import nl.andrewlalis.gymboard_api.service.ExerciseService;
|
import nl.andrewlalis.gymboard_api.domains.api.service.ExerciseService;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller;
|
package nl.andrewlalis.gymboard_api.domains.api.controller;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.CompoundGymId;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.CompoundGymId;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseSubmissionPayload;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionPayload;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseSubmissionResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionResponse;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.GymResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.GymResponse;
|
||||||
import nl.andrewlalis.gymboard_api.service.GymService;
|
import nl.andrewlalis.gymboard_api.domains.api.service.GymService;
|
||||||
import nl.andrewlalis.gymboard_api.service.submission.ExerciseSubmissionService;
|
import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
|
@ -1,7 +1,7 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller;
|
package nl.andrewlalis.gymboard_api.domains.api.controller;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseSubmissionResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionResponse;
|
||||||
import nl.andrewlalis.gymboard_api.service.LeaderboardService;
|
import nl.andrewlalis.gymboard_api.domains.api.service.LeaderboardService;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
@ -1,7 +1,7 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller;
|
package nl.andrewlalis.gymboard_api.domains.api.controller;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseSubmissionResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionResponse;
|
||||||
import nl.andrewlalis.gymboard_api.service.submission.ExerciseSubmissionService;
|
import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
@ -1,7 +1,7 @@
|
||||||
package nl.andrewlalis.gymboard_api.dao;
|
package nl.andrewlalis.gymboard_api.domains.api.dao;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.City;
|
import nl.andrewlalis.gymboard_api.domains.api.model.City;
|
||||||
import nl.andrewlalis.gymboard_api.model.CityId;
|
import nl.andrewlalis.gymboard_api.domains.api.model.CityId;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.dao;
|
package nl.andrewlalis.gymboard_api.domains.api.dao;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.Country;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Country;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package nl.andrewlalis.gymboard_api.dao;
|
package nl.andrewlalis.gymboard_api.domains.api.dao;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.CompoundGymId;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.CompoundGymId;
|
||||||
import nl.andrewlalis.gymboard_api.model.Gym;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||||
import nl.andrewlalis.gymboard_api.model.GymId;
|
import nl.andrewlalis.gymboard_api.domains.api.model.GymId;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.dao.exercise;
|
package nl.andrewlalis.gymboard_api.domains.api.dao.exercise;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.exercise.Exercise;
|
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.dao.exercise;
|
package nl.andrewlalis.gymboard_api.domains.api.dao.exercise;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.exercise.ExerciseSubmission;
|
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.api.dto;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.api.dto;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.exercise.Exercise;
|
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise;
|
||||||
|
|
||||||
public record ExerciseResponse(
|
public record ExerciseResponse(
|
||||||
String shortName,
|
String shortName,
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.api.dto;
|
||||||
|
|
||||||
public record ExerciseSubmissionPayload(
|
public record ExerciseSubmissionPayload(
|
||||||
String name,
|
String name,
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.api.dto;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.exercise.ExerciseSubmission;
|
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.api.dto;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.GeoPoint;
|
import nl.andrewlalis.gymboard_api.domains.api.model.GeoPoint;
|
||||||
|
|
||||||
public record GeoPointResponse(
|
public record GeoPointResponse(
|
||||||
double latitude,
|
double latitude,
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.api.dto;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.Gym;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.api.dto;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.Gym;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||||
|
|
||||||
public record GymSimpleResponse(
|
public record GymSimpleResponse(
|
||||||
String countryCode,
|
String countryCode,
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model;
|
package nl.andrewlalis.gymboard_api.domains.api.model;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model;
|
package nl.andrewlalis.gymboard_api.domains.api.model;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import jakarta.persistence.Embeddable;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model;
|
package nl.andrewlalis.gymboard_api.domains.api.model;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model;
|
package nl.andrewlalis.gymboard_api.domains.api.model;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import jakarta.persistence.Embeddable;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model;
|
package nl.andrewlalis.gymboard_api.domains.api.model;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
import org.hibernate.annotations.CreationTimestamp;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model;
|
package nl.andrewlalis.gymboard_api.domains.api.model;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Embeddable;
|
import jakarta.persistence.Embeddable;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model;
|
package nl.andrewlalis.gymboard_api.domains.api.model;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model.exercise;
|
package nl.andrewlalis.gymboard_api.domains.api.model.exercise;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
|
@ -1,7 +1,7 @@
|
||||||
package nl.andrewlalis.gymboard_api.model.exercise;
|
package nl.andrewlalis.gymboard_api.domains.api.model.exercise;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import nl.andrewlalis.gymboard_api.model.Gym;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
|
@ -1,7 +1,7 @@
|
||||||
package nl.andrewlalis.gymboard_api.service;
|
package nl.andrewlalis.gymboard_api.domains.api.service;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseResponse;
|
||||||
import nl.andrewlalis.gymboard_api.dao.exercise.ExerciseRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseRepository;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
|
@ -1,11 +1,11 @@
|
||||||
package nl.andrewlalis.gymboard_api.service;
|
package nl.andrewlalis.gymboard_api.domains.api.service;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.CompoundGymId;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.CompoundGymId;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseSubmissionResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionResponse;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.GymResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.GymResponse;
|
||||||
import nl.andrewlalis.gymboard_api.dao.GymRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.GymRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.exercise.ExerciseSubmissionRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseSubmissionRepository;
|
||||||
import nl.andrewlalis.gymboard_api.model.Gym;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||||
import nl.andrewlalis.gymboard_api.util.PredicateBuilder;
|
import nl.andrewlalis.gymboard_api.util.PredicateBuilder;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
|
@ -1,13 +1,13 @@
|
||||||
package nl.andrewlalis.gymboard_api.service;
|
package nl.andrewlalis.gymboard_api.domains.api.service;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.CompoundGymId;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.CompoundGymId;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseSubmissionResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionResponse;
|
||||||
import nl.andrewlalis.gymboard_api.dao.GymRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.GymRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.exercise.ExerciseRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.exercise.ExerciseSubmissionRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseSubmissionRepository;
|
||||||
import nl.andrewlalis.gymboard_api.model.Gym;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||||
import nl.andrewlalis.gymboard_api.model.LeaderboardTimeframe;
|
import nl.andrewlalis.gymboard_api.domains.api.model.LeaderboardTimeframe;
|
||||||
import nl.andrewlalis.gymboard_api.model.exercise.Exercise;
|
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise;
|
||||||
import nl.andrewlalis.gymboard_api.util.PredicateBuilder;
|
import nl.andrewlalis.gymboard_api.util.PredicateBuilder;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.service.cdn_client;
|
package nl.andrewlalis.gymboard_api.domains.api.service.cdn_client;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.service.cdn_client;
|
package nl.andrewlalis.gymboard_api.domains.api.service.cdn_client;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package nl.andrewlalis.gymboard_api.service.submission;
|
package nl.andrewlalis.gymboard_api.domains.api.service.submission;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.CompoundGymId;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.CompoundGymId;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseSubmissionPayload;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionPayload;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseSubmissionResponse;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionResponse;
|
||||||
import nl.andrewlalis.gymboard_api.dao.GymRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.GymRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.exercise.ExerciseRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.exercise.ExerciseSubmissionRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseSubmissionRepository;
|
||||||
import nl.andrewlalis.gymboard_api.model.Gym;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||||
import nl.andrewlalis.gymboard_api.model.exercise.Exercise;
|
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise;
|
||||||
import nl.andrewlalis.gymboard_api.model.exercise.ExerciseSubmission;
|
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission;
|
||||||
import nl.andrewlalis.gymboard_api.util.ULID;
|
import nl.andrewlalis.gymboard_api.util.ULID;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
|
@ -1,15 +1,13 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller;
|
package nl.andrewlalis.gymboard_api.domains.auth.controller;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.*;
|
import nl.andrewlalis.gymboard_api.domains.auth.dto.*;
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.User;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||||
import nl.andrewlalis.gymboard_api.service.auth.TokenService;
|
import nl.andrewlalis.gymboard_api.domains.auth.service.TokenService;
|
||||||
import nl.andrewlalis.gymboard_api.service.auth.UserService;
|
import nl.andrewlalis.gymboard_api.domains.auth.service.UserService;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
@ -80,4 +78,16 @@ public class AuthController {
|
||||||
public UserResponse getMyUser(@AuthenticationPrincipal User user) {
|
public UserResponse getMyUser(@AuthenticationPrincipal User user) {
|
||||||
return new UserResponse(user);
|
return new UserResponse(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/auth/reset-password")
|
||||||
|
public ResponseEntity<Void> generatePasswordResetCode(@RequestParam String email) {
|
||||||
|
userService.generatePasswordResetCode(email);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = "/auth/reset-password")
|
||||||
|
public ResponseEntity<Void> resetPassword(@RequestBody PasswordResetPayload payload) {
|
||||||
|
userService.resetUserPassword(payload);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package nl.andrewlalis.gymboard_api.domains.auth.dao;
|
||||||
|
|
||||||
|
import nl.andrewlalis.gymboard_api.domains.auth.model.PasswordResetCode;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface PasswordResetCodeRepository extends JpaRepository<PasswordResetCode, String> {
|
||||||
|
@Modifying
|
||||||
|
void deleteAllByCreatedAtBefore(LocalDateTime cutoff);
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.dao.auth;
|
package nl.andrewlalis.gymboard_api.domains.auth.dao;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.Role;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.Role;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.dao.auth;
|
package nl.andrewlalis.gymboard_api.domains.auth.dao;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.UserActivationCode;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.UserActivationCode;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.dao.auth;
|
package nl.andrewlalis.gymboard_api.domains.auth.dao;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.User;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
|
@ -0,0 +1,3 @@
|
||||||
|
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||||
|
|
||||||
|
public record PasswordResetPayload(String code, String newPassword) {}
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||||
|
|
||||||
public record TokenCredentials(
|
public record TokenCredentials(
|
||||||
String email,
|
String email,
|
|
@ -0,0 +1,3 @@
|
||||||
|
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||||
|
|
||||||
|
public record TokenResponse(String token) {}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||||
|
|
||||||
|
public record UserActivationPayload(String code) {}
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||||
|
|
||||||
public record UserCreationPayload(
|
public record UserCreationPayload(
|
||||||
String email,
|
String email,
|
|
@ -1,6 +1,6 @@
|
||||||
package nl.andrewlalis.gymboard_api.controller.dto;
|
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.User;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||||
|
|
||||||
public record UserResponse(
|
public record UserResponse(
|
||||||
String id,
|
String id,
|
|
@ -0,0 +1,39 @@
|
||||||
|
package nl.andrewlalis.gymboard_api.domains.auth.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "auth_user_password_reset_code")
|
||||||
|
public class PasswordResetCode {
|
||||||
|
@Id
|
||||||
|
@Column(nullable = false, updatable = false, length = 127)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@CreationTimestamp
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
public PasswordResetCode() {}
|
||||||
|
|
||||||
|
public PasswordResetCode(String code, User user) {
|
||||||
|
this.code = code;
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model.auth;
|
package nl.andrewlalis.gymboard_api.domains.auth.model;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model.auth;
|
package nl.andrewlalis.gymboard_api.domains.auth.model;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model.auth;
|
package nl.andrewlalis.gymboard_api.domains.auth.model;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
@ -72,6 +72,10 @@ public class User {
|
||||||
return passwordHash;
|
return passwordHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPasswordHash(String passwordHash) {
|
||||||
|
this.passwordHash = passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package nl.andrewlalis.gymboard_api.model.auth;
|
package nl.andrewlalis.gymboard_api.domains.auth.model;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
import org.hibernate.annotations.CreationTimestamp;
|
|
@ -1,15 +1,15 @@
|
||||||
package nl.andrewlalis.gymboard_api.service.auth;
|
package nl.andrewlalis.gymboard_api.domains.auth.service;
|
||||||
|
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.jsonwebtoken.Jws;
|
import io.jsonwebtoken.Jws;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.TokenCredentials;
|
import nl.andrewlalis.gymboard_api.domains.auth.dto.TokenCredentials;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.TokenResponse;
|
import nl.andrewlalis.gymboard_api.domains.auth.dto.TokenResponse;
|
||||||
import nl.andrewlalis.gymboard_api.dao.auth.UserRepository;
|
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.Role;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.Role;
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.TokenAuthentication;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.TokenAuthentication;
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.User;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
|
@ -1,14 +1,18 @@
|
||||||
package nl.andrewlalis.gymboard_api.service.auth;
|
package nl.andrewlalis.gymboard_api.domains.auth.service;
|
||||||
|
|
||||||
import jakarta.mail.MessagingException;
|
import jakarta.mail.MessagingException;
|
||||||
import jakarta.mail.internet.MimeMessage;
|
import jakarta.mail.internet.MimeMessage;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.UserActivationPayload;
|
import nl.andrewlalis.gymboard_api.domains.auth.dao.PasswordResetCodeRepository;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.UserCreationPayload;
|
import nl.andrewlalis.gymboard_api.domains.auth.dto.PasswordResetPayload;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.UserResponse;
|
import nl.andrewlalis.gymboard_api.domains.auth.dto.UserActivationPayload;
|
||||||
import nl.andrewlalis.gymboard_api.dao.auth.UserActivationCodeRepository;
|
import nl.andrewlalis.gymboard_api.domains.auth.dto.UserCreationPayload;
|
||||||
import nl.andrewlalis.gymboard_api.dao.auth.UserRepository;
|
import nl.andrewlalis.gymboard_api.domains.auth.dto.UserResponse;
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.User;
|
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserActivationCodeRepository;
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.UserActivationCode;
|
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
||||||
|
import nl.andrewlalis.gymboard_api.domains.auth.model.PasswordResetCode;
|
||||||
|
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||||
|
import nl.andrewlalis.gymboard_api.domains.auth.model.UserActivationCode;
|
||||||
|
import nl.andrewlalis.gymboard_api.util.StringGenerator;
|
||||||
import nl.andrewlalis.gymboard_api.util.ULID;
|
import nl.andrewlalis.gymboard_api.util.ULID;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -31,6 +35,7 @@ public class UserService {
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final UserActivationCodeRepository activationCodeRepository;
|
private final UserActivationCodeRepository activationCodeRepository;
|
||||||
|
private final PasswordResetCodeRepository passwordResetCodeRepository;
|
||||||
private final ULID ulid;
|
private final ULID ulid;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final JavaMailSender mailSender;
|
private final JavaMailSender mailSender;
|
||||||
|
@ -40,12 +45,15 @@ public class UserService {
|
||||||
|
|
||||||
public UserService(
|
public UserService(
|
||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
UserActivationCodeRepository activationCodeRepository, ULID ulid,
|
UserActivationCodeRepository activationCodeRepository,
|
||||||
|
PasswordResetCodeRepository passwordResetCodeRepository,
|
||||||
|
ULID ulid,
|
||||||
PasswordEncoder passwordEncoder,
|
PasswordEncoder passwordEncoder,
|
||||||
JavaMailSender mailSender
|
JavaMailSender mailSender
|
||||||
) {
|
) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.activationCodeRepository = activationCodeRepository;
|
this.activationCodeRepository = activationCodeRepository;
|
||||||
|
this.passwordResetCodeRepository = passwordResetCodeRepository;
|
||||||
this.ulid = ulid;
|
this.ulid = ulid;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.mailSender = mailSender;
|
this.mailSender = mailSender;
|
||||||
|
@ -117,13 +125,74 @@ public class UserService {
|
||||||
public UserResponse activateUser(UserActivationPayload payload) {
|
public UserResponse activateUser(UserActivationPayload payload) {
|
||||||
UserActivationCode activationCode = activationCodeRepository.findByCode(payload.code())
|
UserActivationCode activationCode = activationCodeRepository.findByCode(payload.code())
|
||||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||||
|
User user = activationCode.getUser();
|
||||||
|
if (!user.isActivated()) {
|
||||||
LocalDateTime cutoff = LocalDateTime.now().minusDays(1);
|
LocalDateTime cutoff = LocalDateTime.now().minusDays(1);
|
||||||
if (activationCode.getCreatedAt().isBefore(cutoff)) {
|
if (activationCode.getCreatedAt().isBefore(cutoff)) {
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Code is expired.");
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Code is expired.");
|
||||||
}
|
}
|
||||||
User user = activationCode.getUser();
|
|
||||||
user.setActivated(true);
|
user.setActivated(true);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
activationCodeRepository.delete(activationCode);
|
||||||
return new UserResponse(user);
|
return new UserResponse(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void generatePasswordResetCode(String email) {
|
||||||
|
User user = userRepository.findByEmail(email)
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||||
|
if (!user.isActivated()) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordResetCode passwordResetCode = passwordResetCodeRepository.save(new PasswordResetCode(
|
||||||
|
StringGenerator.randomString(127, StringGenerator.Alphabet.ALPHANUMERIC),
|
||||||
|
user
|
||||||
|
));
|
||||||
|
|
||||||
|
// Send email.
|
||||||
|
String resetLink = webOrigin + "/password-reset?code=" + passwordResetCode.getCode();
|
||||||
|
String emailContent = String.format(
|
||||||
|
"""
|
||||||
|
<p>Hello %s,</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You've just requested to reset your password.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please click <a href="%s">here</a> to reset your password.
|
||||||
|
</p>
|
||||||
|
""",
|
||||||
|
user.getName(),
|
||||||
|
resetLink
|
||||||
|
);
|
||||||
|
MimeMessage msg = mailSender.createMimeMessage();
|
||||||
|
try {
|
||||||
|
MimeMessageHelper helper = new MimeMessageHelper(msg, "UTF-8");
|
||||||
|
helper.setFrom("Gymboard <noreply@gymboard.io>");
|
||||||
|
helper.setSubject("Gymboard Account Password Reset");
|
||||||
|
helper.setTo(user.getEmail());
|
||||||
|
helper.setText(emailContent, true);
|
||||||
|
mailSender.send(msg);
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
log.error("Error sending user password reset email.", e);
|
||||||
|
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void resetUserPassword(PasswordResetPayload payload) {
|
||||||
|
PasswordResetCode code = passwordResetCodeRepository.findById(payload.code())
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||||
|
LocalDateTime cutoff = LocalDateTime.now().minusMinutes(30);
|
||||||
|
if (code.getCreatedAt().isBefore(cutoff)) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Validate password.
|
||||||
|
|
||||||
|
code.getUser().setPasswordHash(passwordEncoder.encode(payload.newPassword()));
|
||||||
|
passwordResetCodeRepository.delete(code);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,25 +1,25 @@
|
||||||
package nl.andrewlalis.gymboard_api.util;
|
package nl.andrewlalis.gymboard_api.util;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.CompoundGymId;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.CompoundGymId;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.ExerciseSubmissionPayload;
|
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionPayload;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.UserCreationPayload;
|
import nl.andrewlalis.gymboard_api.domains.auth.dto.UserCreationPayload;
|
||||||
import nl.andrewlalis.gymboard_api.dao.CityRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.CityRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.CountryRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.CountryRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.GymRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.GymRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.auth.RoleRepository;
|
import nl.andrewlalis.gymboard_api.domains.auth.dao.RoleRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.auth.UserRepository;
|
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
||||||
import nl.andrewlalis.gymboard_api.dao.exercise.ExerciseRepository;
|
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseRepository;
|
||||||
import nl.andrewlalis.gymboard_api.model.City;
|
import nl.andrewlalis.gymboard_api.domains.api.model.City;
|
||||||
import nl.andrewlalis.gymboard_api.model.Country;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Country;
|
||||||
import nl.andrewlalis.gymboard_api.model.GeoPoint;
|
import nl.andrewlalis.gymboard_api.domains.api.model.GeoPoint;
|
||||||
import nl.andrewlalis.gymboard_api.model.Gym;
|
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.Role;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.Role;
|
||||||
import nl.andrewlalis.gymboard_api.model.auth.User;
|
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||||
import nl.andrewlalis.gymboard_api.model.exercise.Exercise;
|
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise;
|
||||||
import nl.andrewlalis.gymboard_api.model.exercise.ExerciseSubmission;
|
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.ExerciseSubmission;
|
||||||
import nl.andrewlalis.gymboard_api.service.auth.UserService;
|
import nl.andrewlalis.gymboard_api.domains.auth.service.UserService;
|
||||||
import nl.andrewlalis.gymboard_api.service.cdn_client.CdnClient;
|
import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.CdnClient;
|
||||||
import nl.andrewlalis.gymboard_api.service.submission.ExerciseSubmissionService;
|
import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService;
|
||||||
import org.apache.commons.csv.CSVFormat;
|
import org.apache.commons.csv.CSVFormat;
|
||||||
import org.apache.commons.csv.CSVRecord;
|
import org.apache.commons.csv.CSVRecord;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package nl.andrewlalis.gymboard_api.util;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class StringGenerator {
|
||||||
|
public enum Alphabet {
|
||||||
|
ALPHANUMERIC("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
|
||||||
|
|
||||||
|
public final String value;
|
||||||
|
|
||||||
|
Alphabet(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String randomString(int length, Alphabet alphabet) {
|
||||||
|
Random random = new SecureRandom();
|
||||||
|
StringBuilder sb = new StringBuilder(length);
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
sb.append(alphabet.value.charAt(random.nextInt(alphabet.value.length())));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1 @@
|
||||||
spring.jpa.open-in-view=false
|
spring.jpa.open-in-view=false
|
||||||
|
|
||||||
# TODO: Find a better way than dumping files into memory.
|
|
||||||
spring.servlet.multipart.enabled=true
|
|
||||||
spring.servlet.multipart.max-file-size=1GB
|
|
||||||
spring.servlet.multipart.max-request-size=2GB
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class GymboardApiApplicationTests {
|
class GymboardApiApplicationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
void contextLoads() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
|
||||||
|
spring.datasource.driver-class-name=org.h2.Driver
|
||||||
|
|
||||||
|
spring.jpa.open-in-view=false
|
||||||
|
|
||||||
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
|
||||||
|
spring.mail.host=127.0.0.1
|
||||||
|
spring.mail.port=1025
|
||||||
|
spring.mail.protocol=smtp
|
||||||
|
spring.mail.properties.mail.smtp.timeout=10000
|
||||||
|
|
||||||
|
app.auth.private-key-location=./private_key.der
|
||||||
|
app.web-origin=http://localhost:9000
|
||||||
|
app.cdn-origin=http://localhost:8082
|
Loading…
Reference in New Issue