From 1a83c642c19868dd9c7155cc89049fc4d4777ba7 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:40:14 +0700
Subject: [PATCH 01/70] add: update dependencies in pom.xml and include new
 database and security configurations

---
 pom.xml | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 65 insertions(+), 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index 936334a..7399793 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,8 +66,8 @@
 		<!-- JUnit 5 for unit and regression testing -->
 		<dependency>
 			<groupId>org.junit.jupiter</groupId>
-			<artifactId>junit-jupiter-api</artifactId>
-			<version>5.7.1</version>
+			<artifactId>junit-jupiter</artifactId>
+			<version>5.11.2</version>
 			<scope>test</scope>
 		</dependency>
 
@@ -79,11 +79,34 @@
 			<scope>test</scope>
 		</dependency>
 
+		<dependency>
+			<groupId>org.postgresql</groupId>
+			<artifactId>postgresql</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+
+
 		<!-- REST-assured for integration testing -->
 		<dependency>
 			<groupId>io.rest-assured</groupId>
 			<artifactId>rest-assured</artifactId>
-			<version>4.4.0</version>
+			<version>4.5.1</version>
+			<scope>test</scope>
+			<exclusions>
+				<exclusion>
+					<groupId>org.codehaus.groovy</groupId>
+					<artifactId>groovy</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>org.codehaus.groovy</groupId>
+					<artifactId>groovy-xml</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>io.rest-assured</groupId>
+			<artifactId>json-schema-validator</artifactId>
+			<version>4.5.1</version>
 			<scope>test</scope>
 		</dependency>
 
@@ -94,6 +117,45 @@
 			<version>2.5.4</version>
 		</dependency>
 
+		<!-- Spring Data JPA -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-jpa</artifactId>
+		</dependency>
+
+		<!-- Spring Security for Password Encoding -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
+
+		<!-- H2 Database for development/testing -->
+		<dependency>
+			<groupId>com.h2database</groupId>
+			<artifactId>h2</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-test</artifactId>
+			<version>6.1.1</version>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-validation</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+
+
+
 	</dependencies>
 
 	<build>
-- 
GitLab


From ee659a1725f0c2a63fc59f4647e3608070490fce Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:41:46 +0700
Subject: [PATCH 02/70] [RED] Implement unit test for HelloController's
 sayHello method

---
 .../authentication/HelloControllerTest.java       | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
 create mode 100644 src/test/java/com/safetypin/authentication/HelloControllerTest.java

diff --git a/src/test/java/com/safetypin/authentication/HelloControllerTest.java b/src/test/java/com/safetypin/authentication/HelloControllerTest.java
new file mode 100644
index 0000000..cb308d4
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/HelloControllerTest.java
@@ -0,0 +1,15 @@
+package com.safetypin.authentication;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class HelloControllerTest {
+
+    private final HelloController helloController = new HelloController();
+
+    @Test
+    void testSayHelloReturnsHelloWorld() {
+        String greeting = helloController.sayHello();
+        assertEquals("Hello, World!", greeting);
+    }
+}
-- 
GitLab


From 3dbe6c09c06137a5cecdd63ff1c67f4eb7360433 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:42:32 +0700
Subject: [PATCH 03/70] [REFACTOR] Update AuthenticationApplicationTests to
 verify main method execution

---
 .../authentication/AuthenticationApplicationTests.java | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/AuthenticationApplicationTests.java b/src/test/java/com/safetypin/authentication/AuthenticationApplicationTests.java
index 599099f..a528070 100644
--- a/src/test/java/com/safetypin/authentication/AuthenticationApplicationTests.java
+++ b/src/test/java/com/safetypin/authentication/AuthenticationApplicationTests.java
@@ -1,13 +1,13 @@
 package com.safetypin.authentication;
 
 import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 
-@SpringBootTest
-class AuthenticationApplicationTests {
+class AuthenticationApplicationTest {
 
 	@Test
-	void contextLoads() {
+	void testMainDoesNotThrowException() {
+		// Calling the main method should load the context without throwing an exception.
+		assertDoesNotThrow(() -> AuthenticationApplication.main(new String[] {}));
 	}
-
 }
-- 
GitLab


From a81411a6f9aeffc5b6c1dc3e7e6c2c35ae503f12 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:42:53 +0700
Subject: [PATCH 04/70] [REFACTOR] Remove unnecessary blank line in
 AuthenticationApplication

---
 .../com/safetypin/authentication/AuthenticationApplication.java  | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/main/java/com/safetypin/authentication/AuthenticationApplication.java b/src/main/java/com/safetypin/authentication/AuthenticationApplication.java
index 9b1e951..b54a9d5 100644
--- a/src/main/java/com/safetypin/authentication/AuthenticationApplication.java
+++ b/src/main/java/com/safetypin/authentication/AuthenticationApplication.java
@@ -8,7 +8,6 @@ import org.springframework.web.bind.annotation.RestController;
 
 @SpringBootApplication
 public class AuthenticationApplication {
-
 	public static void main(String[] args) {
 		SpringApplication.run(AuthenticationApplication.class, args);
 	}
-- 
GitLab


From aef3175cdac1501b12e39ad1e05acef2c6853d1a Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:43:19 +0700
Subject: [PATCH 05/70] [RED] Implement unit tests for AuthenticationController
 methods

---
 .../AuthenticationControllerTest.java         | 187 ++++++++++++++++++
 1 file changed, 187 insertions(+)
 create mode 100644 src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java

diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
new file mode 100644
index 0000000..9d335f4
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
@@ -0,0 +1,187 @@
+package com.safetypin.authentication.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.safetypin.authentication.dto.PasswordResetRequest;
+import com.safetypin.authentication.dto.RegistrationRequest;
+import com.safetypin.authentication.dto.SocialLoginRequest;
+import com.safetypin.authentication.model.User;
+import com.safetypin.authentication.service.AuthenticationService;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.time.LocalDate;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@WebMvcTest(AuthenticationController.class)
+@Import({AuthenticationControllerTest.TestConfig.class, AuthenticationControllerTest.TestSecurityConfig.class})
+public class AuthenticationControllerTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @Autowired
+    private AuthenticationService authenticationService;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @TestConfiguration
+    static class TestConfig {
+        @Bean
+        public AuthenticationService authenticationService() {
+            return Mockito.mock(AuthenticationService.class);
+        }
+    }
+
+    @TestConfiguration
+    static class TestSecurityConfig {
+        @Bean
+        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+            http.csrf(csrf -> csrf.disable())
+                    .authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
+            return http.build();
+        }
+    }
+
+    @Test
+    public void testRegisterEmail() throws Exception {
+        RegistrationRequest request = new RegistrationRequest();
+        request.setEmail("email@example.com");
+        request.setPassword("password");
+        request.setName("Test User");
+        request.setBirthdate(LocalDate.now().minusYears(20));
+
+        User user = new User("email@example.com", "encodedPassword", "Test User", false, "USER",
+                request.getBirthdate(), "EMAIL", null);
+        user.setId(1L);
+        Mockito.when(authenticationService.registerUser(any(RegistrationRequest.class))).thenReturn(user);
+
+        mockMvc.perform(post("/api/auth/register-email")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.id").value(1L))
+                .andExpect(jsonPath("$.email").value("email@example.com"));
+    }
+
+    @Test
+    public void testRegisterSocial() throws Exception {
+        SocialLoginRequest request = new SocialLoginRequest();
+        request.setProvider("GOOGLE");
+        request.setSocialToken("token");
+        request.setEmail("social@example.com");
+        request.setName("Social User");
+        request.setBirthdate(LocalDate.now().minusYears(25));
+        request.setSocialId("social123");
+
+        User user = new User("social@example.com", null, "Social User", true, "USER",
+                request.getBirthdate(), "GOOGLE", "social123");
+        user.setId(2L);
+        Mockito.when(authenticationService.socialLogin(any(SocialLoginRequest.class))).thenReturn(user);
+
+        mockMvc.perform(post("/api/auth/register-social")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.id").value(2L))
+                .andExpect(jsonPath("$.email").value("social@example.com"));
+    }
+
+    @Test
+    public void testLoginEmail() throws Exception {
+        User user = new User("email@example.com", "encodedPassword", "Test User", true, "USER",
+                LocalDate.now().minusYears(20), "EMAIL", null);
+        user.setId(1L);
+        Mockito.when(authenticationService.loginUser(eq("email@example.com"), eq("password"))).thenReturn(user);
+
+        mockMvc.perform(post("/api/auth/login-email")
+                        .param("email", "email@example.com")
+                        .param("password", "password"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.id").value(1L))
+                .andExpect(jsonPath("$.email").value("email@example.com"));
+    }
+
+    @Test
+    public void testLoginSocial() throws Exception {
+        User user = new User("social@example.com", null, "Social User", true, "USER",
+                LocalDate.now().minusYears(25), "GOOGLE", "social123");
+        user.setId(2L);
+        Mockito.when(authenticationService.loginSocial(eq("social@example.com"))).thenReturn(user);
+
+        mockMvc.perform(post("/api/auth/login-social")
+                        .param("email", "social@example.com"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.id").value(2L))
+                .andExpect(jsonPath("$.email").value("social@example.com"));
+    }
+
+    @Test
+    public void testVerifyOTP_Success() throws Exception {
+        Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("123456"))).thenReturn(true);
+
+        mockMvc.perform(post("/api/auth/verify-otp")
+                        .param("email", "email@example.com")
+                        .param("otp", "123456"))
+                .andExpect(status().isOk())
+                .andExpect(content().string("User verified successfully"));
+    }
+
+    @Test
+    public void testVerifyOTP_Failure() throws Exception {
+        Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("000000"))).thenReturn(false);
+
+        mockMvc.perform(post("/api/auth/verify-otp")
+                        .param("email", "email@example.com")
+                        .param("otp", "000000"))
+                .andExpect(status().isOk())
+                .andExpect(content().string("OTP verification failed"));
+    }
+
+    @Test
+    public void testForgotPassword() throws Exception {
+        PasswordResetRequest request = new PasswordResetRequest();
+        request.setEmail("email@example.com");
+
+        Mockito.doNothing().when(authenticationService).forgotPassword("email@example.com");
+
+        mockMvc.perform(post("/api/auth/forgot-password")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isOk())
+                .andExpect(content().string("Password reset instructions have been sent to your email (simulated)"));
+    }
+
+    @Test
+    public void testPostContent() throws Exception {
+        Mockito.when(authenticationService.postContent(eq("email@example.com"), eq("Test Content")))
+                .thenReturn("Content posted successfully");
+
+        mockMvc.perform(post("/api/auth/post")
+                        .param("email", "email@example.com")
+                        .param("content", "Test Content"))
+                .andExpect(status().isOk())
+                .andExpect(content().string("Content posted successfully"));
+    }
+
+    @Test
+    public void testDashboard() throws Exception {
+        mockMvc.perform(get("/api/auth/dashboard"))
+                .andExpect(status().isOk())
+                .andExpect(content().string("{}"));
+    }
+}
-- 
GitLab


From 8fd71bed8b4cc9f53eb66b36861f018e6610af92 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:44:24 +0700
Subject: [PATCH 06/70] [GREEN] Add AuthenticationController with user
 registration and login endpoints

---
 .../controller/AuthenticationController.java  | 90 +++++++++++++++++++
 1 file changed, 90 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/controller/AuthenticationController.java

diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
new file mode 100644
index 0000000..9b09ad8
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
@@ -0,0 +1,90 @@
+package com.safetypin.authentication.controller;
+
+import com.safetypin.authentication.dto.PasswordResetRequest;
+import com.safetypin.authentication.dto.RegistrationRequest;
+import com.safetypin.authentication.dto.SocialLoginRequest;
+import com.safetypin.authentication.exception.InvalidCredentialsException;
+import com.safetypin.authentication.model.User;
+import com.safetypin.authentication.service.AuthenticationService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import jakarta.validation.Valid;
+import com.safetypin.authentication.dto.ErrorResponse;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/auth")
+public class AuthenticationController {
+
+    private final AuthenticationService authenticationService;
+
+    public AuthenticationController(AuthenticationService authenticationService) {
+        this.authenticationService = authenticationService;
+    }
+
+
+    // Endpoint for email registration
+    @PostMapping("/register-email")
+    public User registerEmail(@Valid @RequestBody RegistrationRequest request) {
+        return authenticationService.registerUser(request);
+    }
+
+    // Endpoint for social registration/login
+    @PostMapping("/register-social")
+    public User registerSocial(@Valid @RequestBody SocialLoginRequest request) {
+        return authenticationService.socialLogin(request);
+    }
+
+    // OTP verification endpoint
+    @PostMapping("/verify-otp")
+    public String verifyOTP(@RequestParam String email, @RequestParam String otp) {
+        boolean verified = authenticationService.verifyOTP(email, otp);
+        return verified ? "User verified successfully" : "OTP verification failed";
+    }
+
+
+
+    // Endpoint for email login
+    @PostMapping("/login-email")
+    public ResponseEntity<?> loginEmail(@RequestParam String email, @RequestParam String password) {
+        try {
+            return ResponseEntity.ok(authenticationService.loginUser(email, password));
+        } catch (InvalidCredentialsException e){
+            //TODO
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+                    .body(e.getMessage());
+        }
+
+    }
+
+    // Endpoint for social login
+    @PostMapping("/login-social")
+    public User loginSocial(@RequestParam String email) {
+        return authenticationService.loginSocial(email);
+    }
+
+
+
+
+    // Endpoint for forgot password (only for email users)
+    @PostMapping("/forgot-password")
+    public String forgotPassword(@Valid @RequestBody PasswordResetRequest request) {
+        authenticationService.forgotPassword(request.getEmail());
+        return "Password reset instructions have been sent to your email (simulated)";
+    }
+
+
+
+
+    // Endpoint simulating a content post that requires a verified account
+    @PostMapping("/post")
+    public String postContent(@RequestParam String email, @RequestParam String content) {
+        return authenticationService.postContent(email, content);
+    }
+
+    // On successful login, return an empty map as a placeholder for future reports
+    @GetMapping("/dashboard")
+    public String dashboard() {
+        return "{}";
+    }
+}
-- 
GitLab


From dcad3b5b311999f2c2791e239482df96ce341a9b Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:45:31 +0700
Subject: [PATCH 07/70] [RED] Add unit tests for UserRepository to verify email
 lookup functionality

---
 .../repository/UserRepositoryTest.java        |  46 +++
 .../service/AuthenticationServiceTest.java    | 333 ++++++++++++++++++
 2 files changed, 379 insertions(+)
 create mode 100644 src/test/java/com/safetypin/authentication/repository/UserRepositoryTest.java
 create mode 100644 src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java

diff --git a/src/test/java/com/safetypin/authentication/repository/UserRepositoryTest.java b/src/test/java/com/safetypin/authentication/repository/UserRepositoryTest.java
new file mode 100644
index 0000000..fd7e4ff
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/repository/UserRepositoryTest.java
@@ -0,0 +1,46 @@
+package com.safetypin.authentication.repository;
+
+import com.safetypin.authentication.model.User;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@DataJpaTest
+class UserRepositoryTest {
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @BeforeEach
+    void setUp() {
+        userRepository.deleteAll();
+
+        // Create and save a User entity
+        User user = new User();
+        user.setEmail("test@example.com");
+        user.setPassword("password");
+        user.setName("Test User");
+        user.setRole("USER");
+        userRepository.save(user);
+    }
+
+    @Test
+    void testFindByEmailWhenUserExists() {
+        // Retrieve the user by email
+        User foundUser = userRepository.findByEmail("test@example.com");
+        assertNotNull(foundUser, "Expected to find a user with the given email");
+        assertEquals("test@example.com", foundUser.getEmail());
+        assertEquals("Test User", foundUser.getName());
+    }
+
+    @Test
+    void testFindByEmailWhenUserDoesNotExist() {
+        // Attempt to find a user that doesn't exist
+        User foundUser = userRepository.findByEmail("nonexistent@example.com");
+        assertNull(foundUser, "Expected no user to be found for a non-existent email");
+    }
+
+}
diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
new file mode 100644
index 0000000..7064385
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
@@ -0,0 +1,333 @@
+package com.safetypin.authentication.service;
+
+import com.safetypin.authentication.dto.RegistrationRequest;
+import com.safetypin.authentication.dto.SocialLoginRequest;
+import com.safetypin.authentication.exception.InvalidCredentialsException;
+import com.safetypin.authentication.exception.UserAlreadyExistsException;
+import com.safetypin.authentication.model.User;
+import com.safetypin.authentication.repository.UserRepository;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class AuthenticationServiceTest {
+
+    @Mock
+    private UserRepository userRepository;
+
+    @Mock
+    private PasswordEncoder passwordEncoder;
+
+    @Mock
+    private OTPService otpService;
+
+    @InjectMocks
+    private AuthenticationService authenticationService;
+
+    // registerUser tests
+
+    @Test
+    public void testRegisterUser_UnderAge() {
+        RegistrationRequest request = new RegistrationRequest();
+        request.setEmail("test@example.com");
+        request.setPassword("password");
+        request.setName("Test User");
+        // set birthdate to 17 years old
+        request.setBirthdate(LocalDate.now().minusYears(17));
+
+        Exception exception = assertThrows(IllegalArgumentException.class, () ->
+                authenticationService.registerUser(request)
+        );
+        assertEquals("User must be at least 18 years old", exception.getMessage());
+    }
+
+    @Test
+    public void testRegisterUser_DuplicateEmail() {
+        RegistrationRequest request = new RegistrationRequest();
+        request.setEmail("test@example.com");
+        request.setPassword("password");
+        request.setName("Test User");
+        request.setBirthdate(LocalDate.now().minusYears(20));
+
+        when(userRepository.findByEmail("test@example.com")).thenReturn(new User());
+
+        Exception exception = assertThrows(UserAlreadyExistsException.class, () ->
+                authenticationService.registerUser(request)
+        );
+        assertTrue(exception.getMessage().contains("User already exists with this email"));
+    }
+
+    @Test
+    public void testRegisterUser_Success() {
+        RegistrationRequest request = new RegistrationRequest();
+        request.setEmail("test@example.com");
+        request.setPassword("password");
+        request.setName("Test User");
+        request.setBirthdate(LocalDate.now().minusYears(20));
+
+        when(userRepository.findByEmail("test@example.com")).thenReturn(null);
+        when(passwordEncoder.encode("password")).thenReturn("encodedPassword");
+        User savedUser = new User("test@example.com", "encodedPassword", "Test User", false, "USER",
+                request.getBirthdate(), "EMAIL", null);
+        savedUser.setId(1L);
+        when(userRepository.save(any(User.class))).thenReturn(savedUser);
+
+        User result = authenticationService.registerUser(request);
+        assertNotNull(result);
+        assertEquals("test@example.com", result.getEmail());
+        // OTPService should be invoked to generate OTP.
+        verify(otpService, times(1)).generateOTP("test@example.com");
+    }
+
+    // socialLogin tests
+
+    @Test
+    public void testSocialLogin_UnderAge() {
+        SocialLoginRequest request = new SocialLoginRequest();
+        request.setEmail("social@example.com");
+        request.setName("Social User");
+        request.setBirthdate(LocalDate.now().minusYears(17));
+        request.setProvider("GOOGLE");
+        request.setSocialId("social123");
+        request.setSocialToken("token");
+
+        Exception exception = assertThrows(IllegalArgumentException.class, () ->
+                authenticationService.socialLogin(request)
+        );
+        assertEquals("User must be at least 18 years old", exception.getMessage());
+    }
+
+    @Test
+    public void testSocialLogin_DuplicateEmailWithEmailProvider() {
+        SocialLoginRequest request = new SocialLoginRequest();
+        request.setEmail("social@example.com");
+        request.setName("Social User");
+        request.setBirthdate(LocalDate.now().minusYears(25));
+        request.setProvider("APPLE");
+        request.setSocialId("social123");
+        request.setSocialToken("token");
+
+        User existingUser = new User("social@example.com", "encodedPassword", "Existing User", false, "USER",
+                LocalDate.now().minusYears(30), "EMAIL", null);
+        when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser);
+
+        Exception exception = assertThrows(IllegalArgumentException.class, () ->
+                authenticationService.socialLogin(request)
+        );
+        assertTrue(exception.getMessage().contains("An account with this email exists"));
+    }
+
+    @Test
+    public void testSocialLogin_ExistingSocialUser() {
+        SocialLoginRequest request = new SocialLoginRequest();
+        request.setEmail("social@example.com");
+        request.setName("Social User");
+        request.setBirthdate(LocalDate.now().minusYears(25));
+        request.setProvider("GOOGLE");
+        request.setSocialId("social123");
+        request.setSocialToken("token");
+
+        User existingUser = new User("social@example.com", null, "Social User", true, "USER",
+                LocalDate.now().minusYears(25), "GOOGLE", "social123");
+        when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser);
+
+        User result = authenticationService.socialLogin(request);
+        assertNotNull(result);
+        assertEquals("social@example.com", result.getEmail());
+    }
+
+    @Test
+    public void testSocialLogin_NewUser() {
+        SocialLoginRequest request = new SocialLoginRequest();
+        request.setEmail("social@example.com");
+        request.setName("Social User");
+        request.setBirthdate(LocalDate.now().minusYears(25));
+        request.setProvider("GOOGLE");
+        request.setSocialId("social123");
+        request.setSocialToken("token");
+
+        when(userRepository.findByEmail("social@example.com")).thenReturn(null);
+        User savedUser = new User("social@example.com", null, "Social User", true, "USER",
+                request.getBirthdate(), "GOOGLE", "social123");
+        savedUser.setId(2L);
+        when(userRepository.save(any(User.class))).thenReturn(savedUser);
+
+        User result = authenticationService.socialLogin(request);
+        assertNotNull(result);
+        assertEquals("social@example.com", result.getEmail());
+    }
+
+    // loginUser tests
+
+    @Test
+    public void testLoginUser_EmailNotFound() {
+        when(userRepository.findByEmail("notfound@example.com")).thenReturn(null);
+        Exception exception = assertThrows(InvalidCredentialsException.class, () ->
+                authenticationService.loginUser("notfound@example.com", "password")
+        );
+        assertTrue(exception.getMessage().contains("Invalid email"));
+    }
+
+    @Test
+    public void testLoginUser_InvalidPassword_NullPassword() {
+        User user = new User("test@example.com", null, "Test User", true, "USER",
+                LocalDate.now().minusYears(20), "EMAIL", null);
+        when(userRepository.findByEmail("test@example.com")).thenReturn(user);
+
+        Exception exception = assertThrows(InvalidCredentialsException.class, () ->
+                authenticationService.loginUser("test@example.com", "password")
+        );
+        assertTrue(exception.getMessage().contains("Invalid password"));
+    }
+
+    @Test
+    public void testLoginUser_InvalidPassword_WrongMatch() {
+        User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
+                LocalDate.now().minusYears(20), "EMAIL", null);
+        when(userRepository.findByEmail("test@example.com")).thenReturn(user);
+        when(passwordEncoder.matches("wrongPassword", "encodedPassword")).thenReturn(false);
+
+        Exception exception = assertThrows(InvalidCredentialsException.class, () ->
+                authenticationService.loginUser("test@example.com", "wrongPassword")
+        );
+        assertTrue(exception.getMessage().contains("Invalid password"));
+    }
+
+    @Test
+    public void testLoginUser_Success() {
+        User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
+                LocalDate.now().minusYears(20), "EMAIL", null);
+        when(userRepository.findByEmail("test@example.com")).thenReturn(user);
+        when(passwordEncoder.matches("password", "encodedPassword")).thenReturn(true);
+
+        User result = authenticationService.loginUser("test@example.com", "password");
+        assertNotNull(result);
+        assertEquals("test@example.com", result.getEmail());
+    }
+
+    // loginSocial tests
+
+    @Test
+    public void testLoginSocial_UserNotFound() {
+        when(userRepository.findByEmail("notfound@example.com")).thenReturn(null);
+        Exception exception = assertThrows(InvalidCredentialsException.class, () ->
+                authenticationService.loginSocial("notfound@example.com")
+        );
+        assertTrue(exception.getMessage().contains("Social login failed"));
+    }
+
+    @Test
+    public void testLoginSocial_Success() {
+        User user = new User("social@example.com", null, "Social User", true, "USER",
+                LocalDate.now().minusYears(25), "GOOGLE", "social123");
+        when(userRepository.findByEmail("social@example.com")).thenReturn(user);
+
+        User result = authenticationService.loginSocial("social@example.com");
+        assertNotNull(result);
+        assertEquals("social@example.com", result.getEmail());
+    }
+
+    // verifyOTP tests
+
+    @Test
+    public void testVerifyOTP_Success() {
+        // OTPService returns true and user is found
+        when(otpService.verifyOTP("test@example.com", "123456")).thenReturn(true);
+        User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER",
+                LocalDate.now().minusYears(20), "EMAIL", null);
+        when(userRepository.findByEmail("test@example.com")).thenReturn(user);
+        when(userRepository.save(any(User.class))).thenReturn(user);
+
+        boolean result = authenticationService.verifyOTP("test@example.com", "123456");
+        assertTrue(result);
+        assertTrue(user.isVerified());
+        verify(userRepository, times(1)).save(user);
+    }
+
+    @Test
+    public void testVerifyOTP_Success_UserNotFound() {
+        // OTPService returns true but user is not found
+        when(otpService.verifyOTP("nonexistent@example.com", "123456")).thenReturn(true);
+        when(userRepository.findByEmail("nonexistent@example.com")).thenReturn(null);
+
+        boolean result = authenticationService.verifyOTP("nonexistent@example.com", "123456");
+        assertTrue(result);
+        verify(userRepository, never()).save(any(User.class));
+    }
+
+    @Test
+    public void testVerifyOTP_Failure() {
+        when(otpService.verifyOTP("test@example.com", "000000")).thenReturn(false);
+        boolean result = authenticationService.verifyOTP("test@example.com", "000000");
+        assertFalse(result);
+        verify(userRepository, never()).save(any(User.class));
+    }
+
+    // forgotPassword tests
+
+    @Test
+    public void testForgotPassword_Success() {
+        User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
+                LocalDate.now().minusYears(20), "EMAIL", null);
+        when(userRepository.findByEmail("test@example.com")).thenReturn(user);
+
+        assertDoesNotThrow(() -> authenticationService.forgotPassword("test@example.com"));
+    }
+
+    @Test
+    public void testForgotPassword_Invalid() {
+        // Case 1: user not found
+        when(userRepository.findByEmail("notfound@example.com")).thenReturn(null);
+        Exception exception1 = assertThrows(IllegalArgumentException.class, () ->
+                authenticationService.forgotPassword("notfound@example.com")
+        );
+        assertTrue(exception1.getMessage().contains("Password reset is only available for email-registered users."));
+
+        // Case 2: user exists but provider is not EMAIL
+        User user = new User("social@example.com", null, "Social User", true, "USER",
+                LocalDate.now().minusYears(25), "GOOGLE", "social123");
+        when(userRepository.findByEmail("social@example.com")).thenReturn(user);
+        Exception exception2 = assertThrows(IllegalArgumentException.class, () ->
+                authenticationService.forgotPassword("social@example.com")
+        );
+        assertTrue(exception2.getMessage().contains("Password reset is only available for email-registered users."));
+    }
+
+    // postContent tests
+
+    @Test
+    public void testPostContent_UserNotFound() {
+        when(userRepository.findByEmail("notfound@example.com")).thenReturn(null);
+        String response = authenticationService.postContent("notfound@example.com", "Content");
+        assertEquals("User not found. Please register.", response);
+    }
+
+    @Test
+    public void testPostContent_UserNotVerified() {
+        User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER",
+                LocalDate.now().minusYears(20), "EMAIL", null);
+        when(userRepository.findByEmail("test@example.com")).thenReturn(user);
+        String response = authenticationService.postContent("test@example.com", "Content");
+        assertTrue(response.contains("not verified"));
+    }
+
+    @Test
+    public void testPostContent_UserVerified() {
+        User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
+                LocalDate.now().minusYears(20), "EMAIL", null);
+        when(userRepository.findByEmail("test@example.com")).thenReturn(user);
+        String response = authenticationService.postContent("test@example.com", "Content");
+        assertEquals("Content posted successfully", response);
+    }
+}
-- 
GitLab


From 83c65b8ee866158a5df8aa586ddaf8994adcc639 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:45:41 +0700
Subject: [PATCH 08/70] [RED] Add unit tests for OTPService to validate OTP
 generation and verification

---
 .../service/OTPServiceTest.java               | 80 +++++++++++++++++++
 1 file changed, 80 insertions(+)
 create mode 100644 src/test/java/com/safetypin/authentication/service/OTPServiceTest.java

diff --git a/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java b/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java
new file mode 100644
index 0000000..cfca094
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java
@@ -0,0 +1,80 @@
+package com.safetypin.authentication.service;
+
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Constructor;
+import java.time.LocalDateTime;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class OTPServiceTest {
+
+    @Test
+    void testGenerateOTP() {
+        OTPService otpService = new OTPService();
+        String email = "user@example.com";
+        String otp = otpService.generateOTP(email);
+        assertNotNull(otp, "OTP should not be null");
+        assertEquals(6, otp.length(), "OTP should be 6 characters long");
+        assertTrue(otp.matches("\\d{6}"), "OTP should consist of 6 digits");
+    }
+
+    @Test
+    void testVerifyOTPSuccess() {
+        OTPService otpService = new OTPService();
+        String email = "user@example.com";
+        String otp = otpService.generateOTP(email);
+        // Immediately verify the generated OTP; it should succeed.
+        boolean result = otpService.verifyOTP(email, otp);
+        assertTrue(result, "The OTP should verify successfully");
+    }
+
+    @Test
+    void testVerifyOTPWrongOtp() {
+        OTPService otpService = new OTPService();
+        String email = "user@example.com";
+        otpService.generateOTP(email);
+        // Try verifying with an incorrect OTP.
+        boolean result = otpService.verifyOTP(email, "000000");
+        assertFalse(result, "Verification should fail for an incorrect OTP");
+    }
+
+    @Test
+    void testVerifyOTPExpired() throws Exception {
+        OTPService otpService = new OTPService();
+        String email = "user@example.com";
+        String otp = otpService.generateOTP(email);
+
+        // Access the private otpStorage field via reflection.
+        java.lang.reflect.Field otpStorageField = OTPService.class.getDeclaredField("otpStorage");
+        otpStorageField.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        ConcurrentHashMap<String, Object> otpStorage = (ConcurrentHashMap<String, Object>) otpStorageField.get(otpService);
+
+        // Retrieve the current OTPDetails instance.
+        Object oldOtpDetails = otpStorage.get(email);
+        assertNotNull(oldOtpDetails, "OTPDetails instance should exist");
+
+        // Use reflection to get the private constructor of OTPDetails.
+        Class<?> otpDetailsClass = oldOtpDetails.getClass();
+        Constructor<?> constructor = otpDetailsClass.getDeclaredConstructor(String.class, LocalDateTime.class);
+        constructor.setAccessible(true);
+        // Create a new OTPDetails instance with an expired time (3 minutes ago).
+        Object expiredOtpDetails = constructor.newInstance(otp, LocalDateTime.now().minusMinutes(3));
+        // Replace the old OTPDetails with the expired one.
+        otpStorage.put(email, expiredOtpDetails);
+
+        // Now verification should fail because the OTP is expired.
+        boolean result = otpService.verifyOTP(email, otp);
+        assertFalse(result, "The OTP should be expired and verification should fail");
+    }
+
+    @Test
+    void testVerifyOTPWhenNotGenerated() {
+        OTPService otpService = new OTPService();
+        // No OTP was generated for this email, so verification should return false.
+        boolean result = otpService.verifyOTP("nonexistent@example.com", "123456");
+        assertFalse(result, "Verification should fail when no OTP is generated for the given email");
+    }
+}
-- 
GitLab


From 97e905194c99d76d2a16af3a09c9fea65c4af0ba Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:48:01 +0700
Subject: [PATCH 09/70] [GREEN] Add UserRepository interface for user data
 access with email lookup

---
 .../authentication/repository/UserRepository.java      | 10 ++++++++++
 1 file changed, 10 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/repository/UserRepository.java

diff --git a/src/main/java/com/safetypin/authentication/repository/UserRepository.java b/src/main/java/com/safetypin/authentication/repository/UserRepository.java
new file mode 100644
index 0000000..bef7fa9
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/repository/UserRepository.java
@@ -0,0 +1,10 @@
+package com.safetypin.authentication.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import com.safetypin.authentication.model.User;
+
+@Repository
+public interface UserRepository extends JpaRepository<User, Long> {
+    User findByEmail(String email);
+}
-- 
GitLab


From 953b1f5fc3ddfdbc77d7b4d5604a7660ffd9bd3c Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:48:25 +0700
Subject: [PATCH 10/70] [GREEN] Add OTPService for generating and verifying
 one-time passwords

---
 .../authentication/service/OTPService.java    | 46 +++++++++++++++++++
 1 file changed, 46 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/service/OTPService.java

diff --git a/src/main/java/com/safetypin/authentication/service/OTPService.java b/src/main/java/com/safetypin/authentication/service/OTPService.java
new file mode 100644
index 0000000..ac9ee7d
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/service/OTPService.java
@@ -0,0 +1,46 @@
+package com.safetypin.authentication.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import java.time.LocalDateTime;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class OTPService {
+
+    private static final long OTP_EXPIRATION_SECONDS = 120; // 2 minutes expiration
+    private static final Logger log = LoggerFactory.getLogger(OTPService.class);
+    private final ConcurrentHashMap<String, OTPDetails> otpStorage = new ConcurrentHashMap<>();
+    private final Random random = new Random();
+
+    public String generateOTP(String email) {
+        String otp = String.format("%06d", random.nextInt(1000000));
+        OTPDetails details = new OTPDetails(otp, LocalDateTime.now());
+        otpStorage.put(email, details);
+        // Simulate sending OTP via email (in production, integrate with an email service)
+        log.info("Sending OTP {} to {}", otp, email);
+        return otp;
+    }
+
+    public boolean verifyOTP(String email, String otp) {
+        OTPDetails details = otpStorage.get(email);
+        if (details == null) {
+            return false;
+        }
+        // Check if OTP has expired
+        if (details.generatedAt().plusSeconds(OTP_EXPIRATION_SECONDS).isBefore(LocalDateTime.now())) {
+            otpStorage.remove(email);
+            return false;
+        }
+        if (details.otp().equals(otp)) {
+            otpStorage.remove(email);
+            return true;
+        }
+        return false;
+    }
+
+    private record OTPDetails(String otp, LocalDateTime generatedAt) {
+    }
+}
-- 
GitLab


From 40ad2141356a727cacbcc35678d2bf9f6eacc754 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:50:55 +0700
Subject: [PATCH 11/70] [GREEN] Add AuthenticationService for user
 registration, login, and OTP verification

---
 .../service/AuthenticationService.java        | 135 ++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/service/AuthenticationService.java

diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
new file mode 100644
index 0000000..5b92625
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -0,0 +1,135 @@
+package com.safetypin.authentication.service;
+
+import com.safetypin.authentication.dto.RegistrationRequest;
+import com.safetypin.authentication.dto.SocialLoginRequest;
+import com.safetypin.authentication.exception.InvalidCredentialsException;
+import com.safetypin.authentication.exception.UserAlreadyExistsException;
+import com.safetypin.authentication.model.User;
+import com.safetypin.authentication.repository.UserRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import java.time.LocalDate;
+import java.time.Period;
+
+@Service
+public class AuthenticationService {
+
+    private final UserRepository userRepository;
+    private final PasswordEncoder passwordEncoder;
+    private final OTPService otpService;
+    private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class);
+
+    public AuthenticationService(UserRepository userRepository, PasswordEncoder passwordEncoder, OTPService otpService) {
+        this.userRepository = userRepository;
+        this.passwordEncoder = passwordEncoder;
+        this.otpService = otpService;
+    }
+
+    // Registration using email – includes birthdate and OTP generation
+    public User registerUser(RegistrationRequest request) {
+        if (calculateAge(request.getBirthdate()) < 18) {
+            throw new IllegalArgumentException("User must be at least 18 years old");
+        }
+        if (userRepository.findByEmail(request.getEmail()) != null) {
+            throw new UserAlreadyExistsException("User already exists with this email. If you registered using social login, please sign in with Google/Apple.");
+        }
+        String encodedPassword = passwordEncoder.encode(request.getPassword());
+        User user = new User(request.getEmail(), encodedPassword, request.getName(), false, "USER",
+                request.getBirthdate(), "EMAIL", null);
+        user = userRepository.save(user);
+        otpService.generateOTP(request.getEmail());
+        logger.info("OTP generated for {} at {}", request.getEmail(), java.time.LocalDateTime.now());
+        return user;
+    }
+
+    // Social registration/login – simulating data fetched from Google/Apple
+    public User socialLogin(SocialLoginRequest request) {
+        if (calculateAge(request.getBirthdate()) < 18) {
+            throw new IllegalArgumentException("User must be at least 18 years old");
+        }
+        User existing = userRepository.findByEmail(request.getEmail());
+        if (existing != null) {
+            if ("EMAIL".equals(existing.getProvider())) {
+                throw new IllegalArgumentException("An account with this email exists. Please sign in using your email and password.");
+            }
+            return existing;
+        }
+        User user = new User(request.getEmail(), null, request.getName(), true, "USER",
+                request.getBirthdate(), request.getProvider().toUpperCase(), request.getSocialId());
+        user = userRepository.save(user);
+        logger.info("User registered via {}: {} at {}", request.getProvider(), request.getEmail(), java.time.LocalDateTime.now());
+        return user;
+    }
+
+    // Email login with detailed error messages
+    public User loginUser(String email, String rawPassword) {
+        User user = userRepository.findByEmail(email);
+        if (user == null) {
+            // email not exists
+            logger.warn("Login failed: Email not found for {}", email);
+            throw new InvalidCredentialsException("Invalid email");
+        }
+        if (!passwordEncoder.matches(rawPassword, user.getPassword())) {
+            // incorrect password
+            logger.warn("Login failed: Incorrect password for {}", email);
+            throw new InvalidCredentialsException("Invalid password");
+        }
+        logger.info("User logged in: {} at {}", email, java.time.LocalDateTime.now());
+        return user;
+    }
+
+    // Social login verification (assumed to be pre-verified externally)
+    public User loginSocial(String email) {
+        User user = userRepository.findByEmail(email);
+        if (user == null) {
+            throw new InvalidCredentialsException("Social login failed: Email not found");
+        }
+        logger.info("User logged in via social: {} at {}", email, java.time.LocalDateTime.now());
+        return user;
+    }
+
+    // OTP verification – marks user as verified upon success
+    public boolean verifyOTP(String email, String otp) {
+        boolean result = otpService.verifyOTP(email, otp);
+        if (result) {
+            User user = userRepository.findByEmail(email);
+            if (user != null) {
+                user.setVerified(true);
+                userRepository.save(user);
+                logger.info("OTP verified for {} at {}", email, java.time.LocalDateTime.now());
+            }
+        } else {
+            logger.warn("OTP verification failed for {} at {}", email, java.time.LocalDateTime.now());
+        }
+        return result;
+    }
+
+    // Forgot password – only applicable for email-registered users
+    public void forgotPassword(String email) {
+        User user = userRepository.findByEmail(email);
+        if (user == null || !"EMAIL".equals(user.getProvider())) {
+            throw new IllegalArgumentException("Password reset is only available for email-registered users.");
+        }
+        // In production, send a reset token via email.
+        logger.info("Password reset requested for {} at {}", email, java.time.LocalDateTime.now());
+    }
+
+    // Example method representing posting content that requires a verified account
+    public String postContent(String email, String content) {
+        User user = userRepository.findByEmail(email);
+        if (user == null) {
+            return "User not found. Please register.";
+        }
+        if (!user.isVerified()) {
+            return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes.";
+        }
+        // For demo purposes, we assume the post is successful.
+        return "Content posted successfully";
+    }
+
+    private int calculateAge(LocalDate birthdate) {
+        return Period.between(birthdate, LocalDate.now()).getYears();
+    }
+}
-- 
GitLab


From f661a1e7ed118eddd334d72b0048cbffd494a1d5 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:51:08 +0700
Subject: [PATCH 12/70] [RED] Add unit tests for DTOs to validate request and
 error response structures

---
 .../safetypin/authentication/dto/DtoTest.java | 93 +++++++++++++++++++
 1 file changed, 93 insertions(+)
 create mode 100644 src/test/java/com/safetypin/authentication/dto/DtoTest.java

diff --git a/src/test/java/com/safetypin/authentication/dto/DtoTest.java b/src/test/java/com/safetypin/authentication/dto/DtoTest.java
new file mode 100644
index 0000000..645db7e
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/dto/DtoTest.java
@@ -0,0 +1,93 @@
+package com.safetypin.authentication.dto;
+
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import jakarta.validation.ConstraintViolation;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Set;
+
+public class DtoTest {
+
+    private final Validator validator;
+
+    public DtoTest() {
+        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+        this.validator = factory.getValidator();
+    }
+
+    @Test
+    void testErrorResponseConstructor() {
+        ErrorResponse errorResponse = new ErrorResponse(404, "Resource not found");
+
+        assertThat(errorResponse.getStatus()).isEqualTo(404);
+        assertThat(errorResponse.getMessage()).isEqualTo("Resource not found");
+        assertThat(errorResponse.getTimestamp()).isNotNull();
+        assertThat(errorResponse.getTimestamp()).isBeforeOrEqualTo(LocalDateTime.now());
+    }
+
+    @Test
+    void testPasswordResetRequestValid() {
+        PasswordResetRequest request = new PasswordResetRequest();
+        request.setEmail("user@example.com");
+
+        Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request);
+        assertThat(violations).isEmpty();
+    }
+
+    @Test
+    void testPasswordResetRequestInvalidEmail() {
+        PasswordResetRequest request = new PasswordResetRequest();
+        request.setEmail("invalid-email");
+
+        Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request);
+        assertThat(violations).isNotEmpty();
+    }
+
+    @Test
+    void testRegistrationRequestValid() {
+        RegistrationRequest request = new RegistrationRequest();
+        request.setEmail("user@example.com");
+        request.setPassword("securePassword");
+        request.setName("John Doe");
+        request.setBirthdate(LocalDate.of(1995, 5, 10));
+
+        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);
+        assertThat(violations).isEmpty();
+    }
+
+    @Test
+    void testRegistrationRequestMissingFields() {
+        RegistrationRequest request = new RegistrationRequest(); // Missing required fields
+
+        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);
+        assertThat(violations).isNotEmpty();
+        assertThat(violations).hasSize(4); // Email, password, name, and birthdate should all be invalid
+    }
+
+    @Test
+    void testSocialLoginRequestValid() {
+        SocialLoginRequest request = new SocialLoginRequest();
+        request.setProvider("GOOGLE");
+        request.setSocialToken("validToken");
+        request.setEmail("socialuser@example.com");
+        request.setName("Social User");
+        request.setBirthdate(LocalDate.of(2000, 1, 1));
+        request.setSocialId("123456789");
+
+        Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request);
+        assertThat(violations).isEmpty();
+    }
+
+    @Test
+    void testSocialLoginRequestMissingFields() {
+        SocialLoginRequest request = new SocialLoginRequest(); // Missing required fields
+
+        Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request);
+        assertThat(violations).isNotEmpty();
+        assertThat(violations).hasSize(6); // All fields should be invalid
+    }
+}
-- 
GitLab


