Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 34 additions & 27 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,54 +1,61 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.4'
id 'io.spring.dependency-management' version '1.1.6'
id 'java'
id 'org.springframework.boot' version '3.3.4'
id 'io.spring.dependency-management' version '1.1.6'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
mavenCentral()
}

dependencies {

// spring boot
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
// spring boot
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'org.springframework.security:spring-security-test'
implementation 'org.springframework.boot:spring-boot-starter-security'

// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// db
implementation 'org.postgresql:postgresql:42.7.1'
// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// test
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// db
implementation 'org.postgresql:postgresql:42.7.1'
// implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'org.neo4j.driver:neo4j-java-driver:5.14.0'

// swaggerDoc
// implementation 'io.springfox:springfox-swagger2:2.9.2'
// implementation 'io.springfox:springfox-swagger-ui:2.9.2'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
// test
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// swaggerDoc
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'


}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.example.silverbridgeX_user.RecommendActivity.controller;

import com.example.silverbridgeX_user.RecommendActivity.service.RecommendActivityService;
import com.example.silverbridgeX_user.global.api_payload.ApiResponse;
import com.example.silverbridgeX_user.global.api_payload.SuccessCode;
import com.example.silverbridgeX_user.user.domain.User;
import com.example.silverbridgeX_user.user.jwt.CustomUserDetails;
import com.example.silverbridgeX_user.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "활동", description = "활동 관련 api 입니다.")
@RestController
@RequiredArgsConstructor
@RequestMapping("/activities")
public class RecommendActivityController {
private final RecommendActivityService recommendActivityService;
private final UserService userService;

@Operation(summary = "활동 선택", description = "로그 저장, selected 간선 생성합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "ACTIVITY_2001", description = "선택한 활동 로그와 selected 간선 저장 완료했습니다."),
})
@DeleteMapping("/select")
public ApiResponse<String> select(
@RequestParam(name = "activityId") Long activityId,
@AuthenticationPrincipal CustomUserDetails customUserDetails
) {
User user = userService.findByUserName(customUserDetails.getUsername());
recommendActivityService.handleActivitySelection(user, activityId);
return ApiResponse.onSuccess(SuccessCode.USER_LOGOUT_SUCCESS, "로그 저장 완료");
}

@Operation(summary = "활동 열람", description = "로그 저장합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "ACTIVITY_2002", description = "열람한 활동 로그 저장 완료했습니다."),
})
@DeleteMapping("/view")
public ApiResponse<String> view(
@RequestParam(name = "activityId") Long activityId,
@AuthenticationPrincipal CustomUserDetails customUserDetails
) {
User user = userService.findByUserName(customUserDetails.getUsername());

recommendActivityService.handleActivitySelection(user, activityId);

return ApiResponse.onSuccess(SuccessCode.USER_LOGOUT_SUCCESS, "로그 저장 완료");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.silverbridgeX_user.RecommendActivity.domain;

import com.example.silverbridgeX_user.user.domain.User;
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 java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RecommendActivity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

LocalDate date;

private Integer number;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "activity_id")
private User activity;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.silverbridgeX_user.RecommendActivity.repository;

import com.example.silverbridgeX_user.RecommendActivity.domain.RecommendActivity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RecommendActivityRepository extends JpaRepository<RecommendActivity, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.example.silverbridgeX_user.RecommendActivity.service;

import com.example.silverbridgeX_user.activity.converter.ActivityLogConverter;
import com.example.silverbridgeX_user.activity.domain.Activity;
import com.example.silverbridgeX_user.activity.domain.ActivityLog;
import com.example.silverbridgeX_user.activity.repository.ActivityLogRepository;
import com.example.silverbridgeX_user.activity.repository.ActivityRepository;
import com.example.silverbridgeX_user.global.api_payload.ErrorCode;
import com.example.silverbridgeX_user.global.exception.GeneralException;
import com.example.silverbridgeX_user.user.domain.User;
import java.time.LocalDateTime;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Session;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class RecommendActivityService {
private final ActivityLogRepository activityLogRepository;
private final ActivityRepository activityRepository;
private final Driver neo4jDriver; // Neo4j Java Driver

public void handleActivitySelection(User user, Long activityId){
LocalDateTime now = LocalDateTime.now();
// 1. 로그 저장
Activity activity = activityRepository.findById(activityId)
.orElseThrow(() -> GeneralException.of(ErrorCode.ACTIVITY_NOT_FOUND));
ActivityLog log = ActivityLogConverter.saveActivityLog(user, activity, now, "SELECT");
activityLogRepository.save(log);

// 2. 간선 갱신
try (Session session = neo4jDriver.session()) {
session.writeTransaction(tx -> {
// 기존 :PREFERRED 제거
tx.run("""
MATCH (u:User {id: $uid})-[r:PREFERRED]->(a:Activity {id: $aid})
DELETE r
""",
Map.of("uid", user.getId().toString(), "aid", "act" + activity.getId()));

// :SELECTED 생성
tx.run("""
MATCH (u:User {id: $uid}), (a:Activity {id: $aid})
MERGE (u)-[:SELECTED]->(a)
""",
Map.of("uid", user.getId().toString(), "aid", "act" + activity.getId()));

return null;
});
}
}

public void handleActivityView(User user, Long activityId, LocalDateTime now){
Activity activity = activityRepository.findById(activityId)
.orElseThrow(() -> GeneralException.of(ErrorCode.ACTIVITY_NOT_FOUND));
ActivityLog log = ActivityLogConverter.saveActivityLog(user, activity, now, "VIEW");
activityLogRepository.save(log);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example.silverbridgeX_user.activity.converter;

import com.example.silverbridgeX_user.activity.domain.ActionType;
import com.example.silverbridgeX_user.activity.domain.Activity;
import com.example.silverbridgeX_user.activity.domain.ActivityLog;
import com.example.silverbridgeX_user.user.domain.User;
import com.example.silverbridgeX_user.user.dto.UserRequestDto;
import java.time.LocalDateTime;
import java.util.Objects;
import lombok.NoArgsConstructor;

@NoArgsConstructor
public class ActivityLogConverter {
public static ActivityLog saveActivityLog(User user, Activity activity, LocalDateTime now, String type) {
ActionType actionType = null;
if(Objects.equals(type, "View")){
actionType = ActionType.VIEW;
}else if(Objects.equals(type, "SELECT")){
actionType = ActionType.SELECT;
}

return ActivityLog.builder()
.actionType(actionType)
.time(now)
.user(user)
.activity(activity)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.silverbridgeX_user.activity.domain;

public enum ActionType {
VIEW,
SELECT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.example.silverbridgeX_user.activity.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Transient;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Activity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(unique = true)
private String name;

private ActivityType activityType;

private String description;

@Column(columnDefinition = "vector(384)")
@Transient // JPA 저장 시 무시
private String descriptionEmbedding;

private String streetAddress;

private String lotNumberAddress;

private String latitude;

private String longitude;

private LocalDate startDate;

private LocalDate endDate;

private String homepageUrl;

private String phoneNumber;

private Long chosen;

private Long shown;

private Double CTR;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.example.silverbridgeX_user.activity.domain;

import com.example.silverbridgeX_user.user.domain.User;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import lombok.*;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "activity_log")
public class ActivityLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

private ActionType actionType;

private LocalDateTime time;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "activity_id")
private Activity activity;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.silverbridgeX_user.activity.domain;

public enum ActivityType {
FESTIVAL,

}
Loading