diff --git a/src/.env b/src/.env index 21d97047..cc7428c3 100644 --- a/src/.env +++ b/src/.env @@ -66,6 +66,11 @@ MONGO_INITDB_ROOT_USERNAME=kickzo MONGO_INITDB_ROOT_PASSWORD=test123 MONGO_INITDB_DATABASE=chatDB +# ElastricSearch & Kibana +ELASTIC_PORT=9200 +ELASTIC_PASSWORD=+bAf9ynghd3*Y7L6qFQi +KIBANA_PORT=5601 + # JWT ACCESS_TOKEN_SECRET=kickzo_access_token_secret REFRESH_TOKEN_SECRET=kickzo_refresh_token_secret diff --git a/src/backend/main-server/main/build.gradle b/src/backend/main-server/main/build.gradle index 25909b8d..b6080723 100644 --- a/src/backend/main-server/main/build.gradle +++ b/src/backend/main-server/main/build.gradle @@ -38,6 +38,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch' } tasks.named('test') { diff --git a/src/backend/main-server/main/docker-compose.yml b/src/backend/main-server/main/docker-compose.yml index d20352cb..b2bbfd98 100644 --- a/src/backend/main-server/main/docker-compose.yml +++ b/src/backend/main-server/main/docker-compose.yml @@ -13,10 +13,54 @@ services: SPRING_DATASOURCE_URL: jdbc:mysql://${MYSQL_HOST}:${MYSQL_CONTAINER_PORT}/${MYSQL_DATABASE} SPRING_DATASOURCE_USERNAME: ${MYSQL_USER} SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD} + SPRING_ELASTICSEARCH_URIS: http://elasticsearch:9200 + SPRING_ELASTICSEARCH_USERNAME: kickzo + SPRING_ELASTICSEARCH_PASSWORD: test123 SPRING_APPLICATION_NAME: main + depends_on: + - elasticsearch networks: - kickzo-network + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0 + container_name: elasticsearch + ports: + - "${ELASTIC_PORT}:${ELASTIC_PORT}" + environment: + - discovery.type=single-node # 단일 노드로 실행 + - xpack.security.enabled=true + - bootstrap.memory_lock=true # 메모리 잠금 활성화 + - ES_JAVA_OPTS=-Xms512m -Xmx512m # Elasticsearch JVM 메모리 설정 + - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} + volumes: + - es-data:/usr/share/elasticsearch/data + - ./elasticsearch-init.sh:/usr/share/elasticsearch/init.sh + entrypoint: [ "/bin/sh", "-c", "/usr/share/elasticsearch/init.sh & /usr/local/bin/docker-entrypoint.sh" ] + ulimits: + memlock: + soft: -1 + hard: -1 + networks: + - kickzo-network + + kibana: + image: docker.elastic.co/kibana/kibana:8.5.0 + container_name: kibana + ports: + - "${KIBANA_PORT}:${KIBANA_PORT}" + environment: + ELASTICSEARCH_HOSTS: http://elasticsearch:9200 + ELASTICSEARCH_USERNAME: kickzo + ELASTICSEARCH_PASSWORD: test123 + depends_on: + - elasticsearch + networks: + - kickzo-network + +volumes: + es-data: + networks: kickzo-network: driver: bridge \ No newline at end of file diff --git a/src/backend/main-server/main/elasticsearch-init.sh b/src/backend/main-server/main/elasticsearch-init.sh new file mode 100644 index 00000000..111a2420 --- /dev/null +++ b/src/backend/main-server/main/elasticsearch-init.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +echo "⏳ Waiting for Elasticsearch to be ready..." +until curl -s -u elastic:${ELASTIC_PASSWORD} "http://localhost:9200/_cluster/health" | grep -q '"status":"green"\|"status":"yellow"'; do + sleep 5 +done + +echo "✅ Elasticsearch is ready. Creating user and roles..." + +# kickzo_role 생성 +curl -X PUT "http://localhost:9200/_security/role/kickzo_role" \ +-H "Content-Type: application/json" \ +-u elastic:${ELASTIC_PASSWORD} \ +-d '{ + "cluster": ["all"], + "indices": [ + { + "names": [".kibana*", ".kibana_task_manager*"], + "privileges": ["create_index", "manage", "all"], + "allow_restricted_indices": true + } + ] +}' + +# kickzo 사용자 생성 또는 업데이트 +curl -X PUT "http://localhost:9200/_security/user/kickzo" \ +-H "Content-Type: application/json" \ +-u elastic:${ELASTIC_PASSWORD} \ +-d '{ + "password": "test123", + "roles": ["superuser", "kickzo_role"] +}' + +echo "✅ Elasticsearch user and roles setup completed." diff --git a/src/backend/main-server/main/src/main/java/com/kickzo/main/MainApplication.java b/src/backend/main-server/main/src/main/java/com/kickzo/main/MainApplication.java index 0b051d8e..767c1d05 100644 --- a/src/backend/main-server/main/src/main/java/com/kickzo/main/MainApplication.java +++ b/src/backend/main-server/main/src/main/java/com/kickzo/main/MainApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; @SpringBootApplication +@EnableElasticsearchRepositories public class MainApplication { public static void main(String[] args) { diff --git a/src/backend/main-server/main/src/main/java/com/kickzo/main/RoomDocument.java b/src/backend/main-server/main/src/main/java/com/kickzo/main/RoomDocument.java new file mode 100644 index 00000000..c484166e --- /dev/null +++ b/src/backend/main-server/main/src/main/java/com/kickzo/main/RoomDocument.java @@ -0,0 +1,28 @@ +package com.kickzo.main; + +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Document(indexName = "rooms") // Elasticsearch 인덱스 이름 +public class RoomDocument { + @Id + private Long id; + + @Field(type = FieldType.Text) + private String title; + + @Field(type = FieldType.Boolean) + private Boolean isPublic; + + @Field(type = FieldType.Integer) + private int userCount; + + @Field(type = FieldType.Keyword) + private String creator; +} diff --git a/src/backend/main-server/main/src/main/java/com/kickzo/main/RoomSearchController.java b/src/backend/main-server/main/src/main/java/com/kickzo/main/RoomSearchController.java new file mode 100644 index 00000000..e873a956 --- /dev/null +++ b/src/backend/main-server/main/src/main/java/com/kickzo/main/RoomSearchController.java @@ -0,0 +1,19 @@ +package com.kickzo.main; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/search") +@RequiredArgsConstructor +public class RoomSearchController { + + private final RoomSearchRepository roomSearchRepository; + + @GetMapping() + public List searchRooms(@RequestParam String keyword) { + return roomSearchRepository.findByTitleContainingOrCreatorContaining(keyword, keyword); + } +} diff --git a/src/backend/main-server/main/src/main/java/com/kickzo/main/RoomSearchRepository.java b/src/backend/main-server/main/src/main/java/com/kickzo/main/RoomSearchRepository.java new file mode 100644 index 00000000..be3f0aa6 --- /dev/null +++ b/src/backend/main-server/main/src/main/java/com/kickzo/main/RoomSearchRepository.java @@ -0,0 +1,10 @@ +package com.kickzo.main; + +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface RoomSearchRepository extends ElasticsearchRepository { + List findByTitleContainingOrCreatorContaining(String title, String creator); +} diff --git a/src/backend/main-server/main/src/main/java/com/kickzo/main/service/MainPageService.java b/src/backend/main-server/main/src/main/java/com/kickzo/main/service/MainPageService.java index 86257d02..b3f94f06 100644 --- a/src/backend/main-server/main/src/main/java/com/kickzo/main/service/MainPageService.java +++ b/src/backend/main-server/main/src/main/java/com/kickzo/main/service/MainPageService.java @@ -11,6 +11,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.kickzo.main.RoomDocument; +import com.kickzo.main.RoomSearchRepository; import com.kickzo.main.dto.data.PlaylistItem; import com.kickzo.main.dto.request.CreateRoomRequestDto; import com.kickzo.main.dto.response.CreateRoomResponseDto; @@ -37,6 +39,7 @@ public class MainPageService { private final RoomRepository roomRepository; private final RoomUserRepository roomUserRepository; private final UserRepository userRepository; + private final RoomSearchRepository roomSearchRepository; // Elasticsearch Repository private static final int MAX_ROOMS_PER_USER = 5; private static final int ROLE_CREATOR = 0; @@ -70,6 +73,14 @@ public CreateRoomResponseDto createRoom(Long userId, CreateRoomRequestDto reques Room newRoom = saveNewRoom(creatorNickname, requestDto, randomCode); saveRoomUser(newRoom.getId(), userId); + // Elasticsearch에도 저장 + RoomDocument roomDocument = new RoomDocument(); + roomDocument.setId(newRoom.getId()); + roomDocument.setTitle(newRoom.getTitle()); + roomDocument.setCreator(newRoom.getCreator()); + + roomSearchRepository.save(roomDocument); + return new CreateRoomResponseDto(randomCode); } diff --git a/src/backend/main-server/main/src/main/resources/application-dev.properties b/src/backend/main-server/main/src/main/resources/application-dev.properties index 0fb57c01..ef964fd0 100644 --- a/src/backend/main-server/main/src/main/resources/application-dev.properties +++ b/src/backend/main-server/main/src/main/resources/application-dev.properties @@ -6,4 +6,8 @@ spring.kafka.bootstrap-servers=localhost:19092 spring.data.redis.database=3 spring.data.redis.host=localhost spring.data.redis.port=7003 -spring.data.redis.timeout=60000 \ No newline at end of file +spring.data.redis.timeout=60000 + +spring.elasticsearch.uris=http://localhost:9200 +spring.elasticsearch.connection-timeout=10s +spring.elasticsearch.socket-timeout=30s \ No newline at end of file diff --git a/src/backend/main-server/main/src/main/resources/application-docker.properties b/src/backend/main-server/main/src/main/resources/application-docker.properties index 7dfa5ddb..6a02889c 100644 --- a/src/backend/main-server/main/src/main/resources/application-docker.properties +++ b/src/backend/main-server/main/src/main/resources/application-docker.properties @@ -6,4 +6,8 @@ spring.kafka.bootstrap-servers=kafka:9092 spring.data.redis.database=3 spring.data.redis.host=redis spring.data.redis.port=6379 -spring.data.redis.timeout=60000 \ No newline at end of file +spring.data.redis.timeout=60000 + +spring.elasticsearch.uris=http://elasticsearch:9200 +spring.elasticsearch.connection-timeout=20s +spring.elasticsearch.socket-timeout=60s \ No newline at end of file diff --git a/src/infra/kong/kong.yml b/src/infra/kong/kong.yml index 5cc61c6a..2e791804 100644 --- a/src/infra/kong/kong.yml +++ b/src/infra/kong/kong.yml @@ -56,6 +56,7 @@ services: paths: - /api/rooms/all - /api/rooms/participants + - /api/search strip_path: false - name: main-private-route