From 3df49e016cebfbf0f7818efd09ed024b9103120e Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:51:23 +0700
Subject: [PATCH 13/70] [GREEN] Add ErrorResponse DTO for standardized error
 handling in API responses

---
 .../authentication/dto/ErrorResponse.java     | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/dto/ErrorResponse.java

diff --git a/src/main/java/com/safetypin/authentication/dto/ErrorResponse.java b/src/main/java/com/safetypin/authentication/dto/ErrorResponse.java
new file mode 100644
index 0000000..e7bb410
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/dto/ErrorResponse.java
@@ -0,0 +1,25 @@
+package com.safetypin.authentication.dto;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Data
+@Getter
+@Setter
+@NoArgsConstructor
+public class ErrorResponse{
+    private int status;
+    private String message;
+    private LocalDateTime timestamp;
+
+    public ErrorResponse(int status, String message) {
+        this.status = status;
+        this.message = message;
+        this.timestamp = LocalDateTime.now();
+    }
+
+}
\ No newline at end of file
-- 
GitLab


From 2beecbe70a606c0ef2a64c668ffa1629a4760368 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:51:31 +0700
Subject: [PATCH 14/70] [GREEN] Add PasswordResetRequest DTO for handling
 password reset requests

---
 .../dto/PasswordResetRequest.java              | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/dto/PasswordResetRequest.java

diff --git a/src/main/java/com/safetypin/authentication/dto/PasswordResetRequest.java b/src/main/java/com/safetypin/authentication/dto/PasswordResetRequest.java
new file mode 100644
index 0000000..9b12ed8
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/dto/PasswordResetRequest.java
@@ -0,0 +1,18 @@
+package com.safetypin.authentication.dto;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class PasswordResetRequest {
+
+    @NotBlank
+    @Email
+    private String email;
+
+    // Getters and setters
+
+}
-- 
GitLab


From 1c1a542a225b8283417c2e37a0d8d88ca3ab744a Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:51:41 +0700
Subject: [PATCH 15/70] [GREEN] Add RegistrationRequest DTO for user
 registration data validation

---
 .../dto/RegistrationRequest.java              | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/dto/RegistrationRequest.java

diff --git a/src/main/java/com/safetypin/authentication/dto/RegistrationRequest.java b/src/main/java/com/safetypin/authentication/dto/RegistrationRequest.java
new file mode 100644
index 0000000..ddb267f
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/dto/RegistrationRequest.java
@@ -0,0 +1,30 @@
+package com.safetypin.authentication.dto;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDate;
+
+@Setter
+@Getter
+public class RegistrationRequest {
+
+    @NotBlank
+    @Email
+    private String email;
+
+    @NotBlank
+    private String password;
+
+    @NotBlank
+    private String name;
+
+    @NotNull
+    private LocalDate birthdate;
+
+    // Getters and setters
+
+}
-- 
GitLab


From 242cf0a5040c7b11610438afdba2b08f5cd5b8ea Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:51:54 +0700
Subject: [PATCH 16/70] [GREEN] Add SocialLoginRequest DTO and custom
 exceptions for user authentication

---
 .../dto/SocialLoginRequest.java               | 35 +++++++++++++++++++
 1 file changed, 35 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/dto/SocialLoginRequest.java

diff --git a/src/main/java/com/safetypin/authentication/dto/SocialLoginRequest.java b/src/main/java/com/safetypin/authentication/dto/SocialLoginRequest.java
new file mode 100644
index 0000000..1b34b2e
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/dto/SocialLoginRequest.java
@@ -0,0 +1,35 @@
+package com.safetypin.authentication.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDate;
+
+@Setter
+@Getter
+public class SocialLoginRequest {
+
+    @NotBlank
+    private String provider; // "GOOGLE" or "APPLE"
+
+    @NotBlank
+    private String socialToken; // Token from the social provider
+
+    // Simulated fields as if retrieved from the provider
+    @NotBlank
+    private String email;
+
+    @NotBlank
+    private String name;
+
+    @NotNull
+    private LocalDate birthdate;
+
+    @NotBlank
+    private String socialId; // ID provided by the social provider
+
+    // Getters and setters
+
+}
-- 
GitLab


From 03176bde2547882da4e9f42b6710e357de2fdbe9 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:52:11 +0700
Subject: [PATCH 17/70] [RED] Add unit tests for User model to validate
 constructors and field defaults

---
 .../authentication/model/UserTest.java        | 82 +++++++++++++++++++
 1 file changed, 82 insertions(+)
 create mode 100644 src/test/java/com/safetypin/authentication/model/UserTest.java

diff --git a/src/test/java/com/safetypin/authentication/model/UserTest.java b/src/test/java/com/safetypin/authentication/model/UserTest.java
new file mode 100644
index 0000000..2001bd2
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/model/UserTest.java
@@ -0,0 +1,82 @@
+package com.safetypin.authentication.model;
+
+import org.junit.jupiter.api.Test;
+import java.time.LocalDate;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class UserTest {
+
+    @Test
+    public void testDefaultConstructorDefaults() {
+        User user = new User();
+        // Verify that default constructor sets all fields to their default values
+        assertNull(user.getId(), "Default id should be null");
+        assertNull(user.getEmail(), "Default email should be null");
+        assertNull(user.getPassword(), "Default password should be null");
+        assertNull(user.getName(), "Default name should be null");
+        assertFalse(user.isVerified(), "Default isVerified should be false");
+        assertNull(user.getRole(), "Default role should be null");
+        assertNull(user.getBirthdate(), "Default birthdate should be null");
+        assertNull(user.getProvider(), "Default provider should be null");
+        assertNull(user.getSocialId(), "Default socialId should be null");
+    }
+
+    @Test
+    public void testSettersAndGetters() {
+        User user = new User();
+        Long id = 123L;
+        String email = "test@example.com";
+        String password = "secret";
+        String name = "Test User";
+        boolean verified = true;
+        String role = "ADMIN";
+        LocalDate birthdate = LocalDate.of(2000, 1, 1);
+        String provider = "GOOGLE";
+        String socialId = "social123";
+
+        user.setId(id);
+        user.setEmail(email);
+        user.setPassword(password);
+        user.setName(name);
+        user.setVerified(verified);
+        user.setRole(role);
+        user.setBirthdate(birthdate);
+        user.setProvider(provider);
+        user.setSocialId(socialId);
+
+        assertEquals(id, user.getId());
+        assertEquals(email, user.getEmail());
+        assertEquals(password, user.getPassword());
+        assertEquals(name, user.getName());
+        assertTrue(user.isVerified());
+        assertEquals(role, user.getRole());
+        assertEquals(birthdate, user.getBirthdate());
+        assertEquals(provider, user.getProvider());
+        assertEquals(socialId, user.getSocialId());
+    }
+
+    @Test
+    public void testParameterizedConstructor() {
+        String email = "test2@example.com";
+        String password = "password123";
+        String name = "Another User";
+        boolean verified = false;
+        String role = "USER";
+        LocalDate birthdate = LocalDate.of(1995, 5, 15);
+        String provider = "EMAIL";
+        String socialId = null;
+
+        User user = new User(email, password, name, verified, role, birthdate, provider, socialId);
+
+        // id remains null until set (by the persistence layer)
+        assertNull(user.getId(), "Id should be null when not set");
+        assertEquals(email, user.getEmail());
+        assertEquals(password, user.getPassword());
+        assertEquals(name, user.getName());
+        assertEquals(verified, user.isVerified());
+        assertEquals(role, user.getRole());
+        assertEquals(birthdate, user.getBirthdate());
+        assertEquals(provider, user.getProvider());
+        assertNull(user.getSocialId(), "SocialId should be null");
+    }
+}
-- 
GitLab


From 88ffc89ec7c2778db757672c86976aced09baa9c Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:52:33 +0700
Subject: [PATCH 18/70] [GREEN] Add User entity with fields for authentication
 and social login

---
 .../safetypin/authentication/model/User.java  | 76 +++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/model/User.java

diff --git a/src/main/java/com/safetypin/authentication/model/User.java b/src/main/java/com/safetypin/authentication/model/User.java
new file mode 100644
index 0000000..c08cb81
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/model/User.java
@@ -0,0 +1,76 @@
+package com.safetypin.authentication.model;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDate;
+
+@Entity
+@Table(name = "users")
+public class User {
+
+    @Setter
+    @Getter
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Setter
+    @Getter
+    @Column(nullable = false, unique = true)
+    private String email;
+
+    // May be null for social login users
+    @Setter
+    @Getter
+    @Column(nullable = false)
+    private String password;
+
+    @Setter
+    @Getter
+    @Column(nullable = false)
+    private String name;
+
+    @Column(nullable = false)
+    private boolean isVerified = false;
+
+    @Setter
+    @Getter
+    private String role;
+
+    // New fields
+    @Setter
+    @Getter
+    private LocalDate birthdate;
+    @Setter
+    @Getter
+    private String provider;  // "EMAIL", "GOOGLE", "APPLE"
+    @Setter
+    @Getter
+    private String socialId;  // For social login users
+
+    public User() {}
+
+    public User(String email, String password, String name, boolean isVerified, String role,
+                LocalDate birthdate, String provider, String socialId) {
+        this.email = email;
+        this.password = password;
+        this.name = name;
+        this.isVerified = isVerified;
+        this.role = role;
+        this.birthdate = birthdate;
+        this.provider = provider;
+        this.socialId = socialId;
+    }
+
+    // Getters and setters
+
+    public boolean isVerified() {
+        return isVerified;
+    }
+    public void setVerified(boolean verified) {
+        isVerified = verified;
+    }
+
+}
-- 
GitLab


From 374c23bb2afd942744dd6f26b0dad6118ddf2431 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:52:47 +0700
Subject: [PATCH 19/70] [GREEN] Add custom exceptions for user authentication
 errors

---
 .../exception/InvalidCredentialsException.java             | 7 +++++++
 .../exception/UserAlreadyExistsException.java              | 7 +++++++
 2 files changed, 14 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/exception/InvalidCredentialsException.java
 create mode 100644 src/main/java/com/safetypin/authentication/exception/UserAlreadyExistsException.java

diff --git a/src/main/java/com/safetypin/authentication/exception/InvalidCredentialsException.java b/src/main/java/com/safetypin/authentication/exception/InvalidCredentialsException.java
new file mode 100644
index 0000000..e43d14a
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/exception/InvalidCredentialsException.java
@@ -0,0 +1,7 @@
+package com.safetypin.authentication.exception;
+
+public class InvalidCredentialsException extends RuntimeException {
+    public InvalidCredentialsException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/com/safetypin/authentication/exception/UserAlreadyExistsException.java b/src/main/java/com/safetypin/authentication/exception/UserAlreadyExistsException.java
new file mode 100644
index 0000000..c31a5a7
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/exception/UserAlreadyExistsException.java
@@ -0,0 +1,7 @@
+package com.safetypin.authentication.exception;
+
+public class UserAlreadyExistsException extends RuntimeException {
+    public UserAlreadyExistsException(String message) {
+        super(message);
+    }
+}
-- 
GitLab


From 0c05edf95cba5d439875c3e16693b424b816b372 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:52:56 +0700
Subject: [PATCH 20/70] Add security configuration and password encoder for
 authentication

---
 .../security/PasswordEncoderConfig.java       | 14 ++++++++
 .../security/SecurityConfig.java              | 34 +++++++++++++++++++
 2 files changed, 48 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/security/PasswordEncoderConfig.java
 create mode 100644 src/main/java/com/safetypin/authentication/security/SecurityConfig.java

diff --git a/src/main/java/com/safetypin/authentication/security/PasswordEncoderConfig.java b/src/main/java/com/safetypin/authentication/security/PasswordEncoderConfig.java
new file mode 100644
index 0000000..a4e107f
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/security/PasswordEncoderConfig.java
@@ -0,0 +1,14 @@
+package com.safetypin.authentication.security;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+public class PasswordEncoderConfig {
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+}
diff --git a/src/main/java/com/safetypin/authentication/security/SecurityConfig.java b/src/main/java/com/safetypin/authentication/security/SecurityConfig.java
new file mode 100644
index 0000000..c79b353
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/security/SecurityConfig.java
@@ -0,0 +1,34 @@
+package com.safetypin.authentication.security;
+
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+public class SecurityConfig {
+
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http
+                .csrf(AbstractHttpConfigurer::disable)  // Disable CSRF protection (not recommended for production)
+                .authorizeHttpRequests(auth -> auth
+                        .requestMatchers("/**").permitAll() // Allow all requests
+                )
+                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // No session
+                .formLogin(AbstractHttpConfigurer::disable) // Disable login page
+                .httpBasic(AbstractHttpConfigurer::disable); // Disable basic authentication
+
+        return http.build();
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+        return authenticationConfiguration.getAuthenticationManager();
+    }
+}
-- 
GitLab


From 14b0c0834a2f6c75840f4addcc3ebd993ada5510 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 13:53:02 +0700
Subject: [PATCH 21/70] Add DevDataSeeder to populate dummy users in the
 development environment

---
 .../authentication/seeder/DevDataSeeder.java  | 48 +++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java

diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
new file mode 100644
index 0000000..9edfd57
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
@@ -0,0 +1,48 @@
+package com.safetypin.authentication.seeder;
+
+import com.safetypin.authentication.model.User;
+import com.safetypin.authentication.repository.UserRepository;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDate;
+import java.util.List;
+
+@Component
+@Profile({"dev"})  // Runs only in 'dev' profile
+public class DevDataSeeder implements CommandLineRunner {
+
+    private final UserRepository userRepository;
+
+    private final PasswordEncoder passwordEncoder;
+
+    public DevDataSeeder(UserRepository userRepository, PasswordEncoder passwordEncoder) {
+        this.userRepository = userRepository;
+        this.passwordEncoder = passwordEncoder;
+    }
+
+    @Override
+    public void run(String... args) {
+        if (userRepository.count() == 0) {
+            userRepository.saveAll(List.of(
+                    new User("alice@example.com", passwordEncoder.encode("password123"), "Alice Johnson", true, "developer",
+                            LocalDate.of(1998, 5, 21), "EMAIL", "social_1001"),
+                    new User("bob@example.com", passwordEncoder.encode("password456"), "Bob Smith", false, "designer",
+                            LocalDate.of(2000, 8, 15), "GOOGLE", "social_1002"),
+                    new User("charlie@example.com", passwordEncoder.encode("password789"), "Charlie Davis", true, "manager",
+                            LocalDate.of(1995, 12, 3), "APPLE", "social_1003"),
+                    new User("diana@example.com", passwordEncoder.encode("password321"), "Diana Roberts", true, "QA engineer",
+                            LocalDate.of(2002, 6, 10), "EMAIL", "social_1004"),
+                    new User("ethan@example.com", passwordEncoder.encode("password654"), "Ethan Brown", false, "data analyst",
+                            LocalDate.of(1999, 11, 27), "EMAIL", "social_1005")
+            ));
+            System.out.println("Dummy users inserted in DEV environment");
+        } else {
+            System.out.println("User repo is not empty");
+        }
+
+
+    }
+}
\ No newline at end of file
-- 
GitLab


From c6aa639857ca0775dd3077f70250626ae00a8b84 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 14:50:47 +0700
Subject: [PATCH 22/70] Add JNA dependency for testing and remove DtoTest
 class; configure H2 in-memory database for tests

---
 pom.xml                                       |  7 ++
 .../safetypin/authentication/dto/DtoTest.java | 93 -------------------
 src/test/resources/application-dev.properties | 14 +++
 3 files changed, 21 insertions(+), 93 deletions(-)
 delete mode 100644 src/test/java/com/safetypin/authentication/dto/DtoTest.java
 create mode 100644 src/test/resources/application-dev.properties

diff --git a/pom.xml b/pom.xml
index 7399793..bfd64b5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -154,6 +154,13 @@
 			<scope>test</scope>
 		</dependency>
 
+		<dependency>
+			<groupId>net.java.dev.jna</groupId>
+			<artifactId>jna-platform</artifactId>
+			<version>5.13.0</version>
+			<scope>test</scope>
+		</dependency>
+
 
 
 	</dependencies>
diff --git a/src/test/java/com/safetypin/authentication/dto/DtoTest.java b/src/test/java/com/safetypin/authentication/dto/DtoTest.java
deleted file mode 100644
index 645db7e..0000000
--- a/src/test/java/com/safetypin/authentication/dto/DtoTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.safetypin.authentication.dto;
-
-import org.junit.jupiter.api.Test;
-import static org.assertj.core.api.Assertions.assertThat;
-import jakarta.validation.Validation;
-import jakarta.validation.Validator;
-import jakarta.validation.ValidatorFactory;
-import jakarta.validation.ConstraintViolation;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.Set;
-
-public class DtoTest {
-
-    private final Validator validator;
-
-    public DtoTest() {
-        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
-        this.validator = factory.getValidator();
-    }
-
-    @Test
-    void testErrorResponseConstructor() {
-        ErrorResponse errorResponse = new ErrorResponse(404, "Resource not found");
-
-        assertThat(errorResponse.getStatus()).isEqualTo(404);
-        assertThat(errorResponse.getMessage()).isEqualTo("Resource not found");
-        assertThat(errorResponse.getTimestamp()).isNotNull();
-        assertThat(errorResponse.getTimestamp()).isBeforeOrEqualTo(LocalDateTime.now());
-    }
-
-    @Test
-    void testPasswordResetRequestValid() {
-        PasswordResetRequest request = new PasswordResetRequest();
-        request.setEmail("user@example.com");
-
-        Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request);
-        assertThat(violations).isEmpty();
-    }
-
-    @Test
-    void testPasswordResetRequestInvalidEmail() {
-        PasswordResetRequest request = new PasswordResetRequest();
-        request.setEmail("invalid-email");
-
-        Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request);
-        assertThat(violations).isNotEmpty();
-    }
-
-    @Test
-    void testRegistrationRequestValid() {
-        RegistrationRequest request = new RegistrationRequest();
-        request.setEmail("user@example.com");
-        request.setPassword("securePassword");
-        request.setName("John Doe");
-        request.setBirthdate(LocalDate.of(1995, 5, 10));
-
-        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);
-        assertThat(violations).isEmpty();
-    }
-
-    @Test
-    void testRegistrationRequestMissingFields() {
-        RegistrationRequest request = new RegistrationRequest(); // Missing required fields
-
-        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);
-        assertThat(violations).isNotEmpty();
-        assertThat(violations).hasSize(4); // Email, password, name, and birthdate should all be invalid
-    }
-
-    @Test
-    void testSocialLoginRequestValid() {
-        SocialLoginRequest request = new SocialLoginRequest();
-        request.setProvider("GOOGLE");
-        request.setSocialToken("validToken");
-        request.setEmail("socialuser@example.com");
-        request.setName("Social User");
-        request.setBirthdate(LocalDate.of(2000, 1, 1));
-        request.setSocialId("123456789");
-
-        Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request);
-        assertThat(violations).isEmpty();
-    }
-
-    @Test
-    void testSocialLoginRequestMissingFields() {
-        SocialLoginRequest request = new SocialLoginRequest(); // Missing required fields
-
-        Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request);
-        assertThat(violations).isNotEmpty();
-        assertThat(violations).hasSize(6); // All fields should be invalid
-    }
-}
diff --git a/src/test/resources/application-dev.properties b/src/test/resources/application-dev.properties
new file mode 100644
index 0000000..e42e68f
--- /dev/null
+++ b/src/test/resources/application-dev.properties
@@ -0,0 +1,14 @@
+# src/test/resources/application-dev.properties
+
+# Configure H2 in-memory database
+spring.datasource.url=jdbc:h2:mem:devdb;DB_CLOSE_DELAY=-1
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.username=sa
+spring.datasource.password=
+
+# JPA/Hibernate configurations
+spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
+spring.jpa.hibernate.ddl-auto=create-drop
+
+# (Optional) Enable SQL logging
+spring.jpa.show-sql=true
-- 
GitLab


From 2f2123477f32c94c94fd50842109fc53f2fcc026 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 14:51:21 +0700
Subject: [PATCH 23/70] [REFACTOR] Add unit tests for error response and
 request validation in authentication DTOs

---
 .../authentication/dto/ErrorResponseTest.java | 18 ++++++++
 .../dto/PasswordResetRequestTest.java         | 37 ++++++++++++++++
 .../dto/RegistrationRequestTest.java          | 42 ++++++++++++++++++
 .../dto/SocialLoginRequestTest.java           | 44 +++++++++++++++++++
 4 files changed, 141 insertions(+)
 create mode 100644 src/test/java/com/safetypin/authentication/dto/ErrorResponseTest.java
 create mode 100644 src/test/java/com/safetypin/authentication/dto/PasswordResetRequestTest.java
 create mode 100644 src/test/java/com/safetypin/authentication/dto/RegistrationRequestTest.java
 create mode 100644 src/test/java/com/safetypin/authentication/dto/SocialLoginRequestTest.java

