diff --git a/.gitea/workflows/demo.yaml b/.gitea/workflows/demo.yaml new file mode 100644 index 0000000..d3f1e27 --- /dev/null +++ b/.gitea/workflows/demo.yaml @@ -0,0 +1,75 @@ +name: Gitea Actions Demo +run-name: ${{ gitea.actor }} is testing out Gitea Actions + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + Explore-Gitea-Actions-2: + runs-on: ubuntu-latest + steps: + - name: Info + run: | + echo "Triggered by ${{ gitea.event_name }} event" + echo "Branch: ${{ gitea.ref }}" + echo "Repository: ${{ gitea.repository }}" + + - name: Install Node.js, npm, and Yarn (optimized) + run: | + set -e + echo "Preparing Node.js, npm, and Yarn setup..." + + if command -v apt >/dev/null 2>&1; then + echo "→ Using apt (Debian/Ubuntu)..." + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq >/dev/null + apt-get install -y -qq curl ca-certificates >/dev/null + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null 2>&1 + apt-get install -y -qq nodejs >/dev/null + npm install -g yarn --silent + + elif command -v apk >/dev/null 2>&1; then + echo "→ Using apk (Alpine)..." + apk add --no-cache curl nodejs npm >/dev/null + npm install -g yarn --silent + + elif command -v dnf >/dev/null 2>&1; then + echo "→ Using dnf (Fedora/RHEL)..." + dnf install -y -q curl ca-certificates nodejs npm >/dev/null + npm install -g yarn --silent + + else + echo "No supported package manager found (apt, apk, dnf)." + exit 1 + fi + + echo "Node.js, npm, and Yarn installed successfully:" + node -v + npm -v + yarn -v + + - name: Checkout repository manually + env: + TOKEN: ${{ secrets.ACCESS_TOKEN }} + CLONE_URL: ${{ vars.CLONE_URL }} + run: | + echo "Cloning from $CLONE_URL" + echo "Cloning ALL_REPO_TOKEN $ALL_REPO_TOKEN" + CLONE_URL_WITH_AUTH=$(echo "$CLONE_URL" | sed "s#https://#https://$ALL_REPO_TOKEN@#") + git clone --quiet "$CLONE_URL_WITH_AUTH" . + echo "${{ gitea.repository }} cloned successfully." + + - name: List repo files + run: | + ls . + node -v + npm -v + yarn -v + + - name: Done + run: echo "Workflow finished." \ No newline at end of file diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..8fc17ec --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,157 @@ +name: checks + +on: + push: + pull_request: + +jobs: + frontend-jobs: + name: Set up Node and other necessary dependencies for Frontend Tests and Build + runs-on: ubuntu-latest + + steps: + - name: Info + run: | + echo "Triggered by ${{ gitea.event_name }} event" + echo "Branch: ${{ gitea.ref }}" + echo "Repository: ${{ gitea.repository }}" + + - name: Install Node.js, npm, and Yarn (optimized) + run: | + set -e + echo "Preparing Node.js, npm, and Yarn setup..." + + if command -v apt >/dev/null 2>&1; then + echo "→ Using apt (Debian/Ubuntu)..." + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq >/dev/null + apt-get install -y -qq curl ca-certificates >/dev/null + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null 2>&1 + apt-get install -y -qq nodejs >/dev/null + npm install -g yarn --silent + + elif command -v apk >/dev/null 2>&1; then + echo "→ Using apk (Alpine)..." + apk add --no-cache curl nodejs npm >/dev/null + npm install -g yarn --silent + + elif command -v dnf >/dev/null 2>&1; then + echo "→ Using dnf (Fedora/RHEL)..." + dnf install -y -q curl ca-certificates nodejs npm >/dev/null + npm install -g yarn --silent + + else + echo "No supported package manager found (apt, apk, dnf)." + exit 1 + fi + + echo "Node.js, npm, and Yarn installed successfully:" + node -v + npm -v + yarn -v + + - name: Checkout repository manually + env: + TOKEN: ${{ secrets.ACCESS_TOKEN }} + CLONE_URL: ${{ vars.CLONE_URL }} + run: | + echo "Cloning from $CLONE_URL" + echo "Cloning ALL_REPO_TOKEN $ALL_REPO_TOKEN" + CLONE_URL_WITH_AUTH=$(echo "$CLONE_URL" | sed "s#https://#https://$ALL_REPO_TOKEN@#") + git clone --quiet "$CLONE_URL_WITH_AUTH" . + echo "${{ gitea.repository }} cloned successfully." + + # Install frontend dependencies and run tests + - name: Install frontend dependencies and run tests + #uses: actions/cache@v3 + working-directory: ./frontend + with: + path: | + ~/.yarn/cache + ./node_modules + key: frontend-${{ runner.os }}-yarn-${{ hashFiles('frontend/yarn.lock') }} + restore-keys: | + frontend-${{ runner.os }}-yarn- + run: | + yarn install --frozen-lockfile + #yarn test --watchAll=false --ci + + #Check frontend linting + - name: Check frontend linting + working-directory: ./frontend + run: yarn lint + + backend-jobs: + name: Set up Java for Backend Tests and Build + runs-on: ubuntu-latest + #container: + #image: eclipse-temurin:21-jdk + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Java 21 + Maven + run: | + echo "Detecting package manager..." + if command -v apt-get >/dev/null 2>&1; then + PM=apt + elif command -v apk >/dev/null 2>&1; then + PM=apk + elif command -v yum >/dev/null 2>&1; then + PM=yum + else + echo "No known package manager found. Will install JDK manually." + PM=none + fi + + echo "Package manager detected: $PM" + + if [ "$PM" = "apt" ]; then + apt-get update -y + apt-get install -y openjdk-21-jdk maven wget tar + JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 + elif [ "$PM" = "apk" ]; then + apk update + apk add openjdk21 maven wget tar + JAVA_HOME=/usr/lib/jvm/java-21-openjdk + elif [ "$PM" = "yum" ]; then + yum install -y java-21-openjdk-devel maven wget tar + JAVA_HOME=/usr/lib/jvm/java-21-openjdk + else + # Fallback: manual download + JDK_URL="https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz" + mkdir -p /opt/jdk + wget -q -O /tmp/jdk.tar.gz $JDK_URL + tar -xzf /tmp/jdk.tar.gz -C /opt/jdk --strip-components=1 + JAVA_HOME=/opt/jdk + fi + + # Export JAVA_HOME and update PATH for subsequent steps + echo "JAVA_HOME=$JAVA_HOME" >> $GITEA_ENV + echo "$JAVA_HOME/bin" >> $GITEA_PATH + export JAVA_HOME=$JAVA_HOME + export PATH=$JAVA_HOME/bin:$PATH + + # Verify Java and Maven installation + java -version + mvn -version + + - name: Verify Java setup + run: | + java -version + + - name: Verify Java setup + run: | + ls -l $JAVA_HOME/bin/java + file $JAVA_HOME/bin/java || true + java -version + + # Run backend tests and build + - name: Test & build backend + working-directory: . + run: + mvn clean test -Dspring.profiles.active=test + + - name: Done + run: echo "Workflow successfully completed." \ No newline at end of file diff --git a/README.md b/README.md index 8ab4c82..823138c 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,19 @@ 4. _Swagger config_ manually 5. _Dockerfile_ manually _env (only during image build & deployment)_ manually -- add changes `git add .` -- commit changes `git commit -m "Set version to v.."` -- push chnages `git push --set-upstream origin release/v..` -- Create and review the release pull request, then MERGE the release branch `git merge --no-ff release/v.. -m "Merge release v.."`. -- checkout and update main/master branch `git checkout main/master` & `git pull origin main/master` +- add changes + `git add .` +- commit changes + `git commit -m "Set version to v.."` +- push changes + `git push --set-upstream origin release/v..` +- Create and review the release pull request, then MERGE the release branch + `git merge --no-ff release/v.. -m "Merge release v.."`. +- checkout and update main/master branch + `git checkout main/master` & `git pull origin main/master` - Create tag with the version number (format is very important) `git tag v.. -m "Release v.."` -- Push tag `git push origin v..` +- Push tag + `git push origin v..` - Create a GitHub Release from the tag - (from UI) # 2. PATCH (hotfix) RELEASES FOR MY_APP diff --git a/pom.xml b/pom.xml index 3636c86..f4ecbdb 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ - 17 + 21 @@ -45,6 +45,17 @@ spring-boot-starter-test test + + com.h2database + h2 + test + + + org.projectlombok + lombok + 1.18.36 + provided + diff --git a/src/main/java/com/ebrains/cruddemo/CruddemoApplication.java b/src/main/java/com/ebrains/cruddemo/CruddemoApplication.java index 2a09959..1e3bc06 100644 --- a/src/main/java/com/ebrains/cruddemo/CruddemoApplication.java +++ b/src/main/java/com/ebrains/cruddemo/CruddemoApplication.java @@ -1,13 +1,7 @@ package com.ebrains.cruddemo; -import com.ebrains.cruddemo.dao.StudentDAO; -import com.ebrains.cruddemo.entity.Student; -import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; - -import java.util.List; @SpringBootApplication public class CruddemoApplication { @@ -17,108 +11,4 @@ public class CruddemoApplication { SpringApplication.run(CruddemoApplication.class, args); } - @Bean - public CommandLineRunner commandLineRunner(StudentDAO studentDAO) { - return runner ->{ - createMultipleStudent(studentDAO); - //queryForStudent(studentDAO); - //queryForStudentByLastName(studentDAO); - //updateStudent(studentDAO); - //deleteStudent(studentDAO); - //deleteAllStudent(studentDAO); - }; - } - - private void deleteAllStudent(StudentDAO studentDAO) { - int numOfRowDeleted = studentDAO.deleteAll(); - System.out.println("Number of row deleted: " + numOfRowDeleted); - } - - - private void deleteStudent(StudentDAO studentDAO) { - Student student = studentDAO.findById(3); - System.out.println("Student to be deleted: "+student); - studentDAO.delete(3); - } - - private void updateStudent(StudentDAO studentDAO) { - //retrieve the student base on id - Student student = studentDAO.findById(1); - - //change the lastname - System.out.println("updating the student ..."); - student.setLastName("Lobby"); - - //update the student - studentDAO.update(student); - - //Display the updated student - System.out.println("student updated: "+student); - } - - private void queryForStudentByLastName(StudentDAO studentDAO) { - //get the list of student - List students = studentDAO.findByLastName("Doe"); - for (Student student : students) { - System.out.println(student); - } - } - - private void queryForStudent(StudentDAO studentDAO) { - //get the list of student - List students = studentDAO.findAll(); - - //display the student - for (Student student : students) { - System.out.println(student); - } - } - - private void createStudent(StudentDAO studentDAO) { - System.out.println("Creating student ...."); - Student student = new Student("paul","okeke","paul@gmail.com"); - - //saving the student object - System.out.println("saving student ...."); - studentDAO.save(student); - - System.out.println("Student saved successfully.Generated Id: " + student.getId()); - } - - private void createMultipleStudent(StudentDAO studentDAO) { - System.out.println("Creating student ...."); - Student student = new Student("John","smith","smith@gmail.com"); - Student student1 = new Student("Mary","Doe","doe@gmail.com"); - Student student2= new Student("Eric","onu","onu@gmail.com"); - - //saving the multiple student object - studentDAO.save(student); - studentDAO.save(student1); - studentDAO.save(student2); - - System.out.println("Students saved successfully"); - } - - private void readStudent(StudentDAO studentDAO) { - //create a student object - System.out.println("Creating student ...."); - Student student = new Student("Onowu","Igbo","igbo@gmail.com"); - - //save the save - studentDAO.save(student); - System.out.println("Student saved successfully"); - - //display id of the saved student - long theId= student.getId(); - System.out.println("saved student successfully.Generated Id: " + theId); - - //retrieved the student based on the id ie the primary key - System.out.println("retrieving student saved successfully"); - //Student savedStudent= studentDAO.findById(theId); - - //display the student - //System.out.println("student retrieved successfully: " + savedStudent); - } - - } diff --git a/src/main/java/com/ebrains/cruddemo/dao/StudentDAO.java b/src/main/java/com/ebrains/cruddemo/dao/StudentDAO.java deleted file mode 100644 index d5717a9..0000000 --- a/src/main/java/com/ebrains/cruddemo/dao/StudentDAO.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ebrains.cruddemo.dao; - -import com.ebrains.cruddemo.entity.Student; - -import java.util.List; - -public interface StudentDAO { - void save(Student student); - - Student findById(Integer id); - - List findAll(); - - List findByLastName(String lastName); - - void update(Student student); - - void delete(Integer id); - - int deleteAll(); - -} diff --git a/src/main/java/com/ebrains/cruddemo/dao/StudentDAOImpl.java b/src/main/java/com/ebrains/cruddemo/dao/StudentDAOImpl.java deleted file mode 100644 index 3aa5d08..0000000 --- a/src/main/java/com/ebrains/cruddemo/dao/StudentDAOImpl.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.ebrains.cruddemo.dao; - -import com.ebrains.cruddemo.entity.Student; -import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; - -@Repository -public class StudentDAOImpl implements StudentDAO { - //we define feilds for entity manager - private EntityManager entityManager; - - //eject entity manager using constructor injection - public StudentDAOImpl(EntityManager entityManager) { - this.entityManager = entityManager; - } - - //implement the save method - //this method saves the studnet to the database - @Transactional - @Override - public void save(Student student) { - entityManager.persist(student); - } - - @Override - public Student findById(Integer id) { - //Student.class is the entity class - return entityManager.find(Student.class, id); - } - - @Override - public List findAll() { - //create the query - TypedQuery query = entityManager.createQuery("from Student", Student.class); - //TypedQuery query = entityManager.createQuery("from Student order by lastName desc", Student.class); - - //return the list of student - return query.getResultList(); - } - - @Override - public List findByLastName(String lastName) { - //create the query - TypedQuery query = entityManager.createQuery("from Student where lastName=:theData", Student.class); - - //set query parameter - query.setParameter("theData", lastName); - - //return the list of student with the last name - return query.getResultList(); - } - - @Override - @Transactional - public void update(Student student) { - entityManager.merge(student); - } - - @Override - @Transactional - public void delete(Integer id) { - //retrieve the student - Student student = entityManager.find(Student.class, id); - - //delete the student - entityManager.remove(student); - } - - @Override - @Transactional - public int deleteAll() { - return entityManager.createQuery("delete from Student").executeUpdate(); - - } -} diff --git a/src/main/java/com/ebrains/cruddemo/entity/Student.java b/src/main/java/com/ebrains/cruddemo/entity/Student.java index e230fef..c043341 100644 --- a/src/main/java/com/ebrains/cruddemo/entity/Student.java +++ b/src/main/java/com/ebrains/cruddemo/entity/Student.java @@ -1,70 +1,20 @@ package com.ebrains.cruddemo.entity; import jakarta.persistence.*; +import lombok.Data; -@Entity -@Table(name="student") +@Entity(name="student") +@Table +@Data public class Student { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") + @Column private Long id; - @Column(name = "first_name") + @Column private String firstName; - @Column(name = "last_name") + @Column private String lastName; - @Column(name = "email") + @Column private String email; - - public Student() { - - } - public Student(String firstName, String lastName, String email) { - this.firstName = firstName; - this.lastName = lastName; - this.email = email; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - @Override - public String toString() { - return "Student{" + - "id=" + id + - ", firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", email='" + email + '\'' + - '}'; - } } diff --git a/src/main/java/com/ebrains/cruddemo/repository/StudentRepository.java b/src/main/java/com/ebrains/cruddemo/repository/StudentRepository.java new file mode 100644 index 0000000..c14347f --- /dev/null +++ b/src/main/java/com/ebrains/cruddemo/repository/StudentRepository.java @@ -0,0 +1,9 @@ +package com.ebrains.cruddemo.repository; + +import com.ebrains.cruddemo.entity.Student; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface StudentRepository extends CrudRepository { +} diff --git a/src/main/java/com/ebrains/cruddemo/service/StudentService.java b/src/main/java/com/ebrains/cruddemo/service/StudentService.java new file mode 100644 index 0000000..0b8fb59 --- /dev/null +++ b/src/main/java/com/ebrains/cruddemo/service/StudentService.java @@ -0,0 +1,11 @@ +package com.ebrains.cruddemo.service; + +import com.ebrains.cruddemo.entity.Student; +import org.springframework.stereotype.Service; + +@Service +public interface StudentService { + Student getStudentById(long id); + void saveStudent(Student student); + void deleteStudentById(long id); +} diff --git a/src/main/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImpl.java b/src/main/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImpl.java new file mode 100644 index 0000000..3242e21 --- /dev/null +++ b/src/main/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImpl.java @@ -0,0 +1,28 @@ +package com.ebrains.cruddemo.serviceimpl; + +import com.ebrains.cruddemo.entity.Student; +import com.ebrains.cruddemo.repository.StudentRepository; +import com.ebrains.cruddemo.service.StudentService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class StudentServiceImpl implements StudentService { + private final StudentRepository studentRepository; + + @Override + public Student getStudentById(long id) { + return studentRepository.findById(id).orElse(null); + } + + @Override + public void saveStudent(Student student) { + studentRepository.save(student); + } + + @Override + public void deleteStudentById(long id) { + studentRepository.deleteById(id); + } +} diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties new file mode 100644 index 0000000..c576915 --- /dev/null +++ b/src/main/resources/application-test.properties @@ -0,0 +1,6 @@ +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password= +spring.datasource.driver-class-name=org.h2.Driver +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ba7942f..5145dd5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,9 +1,10 @@ spring.application.name=cruddemo -spring.datasource.url=jdbc:mysql://localhost:3306/student_tracker -spring.datasource.username=springstudent -spring.datasource.password=springstudent +spring.datasource.url=jdbc:mysql://localhost:33070/student_tracker +spring.datasource.username=root +spring.datasource.password=password spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect #Turn off the springboot logo spring.main.banner-mode=off diff --git a/src/test/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImplTest.java b/src/test/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImplTest.java new file mode 100644 index 0000000..d3cff93 --- /dev/null +++ b/src/test/java/com/ebrains/cruddemo/serviceimpl/StudentServiceImplTest.java @@ -0,0 +1,60 @@ +package com.ebrains.cruddemo.serviceimpl; + +import com.ebrains.cruddemo.entity.Student; +import com.ebrains.cruddemo.repository.StudentRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.context.ActiveProfiles; +import java.util.Optional; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@ActiveProfiles("test") // ensures application-test.properties is used +public class StudentServiceImplTest { + + private final StudentRepository studentRepository = mock(StudentRepository.class); + + @InjectMocks + private StudentServiceImpl studentService; + + @Test + void getStudentById_found_returnsStudent() { + Student s = new Student(); + when(studentRepository.findById(1L)).thenReturn(Optional.of(s)); + + Student result = studentService.getStudentById(1L); + + assertSame(s, result); + verify(studentRepository).findById(1L); + } + + @Test + void getStudentById_notFound_returnsNull() { + when(studentRepository.findById(2L)).thenReturn(Optional.empty()); + + Student result = studentService.getStudentById(2L); + + assertNull(result); + verify(studentRepository).findById(2L); + } + + @Test + void saveStudent_delegatesToRepository() { + Student s = new Student(); + + studentService.saveStudent(s); + + verify(studentRepository).save(s); + } + + @Test + void deleteStudentById_delegatesToRepository() { + studentService.deleteStudentById(3L); + + verify(studentRepository).deleteById(3L); + } +}