diff --git a/.github/workflows/RELEASE-docker-build-and-push.yml b/.github/workflows/RELEASE-docker-build-and-push.yml new file mode 100644 index 0000000..2262705 --- /dev/null +++ b/.github/workflows/RELEASE-docker-build-and-push.yml @@ -0,0 +1,57 @@ +name: RELEASE - Build and push Docker official image to Github packages +on: + push: + branches: + - main + paths-ignore: + - "**/README.md" + - ".github/workflows/**" + - "**/.gitignore" + workflow_dispatch: + +env: + IMAGE_NAME: ghcr.io/${{ github.repository }}/sympoll-group-service + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to GitHub Packages + run: echo "${{ secrets.PACKAGE_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin + + - name: Get current date + id: date + run: echo "::set-output name=DATE::$(date +'%d.%m.%Y')" + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: ~/.docker/builder + key: ${{ runner.os }}-build-${{ github.sha }} # Cache key based on OS and commit SHA + restore-keys: | # Check for existing cache based on previous builds with same OS and SHA + ${{ runner.os }}-build- + + - name: Build and push Docker image + id: docker_build + uses: docker/build-push-action@v2 + with: + context: . + file: Dockerfile + push: true + platforms: linux/amd64,linux/arm64 + tags: | + ${{ env.IMAGE_NAME }}:${{ steps.date.outputs.DATE }} + ${{ env.IMAGE_NAME }}:latest + cache-from: type=gha,key=${{ runner.os }}-build-${{ github.sha }} # Use cached layers based on commit SHA + cache-to: type=gha,mode=max # Enable caching to GitHub Actions + diff --git a/.github/workflows/TEST_CONTAINER-docker-build-and-push.yml b/.github/workflows/TEST_CONTAINER-docker-build-and-push.yml new file mode 100644 index 0000000..0a618dc --- /dev/null +++ b/.github/workflows/TEST_CONTAINER-docker-build-and-push.yml @@ -0,0 +1,49 @@ +name: TEST CONTAINER - Build and push Docker test image to Github packages +on: + workflow_dispatch: + +env: + IMAGE_NAME: ghcr.io/${{ github.repository }}/sympoll-group-service-test + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to GitHub Packages + run: echo "${{ secrets.PACKAGE_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin + + - name: Get current date + id: date + run: echo "::set-output name=DATE::$(date +'%d.%m.%Y')" + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: ~/.docker/builder + key: ${{ runner.os }}-build-${{ github.sha }} # Cache key based on OS and commit SHA + restore-keys: | # Check for existing cache based on previous builds with same OS and SHA + ${{ runner.os }}-build- + + - name: Build and push Docker image + id: docker_build + uses: docker/build-push-action@v2 + with: + context: . + file: Dockerfile + push: true + platforms: linux/amd64,linux/arm64 + tags: | + ${{ env.IMAGE_NAME }}:${{ steps.date.outputs.DATE }} + ${{ env.IMAGE_NAME }}:latest + cache-from: type=gha,key=${{ runner.os }}-build-${{ github.sha }} # Use cached layers based on commit SHA + cache-to: type=gha,mode=max # Enable caching to GitHub Actions diff --git a/docker/init.sql b/docker/init.sql new file mode 100644 index 0000000..9c6ef41 --- /dev/null +++ b/docker/init.sql @@ -0,0 +1,16 @@ +-- Group Service Schema +CREATE TABLE groups +( + group_id VARCHAR(255) PRIMARY KEY, + group_name VARCHAR(255), + description TEXT, + creator_id UUID, + time_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE members +( + user_id uuid NOT NULL, + group_id VARCHAR(255) REFERENCES groups(group_id) ON DELETE CASCADE NOT NULL, + PRIMARY KEY (group_id, user_id) +); \ No newline at end of file diff --git a/pom.xml b/pom.xml index dc32c63..d5713ff 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,13 @@ spring-boot-starter-data-jpa 3.2.4 + + + org.projectlombok + lombok + 1.18.34 + provided + diff --git a/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/controller/ServiceController.java b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/controller/ServiceController.java new file mode 100644 index 0000000..c974828 --- /dev/null +++ b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/controller/ServiceController.java @@ -0,0 +1,26 @@ +package com.MTAPizza.Sympoll.groupmanagementservice.controller; + +import com.MTAPizza.Sympoll.groupmanagementservice.dto.request.GroupCreateRequest; +import com.MTAPizza.Sympoll.groupmanagementservice.dto.response.GroupResponse; +import com.MTAPizza.Sympoll.groupmanagementservice.service.GroupService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("api/group") +@RequiredArgsConstructor +public class ServiceController { + private final GroupService groupService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public GroupResponse createGroup(@RequestBody GroupCreateRequest groupCreateRequest) { + log.info("Received request to create a group"); + log.debug("Group to create received: {}", groupCreateRequest); + return groupService.createGroup(groupCreateRequest); + } + +} diff --git a/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/dto/request/GroupCreateRequest.java b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/dto/request/GroupCreateRequest.java new file mode 100644 index 0000000..aae7054 --- /dev/null +++ b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/dto/request/GroupCreateRequest.java @@ -0,0 +1,10 @@ +package com.MTAPizza.Sympoll.groupmanagementservice.dto.request; + +import java.util.UUID; + +public record GroupCreateRequest( + String groupId, // can be null, if so then generate a group number + String groupName, + String description, + UUID creatorId +) {} \ No newline at end of file diff --git a/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/dto/response/GroupResponse.java b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/dto/response/GroupResponse.java new file mode 100644 index 0000000..9503e0a --- /dev/null +++ b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/dto/response/GroupResponse.java @@ -0,0 +1,14 @@ +package com.MTAPizza.Sympoll.groupmanagementservice.dto.response; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +public record GroupResponse ( + String groupId, + String groupName, + String description, + UUID creatorId, + LocalDateTime timeCreated, + List membersList +) {} diff --git a/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/dto/response/MemberResponse.java b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/dto/response/MemberResponse.java new file mode 100644 index 0000000..2afe8c3 --- /dev/null +++ b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/dto/response/MemberResponse.java @@ -0,0 +1,7 @@ +package com.MTAPizza.Sympoll.groupmanagementservice.dto.response; + +import java.util.UUID; + +public record MemberResponse( + UUID userId +) {} diff --git a/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/model/Group.java b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/model/Group.java new file mode 100644 index 0000000..3ad852e --- /dev/null +++ b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/model/Group.java @@ -0,0 +1,54 @@ +package com.MTAPizza.Sympoll.groupmanagementservice.model; + +import com.MTAPizza.Sympoll.groupmanagementservice.dto.response.GroupResponse; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "groups") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Group { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private String groupId; + + @Column(name = "group_name") + private String groupName; + + @Column(name = "description") + private String description; + + @Column(name = "created_by_user") + private UUID creatorId; + + @Column(name = "time_created") + private final LocalDateTime timeCreated = LocalDateTime.now(); // Initialize to the current time. + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + @JoinColumn(name = "group_id") + private List membersList = new ArrayList<>(); // Initialize to an empty members list. + + // TODO: Add Admins list, will be initialized with the creatorId as the only admin. + + public GroupResponse toGroupResponse() { + return new GroupResponse( + groupId, + groupName, + description, + creatorId, + timeCreated, + membersList.stream().map(Member::toMemberResponse).toList() // Convert to member response + ); + } +} diff --git a/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/model/Member.java b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/model/Member.java new file mode 100644 index 0000000..0cdbe01 --- /dev/null +++ b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/model/Member.java @@ -0,0 +1,27 @@ +package com.MTAPizza.Sympoll.groupmanagementservice.model; + +import com.MTAPizza.Sympoll.groupmanagementservice.dto.response.MemberResponse; +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Entity +@Table(name = "members") +@NoArgsConstructor +@Data +public class Member { + @Column(name = "group_id") + private String groupId; + + @Id + @Column(name = "user_id") + private UUID userId; + + public MemberResponse toMemberResponse() { + return new MemberResponse( + userId + ); + } +} diff --git a/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/repository/GroupRepository.java b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/repository/GroupRepository.java new file mode 100644 index 0000000..a54cb79 --- /dev/null +++ b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/repository/GroupRepository.java @@ -0,0 +1,9 @@ +package com.MTAPizza.Sympoll.groupmanagementservice.repository; + +import com.MTAPizza.Sympoll.groupmanagementservice.model.Group; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface GroupRepository extends JpaRepository { +} diff --git a/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/repository/MemberRepository.java b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/repository/MemberRepository.java new file mode 100644 index 0000000..10d5d5b --- /dev/null +++ b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/repository/MemberRepository.java @@ -0,0 +1,9 @@ +package com.MTAPizza.Sympoll.groupmanagementservice.repository; + +import com.MTAPizza.Sympoll.groupmanagementservice.model.Member; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MemberRepository extends JpaRepository { +} diff --git a/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/service/GroupService.java b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/service/GroupService.java new file mode 100644 index 0000000..50ae207 --- /dev/null +++ b/src/main/java/com/MTAPizza/Sympoll/groupmanagementservice/service/GroupService.java @@ -0,0 +1,33 @@ +package com.MTAPizza.Sympoll.groupmanagementservice.service; + +import com.MTAPizza.Sympoll.groupmanagementservice.dto.request.GroupCreateRequest; +import com.MTAPizza.Sympoll.groupmanagementservice.dto.response.GroupResponse; +import com.MTAPizza.Sympoll.groupmanagementservice.model.Group; +import com.MTAPizza.Sympoll.groupmanagementservice.repository.GroupRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class GroupService { + private final GroupRepository groupRepository; + + public GroupResponse createGroup(GroupCreateRequest groupCreateRequest) { + // TODO: Validate request + + // TODO: Add creator Id into a new list of admins + Group createdGroup = Group.builder() + .groupId(groupCreateRequest.groupId()) + .groupName(groupCreateRequest.groupName()) + .description(groupCreateRequest.description()) + .creatorId(groupCreateRequest.creatorId()) + .build(); + + groupRepository.save(createdGroup); + log.info("Group with ID - '{}' was created by - '{}'", createdGroup.getGroupId(), createdGroup.getCreatorId()); + + return createdGroup.toGroupResponse(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d4c4cfc..ad34d9c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,14 @@ -spring.application.name=group-management-service +server.port=8085 +spring.application.name=group-service +spring.datasource.url=jdbc:postgresql://group-db:5435/postgres +spring.datasource.username=postgres +spring.datasource.password=1 +spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect + +# Use These Settings to Debug Spring Boot: +# --------------------------------------- +# logging.level.root=DEBUG +# logging.level.org.springframework.web=DEBUG +# logging.level.org.springframework=DEBUG +# logging.level.org.hibernate=ERROR \ No newline at end of file