diff --git a/src/test/java/com/safetypin/authentication/dto/ErrorResponseTest.java b/src/test/java/com/safetypin/authentication/dto/ErrorResponseTest.java
new file mode 100644
index 0000000..1398d5a
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/dto/ErrorResponseTest.java
@@ -0,0 +1,18 @@
+package com.safetypin.authentication.dto;
+
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import java.time.LocalDateTime;
+
+class ErrorResponseTest {
+
+    @Test
+    void testErrorResponseConstructor() {
+        ErrorResponse errorResponse = new ErrorResponse(404, "Resource not found");
+
+        assertThat(errorResponse.getStatus()).isEqualTo(404);
+        assertThat(errorResponse.getMessage()).isEqualTo("Resource not found");
+        assertThat(errorResponse.getTimestamp()).isNotNull();
+        assertThat(errorResponse.getTimestamp()).isBeforeOrEqualTo(LocalDateTime.now());
+    }
+}
diff --git a/src/test/java/com/safetypin/authentication/dto/PasswordResetRequestTest.java b/src/test/java/com/safetypin/authentication/dto/PasswordResetRequestTest.java
new file mode 100644
index 0000000..013ec5d
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/dto/PasswordResetRequestTest.java
@@ -0,0 +1,37 @@
+package com.safetypin.authentication.dto;
+
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import jakarta.validation.ConstraintViolation;
+import java.util.Set;
+
+class PasswordResetRequestTest {
+
+    private final Validator validator;
+
+    public PasswordResetRequestTest() {
+        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+        this.validator = factory.getValidator();
+    }
+
+    @Test
+    void testPasswordResetRequestValid() {
+        PasswordResetRequest request = new PasswordResetRequest();
+        request.setEmail("user@example.com");
+
+        Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request);
+        assertThat(violations).isEmpty();
+    }
+
+    @Test
+    void testPasswordResetRequestInvalidEmail() {
+        PasswordResetRequest request = new PasswordResetRequest();
+        request.setEmail("invalid-email");
+
+        Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request);
+        assertThat(violations).isNotEmpty();
+    }
+}
diff --git a/src/test/java/com/safetypin/authentication/dto/RegistrationRequestTest.java b/src/test/java/com/safetypin/authentication/dto/RegistrationRequestTest.java
new file mode 100644
index 0000000..b231e67
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/dto/RegistrationRequestTest.java
@@ -0,0 +1,42 @@
+package com.safetypin.authentication.dto;
+
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import jakarta.validation.ConstraintViolation;
+import java.time.LocalDate;
+import java.util.Set;
+
+class RegistrationRequestTest {
+
+    private final Validator validator;
+
+    public RegistrationRequestTest() {
+        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+        this.validator = factory.getValidator();
+    }
+
+    @Test
+    void testRegistrationRequestValid() {
+        RegistrationRequest request = new RegistrationRequest();
+        request.setEmail("user@example.com");
+        request.setPassword("securePassword");
+        request.setName("John Doe");
+        request.setBirthdate(LocalDate.of(1995, 5, 10));
+
+        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);
+        assertThat(violations).isEmpty();
+    }
+
+    @Test
+    void testRegistrationRequestMissingFields() {
+        RegistrationRequest request = new RegistrationRequest(); // Missing required fields
+
+        Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request);
+        assertThat(violations)
+                .isNotEmpty()
+                .hasSize(4); // Email, password, name, and birthdate should all be invalid
+    }
+}
diff --git a/src/test/java/com/safetypin/authentication/dto/SocialLoginRequestTest.java b/src/test/java/com/safetypin/authentication/dto/SocialLoginRequestTest.java
new file mode 100644
index 0000000..fe4a2a6
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/dto/SocialLoginRequestTest.java
@@ -0,0 +1,44 @@
+package com.safetypin.authentication.dto;
+
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import jakarta.validation.ConstraintViolation;
+import java.time.LocalDate;
+import java.util.Set;
+
+class SocialLoginRequestTest {
+
+    private final Validator validator;
+
+    public SocialLoginRequestTest() {
+        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+        this.validator = factory.getValidator();
+    }
+
+    @Test
+    void testSocialLoginRequestValid() {
+        SocialLoginRequest request = new SocialLoginRequest();
+        request.setProvider("GOOGLE");
+        request.setSocialToken("validToken");
+        request.setEmail("socialuser@example.com");
+        request.setName("Social User");
+        request.setBirthdate(LocalDate.of(2000, 1, 1));
+        request.setSocialId("123456789");
+
+        Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request);
+        assertThat(violations).isEmpty();
+    }
+
+    @Test
+    void testSocialLoginRequestMissingFields() {
+        SocialLoginRequest request = new SocialLoginRequest(); // Missing required fields
+
+        Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request);
+        assertThat(violations)
+                .isNotEmpty()
+                .hasSize(6); // All fields should be invalid
+    }
+}
-- 
GitLab


From d9732c644e1d4398d50243b4af5caf49ee9a6349 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 14:51:44 +0700
Subject: [PATCH 24/70] [RED] Add unit tests for DevDataSeeder to verify user
 insertion logic

---
 .../seeder/DevDataSeederTest.java             | 60 +++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100644 src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java

diff --git a/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java
new file mode 100644
index 0000000..8b58e96
--- /dev/null
+++ b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java
@@ -0,0 +1,60 @@
+package com.safetypin.authentication.seeder;
+
+import com.safetypin.authentication.model.User;
+import com.safetypin.authentication.repository.UserRepository;
+import jakarta.transaction.Transactional;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.time.LocalDate;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@SpringBootTest
+@ActiveProfiles("dev")  // Use the 'dev' profile during tests
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@Transactional
+class DevDataSeederTest {
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+
+    // Test that the seeder inserts 5 users when no users exist
+    @Test
+    void testSeederInsertsUsersWhenEmpty() {
+        userRepository.deleteAll(); // Ensure the database is empty before seeding
+
+        new DevDataSeeder(userRepository, passwordEncoder).run(); // Run the seeder
+
+        List<User> users = userRepository.findAll();
+        assertEquals(5, users.size(), "Seeder should insert 5 users when repository is empty");
+    }
+
+    // Test that the seeder does not add any users if at least one user already exists
+    @Test
+    void testSeederDoesNotInsertIfUsersExist() {
+        // Save an existing user into the repository
+        userRepository.save(new User("existing@example.com",
+                passwordEncoder.encode("test"),
+                "Existing User",
+                true,
+                "admin",
+                LocalDate.of(1990, 1, 1),
+                "EMAIL",
+                "social_9999"));
+
+        long countBefore = userRepository.count();
+        new DevDataSeeder(userRepository, passwordEncoder).run();
+        long countAfter = userRepository.count();
+
+        assertEquals(countBefore, countAfter, "Seeder should not insert new users if users already exist");
+    }
+}
-- 
GitLab


From 22dd758f4bb3bd491ded36ed6b5653ae1e87649a Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 14:51:53 +0700
Subject: [PATCH 25/70] [REFACTOR] Update DevDataSeeder to use Runnable
 interface and simplify user seeding logic

---
 .../authentication/seeder/DevDataSeeder.java  | 73 ++++++++++++-------
 1 file changed, 47 insertions(+), 26 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
index 9edfd57..e33c4f6 100644
--- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
+++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
@@ -2,20 +2,13 @@ package com.safetypin.authentication.seeder;
 
 import com.safetypin.authentication.model.User;
 import com.safetypin.authentication.repository.UserRepository;
-import org.springframework.boot.CommandLineRunner;
-import org.springframework.context.annotation.Profile;
 import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.stereotype.Component;
 
 import java.time.LocalDate;
-import java.util.List;
 
-@Component
-@Profile({"dev"})  // Runs only in 'dev' profile
-public class DevDataSeeder implements CommandLineRunner {
+public class DevDataSeeder implements Runnable {
 
     private final UserRepository userRepository;
-
     private final PasswordEncoder passwordEncoder;
 
     public DevDataSeeder(UserRepository userRepository, PasswordEncoder passwordEncoder) {
@@ -24,25 +17,53 @@ public class DevDataSeeder implements CommandLineRunner {
     }
 
     @Override
-    public void run(String... args) {
+    public void run() {
+        // Only seed if there are no users in the repository
         if (userRepository.count() == 0) {
-            userRepository.saveAll(List.of(
-                    new User("alice@example.com", passwordEncoder.encode("password123"), "Alice Johnson", true, "developer",
-                            LocalDate.of(1998, 5, 21), "EMAIL", "social_1001"),
-                    new User("bob@example.com", passwordEncoder.encode("password456"), "Bob Smith", false, "designer",
-                            LocalDate.of(2000, 8, 15), "GOOGLE", "social_1002"),
-                    new User("charlie@example.com", passwordEncoder.encode("password789"), "Charlie Davis", true, "manager",
-                            LocalDate.of(1995, 12, 3), "APPLE", "social_1003"),
-                    new User("diana@example.com", passwordEncoder.encode("password321"), "Diana Roberts", true, "QA engineer",
-                            LocalDate.of(2002, 6, 10), "EMAIL", "social_1004"),
-                    new User("ethan@example.com", passwordEncoder.encode("password654"), "Ethan Brown", false, "data analyst",
-                            LocalDate.of(1999, 11, 27), "EMAIL", "social_1005")
-            ));
-            System.out.println("Dummy users inserted in DEV environment");
-        } else {
-            System.out.println("User repo is not empty");
-        }
+            userRepository.save(new User("user1@example.com",
+                    passwordEncoder.encode("password1"),
+                    "User One",
+                    true,
+                    "user",
+                    LocalDate.of(1990, 1, 1),
+                    "EMAIL",
+                    "social1"));
+
+            userRepository.save(new User("user2@example.com",
+                    passwordEncoder.encode("password2"),
+                    "User Two",
+                    true,
+                    "user",
+                    LocalDate.of(1991, 2, 2),
+                    "EMAIL",
+                    "social2"));
 
+            userRepository.save(new User("user3@example.com",
+                    passwordEncoder.encode("password3"),
+                    "User Three",
+                    true,
+                    "user",
+                    LocalDate.of(1992, 3, 3),
+                    "EMAIL",
+                    "social3"));
 
+            userRepository.save(new User("user4@example.com",
+                    passwordEncoder.encode("password4"),
+                    "User Four",
+                    true,
+                    "user",
+                    LocalDate.of(1993, 4, 4),
+                    "EMAIL",
+                    "social4"));
+
+            userRepository.save(new User("user5@example.com",
+                    passwordEncoder.encode("password5"),
+                    "User Five",
+                    true,
+                    "user",
+                    LocalDate.of(1994, 5, 5),
+                    "EMAIL",
+                    "social5"));
+        }
     }
-}
\ No newline at end of file
+}
-- 
GitLab


From 7075b72a832a050146f366e5f809d9a63d30cefd Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 14:52:02 +0700
Subject: [PATCH 26/70] [REFACTOR] Update AuthenticationControllerTest to
 remove redundant access modifiers

---
 .../AuthenticationControllerTest.java         | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
index 9d335f4..8364783 100644
--- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
+++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
@@ -28,7 +28,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 
 @WebMvcTest(AuthenticationController.class)
 @Import({AuthenticationControllerTest.TestConfig.class, AuthenticationControllerTest.TestSecurityConfig.class})
-public class AuthenticationControllerTest {
+class AuthenticationControllerTest {
 
     @Autowired
     private MockMvc mockMvc;
@@ -52,13 +52,13 @@ public class AuthenticationControllerTest {
         @Bean
         public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
             http.csrf(csrf -> csrf.disable())
-                    .authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
+                    .authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
             return http.build();
         }
     }
 
     @Test
-    public void testRegisterEmail() throws Exception {
+    void testRegisterEmail() throws Exception {
         RegistrationRequest request = new RegistrationRequest();
         request.setEmail("email@example.com");
         request.setPassword("password");
@@ -79,7 +79,7 @@ public class AuthenticationControllerTest {
     }
 
     @Test
-    public void testRegisterSocial() throws Exception {
+    void testRegisterSocial() throws Exception {
         SocialLoginRequest request = new SocialLoginRequest();
         request.setProvider("GOOGLE");
         request.setSocialToken("token");
@@ -102,7 +102,7 @@ public class AuthenticationControllerTest {
     }
 
     @Test
-    public void testLoginEmail() throws Exception {
+    void testLoginEmail() throws Exception {
         User user = new User("email@example.com", "encodedPassword", "Test User", true, "USER",
                 LocalDate.now().minusYears(20), "EMAIL", null);
         user.setId(1L);
@@ -117,7 +117,7 @@ public class AuthenticationControllerTest {
     }
 
     @Test
-    public void testLoginSocial() throws Exception {
+    void testLoginSocial() throws Exception {
         User user = new User("social@example.com", null, "Social User", true, "USER",
                 LocalDate.now().minusYears(25), "GOOGLE", "social123");
         user.setId(2L);
@@ -131,7 +131,7 @@ public class AuthenticationControllerTest {
     }
 
     @Test
-    public void testVerifyOTP_Success() throws Exception {
+    void testVerifyOTP_Success() throws Exception {
         Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("123456"))).thenReturn(true);
 
         mockMvc.perform(post("/api/auth/verify-otp")
@@ -142,7 +142,7 @@ public class AuthenticationControllerTest {
     }
 
     @Test
-    public void testVerifyOTP_Failure() throws Exception {
+    void testVerifyOTP_Failure() throws Exception {
         Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("000000"))).thenReturn(false);
 
         mockMvc.perform(post("/api/auth/verify-otp")
@@ -153,7 +153,7 @@ public class AuthenticationControllerTest {
     }
 
     @Test
-    public void testForgotPassword() throws Exception {
+    void testForgotPassword() throws Exception {
         PasswordResetRequest request = new PasswordResetRequest();
         request.setEmail("email@example.com");
 
@@ -167,7 +167,7 @@ public class AuthenticationControllerTest {
     }
 
     @Test
-    public void testPostContent() throws Exception {
+    void testPostContent() throws Exception {
         Mockito.when(authenticationService.postContent(eq("email@example.com"), eq("Test Content")))
                 .thenReturn("Content posted successfully");
 
@@ -179,7 +179,7 @@ public class AuthenticationControllerTest {
     }
 
     @Test
-    public void testDashboard() throws Exception {
+    void testDashboard() throws Exception {
         mockMvc.perform(get("/api/auth/dashboard"))
                 .andExpect(status().isOk())
                 .andExpect(content().string("{}"));
-- 
GitLab


From a486627f9a80d3a1f7836c1dfd6b10c8b738e0b3 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 14:52:07 +0700
Subject: [PATCH 27/70] [REFACTOR] Replace Random with SecureRandom in
 OTPService for improved security

---
 .../com/safetypin/authentication/service/OTPService.java     | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/service/OTPService.java b/src/main/java/com/safetypin/authentication/service/OTPService.java
index ac9ee7d..b338aca 100644
--- a/src/main/java/com/safetypin/authentication/service/OTPService.java
+++ b/src/main/java/com/safetypin/authentication/service/OTPService.java
@@ -3,8 +3,9 @@ package com.safetypin.authentication.service;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
+
+import java.security.SecureRandom;
 import java.time.LocalDateTime;
-import java.util.Random;
 import java.util.concurrent.ConcurrentHashMap;
 
 @Service
@@ -13,7 +14,7 @@ public class OTPService {
     private static final long OTP_EXPIRATION_SECONDS = 120; // 2 minutes expiration
     private static final Logger log = LoggerFactory.getLogger(OTPService.class);
     private final ConcurrentHashMap<String, OTPDetails> otpStorage = new ConcurrentHashMap<>();
-    private final Random random = new Random();
+    private final SecureRandom random = new SecureRandom();
 
     public String generateOTP(String email) {
         String otp = String.format("%06d", random.nextInt(1000000));
-- 
GitLab


From 9209188d76c389dbd334514c00f207afb0224e5f Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 15:04:03 +0700
Subject: [PATCH 28/70] [REFACTOR] Fix syntax error in
 AuthenticationControllerTest for improved readability

---
 .../controller/AuthenticationControllerTest.java            | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
index 8364783..5d3df22 100644
--- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
+++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
@@ -51,9 +51,9 @@ class AuthenticationControllerTest {
     static class TestSecurityConfig {
         @Bean
         public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
-            http.csrf(csrf -> csrf.disable())
-                    .authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
-            return http.build();
+            http.csrf(csrf -> csrf.disable());
+            .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
+                return http.build();
         }
     }
 
-- 
GitLab


From 01f6fb0fdc96a614ef509f2b42911be2b14b5110 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 15:15:46 +0700
Subject: [PATCH 29/70] [REFACTOR] Fix indentation issue in
 AuthenticationControllerTest for improved readability

---
 .../controller/AuthenticationControllerTest.java               | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
index 5d3df22..2908d9a 100644
--- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
+++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
@@ -51,8 +51,7 @@ class AuthenticationControllerTest {
     static class TestSecurityConfig {
         @Bean
         public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
-            http.csrf(csrf -> csrf.disable());
-            .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
+                http.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
                 return http.build();
         }
     }
-- 
GitLab


From ed4bc1ed75c76058efd24272b1f8075bb4a7fe55 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 15:15:53 +0700
Subject: [PATCH 30/70] [REFACTOR] Remove redundant access modifiers in
 AuthenticationServiceTest for improved clarity

---
 .../service/AuthenticationServiceTest.java    | 45 +++++++++----------
 1 file changed, 22 insertions(+), 23 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
index 7064385..bdf05fe 100644
--- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
+++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
@@ -17,11 +17,10 @@ import java.time.LocalDate;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
 
 @ExtendWith(MockitoExtension.class)
-public class AuthenticationServiceTest {
+class AuthenticationServiceTest {
 
     @Mock
     private UserRepository userRepository;
@@ -38,7 +37,7 @@ public class AuthenticationServiceTest {
     // registerUser tests
 
     @Test
-    public void testRegisterUser_UnderAge() {
+    void testRegisterUser_UnderAge() {
         RegistrationRequest request = new RegistrationRequest();
         request.setEmail("test@example.com");
         request.setPassword("password");
@@ -53,7 +52,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testRegisterUser_DuplicateEmail() {
+    void testRegisterUser_DuplicateEmail() {
         RegistrationRequest request = new RegistrationRequest();
         request.setEmail("test@example.com");
         request.setPassword("password");
@@ -69,7 +68,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testRegisterUser_Success() {
+    void testRegisterUser_Success() {
         RegistrationRequest request = new RegistrationRequest();
         request.setEmail("test@example.com");
         request.setPassword("password");
@@ -93,7 +92,7 @@ public class AuthenticationServiceTest {
     // socialLogin tests
 
     @Test
-    public void testSocialLogin_UnderAge() {
+    void testSocialLogin_UnderAge() {
         SocialLoginRequest request = new SocialLoginRequest();
         request.setEmail("social@example.com");
         request.setName("Social User");
@@ -109,7 +108,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testSocialLogin_DuplicateEmailWithEmailProvider() {
+    void testSocialLogin_DuplicateEmailWithEmailProvider() {
         SocialLoginRequest request = new SocialLoginRequest();
         request.setEmail("social@example.com");
         request.setName("Social User");
@@ -129,7 +128,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testSocialLogin_ExistingSocialUser() {
+    void testSocialLogin_ExistingSocialUser() {
         SocialLoginRequest request = new SocialLoginRequest();
         request.setEmail("social@example.com");
         request.setName("Social User");
@@ -148,7 +147,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testSocialLogin_NewUser() {
+    void testSocialLogin_NewUser() {
         SocialLoginRequest request = new SocialLoginRequest();
         request.setEmail("social@example.com");
         request.setName("Social User");
@@ -171,7 +170,7 @@ public class AuthenticationServiceTest {
     // loginUser tests
 
     @Test
-    public void testLoginUser_EmailNotFound() {
+    void testLoginUser_EmailNotFound() {
         when(userRepository.findByEmail("notfound@example.com")).thenReturn(null);
         Exception exception = assertThrows(InvalidCredentialsException.class, () ->
                 authenticationService.loginUser("notfound@example.com", "password")
@@ -180,7 +179,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testLoginUser_InvalidPassword_NullPassword() {
+    void testLoginUser_InvalidPassword_NullPassword() {
         User user = new User("test@example.com", null, "Test User", true, "USER",
                 LocalDate.now().minusYears(20), "EMAIL", null);
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
@@ -192,7 +191,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testLoginUser_InvalidPassword_WrongMatch() {
+    void testLoginUser_InvalidPassword_WrongMatch() {
         User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
                 LocalDate.now().minusYears(20), "EMAIL", null);
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
@@ -205,7 +204,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testLoginUser_Success() {
+    void testLoginUser_Success() {
         User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
                 LocalDate.now().minusYears(20), "EMAIL", null);
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
@@ -219,7 +218,7 @@ public class AuthenticationServiceTest {
     // loginSocial tests
 
     @Test
-    public void testLoginSocial_UserNotFound() {
+    void testLoginSocial_UserNotFound() {
         when(userRepository.findByEmail("notfound@example.com")).thenReturn(null);
         Exception exception = assertThrows(InvalidCredentialsException.class, () ->
                 authenticationService.loginSocial("notfound@example.com")
@@ -228,7 +227,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testLoginSocial_Success() {
+    void testLoginSocial_Success() {
         User user = new User("social@example.com", null, "Social User", true, "USER",
                 LocalDate.now().minusYears(25), "GOOGLE", "social123");
         when(userRepository.findByEmail("social@example.com")).thenReturn(user);
@@ -241,7 +240,7 @@ public class AuthenticationServiceTest {
     // verifyOTP tests
 
     @Test
-    public void testVerifyOTP_Success() {
+    void testVerifyOTP_Success() {
         // OTPService returns true and user is found
         when(otpService.verifyOTP("test@example.com", "123456")).thenReturn(true);
         User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER",
@@ -256,7 +255,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testVerifyOTP_Success_UserNotFound() {
+    void testVerifyOTP_Success_UserNotFound() {
         // OTPService returns true but user is not found
         when(otpService.verifyOTP("nonexistent@example.com", "123456")).thenReturn(true);
         when(userRepository.findByEmail("nonexistent@example.com")).thenReturn(null);
@@ -267,7 +266,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testVerifyOTP_Failure() {
+    void testVerifyOTP_Failure() {
         when(otpService.verifyOTP("test@example.com", "000000")).thenReturn(false);
         boolean result = authenticationService.verifyOTP("test@example.com", "000000");
         assertFalse(result);
@@ -277,7 +276,7 @@ public class AuthenticationServiceTest {
     // forgotPassword tests
 
     @Test
-    public void testForgotPassword_Success() {
+    void testForgotPassword_Success() {
         User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
                 LocalDate.now().minusYears(20), "EMAIL", null);
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
@@ -286,7 +285,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testForgotPassword_Invalid() {
+    void testForgotPassword_Invalid() {
         // Case 1: user not found
         when(userRepository.findByEmail("notfound@example.com")).thenReturn(null);
         Exception exception1 = assertThrows(IllegalArgumentException.class, () ->
@@ -307,14 +306,14 @@ public class AuthenticationServiceTest {
     // postContent tests
 
     @Test
-    public void testPostContent_UserNotFound() {
+    void testPostContent_UserNotFound() {
         when(userRepository.findByEmail("notfound@example.com")).thenReturn(null);
         String response = authenticationService.postContent("notfound@example.com", "Content");
         assertEquals("User not found. Please register.", response);
     }
 
     @Test
-    public void testPostContent_UserNotVerified() {
+    void testPostContent_UserNotVerified() {
         User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER",
                 LocalDate.now().minusYears(20), "EMAIL", null);
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
@@ -323,7 +322,7 @@ public class AuthenticationServiceTest {
     }
 
     @Test
-    public void testPostContent_UserVerified() {
+    void testPostContent_UserVerified() {
         User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
                 LocalDate.now().minusYears(20), "EMAIL", null);
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
-- 
GitLab


From bb1325fdfa31ef9c92335967effb19db189a495a Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 15:15:57 +0700
Subject: [PATCH 31/70] [REFACTOR] Remove unused import in
 AuthenticationController for cleaner code

---
 .../authentication/controller/AuthenticationController.java      | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
index 9b09ad8..a42494e 100644
--- a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
+++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
@@ -9,7 +9,6 @@ import com.safetypin.authentication.service.AuthenticationService;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import jakarta.validation.Valid;
-import com.safetypin.authentication.dto.ErrorResponse;
 import org.springframework.web.bind.annotation.*;
 
 @RestController
-- 
GitLab


From ad11accf04108acb3421a6c4bb7c3bb8e2d262b0 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 15:16:01 +0700
Subject: [PATCH 32/70] [REFACTOR] Change access modifiers in UserTest for
 improved encapsulation

---
 .../com/safetypin/authentication/model/UserTest.java   | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/model/UserTest.java b/src/test/java/com/safetypin/authentication/model/UserTest.java
index 2001bd2..f66bde4 100644
--- a/src/test/java/com/safetypin/authentication/model/UserTest.java
+++ b/src/test/java/com/safetypin/authentication/model/UserTest.java
@@ -1,13 +1,15 @@
 package com.safetypin.authentication.model;
 
 import org.junit.jupiter.api.Test;
+
 import java.time.LocalDate;
+
 import static org.junit.jupiter.api.Assertions.*;
 
-public class UserTest {
+class UserTest {
 
     @Test
-    public void testDefaultConstructorDefaults() {
+    void testDefaultConstructorDefaults() {
         User user = new User();
         // Verify that default constructor sets all fields to their default values
         assertNull(user.getId(), "Default id should be null");
@@ -22,7 +24,7 @@ public class UserTest {
     }
 
     @Test
-    public void testSettersAndGetters() {
+    void testSettersAndGetters() {
         User user = new User();
         Long id = 123L;
         String email = "test@example.com";
@@ -56,7 +58,7 @@ public class UserTest {
     }
 
     @Test
-    public void testParameterizedConstructor() {
+    void testParameterizedConstructor() {
         String email = "test2@example.com";
         String password = "password123";
         String name = "Another User";
-- 
GitLab


From 997a2f648acfe5acf67a94163cdb44f4712b16ed Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 15:21:27 +0700
Subject: [PATCH 33/70] [REFACTOR] Update SecurityConfig to clarify CSRF
 protection handling

---
 .../com/safetypin/authentication/security/SecurityConfig.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/com/safetypin/authentication/security/SecurityConfig.java b/src/main/java/com/safetypin/authentication/security/SecurityConfig.java
index c79b353..dae1a16 100644
--- a/src/main/java/com/safetypin/authentication/security/SecurityConfig.java
+++ b/src/main/java/com/safetypin/authentication/security/SecurityConfig.java
@@ -16,7 +16,7 @@ public class SecurityConfig {
     @Bean
     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
         http
-                .csrf(AbstractHttpConfigurer::disable)  // Disable CSRF protection (not recommended for production)
+                // CSRF protection is enabled by default, so we don't disable it here
                 .authorizeHttpRequests(auth -> auth
                         .requestMatchers("/**").permitAll() // Allow all requests
                 )
-- 
GitLab


From 204e9066e8a030e41b12a3180a333f6b4e3dca89 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 15:55:41 +0700
Subject: [PATCH 34/70] [REFACTOR] Update dependencies in pom.xml for improved
 testing support

---
 pom.xml | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index bfd64b5..a35f43c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -149,15 +149,17 @@
 		</dependency>
 
 		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-test</artifactId>
+			<groupId>net.java.dev.jna</groupId>
+			<artifactId>jna-platform</artifactId>
+			<version>5.13.0</version>
 			<scope>test</scope>
 		</dependency>
 
+
 		<dependency>
-			<groupId>net.java.dev.jna</groupId>
-			<artifactId>jna-platform</artifactId>
-			<version>5.13.0</version>
+			<groupId>org.jacoco</groupId>
+			<artifactId>org.jacoco.agent</artifactId>
+			<version>0.8.11</version>
 			<scope>test</scope>
 		</dependency>
 
-- 
GitLab


From 260d989e87e8bff9895b2cc142a410a25e802615 Mon Sep 17 00:00:00 2001
From: Fredo <fredotanzil@gmail.com>
Date: Wed, 26 Feb 2025 17:04:24 +0700
Subject: [PATCH 35/70] [REFACTOR] remove todo

---
 .../authentication/controller/AuthenticationController.java      | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
index a42494e..08f921c 100644
--- a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
+++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
@@ -49,7 +49,6 @@ public class AuthenticationController {
         try {
             return ResponseEntity.ok(authenticationService.loginUser(email, password));
         } catch (InvalidCredentialsException e){
-            //TODO
             return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                     .body(e.getMessage());
         }
-- 
GitLab


From bc8b933664de482e087d2df967d30ee7e7d66631 Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Wed, 26 Feb 2025 17:41:00 +0700
Subject: [PATCH 36/70] Create AuthResponse DTO to standardize between success
 and failed response

---
 .../safetypin/authentication/dto/AuthResponse.java  | 13 +++++++++++++
 1 file changed, 13 insertions(+)
 create mode 100644 src/main/java/com/safetypin/authentication/dto/AuthResponse.java

diff --git a/src/main/java/com/safetypin/authentication/dto/AuthResponse.java b/src/main/java/com/safetypin/authentication/dto/AuthResponse.java
new file mode 100644
index 0000000..a29962f
--- /dev/null
+++ b/src/main/java/com/safetypin/authentication/dto/AuthResponse.java
@@ -0,0 +1,13 @@
+package com.safetypin.authentication.dto;
+
+import lombok.*;
+
+@Data
+@Getter
+@Setter
+@AllArgsConstructor
+public class AuthResponse {
+    private boolean success;
+    private String message;
+    private Object data;
+}
\ No newline at end of file
-- 
GitLab


From 616c0b6b1ae898af37b6477c456b21049144d8b2 Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Wed, 26 Feb 2025 17:46:32 +0700
Subject: [PATCH 37/70] [REFACTOR] Replace multiple duplicated strings with a
 constant variable Prevent change propagation by only changing in a single
 place to increase maintainability

---
 .../authentication/service/AuthenticationService.java      | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index 5b92625..0af2bed 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -15,6 +15,7 @@ import java.time.Period;
 
 @Service
 public class AuthenticationService {
+    private static final String EMAIL_PROVIDER = "EMAIL";
 
     private final UserRepository userRepository;
     private final PasswordEncoder passwordEncoder;
@@ -37,7 +38,7 @@ public class AuthenticationService {
         }
         String encodedPassword = passwordEncoder.encode(request.getPassword());
         User user = new User(request.getEmail(), encodedPassword, request.getName(), false, "USER",
-                request.getBirthdate(), "EMAIL", null);
+                request.getBirthdate(), EMAIL_PROVIDER, null);
         user = userRepository.save(user);
         otpService.generateOTP(request.getEmail());
         logger.info("OTP generated for {} at {}", request.getEmail(), java.time.LocalDateTime.now());
@@ -51,7 +52,7 @@ public class AuthenticationService {
         }
         User existing = userRepository.findByEmail(request.getEmail());
         if (existing != null) {
-            if ("EMAIL".equals(existing.getProvider())) {
+            if (EMAIL_PROVIDER.equals(existing.getProvider())) {
                 throw new IllegalArgumentException("An account with this email exists. Please sign in using your email and password.");
             }
             return existing;
@@ -109,7 +110,7 @@ public class AuthenticationService {
     // Forgot password – only applicable for email-registered users
     public void forgotPassword(String email) {
         User user = userRepository.findByEmail(email);
-        if (user == null || !"EMAIL".equals(user.getProvider())) {
+        if (user == null || !EMAIL_PROVIDER.equals(user.getProvider())) {
             throw new IllegalArgumentException("Password reset is only available for email-registered users.");
         }
         // In production, send a reset token via email.
-- 
GitLab


From 99c1b2156368f7bc936f9d6f7e648bfbc14d7251 Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Wed, 26 Feb 2025 17:51:01 +0700
Subject: [PATCH 38/70] [REFACTOR] Use argument passed to example method
 sonarqube is more happy :))

---
 .../safetypin/authentication/service/AuthenticationService.java  | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index 0af2bed..dce7ad4 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -126,6 +126,7 @@ public class AuthenticationService {
         if (!user.isVerified()) {
             return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes.";
         }
+        logger.info("AuthenticationService.postContent :: Content posted: {}", content);
         // For demo purposes, we assume the post is successful.
         return "Content posted successfully";
     }
-- 
GitLab


From 5889deb8f622ad2d74d9411a663181dd6887f186 Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Wed, 26 Feb 2025 17:55:47 +0700
Subject: [PATCH 39/70] [REFACTOR] change registerEmail+registerSocial to
 handle thrown exceptions and return ResponseEntity<AuthResponse> instead

---
 .../controller/AuthenticationController.java  | 26 +++++++++++++++----
 1 file changed, 21 insertions(+), 5 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
index 08f921c..d7e04c0 100644
--- a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
+++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
@@ -1,9 +1,11 @@
 package com.safetypin.authentication.controller;
 
+import com.safetypin.authentication.dto.AuthResponse;
 import com.safetypin.authentication.dto.PasswordResetRequest;
 import com.safetypin.authentication.dto.RegistrationRequest;
 import com.safetypin.authentication.dto.SocialLoginRequest;
 import com.safetypin.authentication.exception.InvalidCredentialsException;
+import com.safetypin.authentication.exception.UserAlreadyExistsException;
 import com.safetypin.authentication.model.User;
 import com.safetypin.authentication.service.AuthenticationService;
 import org.springframework.http.HttpStatus;
@@ -24,14 +26,28 @@ public class AuthenticationController {
 
     // Endpoint for email registration
     @PostMapping("/register-email")
-    public User registerEmail(@Valid @RequestBody RegistrationRequest request) {
-        return authenticationService.registerUser(request);
+    public ResponseEntity<AuthResponse> registerEmail(@Valid @RequestBody RegistrationRequest request) {
+        User user;
+        try {
+            user = authenticationService.registerUser(request);
+        } catch (IllegalArgumentException | UserAlreadyExistsException e) {
+            AuthResponse response = new AuthResponse(false, e.getMessage(), null);
+            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
+        }
+        return ResponseEntity.ok().body(new AuthResponse(true, "OK", user));
     }
 
     // Endpoint for social registration/login
     @PostMapping("/register-social")
-    public User registerSocial(@Valid @RequestBody SocialLoginRequest request) {
-        return authenticationService.socialLogin(request);
+    public ResponseEntity<AuthResponse> registerSocial(@Valid @RequestBody SocialLoginRequest request) {
+        User user;
+        try {
+            user = authenticationService.socialLogin(request);
+        } catch (IllegalArgumentException | UserAlreadyExistsException e) {
+            AuthResponse response = new AuthResponse(false, e.getMessage(), null);
+            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
+        }
+        return ResponseEntity.ok().body(new AuthResponse(true, "OK", user));
     }
 
     // OTP verification endpoint
@@ -45,7 +61,7 @@ public class AuthenticationController {
 
     // Endpoint for email login
     @PostMapping("/login-email")
-    public ResponseEntity<?> loginEmail(@RequestParam String email, @RequestParam String password) {
+    public ResponseEntity<Object> loginEmail(@RequestParam String email, @RequestParam String password) {
         try {
             return ResponseEntity.ok(authenticationService.loginUser(email, password));
         } catch (InvalidCredentialsException e){
-- 
GitLab


From 46aadd7728e46d95bbadade590499e585b2613ff Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Wed, 26 Feb 2025 17:56:31 +0700
Subject: [PATCH 40/70] [REFACTOR] change exception type to better reflect what
 errored

---
 .../safetypin/authentication/service/AuthenticationService.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index dce7ad4..96bca07 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -53,7 +53,7 @@ public class AuthenticationService {
         User existing = userRepository.findByEmail(request.getEmail());
         if (existing != null) {
             if (EMAIL_PROVIDER.equals(existing.getProvider())) {
-                throw new IllegalArgumentException("An account with this email exists. Please sign in using your email and password.");
+                throw new UserAlreadyExistsException("An account with this email exists. Please sign in using your email and password.");
             }
             return existing;
         }
-- 
GitLab


From 31ea98b5ab051f7ab39cbfa5bce9bd8130e7c003 Mon Sep 17 00:00:00 2001
From: Fredo <fredotanzil@gmail.com>
Date: Wed, 26 Feb 2025 18:40:21 +0700
Subject: [PATCH 41/70] [GREEN] Fix DevDataSeeder not working

---
 .../safetypin/authentication/seeder/DevDataSeeder.java | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
index e33c4f6..58dc982 100644
--- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
+++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
@@ -2,10 +2,15 @@ package com.safetypin.authentication.seeder;
 
 import com.safetypin.authentication.model.User;
 import com.safetypin.authentication.repository.UserRepository;
+import jakarta.annotation.PostConstruct;
+import org.springframework.context.annotation.Profile;
 import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Component;
 
 import java.time.LocalDate;
 
+@Component
+@Profile({"dev"})
 public class DevDataSeeder implements Runnable {
 
     private final UserRepository userRepository;
@@ -16,6 +21,11 @@ public class DevDataSeeder implements Runnable {
         this.passwordEncoder = passwordEncoder;
     }
 
+    @PostConstruct
+    public void init() {
+        run();
+    }
+
     @Override
     public void run() {
         // Only seed if there are no users in the repository
-- 
GitLab


From 5d212604274f1f3e857d52e9ee2cf4f5fe25ce21 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 20:16:00 +0700
Subject: [PATCH 42/70] [REFACTOR] AuthenticationControllerTest to disable CSRF
 and update JSON path assertions

---
 .../controller/AuthenticationControllerTest.java  | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
index 2908d9a..2051cf1 100644
--- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
+++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
@@ -15,6 +15,7 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
 import org.springframework.http.MediaType;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.test.web.servlet.MockMvc;
 
@@ -51,11 +52,13 @@ class AuthenticationControllerTest {
     static class TestSecurityConfig {
         @Bean
         public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
-                http.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
-                return http.build();
+            http.csrf(AbstractHttpConfigurer::disable)
+                    .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
+            return http.build();
         }
     }
 
+
     @Test
     void testRegisterEmail() throws Exception {
         RegistrationRequest request = new RegistrationRequest();
@@ -73,8 +76,8 @@ class AuthenticationControllerTest {
                         .contentType(MediaType.APPLICATION_JSON)
                         .content(objectMapper.writeValueAsString(request)))
                 .andExpect(status().isOk())
-                .andExpect(jsonPath("$.id").value(1L))
-                .andExpect(jsonPath("$.email").value("email@example.com"));
+                .andExpect(jsonPath("$.data.id").value(1L))
+                .andExpect(jsonPath("$.data.email").value("email@example.com"));
     }
 
     @Test
@@ -96,8 +99,8 @@ class AuthenticationControllerTest {
                         .contentType(MediaType.APPLICATION_JSON)
                         .content(objectMapper.writeValueAsString(request)))
                 .andExpect(status().isOk())
-                .andExpect(jsonPath("$.id").value(2L))
-                .andExpect(jsonPath("$.email").value("social@example.com"));
+                .andExpect(jsonPath("$.data.id").value(2L))
+                .andExpect(jsonPath("$.data.email").value("social@example.com"));
     }
 
     @Test
-- 
GitLab


From 19277c810dacf0a96db1b9b5555574e78225e322 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Wed, 26 Feb 2025 20:16:08 +0700
Subject: [PATCH 43/70] [REFACTOR] Update exception type in
 AuthenticationServiceTest for social login

---
 .../authentication/service/AuthenticationServiceTest.java       | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
index bdf05fe..b1f12b9 100644
--- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
+++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
@@ -121,7 +121,7 @@ class AuthenticationServiceTest {
                 LocalDate.now().minusYears(30), "EMAIL", null);
         when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser);
 
-        Exception exception = assertThrows(IllegalArgumentException.class, () ->
+        Exception exception = assertThrows(UserAlreadyExistsException.class, () ->
                 authenticationService.socialLogin(request)
         );
         assertTrue(exception.getMessage().contains("An account with this email exists"));
-- 
GitLab


From a0ec570d468c6c022dfeca39fbbf623a56d89f5b Mon Sep 17 00:00:00 2001
From: Muhammad Raihan Akbar <ianakbar711@gmail.com>
Date: Wed, 26 Feb 2025 21:34:40 +0700
Subject: [PATCH 44/70] [REFACTOR] changed app dev db properties to localhost

---
 .env                                          | 3 ---
 src/main/resources/application-dev.properties | 6 +++---
 2 files changed, 3 insertions(+), 6 deletions(-)
 delete mode 100644 .env

diff --git a/.env b/.env
deleted file mode 100644
index 6d5f502..0000000
--- a/.env
+++ /dev/null
@@ -1,3 +0,0 @@
-DB_URL=jdbc:postgresql://localhost:5432/be-authentication
-DB_USERNAME=postgres
-DB_PASSWORD=postgres
diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties
index a0f2b58..b7c904d 100644
--- a/src/main/resources/application-dev.properties
+++ b/src/main/resources/application-dev.properties
@@ -1,8 +1,8 @@
 spring.application.name=authentication
 
-spring.datasource.url=${DB_URL}
-spring.datasource.username=${DB_PASSWORD}
-spring.datasource.password=${DB_PASSWORD}
+spring.datasource.url=jdbc:postgresql://localhost:5432/be-authentication
+spring.datasource.username=postgres
+spring.datasource.password=postgres
 
 spring.datasource.driver-class-name=org.postgresql.Driver
 spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
-- 
GitLab


From 35e8c162a9ade451791863fe98cdfe365c0da8db Mon Sep 17 00:00:00 2001
From: Muhammad Raihan Akbar <ianakbar711@gmail.com>
Date: Wed, 26 Feb 2025 22:23:15 +0700
Subject: [PATCH 45/70] [REFACTOR] edited app properties for active profile
 setup

---
 src/main/resources/application.properties | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index c12917a..7087d05 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,2 @@
-spring.application.name=authentication
\ No newline at end of file
+spring.application.name=authentication
+spring.profiles.active=${PRODUCTION:dev}
\ No newline at end of file
-- 
GitLab


From 09e7c72125b87dddba95dc4ada16ef4442ff8dbb Mon Sep 17 00:00:00 2001
From: Muhammad Raihan Akbar <ianakbar711@gmail.com>
Date: Wed, 26 Feb 2025 22:29:29 +0700
Subject: [PATCH 46/70] [REFACTOR] configured env and staging properties

---
 Dockerfile                                     | 18 ++++++++++++++++++
 .../resources/application-staging.properties   | 13 ++++++++++++-
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/Dockerfile b/Dockerfile
index a5403ef..9372156 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,6 +6,24 @@ RUN mvn clean package -DskipTests
 
 # Step 2: Use OpenJDK 21 to run the application
 FROM openjdk:21-jdk-slim
+
+# Setup envs
+ARG PRODUCTION
+ARG JDBC_DATABASE_PASSWORD
+ARG JDBC_DATABASE_URL
+ARG JDBC_DATABASE_USERNAME
+ARG JDBC_STAGING_DATABASE_USERNAME
+ARG JDBC_STAGING_DATABASE_URL
+ARG JDBC_STAGING_DATABASE_URL
+
+ENV PRODUCTION ${PRODUCTION}
+ENV JDBC_DATABASE_PASSWORD ${JDBC_DATABASE_PASSWORD}
+ENV JDBC_DATABASE_URL ${JDBC_DATABASE_URL}
+ENV JDBC_DATABASE_USERNAME ${JDBC_DATABASE_USERNAME}
+ENV JDBC_STAGING_DATABASE_PASSWORD ${JDBC_STAGING_DATABASE_PASSWORD}
+ENV JDBC_STAGING_DATABASE_URL ${JDBC_STAGING_DATABASE_URL}
+ENV JDBC_STAGING_DATABASE_USERNAME ${JDBC_STAGING_DATABASE_USERNAME}
+
 WORKDIR /app
 COPY --from=builder /app/target/authentication-0.0.1-SNAPSHOT.jar app.jar
 EXPOSE 8080
diff --git a/src/main/resources/application-staging.properties b/src/main/resources/application-staging.properties
index c12917a..a0f2b58 100644
--- a/src/main/resources/application-staging.properties
+++ b/src/main/resources/application-staging.properties
@@ -1 +1,12 @@
-spring.application.name=authentication
\ No newline at end of file
+spring.application.name=authentication
+
+spring.datasource.url=${DB_URL}
+spring.datasource.username=${DB_PASSWORD}
+spring.datasource.password=${DB_PASSWORD}
+
+spring.datasource.driver-class-name=org.postgresql.Driver
+spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
+
+# Hibernate Properties
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.show-sql=true
\ No newline at end of file
-- 
GitLab


From 0d88a0df36d481d6b683185aa2a604ec42dca10c Mon Sep 17 00:00:00 2001
From: Muhammad Raihan Akbar <ianakbar711@gmail.com>
Date: Wed, 26 Feb 2025 22:34:24 +0700
Subject: [PATCH 47/70] [REFACTOR] reconfigured the db variables for staging
 properties

---
 src/main/resources/application-staging.properties | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/main/resources/application-staging.properties b/src/main/resources/application-staging.properties
index a0f2b58..8491638 100644
--- a/src/main/resources/application-staging.properties
+++ b/src/main/resources/application-staging.properties
@@ -1,8 +1,8 @@
 spring.application.name=authentication
 
-spring.datasource.url=${DB_URL}
-spring.datasource.username=${DB_PASSWORD}
-spring.datasource.password=${DB_PASSWORD}
+spring.datasource.url=${JDBC_STAGING_DATABASE_URL}
+spring.datasource.username=${JDBC_STAGING_DATABASE_USERNAME}
+spring.datasource.password=${JDBC_STAGING_DATABASE_PASSWORD}
 
 spring.datasource.driver-class-name=org.postgresql.Driver
 spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
-- 
GitLab


From 94d37fa4e84c28b8fc9bfe4de4cafd18c975aa33 Mon Sep 17 00:00:00 2001
From: Muhammad Raihan Akbar <ianakbar711@gmail.com>
Date: Wed, 26 Feb 2025 22:42:18 +0700
Subject: [PATCH 48/70] [REFACTOR] added jacoco plugin for test coverages

---
 pom.xml | 29 +++++++++++++++++++----------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/pom.xml b/pom.xml
index a35f43c..feeca04 100644
--- a/pom.xml
+++ b/pom.xml
@@ -155,16 +155,6 @@
 			<scope>test</scope>
 		</dependency>
 
-
-		<dependency>
-			<groupId>org.jacoco</groupId>
-			<artifactId>org.jacoco.agent</artifactId>
-			<version>0.8.11</version>
-			<scope>test</scope>
-		</dependency>
-
-
-
 	</dependencies>
 
 	<build>
@@ -212,6 +202,25 @@
 				<artifactId>maven-jar-plugin</artifactId>
 				<version>3.2.0</version>
 			</plugin>
+			<plugin>
+				<groupId>org.jacoco</groupId>
+				<artifactId>jacoco-maven-plugin</artifactId>
+				<version>0.8.12</version>
+				<executions>
+					<execution>
+						<goals>
+							<goal>prepare-agent</goal>
+						</goals>
+					</execution>
+					<execution>
+						<id>report</id>
+						<phase>test</phase>
+						<goals>
+							<goal>report</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
 		</plugins>
 	</build>
 
-- 
GitLab


From 01197d8108c7689c46bde78fcbad15272cf8e2b8 Mon Sep 17 00:00:00 2001
From: Muhammad Raihan Akbar <ianakbar711@gmail.com>
Date: Wed, 26 Feb 2025 22:44:30 +0700
Subject: [PATCH 49/70] [REFACTOR] added continuous deployment for staging
 environment

---
 .github/workflows/staging-ci-cd.yml | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/staging-ci-cd.yml b/.github/workflows/staging-ci-cd.yml
index ccc2d24..e69f52a 100644
--- a/.github/workflows/staging-ci-cd.yml
+++ b/.github/workflows/staging-ci-cd.yml
@@ -40,16 +40,34 @@ jobs:
         uses: actions/checkout@v3
 
       - name: Install the gcloud CLI
-        uses: google-github-actions/setup-gcloud@v0
+        uses: google-github-actions/setup-gcloud@v2
         with:
           project_id: ${{ secrets.GOOGLE_PROJECT }}
           service_account_key: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
           export_default_credentials: true
 
+      - name: Authenticate with GCP
+        uses: google-github-actions/auth@v1
+        with:
+          credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
+
       - name: Build and Push Docker Image
         env:
           GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }}
         run: |
           gcloud auth configure-docker us-central1-docker.pkg.dev
-          docker build -t us-central1-docker.pkg.dev/$GOOGLE_PROJECT/my-repository/authentication:latest .
-          docker push us-central1-docker.pkg.dev/$GOOGLE_PROJECT/my-repository/authentication:latest
\ No newline at end of file
+          docker build -t us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest .
+          docker push us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest
+
+      - name: Install required components
+        run: |
+          gcloud components update
+          gcloud components install gke-gcloud-auth-plugin
+
+      - name: Deploy to GKE
+        env:
+          GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }}
+        run: |
+          gcloud container clusters get-credentials safetypin-staging --region asia-southeast2
+          sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" resources.yaml
+          kubectl apply -f resources.yaml
\ No newline at end of file
-- 
GitLab


From 761fae8719e89af6d1b0a137e9a6144f14448fe1 Mon Sep 17 00:00:00 2001
From: Muhammad Raihan Akbar <ianakbar711@gmail.com>
Date: Wed, 26 Feb 2025 23:11:07 +0700
Subject: [PATCH 50/70] [REFACTOR] added build args for docker in staging
 environment

---
 .github/workflows/staging-ci-cd.yml | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/staging-ci-cd.yml b/.github/workflows/staging-ci-cd.yml
index e69f52a..f64e83a 100644
--- a/.github/workflows/staging-ci-cd.yml
+++ b/.github/workflows/staging-ci-cd.yml
@@ -53,10 +53,14 @@ jobs:
 
       - name: Build and Push Docker Image
         env:
+          PRODUCTION: staging
           GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }}
+          JDBC_STAGING_DATABASE_PASSWORD: ${{ secrets.JDBC_STAGING_DATABASE_PASSWORD }}
+          JDBC_STAGING_DATABASE_URL: ${{ secrets.JDBC_STAGING_DATABASE_URL }}
+          JDBC_STAGING_DATABASE_USERNAME: ${{ secrets.JDBC_STAGING_DATABASE_USERNAME }}
         run: |
           gcloud auth configure-docker us-central1-docker.pkg.dev
-          docker build -t us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest .
+          docker build --build-arg PRODUCTION=$PRODUCTION --build-arg JDBC_STAGING_DATABASE_PASSWORD=$JDBC_STAGING_DATABASE_PASSWORD --build-arg JDBC_STAGING_DATABASE_URL=$JDBC_STAGING_DATABASE_URL --build-arg JDBC_STAGING_DATABASE_USERNAME=$JDBC_STAGING_DATABASE_USERNAME -t us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest .
           docker push us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest
 
       - name: Install required components
-- 
GitLab


From 9904cf2ea61ca9f9d0ab9bede289aec838576e62 Mon Sep 17 00:00:00 2001
From: Muhammad Raihan Akbar <ianakbar711@gmail.com>
Date: Wed, 26 Feb 2025 23:19:20 +0700
Subject: [PATCH 51/70] [REFACTOR] changed the deployment repository for
 staging

---
 .github/workflows/staging-ci-cd.yml | 1 +
 resources.yaml                      | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/staging-ci-cd.yml b/.github/workflows/staging-ci-cd.yml
index f64e83a..0c52350 100644
--- a/.github/workflows/staging-ci-cd.yml
+++ b/.github/workflows/staging-ci-cd.yml
@@ -71,6 +71,7 @@ jobs:
       - name: Deploy to GKE
         env:
           GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }}
+          GOOGLE_REPOSiTORY: staging-repository
         run: |
           gcloud container clusters get-credentials safetypin-staging --region asia-southeast2
           sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" resources.yaml
diff --git a/resources.yaml b/resources.yaml
index a3d64c5..f9611f7 100644
--- a/resources.yaml
+++ b/resources.yaml
@@ -29,6 +29,6 @@ spec:
     spec:
       containers:
         - name: authentication
-          image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/my-repository/authentication:latest
+          image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/GOOGLE_REPOSITORY/authentication:latest
           ports:
             - containerPort: 8080
\ No newline at end of file
-- 
GitLab


From b0eb787f33249e2f4f7bc897c9e499be52b5e698 Mon Sep 17 00:00:00 2001
From: Muhammad Raihan Akbar <ianakbar711@gmail.com>
Date: Wed, 26 Feb 2025 23:34:20 +0700
Subject: [PATCH 52/70] [REFACTOR] reconfigured yaml file to differentiate prod
 and staging

---
 .github/workflows/staging-ci-cd.yml |  4 ++--
 resources.yaml => production.yaml   |  2 +-
 staging.yaml                        | 34 +++++++++++++++++++++++++++++
 3 files changed, 37 insertions(+), 3 deletions(-)
 rename resources.yaml => production.yaml (84%)
 create mode 100644 staging.yaml

diff --git a/.github/workflows/staging-ci-cd.yml b/.github/workflows/staging-ci-cd.yml
index 0c52350..b9f2245 100644
--- a/.github/workflows/staging-ci-cd.yml
+++ b/.github/workflows/staging-ci-cd.yml
@@ -74,5 +74,5 @@ jobs:
           GOOGLE_REPOSiTORY: staging-repository
         run: |
           gcloud container clusters get-credentials safetypin-staging --region asia-southeast2
-          sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" resources.yaml
-          kubectl apply -f resources.yaml
\ No newline at end of file
+          sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" staging.yaml
+          kubectl apply -f staging.yaml
\ No newline at end of file
diff --git a/resources.yaml b/production.yaml
similarity index 84%
rename from resources.yaml
rename to production.yaml
index f9611f7..a3d64c5 100644
--- a/resources.yaml
+++ b/production.yaml
@@ -29,6 +29,6 @@ spec:
     spec:
       containers:
         - name: authentication
-          image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/GOOGLE_REPOSITORY/authentication:latest
+          image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/my-repository/authentication:latest
           ports:
             - containerPort: 8080
\ No newline at end of file
diff --git a/staging.yaml b/staging.yaml
new file mode 100644
index 0000000..91457e7
--- /dev/null
+++ b/staging.yaml
@@ -0,0 +1,34 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: authentication
+spec:
+  type: LoadBalancer
+  selector:
+    app: authentication
+  ports:
+    - port: 80
+      targetPort: 8080
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: authentication
+  labels:
+    app: authentication
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: authentication
+  template:
+    metadata:
+      labels:
+        app: authentication
+    spec:
+      containers:
+        - name: authentication
+          image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/staging-repository/authentication:latest
+          ports:
+            - containerPort: 8080
\ No newline at end of file
-- 
GitLab


From 13ac5df5ee4a73d2d94ad170c6fbc754570486f7 Mon Sep 17 00:00:00 2001
From: Fredo <fredotanzil@gmail.com>
Date: Thu, 27 Feb 2025 12:28:37 +0700
Subject: [PATCH 53/70] handle fail login email

---
 .../controller/AuthenticationController.java       | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
index d7e04c0..ee3bcfc 100644
--- a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
+++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java
@@ -65,16 +65,22 @@ public class AuthenticationController {
         try {
             return ResponseEntity.ok(authenticationService.loginUser(email, password));
         } catch (InvalidCredentialsException e){
-            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
-                    .body(e.getMessage());
+            AuthResponse response = new AuthResponse(false, e.getMessage(), null);
+            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
         }
 
     }
 
     // Endpoint for social login
     @PostMapping("/login-social")
-    public User loginSocial(@RequestParam String email) {
-        return authenticationService.loginSocial(email);
+    public ResponseEntity<Object> loginSocial(@RequestParam String email) {
+        try {
+            return ResponseEntity.ok(authenticationService.loginSocial(email));
+        } catch (InvalidCredentialsException e){
+            AuthResponse response = new AuthResponse(false, e.getMessage(), null);
+            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
+        }
+
     }
 
 
-- 
GitLab


From 125e7f7f6c621de4554216848fd3f3b4984ef02c Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Fri, 28 Feb 2025 16:39:09 +0700
Subject: [PATCH 54/70] [REFACTOR] Remove duplicated string in seeding with
 existing string constant

---
 .../authentication/seeder/DevDataSeeder.java        | 13 ++++++++-----
 .../service/AuthenticationService.java              |  2 +-
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
index 58dc982..4f23a81 100644
--- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
+++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
@@ -2,6 +2,9 @@ package com.safetypin.authentication.seeder;
 
 import com.safetypin.authentication.model.User;
 import com.safetypin.authentication.repository.UserRepository;
+import static com.safetypin.authentication.service.AuthenticationService.EMAIL_PROVIDER;
+
+import com.safetypin.authentication.service.AuthenticationService;
 import jakarta.annotation.PostConstruct;
 import org.springframework.context.annotation.Profile;
 import org.springframework.security.crypto.password.PasswordEncoder;
@@ -36,7 +39,7 @@ public class DevDataSeeder implements Runnable {
                     true,
                     "user",
                     LocalDate.of(1990, 1, 1),
-                    "EMAIL",
+                    EMAIL_PROVIDER,
                     "social1"));
 
             userRepository.save(new User("user2@example.com",
@@ -45,7 +48,7 @@ public class DevDataSeeder implements Runnable {
                     true,
                     "user",
                     LocalDate.of(1991, 2, 2),
-                    "EMAIL",
+                    EMAIL_PROVIDER,
                     "social2"));
 
             userRepository.save(new User("user3@example.com",
@@ -54,7 +57,7 @@ public class DevDataSeeder implements Runnable {
                     true,
                     "user",
                     LocalDate.of(1992, 3, 3),
-                    "EMAIL",
+                    EMAIL_PROVIDER,
                     "social3"));
 
             userRepository.save(new User("user4@example.com",
@@ -63,7 +66,7 @@ public class DevDataSeeder implements Runnable {
                     true,
                     "user",
                     LocalDate.of(1993, 4, 4),
-                    "EMAIL",
+                    EMAIL_PROVIDER,
                     "social4"));
 
             userRepository.save(new User("user5@example.com",
@@ -72,7 +75,7 @@ public class DevDataSeeder implements Runnable {
                     true,
                     "user",
                     LocalDate.of(1994, 5, 5),
-                    "EMAIL",
+                    EMAIL_PROVIDER,
                     "social5"));
         }
     }
diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index 96bca07..3a177cc 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -15,7 +15,7 @@ import java.time.Period;
 
 @Service
 public class AuthenticationService {
-    private static final String EMAIL_PROVIDER = "EMAIL";
+    public static final String EMAIL_PROVIDER = "EMAIL";
 
     private final UserRepository userRepository;
     private final PasswordEncoder passwordEncoder;
-- 
GitLab


From f952c7c610e7898f601d59d18f0c6a333809c1c0 Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Fri, 28 Feb 2025 17:21:05 +0700
Subject: [PATCH 55/70] Fix compile errors in IntelliJ by adding version to
 lombok in pom.xml

---
 pom.xml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/pom.xml b/pom.xml
index feeca04..bfbd19b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,6 +85,11 @@
 			<scope>runtime</scope>
 		</dependency>
 
+		<dependency>
+			<groupId>net.datafaker</groupId>
+			<artifactId>datafaker</artifactId>
+			<version>2.4.2</version>
+		</dependency>
 
 		<!-- REST-assured for integration testing -->
 		<dependency>
@@ -171,6 +176,7 @@
 						<path>
 							<groupId>org.projectlombok</groupId>
 							<artifactId>lombok</artifactId>
+							<version>1.18.36</version>
 						</path>
 					</annotationProcessorPaths>
 				</configuration>
-- 
GitLab


From 62dbeb76defcf6fee9608e78881ee6ced421a705 Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Fri, 28 Feb 2025 17:59:44 +0700
Subject: [PATCH 56/70] [REFACTOR] Remove long constructor in User and replace
 all occurrences with the no-arg constructor and set attribute.

---
 .../safetypin/authentication/model/User.java  |  37 ++---
 .../authentication/seeder/DevDataSeeder.java  |  92 ++++++-----
 .../service/AuthenticationService.java        |  24 ++-
 .../AuthenticationControllerTest.java         |  44 ++++-
 .../authentication/model/UserTest.java        |  11 +-
 .../seeder/DevDataSeederTest.java             |  18 +-
 .../service/AuthenticationServiceTest.java    | 156 +++++++++++++++---
 7 files changed, 268 insertions(+), 114 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/model/User.java b/src/main/java/com/safetypin/authentication/model/User.java
index c08cb81..d38fe7d 100644
--- a/src/main/java/com/safetypin/authentication/model/User.java
+++ b/src/main/java/com/safetypin/authentication/model/User.java
@@ -10,59 +10,42 @@ import java.time.LocalDate;
 @Table(name = "users")
 public class User {
 
-    @Setter
-    @Getter
     @Id
+    @Setter @Getter
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
 
-    @Setter
-    @Getter
+    @Setter @Getter
     @Column(nullable = false, unique = true)
     private String email;
 
     // May be null for social login users
-    @Setter
-    @Getter
+    @Setter @Getter
     @Column(nullable = false)
     private String password;
 
-    @Setter
-    @Getter
+    @Setter @Getter
     @Column(nullable = false)
     private String name;
 
     @Column(nullable = false)
     private boolean isVerified = false;
 
-    @Setter
-    @Getter
+    @Setter @Getter
     private String role;
 
     // New fields
-    @Setter
-    @Getter
+    @Setter @Getter
     private LocalDate birthdate;
-    @Setter
-    @Getter
+
+    @Setter  @Getter
     private String provider;  // "EMAIL", "GOOGLE", "APPLE"
-    @Setter
-    @Getter
+
+    @Setter @Getter
     private String socialId;  // For social login users
 
     public User() {}
 
-    public User(String email, String password, String name, boolean isVerified, String role,
-                LocalDate birthdate, String provider, String socialId) {
-        this.email = email;
-        this.password = password;
-        this.name = name;
-        this.isVerified = isVerified;
-        this.role = role;
-        this.birthdate = birthdate;
-        this.provider = provider;
-        this.socialId = socialId;
-    }
 
     // Getters and setters
 
diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
index 4f23a81..7b9a5b2 100644
--- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
+++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
@@ -33,50 +33,62 @@ public class DevDataSeeder implements Runnable {
     public void run() {
         // Only seed if there are no users in the repository
         if (userRepository.count() == 0) {
-            userRepository.save(new User("user1@example.com",
-                    passwordEncoder.encode("password1"),
-                    "User One",
-                    true,
-                    "user",
-                    LocalDate.of(1990, 1, 1),
-                    EMAIL_PROVIDER,
-                    "social1"));
 
-            userRepository.save(new User("user2@example.com",
-                    passwordEncoder.encode("password2"),
-                    "User Two",
-                    true,
-                    "user",
-                    LocalDate.of(1991, 2, 2),
-                    EMAIL_PROVIDER,
-                    "social2"));
+            User user1 = new User();
+            user1.setEmail("user1@example.com");
+            user1.setPassword(passwordEncoder.encode("password1"));
+            user1.setName("User One");
+            user1.setVerified(true);
+            user1.setRole("user");
+            user1.setBirthdate(LocalDate.of(1990, 1, 1));
+            user1.setProvider(EMAIL_PROVIDER);
+            user1.setSocialId("social1");
+            userRepository.save(user1);
 
-            userRepository.save(new User("user3@example.com",
-                    passwordEncoder.encode("password3"),
-                    "User Three",
-                    true,
-                    "user",
-                    LocalDate.of(1992, 3, 3),
-                    EMAIL_PROVIDER,
-                    "social3"));
+            User user2 = new User();
+            user2.setEmail("user2@example.com");
+            user2.setPassword(passwordEncoder.encode("password2"));
+            user2.setName("User Two");
+            user2.setVerified(true);
+            user2.setRole("user");
+            user2.setBirthdate(LocalDate.of(1991, 2, 2));
+            user2.setProvider(EMAIL_PROVIDER);
+            user2.setSocialId("social2");
+            userRepository.save(user2);
 
-            userRepository.save(new User("user4@example.com",
-                    passwordEncoder.encode("password4"),
-                    "User Four",
-                    true,
-                    "user",
-                    LocalDate.of(1993, 4, 4),
-                    EMAIL_PROVIDER,
-                    "social4"));
 
-            userRepository.save(new User("user5@example.com",
-                    passwordEncoder.encode("password5"),
-                    "User Five",
-                    true,
-                    "user",
-                    LocalDate.of(1994, 5, 5),
-                    EMAIL_PROVIDER,
-                    "social5"));
+            User user3 = new User();
+            user3.setEmail("user3@example.com");
+            user3.setPassword(passwordEncoder.encode("password3"));
+            user3.setName("User Three");
+            user3.setVerified(true);
+            user3.setRole("user");
+            user3.setBirthdate(LocalDate.of(1992, 3, 3));
+            user3.setProvider(EMAIL_PROVIDER);
+            user3.setSocialId("social3");
+            userRepository.save(user3);
+
+            User user4 = new User();
+            user4.setEmail("user4@example.com");
+            user4.setPassword(passwordEncoder.encode("password4"));
+            user4.setName("User Four");
+            user4.setVerified(true);
+            user4.setRole("user");
+            user4.setBirthdate(LocalDate.of(1993, 4, 4));
+            user4.setProvider(EMAIL_PROVIDER);
+            user4.setSocialId("social4");
+            userRepository.save(user4);
+
+            User user5 = new User();
+            user5.setEmail("user5@example.com");
+            user5.setPassword(passwordEncoder.encode("password5"));
+            user5.setName("User Five");
+            user5.setVerified(true);
+            user5.setRole("user");
+            user5.setBirthdate(LocalDate.of(1994, 5, 5));
+            user5.setProvider(EMAIL_PROVIDER);
+            user5.setSocialId("social5");
+            userRepository.save(user5);
         }
     }
 }
diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index 3a177cc..c5616f6 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -37,8 +37,16 @@ public class AuthenticationService {
             throw new UserAlreadyExistsException("User already exists with this email. If you registered using social login, please sign in with Google/Apple.");
         }
         String encodedPassword = passwordEncoder.encode(request.getPassword());
-        User user = new User(request.getEmail(), encodedPassword, request.getName(), false, "USER",
-                request.getBirthdate(), EMAIL_PROVIDER, null);
+
+        User user = new User();
+        user.setEmail(request.getEmail());
+        user.setPassword(encodedPassword);
+        user.setName(request.getName());
+        user.setVerified(false);
+        user.setRole("USER");
+        user.setBirthdate(request.getBirthdate());
+        user.setProvider(EMAIL_PROVIDER);
+        user.setSocialId(null);
         user = userRepository.save(user);
         otpService.generateOTP(request.getEmail());
         logger.info("OTP generated for {} at {}", request.getEmail(), java.time.LocalDateTime.now());
@@ -57,8 +65,16 @@ public class AuthenticationService {
             }
             return existing;
         }
-        User user = new User(request.getEmail(), null, request.getName(), true, "USER",
-                request.getBirthdate(), request.getProvider().toUpperCase(), request.getSocialId());
+        User user = new User();
+        user.setEmail(request.getEmail());
+        user.setPassword(null);
+        user.setName(request.getName());
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(request.getBirthdate());
+        user.setProvider(request.getProvider().toUpperCase());
+        user.setSocialId(request.getSocialId());
+
         user = userRepository.save(user);
         logger.info("User registered via {}: {} at {}", request.getProvider(), request.getEmail(), java.time.LocalDateTime.now());
         return user;
diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
index 2051cf1..f0de3fd 100644
--- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
+++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
@@ -67,8 +67,14 @@ class AuthenticationControllerTest {
         request.setName("Test User");
         request.setBirthdate(LocalDate.now().minusYears(20));
 
-        User user = new User("email@example.com", "encodedPassword", "Test User", false, "USER",
-                request.getBirthdate(), "EMAIL", null);
+        User user = new User();
+        user.setEmail("email@example.com");
+        user.setPassword("encodedPassword");
+        user.setName("Test User");
+        user.setRole("USER");
+        user.setBirthdate(request.getBirthdate());
+        user.setProvider("EMAIL");
+
         user.setId(1L);
         Mockito.when(authenticationService.registerUser(any(RegistrationRequest.class))).thenReturn(user);
 
@@ -90,8 +96,15 @@ class AuthenticationControllerTest {
         request.setBirthdate(LocalDate.now().minusYears(25));
         request.setSocialId("social123");
 
-        User user = new User("social@example.com", null, "Social User", true, "USER",
-                request.getBirthdate(), "GOOGLE", "social123");
+        User user = new User();
+        user.setEmail("social@example.com");
+        user.setPassword(null);
+        user.setName("Social User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(request.getBirthdate());
+        user.setProvider("GOOGLE");
+        user.setSocialId("social123");
         user.setId(2L);
         Mockito.when(authenticationService.socialLogin(any(SocialLoginRequest.class))).thenReturn(user);
 
@@ -105,8 +118,16 @@ class AuthenticationControllerTest {
 
     @Test
     void testLoginEmail() throws Exception {
-        User user = new User("email@example.com", "encodedPassword", "Test User", true, "USER",
-                LocalDate.now().minusYears(20), "EMAIL", null);
+        User user = new User();
+        user.setEmail("email@example.com");
+        user.setPassword("encodedPassword");
+        user.setName("Test User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(20));
+        user.setProvider("EMAIL");
+        user.setSocialId(null);
+
         user.setId(1L);
         Mockito.when(authenticationService.loginUser(eq("email@example.com"), eq("password"))).thenReturn(user);
 
@@ -120,8 +141,15 @@ class AuthenticationControllerTest {
 
     @Test
     void testLoginSocial() throws Exception {
-        User user = new User("social@example.com", null, "Social User", true, "USER",
-                LocalDate.now().minusYears(25), "GOOGLE", "social123");
+        User user = new User();
+        user.setEmail("social@example.com");
+        user.setPassword(null);
+        user.setName("Social User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(25));
+        user.setProvider("GOOGLE");
+        user.setSocialId("social123");
         user.setId(2L);
         Mockito.when(authenticationService.loginSocial(eq("social@example.com"))).thenReturn(user);
 
diff --git a/src/test/java/com/safetypin/authentication/model/UserTest.java b/src/test/java/com/safetypin/authentication/model/UserTest.java
index f66bde4..0b3c180 100644
--- a/src/test/java/com/safetypin/authentication/model/UserTest.java
+++ b/src/test/java/com/safetypin/authentication/model/UserTest.java
@@ -68,7 +68,16 @@ class UserTest {
         String provider = "EMAIL";
         String socialId = null;
 
-        User user = new User(email, password, name, verified, role, birthdate, provider, socialId);
+        User user = new User();
+        user.setEmail(email);
+        user.setPassword(password);
+        user.setName(name);
+        user.setVerified(verified);
+        user.setRole(role);
+        user.setBirthdate(birthdate);
+        user.setProvider(provider);
+        user.setSocialId(socialId);
+
 
         // id remains null until set (by the persistence layer)
         assertNull(user.getId(), "Id should be null when not set");
diff --git a/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java
index 8b58e96..04e4931 100644
--- a/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java
+++ b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java
@@ -42,14 +42,16 @@ class DevDataSeederTest {
     @Test
     void testSeederDoesNotInsertIfUsersExist() {
         // Save an existing user into the repository
-        userRepository.save(new User("existing@example.com",
-                passwordEncoder.encode("test"),
-                "Existing User",
-                true,
-                "admin",
-                LocalDate.of(1990, 1, 1),
-                "EMAIL",
-                "social_9999"));
+        User user = new User();
+        user.setEmail("existing@example.com");
+        user.setPassword(passwordEncoder.encode("test"));
+        user.setName("Existing User");
+        user.setVerified(true);
+        user.setRole("admin");
+        user.setBirthdate(LocalDate.of(1990, 1, 1));
+        user.setProvider("EMAIL");
+        user.setSocialId("social_9999");
+        userRepository.save(user);
 
         long countBefore = userRepository.count();
         new DevDataSeeder(userRepository, passwordEncoder).run();
diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
index b1f12b9..32b2265 100644
--- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
+++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
@@ -77,8 +77,16 @@ class AuthenticationServiceTest {
 
         when(userRepository.findByEmail("test@example.com")).thenReturn(null);
         when(passwordEncoder.encode("password")).thenReturn("encodedPassword");
-        User savedUser = new User("test@example.com", "encodedPassword", "Test User", false, "USER",
-                request.getBirthdate(), "EMAIL", null);
+        User savedUser = new User();
+        savedUser.setEmail("test@example.com");
+        savedUser.setPassword("encodedPassword");
+        savedUser.setName("Test User");
+        savedUser.setVerified(false);
+        savedUser.setRole("USER");
+        savedUser.setBirthdate(request.getBirthdate());
+        savedUser.setProvider("EMAIL");
+        savedUser.setSocialId(null);
+
         savedUser.setId(1L);
         when(userRepository.save(any(User.class))).thenReturn(savedUser);
 
@@ -117,8 +125,16 @@ class AuthenticationServiceTest {
         request.setSocialId("social123");
         request.setSocialToken("token");
 
-        User existingUser = new User("social@example.com", "encodedPassword", "Existing User", false, "USER",
-                LocalDate.now().minusYears(30), "EMAIL", null);
+        User existingUser = new User();
+        existingUser.setEmail("social@example.com");
+        existingUser.setPassword("encodedPassword");
+        existingUser.setName("Existing User");
+        existingUser.setVerified(false);
+        existingUser.setRole("USER");
+        existingUser.setBirthdate(LocalDate.now().minusYears(30));
+        existingUser.setProvider("EMAIL");
+        existingUser.setSocialId(null);
+
         when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser);
 
         Exception exception = assertThrows(UserAlreadyExistsException.class, () ->
@@ -137,8 +153,16 @@ class AuthenticationServiceTest {
         request.setSocialId("social123");
         request.setSocialToken("token");
 
-        User existingUser = new User("social@example.com", null, "Social User", true, "USER",
-                LocalDate.now().minusYears(25), "GOOGLE", "social123");
+        User existingUser = new User();
+        existingUser.setEmail("social@example.com");
+        existingUser.setPassword(null);
+        existingUser.setName("Social User");
+        existingUser.setVerified(true);
+        existingUser.setRole("USER");
+        existingUser.setBirthdate(LocalDate.now().minusYears(25));
+        existingUser.setProvider("GOOGLE");
+        existingUser.setSocialId("social123");
+
         when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser);
 
         User result = authenticationService.socialLogin(request);
@@ -157,8 +181,16 @@ class AuthenticationServiceTest {
         request.setSocialToken("token");
 
         when(userRepository.findByEmail("social@example.com")).thenReturn(null);
-        User savedUser = new User("social@example.com", null, "Social User", true, "USER",
-                request.getBirthdate(), "GOOGLE", "social123");
+        User savedUser = new User();
+        savedUser.setEmail("social@example.com");
+        savedUser.setPassword(null);
+        savedUser.setName("Social User");
+        savedUser.setVerified(true);
+        savedUser.setRole("USER");
+        savedUser.setBirthdate(request.getBirthdate());
+        savedUser.setProvider("GOOGLE");
+        savedUser.setSocialId("social123");
+
         savedUser.setId(2L);
         when(userRepository.save(any(User.class))).thenReturn(savedUser);
 
@@ -180,8 +212,16 @@ class AuthenticationServiceTest {
 
     @Test
     void testLoginUser_InvalidPassword_NullPassword() {
-        User user = new User("test@example.com", null, "Test User", true, "USER",
-                LocalDate.now().minusYears(20), "EMAIL", null);
+        User user = new User();
+        user.setEmail("test@example.com");
+        user.setPassword(null);
+        user.setName("Test User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(20));
+        user.setProvider("EMAIL");
+        user.setSocialId(null);
+
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
 
         Exception exception = assertThrows(InvalidCredentialsException.class, () ->
@@ -192,8 +232,16 @@ class AuthenticationServiceTest {
 
     @Test
     void testLoginUser_InvalidPassword_WrongMatch() {
-        User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
-                LocalDate.now().minusYears(20), "EMAIL", null);
+        User user = new User();
+        user.setEmail("test@example.com");
+        user.setPassword("encodedPassword");
+        user.setName("Test User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(20));
+        user.setProvider("EMAIL");
+        user.setSocialId(null);
+
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
         when(passwordEncoder.matches("wrongPassword", "encodedPassword")).thenReturn(false);
 
@@ -205,8 +253,16 @@ class AuthenticationServiceTest {
 
     @Test
     void testLoginUser_Success() {
-        User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
-                LocalDate.now().minusYears(20), "EMAIL", null);
+        User user = new User();
+        user.setEmail("test@example.com");
+        user.setPassword("encodedPassword");
+        user.setName("Test User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(20));
+        user.setProvider("EMAIL");
+        user.setSocialId(null);
+
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
         when(passwordEncoder.matches("password", "encodedPassword")).thenReturn(true);
 
@@ -228,8 +284,16 @@ class AuthenticationServiceTest {
 
     @Test
     void testLoginSocial_Success() {
-        User user = new User("social@example.com", null, "Social User", true, "USER",
-                LocalDate.now().minusYears(25), "GOOGLE", "social123");
+        User user = new User();
+        user.setEmail("social@example.com");
+        user.setPassword(null);
+        user.setName("Social User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(25));
+        user.setProvider("GOOGLE");
+        user.setSocialId("social123");
+
         when(userRepository.findByEmail("social@example.com")).thenReturn(user);
 
         User result = authenticationService.loginSocial("social@example.com");
@@ -243,8 +307,16 @@ class AuthenticationServiceTest {
     void testVerifyOTP_Success() {
         // OTPService returns true and user is found
         when(otpService.verifyOTP("test@example.com", "123456")).thenReturn(true);
-        User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER",
-                LocalDate.now().minusYears(20), "EMAIL", null);
+        User user = new User();
+        user.setEmail("test@example.com");
+        user.setPassword("encodedPassword");
+        user.setName("Test User");
+        user.setVerified(false);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(20));
+        user.setProvider("EMAIL");
+        user.setSocialId(null);
+
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
         when(userRepository.save(any(User.class))).thenReturn(user);
 
@@ -277,8 +349,16 @@ class AuthenticationServiceTest {
 
     @Test
     void testForgotPassword_Success() {
-        User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
-                LocalDate.now().minusYears(20), "EMAIL", null);
+        User user = new User();
+        user.setEmail("test@example.com");
+        user.setPassword("encodedPassword");
+        user.setName("Test User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(20));
+        user.setProvider("EMAIL");
+        user.setSocialId(null);
+
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
 
         assertDoesNotThrow(() -> authenticationService.forgotPassword("test@example.com"));
@@ -294,8 +374,16 @@ class AuthenticationServiceTest {
         assertTrue(exception1.getMessage().contains("Password reset is only available for email-registered users."));
 
         // Case 2: user exists but provider is not EMAIL
-        User user = new User("social@example.com", null, "Social User", true, "USER",
-                LocalDate.now().minusYears(25), "GOOGLE", "social123");
+        User user = new User();
+        user.setEmail("social@example.com");
+        user.setPassword(null);
+        user.setName("Social User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(25));
+        user.setProvider("GOOGLE");
+        user.setSocialId("social123");
+
         when(userRepository.findByEmail("social@example.com")).thenReturn(user);
         Exception exception2 = assertThrows(IllegalArgumentException.class, () ->
                 authenticationService.forgotPassword("social@example.com")
@@ -314,8 +402,16 @@ class AuthenticationServiceTest {
 
     @Test
     void testPostContent_UserNotVerified() {
-        User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER",
-                LocalDate.now().minusYears(20), "EMAIL", null);
+        User user = new User();
+        user.setEmail("test@example.com");
+        user.setPassword("encodedPassword");
+        user.setName("Test User");
+        user.setVerified(false);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(20));
+        user.setProvider("EMAIL");
+        user.setSocialId(null);
+
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
         String response = authenticationService.postContent("test@example.com", "Content");
         assertTrue(response.contains("not verified"));
@@ -323,8 +419,16 @@ class AuthenticationServiceTest {
 
     @Test
     void testPostContent_UserVerified() {
-        User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER",
-                LocalDate.now().minusYears(20), "EMAIL", null);
+        User user = new User();
+        user.setEmail("test@example.com");
+        user.setPassword("encodedPassword");
+        user.setName("Test User");
+        user.setVerified(true);
+        user.setRole("USER");
+        user.setBirthdate(LocalDate.now().minusYears(20));
+        user.setProvider("EMAIL");
+        user.setSocialId(null);
+
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
         String response = authenticationService.postContent("test@example.com", "Content");
         assertEquals("Content posted successfully", response);
-- 
GitLab


From 8b2b1716e1f298607f13d084281ead29fc8d3d8c Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Fri, 28 Feb 2025 18:02:19 +0700
Subject: [PATCH 57/70] [REFACTOR] Replace empty constructor in User with
 @NoArgsConstructor

---
 src/main/java/com/safetypin/authentication/model/User.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/model/User.java b/src/main/java/com/safetypin/authentication/model/User.java
index d38fe7d..41e776c 100644
--- a/src/main/java/com/safetypin/authentication/model/User.java
+++ b/src/main/java/com/safetypin/authentication/model/User.java
@@ -2,12 +2,14 @@ package com.safetypin.authentication.model;
 
 import jakarta.persistence.*;
 import lombok.Getter;
+import lombok.NoArgsConstructor;
 import lombok.Setter;
 
 import java.time.LocalDate;
 
 @Entity
 @Table(name = "users")
+@NoArgsConstructor
 public class User {
 
     @Id
@@ -44,8 +46,6 @@ public class User {
     @Setter @Getter
     private String socialId;  // For social login users
 
-    public User() {}
-
 
     // Getters and setters
 
-- 
GitLab


From 75308f5b40f123fb3a7639f6b88aaede185f655c Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 20:51:06 +0700
Subject: [PATCH 58/70] Add README.md with project overview, features, and
 installation instructions

---
 README.md | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 180 insertions(+)
 create mode 100644 README.md

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..10b7c73
--- /dev/null
+++ b/README.md
@@ -0,0 +1,180 @@
+# Authentication Microservice
+[![Quality gate](https://sonarcloud.io/api/project_badges/quality_gate?project=safetypin-official_be-authentication)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+
+[![SonarQube Cloud](https://sonarcloud.io/images/project_badges/sonarcloud-dark.svg)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+
+[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=safetypin-official_be-authentication&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=safetypin-official_be-authentication&metric=bugs)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=safetypin-official_be-authentication&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=safetypin-official_be-authentication&metric=coverage)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=safetypin-official_be-authentication&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=safetypin-official_be-authentication&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=safetypin-official_be-authentication&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=safetypin-official_be-authentication&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=safetypin-official_be-authentication&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication)
+
+## Overview
+
+The **Authentication Microservice** is a Spring Boot-based REST API that handles user authentication and authorization. It supports both traditional email-based registration/login as well as social authentication (e.g., Google, Apple). The service includes features such as OTP (One-Time Password) verification, password reset simulation, and a simple content posting endpoint for verified users.
+
+## Table of Contents
+
+- [Features](#features)
+- [Tech Stack](#tech-stack)
+- [Prerequisites](#prerequisites)
+- [Installation](#installation)
+- [Configuration](#configuration)
+- [API Endpoints](#api-endpoints)
+- [Running Tests](#running-tests)
+- [Development](#development)
+- [Contributing](#contributing)
+- [License](#license)
+- [Acknowledgements](#acknowledgements)
+
+## Features
+
+- **Email Registration & Login:** Supports registration and login using email and password.
+- **Social Authentication:** Simulated endpoints for registration and login using social providers.
+- **OTP Verification:** Generates and verifies OTPs for user account validation.
+- **Password Reset:** Simulated password reset functionality for email-based users.
+- **Content Posting:** A secured endpoint that only allows verified users to post content.
+- **Dev Data Seeder:** Automatically seeds development data when running under the `dev` profile.
+- **Robust Testing:** Comprehensive unit and integration tests using JUnit 5, Spring Boot Test, and TestContainers.
+
+## Tech Stack
+
+- **Java:** 21
+- **Spring Boot:** 3.4.2
+- **Maven:** Build automation and dependency management
+- **Spring Security:** For authentication and password encoding
+- **Spring Data JPA:** For ORM and database access
+- **PostgresSQL & H2:** Database support (PostgresSQL for production; H2 for development/testing)
+- **JUnit 5 & TestContainers:** For testing and integration testing
+
+## Prerequisites
+
+- **Java 21**
+- **Maven 3.6+**
+- A running PostgresSQL instance (for production) or H2 (for development/testing)
+
+## Installation
+
+1. **Clone the repository:**
+```
+   git clone https://github.com/yourusername/authentication-microservice.git  
+   cd authentication-microservice
+```
+2. **Build the project using Maven:**
+```
+   mvn clean install
+```
+3. **Run the application:**
+```
+   mvn spring-boot:run
+```
+   The service will start on the default port (typically 8080).
+
+## Configuration
+
+- **application.properties:** Configure your database, server port, and other environment-specific settings.
+- **Profiles:** Use the `dev` profile for development. The `DevDataSeeder` will automatically seed sample user data when running under this profile:
+
+```
+mvn spring-boot:run -Dspring-boot.run.profiles=dev
+```
+
+## API Endpoints
+
+### Public Endpoints
+
+- **GET `/`**  
+  Returns a simple "Hello, World!" greeting.
+
+### Authentication Endpoints
+
+- **POST `/api/auth/register-email`**  
+  Registers a new user using email.
+    - **Request Body:** JSON containing `email`, `password`, `name`, and `birthdate`.
+    - **Response:** Success message with user data.
+
+- **POST `/api/auth/register-social`**  
+  Registers or logs in a user using social authentication.
+    - **Request Body:** JSON containing `provider`, `socialToken`, `email`, `name`, `birthdate`, and `socialId`.
+    - **Response:** Success message with user data.
+
+- **POST `/api/auth/login-email`**  
+  Authenticates a user using email and password.
+    - **Parameters:** `email`, `password`.
+    - **Response:** User data on successful login.
+
+- **POST `/api/auth/login-social`**  
+  Logs in a user via social authentication.
+    - **Parameter:** `email`.
+    - **Response:** User data on successful social login.
+
+- **POST `/api/auth/verify-otp`**  
+  Verifies the OTP for account validation.
+    - **Parameters:** `email`, `otp`.
+    - **Response:** Message indicating success or failure of OTP verification.
+
+- **POST `/api/auth/forgot-password`**  
+  Simulates password reset for email-registered users.
+    - **Request Body:** JSON containing `email`.
+    - **Response:** Message indicating that reset instructions have been sent.
+
+- **POST `/api/auth/post`**  
+  Allows posting of content for verified users.
+    - **Parameters:** `email`, `content`.
+    - **Response:** Success or failure message based on user verification.
+
+- **GET `/api/auth/dashboard`**  
+  Returns dashboard data (currently a placeholder).
+    - **Response:** An empty JSON object.
+
+## Running Tests
+
+To run all unit and integration tests, execute:
+```
+mvn test
+```
+
+Tests are written using JUnit 5 and cover controllers, services, repository interactions, and utility components such as OTP generation and validation.
+
+## Development
+
+- **Code Style:** The project adheres to standard Java coding conventions and uses Lombok to reduce boilerplate.
+- **Continuous Integration:** Integration with CI tools is recommended. Test coverage is ensured using Maven Surefire and Failsafe plugins.
+- **Debugging:** Utilize Spring Boot DevTools for hot-reloading during development.
+
+## Contributing
+
+Contributions are welcome! Please follow these steps:
+
+1. Fork the repository.
+2. Create a new feature branch (git checkout -b feature/YourFeature).
+3. Commit your changes (git commit -m 'Add some feature').
+4. Push to the branch (git push origin feature/YourFeature).
+5. Open a Pull Request.
+
+Please ensure that your code adheres to the existing coding style and that all tests pass before submitting your PR.
+
+## License
+
+This project is licensed under the
+Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)
+License. See the LICENSE file for details.
+
+## Acknowledgements
+
+- Thanks to the Spring Boot team and the open-source community for their continuous contributions.
+- Special thanks to contributors who have helped improve the project.
+
+## Author
+SafetyPin Team
+- Darrel Danadyaksa Poli - 2206081995
+- Fredo Melvern Tanzil - 2206024713
+- Sefriano Edsel Jieftara Djie - 2206818966=
+- Alma Putri Nashrida - 2206814671
+- Andi Salsabila Ardian - 2206083571
+- Muhammad Raihan Akbar - 2206827674
+
-- 
GitLab


From 292037689feba51ab48be1bb1bb0dc534bda1ad9 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 21:16:52 +0700
Subject: [PATCH 59/70] [REFACTOR] Simplify Mockito argument matchers in
 AuthenticationControllerTest

---
 .../controller/AuthenticationControllerTest.java      | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
index f0de3fd..6ecff60 100644
--- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
+++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java
@@ -22,7 +22,6 @@ import org.springframework.test.web.servlet.MockMvc;
 import java.time.LocalDate;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@@ -129,7 +128,7 @@ class AuthenticationControllerTest {
         user.setSocialId(null);
 
         user.setId(1L);
-        Mockito.when(authenticationService.loginUser(eq("email@example.com"), eq("password"))).thenReturn(user);
+        Mockito.when(authenticationService.loginUser("email@example.com", "password")).thenReturn(user);
 
         mockMvc.perform(post("/api/auth/login-email")
                         .param("email", "email@example.com")
@@ -151,7 +150,7 @@ class AuthenticationControllerTest {
         user.setProvider("GOOGLE");
         user.setSocialId("social123");
         user.setId(2L);
-        Mockito.when(authenticationService.loginSocial(eq("social@example.com"))).thenReturn(user);
+        Mockito.when(authenticationService.loginSocial("social@example.com")).thenReturn(user);
 
         mockMvc.perform(post("/api/auth/login-social")
                         .param("email", "social@example.com"))
@@ -162,7 +161,7 @@ class AuthenticationControllerTest {
 
     @Test
     void testVerifyOTP_Success() throws Exception {
-        Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("123456"))).thenReturn(true);
+        Mockito.when(authenticationService.verifyOTP("email@example.com", "123456")).thenReturn(true);
 
         mockMvc.perform(post("/api/auth/verify-otp")
                         .param("email", "email@example.com")
@@ -173,7 +172,7 @@ class AuthenticationControllerTest {
 
     @Test
     void testVerifyOTP_Failure() throws Exception {
-        Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("000000"))).thenReturn(false);
+        Mockito.when(authenticationService.verifyOTP("email@example.com", "000000")).thenReturn(false);
 
         mockMvc.perform(post("/api/auth/verify-otp")
                         .param("email", "email@example.com")
@@ -198,7 +197,7 @@ class AuthenticationControllerTest {
 
     @Test
     void testPostContent() throws Exception {
-        Mockito.when(authenticationService.postContent(eq("email@example.com"), eq("Test Content")))
+        Mockito.when(authenticationService.postContent("email@example.com", "Test Content"))
                 .thenReturn("Content posted successfully");
 
         mockMvc.perform(post("/api/auth/post")
-- 
GitLab


From b95a288569f5560ad68a2c276684e723d7183059 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 21:16:58 +0700
Subject: [PATCH 60/70] [REFACTOR] Update age verification to 16 and improve
 logging messages in AuthenticationService

---
 .../service/AuthenticationService.java        | 29 ++++++++++---------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index c5616f6..97c7e5d 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -30,11 +30,12 @@ public class AuthenticationService {
 
     // Registration using email – includes birthdate and OTP generation
     public User registerUser(RegistrationRequest request) {
-        if (calculateAge(request.getBirthdate()) < 18) {
-            throw new IllegalArgumentException("User must be at least 18 years old");
+        if (calculateAge(request.getBirthdate()) < 16) {
+            throw new IllegalArgumentException("User must be at least 16 years old");
         }
-        if (userRepository.findByEmail(request.getEmail()) != null) {
-            throw new UserAlreadyExistsException("User already exists with this email. If you registered using social login, please sign in with Google/Apple.");
+        User existingUser = userRepository.findByEmail(request.getEmail());
+        if (existingUser != null) {
+            throw new UserAlreadyExistsException("Email address is already registered. If you previously used social login (Google/Apple), please use that method to sign in.");
         }
         String encodedPassword = passwordEncoder.encode(request.getPassword());
 
@@ -49,14 +50,14 @@ public class AuthenticationService {
         user.setSocialId(null);
         user = userRepository.save(user);
         otpService.generateOTP(request.getEmail());
-        logger.info("OTP generated for {} at {}", request.getEmail(), java.time.LocalDateTime.now());
+        logger.info("OTP generated for user at {}", java.time.LocalDateTime.now());
         return user;
     }
 
     // Social registration/login – simulating data fetched from Google/Apple
     public User socialLogin(SocialLoginRequest request) {
-        if (calculateAge(request.getBirthdate()) < 18) {
-            throw new IllegalArgumentException("User must be at least 18 years old");
+        if (calculateAge(request.getBirthdate()) < 16) {
+            throw new IllegalArgumentException("User must be at least 16 years old");
         }
         User existing = userRepository.findByEmail(request.getEmail());
         if (existing != null) {
@@ -76,7 +77,7 @@ public class AuthenticationService {
         user.setSocialId(request.getSocialId());
 
         user = userRepository.save(user);
-        logger.info("User registered via {}: {} at {}", request.getProvider(), request.getEmail(), java.time.LocalDateTime.now());
+        logger.info("User registered via social login at {}", java.time.LocalDateTime.now());
         return user;
     }
 
@@ -85,12 +86,12 @@ public class AuthenticationService {
         User user = userRepository.findByEmail(email);
         if (user == null) {
             // email not exists
-            logger.warn("Login failed: Email not found for {}", email);
+            logger.warn("Login failed: Email not found");
             throw new InvalidCredentialsException("Invalid email");
         }
         if (!passwordEncoder.matches(rawPassword, user.getPassword())) {
             // incorrect password
-            logger.warn("Login failed: Incorrect password for {}", email);
+            logger.warn("Login failed: Incorrect password attempt");
             throw new InvalidCredentialsException("Invalid password");
         }
         logger.info("User logged in: {} at {}", email, java.time.LocalDateTime.now());
@@ -103,7 +104,7 @@ public class AuthenticationService {
         if (user == null) {
             throw new InvalidCredentialsException("Social login failed: Email not found");
         }
-        logger.info("User logged in via social: {} at {}", email, java.time.LocalDateTime.now());
+        logger.info("User logged in via social authentication at {}", java.time.LocalDateTime.now());
         return user;
     }
 
@@ -115,10 +116,10 @@ public class AuthenticationService {
             if (user != null) {
                 user.setVerified(true);
                 userRepository.save(user);
-                logger.info("OTP verified for {} at {}", email, java.time.LocalDateTime.now());
+                logger.info("OTP successfully verified at {}", java.time.LocalDateTime.now());
             }
         } else {
-            logger.warn("OTP verification failed for {} at {}", email, java.time.LocalDateTime.now());
+            logger.warn("OTP verification failed at {}", java.time.LocalDateTime.now());
         }
         return result;
     }
@@ -130,7 +131,7 @@ public class AuthenticationService {
             throw new IllegalArgumentException("Password reset is only available for email-registered users.");
         }
         // In production, send a reset token via email.
-        logger.info("Password reset requested for {} at {}", email, java.time.LocalDateTime.now());
+        logger.info("Password reset requested at {}", java.time.LocalDateTime.now());
     }
 
     // Example method representing posting content that requires a verified account
-- 
GitLab


From e0b3158c002413df6f770958d180222bd0e0fc35 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 21:17:05 +0700
Subject: [PATCH 61/70] [REFACTOR] Remove unused import of
 AuthenticationService in DevDataSeeder

---
 .../java/com/safetypin/authentication/seeder/DevDataSeeder.java  | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
index 7b9a5b2..448c2bd 100644
--- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
+++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
@@ -4,7 +4,6 @@ import com.safetypin.authentication.model.User;
 import com.safetypin.authentication.repository.UserRepository;
 import static com.safetypin.authentication.service.AuthenticationService.EMAIL_PROVIDER;
 
-import com.safetypin.authentication.service.AuthenticationService;
 import jakarta.annotation.PostConstruct;
 import org.springframework.context.annotation.Profile;
 import org.springframework.security.crypto.password.PasswordEncoder;
-- 
GitLab


From 2de6ad536a36c4decdf059a3d44aac4271dc04ba Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 21:29:14 +0700
Subject: [PATCH 62/70] [REFACTOR] Update age verification in
 AuthenticationServiceTest to 16 years old

---
 .../service/AuthenticationServiceTest.java             | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
index 32b2265..97b3d10 100644
--- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
+++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
@@ -43,12 +43,12 @@ class AuthenticationServiceTest {
         request.setPassword("password");
         request.setName("Test User");
         // set birthdate to 17 years old
-        request.setBirthdate(LocalDate.now().minusYears(17));
+        request.setBirthdate(LocalDate.now().minusYears(15));
 
         Exception exception = assertThrows(IllegalArgumentException.class, () ->
                 authenticationService.registerUser(request)
         );
-        assertEquals("User must be at least 18 years old", exception.getMessage());
+        assertEquals("User must be at least 16 years old", exception.getMessage());
     }
 
     @Test
@@ -64,7 +64,7 @@ class AuthenticationServiceTest {
         Exception exception = assertThrows(UserAlreadyExistsException.class, () ->
                 authenticationService.registerUser(request)
         );
-        assertTrue(exception.getMessage().contains("User already exists with this email"));
+        assertTrue(exception.getMessage().contains("Email address is already registered"));
     }
 
     @Test
@@ -104,7 +104,7 @@ class AuthenticationServiceTest {
         SocialLoginRequest request = new SocialLoginRequest();
         request.setEmail("social@example.com");
         request.setName("Social User");
-        request.setBirthdate(LocalDate.now().minusYears(17));
+        request.setBirthdate(LocalDate.now().minusYears(15));
         request.setProvider("GOOGLE");
         request.setSocialId("social123");
         request.setSocialToken("token");
@@ -112,7 +112,7 @@ class AuthenticationServiceTest {
         Exception exception = assertThrows(IllegalArgumentException.class, () ->
                 authenticationService.socialLogin(request)
         );
-        assertEquals("User must be at least 18 years old", exception.getMessage());
+        assertEquals("User must be at least 16 years old", exception.getMessage());
     }
 
     @Test
-- 
GitLab


From f1e8619074c702c153072c2e6ddf98bf9d8ec5e2 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 21:40:41 +0700
Subject: [PATCH 63/70] [REFACTOR] Improve logging messages in
 AuthenticationService for clarity

---
 .../authentication/service/AuthenticationService.java         | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index 97c7e5d..ebeb393 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -94,7 +94,7 @@ public class AuthenticationService {
             logger.warn("Login failed: Incorrect password attempt");
             throw new InvalidCredentialsException("Invalid password");
         }
-        logger.info("User logged in: {} at {}", email, java.time.LocalDateTime.now());
+        logger.info("User logged in at {}", java.time.LocalDateTime.now());
         return user;
     }
 
@@ -143,7 +143,7 @@ public class AuthenticationService {
         if (!user.isVerified()) {
             return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes.";
         }
-        logger.info("AuthenticationService.postContent :: Content posted: {}", content);
+        logger.info("AuthenticationService.postContent :: Content posted by user with email: {}", email);
         // For demo purposes, we assume the post is successful.
         return "Content posted successfully";
     }
-- 
GitLab


From 9f51f8381dc0d767a9a9b82b76cb1da50d769a48 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 21:40:52 +0700
Subject: [PATCH 64/70] [REFACTOR] Add NOSONAR comments to password encoding in
 DevDataSeeder

---
 .../safetypin/authentication/seeder/DevDataSeeder.java | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
index 448c2bd..e864402 100644
--- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
+++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java
@@ -35,7 +35,7 @@ public class DevDataSeeder implements Runnable {
 
             User user1 = new User();
             user1.setEmail("user1@example.com");
-            user1.setPassword(passwordEncoder.encode("password1"));
+            user1.setPassword(passwordEncoder.encode("password1")); //NOSONAR
             user1.setName("User One");
             user1.setVerified(true);
             user1.setRole("user");
@@ -46,7 +46,7 @@ public class DevDataSeeder implements Runnable {
 
             User user2 = new User();
             user2.setEmail("user2@example.com");
-            user2.setPassword(passwordEncoder.encode("password2"));
+            user2.setPassword(passwordEncoder.encode("password2")); //NOSONAR
             user2.setName("User Two");
             user2.setVerified(true);
             user2.setRole("user");
@@ -58,7 +58,7 @@ public class DevDataSeeder implements Runnable {
 
             User user3 = new User();
             user3.setEmail("user3@example.com");
-            user3.setPassword(passwordEncoder.encode("password3"));
+            user3.setPassword(passwordEncoder.encode("password3")); //NOSONAR
             user3.setName("User Three");
             user3.setVerified(true);
             user3.setRole("user");
@@ -69,7 +69,7 @@ public class DevDataSeeder implements Runnable {
 
             User user4 = new User();
             user4.setEmail("user4@example.com");
-            user4.setPassword(passwordEncoder.encode("password4"));
+            user4.setPassword(passwordEncoder.encode("password4")); //NOSONAR
             user4.setName("User Four");
             user4.setVerified(true);
             user4.setRole("user");
@@ -80,7 +80,7 @@ public class DevDataSeeder implements Runnable {
 
             User user5 = new User();
             user5.setEmail("user5@example.com");
-            user5.setPassword(passwordEncoder.encode("password5"));
+            user5.setPassword(passwordEncoder.encode("password5")); //NOSONAR
             user5.setName("User Five");
             user5.setVerified(true);
             user5.setRole("user");
-- 
GitLab


From a99ae5fd1215590fcc77f517efbed7f1602c8a3e Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 21:45:48 +0700
Subject: [PATCH 65/70] [REFACTOR] Enhance logging in AuthenticationService to
 include content details

---
 .../safetypin/authentication/service/AuthenticationService.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index ebeb393..88df72f 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -143,7 +143,7 @@ public class AuthenticationService {
         if (!user.isVerified()) {
             return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes.";
         }
-        logger.info("AuthenticationService.postContent :: Content posted by user with email: {}", email);
+        logger.info("AuthenticationService.postContent :: Content {} posted by user with email: {}", content, email);
         // For demo purposes, we assume the post is successful.
         return "Content posted successfully";
     }
-- 
GitLab


From f1a8916f624bfeb3f95ec6e5b8daadc630c2a2a6 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 21:54:15 +0700
Subject: [PATCH 66/70] [REFACTOR] Update content posting test to verify
 response message format

---
 .../authentication/service/AuthenticationServiceTest.java     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
index 97b3d10..1dbe0c1 100644
--- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
+++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
@@ -430,7 +430,7 @@ class AuthenticationServiceTest {
         user.setSocialId(null);
 
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
-        String response = authenticationService.postContent("test@example.com", "Content");
-        assertEquals("Content posted successfully", response);
+        String response = authenticationService.postContent("test@example.com", "ThisIsAContent");
+        assertEquals("Content 'ThisIsAContent' posted successfully.", response);
     }
 }
-- 
GitLab


From 26e794d29413ca69727d2df8f6893aa12170b0da Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 21:54:19 +0700
Subject: [PATCH 67/70] [REFACTOR] Update logging and response message format
 in postContent method

---
 .../authentication/service/AuthenticationService.java         | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index 88df72f..4dd868e 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -143,9 +143,9 @@ public class AuthenticationService {
         if (!user.isVerified()) {
             return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes.";
         }
-        logger.info("AuthenticationService.postContent :: Content {} posted by user with email: {}", content, email);
+        logger.info("AuthenticationService.postContent :: Content posted by user with email: {}", email);
         // For demo purposes, we assume the post is successful.
-        return "Content posted successfully";
+        return String.format("Content '%s' posted successfully.", content);
     }
 
     private int calculateAge(LocalDate birthdate) {
-- 
GitLab


From 3db5e91eb8c0c84e4e773d06cda43d5a4006b992 Mon Sep 17 00:00:00 2001
From: KronosDP <darrel.danadyaksa19@gmail.com>
Date: Fri, 28 Feb 2025 22:01:32 +0700
Subject: [PATCH 68/70] [REFACTOR] Simplify response messages in postContent
 method and update corresponding tests

---
 .../authentication/service/AuthenticationService.java       | 6 +++---
 .../authentication/service/AuthenticationServiceTest.java   | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
index 4dd868e..71e3683 100644
--- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
+++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java
@@ -135,7 +135,7 @@ public class AuthenticationService {
     }
 
     // Example method representing posting content that requires a verified account
-    public String postContent(String email, String content) {
+    public String postContent(String email, String content) { // NOSONAR
         User user = userRepository.findByEmail(email);
         if (user == null) {
             return "User not found. Please register.";
@@ -143,9 +143,9 @@ public class AuthenticationService {
         if (!user.isVerified()) {
             return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes.";
         }
-        logger.info("AuthenticationService.postContent :: Content posted by user with email: {}", email);
+        logger.info("Content posted successfully by user");
         // For demo purposes, we assume the post is successful.
-        return String.format("Content '%s' posted successfully.", content);
+        return "Content posted successfully";
     }
 
     private int calculateAge(LocalDate birthdate) {
diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
index 1dbe0c1..97b3d10 100644
--- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
+++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java
@@ -430,7 +430,7 @@ class AuthenticationServiceTest {
         user.setSocialId(null);
 
         when(userRepository.findByEmail("test@example.com")).thenReturn(user);
-        String response = authenticationService.postContent("test@example.com", "ThisIsAContent");
-        assertEquals("Content 'ThisIsAContent' posted successfully.", response);
+        String response = authenticationService.postContent("test@example.com", "Content");
+        assertEquals("Content posted successfully", response);
     }
 }
-- 
GitLab


From c22a73be25921c86bc816eab1a29571226da933b Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Fri, 28 Feb 2025 22:12:27 +0700
Subject: [PATCH 69/70] [REFACTOR] remove unneeded dependency

---
 pom.xml | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/pom.xml b/pom.xml
index bfbd19b..e9bfa77 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,12 +85,6 @@
 			<scope>runtime</scope>
 		</dependency>
 
-		<dependency>
-			<groupId>net.datafaker</groupId>
-			<artifactId>datafaker</artifactId>
-			<version>2.4.2</version>
-		</dependency>
-
 		<!-- REST-assured for integration testing -->
 		<dependency>
 			<groupId>io.rest-assured</groupId>
-- 
GitLab


From 50bc3759dfc0a06dc28195cb16d5ef6f475c33b2 Mon Sep 17 00:00:00 2001
From: riorio805 <sefrianojieftara@gmail.com>
Date: Fri, 28 Feb 2025 22:13:29 +0700
Subject: [PATCH 70/70] [REFACTOR] Change production-cd workflow to use
 production.yaml file was renamed from resources.yaml

---
 .github/workflows/production-cd.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/production-cd.yml b/.github/workflows/production-cd.yml
index 1e4a411..705daa4 100644
--- a/.github/workflows/production-cd.yml
+++ b/.github/workflows/production-cd.yml
@@ -49,5 +49,5 @@ jobs:
           GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }}
         run: |
           gcloud container clusters get-credentials safetypin-cluster --region asia-southeast2
-          sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" resources.yaml
-          kubectl apply -f resources.yaml
\ No newline at end of file
+          sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" production.yaml
+          kubectl apply -f production.yaml
\ No newline at end of file
-- 
GitLab