Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/main/java/de/dhbw/tinf22b6/codespark/api/model/Account.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
Expand Down Expand Up @@ -46,11 +47,20 @@ public class Account {
@OneToOne(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
private ExamDate examDate;

public Account(String username, String email, String password, UserRoleType role, boolean verified) {
@Column(nullable = false, updatable = false)
private LocalDateTime creationDate;

@Column(nullable = false)
private LocalDateTime lastLogin;

public Account(String username, String email, String password, UserRoleType role, boolean verified,
LocalDateTime creationDate, LocalDateTime lastLogin) {
this.username = username;
this.email = email;
this.password = password;
this.role = role;
this.verified = verified;
this.creationDate = creationDate;
this.lastLogin = lastLogin;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalDateTime;
import java.util.UUID;

@Getter
Expand All @@ -24,4 +25,7 @@ public class AccountDetailsResponse {

@JsonProperty("profile_image_url")
private String profileImageUrl;

@JsonProperty("creation_date")
private LocalDateTime creationDate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
Expand Down Expand Up @@ -60,7 +62,8 @@ public AccountDetailsResponse getAccountDetails(Account account) {
account.getId(),
account.getUsername(),
account.getEmail(),
account.getProfileImageUrl()
account.getProfileImageUrl(),
account.getCreationDate()
);
}

Expand All @@ -75,7 +78,9 @@ public void createAccount(AccountCreateRequest request) {
}

String encodedPassword = passwordEncoder.encode(request.getPassword());
Account account = new Account(request.getUsername(), request.getEmail(), encodedPassword, UserRoleType.USER, false);
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
Account account = new Account(request.getUsername(), request.getEmail(), encodedPassword,
UserRoleType.USER, false, now, now);
accountRepository.save(account);

String token = UUID.randomUUID().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneOffset;

@Service
public class AuthServiceImpl implements AuthService {
private final AccountRepository accountRepository;
Expand Down Expand Up @@ -45,6 +48,9 @@ public TokenResponse loginAccount(LoginRequest request) {
String accessToken = jwtUtil.generateAccessToken(account.getUsername());
String refreshToken = jwtUtil.generateRefreshToken(account.getUsername());

account.setLastLogin(LocalDateTime.now(ZoneOffset.UTC));
accountRepository.save(account);

return new TokenResponse(accessToken, refreshToken);
}

Expand All @@ -55,7 +61,14 @@ public TokenResponse refreshAccessToken(RefreshTokenRequest request) {
}

String username = jwtUtil.extractUsername(request.getRefreshToken());
Account account = accountRepository.findByUsername(username)
.orElseThrow(() -> new InvalidAccountCredentialsException("No account with username " + username + "was found."));

String newAccessToken = jwtUtil.generateAccessToken(username);

account.setLastLogin(LocalDateTime.now(ZoneOffset.UTC));
accountRepository.save(account);

return new TokenResponse(newAccessToken, request.getRefreshToken());
}
}
2 changes: 2 additions & 0 deletions src/main/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ spring:
console:
enabled: true
path: /h2-console
liquibase:
enabled: false

auth:
jwt:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
databaseChangeLog:
- changeSet:
id: 1
author: denniskp
changes:
- addColumn:
tableName: account
columns:
- column:
name: creation_date
type: TIMESTAMP
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: true
- column:
name: last_login
type: TIMESTAMP
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: true

- changeSet:
id: 1.1
author: denniskp
comment: "Backfill existing records"
changes:
- update:
tableName: account
columns:
- column:
name: creation_date
valueComputed: CURRENT_TIMESTAMP
- column:
name: last_login
valueComputed: CURRENT_TIMESTAMP

- changeSet:
id: 1.2
author: denniskp
comment: "Make both columns NOT NULL"
changes:
- addNotNullConstraint:
tableName: account
columnName: creation_date
- addNotNullConstraint:
tableName: account
columnName: last_login
4 changes: 3 additions & 1 deletion src/main/resources/db/changelog/db.changelog-master.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
databaseChangeLog: []
databaseChangeLog:
- include:
file: db/changelog/changes/001_add-account-timestamps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;
import java.util.UUID;

import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -61,7 +62,8 @@ void testGetAccountDetails() throws Exception {
UUID.fromString("e6c9f025-33ea-4b92-a114-6b6ee6c47d36"),
"user",
"email@example.com",
"https://photos.com/user-profile-photo"
"https://photos.com/user-profile-photo",
LocalDateTime.of(2022, 1, 1, 12, 0, 0)
);
Mockito.when(accountService.getAccountDetails(any())).thenReturn(mockResponse);

Expand All @@ -70,7 +72,8 @@ void testGetAccountDetails() throws Exception {
.andExpect(jsonPath("$.id").value("e6c9f025-33ea-4b92-a114-6b6ee6c47d36"))
.andExpect(jsonPath("$.username").value("user"))
.andExpect(jsonPath("$.email").value("email@example.com"))
.andExpect(jsonPath("$.profile_image_url").value("https://photos.com/user-profile-photo"));
.andExpect(jsonPath("$.profile_image_url").value("https://photos.com/user-profile-photo"))
.andExpect(jsonPath("$.creation_date").value("2022-01-01T12:00:00"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;

import java.time.LocalDateTime;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -19,7 +20,8 @@ class AccountRepositoryTests {

@Test
void testFindByUsername_shouldReturnAccount() {
Account account = new Account("john_doe", "john@example.com", "hashed_pwd", UserRoleType.USER, true);
Account account = new Account("john_doe", "john@example.com", "hashed_pwd",
UserRoleType.USER, true, LocalDateTime.now(), LocalDateTime.now());
accountRepository.save(account);

Optional<Account> result = accountRepository.findByUsername("john_doe");
Expand All @@ -30,7 +32,8 @@ void testFindByUsername_shouldReturnAccount() {

@Test
void findByEmail_shouldReturnAccount() {
Account account = new Account("jane_doe", "jane@example.com", "hashed_pwd", UserRoleType.USER, false);
Account account = new Account("jane_doe", "jane@example.com", "hashed_pwd",
UserRoleType.USER, false, LocalDateTime.now(), LocalDateTime.now());
accountRepository.save(account);

Optional<Account> result = accountRepository.findByEmail("jane@example.com");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;

import java.time.LocalDateTime;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -23,7 +24,8 @@ class ConversationRepositoryTests {

@Test
void testFindByAccount_shouldReturnConversation() {
Account account = new Account("user1", "user1@example.com", "password", UserRoleType.USER, true);
Account account = new Account("user1", "user1@example.com", "password", UserRoleType.USER,
true, LocalDateTime.now(), LocalDateTime.now());
account = accountRepository.save(account);

Conversation conversation = new Conversation(account);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ class ExamDateRepositoryTests {

@Test
void testFindByAccount_shouldReturnExamDate() {
Account account = new Account("student1", "student1@example.com", "password", UserRoleType.USER, true);
Account account = new Account("student1", "student1@example.com", "password", UserRoleType.USER,
true, LocalDateTime.now(), LocalDateTime.now());
account = accountRepository.save(account);

ExamDate examDate = new ExamDate(LocalDateTime.of(2025, 7, 15, 10, 0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class UserBadgeRepositoryTests {

@Test
void testFindByAccountAndBadge_shouldReturnUserBadge() {
Account account = new Account("user1", "user1@example.com", "secret", UserRoleType.USER, true);
Account account = new Account("user1", "user1@example.com", "secret", UserRoleType.USER,
true, LocalDateTime.now(), LocalDateTime.now());
account = accountRepository.save(account);

Badge badge = new Badge("First Win", "Earned after completing a lesson", BadgeType.FIRST_LESSON_COMPLETED, "icon.png");
Expand All @@ -46,7 +47,8 @@ void testFindByAccountAndBadge_shouldReturnUserBadge() {

@Test
void testExistsByAccountAndBadge_shouldReturnTrue() {
Account account = new Account("user2", "user2@example.com", "pass", UserRoleType.USER, true);
Account account = new Account("user2", "user2@example.com", "pass", UserRoleType.USER,
true, LocalDateTime.now(), LocalDateTime.now());
account = accountRepository.save(account);

Badge badge = new Badge("Achiever", "Earned after completing a chapter", BadgeType.FIRST_CHAPTER_COMPLETED, "icon2.png");
Expand All @@ -62,7 +64,8 @@ void testExistsByAccountAndBadge_shouldReturnTrue() {

@Test
void testExistsByAccountAndBadge_shouldReturnFalseWhenNotExists() {
Account account = accountRepository.save(new Account("user3", "user3@example.com", "pw", UserRoleType.USER, true));
Account account = accountRepository.save(new Account("user3", "user3@example.com", "pw", UserRoleType.USER,
true, LocalDateTime.now(), LocalDateTime.now()));
Badge badge = badgeRepository.save(new Badge("Unobtainable", "Never awarded", BadgeType.ALL_CHAPTERS_COMPLETED, "icon3.png"));

boolean exists = userBadgeRepository.existsByAccountAndBadge(account, badge);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

Expand All @@ -34,10 +35,11 @@ class UserLessonProgressRepositoryTests {

@Test
void testFindByAccountAndLesson_shouldReturnProgress() {
Account account = accountRepository.save(new Account("testUser", "user@example.com", "pw", UserRoleType.USER, true));
Account account = accountRepository.save(new Account("testUser", "user@example.com", "pw", UserRoleType.USER,
true, LocalDateTime.now(), LocalDateTime.now()));
Chapter chapter = chapterRepository.save(new Chapter("Chapter 1", "Intro", null, null));

TheoryLesson lesson = lessonRepository.save(new TheoryLesson("L1", "Theory", LessonType.THEORY, chapter, null, null, "Sample text"));
TheoryLesson lesson = lessonRepository.save(new TheoryLesson("L1", "Theory", LessonType.THEORY,chapter, null, null, "Sample text"));
progressRepository.save(new UserLessonProgress(account, lesson, LessonProgressState.ATTEMPTED));

Optional<UserLessonProgress> result = progressRepository.findByAccountAndLesson(account, lesson);
Expand All @@ -48,7 +50,8 @@ void testFindByAccountAndLesson_shouldReturnProgress() {

@Test
void testFindByAccountAndState_shouldReturnCorrectList() {
Account account = accountRepository.save(new Account("testUser2", "user2@example.com", "pw", UserRoleType.USER, true));
Account account = accountRepository.save(new Account("testUser2", "user2@example.com", "pw", UserRoleType.USER,
true, LocalDateTime.now(), LocalDateTime.now()));
Chapter chapter = chapterRepository.save(new Chapter("Chapter 2", "Intro 2", null, null));

TheoryLesson lesson1 = lessonRepository.save(new TheoryLesson("L2", "Theory 2", LessonType.THEORY, chapter, null, null, "Text 1"));
Expand All @@ -65,7 +68,8 @@ void testFindByAccountAndState_shouldReturnCorrectList() {

@Test
void testFindByAccountAndLesson_shouldReturnEmptyWhenNone() {
Account account = accountRepository.save(new Account("user3", "user3@example.com", "pw", UserRoleType.USER, true));
Account account = accountRepository.save(new Account("user3", "user3@example.com", "pw", UserRoleType.USER,
true, LocalDateTime.now(), LocalDateTime.now()));
Chapter chapter = chapterRepository.save(new Chapter("Chapter X", "X", null, null));
TheoryLesson lesson = lessonRepository.save(new TheoryLesson("L4", "No progress", LessonType.THEORY, chapter, null, null, "Empty"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.test.context.ActiveProfiles;

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -24,7 +25,8 @@ class VerificationTokenRepositoryTests {

@Test
void testFindByTokenAndType_shouldReturnToken() {
Account account = new Account("user1", "test@example.com", "pass", null, true);
Account account = new Account("user1", "test@example.com", "pass", null,
true, LocalDateTime.now(), LocalDateTime.now());
accountRepository.save(account);

VerificationToken token = new VerificationToken(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
Expand Down Expand Up @@ -63,7 +64,8 @@ void setUp() {

@Test
void getAccountDetails_shouldReturnCorrectResponse() {
Account account = new Account("user", "user@example.com", "pass", UserRoleType.USER, true);
Account account = new Account("user", "user@example.com", "pass",
UserRoleType.USER, true, LocalDateTime.now(), LocalDateTime.now());
account.setId(UUID.randomUUID());
account.setProfileImageUrl("http://image.url");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,15 @@ void loginAccount_shouldThrow_whenAccountNotVerified() {
@Test
void refreshAccessToken_shouldReturnNewAccessToken_whenValid() {
RefreshTokenRequest request = new RefreshTokenRequest("refresh-token");
Account account = new Account();
account.setUsername("testuser");
account.setPassword("encoded");
account.setVerified(false);

when(jwtUtil.validateToken("refresh-token")).thenReturn(true);
when(jwtUtil.extractUsername("refresh-token")).thenReturn("testuser");
when(jwtUtil.generateAccessToken("testuser")).thenReturn("new-access-token");
when(accountRepository.findByUsername("testuser")).thenReturn(Optional.of(account));

TokenResponse response = authService.refreshAccessToken(request);

Expand Down