From e771702ff5ecc8efade34ecad7fca71be7b123f6 Mon Sep 17 00:00:00 2001 From: ferid333 <135500346+ferid333@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:21:02 +1000 Subject: [PATCH] DVLP-011: Add CI workflow --- .dockerignore | 8 + .github/workflows/ci.yml | 102 ++++ Dockerfile | 23 + README.md | 442 +++++++++++++++++- docker-compose.yml | 86 ++++ .../messaging/chat/config/OpenApiConfig.java | 1 - .../controller/ConversationController.java | 1 - .../messaging/chat/dao/entity/Attachment.java | 12 +- .../chat/dao/entity/Conversation.java | 9 +- .../dao/entity/ConversationDeliveryState.java | 10 +- .../dao/entity/ConversationParticipant.java | 11 +- .../messaging/chat/dao/entity/Message.java | 14 +- .../chat/dao/entity/ReadDeliveryState.java | 10 +- .../chat/service/WebSocketTicketService.java | 2 - 14 files changed, 719 insertions(+), 12 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d9021c8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.gradle +.gradle-user +.idea +build +out +.git +.github +*.iml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1f9fbd7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,102 @@ +name: CI + +on: + pull_request: + branches: + - master + - develop + push: + branches: + - master + - develop + +permissions: + contents: read + +jobs: + pull-request-rules: + name: Pull Request Rules + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate PR title + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + if [[ ! "$PR_TITLE" =~ ^DVLP ]]; then + echo "PR title must start with DVLP." + echo "Actual title: $PR_TITLE" + exit 1 + fi + + title_length=${#PR_TITLE} + if (( title_length > 120 )); then + echo "PR title must not be longer than 120 characters." + echo "Actual length: $title_length" + exit 1 + fi + + - name: Validate commit titles + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + invalid_commits=$(git log --format='%H%x09%s' "$BASE_SHA..$HEAD_SHA" | awk -F '\t' '$2 !~ /^DVLP/ { print }') + + if [[ -n "$invalid_commits" ]]; then + echo "Every commit title in the PR must start with DVLP." + echo "$invalid_commits" + exit 1 + fi + + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Make Gradle wrapper executable + run: chmod +x ./gradlew + + - name: Run unit tests + run: ./gradlew test + + checkstyle: + name: Checkstyle + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Make Gradle wrapper executable + run: chmod +x ./gradlew + + - name: Run Checkstyle + run: ./gradlew checkstyleMain checkstyleTest diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4848709 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM eclipse-temurin:21-jdk AS builder + +WORKDIR /workspace + +COPY gradlew . +COPY gradle gradle +COPY build.gradle . +COPY settings.gradle . +COPY src src +COPY config config + +RUN chmod +x ./gradlew +RUN ./gradlew bootJar --no-daemon + +FROM eclipse-temurin:21-jre + +WORKDIR /app + +COPY --from=builder /workspace/build/libs/*.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/README.md b/README.md index a6303b2..011ddec 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,440 @@ -# Messaging-System -Messaging System Backend +# Messaging Chat Service + +Backend service for a chat/messaging system. It provides REST APIs for conversations, messages, read/delivery state, and attachment upload flow, plus STOMP over WebSocket for real-time message delivery. + +## Tech Stack + +| Area | Technology | +| --- | --- | +| Language | Java 21 | +| Framework | Spring Boot 4.0.5 | +| Build tool | Gradle | +| REST API | Spring Web / Spring MVC | +| WebSocket | Spring WebSocket + STOMP + SockJS | +| Persistence | Spring Data JPA, Hibernate | +| Database | PostgreSQL 16 | +| Migrations | Liquibase | +| Cache / WebSocket tickets | Redis | +| Messaging | RabbitMQ, Spring AMQP | +| Object storage | AWS S3 SDK v2 | +| API documentation | springdoc-openapi / Swagger UI | +| Mapping | MapStruct | +| Validation | Jakarta Bean Validation | +| Boilerplate reduction | Lombok | +| Testing | Spock, Groovy, Spring Boot Test | +| Containerization | Docker, Docker Compose | +| Code quality | Checkstyle | + +## Main Features + +- Create conversations with multiple participants. +- List conversations for a user with cursor pagination. +- Send text, image, or file messages. +- Fetch messages for a conversation with pagination. +- Track delivered and read message state. +- Send and receive chat events over WebSocket/STOMP. +- Generate S3 presigned upload URLs for attachments. +- Confirm uploaded attachments by checking S3 object metadata. +- Publish message notification events to RabbitMQ after database commit. +- Manage schema changes through Liquibase migrations. + +## Project Structure + +```text +. ++-- src/main/java/com/messaging/chat +| +-- config # WebSocket, RabbitMQ, S3, OpenAPI configuration +| +-- controller # REST and WebSocket controllers +| +-- dao # JPA entities and repositories +| +-- mapper # MapStruct mappers +| +-- model # DTOs, constants, exceptions +| +-- service # Business logic +| +-- util # Utility classes ++-- src/main/resources +| +-- application.yaml +| +-- db/changelog # Liquibase changelogs ++-- build.gradle ++-- Dockerfile ++-- docker-compose.yml +``` + +## Prerequisites + +- Java 21 +- Docker and Docker Compose +- Gradle wrapper included in the project +- Optional: an AWS S3 bucket, LocalStack, or MinIO-compatible setup for attachment upload testing + +## Running With Docker Compose + +Build and start all Compose services: + +```bash +docker compose up --build +``` + +The Compose file starts: + +| Service | URL / Port | +| --- | --- | +| Chat service | `http://localhost:8080` | +| PostgreSQL | `localhost:5432` | +| Redis | `localhost:6379` | +| RabbitMQ AMQP | `localhost:5672` | +| RabbitMQ Management UI | `http://localhost:15672` | + +RabbitMQ default credentials: + +```text +guest / guest +``` + +Stop services: + +```bash +docker compose down +``` + +Stop services and remove volumes: + +```bash +docker compose down -v +``` + +## Running Locally Without Dockerizing the App + +Start infrastructure only: + +```bash +docker compose up postgres redis rabbitmq +``` + +Then run the app from the project root: + +```bash +./gradlew bootRun +``` + +On Windows PowerShell: + +```powershell +.\gradlew.bat bootRun +``` + +By default, `application.yaml` points PostgreSQL to `localhost:5432`. When the app runs inside Docker, `docker-compose.yml` overrides this to use the Compose service name `postgres`. + +## Configuration + +Spring Boot loads configuration from `src/main/resources/application.yaml`. Environment variables from Docker Compose override those values at startup through Spring Boot externalized configuration. + +For example: + +```yaml +SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/chat +``` + +maps to: + +```yaml +spring: + datasource: + url: jdbc:postgresql://postgres:5432/chat +``` + +Important environment variables used by `chat-service`: + +| Environment variable | Spring property | Purpose | +| --- | --- | --- | +| `SPRING_DATASOURCE_URL` | `spring.datasource.url` | PostgreSQL JDBC URL | +| `SPRING_DATASOURCE_USERNAME` | `spring.datasource.username` | PostgreSQL username | +| `SPRING_DATASOURCE_PASSWORD` | `spring.datasource.password` | PostgreSQL password | +| `SPRING_RABBITMQ_HOST` | `spring.rabbitmq.host` | RabbitMQ host | +| `SPRING_RABBITMQ_PORT` | `spring.rabbitmq.port` | RabbitMQ port | +| `SPRING_RABBITMQ_USERNAME` | `spring.rabbitmq.username` | RabbitMQ username | +| `SPRING_RABBITMQ_PASSWORD` | `spring.rabbitmq.password` | RabbitMQ password | +| `SPRING_DATA_REDIS_HOST` | `spring.data.redis.host` | Redis host | +| `SPRING_DATA_REDIS_PORT` | `spring.data.redis.port` | Redis port | +| `STORAGE_S3_BUCKET_NAME` | `storage.s3.bucket-name` | S3 bucket for attachments | +| `STORAGE_S3_REGION` | `storage.s3.region` | S3 region | +| `STORAGE_S3_ACCESS_KEY_ID` | `storage.s3.access-key-id` | S3 access key | +| `STORAGE_S3_SECRET_ACCESS_KEY` | `storage.s3.secret-access-key` | S3 secret key | +| `RABBITMQ_NOTIFICATION_QUEUE` | `rabbitmq.notification-queue` | Notification queue name | +| `RABBITMQ_NOTIFICATION_EXCHANGE` | `rabbitmq.notification-exchange` | Notification exchange name | +| `RABBITMQ_NOTIFICATION_ROUTING_KEY` | `rabbitmq.notification-routing-key` | Notification routing key | +| `RABBITMQ_NOTIFICATION_DLQ` | `rabbitmq.notification-dlq` | Dead-letter queue name | +| `RABBITMQ_NOTIFICATION_DLX` | `rabbitmq.notification-dlx` | Dead-letter exchange name | + +Note: `docker-compose.yml` does not currently start an S3-compatible service. The S3 values in Compose are placeholders unless you connect them to a real S3 bucket or add a local S3-compatible service such as LocalStack or MinIO. + +## API Documentation + +When the app is running, OpenAPI documentation is available at: + +```text +http://localhost:8080/swagger-ui/index.html +``` + +The generated OpenAPI JSON is available at: + +```text +http://localhost:8080/v3/api-docs +``` + +## REST API Overview + +All REST endpoints use the `userId` request header to identify the current user. + +### Conversations + +Create a conversation: + +```http +POST /ms-chat/conversations +userId: 1 +Content-Type: application/json + +{ + "participantUserIds": [2, 3] +} +``` + +List conversations: + +```http +GET /ms-chat/conversations?limit=20&cursor=50 +userId: 1 +``` + +List messages in a conversation: + +```http +GET /ms-chat/conversations/20/messages?limit=30&beforeMessageId=171 +userId: 1 +``` + +Send a message: + +```http +POST /ms-chat/conversations/20/messages +userId: 1 +Content-Type: application/json + +{ + "clientMessageId": "550e8400-e29b-41d4-a716-446655440000", + "type": "TEXT", + "textContent": "Hello", + "attachmentIds": [] +} +``` + +Mark messages delivered: + +```http +POST /ms-chat/conversations/20/delivery +userId: 1 +Content-Type: application/json + +{ + "messageId": 100 +} +``` + +Mark messages read: + +```http +POST /ms-chat/conversations/20/read +userId: 1 +Content-Type: application/json + +{ + "messageId": 100 +} +``` + +Supported message types: + +```text +TEXT, IMAGE, FILE +``` + +### Attachments + +Create a presigned upload URL: + +```http +POST /ms-chat/attachments/presign-upload +userId: 1 +Content-Type: application/json + +{ + "fileName": "photo.png", + "contentType": "image/png", + "fileSize": 1048576, + "checksum": "1B2M2Y8AsgTpgAmY7PhCfg==" +} +``` + +The response contains: + +- `attachmentId` +- `storageKey` +- `uploadUrl` +- HTTP method, currently `PUT` +- expiration time +- required upload headers + +After uploading the file to S3 with the returned URL, confirm the attachment: + +```http +POST /ms-chat/attachments/10/confirm +userId: 1 +``` + +Attachment rules: + +- Maximum file size is less than 5 MB. +- Executable content types are rejected. +- Executable file extensions are rejected. +- Confirmation checks that the uploaded S3 object size and content type match the presigned request. + +## WebSocket / STOMP + +WebSocket endpoint: + +```text +/ws-chat +``` + +SockJS is enabled. + +The connection requires a query parameter: + +```text +/ws-chat?ticket= +``` + +The ticket is resolved from Redis using this key format: + +```text +chat:ws-ticket: +``` + +The Redis value must be the user id. The ticket is consumed with `GETDEL`, so it is single-use. + +Application destination prefix: + +```text +/app +``` + +User destination prefix: + +```text +/user +``` + +Client sends messages to: + +| Action | STOMP destination | +| --- | --- | +| Send message | `/app/conversations/{conversationId}/messages` | +| Mark delivered | `/app/conversations/{conversationId}/delivery` | +| Mark read | `/app/conversations/{conversationId}/read` | + +Client subscribes to user queues: + +| Event | STOMP subscription | +| --- | --- | +| New messages | `/user/queue/messages` | +| Delivery updates | `/user/queue/delivery` | +| Read updates | `/user/queue/read` | + +## RabbitMQ Notifications + +When a message is sent, the service publishes notification events after the database transaction commits. + +Configured RabbitMQ components: + +- Notification queue +- Topic exchange +- Routing key binding +- Dead-letter queue +- Dead-letter exchange + +The queue and exchange names are configured through `rabbitmq.notification-*` properties. + +## Database Migrations + +Liquibase runs automatically on application startup. + +Master changelog: + +```text +src/main/resources/db/changelog/db.changelog-master.yaml +``` + +The application uses: + +```yaml +spring: + jpa: + hibernate: + ddl-auto: none +``` + +That means Hibernate does not create/update schema automatically; schema changes should be added through Liquibase changelogs. + +## Build, Test, and Quality Checks + +Build the project: + +```bash +./gradlew build +``` + +Run tests: + +```bash +./gradlew test +``` + +Run Checkstyle: + +```bash +./gradlew checkstyleMain checkstyleTest +``` + +Create the Spring Boot jar: + +```bash +./gradlew bootJar +``` + +On Windows PowerShell, use `.\gradlew.bat` instead of `./gradlew`. + +## Docker Image + +The `Dockerfile` uses a multi-stage build: + +1. `eclipse-temurin:21-jdk` builds the Spring Boot jar. +2. `eclipse-temurin:21-jre` runs the jar. + +The app listens on port `8080`. + +## Useful Local URLs + +| Tool | URL | +| --- | --- | +| Application | `http://localhost:8080` | +| Swagger UI | `http://localhost:8080/swagger-ui/index.html` | +| OpenAPI JSON | `http://localhost:8080/v3/api-docs` | +| Actuator health | `http://localhost:8080/actuator/health` | +| RabbitMQ UI | `http://localhost:15672` | + +## Development Notes + +- REST requests use the `userId` header instead of a full authentication system. +- WebSocket authentication uses a single-use Redis ticket. +- S3 presigned upload URLs expire after `storage.s3.presign-duration-minutes`, currently configured as `5`. +- Message notification publishing is transactional: RabbitMQ publishing runs after the database commit. +- Docker Compose service names are used for container-to-container networking, for example `postgres`, `redis`, and `rabbitmq`. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..696a672 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,86 @@ +services: + postgres: + image: postgres:16-alpine + container_name: chat-postgres + environment: + POSTGRES_DB: chat + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - "5432:5432" + volumes: + - postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d chat"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + container_name: chat-redis + ports: + - "6379:6379" + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + rabbitmq: + image: rabbitmq:3-management-alpine + container_name: chat-rabbitmq + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + ports: + - "5672:5672" + - "15672:15672" + volumes: + - rabbitmq-data:/var/lib/rabbitmq + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + chat-service: + build: + context: . + dockerfile: Dockerfile + container_name: chat-service + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + rabbitmq: + condition: service_healthy + environment: + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/chat + SPRING_DATASOURCE_USERNAME: postgres + SPRING_DATASOURCE_PASSWORD: postgres + SPRING_RABBITMQ_HOST: rabbitmq + SPRING_RABBITMQ_PORT: 5672 + SPRING_RABBITMQ_USERNAME: guest + SPRING_RABBITMQ_PASSWORD: guest + SPRING_DATA_REDIS_HOST: redis + SPRING_DATA_REDIS_PORT: 6379 + STORAGE_S3_BUCKET_NAME: local-chat-bucket + STORAGE_S3_REGION: us-east-1 + STORAGE_S3_ACCESS_KEY_ID: local-access-key + STORAGE_S3_SECRET_ACCESS_KEY: local-secret-key + RABBITMQ_NOTIFICATION_QUEUE: notificationQueue + RABBITMQ_NOTIFICATION_EXCHANGE: notificationExchange + RABBITMQ_NOTIFICATION_ROUTING_KEY: notificationRouting + RABBITMQ_NOTIFICATION_DLQ: notificationDlq + RABBITMQ_NOTIFICATION_DLX: notificationDlx + ports: + - "8080:8080" + +volumes: + postgres-data: + redis-data: + rabbitmq-data: diff --git a/src/main/java/com/messaging/chat/config/OpenApiConfig.java b/src/main/java/com/messaging/chat/config/OpenApiConfig.java index 03bb6dc..f34ce56 100644 --- a/src/main/java/com/messaging/chat/config/OpenApiConfig.java +++ b/src/main/java/com/messaging/chat/config/OpenApiConfig.java @@ -1,7 +1,6 @@ package com.messaging.chat.config; import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.info.Contact; import io.swagger.v3.oas.annotations.info.Info; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/messaging/chat/controller/ConversationController.java b/src/main/java/com/messaging/chat/controller/ConversationController.java index bdfe59c..5fdc2a6 100644 --- a/src/main/java/com/messaging/chat/controller/ConversationController.java +++ b/src/main/java/com/messaging/chat/controller/ConversationController.java @@ -13,7 +13,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/src/main/java/com/messaging/chat/dao/entity/Attachment.java b/src/main/java/com/messaging/chat/dao/entity/Attachment.java index d6693fe..81ca083 100644 --- a/src/main/java/com/messaging/chat/dao/entity/Attachment.java +++ b/src/main/java/com/messaging/chat/dao/entity/Attachment.java @@ -1,7 +1,17 @@ package com.messaging.chat.dao.entity; import com.messaging.chat.model.constant.FileStatus; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/messaging/chat/dao/entity/Conversation.java b/src/main/java/com/messaging/chat/dao/entity/Conversation.java index df206de..2875257 100644 --- a/src/main/java/com/messaging/chat/dao/entity/Conversation.java +++ b/src/main/java/com/messaging/chat/dao/entity/Conversation.java @@ -1,6 +1,13 @@ package com.messaging.chat.dao.entity; -import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/messaging/chat/dao/entity/ConversationDeliveryState.java b/src/main/java/com/messaging/chat/dao/entity/ConversationDeliveryState.java index dbe2d0c..376ac6d 100644 --- a/src/main/java/com/messaging/chat/dao/entity/ConversationDeliveryState.java +++ b/src/main/java/com/messaging/chat/dao/entity/ConversationDeliveryState.java @@ -1,6 +1,14 @@ package com.messaging.chat.dao.entity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/messaging/chat/dao/entity/ConversationParticipant.java b/src/main/java/com/messaging/chat/dao/entity/ConversationParticipant.java index 06dd7e0..3dd9ff3 100644 --- a/src/main/java/com/messaging/chat/dao/entity/ConversationParticipant.java +++ b/src/main/java/com/messaging/chat/dao/entity/ConversationParticipant.java @@ -1,7 +1,16 @@ package com.messaging.chat.dao.entity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/messaging/chat/dao/entity/Message.java b/src/main/java/com/messaging/chat/dao/entity/Message.java index 960ba99..bb22cae 100644 --- a/src/main/java/com/messaging/chat/dao/entity/Message.java +++ b/src/main/java/com/messaging/chat/dao/entity/Message.java @@ -1,7 +1,19 @@ package com.messaging.chat.dao.entity; import com.messaging.chat.model.constant.MessageType; -import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/messaging/chat/dao/entity/ReadDeliveryState.java b/src/main/java/com/messaging/chat/dao/entity/ReadDeliveryState.java index 998211f..e523476 100644 --- a/src/main/java/com/messaging/chat/dao/entity/ReadDeliveryState.java +++ b/src/main/java/com/messaging/chat/dao/entity/ReadDeliveryState.java @@ -1,6 +1,14 @@ package com.messaging.chat.dao.entity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/messaging/chat/service/WebSocketTicketService.java b/src/main/java/com/messaging/chat/service/WebSocketTicketService.java index f70b859..e567d14 100644 --- a/src/main/java/com/messaging/chat/service/WebSocketTicketService.java +++ b/src/main/java/com/messaging/chat/service/WebSocketTicketService.java @@ -1,8 +1,6 @@ package com.messaging.chat.service; -import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils;