diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d50ec77fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.idea +pronunciationDB diff --git a/backend/pronunciationAppBack/README.md b/backend/pronunciationAppBack/README.md new file mode 100644 index 000000000..92e5a633b --- /dev/null +++ b/backend/pronunciationAppBack/README.md @@ -0,0 +1,51 @@ + + +# Descripción +Este PR introduce mejoras en la estructura de datos de la aplicación Spring Boot, enfocándose en la optimización del modelo de entidades y la correcta implementación de relaciones en JPA. Se han creado las relaciones One-to-One, Many-to-Many, One-to-Many y Many-to-One. Además, se han implementado pruebas para validar el correcto funcionamiento del sistema. + + +1. **Revisar y Mejorar Model v0.2** + - Se ha analizado el diagrama de clases proporcionado. + - Se han identificado mejoras o relaciones faltantes. + - Se ha actualizado el modelo según sea necesario. + +2. **Implementar One-to-One: User y GameProgress** + - Se ha creado una relación bidireccional **One-to-One**. + - Se ha definido a **User** como el lado propietario de la relación. + - Se han utilizado las anotaciones adecuadas de JPA (`@OneToOne`, `@JoinColumn`). + +3. **Crear Many-to-Many: Word y Category** + - Se ha implementado una relación **Many-to-Many**. + - Se ha creado una tabla de unión utilizando la anotación `@JoinTable`. + - Se ha configurado la relación bidireccional si era necesario. + +4. **Implementar One-to-Many y Many-to-One Relationships** + - Se han identificado e implementado todas las relaciones **One-to-Many** y **Many-to-One** a partir del diagrama de clases. + - Se han utilizado las anotaciones correspondientes (`@OneToMany`, `@ManyToOne`). + - Se han configurado los tipos de cascada y estrategias de recuperación adecuadas. + +5. **Configurar Anotaciones de JPA** + - Se han asegurado que todas las entidades tengan las anotaciones adecuadas de JPA. + - Se ha configurado `@Id, @GeneratedValue` para las claves primarias. + - Se han utilizado `@Column` para configuraciones específicas de las columnas. + +6. **Crear Interfaces de Repositorio** + - Se han desarrollado interfaces `JpaRepository` para cada entidad. + - Se han añadido métodos de consulta personalizados si era necesario. + +7. **Implementar Métodos Básicos de Servicio** + - Se han creado clases de servicio para cada entidad. + - Se han implementado operaciones **CRUD** en la capa de servicio. + +8. **Probar Relaciones** + - Se ha creado una clase de prueba para poblar la base de datos con datos de muestra. + - Se ha verificado que todas las relaciones funcionan correctamente. + - Se han probado las operaciones en cascada y las estrategias de recuperación. + +--- + +## Capturas + +### **Análisis y Mejora del Modelo** +> **Diagrama de clases actualizado** +![Class Diagram](screenshots/newModelUml.png) \ No newline at end of file diff --git a/backend/pronunciationAppBack/pom.xml b/backend/pronunciationAppBack/pom.xml index 5963cf3b7..911caae8a 100644 --- a/backend/pronunciationAppBack/pom.xml +++ b/backend/pronunciationAppBack/pom.xml @@ -34,6 +34,11 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.projectlombok + lombok + true + org.springframework.boot spring-boot-starter-web @@ -65,6 +70,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-validation + diff --git a/backend/pronunciationAppBack/screenshots/bdh2.png b/backend/pronunciationAppBack/screenshots/bdh2.png new file mode 100644 index 000000000..6d8b7c1f6 Binary files /dev/null and b/backend/pronunciationAppBack/screenshots/bdh2.png differ diff --git a/backend/pronunciationAppBack/screenshots/createuser.png b/backend/pronunciationAppBack/screenshots/createuser.png new file mode 100644 index 000000000..cadf44ca1 Binary files /dev/null and b/backend/pronunciationAppBack/screenshots/createuser.png differ diff --git a/backend/pronunciationAppBack/screenshots/deleteuser.png b/backend/pronunciationAppBack/screenshots/deleteuser.png new file mode 100644 index 000000000..b31613b19 Binary files /dev/null and b/backend/pronunciationAppBack/screenshots/deleteuser.png differ diff --git a/backend/pronunciationAppBack/screenshots/getallusers.png b/backend/pronunciationAppBack/screenshots/getallusers.png new file mode 100644 index 000000000..90c84ad81 Binary files /dev/null and b/backend/pronunciationAppBack/screenshots/getallusers.png differ diff --git a/backend/pronunciationAppBack/screenshots/getuserbyid.png b/backend/pronunciationAppBack/screenshots/getuserbyid.png new file mode 100644 index 000000000..371cdcd7e Binary files /dev/null and b/backend/pronunciationAppBack/screenshots/getuserbyid.png differ diff --git a/backend/pronunciationAppBack/screenshots/newModelUml.png b/backend/pronunciationAppBack/screenshots/newModelUml.png new file mode 100644 index 000000000..d096c4ae0 Binary files /dev/null and b/backend/pronunciationAppBack/screenshots/newModelUml.png differ diff --git a/backend/pronunciationAppBack/screenshots/updateuser.png b/backend/pronunciationAppBack/screenshots/updateuser.png new file mode 100644 index 000000000..03f1bb28b Binary files /dev/null and b/backend/pronunciationAppBack/screenshots/updateuser.png differ diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/AttemptController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/AttemptController.java new file mode 100644 index 000000000..73915b096 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/AttemptController.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/attempts") +public class AttemptController { + + @Autowired + private AttemptService attemptService; + + @PostMapping + public ResponseEntity createAttempt(@RequestBody Attempt attempt) { + Attempt createdAttempt = attemptService.createAttempt(attempt); + return ResponseEntity.ok(createdAttempt); + } + + @GetMapping("/{id}") + public ResponseEntity getAttemptById(@PathVariable Long id) { + Optional attempt = attemptService.getAttemptById(id); + return attempt.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllAttempts() { + List attempts = attemptService.getAllAttempts(); + return ResponseEntity.ok(attempts); + } + + @PutMapping("/{id}") + public ResponseEntity updateAttempt(@PathVariable Long id, @RequestBody Attempt attemptDetails) { + Attempt updatedAttempt = attemptService.updateAttempt(id, attemptDetails); + if (updatedAttempt != null) { + return ResponseEntity.ok(updatedAttempt); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteAttempt(@PathVariable Long id) { + attemptService.deleteAttempt(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/CategoryController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/CategoryController.java new file mode 100644 index 000000000..4af479deb --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/CategoryController.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/categories") +public class CategoryController { + + @Autowired + private CategoryService categoryService; + + @PostMapping + public ResponseEntity createCategory(@RequestBody Category category) { + Category createdCategory = categoryService.createCategory(category); + return ResponseEntity.ok(createdCategory); + } + + @GetMapping("/{id}") + public ResponseEntity getCategoryById(@PathVariable Long id) { + Optional category = categoryService.getCategoryById(id); + return category.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllCategories() { + List categories = categoryService.getAllCategories(); + return ResponseEntity.ok(categories); + } + + @PutMapping("/{id}") + public ResponseEntity updateCategory(@PathVariable Long id, @RequestBody Category categoryDetails) { + Category updatedCategory = categoryService.updateCategory(id, categoryDetails); + if (updatedCategory != null) { + return ResponseEntity.ok(updatedCategory); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCategory(@PathVariable Long id) { + categoryService.deleteCategory(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/GameProgressController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/GameProgressController.java new file mode 100644 index 000000000..f589c8d5f --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/GameProgressController.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/gameprogresses") +public class GameProgressController { + + @Autowired + private GameProgressService gameProgressService; + + @PostMapping + public ResponseEntity createGameProgress(@RequestBody GameProgress gameProgress) { + GameProgress createdGameProgress = gameProgressService.createGameProgress(gameProgress); + return ResponseEntity.ok(createdGameProgress); + } + + @GetMapping("/{id}") + public ResponseEntity getGameProgressById(@PathVariable Long id) { + Optional gameProgress = gameProgressService.getGameProgressById(id); + return gameProgress.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllGameProgresses() { + List gameProgresses = gameProgressService.getAllGameProgresses(); + return ResponseEntity.ok(gameProgresses); + } + + @PutMapping("/{id}") + public ResponseEntity updateGameProgress(@PathVariable Long id, @RequestBody GameProgress gameProgressDetails) { + GameProgress updatedGameProgress = gameProgressService.updateGameProgress(id, gameProgressDetails); + if (updatedGameProgress != null) { + return ResponseEntity.ok(updatedGameProgress); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteGameProgress(@PathVariable Long id) { + gameProgressService.deleteGameProgress(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/LevelController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/LevelController.java new file mode 100644 index 000000000..e8781a5ed --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/LevelController.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/levels") +public class LevelController { + + @Autowired + private LevelService levelService; + + @PostMapping + public ResponseEntity createLevel(@RequestBody Level level) { + Level createdLevel = levelService.createLevel(level); + return ResponseEntity.ok(createdLevel); + } + + @GetMapping("/{id}") + public ResponseEntity getLevelById(@PathVariable Long id) { + Optional level = levelService.getLevelById(id); + return level.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllLevels() { + List levels = levelService.getAllLevels(); + return ResponseEntity.ok(levels); + } + + @PutMapping("/{id}") + public ResponseEntity updateLevel(@PathVariable Long id, @RequestBody Level levelDetails) { + Level updatedLevel = levelService.updateLevel(id, levelDetails); + if (updatedLevel != null) { + return ResponseEntity.ok(updatedLevel); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteLevel(@PathVariable Long id) { + levelService.deleteLevel(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/PronunciationController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/PronunciationController.java new file mode 100644 index 000000000..359a5f971 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/PronunciationController.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/pronunciations") +public class PronunciationController { + + @Autowired + private PronunciationService pronunciationService; + + @PostMapping + public ResponseEntity createPronunciation(@RequestBody Pronunciation pronunciation) { + Pronunciation createdPronunciation = pronunciationService.createPronunciation(pronunciation); + return ResponseEntity.ok(createdPronunciation); + } + + @GetMapping("/{id}") + public ResponseEntity getPronunciationById(@PathVariable Long id) { + Optional pronunciation = pronunciationService.getPronunciationById(id); + return pronunciation.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllPronunciations() { + List pronunciations = pronunciationService.getAllPronunciations(); + return ResponseEntity.ok(pronunciations); + } + + @PutMapping("/{id}") + public ResponseEntity updatePronunciation(@PathVariable Long id, @RequestBody Pronunciation pronunciationDetails) { + Pronunciation updatedPronunciation = pronunciationService.updatePronunciation(id, pronunciationDetails); + if (updatedPronunciation != null) { + return ResponseEntity.ok(updatedPronunciation); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deletePronunciation(@PathVariable Long id) { + pronunciationService.deletePronunciation(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/StageController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/StageController.java new file mode 100644 index 000000000..c6df5255f --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/StageController.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/stages") +public class StageController { + + @Autowired + private StageService stageService; + + @PostMapping + public ResponseEntity createStage(@RequestBody Stage stage) { + Stage createdStage = stageService.createStage(stage); + return ResponseEntity.ok(createdStage); + } + + @GetMapping("/{id}") + public ResponseEntity getStageById(@PathVariable Long id) { + Optional stage = stageService.getStageById(id); + return stage.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllStages() { + List stages = stageService.getAllStages(); + return ResponseEntity.ok(stages); + } + + @PutMapping("/{id}") + public ResponseEntity updateStage(@PathVariable Long id, @RequestBody Stage stageDetails) { + Stage updatedStage = stageService.updateStage(id, stageDetails); + if (updatedStage != null) { + return ResponseEntity.ok(updatedStage); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteStage(@PathVariable Long id) { + stageService.deleteStage(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/StageWordsController.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/StageWordsController.java new file mode 100644 index 000000000..af501d22a --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/StageWordsController.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/stagewords") +public class StageWordsController { + + @Autowired + private StageWordsService stageWordsService; + + @PostMapping + public ResponseEntity createStageWords(@RequestBody StageWords stageWords) { + StageWords createdStageWords = stageWordsService.createStageWords(stageWords); + return ResponseEntity.ok(createdStageWords); + } + + @GetMapping("/{id}") + public ResponseEntity getStageWordsById(@PathVariable Long id) { + Optional stageWords = stageWordsService.getStageWordsById(id); + return stageWords.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllStageWords() { + List stageWords = stageWordsService.getAllStageWords(); + return ResponseEntity.ok(stageWords); + } + + @PutMapping("/{id}") + public ResponseEntity updateStageWords(@PathVariable Long id, @RequestBody StageWords stageWordsDetails) { + StageWords updatedStageWords = stageWordsService.updateStageWords(id, stageWordsDetails); + if (updatedStageWords != null) { + return ResponseEntity.ok(updatedStageWords); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteStageWords(@PathVariable Long id) { + stageWordsService.deleteStageWords(id); + return ResponseEntity.noContent().build(); + } +} 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..26a9ac70b --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/UserController.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/users") +public class UserController { + + @Autowired + private UserService userService; + + @PostMapping + public ResponseEntity createUser(@RequestBody User user) { + User createdUser = userService.createUser(user); + return ResponseEntity.ok(createdUser); + } + + @GetMapping("/{id}") + public ResponseEntity getUserById(@PathVariable Long id) { + Optional user = userService.getUserById(id); + return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllUsers() { + List users = userService.getAllUsers(); + return ResponseEntity.ok(users); + } + + @PutMapping("/{id}") + public ResponseEntity updateUser(@PathVariable Long id, @RequestBody User userDetails) { + User updatedUser = userService.updateUser(id, userDetails); + if (updatedUser != null) { + return ResponseEntity.ok(updatedUser); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteUser(@PathVariable Long id) { + userService.deleteUser(id); + return ResponseEntity.noContent().build(); + } +} 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..225940d19 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/controller/WordController.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.controller; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.service.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/words") +public class WordController { + + @Autowired + private WordService wordService; + + @PostMapping + public ResponseEntity createWord(@RequestBody Word word) { + Word createdWord = wordService.createWord(word); + return ResponseEntity.ok(createdWord); + } + + @GetMapping("/{id}") + public ResponseEntity getWordById(@PathVariable Long id) { + Optional word = wordService.getWordById(id); + return word.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping + public ResponseEntity> getAllWords() { + List words = wordService.getAllWords(); + return ResponseEntity.ok(words); + } + + @PutMapping("/{id}") + public ResponseEntity updateWord(@PathVariable Long id, @RequestBody Word wordDetails) { + Word updatedWord = wordService.updateWord(id, wordDetails); + if (updatedWord != null) { + return ResponseEntity.ok(updatedWord); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteWord(@PathVariable Long id) { + wordService.deleteWord(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Attempt.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Attempt.java new file mode 100644 index 000000000..9457994cf --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Attempt.java @@ -0,0 +1,34 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.Data; +import java.util.Date; + +@Entity +@Data +public class Attempt { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, updatable = false) + private Long id; + + @Column(name = "user_id", insertable = false, updatable = false) + private String userId; + + @Column(name = "word_id", insertable = false, updatable = false) + private String wordId; + + @Column(name = "timestamp", nullable = false) + private Date timestamp; + + @Column(name = "score", nullable = false) + private float score; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne + @JoinColumn(name = "word_id") + private Word word; +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Category.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Category.java new file mode 100644 index 000000000..219e220ff --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Category.java @@ -0,0 +1,49 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.Data; +import java.util.List; +import java.util.ArrayList; + +@Entity +@Data +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, updatable = false) + private Long id; + + @Column(name = "category_name", nullable = false) + private String categoryName; + + @Column(name = "sub_category_name", nullable = false) + private String subCategoryName; + + @Column(name = "description", nullable = false) + private String description; + + @Column(name = "word_count", nullable = false) + private int wordCount; + + @ManyToMany(mappedBy = "categories") + private List words; + + @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List levels = new ArrayList<>(); + + public String getName() { + return categoryName; + } + + public void setName(String name) { + this.categoryName = name; + } + + public List getLevels() { + return levels; + } + + public void setLevels(List levels) { + this.levels = levels; + } +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/GameProgress.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/GameProgress.java new file mode 100644 index 000000000..811957ef4 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/GameProgress.java @@ -0,0 +1,42 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.Data; +import java.util.Date; + +@Entity +@Data +public class GameProgress { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, updatable = false) + private Long id; + + @Column(name = "current_score", nullable = false) + private int currentScore; + + @Column(name = "current_stage", nullable = false) + private int currentStage; + + @Column(name = "last_played_date", nullable = false) + private Date lastPlayedDate; + + @Column(name = "words_learned", nullable = false) + private int wordsLearned; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false) + private Status status; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne + @JoinColumn(name = "stage_id") + private Stage stage; + + public enum Status { + IN_PROGRESS, COMPLETED, NOT_STARTED + } +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Level.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Level.java new file mode 100644 index 000000000..ef43a914e --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Level.java @@ -0,0 +1,52 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.Data; +import java.util.List; + +@Entity +@Data +public class Level { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, updatable = false) + private Long id; + + @Column(name = "number", nullable = false) + private int number; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "required_score", nullable = false) + private int requiredScore; + + @Column(name = "is_blocked", nullable = false) + private boolean isBlocked; + + @OneToMany(mappedBy = "level", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List words; + + @ManyToOne + @JoinColumn(name = "category_id") + private Category category; + + @OneToMany(mappedBy = "level", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List stages; + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public List getStages() { + return stages; + } + + public void setStages(List stages) { + this.stages = stages; + } +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Pronunciation.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Pronunciation.java new file mode 100644 index 000000000..02ffa93ff --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Pronunciation.java @@ -0,0 +1,43 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.Data; + +@Entity +@Data +public class Pronunciation { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, updatable = false) + private Long id; + + @Column(name = "audio_name", nullable = false) + private String audioName; + + @Column(name = "audio_size", nullable = false) + private int audioSize; + + @Column(name = "audio_url", nullable = false) + private String audioUrl; + + @Column(name = "phonetic_spelling", nullable = false) + private String phoneticSpelling; + + @Column(name = "speaker_gender", nullable = false) + private String speakerGender; + + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + private Type type; + + @Column(name = "accuracy_score", nullable = false) + private float accuracyScore; + + @ManyToOne + @JoinColumn(name = "word_id") + private Word word; + + public enum Type { + STANDARD, SLOW, PHONETIC + } +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Stage.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Stage.java new file mode 100644 index 000000000..391d27fba --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Stage.java @@ -0,0 +1,63 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.Data; +import java.util.List; + +@Entity +@Data +public class Stage { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, updatable = false) + private Long id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "avatar_url", nullable = false) + private String avatarUrl; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false) + private Status status; + + @Column(name = "progress", nullable = false) + private int progress; + + @Column(name = "current_score", nullable = false) + private int currentScore; + + @OneToMany(mappedBy = "stage", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List gameProgresses; + + @OneToMany(mappedBy = "stage", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List stageWords; + + @OneToMany(mappedBy = "stage", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List words; + + @ManyToOne + @JoinColumn(name = "level_id") + private Level level; + + public Level getLevel() { + return level; + } + + public void setLevel(Level level) { + this.level = level; + } + + public List getWords() { + return words; + } + + public void setWords(List words) { + this.words = words; + } + + public enum Status { + LOCKED, UNLOCKED, COMPLETED + } +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/StageWords.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/StageWords.java new file mode 100644 index 000000000..c549f2186 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/StageWords.java @@ -0,0 +1,33 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.Data; +import java.util.Date; + +@Entity +@Data +public class StageWords { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, updatable = false) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false) + private Status status; + + @Column(name = "last_update_date_time", nullable = false) + private Date lastUpdateDateTime; + + @ManyToOne + @JoinColumn(name = "stage_id") + private Stage stage; + + @ManyToOne + @JoinColumn(name = "word_id") + private Word word; + + public enum Status { + LEARNED, IN_PROGRESS, PENDING + } +} \ No newline at end of file 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..ddd4919f8 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/User.java @@ -0,0 +1,46 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.Data; +import java.util.List; + +@Entity +@Data +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, updatable = false) + private Long id; + + @Column(name = "username", nullable = false) + private String username; + + @Column(name = "age", nullable = false) + private int age; + + @Column(name = "email", nullable = false) + private String email; + + @Column(name = "total_score", nullable = false) + private int totalScore; + + @Column(name = "is_active", nullable = false) + private boolean isActive; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List gameProgresses; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List attempts; + + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL) + private GameProgress gameProgress; + + public String getName() { + return username; + } + + public void setName(String name) { + this.username = name; + } +} \ No newline at end of file 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..bed5c434a --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/model/Word.java @@ -0,0 +1,62 @@ +package dev.pronunciationAppBack.model; + +import jakarta.persistence.*; +import lombok.Data; +import java.util.List; + +@Entity +@Data +public class Word { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, updatable = false) + private Long id; + + @Column(name = "text", nullable = false) + private String text; + + @Column(name = "description", nullable = false) + private String description; + + @Column(name = "sentence", nullable = false) + private String sentence; + + @Column(name = "difficulty", nullable = false) + private int difficulty; + + @Column(name = "is_common", nullable = false) + private boolean isCommon; + + @OneToMany(mappedBy = "word", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List stageWords; + + @OneToMany(mappedBy = "word", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List pronunciations; + + @OneToMany(mappedBy = "word", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List attempts; + + @ManyToOne + @JoinColumn(name = "level_id") + private Level level; + + @ManyToMany + @JoinTable( + name = "word_category", + joinColumns = @JoinColumn(name = "word_id"), + inverseJoinColumns = @JoinColumn(name = "category_id") + ) + private List categories; + + @ManyToOne + @JoinColumn(name = "stage_id") + private Stage stage; + + public Stage getStage() { + return stage; + } + + public void setStage(Stage stage) { + this.stage = stage; + } +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/AttemptRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/AttemptRepository.java new file mode 100644 index 000000000..2ecf0e617 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/AttemptRepository.java @@ -0,0 +1,8 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Attempt; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AttemptRepository extends JpaRepository { + // Add custom query methods if required +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/CategoryRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/CategoryRepository.java new file mode 100644 index 000000000..3b1f5a72c --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/CategoryRepository.java @@ -0,0 +1,8 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CategoryRepository extends JpaRepository { + // Add custom query methods if required +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/GameProgressRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/GameProgressRepository.java new file mode 100644 index 000000000..4540576b9 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/GameProgressRepository.java @@ -0,0 +1,8 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.GameProgress; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GameProgressRepository extends JpaRepository { + // Add custom query methods if required +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/LevelRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/LevelRepository.java new file mode 100644 index 000000000..dc4a2c678 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/LevelRepository.java @@ -0,0 +1,8 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Level; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LevelRepository extends JpaRepository { + // Add custom query methods if required +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/PronunciationRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/PronunciationRepository.java new file mode 100644 index 000000000..0db48a2a2 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/PronunciationRepository.java @@ -0,0 +1,8 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Pronunciation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PronunciationRepository extends JpaRepository { + // Add custom query methods if required +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/StageRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/StageRepository.java new file mode 100644 index 000000000..f33c96ac3 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/StageRepository.java @@ -0,0 +1,8 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Stage; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StageRepository extends JpaRepository { + // Add custom query methods if required +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/StageWordsRepository.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/StageWordsRepository.java new file mode 100644 index 000000000..130ba3f60 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/StageWordsRepository.java @@ -0,0 +1,8 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.StageWords; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StageWordsRepository extends JpaRepository { + // Add custom query methods if required +} \ No newline at end of file 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..45aaae32b --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/UserRepository.java @@ -0,0 +1,8 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + // Add custom query methods if required +} \ No newline at end of file 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..e4d4722fb --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/repository/WordRepository.java @@ -0,0 +1,8 @@ +package dev.pronunciationAppBack.repository; + +import dev.pronunciationAppBack.model.Word; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface WordRepository extends JpaRepository { + // Add custom query methods if required +} \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/AttemptService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/AttemptService.java new file mode 100644 index 000000000..b43075347 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/AttemptService.java @@ -0,0 +1,46 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.Attempt; +import dev.pronunciationAppBack.repository.AttemptRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class AttemptService { + + @Autowired + private AttemptRepository attemptRepository; + + public Attempt createAttempt(Attempt attempt) { + return attemptRepository.save(attempt); + } + + public Optional getAttemptById(Long id) { + return attemptRepository.findById(id); + } + + public List getAllAttempts() { + return attemptRepository.findAll(); + } + + public Attempt updateAttempt(Long id, Attempt attemptDetails) { + Optional optionalAttempt = attemptRepository.findById(id); + if (optionalAttempt.isPresent()) { + Attempt attempt = optionalAttempt.get(); + attempt.setUser(attemptDetails.getUser()); + attempt.setWord(attemptDetails.getWord()); + attempt.setTimestamp(attemptDetails.getTimestamp()); + attempt.setScore(attemptDetails.getScore()); + return attemptRepository.save(attempt); + } else { + return null; + } + } + + public void deleteAttempt(Long id) { + attemptRepository.deleteById(id); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/CategoryService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/CategoryService.java new file mode 100644 index 000000000..d57281e0c --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/CategoryService.java @@ -0,0 +1,46 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.Category; +import dev.pronunciationAppBack.repository.CategoryRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class CategoryService { + + @Autowired + private CategoryRepository categoryRepository; + + public Category createCategory(Category category) { + return categoryRepository.save(category); + } + + public Optional getCategoryById(Long id) { + return categoryRepository.findById(id); + } + + public List getAllCategories() { + return categoryRepository.findAll(); + } + + public Category updateCategory(Long id, Category categoryDetails) { + Optional optionalCategory = categoryRepository.findById(id); + if (optionalCategory.isPresent()) { + Category category = optionalCategory.get(); + category.setCategoryName(categoryDetails.getCategoryName()); + category.setSubCategoryName(categoryDetails.getSubCategoryName()); + category.setDescription(categoryDetails.getDescription()); + category.setWordCount(categoryDetails.getWordCount()); + return categoryRepository.save(category); + } else { + return null; + } + } + + public void deleteCategory(Long id) { + categoryRepository.deleteById(id); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/GameProgressService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/GameProgressService.java new file mode 100644 index 000000000..5f1bd69c1 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/GameProgressService.java @@ -0,0 +1,49 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.GameProgress; +import dev.pronunciationAppBack.repository.GameProgressRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class GameProgressService { + + @Autowired + private GameProgressRepository gameProgressRepository; + + public GameProgress createGameProgress(GameProgress gameProgress) { + return gameProgressRepository.save(gameProgress); + } + + public Optional getGameProgressById(Long id) { + return gameProgressRepository.findById(id); + } + + public List getAllGameProgresses() { + return gameProgressRepository.findAll(); + } + + public GameProgress updateGameProgress(Long id, GameProgress gameProgressDetails) { + Optional optionalGameProgress = gameProgressRepository.findById(id); + if (optionalGameProgress.isPresent()) { + GameProgress gameProgress = optionalGameProgress.get(); + gameProgress.setCurrentScore(gameProgressDetails.getCurrentScore()); + gameProgress.setCurrentStage(gameProgressDetails.getCurrentStage()); + gameProgress.setLastPlayedDate(gameProgressDetails.getLastPlayedDate()); + gameProgress.setWordsLearned(gameProgressDetails.getWordsLearned()); + gameProgress.setStatus(gameProgressDetails.getStatus()); + gameProgress.setUser(gameProgressDetails.getUser()); + gameProgress.setStage(gameProgressDetails.getStage()); + return gameProgressRepository.save(gameProgress); + } else { + return null; + } + } + + public void deleteGameProgress(Long id) { + gameProgressRepository.deleteById(id); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/LevelService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/LevelService.java new file mode 100644 index 000000000..60745396f --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/LevelService.java @@ -0,0 +1,47 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.Level; +import dev.pronunciationAppBack.repository.LevelRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class LevelService { + + @Autowired + private LevelRepository levelRepository; + + public Level createLevel(Level level) { + return levelRepository.save(level); + } + + public Optional getLevelById(Long id) { + return levelRepository.findById(id); + } + + public List getAllLevels() { + return levelRepository.findAll(); + } + + public Level updateLevel(Long id, Level levelDetails) { + Optional optionalLevel = levelRepository.findById(id); + if (optionalLevel.isPresent()) { + Level level = optionalLevel.get(); + level.setNumber(levelDetails.getNumber()); + level.setName(levelDetails.getName()); + level.setRequiredScore(levelDetails.getRequiredScore()); + level.setBlocked(levelDetails.isBlocked()); + level.setWords(levelDetails.getWords()); + return levelRepository.save(level); + } else { + return null; + } + } + + public void deleteLevel(Long id) { + levelRepository.deleteById(id); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/PronunciationService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/PronunciationService.java new file mode 100644 index 000000000..11c27e738 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/PronunciationService.java @@ -0,0 +1,50 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.Pronunciation; +import dev.pronunciationAppBack.repository.PronunciationRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class PronunciationService { + + @Autowired + private PronunciationRepository pronunciationRepository; + + public Pronunciation createPronunciation(Pronunciation pronunciation) { + return pronunciationRepository.save(pronunciation); + } + + public Optional getPronunciationById(Long id) { + return pronunciationRepository.findById(id); + } + + public List getAllPronunciations() { + return pronunciationRepository.findAll(); + } + + public Pronunciation updatePronunciation(Long id, Pronunciation pronunciationDetails) { + Optional optionalPronunciation = pronunciationRepository.findById(id); + if (optionalPronunciation.isPresent()) { + Pronunciation pronunciation = optionalPronunciation.get(); + pronunciation.setAudioName(pronunciationDetails.getAudioName()); + pronunciation.setAudioSize(pronunciationDetails.getAudioSize()); + pronunciation.setAudioUrl(pronunciationDetails.getAudioUrl()); + pronunciation.setPhoneticSpelling(pronunciationDetails.getPhoneticSpelling()); + pronunciation.setSpeakerGender(pronunciationDetails.getSpeakerGender()); + pronunciation.setType(pronunciationDetails.getType()); + pronunciation.setAccuracyScore(pronunciationDetails.getAccuracyScore()); + pronunciation.setWord(pronunciationDetails.getWord()); + return pronunciationRepository.save(pronunciation); + } else { + return null; + } + } + + public void deletePronunciation(Long id) { + pronunciationRepository.deleteById(id); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/StageService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/StageService.java new file mode 100644 index 000000000..6f86e253e --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/StageService.java @@ -0,0 +1,49 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.Stage; +import dev.pronunciationAppBack.repository.StageRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class StageService { + + @Autowired + private StageRepository stageRepository; + + public Stage createStage(Stage stage) { + return stageRepository.save(stage); + } + + public Optional getStageById(Long id) { + return stageRepository.findById(id); + } + + public List getAllStages() { + return stageRepository.findAll(); + } + + public Stage updateStage(Long id, Stage stageDetails) { + Optional optionalStage = stageRepository.findById(id); + if (optionalStage.isPresent()) { + Stage stage = optionalStage.get(); + stage.setName(stageDetails.getName()); + stage.setAvatarUrl(stageDetails.getAvatarUrl()); + stage.setStatus(stageDetails.getStatus()); + stage.setProgress(stageDetails.getProgress()); + stage.setCurrentScore(stageDetails.getCurrentScore()); + stage.setGameProgresses(stageDetails.getGameProgresses()); + stage.setStageWords(stageDetails.getStageWords()); + return stageRepository.save(stage); + } else { + return null; + } + } + + public void deleteStage(Long id) { + stageRepository.deleteById(id); + } +} diff --git a/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/StageWordsService.java b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/StageWordsService.java new file mode 100644 index 000000000..c38c2fd76 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/StageWordsService.java @@ -0,0 +1,46 @@ +package dev.pronunciationAppBack.service; + +import dev.pronunciationAppBack.model.StageWords; +import dev.pronunciationAppBack.repository.StageWordsRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class StageWordsService { + + @Autowired + private StageWordsRepository stageWordsRepository; + + public StageWords createStageWords(StageWords stageWords) { + return stageWordsRepository.save(stageWords); + } + + public Optional getStageWordsById(Long id) { + return stageWordsRepository.findById(id); + } + + public List getAllStageWords() { + return stageWordsRepository.findAll(); + } + + public StageWords updateStageWords(Long id, StageWords stageWordsDetails) { + Optional optionalStageWords = stageWordsRepository.findById(id); + if (optionalStageWords.isPresent()) { + StageWords stageWords = optionalStageWords.get(); + stageWords.setStatus(stageWordsDetails.getStatus()); + stageWords.setLastUpdateDateTime(stageWordsDetails.getLastUpdateDateTime()); + stageWords.setStage(stageWordsDetails.getStage()); + stageWords.setWord(stageWordsDetails.getWord()); + return stageWordsRepository.save(stageWords); + } else { + return null; + } + } + + public void deleteStageWords(Long id) { + stageWordsRepository.deleteById(id); + } +} 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..c18af7a92 --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/UserService.java @@ -0,0 +1,50 @@ +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.util.List; +import java.util.Optional; + +@Service +public class UserService { + + @Autowired + private UserRepository userRepository; + + public User createUser(User user) { + return userRepository.save(user); + } + + public Optional getUserById(Long id) { + return userRepository.findById(id); + } + + public List getAllUsers() { + return userRepository.findAll(); + } + + public User updateUser(Long id, User userDetails) { + Optional optionalUser = userRepository.findById(id); + if (optionalUser.isPresent()) { + User user = optionalUser.get(); + user.setUsername(userDetails.getUsername()); + user.setAge(userDetails.getAge()); + user.setEmail(userDetails.getEmail()); + user.setTotalScore(userDetails.getTotalScore()); + user.setActive(userDetails.isActive()); + user.setGameProgresses(userDetails.getGameProgresses()); + user.setAttempts(userDetails.getAttempts()); + user.setGameProgress(userDetails.getGameProgress()); + return userRepository.save(user); + } else { + return null; + } + } + + public void deleteUser(Long id) { + userRepository.deleteById(id); + } +} 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..8ab7e1d6e --- /dev/null +++ b/backend/pronunciationAppBack/src/main/java/dev/pronunciationAppBack/service/WordService.java @@ -0,0 +1,52 @@ +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.Optional; + +@Service +public class WordService { + + @Autowired + private WordRepository wordRepository; + + public Word createWord(Word word) { + return wordRepository.save(word); + } + + public Optional getWordById(Long id) { + return wordRepository.findById(id); + } + + public List getAllWords() { + return wordRepository.findAll(); + } + + public Word updateWord(Long id, Word wordDetails) { + Optional optionalWord = wordRepository.findById(id); + if (optionalWord.isPresent()) { + Word word = optionalWord.get(); + word.setText(wordDetails.getText()); + word.setDescription(wordDetails.getDescription()); + word.setSentence(wordDetails.getSentence()); + word.setDifficulty(wordDetails.getDifficulty()); + word.setCommon(wordDetails.isCommon()); + word.setStageWords(wordDetails.getStageWords()); + word.setPronunciations(wordDetails.getPronunciations()); + word.setAttempts(wordDetails.getAttempts()); + word.setLevel(wordDetails.getLevel()); + word.setCategories(wordDetails.getCategories()); + return wordRepository.save(word); + } else { + return null; + } + } + + public void deleteWord(Long id) { + wordRepository.deleteById(id); + } +} diff --git a/backend/pronunciationAppBack/src/main/resources/application.properties b/backend/pronunciationAppBack/src/main/resources/application.properties index edb9ea0d2..90b59d681 100644 --- a/backend/pronunciationAppBack/src/main/resources/application.properties +++ b/backend/pronunciationAppBack/src/main/resources/application.properties @@ -1 +1,20 @@ 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;NON_KEYWORDS=user +# spring.datasource.username=sa +# spring.datasource.password= + +# H2 LOCAL DB SERVER +spring.datasource.url=jdbc:h2:~/Developer/projects/pronunciationApp/backend/pronunciationDB/pronunciationDB.db;NON_KEYWORDS=user +spring.datasource.username=bielidev +spring.datasource.password=1234 + +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=update +sprint.jpa.hibernate.show-sql=true \ No newline at end of file diff --git a/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/DatabaseRelationshipTest.java b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/DatabaseRelationshipTest.java new file mode 100644 index 000000000..f5aa7e71e --- /dev/null +++ b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/DatabaseRelationshipTest.java @@ -0,0 +1,163 @@ +package dev.pronunciationAppBack; + +import dev.pronunciationAppBack.model.*; +import dev.pronunciationAppBack.model.Stage.Status; +import dev.pronunciationAppBack.repository.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +public class DatabaseRelationshipTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private LevelRepository levelRepository; + + @Autowired + private StageRepository stageRepository; + + @Autowired + private WordRepository wordRepository; + + @BeforeEach + public void setUp() { + // Clear the database before each test + userRepository.deleteAll(); + categoryRepository.deleteAll(); + levelRepository.deleteAll(); + stageRepository.deleteAll(); + wordRepository.deleteAll(); + } + + @Test + @Transactional + public void testRelationships() { + // Create sample data + User user = new User(); + user.setName("Test User"); + user.setEmail("test@test.com"); + + Category category = new Category(); + category.setCategoryName("Test Category"); + category.setDescription("Test Description"); + category.setSubCategoryName("Test Subcategory"); + + Level level = new Level(); + level.setName("Test Level"); + level.setCategory(category); + + Stage stage = new Stage(); + stage.setName("Test Stage"); + stage.setLevel(level); + stage.setAvatarUrl("Test Avatar URL"); + stage.setStatus(Status.COMPLETED); + + Word word = new Word(); + word.setText("Test Word"); + word.setStage(stage); + word.setDescription("Test Description"); + word.setSentence("Test Sentence"); + + // Save data + userRepository.save(user); + categoryRepository.save(category); + levelRepository.save(level); + stageRepository.save(stage); + wordRepository.save(word); + + // Verify relationships + List levels = levelRepository.findAll(); + assertEquals(1, levels.size()); + assertEquals("Test Category", levels.get(0).getCategory().getCategoryName()); + + List stages = stageRepository.findAll(); + assertEquals(1, stages.size()); + assertEquals("Test Level", stages.get(0).getLevel().getName()); + + List words = wordRepository.findAll(); + assertEquals(1, words.size()); + assertEquals("Test Stage", words.get(0).getStage().getName()); + } + + @Test + @Transactional + public void testCascadeOperations() { + // Create sample data + Category category = new Category(); + category.setCategoryName("Cascade Category"); + category.setDescription("Test Description"); + category.setSubCategoryName("Test Subcategory"); + + Level level = new Level(); + level.setName("Cascade Level"); + level.setCategory(category); + category.setLevels(List.of(level)); + + Stage stage = new Stage(); + stage.setName("Cascade Stage"); + stage.setLevel(level); + stage.setAvatarUrl("Test Avatar URL"); + stage.setStatus(Status.COMPLETED); + + Word word = new Word(); + word.setText("Test Word"); + word.setStage(stage); + word.setDescription("Test Description"); + word.setSentence("Test Sentence"); + + // Save data + categoryRepository.save(category); + + // Verify cascade operations + List categories = categoryRepository.findAll(); + assertEquals(1, categories.size()); + assertEquals(1, categories.get(0).getLevels().size()); + } + + @Test + @Transactional + public void testFetchingStrategies() { + // Create sample data + Category category = new Category(); + category.setCategoryName("Fetch Category"); + category.setDescription("Test Description"); + category.setSubCategoryName("Test Subcategory"); + + Level level = new Level(); + level.setName("Fetch Level"); + level.setCategory(category); + + Stage stage = new Stage(); + stage.setName("Fetch Stage"); + stage.setLevel(level); + stage.setAvatarUrl("Test Avatar URL"); + stage.setStatus(Status.COMPLETED); + + Word word = new Word(); + word.setText("Test Word"); + word.setStage(stage); + word.setDescription("Test Description"); + word.setSentence("Test Sentence"); + + // Save data + categoryRepository.save(category); + + // Verify fetching strategies + Category fetchedCategory = categoryRepository.findById(category.getId()).orElse(null); + assertNotNull(fetchedCategory); + } +} diff --git a/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java b/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java deleted file mode 100644 index ea361613f..000000000 --- a/backend/pronunciationAppBack/src/test/java/dev/pronunciationAppBack/PronunciationAppBackApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.pronunciationAppBack; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class PronunciationAppBackApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/backend/resources/Test-JUnit DB/test-JUnit-Word.md b/backend/resources/Test-JUnit DB/test-JUnit-Word.md new file mode 100644 index 000000000..e9312dc38 --- /dev/null +++ b/backend/resources/Test-JUnit DB/test-JUnit-Word.md @@ -0,0 +1,171 @@ +# Test JUnit Word H2 DB + +## Test + +Here are 4 simple test cases for CRUD operations using `JUnit` and `Spring Boot` for the Word `entity`: + +```java +package dev.pronunciationAppBack; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +public class WordRepositoryTest { + + @Autowired + private TestEntityManager entityManager; + + @Autowired + private WordRepository wordRepository; + + @Test + public void testCreateWord() { + Word word = new Word("1", "Example", "A thing characteristic of its kind", "ɪɡˈzæmpəl", "This is an example sentence.", true, 1); + Word savedWord = wordRepository.save(word); + assertThat(savedWord).isNotNull(); + assertThat(savedWord.getId()).isEqualTo("1"); + } + + @Test + public void testReadWord() { + Word word = new Word("2", "Test", "A procedure to evaluate", "test", "This is a test sentence.", true, 2); + entityManager.persist(word); + + Word foundWord = wordRepository.findById("2").orElse(null); + assertThat(foundWord).isNotNull(); + assertThat(foundWord.getWordName()).isEqualTo("Test"); + } + + @Test + public void testUpdateWord() { + Word word = new Word("3", "Update", "To bring up to date", "ˈʌpdeɪt", "This word will be updated.", true, 3); + entityManager.persist(word); + + Word wordToUpdate = wordRepository.findById("3").orElse(null); + assertThat(wordToUpdate).isNotNull(); + wordToUpdate.setDefinition("To make something more modern or up to date"); + wordRepository.save(wordToUpdate); + + Word updatedWord = wordRepository.findById("3").orElse(null); + assertThat(updatedWord).isNotNull(); + assertThat(updatedWord.getDefinition()).isEqualTo("To make something more modern or up to date"); + } + + @Test + public void testDeleteWord() { + Word word = new Word("4", "Delete", "To remove or erase", "dɪˈliːt", "This word will be deleted.", true, 4); + entityManager.persist(word); + + wordRepository.deleteById("4"); + + Word deletedWord = wordRepository.findById("4").orElse(null); + assertThat(deletedWord).isNull(); + } +} +``` + +These test cases cover the basic CRUD (Create, Read, Update, Delete) operations for the Word entity. Here's a brief explanation of each test: + +1. `testCreateWord`: Tests the creation of a new Word entity. +2. `testReadWord`: Tests reading a Word entity from the database. +3. `testUpdateWord`: Tests updating an existing Word entity. +4. `testDeleteWord`: Tests deleting a Word entity from the database. + +## Repository + +Note that these tests assume you have a `WordRepository` interface that extends `JpaRepository`. You'll need to create this interface in your project: + +```java +package dev.pronunciationAppBack; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface WordRepository extends JpaRepository { +} +``` + +Also, make sure you have the necessary dependencies in your `pom.xml` or `build.gradle` file for Spring Boot Test and JPA Test. + +## H2 and application.properties + +> For these JUnit tests to run properly with an in-memory database, we need to add the `H2 database` dependency and configure the `application.properties` file. + +Here's what you need to add: + +1. H2 Database Dependency: + Add this to your `pom.xml` if you're using Maven: + +```xml + + com.h2database + h2 + test + +``` + +Or if you're using Gradle, add this to your `build.gradle`: + +```gradle +dependencies { + testImplementation 'com.h2database:h2' +} +``` + +2. Application Properties: + Create a file named `application.properties` in your `src/test/resources` directory with the following content: + +```properties +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=true +``` + +These settings will: + +- Configure an **in-memory H2 database for testing** +- Set up the database to create tables based on your entities and drop them after the tests +- Show SQL statements in the console, which can be helpful for debugging + +## Local DB + +First you must create the DB: + +```properties +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=create +``` + +Once created, change DDL to none + +```properties +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= + + +# H2 LOCAL DB SERVER +spring.datasource.url=jdbc:h2:/home/albert/MyProjects/DataBase/pronunciationDB/pronunciationDB.db +spring.datasource.username=albert +spring.datasource.password=1234 + +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=none +``` diff --git a/backend/resources/annotations/Param-PathVariable.md b/backend/resources/annotations/Param-PathVariable.md new file mode 100644 index 000000000..386848a2f --- /dev/null +++ b/backend/resources/annotations/Param-PathVariable.md @@ -0,0 +1,66 @@ +# Param vs Path Variable + +## Using @DeleteMapping in Spring Boot with Postman + +Spring Boot's `@DeleteMapping` annotation simplifies the process of handling HTTP DELETE requests. + +> The `@DeleteMapping` annotation in Spring Boot provides a clean and efficient way to handle DELETE requests. Whether you choose to use path variables or query parameters depends on your API design preferences and requirements. Postman is an excellent tool for testing these endpoints, allowing you to easily send DELETE requests and verify the results. + +## DeleteWord Example + +Here's a simple example of a delete operation in a Spring Boot controller: + +```java +@RestController +@RequestMapping("/words") +public class WordController { + + @Autowired + private WordRepository wordRepository; + + @DeleteMapping("/{id}") + public String deleteWord(@PathVariable("id") String idToDelete) { + wordRepository.deleteById(idToDelete); + return "Word deleted"; + } +} +``` + +In this example, the `deleteWord` method is mapped to handle DELETE requests to the `/words/{id}` endpoint[1][4]. The `@PathVariable` annotation binds the `id` from the URL to the `idToDelete` parameter[3]. + +## Testing with Postman + +To test this endpoint using Postman, follow these steps: + +1. Open Postman and create a new request. +2. Set the HTTP method to DELETE. +3. Enter the URL: `http://localhost:8080/words/{id}` (replace `{id}` with the actual ID you want to delete). +4. Click the "Send" button to execute the request. + +### Using Path Variable + +For our `deleteWord` example, we're using a path variable. The ID is part of the URL path: + +``` +DELETE http://localhost:8080/words/123 +``` + +Here, `123` is the ID of the word to be deleted[5]. + +### Using Query Parameter (Alternative Approach) + +While our example uses a path variable, you could also design your endpoint to use a query parameter: + +```java +@DeleteMapping +public String deleteWord(@RequestParam("id") String idToDelete) { + wordRepository.deleteById(idToDelete); + return "Word deleted"; +} +``` + +To test this with Postman: + +1. Set the URL to `http://localhost:8080/words` +2. Add a query parameter: Key: `id`, Value: `123` +3. The full URL will look like: `http://localhost:8080/words?id=123` diff --git a/backend/resources/containers/Containers-Spring.md b/backend/resources/containers/Containers-Spring.md new file mode 100644 index 000000000..4da920796 --- /dev/null +++ b/backend/resources/containers/Containers-Spring.md @@ -0,0 +1,150 @@ +# Popular Containers in Spring Boot + +> Containers in Java are typically defined by their ability to hold and manage collections of objects or references. + +However, for example, List`and`Optional` serve fundamentally different purposes: + +- `List` is a collection container designed to store multiple elements with order and allow dynamic manipulation. +- `Optional` is a wrapper to explicitly handle the presence or absence of a single value, preventing null reference issues. + +While both can "contain" elements, they solve distinct programming challenges: data storage versus null-safety management. + +#### Wrapper and Collection + +> A **wrapper** is a layer of code that "wraps around" something simpler, adding extra functionality or protection. Like a protective cover that makes something easier to use or more powerful. +> +> In Java, a wrapper takes a basic object or value and provides additional methods or behaviors to interact with it more conveniently. + + + +> A **Collection** is a more structured way of storing and managing those items, with methods to add, remove, and manipulate the group of elements. +> +> An **Iterable** is something you can loop through, like a collection of items. +> +> + +**Container Comparison: List vs Optional** + +| Characteristic | List | Optional | +| -------------------- | ------------------------------- | ------------------------------------------- | +| **Purpose** | Store multiple elements | Represent optional value | +| **Nullability** | Can contain null elements | Explicitly prevents null | +| **Size** | Dynamic, variable length | Always contains 0 or 1 element | +| **Mutability** | Mutable (add/remove elements) | Immutable | +| **Creation** | `new ArrayList<>()` | `Optional.of()`, `Optional.empty()` | +| **Common Methods** | `.add()`, `.remove()`, `.get()` | `.isPresent()`, `.orElse()`, `.ifPresent()` | +| **Java 8+ Feature** | Pre-Java 8 | Introduced in Java 8 | +| **Typical Use Case** | Collection storage | Avoiding null checks | +| **Stream Support** | `.stream()` directly | Treated as stream with `.stream()` | +| **Performance** | Higher memory overhead | Lightweight wrapper | + +### Response Containers + +1. **ResponseEntity** + + - Full control over HTTP response + - Set status codes, headers, body + - Example: + + ```java + return ResponseEntity.ok(word); + return ResponseEntity.notFound().build(); + return ResponseEntity.status(HttpStatus.CREATED).body(word); + ``` + +2. **Optional** + + - Prevent null pointer exceptions + - Avoid explicit null checks + - Example: + + ```java + Optional word = repository.findById(id); + return word.orElseThrow(() -> new ResourceNotFoundException()); + ``` + +3. **Page** + + - Pagination support + - Metadata about result set + - Example: + + ```java + Page words = repository.findAll(PageRequest.of(0, 10)); + ``` + +4. **Mono** and **Flux** (Reactive Programming) + + - Asynchronous data streams + - Non-blocking operations + - Example: + + ```java + Mono wordMono = wordRepository.findById(id); + Flux wordFlux = wordRepository.findAll(); + ``` + +5. **Resource** (HATEOAS) + + - Include hyperlinks in responses + - Support for hypermedia-driven APIs + - Example: + + ```java + Resource resource = new Resource<>(word); + resource.add(linkTo(methodOn(WordController.class).getWord(id)).withSelfRel()); + ``` + +### Key Benefits + +- Type safety +- Explicit error handling +- Flexible response management +- Support for modern architectural patterns + +## Non-Response + +### Data Containers + +1. **List** + + - Basic collection of elements + - Dynamic sizing + - Example: `List words = new ArrayList<>();` + +2. **Set** + + - Unique elements + - No duplicates + - Example: `Set uniqueWordNames = new HashSet<>();` + +3. **Map** + + - Key-value pairs + - Fast lookups + - Example: `Map wordMap = new HashMap<>();` + +4. **Stream** + + - Functional-style operations + - Lazy evaluation + - Example: `words.stream().filter(w -> w.getLevel() > 2)` + +5. **CompletableFuture** + + - Asynchronous computation + - Chaining operations + - Example: `CompletableFuture wordFuture = CompletableFuture.supplyAsync(() -> createWord());` + +6. **Queue** + + - First-In-First-Out (FIFO) + - Task scheduling + - Example: `Queue wordQueue = new LinkedList<>();` + +### Key Characteristics + +- Thread-safety +- Performance optimization +- Flexible data manipulation +- Support for functional programming 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/create project/pronunciationAppBack-v0.0-project-structure.png b/backend/resources/create project/pronunciationAppBack-v0.0-project-structure.png new file mode 100644 index 000000000..be08fd7a4 Binary files /dev/null and b/backend/resources/create project/pronunciationAppBack-v0.0-project-structure.png differ 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/mock-data/bash-script/cli-execute-bash.png b/backend/resources/mock-data/bash-script/cli-execute-bash.png new file mode 100644 index 000000000..3af293426 Binary files /dev/null and b/backend/resources/mock-data/bash-script/cli-execute-bash.png differ diff --git a/backend/resources/mock-data/bash-script/data.json b/backend/resources/mock-data/bash-script/data.json new file mode 100644 index 000000000..edfdfa618 --- /dev/null +++ b/backend/resources/mock-data/bash-script/data.json @@ -0,0 +1,318 @@ +[ + { + "id": "8f7d1b9e3a2c5f6e", + "wordName": "aberration", + "definition": "a departure from what is normal, usual, or expected", + "phoneticSpelling": "ˌæbəˈreɪʃən", + "sentence": "The sudden drop in temperature was an aberration for this time of year.", + "level": 3, + "active": true + }, + { + "id": "2c4a6b8d0e1f3g5h", + "wordName": "benevolent", + "definition": "kind, generous, and caring about others", + "phoneticSpelling": "bəˈnevələnt", + "sentence": "The benevolent donor provided funds for the new hospital wing.", + "level": 2, + "active": true + }, + { + "id": "7j9k1l3m5n2o4p6q", + "wordName": "cacophony", + "definition": "a harsh, discordant mixture of sounds", + "phoneticSpelling": "kəˈkɒfəni", + "sentence": "The cacophony of car horns filled the busy street.", + "level": 4, + "active": true + }, + { + "id": "1r3s5t7u9v2w4x6y", + "wordName": "diligent", + "definition": "having or showing care and conscientiousness in one's work or duties", + "phoneticSpelling": "ˈdɪlɪdʒənt", + "sentence": "The diligent student always completed her homework on time.", + "level": 2, + "active": true + }, + { + "id": "8z0a2b4c6d1e3f5g", + "wordName": "ephemeral", + "definition": "lasting for a very short time", + "phoneticSpelling": "ɪˈfemərəl", + "sentence": "The beauty of cherry blossoms is ephemeral, lasting only a few days.", + "level": 3, + "active": true + }, + { + "id": "7h9i1j3k5l2m4n6o", + "wordName": "facetious", + "definition": "treating serious issues with deliberately inappropriate humor", + "phoneticSpelling": "fəˈsiːʃəs", + "sentence": "His facetious remarks about the company's financial troubles were not well-received.", + "level": 4, + "active": true + }, + { + "id": "5p7q9r1s3t5u2v4w", + "wordName": "gregarious", + "definition": "fond of company; sociable", + "phoneticSpelling": "ɡrɪˈɡeəriəs", + "sentence": "The gregarious host made sure all the guests felt welcome at the party.", + "level": 3, + "active": true + }, + { + "id": "6x8y0z2a4b1c3d5e", + "wordName": "harbinger", + "definition": "a person or thing that announces or signals the approach of another", + "phoneticSpelling": "ˈhɑːbɪndʒə", + "sentence": "The robin is often seen as a harbinger of spring.", + "level": 4, + "active": true + }, + { + "id": "9f1g3h5i7j2k4l6m", + "wordName": "incessant", + "definition": "continuing without pause or interruption", + "phoneticSpelling": "ɪnˈsesənt", + "sentence": "The incessant barking of the neighbor's dog kept us awake all night.", + "level": 3, + "active": true + }, + { + "id": "2n4o6p8q0r1s3t5u", + "wordName": "juxtapose", + "definition": "to place or deal with close together for contrasting effect", + "phoneticSpelling": "ˈdʒʌkstəpəʊz", + "sentence": "The artist decided to juxtapose images of war and peace in her latest work.", + "level": 4, + "active": true + }, + { + "id": "7v9w1x3y5z2a4b6c", + "wordName": "kinetic", + "definition": "relating to or resulting from motion", + "phoneticSpelling": "kɪˈnetɪk", + "sentence": "The sculpture's kinetic elements moved gently in the breeze.", + "level": 3, + "active": true + }, + { + "id": "8d0e2f4g6h1i3j5k", + "wordName": "lethargic", + "definition": "sluggish and apathetic", + "phoneticSpelling": "lɪˈθɑːdʒɪk", + "sentence": "The hot weather made everyone feel lethargic and unmotivated.", + "level": 2, + "active": true + }, + { + "id": "1l3m5n7o9p2q4r6s", + "wordName": "mellifluous", + "definition": "sweet or musical; pleasant to hear", + "phoneticSpelling": "məˈlɪfluəs", + "sentence": "The singer's mellifluous voice captivated the audience.", + "level": 4, + "active": true + }, + { + "id": "5t7u9v1w3x5y2z4a", + "wordName": "nefarious", + "definition": "wicked or criminal", + "phoneticSpelling": "nɪˈfeəriəs", + "sentence": "The detective uncovered the CEO's nefarious scheme to embezzle company funds.", + "level": 3, + "active": true + }, + { + "id": "6b8c0d2e4f1g3h5i", + "wordName": "obfuscate", + "definition": "to render obscure, unclear, or unintelligible", + "phoneticSpelling": "ˈɒbfʌskeɪt", + "sentence": "The politician tried to obfuscate the issue by using complex jargon.", + "level": 4, + "active": true + }, + { + "id": "9j1k3l5m7n2o4p6q", + "wordName": "panacea", + "definition": "a solution or remedy for all difficulties or diseases", + "phoneticSpelling": "ˌpænəˈsiːə", + "sentence": "Exercise is not a panacea for all health problems, but it certainly helps.", + "level": 3, + "active": true + }, + { + "id": "2r4s6t8u0v1w3x5y", + "wordName": "quintessential", + "definition": "representing the most perfect or typical example of a quality or class", + "phoneticSpelling": "ˌkwɪntɪˈsenʃəl", + "sentence": "The small town diner was the quintessential American eating establishment.", + "level": 4, + "active": true + }, + { + "id": "7z9a1b3c5d2e4f6g", + "wordName": "resilient", + "definition": "able to withstand or recover quickly from difficult conditions", + "phoneticSpelling": "rɪˈzɪliənt", + "sentence": "The resilient community quickly rebuilt after the natural disaster.", + "level": 3, + "active": true + }, + { + "id": "8h0i2j4k6l1m3n5o", + "wordName": "surreptitious", + "definition": "kept secret, especially because it would not be approved of", + "phoneticSpelling": "ˌsʌrəpˈtɪʃəs", + "sentence": "He made a surreptitious attempt to leave the party without saying goodbye.", + "level": 4, + "active": true + }, + { + "id": "1p3q5r7s9t2u4v6w", + "wordName": "tenacious", + "definition": "tending to keep a firm hold of something; clinging or adhering closely", + "phoneticSpelling": "təˈneɪʃəs", + "sentence": "The tenacious climber refused to give up, even when faced with steep cliffs.", + "level": 3, + "active": true + }, + { + "id": "5x7y9z1a3b5c2d4e", + "wordName": "ubiquitous", + "definition": "present, appearing, or found everywhere", + "phoneticSpelling": "juːˈbɪkwɪtəs", + "sentence": "Smartphones have become ubiquitous in modern society.", + "level": 4, + "active": true + }, + { + "id": "6f8g0h2i4j1k3l5m", + "wordName": "vociferous", + "definition": "expressing or characterized by vehement opinions; loud and forceful", + "phoneticSpelling": "və(ʊ)ˈsɪfərəs", + "sentence": "The vociferous crowd demanded justice for the wrongly accused man.", + "level": 4, + "active": true + }, + { + "id": "9n1o3p5q7r2s4t6u", + "wordName": "whimsical", + "definition": "playfully quaint or fanciful, especially in an appealing and amusing way", + "phoneticSpelling": "ˈwɪmzɪkəl", + "sentence": "The artist's whimsical sculptures brought a smile to everyone's face.", + "level": 3, + "active": true + }, + { + "id": "2v4w6x8y0z1a3b5c", + "wordName": "xenophobia", + "definition": "dislike of or prejudice against people from other countries", + "phoneticSpelling": "ˌzenəˈfəʊbiə", + "sentence": "The politician's xenophobia was evident in his discriminatory policies.", + "level": 4, + "active": true + }, + { + "id": "7d9e1f3g5h2i4j6k", + "wordName": "yearn", + "definition": "to have an intense feeling of longing for something", + "phoneticSpelling": "jɜːn", + "sentence": "After months away, she yearned to return to her hometown.", + "level": 2, + "active": true + }, + { + "id": "8l0m2n4o6p1q3r5s", + "wordName": "zealous", + "definition": "having or showing zeal; enthusiastic and diligent", + "phoneticSpelling": "ˈzeləs", + "sentence": "The zealous volunteer dedicated all her free time to the charity.", + "level": 3, + "active": true + }, + { + "id": "1t3u5v7w9x2y4z6a", + "wordName": "ambivalent", + "definition": "having mixed feelings or contradictory ideas about something or someone", + "phoneticSpelling": "æmˈbɪvələnt", + "sentence": "She felt ambivalent about moving to a new city for her job.", + "level": 3, + "active": true + }, + { + "id": "5b7c9d1e3f5g2h4i", + "wordName": "brevity", + "definition": "concise and exact use of words in writing or speech", + "phoneticSpelling": "ˈbrevɪti", + "sentence": "The speaker's brevity was appreciated by the audience.", + "level": 3, + "active": true + }, + { + "id": "6j8k0l2m4n1o3p5q", + "wordName": "cognizant", + "definition": "having knowledge or awareness", + "phoneticSpelling": "ˈkɒɡnɪzənt", + "sentence": "The manager was cognizant of the team's concerns about the new project.", + "level": 4, + "active": true + }, + { + "id": "9r1s3t5u7v2w4x6y", + "wordName": "dexterous", + "definition": "showing or having skill, especially with the hands", + "phoneticSpelling": "ˈdekstərəs", + "sentence": "The dexterous magician amazed the crowd with his sleight of hand.", + "level": 3, + "active": true + }, + { + "id": "2z4a6b8c0d1e3f5g", + "wordName": "eloquent", + "definition": "fluent or persuasive in speaking or writing", + "phoneticSpelling": "ˈeləkwənt", + "sentence": "Her eloquent speech moved the entire audience to tears.", + "level": 3, + "active": true + }, + { + "id": "7h9i1j3k5l2m4n6o", + "wordName": "fortuitous", + "definition": "happening by chance rather than intention", + "phoneticSpelling": "fɔːˈtjuːɪtəs", + "sentence": "Their meeting at the airport was entirely fortuitous.", + "level": 4, + "active": true + }, + { + "id": "8p0q2r4s6t1u3v5w", + "wordName": "garrulous", + "definition": "excessively talkative, especially on trivial matters", + "phoneticSpelling": "ˈɡærʊləs", + "sentence": "The garrulous passenger talked throughout the entire flight.", + "level": 4, + "active": true + }, + { + "id": "1x3y5z7a9b2c4d6e", + "wordName": "haphazard", + "definition": "lacking any obvious principle of organization", + "phoneticSpelling": "hæpˈhæzəd", + "sentence": "The room was in a haphazard state, with clothes and books strewn everywhere.", + "level": 3, + "active": true + }, + { + "id": "5f7g9h1i3j5k2l4m", + "wordName": "iconoclast", + "definition": "a person who attacks or criticizes cherished beliefs or institutions", + "phoneticSpelling": "aɪˈkɒnəklæst", + "sentence": "The young artist was seen as an iconoclast in the conservative art world.", + "level": 4, + "active": true + } +] + diff --git a/backend/resources/mock-data/bash-script/h2-db-fill-with-mock-data.png b/backend/resources/mock-data/bash-script/h2-db-fill-with-mock-data.png new file mode 100644 index 000000000..9587257d3 Binary files /dev/null and b/backend/resources/mock-data/bash-script/h2-db-fill-with-mock-data.png differ diff --git a/backend/resources/mock-data/bash-script/import_words.sh b/backend/resources/mock-data/bash-script/import_words.sh new file mode 100755 index 000000000..568b90fdd --- /dev/null +++ b/backend/resources/mock-data/bash-script/import_words.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Check if jq is installed +if ! command -v jq &> /dev/null +then + echo "jq is required but not installed. Please install jq and try again." + exit 1 +fi + +# Read the JSON file and process each word object +jq -c '.[]' data.json | while read -r word; do + # Send POST request for each word + curl -X POST \ + -H "Content-Type: application/json" \ + -d "$word" \ + http://localhost:8080/api/words/createWord + + echo -e "\nWord processed" + echo "----------" + + # Optional: Add a small delay between requests to avoid overwhelming the server + sleep 0.5 +done + +echo -e "\nAll words have been posted" + + diff --git a/backend/resources/mock-data/import-csv/import-csv.md b/backend/resources/mock-data/import-csv/import-csv.md new file mode 100644 index 000000000..ff13c8d9d --- /dev/null +++ b/backend/resources/mock-data/import-csv/import-csv.md @@ -0,0 +1,32 @@ +# Import CSV to H2 + +> To test the H2 database CSV import functionality with a basic CSV file for a UserApp object, follow these steps: + +1. Create a CSV file named `users.csv` with the following content: + +``` +id,username,email,created_at +1,john_doe,john@example.com,2025-01-24 20:00:00 +2,jane_smith,jane@example.com,2025-01-24 20:15:00 +3,bob_johnson,bob@example.com,2025-01-24 20:30:00 +``` + +2. Save this file in a location accessible to your H2 database, for example, `/path/to/users.csv`. + +3. Connect to your H2 database and execute the following SQL command: + +```sql +CREATE TABLE UserApp AS SELECT * FROM CSVREAD('/path/to/users.csv'); +``` + +This command will create a new table named `UserApp` with columns matching the CSV file structure. + +4. To verify the import, you can run a SELECT query: + +```sql +SELECT * FROM UserApp; +``` + +This should display the imported data from the CSV file. + +> **Note** that the H2 database will automatically infer the column types based on the data in the CSV file[2]. In this case, `id` will likely be treated as an INTEGER, while `username` and `email` will be VARCHAR, and `created_at` will be TIMESTAMP. diff --git a/backend/resources/mock-data/import-csv/userApp-from-csv.png b/backend/resources/mock-data/import-csv/userApp-from-csv.png new file mode 100644 index 000000000..163c5bd77 Binary files /dev/null and b/backend/resources/mock-data/import-csv/userApp-from-csv.png differ diff --git a/backend/resources/mock-data/import-csv/userApp-sql-import-csv.png b/backend/resources/mock-data/import-csv/userApp-sql-import-csv.png new file mode 100644 index 000000000..7b92d9239 Binary files /dev/null and b/backend/resources/mock-data/import-csv/userApp-sql-import-csv.png differ diff --git a/backend/resources/mock-data/import-csv/users.csv b/backend/resources/mock-data/import-csv/users.csv new file mode 100644 index 000000000..d4ca3dc38 --- /dev/null +++ b/backend/resources/mock-data/import-csv/users.csv @@ -0,0 +1,4 @@ +id,username,email,created_at +1,john_doe,john@example.com,2025-01-24 20:00:00 +2,jane_smith,jane@example.com,2025-01-24 20:15:00 +3,bob_johnson,bob@example.com,2025-01-24 20:30:00 diff --git a/backend/resources/pronunciationApp-v0.1.md b/backend/resources/pronunciationApp-v0.1.md new file mode 100644 index 000000000..62dbb8fcd --- /dev/null +++ b/backend/resources/pronunciationApp-v0.1.md @@ -0,0 +1,390 @@ +# PronunciationApp Backend v0.1 + +## Project + +- pronunciationApp Backend **GitHub** [code](https://github.com/AlbertProfe/pronunciationApp/tree/backend-spring-boot/backend/pronunciationAppBack) + +- pronunciatoinApp Backend [resources](https://github.com/AlbertProfe/pronunciationApp/tree/backend-spring-boot/backend/resources) **documentation** + +### Project Structure + +A Spring Boot backend application for the `Pronunciation App`, using: + +- H2 Database (local file-based or memory for certain purposes) +- `Spring Data JPA` +- REST Controller +- Service Layer +- Entity Mapping: `@Entity` + +### Dependencies + +Add these to your `pom.xml`: + +```xml + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + com.h2database + h2 + runtime + + +``` + +### Entity + +`Word.java`: + +```java +package com.pronunciationapp.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +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; + + // Constructors + public Word() {} + + public Word(String id, String wordName, String definition, + String phoneticSpelling, String sentence, + boolean isActive, int level) { + this.id = id; + this.wordName = wordName; + this.definition = definition; + this.phoneticSpelling = phoneticSpelling; + this.sentence = sentence; + this.isActive = isActive; + this.level = level; + } + + // Getters and Setters + // ... (generate these for all fields) +} +``` + +### Repository + +`WordRepository.java`: + +```java +package com.pronunciationapp.repository; + +import com.pronunciationapp.model.Word; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface WordRepository extends JpaRepository { + List findByIsActiveTrue(); + List findByLevel(int level); + Word findByWordName(String wordName); +} +``` + +### Service + +`WordService.java`: + +```java +package com.pronunciationapp.service; + +import com.pronunciationapp.model.Word; +import com.pronunciationapp.repository.WordRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Service +public class WordService { + + @Autowired + private WordRepository wordRepository; + + public List getAllWords() { + return wordRepository.findAll(); + } + + public Optional getWordById(String id) { + return wordRepository.findById(id); + } + + public Word createWord(Word word) { + if (word.getId() == null) { + word.setId(UUID.randomUUID().toString()); + } + return wordRepository.save(word); + } + + public Word updateWord(String id, Word wordDetails) { + Word word = wordRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Word not found")); + + word.setWordName(wordDetails.getWordName()); + word.setDefinition(wordDetails.getDefinition()); + word.setPhoneticSpelling(wordDetails.getPhoneticSpelling()); + word.setSentence(wordDetails.getSentence()); + word.setActive(wordDetails.isActive()); + word.setLevel(wordDetails.getLevel()); + + return wordRepository.save(word); + } + + public void deleteWord(String id) { + Word word = wordRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Word not found")); + wordRepository.delete(word); + } + + public List getActiveWords() { + return wordRepository.findByIsActiveTrue(); + } + + public List getWordsByLevel(int level) { + return wordRepository.findByLevel(level); + } +} +``` + +### REST Controller + +`WordController.java`: + +```java +package com.pronunciationapp.controller; + +import com.pronunciationapp.model.Word; +import com.pronunciationapp.service.WordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/words") +public class WordController { + + @Autowired + private WordService wordService; + + @GetMapping + public List getAllWords() { + return wordService.getAllWords(); + } + + @GetMapping("/{id}") + public ResponseEntity getWordById(@PathVariable String id) { + return wordService.getWordById(id) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @PostMapping + public Word createWord(@RequestBody Word word) { + return wordService.createWord(word); + } + + @PutMapping("/{id}") + public ResponseEntity updateWord( + @PathVariable String id, + @RequestBody Word wordDetails + ) { + Word updatedWord = wordService.updateWord(id, wordDetails); + return ResponseEntity.ok(updatedWord); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteWord(@PathVariable String id) { + wordService.deleteWord(id); + return ResponseEntity.ok().build(); + } + + @GetMapping("/active") + public List getActiveWords() { + return wordService.getActiveWords(); + } + + @GetMapping("/level/{level}") + public List getWordsByLevel(@PathVariable int level) { + return wordService.getWordsByLevel(level); + } +} +``` + +### Application Properties + +`application.properties`: + +```properties +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 LOCAL DB SERVER +spring.datasource.url=jdbc:h2:/home/albert/MyProjects/DataBase/pronunciationDB/pronunciationDB.db +spring.datasource.username=albert +spring.datasource.password=1234 + +# DDL OPTIONS: create-drop, create, update, none, validate +spring.jpa.hibernate.ddl-auto=update + +# Enable H2 web console +spring.h2.console.path=/h2-console +``` + +## Testing with Postman + +Sample JSON for testing: + +```json +{ + "words": [ + { + "id": "8b9248a4e0b64bbccf82e7723a3734279bf9bbc4", + "wordName": "benevolent", + "definition": "Kind and generous", + "phoneticSpelling": "/bɪˈnɛvələnt/", + "sentence": "Her benevolent actions helped many people in need.", + "isActive": true, + "level": 2 + }, + { + "id": "3a7bd3e2a07e8c7e9b6e0d2c1f4a5b8d9c0e3f2", + "wordName": "serendipity", + "definition": "A fortunate discovery", + "phoneticSpelling": "/ˌserənˈdɪpɪti/", + "sentence": "Meeting her was pure serendipity.", + "isActive": true, + "level": 3 + } + ] +} +``` + +### Postman Collection Setup + +Create a new collection for `PronunciationApp` with these CRUD endpoints: + +1. **GET All Words** + + - Method: GET + - URL: `http://localhost:8080/api/words` + - Expected: Returns full list of words + +2. **GET Word by ID** + + - Method: GET + - URL: `http://localhost:8080/api/words/{id}` + - Params: Replace `{id}` with a specific word ID + - Expected: Returns single word details + +3. **POST Create Word** + + - Method: POST + + - URL: `http://localhost:8080/api/words` + + - Body (raw JSON): + + ```json + { + "wordName": "ephemeral", + "definition": "Lasting for a very short time", + "phoneticSpelling": "/ɪˈfɛmərəl/", + "sentence": "The beauty of cherry blossoms is ephemeral.", + "isActive": true, + "level": 2 + } + ``` + + - Expected: Returns created word with auto-generated ID + +4. **PUT Update Word** + + - Method: PUT + + - URL: `http://localhost:8080/api/words/{id}` + + - Params: Replace `{id}` with existing word ID + + - Body (raw JSON): + + ```json + { + "wordName": "ephemeral", + "definition": "Lasting for a very short time", + "phoneticSpelling": "/ɪˈfɛmərəl/", + "sentence": "The beauty of cherry blossoms is ephemeral.", + "isActive": false, + "level": 3 + } + ``` + + - Expected: Returns updated word + +5. **DELETE Word** + + - Method: DELETE + - URL: `http://localhost:8080/api/words/{id}` + - Params: Replace `{id}` with word ID to delete + - Expected: 200 OK response + +### Additional Test Endpoints + +- **GET Active Words**: `http://localhost:8080/api/words/active` +- **GET Words by Level**: `http://localhost:8080/api/words/level/2` + +### Testing Workflow + +1. Import collection in Postman +2. Start Spring Boot application +3. Execute requests in sequence +4. Verify responses match expected results + +### Common Testing Scenarios + +- Test creating words with missing fields +- Validate ID generation +- Check level and active status constraints +- Test boundary conditions (max/min levels) + +## Running the Application + +1. Ensure you have Java 17+ and Maven installed +2. Configure the database path in `application.properties` +3. Run the Spring Boot application +4. Access H2 console at `http://localhost:8080/h2-console` +5. Access API endpoints at `http://localhost:8080/api/words` + +## Notes + +- The application uses a **local** H2 database file +- UUID is used for generating unique word IDs +- Basic CRUD operations are implemented +- Additional **query methods are available in the repository** and service layers 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-basic-CRUD-controller-2.png b/backend/resources/pronunciationAppBack-v0.0-basic-CRUD-controller-2.png new file mode 100644 index 000000000..e97453151 Binary files /dev/null and b/backend/resources/pronunciationAppBack-v0.0-basic-CRUD-controller-2.png differ diff --git a/backend/resources/pronunciationAppBack-v0.0-basic-CRUD-controller.png b/backend/resources/pronunciationAppBack-v0.0-basic-CRUD-controller.png new file mode 100644 index 000000000..2d4acde25 Binary files /dev/null and b/backend/resources/pronunciationAppBack-v0.0-basic-CRUD-controller.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 diff --git a/backend/resources/pronunciationAppBack-v0.0-project-structure-2.png b/backend/resources/pronunciationAppBack-v0.0-project-structure-2.png new file mode 100644 index 000000000..22a050a1f Binary files /dev/null and b/backend/resources/pronunciationAppBack-v0.0-project-structure-2.png differ diff --git a/backend/resources/pronunciationAppBack-v0.0-project-structure.png b/backend/resources/pronunciationAppBack-v0.0-project-structure.png index be08fd7a4..3dbc4ee10 100644 Binary files a/backend/resources/pronunciationAppBack-v0.0-project-structure.png and b/backend/resources/pronunciationAppBack-v0.0-project-structure.png differ diff --git a/backend/resources/springboot-for-5yo.md b/backend/resources/springboot-for-5yo.md new file mode 100644 index 000000000..124653414 --- /dev/null +++ b/backend/resources/springboot-for-5yo.md @@ -0,0 +1,41 @@ +# Spring Boot and MVC to a 5yo kid + +> Imagine a big restaurant called `Spring Boot`. + +Here's how it works: + +- The menu is like the `View`. It shows you all the yummy food you can order. + +- When you come in, you meet the maitre. They're like the `Front Controller`, welcoming you and guiding you to your table. + +- The waiter is like the `Controller`. They take your order and make sure you get what you wan. + +- The kitchen is like the `Service`. It's where all the food is prepared. + +- In the kitchen, there's a chef who's like `JPA`. They know all the recipes and how to make the dishes just right. + +- The cook is like `SQL queries.` They do the actual cooking, following the chef's instructions. + +- Each plate has a super-sercret code just for superheroes, this secret code is the `id` at `database`. + +- All the ingredients are kept in the pantry and fridge, which is like the `database`. + +- And finally, the plates and dishes are like the `Model`. They hold all the delicious food that comes to your table. + +- Imagine you want pizza at home. The `API Rest` is like a super-fast delivery superhero who brings your yummy food exactly where you want it! + +- What if you could watch chefs cooking? The `WebSocket` is like a magical window where you see everything happening in the kitchen, right when it's happening! + +- The `Security` is like a kind and strong firend who makes sure only nice people can enter and keeps all the secret recipes safe. + +- The `Thymeleaf Templates` and `Vaadin` create super cool, colorful menus that make you smile and want to try everything. + +- When you want the perfect meal, the `AI` is like a magic waiter who knows exactly what you like and suggests the most delicious food just for you! + +- Someone needs to make sure everything works perfectly. The `Actuator` is like the restaurant's superhero manager who watches over everything. + +- What if restaurants could help each other? The `Spring Cloud` is like a big friendly restaurant family that shares cool tricks and helps everyone. + +- Want to cook something amazing super fast? The `Spring Initializr` is like a magic kitchen that appears instantly, ready to make any delicious meal you want! + +All these parts work together to make sure you have a great meal at the Spring Boot restaurant!