diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index f974c93..6acc985 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -39,7 +39,7 @@ services:
ports:
- "8585:8585"
- "5005:5005"
- image: maven:3.9.9-eclipse-temurin-25
+ image: maven:3.9.12-eclipse-temurin-25
entrypoint: './mvnw -ntp spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005" -Dmaven.plugin.validation=VERBOSE'
working_dir: /app
volumes:
diff --git a/server/pom.xml b/server/pom.xml
index 2849a5f..682a868 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -173,7 +173,7 @@
true
true
- true
+ false
diff --git a/server/src/main/java/br/com/tasknoteapp/server/config/SecurityConfig.java b/server/src/main/java/br/com/tasknoteapp/server/config/SecurityConfig.java
index 8a14b1e..ba7f707 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/config/SecurityConfig.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/config/SecurityConfig.java
@@ -14,6 +14,7 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+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.web.SecurityFilterChain;
@@ -59,6 +60,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.permitAll())
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
+ .sessionManagement(
+ session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(
exceptionHandling ->
exceptionHandling.authenticationEntryPoint(
diff --git a/server/src/main/java/br/com/tasknoteapp/server/controller/NoteController.java b/server/src/main/java/br/com/tasknoteapp/server/controller/NoteController.java
index 93ed495..2dc772f 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/controller/NoteController.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/controller/NoteController.java
@@ -1,6 +1,5 @@
package br.com.tasknoteapp.server.controller;
-import br.com.tasknoteapp.server.entity.NoteEntity;
import br.com.tasknoteapp.server.exception.NoteNotFoundException;
import br.com.tasknoteapp.server.request.NotePatchRequest;
import br.com.tasknoteapp.server.request.NoteRequest;
@@ -77,8 +76,8 @@ public ResponseEntity patchNote(
*/
@PostMapping
public ResponseEntity postNotes(@RequestBody @Valid NoteRequest noteRequest) {
- NoteEntity createdNote = noteService.createNote(noteRequest);
- return ResponseEntity.status(HttpStatus.CREATED).body(NoteResponse.fromEntity(createdNote));
+ NoteResponse createdNote = noteService.createNote(noteRequest);
+ return ResponseEntity.status(HttpStatus.CREATED).body(createdNote);
}
/**
diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/NoteEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/NoteEntity.java
index 1ea0e06..dd3d21b 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/entity/NoteEntity.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/entity/NoteEntity.java
@@ -8,7 +8,6 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
-import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
@@ -30,9 +29,6 @@ public class NoteEntity {
@ManyToOne(fetch = FetchType.LAZY)
private UserEntity user;
- @OneToOne(mappedBy = "note", fetch = FetchType.LAZY)
- private NoteUrlEntity noteUrl;
-
@Column(name = "tag", nullable = true, length = 30)
private String tag;
@@ -77,14 +73,6 @@ public void setUser(UserEntity user) {
this.user = user;
}
- public NoteUrlEntity getNoteUrl() {
- return noteUrl;
- }
-
- public void setNoteUrl(NoteUrlEntity noteUrl) {
- this.noteUrl = noteUrl;
- }
-
public String getTag() {
return tag;
}
diff --git a/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java b/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java
index f0841e1..c72c9b3 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/entity/UserEntity.java
@@ -5,7 +5,6 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
-import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.Collection;
@@ -41,9 +40,6 @@ public class UserEntity implements UserDetails {
@Column(name = "name", length = 20)
private String name;
- @OneToMany(mappedBy = "user")
- private List tasks;
-
@Column(name = "email_confirmed_at", nullable = true)
private LocalDateTime emailConfirmedAt;
@@ -147,14 +143,6 @@ public void setName(String name) {
this.name = name;
}
- public List getTasks() {
- return tasks;
- }
-
- public void setTasks(List tasks) {
- this.tasks = tasks;
- }
-
public LocalDateTime getEmailConfirmedAt() {
return emailConfirmedAt;
}
diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/NoteRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/NoteRepository.java
index e8d9606..a2fb375 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/repository/NoteRepository.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/repository/NoteRepository.java
@@ -13,6 +13,8 @@ public interface NoteRepository extends JpaRepository {
Optional findByShareToken(String shareToken);
+ Optional findByIdAndUser_id(Long id, Long userId);
+
@Query(
"select n from NoteEntity n where (upper(n.title) like %?1% or upper(n.description) like"
+ " %?1%) and n.user.id = ?2")
diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/NoteUrlRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/NoteUrlRepository.java
index 27671b7..98a5e9b 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/repository/NoteUrlRepository.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/repository/NoteUrlRepository.java
@@ -1,10 +1,16 @@
package br.com.tasknoteapp.server.repository;
import br.com.tasknoteapp.server.entity.NoteUrlEntity;
+import java.util.List;
+import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
/** This interface represents a note url repository, for database access. */
public interface NoteUrlRepository extends JpaRepository {
+ Optional findByNote_id(Long noteId);
+
+ List findAllByNote_idIn(List noteIds);
+
void deleteByNote_id(Long noteId);
}
diff --git a/server/src/main/java/br/com/tasknoteapp/server/repository/UserPwdLimitRepository.java b/server/src/main/java/br/com/tasknoteapp/server/repository/UserPwdLimitRepository.java
index bfde862..3a8a28d 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/repository/UserPwdLimitRepository.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/repository/UserPwdLimitRepository.java
@@ -12,6 +12,8 @@ public interface UserPwdLimitRepository extends JpaRepository findAllByUser_id(Long userId, Sort sort);
+ List findTop3ByUser_idOrderByWhenHappenedDesc(Long userId);
+
@Modifying
@Query("delete UserPwdLimitEntity u where u.user.id = ?1")
void deleteAllForUser(Long userId);
diff --git a/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java b/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java
index 47cbaba..6f65ce0 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/response/NoteResponse.java
@@ -1,9 +1,7 @@
package br.com.tasknoteapp.server.response;
import br.com.tasknoteapp.server.entity.NoteEntity;
-import br.com.tasknoteapp.server.entity.NoteUrlEntity;
import br.com.tasknoteapp.server.util.TimeAgoUtil;
-import java.util.Objects;
/** This record represents a task and its urls object to be returned. */
public record NoteResponse(
@@ -16,9 +14,7 @@ public record NoteResponse(
* @param entity The NoteEntity source data.
* @return NoteResponse instance with all note data and urls, if any.
*/
- public static NoteResponse fromEntity(NoteEntity entity) {
- NoteUrlEntity noteUrl = entity.getNoteUrl();
- String url = Objects.isNull(noteUrl) ? null : noteUrl.getUrl();
+ public static NoteResponse fromEntity(NoteEntity entity, String url) {
String timeAgoFmt = TimeAgoUtil.format(entity.getLastUpdate());
return new NoteResponse(
diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java b/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java
index c4a5751..e1e35bf 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/service/AuthService.java
@@ -36,8 +36,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
-import org.springframework.data.domain.Sort;
-import org.springframework.data.domain.Sort.Direction;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -513,8 +511,10 @@ private Optional getGravatarImageUrl(String email) {
}
private void checkLoginAttemptLimit(Long userId) {
- Sort sort = Sort.by(Direction.DESC, "whenHappened");
- List userPwdList = userPwdLimitRepository.findAllByUser_id(userId, sort);
+ // Fetch only the 3 most recent failed attempts to avoid loading unbounded rows for
+ // targeted/brute-forced accounts.
+ List userPwdList =
+ userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(userId);
logger.warn("login count attempt for user {}: {}", userId, userPwdList.size());
diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java b/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java
index 1d9cc62..92b8b35 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/service/MailgunEmailService.java
@@ -8,6 +8,7 @@
import br.com.tasknoteapp.server.templates.MailgunTemplateResetPwdConfirm;
import br.com.tasknoteapp.server.templates.MailgunTemplateSignUp;
import java.nio.charset.StandardCharsets;
+import java.time.Duration;
import java.util.Base64;
import java.util.Objects;
import org.slf4j.Logger;
@@ -53,7 +54,11 @@ public MailgunEmailService(
this.senderEmail = sender;
this.targetEnv = targetEnv;
this.restTemplate =
- templateBuilder.defaultHeader(HttpHeaders.AUTHORIZATION, basicAuth("api", apiKey)).build();
+ templateBuilder
+ .connectTimeout(Duration.ofSeconds(5))
+ .readTimeout(Duration.ofSeconds(10))
+ .defaultHeader(HttpHeaders.AUTHORIZATION, basicAuth("api", apiKey))
+ .build();
}
/**
diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java b/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java
index 6ad00c9..941c10f 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/service/NoteService.java
@@ -4,7 +4,6 @@
import br.com.tasknoteapp.server.entity.NoteUrlEntity;
import br.com.tasknoteapp.server.entity.UserEntity;
import br.com.tasknoteapp.server.exception.NoteNotFoundException;
-import br.com.tasknoteapp.server.exception.TaskNotFoundException;
import br.com.tasknoteapp.server.repository.NoteRepository;
import br.com.tasknoteapp.server.repository.NoteUrlRepository;
import br.com.tasknoteapp.server.request.NotePatchRequest;
@@ -14,9 +13,11 @@
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@@ -67,35 +68,39 @@ public List getAllNotes() {
List notes = noteRepository.findAllByUser_id(user.getId());
logger.info(notes.size() + " notes found!");
- return notes.stream().map(NoteResponse::fromEntity).toList();
+ return getNotesUrl(notes);
}
/**
* Get a note by its id.
*
- * @param noteId The task id in the database.
- * @return {@link NoteResponse} with the found task or throw a {@link TaskNotFoundException}.
+ * @param noteId The note id in the database.
+ * @return {@link NoteResponse} with the found note or throw a {@link NoteNotFoundException}.
*/
public NoteResponse getNoteById(Long noteId) {
UserEntity user = getCurrentUser();
logger.info("Get note " + noteId + " to user " + user.getId());
- Optional task = noteRepository.findById(noteId);
- if (task.isEmpty()) {
+ Optional note = noteRepository.findById(noteId);
+ if (note.isEmpty()) {
+ throw new NoteNotFoundException();
+ }
+
+ if (!note.get().getUser().getId().equals(user.getId())) {
throw new NoteNotFoundException();
}
logger.info("Note found! Id " + noteId);
- return NoteResponse.fromEntity(task.get());
+ return NoteResponse.fromEntity(note.get(), getNoteUrl(noteId));
}
/**
* Create a note for the user.
*
* @param noteRequest The note content.
- * @return {@link NoteEntity} created in the database
+ * @return {@link NoteResponse} with created note data.
*/
- public NoteEntity createNote(NoteRequest noteRequest) {
+ public NoteResponse createNote(NoteRequest noteRequest) {
UserEntity user = getCurrentUser();
logger.info("Creating note to user " + user.getId());
@@ -110,13 +115,14 @@ public NoteEntity createNote(NoteRequest noteRequest) {
logger.info("Note created! Id " + created.getId());
+ String savedUrl = null;
if (!Objects.isNull(noteRequest.url()) && !noteRequest.url().isEmpty()) {
- NoteUrlEntity urlEntity = saveUrl(note, noteRequest.url());
- note.setNoteUrl(urlEntity);
+ NoteUrlEntity urlEntity = saveUrl(created, noteRequest.url());
+ savedUrl = urlEntity.getUrl();
}
logger.info("Finished note creation!");
- return created;
+ return NoteResponse.fromEntity(created, savedUrl);
}
/**
@@ -132,7 +138,7 @@ public NoteResponse patchNote(Long noteId, NotePatchRequest patch) {
logger.info("Patching task " + noteId + " to user " + user.getId());
- Optional note = noteRepository.findById(noteId);
+ Optional note = noteRepository.findByIdAndUser_id(noteId, user.getId());
if (note.isEmpty()) {
throw new NoteNotFoundException();
}
@@ -154,8 +160,7 @@ public NoteResponse patchNote(Long noteId, NotePatchRequest patch) {
logger.info("URL deleted from task " + noteId);
if (!Objects.isNull(patch.url()) && !patch.url().isBlank()) {
- NoteUrlEntity urlEntity = saveUrl(noteEntity, patch.url());
- noteEntity.setNoteUrl(urlEntity);
+ saveUrl(noteEntity, patch.url());
} else {
logger.info("No urls to patch for task " + noteId);
}
@@ -165,7 +170,7 @@ public NoteResponse patchNote(Long noteId, NotePatchRequest patch) {
logger.info("Note patched! Id " + patchedNote.getId());
- return NoteResponse.fromEntity(patchedNote);
+ return NoteResponse.fromEntity(patchedNote, getNoteUrl(patchedNote.getId()));
}
/**
@@ -179,20 +184,15 @@ public void deleteNote(Long noteId) {
logger.info("Deleting note " + noteId + " to user " + user.getId());
- Optional note = noteRepository.findById(noteId);
+ Optional note = noteRepository.findByIdAndUser_id(noteId, user.getId());
if (note.isEmpty()) {
throw new NoteNotFoundException();
}
NoteEntity noteEntity = note.get();
- NoteUrlEntity noteUrl = noteEntity.getNoteUrl();
- if (!Objects.isNull(noteUrl)) {
- noteUrlRepository.delete(noteUrl);
- logger.info("URL Deleted from task " + noteId);
- } else {
- logger.info("No urls to delete for task " + noteId);
- }
+ noteUrlRepository.deleteByNote_id(noteId);
+ logger.info("URL deleted from task " + noteId);
noteRepository.delete(noteEntity);
@@ -213,7 +213,7 @@ public List searchNotes(String searchTerm) {
List notes =
noteRepository.findAllBySearchTerm(searchTerm.toUpperCase(), user.getId());
logger.info(notes.size() + " tasks found!");
- return notes.stream().map(NoteResponse::fromEntity).toList();
+ return getNotesUrl(notes);
}
/**
@@ -222,16 +222,18 @@ public List searchNotes(String searchTerm) {
* @param noteId The note id from the database.
* @return {@link NoteResponse} containing the updated note with share token.
*/
+ @Transactional
public NoteResponse shareNote(Long noteId) {
UserEntity user = getCurrentUser();
logger.info("Sharing note " + noteId + " for user " + user.getId());
- Optional noteOpt = noteRepository.findById(noteId);
+ Optional noteOpt = noteRepository.findByIdAndUser_id(noteId, user.getId());
if (noteOpt.isEmpty()) {
throw new NoteNotFoundException();
}
NoteEntity noteEntity = noteOpt.get();
+
if (!noteEntity.isShared()) {
noteEntity.setShared(true);
noteEntity.setShareToken(UUID.randomUUID().toString());
@@ -239,7 +241,7 @@ public NoteResponse shareNote(Long noteId) {
logger.info("Note " + noteId + " shared with token " + noteEntity.getShareToken());
}
- return NoteResponse.fromEntity(noteEntity);
+ return NoteResponse.fromEntity(noteEntity, getNoteUrl(noteEntity.getId()));
}
/**
@@ -252,7 +254,7 @@ public NoteResponse unshareNote(Long noteId) {
UserEntity user = getCurrentUser();
logger.info("Unsharing note " + noteId + " for user " + user.getId());
- Optional noteOpt = noteRepository.findById(noteId);
+ Optional noteOpt = noteRepository.findByIdAndUser_id(noteId, user.getId());
if (noteOpt.isEmpty()) {
throw new NoteNotFoundException();
}
@@ -263,7 +265,7 @@ public NoteResponse unshareNote(Long noteId) {
noteRepository.save(noteEntity);
logger.info("Note " + noteId + " unshared.");
- return NoteResponse.fromEntity(noteEntity);
+ return NoteResponse.fromEntity(noteEntity, getNoteUrl(noteEntity.getId()));
}
/**
@@ -280,7 +282,7 @@ public NoteResponse getSharedNote(String shareToken) {
throw new NoteNotFoundException();
}
- return NoteResponse.fromEntity(noteOpt.get());
+ return NoteResponse.fromEntity(noteOpt.get(), getNoteUrl(noteOpt.get().getId()));
}
private UserEntity getCurrentUser() {
@@ -289,6 +291,22 @@ private UserEntity getCurrentUser() {
return authService.findByEmail(email).orElseThrow();
}
+ private String getNoteUrl(Long noteId) {
+ return noteUrlRepository.findByNote_id(noteId).map(NoteUrlEntity::getUrl).orElse(null);
+ }
+
+ private List getNotesUrl(List notes) {
+ List noteIds = notes.stream().map(NoteEntity::getId).toList();
+ if (noteIds.isEmpty()) {
+ return notes.stream().map(n -> NoteResponse.fromEntity(n, null)).toList();
+ }
+ List urls = noteUrlRepository.findAllByNote_idIn(noteIds);
+ Map noteUrls =
+ urls.stream().collect(Collectors.toMap(nu -> nu.getNote().getId(), NoteUrlEntity::getUrl));
+
+ return notes.stream().map(n -> NoteResponse.fromEntity(n, noteUrls.get(n.getId()))).toList();
+ }
+
private NoteUrlEntity saveUrl(NoteEntity noteEntity, String url) {
NoteUrlEntity noteUrl = new NoteUrlEntity();
noteUrl.setUrl(url);
diff --git a/server/src/main/java/br/com/tasknoteapp/server/service/impl/UserServiceImpl.java b/server/src/main/java/br/com/tasknoteapp/server/service/impl/UserServiceImpl.java
index d2833b9..d461e68 100644
--- a/server/src/main/java/br/com/tasknoteapp/server/service/impl/UserServiceImpl.java
+++ b/server/src/main/java/br/com/tasknoteapp/server/service/impl/UserServiceImpl.java
@@ -4,7 +4,6 @@
import br.com.tasknoteapp.server.repository.UserRepository;
import br.com.tasknoteapp.server.service.UserService;
import java.util.Optional;
-import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
@@ -12,24 +11,21 @@
@Service
class UserServiceImpl implements UserService {
- private final UserRepository userRepository;
+ private final UserDetailsService cachedUserDetailsService;
public UserServiceImpl(UserRepository userRepository) {
- this.userRepository = userRepository;
+ this.cachedUserDetailsService =
+ email -> {
+ Optional user = userRepository.findByEmail(email);
+ if (user.isEmpty()) {
+ throw new RuntimeException("User not found: " + email);
+ }
+ return user.get();
+ };
}
@Override
public UserDetailsService userDetailsService() {
- return new UserDetailsService() {
- @Override
- public UserDetails loadUserByUsername(String email) {
- Optional user = userRepository.findByEmail(email);
- if (user.isEmpty()) {
- throw new RuntimeException("User not found: " + email);
- }
-
- return user.get();
- }
- };
+ return this.cachedUserDetailsService;
}
}
diff --git a/server/src/main/resources/application-native.yml b/server/src/main/resources/application-native.yml
index e214b18..1f28ab1 100644
--- a/server/src/main/resources/application-native.yml
+++ b/server/src/main/resources/application-native.yml
@@ -36,7 +36,8 @@ spring:
locations: classpath:db/migration
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
+ open-in-view: false
properties:
hibernate:
default_schema: tasknote
- show-sql: true
+ show-sql: false
diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml
index e214b18..1f28ab1 100644
--- a/server/src/main/resources/application.yml
+++ b/server/src/main/resources/application.yml
@@ -36,7 +36,8 @@ spring:
locations: classpath:db/migration
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
+ open-in-view: false
properties:
hibernate:
default_schema: tasknote
- show-sql: true
+ show-sql: false
diff --git a/server/src/test/java/br/com/tasknoteapp/server/controller/NoteControllerTest.java b/server/src/test/java/br/com/tasknoteapp/server/controller/NoteControllerTest.java
index a40ddff..729c3f5 100644
--- a/server/src/test/java/br/com/tasknoteapp/server/controller/NoteControllerTest.java
+++ b/server/src/test/java/br/com/tasknoteapp/server/controller/NoteControllerTest.java
@@ -12,7 +12,6 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-import br.com.tasknoteapp.server.entity.NoteEntity;
import br.com.tasknoteapp.server.exception.NoteNotFoundException;
import br.com.tasknoteapp.server.request.NotePatchRequest;
import br.com.tasknoteapp.server.request.NoteRequest;
@@ -199,10 +198,8 @@ void patchNote_unauthorized_shouldFail() throws Exception {
void postNotes_happyPath_shouldSucceed() throws Exception {
NoteRequest request = new NoteRequest("Title", "Description", null, null);
- NoteEntity entity = new NoteEntity();
- entity.setId(1L);
- entity.setTitle(request.title());
- entity.setDescription(request.description());
+ NoteResponse entity = new NoteResponse(1L, request.title(), request.description(),
+ null, null, null, false, null);
when(noteService.createNote(request)).thenReturn(entity);
@@ -223,9 +220,9 @@ void postNotes_happyPath_shouldSucceed() throws Exception {
.accept(MediaType.APPLICATION_JSON)
.content(payloadJson))
.andExpect(status().isCreated())
- .andExpect(jsonPath("$.id").value(entity.getId()))
- .andExpect(jsonPath("$.title").value(entity.getTitle()))
- .andExpect(jsonPath("$.description").value(entity.getDescription()))
+ .andExpect(jsonPath("$.id").value(entity.id()))
+ .andExpect(jsonPath("$.title").value(entity.title()))
+ .andExpect(jsonPath("$.description").value(entity.description()))
.andExpect(jsonPath("$.url", Matchers.nullValue()))
.andReturn();
}
diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/AuthServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/AuthServiceTest.java
index e1801ea..f7843bc 100644
--- a/server/src/test/java/br/com/tasknoteapp/server/service/AuthServiceTest.java
+++ b/server/src/test/java/br/com/tasknoteapp/server/service/AuthServiceTest.java
@@ -36,8 +36,6 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.env.Environment;
-import org.springframework.data.domain.Sort;
-import org.springframework.data.domain.Sort.Direction;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.User;
@@ -201,8 +199,8 @@ void signInUser_happyPath_shouldSucceed() {
existing.setEmail(request.email());
when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing));
- Sort sort = Sort.by(Direction.DESC, "whenHappened");
- when(userPwdLimitRepository.findAllByUser_id(existing.getId(), sort)).thenReturn(List.of());
+ when(userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(existing.getId()))
+ .thenReturn(List.of());
when(authenticationManager.authenticate(any())).thenReturn(null);
when(jwtService.generateToken(existing)).thenReturn("a1b2c3");
@@ -241,8 +239,7 @@ void signInUser_maxLoginAttempt_shouldFail() {
limit1.setWhenHappened(LocalDateTime.now().minusMinutes(1));
UserPwdLimitEntity limit2 = new UserPwdLimitEntity();
UserPwdLimitEntity limit3 = new UserPwdLimitEntity();
- Sort sort = Sort.by(Direction.DESC, "whenHappened");
- when(userPwdLimitRepository.findAllByUser_id(existing.getId(), sort))
+ when(userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(existing.getId()))
.thenReturn(List.of(limit1, limit2, limit3));
Assertions.assertThrows(
@@ -261,8 +258,8 @@ void signInUser_badCredentials_shouldFail() {
existing.setId(919L);
when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing));
- Sort sort = Sort.by(Direction.DESC, "whenHappened");
- when(userPwdLimitRepository.findAllByUser_id(existing.getId(), sort)).thenReturn(List.of());
+ when(userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(existing.getId()))
+ .thenReturn(List.of());
when(authenticationManager.authenticate(any())).thenThrow(new BadCredentialsException("Wrong"));
UserResponseWithToken token = authService.signInUser(request);
diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java
index 8d1e3f7..9afb562 100644
--- a/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java
+++ b/server/src/test/java/br/com/tasknoteapp/server/service/MailgunEmailServiceTest.java
@@ -30,14 +30,15 @@ class MailgunEmailServiceTest {
@BeforeEach
void setUp() {
+ when(restTemplateBuilder.connectTimeout(any())).thenReturn(restTemplateBuilder);
+ when(restTemplateBuilder.readTimeout(any())).thenReturn(restTemplateBuilder);
+ when(restTemplateBuilder.defaultHeader(any(), any())).thenReturn(restTemplateBuilder);
+ when(restTemplateBuilder.build()).thenReturn(restTemplate);
+
String apiKey = "abx123";
String domain = "domain.com";
String sender = "no-reply@domain.com";
String target = "development";
-
- when(restTemplateBuilder.defaultHeader(any(), any())).thenReturn(restTemplateBuilder);
- when(restTemplateBuilder.build()).thenReturn(restTemplate);
-
mailgunEmailService =
new MailgunEmailService(apiKey, domain, sender, target, restTemplateBuilder);
}
diff --git a/server/src/test/java/br/com/tasknoteapp/server/service/NoteServiceTest.java b/server/src/test/java/br/com/tasknoteapp/server/service/NoteServiceTest.java
index 7f0e756..2b6cda3 100644
--- a/server/src/test/java/br/com/tasknoteapp/server/service/NoteServiceTest.java
+++ b/server/src/test/java/br/com/tasknoteapp/server/service/NoteServiceTest.java
@@ -3,8 +3,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.eq;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -105,9 +105,9 @@ void createNote_withExistingCount() {
when(noteRepository.save(any(NoteEntity.class))).thenReturn(note);
when(noteUrlRepository.save(any(NoteUrlEntity.class))).thenReturn(new NoteUrlEntity());
- NoteEntity createdNote = noteService.createNote(noteRequest);
+ NoteResponse createdNote = noteService.createNote(noteRequest);
- assertEquals("Test Note", createdNote.getTitle());
+ assertEquals("Test Note", createdNote.title());
verify(noteRepository, times(1)).save(any(NoteEntity.class));
verify(noteUrlRepository, times(1)).save(any(NoteUrlEntity.class));
}
@@ -116,13 +116,14 @@ void createNote_withExistingCount() {
void patchNote() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
- when(noteRepository.findById(note.getId())).thenReturn(Optional.of(note));
+ when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
+ .thenReturn(Optional.of(note));
when(noteRepository.save(any(NoteEntity.class))).thenReturn(note);
NoteResponse patchedNote = noteService.patchNote(note.getId(), notePatchRequest);
assertEquals("Updated Note", patchedNote.title());
- verify(noteRepository, times(1)).findById(note.getId());
+ verify(noteRepository, times(1)).findByIdAndUser_id(note.getId(), user.getId());
verify(noteRepository, times(1)).save(any(NoteEntity.class));
}
@@ -130,7 +131,8 @@ void patchNote() {
void patchNote_notFound() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
- when(noteRepository.findById(note.getId())).thenReturn(Optional.empty());
+ when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
+ .thenReturn(Optional.empty());
Long noteId = note.getId();
assertThrows(
@@ -141,11 +143,12 @@ void patchNote_notFound() {
void deleteNote() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
- when(noteRepository.findById(note.getId())).thenReturn(Optional.of(note));
+ when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
+ .thenReturn(Optional.of(note));
noteService.deleteNote(note.getId());
- verify(noteRepository, times(1)).findById(note.getId());
+ verify(noteRepository, times(1)).findByIdAndUser_id(note.getId(), user.getId());
verify(noteRepository, times(1)).delete(note);
}
@@ -167,7 +170,8 @@ void searchNotes() {
void shareNote() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
- when(noteRepository.findById(note.getId())).thenReturn(Optional.of(note));
+ when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
+ .thenReturn(Optional.of(note));
when(noteRepository.save(any(NoteEntity.class))).thenReturn(note);
NoteResponse response = noteService.shareNote(note.getId());
@@ -180,7 +184,8 @@ void shareNote() {
void shareNote_notFound() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
- when(noteRepository.findById(note.getId())).thenReturn(Optional.empty());
+ when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
+ .thenReturn(Optional.empty());
Long noteId = note.getId();
assertThrows(NoteNotFoundException.class, () -> noteService.shareNote(noteId));
@@ -192,7 +197,8 @@ void unshareNote() {
note.setShareToken("some-token");
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
- when(noteRepository.findById(note.getId())).thenReturn(Optional.of(note));
+ when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
+ .thenReturn(Optional.of(note));
when(noteRepository.save(any(NoteEntity.class))).thenReturn(note);
NoteResponse response = noteService.unshareNote(note.getId());
@@ -205,7 +211,8 @@ void unshareNote() {
void unshareNote_notFound() {
when(authUtil.getCurrentUserEmail()).thenReturn(Optional.of(user.getEmail()));
when(authService.findByEmail(user.getEmail())).thenReturn(Optional.of(user));
- when(noteRepository.findById(note.getId())).thenReturn(Optional.empty());
+ when(noteRepository.findByIdAndUser_id(note.getId(), user.getId()))
+ .thenReturn(Optional.empty());
Long noteId = note.getId();
assertThrows(NoteNotFoundException.class, () -> noteService.unshareNote(noteId));