diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..aad394d07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +.idea/ + + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..87ad010fe --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..63287a14d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pronunciationApp.iml b/.idea/pronunciationApp.iml new file mode 100644 index 000000000..d6ebd4805 --- /dev/null +++ b/.idea/pronunciationApp.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/.idea/.gitignore b/backend/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/backend/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/backend/.idea/backend.iml b/backend/.idea/backend.iml new file mode 100644 index 000000000..d6ebd4805 --- /dev/null +++ b/backend/.idea/backend.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/backend/.idea/compiler.xml b/backend/.idea/compiler.xml new file mode 100644 index 000000000..564a55468 --- /dev/null +++ b/backend/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/.idea/encodings.xml b/backend/.idea/encodings.xml new file mode 100644 index 000000000..2c53f5a37 --- /dev/null +++ b/backend/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/.idea/jarRepositories.xml b/backend/.idea/jarRepositories.xml new file mode 100644 index 000000000..712ab9d98 --- /dev/null +++ b/backend/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/backend/.idea/misc.xml b/backend/.idea/misc.xml new file mode 100644 index 000000000..3534a7794 --- /dev/null +++ b/backend/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/.idea/modules.xml b/backend/.idea/modules.xml new file mode 100644 index 000000000..e066844ef --- /dev/null +++ b/backend/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/.idea/vcs.xml b/backend/.idea/vcs.xml new file mode 100644 index 000000000..6c0b86358 --- /dev/null +++ b/backend/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/pronunciationAppBack/.gitignore b/backend/pronunciationAppBack/.gitignore index 549e00a2a..aad394d07 100644 --- a/backend/pronunciationAppBack/.gitignore +++ b/backend/pronunciationAppBack/.gitignore @@ -18,6 +18,8 @@ target/ *.iws *.iml *.ipr +.idea/ + ### NetBeans ### /nbproject/private/ @@ -31,3 +33,4 @@ build/ ### VS Code ### .vscode/ + diff --git a/backend/pronunciationAppBack/pom.xml b/backend/pronunciationAppBack/pom.xml index 5963cf3b7..86715b863 100644 --- a/backend/pronunciationAppBack/pom.xml +++ b/backend/pronunciationAppBack/pom.xml @@ -12,7 +12,7 @@ pronunciationAppBack 0.0.1-SNAPSHOT pronunciationAppBack - Spring Boot for app pronunciatoin + Spring Boot for app pronunciation @@ -27,7 +27,7 @@ - 17 + 21 @@ -38,7 +38,11 @@ org.springframework.boot spring-boot-starter-web - + + org.springframework.boot + spring-boot-starter-validation + 3.4.2 + org.springframework.boot spring-boot-devtools @@ -58,13 +62,19 @@ org.projectlombok lombok - true + 1.18.30 + provided org.springframework.boot spring-boot-starter-test test + + com.github.javafaker + javafaker + 1.0.2 + @@ -79,7 +89,10 @@ lombok - + 21 + 21 + --enable-preview + org.springframework.boot diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/UserController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/UserController.java new file mode 100644 index 000000000..9ad8844b0 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/UserController.java @@ -0,0 +1,163 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.User; +import dev.pronunciationAppBack.repository.UserRepository; +import dev.pronunciationAppBack.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.http.HttpHeaders; + +import java.time.LocalDate; +import java.util.Date; +import java.util.List; + + +@RequestMapping("/api/v1/users") +@RestController +public class UserController { + + @Value("${endpoint.url.users}") + private String endpointUrlUsers; + + @Autowired + private UserService userService; + + @GetMapping("/show-endpoint") + public String showEndPointUsers() { + return "The Users endpoint URL is: " + endpointUrlUsers; + } + + @GetMapping("/allUsers") + public ResponseEntity> getAllUsers() { + List users = userService.getAllUsers(); + HttpHeaders headers = getCommonHeaders("Get all users"); + System.out.println("Number of users: " + users.size()); + if (users.isEmpty()) { + return new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } else { + return new ResponseEntity<>(users, headers, HttpStatus.OK); + } + } + + @PostMapping + public ResponseEntity createUser(@RequestBody User user) { + User createdUser = userService.createUser(user); + HttpHeaders headers = getCommonHeaders("Create a new user"); + return createdUser != null + ? new ResponseEntity<>(createdUser, headers, HttpStatus.CREATED) + : new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST); + } + + @PutMapping("/{id}") + public ResponseEntity updateUser(@PathVariable("id") String id, @RequestBody User user) { + User updatedUser = userService.updateUser(id, user); + HttpHeaders headers = getCommonHeaders("Update a user"); + return updatedUser != null + ? new ResponseEntity<>(updatedUser, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteUser(@PathVariable("id") String id) { + boolean deleted = userService.deleteUser(id); + HttpHeaders headers = getCommonHeaders("Delete a user"); + return deleted + ? new ResponseEntity<>(headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + @GetMapping("/{id}") + public ResponseEntity getUserById(@PathVariable("id") String id) { + User user = userService.getUserById(id); + HttpHeaders headers = getCommonHeaders("Get a user"); + return user != null + ? new ResponseEntity<>(user, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + // New endpoints for PRA02 + + @GetMapping("/active-users") + public ResponseEntity> getActiveUsers() { + List activeUsers = userService.getActiveUsers(); + HttpHeaders headers = getCommonHeaders("Get all active users"); + return activeUsers.isEmpty() ? ResponseEntity.noContent().headers(headers).build() + : ResponseEntity.ok().headers(headers).body(activeUsers); + } + + @GetMapping("/created-after") + public ResponseEntity> getUsersCreatedAtAfter(@RequestParam LocalDate date) { + + List users = userService.getUsersCreatedAtAfter(date); + HttpHeaders headers = getCommonHeaders("Get users created after a specific date"); + return users.isEmpty() ? ResponseEntity.noContent().headers(headers).build() + : ResponseEntity.ok().headers(headers).body(users); + } + + @GetMapping("/username/contains") + public ResponseEntity> getUserNameContains(@RequestParam String username) { + List users = userService.getUserNameContains(username); + HttpHeaders headers = getCommonHeaders("Get users by username containing"); + return users.isEmpty() ? ResponseEntity.noContent().headers(headers).build() + : ResponseEntity.ok().headers(headers).body(users); + + } + + @GetMapping("/email/contains") + public ResponseEntity> getUserEmailContains(@RequestParam String email) { + List users = userService.getUserEmailContains(email); + HttpHeaders headers = getCommonHeaders("Get users with email domain"); + return users.isEmpty() ? ResponseEntity.noContent().headers(headers).build() + : ResponseEntity.ok().headers(headers).body(users); + + } + + // + @GetMapping("/active/created-after") + public ResponseEntity> getActiveUsersCreatedAfter(@RequestParam LocalDate date) { + List users = userService.getActiveUsersCreatedAfter(date); + HttpHeaders headers = getCommonHeaders("Get active users created after a specific date"); + return users.isEmpty() ? ResponseEntity.noContent().headers(headers).build() + : ResponseEntity.ok().headers(headers).body(users); + + } + + @GetMapping("/email/{email}") + public ResponseEntity getUserByEmail(@PathVariable String email) { + User user = userService.getUserByEmail(email).orElse(null); + HttpHeaders headers = getCommonHeaders("Get a user by email"); + return user != null + ? new ResponseEntity<>(user, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + + @GetMapping("/username/{userName}") + public ResponseEntity getUserByUserName(@PathVariable String userName) { + User user = userService.getUserByUserName(userName).orElse(null); + HttpHeaders headers = getCommonHeaders("Get a user by username"); + return user != null + ? new ResponseEntity<>(user, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + @GetMapping("/count") + public ResponseEntity countUsers() { + return ResponseEntity.ok(userService.countUsers()); + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("description", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "H2 Database"); + headers.add("version", "1.0.0"); + headers.add("user-count", String.valueOf(userService.countUsers())); + headers.add("object", "users"); + return headers; + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java new file mode 100644 index 000000000..742ad0fb3 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java @@ -0,0 +1,96 @@ +package dev.pronunciationAppBack.controller; + + +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.service.WordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.Date; +import java.util.List; + + +@RequestMapping("/api/v1/words") +@RestController +public class WordController { + + @Value("${endpoint.url.words}") + private String endpointUrlWords; + + @Autowired + public WordService wordService; + + @GetMapping("/show-endpoint") + public String showEndPointWords() { + return "The Words endpoint URL is: " + endpointUrlWords; + } + + @GetMapping("/allWords") + public ResponseEntity> getAllWords() { + + List words = wordService.getAllWords(); + HttpHeaders headers = getCommonHeaders("Get all customers"); + System.out.println("Number of words: " + words.size()); + for (Word word : words) { + System.out.println("Word: " + word); + } + return !words.isEmpty() + ? new ResponseEntity<>(words, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + + } + + @PostMapping + public ResponseEntity createWord(@RequestBody Word word) { + Word createdWord = wordService.createWord(word); + HttpHeaders headers = getCommonHeaders("Create a new word"); + return createdWord != null + ? new ResponseEntity<>(createdWord, headers, HttpStatus.CREATED) + : new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST); + } + + @PutMapping("/{id}") + public ResponseEntity updateWord(@PathVariable("id") String id, @RequestBody Word word) { + Word updatedWord = wordService.updateWord(id, word); + HttpHeaders headers = getCommonHeaders("Update a word"); + return updatedWord != null + ? new ResponseEntity<>(updatedWord, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteWord(@PathVariable("id") String id) { + boolean deleted = wordService.deleteWord(id); + HttpHeaders headers = getCommonHeaders("Delete a word"); + return deleted + ? new ResponseEntity<>(headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + @GetMapping("/{id}") + public ResponseEntity getWordById(@PathVariable("id") String id) { + Word word = wordService.getWordById(id); + HttpHeaders headers = getCommonHeaders("Get a word"); + return word != null + ? new ResponseEntity<>(word, headers, HttpStatus.OK) + : new ResponseEntity<>(headers, HttpStatus.NOT_FOUND); + } + + private HttpHeaders getCommonHeaders(String description) { + HttpHeaders headers = new HttpHeaders(); + headers.add("description", description); + headers.add("content-type", "application/json"); + headers.add("date", new Date().toString()); + headers.add("server", "H2 Database"); + headers.add("version", "1.0.0"); + headers.add("word-count", String.valueOf(wordService.countWords())); + headers.add("object", "words"); + + return headers; + } + +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/User.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/User.java new file mode 100644 index 000000000..abbb762b0 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/User.java @@ -0,0 +1,100 @@ +package dev.pronunciationAppBack.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; +import jakarta.validation.constraints.Pattern; +import lombok.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.Email; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +@AllArgsConstructor +@NoArgsConstructor +//@Data +@Entity +@Table(name = "\"Users\"") +public class User { + @Id + private String id; + @NotBlank(message = "Username is required") + @Size(min = 3, max = 15) + private String userName; + @Email(message = "Invalid email format") + @NotBlank(message = "Email is required") + private String email; + @Size(min = 8, max = 20, message = "Password must be at least 8 characters long") + @Pattern( + regexp = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).*$", + message = "Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character") + private String password; + private boolean active; + private LocalDate createdAt; + + @JsonIgnore + @OneToMany(mappedBy = "userMapped", cascade = CascadeType.ALL, + fetch = FetchType.LAZY) + private List wordsMapped; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public LocalDate getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDate createdAt) { + this.createdAt = createdAt; + } + @Override + public String toString() { + return "Customer{" + + "id='" + id + '\'' + + "name: " + userName + "\n" + + "email: " + email + "\n" + + "active: " + active + "\n" + + "created at: " + createdAt + "\n" + + '}'; + } + +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java new file mode 100644 index 000000000..856f84831 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java @@ -0,0 +1,100 @@ +package dev.pronunciationAppBack.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.*; + +import java.util.UUID; + +//@Data +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "Words") +public class Word { + + @Id + private String id; + private String wordName; + private String definition; + private String phoneticSpelling; + private String sentence; + private boolean isActive; + private int level; + + @JsonIgnore + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "WORD_USER_FK_ID") + @JsonIgnoreProperties("wordsMapped") + private User userMapped; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getWordName() { + return wordName; + } + + public void setWordName(String wordName) { + this.wordName = wordName; + } + + public String getDefinition() { + return definition; + } + + public void setDefinition(String definition) { + this.definition = definition; + } + + public String getPhoneticSpelling() { + return phoneticSpelling; + } + + public void setPhoneticSpelling(String phoneticSpelling) { + this.phoneticSpelling = phoneticSpelling; + } + + public String getSentence() { + return sentence; + } + + public void setSentence(String sentence) { + this.sentence = sentence; + } + + public boolean isActive() { + return isActive; + } + + public void setActive(boolean active) { + isActive = active; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + @Override + public String toString() { + return "Word{" + + "id='" + id + '\'' + + ", wordName='" + wordName + '\'' + + ", definition='" + definition + '\'' + + ", phoneticSpelling='" + phoneticSpelling + '\'' + + ", sentence='" + sentence + '\'' + + ", isActive=" + isActive + + ", level=" + level + + '}'; + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/UserRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/UserRepository.java new file mode 100644 index 000000000..1e82eccc2 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/UserRepository.java @@ -0,0 +1,38 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + + +public interface UserRepository extends JpaRepository { + // Find active users + List findByActiveTrue(); + + // Find users created after a specific date + List findByCreatedAtAfter(LocalDate date); + + // Find users by username containing + List findByUserNameContaining(String username); + + // Count active users + long countByActiveTrue(); + + // Find users with email domain + List findByEmailContaining(String domain); + + // Complex query with multiple conditions + @Query("SELECT u FROM User u WHERE u.active = true AND u.createdAt > :date") + List findActiveUsersCreatedAfter( + @Param("date") LocalDate date + ); + + Optional findByEmail(String email); + + Optional findByUserName(String userName); +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java new file mode 100644 index 000000000..fa84d8e8c --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java @@ -0,0 +1,10 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Word; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + + +public interface WordRepository extends JpaRepository { +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserService.java new file mode 100644 index 000000000..5e35edf31 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserService.java @@ -0,0 +1,27 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.User; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + + +public interface UserService { + + List getAllUsers(); + User createUser(User user); + User getUserById(String id); + User updateUser(String id, User userDetails); + boolean deleteUser(String id); + long countUsers(); + // Adding new methods for complex queries + List getActiveUsers(); + List getUsersCreatedAtAfter(LocalDate date); + List getUserNameContains(String username); + List getUserEmailContains(String email); + List getActiveUsersCreatedAfter(LocalDate date); + Optional getUserByEmail(String email); + Optional getUserByUserName(String userName); +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserServiceImpl.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserServiceImpl.java new file mode 100644 index 000000000..bfccd67dd --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserServiceImpl.java @@ -0,0 +1,119 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.User; +import dev.pronunciationAppBack.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserRepository userRepository; + + public List getActiveUsers() { + return userRepository.findByActiveTrue(); + } + + @Override + public List getUsersCreatedAtAfter(LocalDate date) { + return userRepository.findByCreatedAtAfter(date); + } + + @Override + public List getUserNameContains(String username) { + return userRepository.findByUserNameContaining(username); + } + + @Override + public List getUserEmailContains(String email) { + return userRepository.findByEmailContaining(email); + } + + @Override + public List getActiveUsersCreatedAfter(LocalDate date) { + return userRepository.findActiveUsersCreatedAfter(date); + } + + @Override + public Optional getUserByEmail(String email) { + return userRepository.findByEmail(email); + } + + @Override + public Optional getUserByUserName(String userName) { + return userRepository.findByUserName(userName); + } + + public long countActiveUsers() { + return userRepository.countByActiveTrue(); + } + + @Override + public List getAllUsers() { + return userRepository.findAll(); + } + + @Override + public User createUser(User user) { + if (userRepository.findByEmail(user.getEmail()).isPresent()) { + throw new IllegalArgumentException("A user with this email already exists."); + } + if (userRepository.findByUserName(user.getUserName()).isPresent()) { + throw new IllegalArgumentException("A user with this username already exists."); + } + + user.setId(UUID.randomUUID().toString()); + return userRepository.save(user); + } + + @Override + public User getUserById(String id) { + return userRepository.findById(id).orElse(null); + } + + @Override + public User updateUser(String id, User userDetails) { + User user = userRepository.findById(id).orElse(null); + if (user != null) { + if (userDetails.getUserName() != null) { + user.setUserName(userDetails.getUserName()); + } + if (userDetails.getEmail() != null) { + user.setEmail(userDetails.getEmail()); + } + if (userDetails.getPassword() != null) { + user.setPassword(userDetails.getPassword()); + } + if (userDetails.isActive()) { + user.setActive(userDetails.isActive()); + } + if (userDetails.getCreatedAt() != null){ + user.setCreatedAt(userDetails.getCreatedAt()); + } + return userRepository.save(user); + } + return null; + } + + @Override + public boolean deleteUser(String id) { + if (userRepository.existsById(id)){ + userRepository.deleteById(id); + System.out.println("Deleted successfully!"); + return true; + } + System.out.println("ID doesn't exist!"); + return false; + } + + @Override + public long countUsers() { + return userRepository.count(); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java new file mode 100644 index 000000000..fb0f44cc6 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java @@ -0,0 +1,15 @@ +package dev.pronunciationAppBack.service; +import java.util.List; +import java.util.UUID; + +import dev.pronunciationAppBack.model.Word; + +public interface WordService { + List getAllWords(); + Word createWord(Word word); + Word getWordById(String id); + Word updateWord(String id, Word wordDetails); + boolean deleteWord(String id); + long countWords(); + +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordServiceImpl.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordServiceImpl.java new file mode 100644 index 000000000..a56e5abb9 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordServiceImpl.java @@ -0,0 +1,73 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.Word; +import dev.pronunciationAppBack.repository.WordRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; +@Service +public class WordServiceImpl implements WordService { + @Autowired + private WordRepository wordRepository; + + @Override + public List getAllWords() { + + return wordRepository.findAll(); + } + + @Override + public Word createWord(Word word) { + word.setId(UUID.randomUUID().toString()); + return wordRepository.save(word); + } + + @Override + public Word getWordById(String id) { + return wordRepository.findById(id).orElse(null); + } + + @Override + public Word updateWord(String id, Word wordDetails) { + Word word = wordRepository.findById(id).orElse(null); + if (word != null) { + if (wordDetails.getWordName() != null) { + word.setWordName(wordDetails.getWordName()); + } + if (wordDetails.getDefinition() != null){ + word.setDefinition(wordDetails.getDefinition()); + } + if (wordDetails.getPhoneticSpelling() != null){ + word.setPhoneticSpelling(wordDetails.getPhoneticSpelling()); + } + if (wordDetails.getSentence() != null){ + word.setSentence(wordDetails.getSentence()); + } + if (wordDetails.isActive()){ + word.setActive(wordDetails.isActive()); + } + if (wordDetails.getLevel() != 0){ + word.setLevel(wordDetails.getLevel()); + } + return wordRepository.save(word); + } + return null; + } + + @Override + public boolean deleteWord(String id) { + if (wordRepository.existsById(id)){ + wordRepository.deleteById(id); + System.out.println("Deleted successfully!"); + return true; + } + System.out.println("ID doesn't exist!"); + return false; + } + + public long countWords() { + return wordRepository.count(); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/utilities/UserFakerGenerator.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/utilities/UserFakerGenerator.java new file mode 100644 index 000000000..5bae480ab --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/utilities/UserFakerGenerator.java @@ -0,0 +1,35 @@ +package dev.pronunciationAppBack.utilities; + +import com.github.javafaker.Faker; +import dev.pronunciationAppBack.model.User; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Component +public class UserFakerGenerator { + + private final Faker faker = new Faker(); + + public User generateRandomUser() { + + User user = new User(); + user.setUserName(faker.name().username()); + user.setEmail(faker.internet().emailAddress()); + user.setPassword(faker.internet().password()); + user.setActive(faker.bool().bool()); + user.setCreatedAt(LocalDate.now()); + + // Additional fields if needed + return user; + } + + public List generateUsers(int count) { + return IntStream.range(0, count) + .mapToObj(i -> generateRandomUser()) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/resources/application.properties b/backend/pronunciationAppBack/src/main/resources/application.properties index edb9ea0d2..8642bf434 100644 --- a/backend/pronunciationAppBack/src/main/resources/application.properties +++ b/backend/pronunciationAppBack/src/main/resources/application.properties @@ -1 +1,31 @@ spring.application.name=pronunciationAppBack + +# H2 DATABASE SERVER +spring.datasource.driverClassName=org.h2.Driver +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true + +# H2 IN MEMORY +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.username=sa +spring.datasource.password= + +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=create-drop + +#spring.datasource.url=jdbc:postgresql://localhost:5432/pronunciation_db +#spring.datasource.username=albertemb +#spring.datasource.password=1234 +#spring.datasource.driver-class-name=org.postgresql.Driver + +# Config of JPA/Hibernate +#spring.jpa.hibernate.ddl-auto=update +#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + +# ENDPOINTS +# Import .env file +spring.config.import=optional:file:.env +# import from environment variables +#endpoint.url.users=${ENDPOINT_URL_USERS} +endpoint.url.users=/api/v1/users/ +endpoint.url.words=/api/v1/words/ \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/UserControllerIntegrationTest.java b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/UserControllerIntegrationTest.java new file mode 100644 index 000000000..4e5378a68 --- /dev/null +++ b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/UserControllerIntegrationTest.java @@ -0,0 +1,67 @@ +package dev.pronunciationAppBack; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.pronunciationAppBack.model.User; +import dev.pronunciationAppBack.repository.UserRepository; +import dev.pronunciationAppBack.utilities.UserFakerGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.ResultMatcher; + +import java.util.List; + +import static java.nio.file.Paths.get; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.post; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class UserControllerIntegrationTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private UserRepository userRepository; + + @Autowired + UserFakerGenerator userFakerGenerator; + + @Test + public void testGenerateUsers() { + List users = userFakerGenerator.generateUsers(5); + + assertNotNull(users); + assertEquals(5, users.size()); + users.forEach(System.out::println); + } + + @Test + void testGetAllUsers() throws Exception { + + mockMvc.perform((RequestBuilder) get("/api/users")) + .andExpect(status().isOk()) + .andExpect((ResultMatcher) content().contentType(MediaType.APPLICATION_JSON)); + } + + @Test + void testCreateUser() throws Exception { + User user = new User(); + user.setUserName("newUser"); + user.setEmail("newuser@example.com"); + + mockMvc.perform((RequestBuilder) post("/api/users") + .contentType(MediaType.APPLICATION_JSON) + .contentType(MediaType.valueOf(new ObjectMapper().writeValueAsString(user)))) + .andExpect(status().isOk()) + .andExpect((ResultMatcher) jsonPath("$.username").value("newuser")); + } +} diff --git a/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/UserServiceUnitTest.java b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/UserServiceUnitTest.java new file mode 100644 index 000000000..c2bf8cad2 --- /dev/null +++ b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/UserServiceUnitTest.java @@ -0,0 +1,37 @@ +package dev.pronunciationAppBack; + +import dev.pronunciationAppBack.model.User; +import dev.pronunciationAppBack.repository.UserRepository; +import dev.pronunciationAppBack.service.UserService; +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 static org.mockito.ArgumentMatchers.any; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserServiceUnitTest { + @Mock + private UserRepository userRepository; + + @InjectMocks + private UserService userService; + + @Test + void testCreateUser() { + User user = new User(); + user.setUserName("testuser"); + + when(userRepository.save(any(User.class))).thenReturn(user); + + User createdUser = userService.createUser(user); + + assertNotNull(createdUser); + assertEquals("testuser", createdUser.getUserName()); + } +} \ No newline at end of file diff --git a/backend/resources/CreateSpringBootproject.md b/backend/resources/create project/CreateSpringBootproject.md similarity index 100% rename from backend/resources/CreateSpringBootproject.md rename to backend/resources/create project/CreateSpringBootproject.md diff --git a/backend/resources/create project/create-spring-boot-b.png b/backend/resources/create project/create-spring-boot-b.png new file mode 100644 index 000000000..7230e9fdd Binary files /dev/null and b/backend/resources/create project/create-spring-boot-b.png differ diff --git a/backend/resources/create project/create-spring-boot.png b/backend/resources/create project/create-spring-boot.png new file mode 100644 index 000000000..415b8d1dd Binary files /dev/null and b/backend/resources/create project/create-spring-boot.png differ diff --git a/backend/resources/pronunciationAppBack-v0.0-project-structure.png b/backend/resources/create project/pronunciationAppBack-v0.0-project-structure.png similarity index 100% rename from backend/resources/pronunciationAppBack-v0.0-project-structure.png rename to backend/resources/create project/pronunciationAppBack-v0.0-project-structure.png diff --git a/backend/resources/start-spring-io-create-project.png b/backend/resources/create project/pronunciationAppBack-v0.0-spring-io-create-project.png similarity index 100% rename from backend/resources/start-spring-io-create-project.png rename to backend/resources/create project/pronunciationAppBack-v0.0-spring-io-create-project.png diff --git a/backend/resources/pronunciationAppBack-v0.0-api-rest-words.png b/backend/resources/pronunciationAppBack-v0.0-api-rest-words.png new file mode 100644 index 000000000..43e002693 Binary files /dev/null and b/backend/resources/pronunciationAppBack-v0.0-api-rest-words.png differ diff --git a/backend/resources/pronunciationAppBack-v0.0-db-2.png b/backend/resources/pronunciationAppBack-v0.0-db-2.png new file mode 100644 index 000000000..c6672f82a Binary files /dev/null and b/backend/resources/pronunciationAppBack-v0.0-db-2.png differ diff --git a/backend/resources/pronunciationAppBack-v0.0-db.png b/backend/resources/pronunciationAppBack-v0.0-db.png new file mode 100644 index 000000000..2a496ab0a Binary files /dev/null and b/backend/resources/pronunciationAppBack-v0.0-db.png differ