Skip to content
Open
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
12 changes: 11 additions & 1 deletion docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ services:
networks:
- backend

sns-post-service:
image: ${DOCKER_USERNAME}/aivle-sns-post:latest
container_name: sns-post-service
depends_on:
- kafka
environment:
- SPRING_PROFILES_ACTIVE=prod
networks:
- backend

networks:
backend:
driver: bridge
driver: bridge
14 changes: 13 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,21 @@ services:
networks:
- backend

sns-service:
build:
context: ./sns-service
dockerfile: Dockerfile
container_name: sns-service
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
networks:
- backend

volumes:
mysql_data:

networks:
backend:
driver: bridge
driver: bridge
Empty file modified gradlew
100644 → 100755
Empty file.
2 changes: 2 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ include 'gateway'
include 'auth-service'
include 'store-service'
include 'shorts-service'
include 'sns-post-service'
include 'sns-service'

12 changes: 12 additions & 0 deletions sns-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM openjdk:17-jdk-alpine

WORKDIR /app

COPY build/libs/*SNAPSHOT.jar app.jar

EXPOSE 8080

ENV TZ=Asia/Seoul
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

ENTRYPOINT ["java","-Xmx400M","-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]
47 changes: 47 additions & 0 deletions sns-service/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
plugins {
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}

dependencies {
// 공통 모듈
implementation project(':common')

// Web/API
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

// Youtube API/인증
implementation 'com.google.api-client:google-api-client:1.34.1'
implementation 'com.google.apis:google-api-services-youtube:v3-rev222-1.25.0'
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
implementation 'com.google.apis:google-api-services-youtubeAnalytics:v2-rev272-1.25.0'

// 보안·인증
implementation 'org.springframework.security:spring-security-crypto'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// DB/JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'com.h2database:h2'

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// 요청 검증
implementation 'org.springframework.boot:spring-boot-starter-validation'

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'

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

testImplementation 'org.springframework.boot:spring-boot-starter-test'

implementation 'com.fasterxml.jackson.core:jackson-databind'
}
14 changes: 14 additions & 0 deletions sns-service/src/main/java/kt/aivle/sns/SnsServiceApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kt.aivle.sns;

import kt.aivle.sns.config.YoutubeOAuthProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication(scanBasePackages = {"kt.aivle.sns", "kt.aivle.common"})
@EnableConfigurationProperties({YoutubeOAuthProperties.class}) // 다른 sns properties 추가
public class SnsServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SnsServiceApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// adapter/in/web/AIPostController.java
// Spring Boot 서버 내 REST API 경로 (클라이언트 → Spring Boot)
package kt.aivle.sns.adapter.in.web;

import jakarta.validation.Valid;
import kt.aivle.sns.adapter.in.web.dto.request.CreateHashtagRequest;
import kt.aivle.sns.adapter.in.web.dto.request.CreatePostRequest;
import kt.aivle.sns.adapter.in.web.dto.response.CreateHashtagResponse;
import kt.aivle.sns.adapter.in.web.dto.response.CreatePostResponse;
import kt.aivle.sns.application.port.in.AiPostUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/posts/ai")
@RequiredArgsConstructor
public class AiPostController {

private final AiPostUseCase aiPostUseCase;

@PostMapping("/post")
public CreatePostResponse createPost(@Valid @RequestBody CreatePostRequest request) {
return aiPostUseCase.createPost(request);
}

@PostMapping("/hashtags")
public CreateHashtagResponse createHashtags(@Valid @RequestBody CreateHashtagRequest request) {
return aiPostUseCase.createHashtags(request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package kt.aivle.sns.adapter.in.web;

import kt.aivle.common.response.ApiResponse;
import kt.aivle.common.response.ResponseUtils;
import kt.aivle.sns.adapter.in.web.dto.SnsAccountResponse;
import kt.aivle.sns.application.service.SnsAccountDelegator;
import kt.aivle.sns.adapter.in.web.dto.SnsAccountUpdateRequest;
import kt.aivle.sns.domain.model.SnsType;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import static kt.aivle.common.code.CommonResponseCode.OK;

@RestController
@RequestMapping("sns/account")
@RequiredArgsConstructor
public class SnsAccountController {

private final SnsAccountDelegator snsAccountDelegator;

private final ResponseUtils responseUtils;

@GetMapping("/{snsType}")
public ResponseEntity<ApiResponse<SnsAccountResponse>> getAccountInfo(@PathVariable SnsType snsType,
@RequestHeader("X-USER-ID") Long userId,
@RequestParam Long storeId) {
SnsAccountResponse account = snsAccountDelegator.getAccountInfo(snsType, userId, storeId);
return responseUtils.build(OK, account);
}

@PutMapping("/{snsType}")
public ResponseEntity<?> updateAccount(@PathVariable SnsType snsType,
@RequestHeader("X-USER-ID") Long userId,
@RequestBody SnsAccountUpdateRequest request) {
snsAccountDelegator.updateAccount(snsType, userId, request);
return ResponseEntity.ok().build();
}

@GetMapping("/{snsType}/list")
public ResponseEntity<?> getPostList(@PathVariable SnsType snsType,
@RequestHeader("X-USER-ID") Long userId,
@RequestParam Long storeId) {
snsAccountDelegator.getPostList(snsType, userId, storeId);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package kt.aivle.sns.adapter.in.web;

import kt.aivle.sns.application.service.SnsOAuthDelegator;
import kt.aivle.sns.domain.model.SnsType;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("sns/oauth")
@RequiredArgsConstructor
public class SnsOAuthController {
private final SnsOAuthDelegator delegator;

@GetMapping("/")
public String home() throws Exception {
return "sns/oauth/";
}

// 각 SNS 연동 버튼 누르면 호출
@GetMapping("/{snsType}/url")
public String getAuthUrl(@PathVariable SnsType snsType,
@RequestHeader("X-USER-ID") Long userId,
@RequestParam Long storeId) {
return delegator.getAuthUrl(snsType, userId, storeId);
}

@GetMapping("/{snsType}/callback")
public String callback(@PathVariable SnsType snsType,
@RequestParam String code,
@RequestParam String state) throws Exception {
delegator.handleCallback(snsType, state, code);

return "계정 연동 완료";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package kt.aivle.sns.adapter.in.web;

import kt.aivle.sns.application.service.SnsPostDelegator;
import kt.aivle.sns.adapter.in.web.dto.PostDeleteRequest;
import kt.aivle.sns.adapter.in.web.dto.PostUpdateRequest;
import kt.aivle.sns.domain.model.SnsType;
import kt.aivle.sns.adapter.in.web.dto.PostUploadRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("sns/video")
@RequiredArgsConstructor
public class SnsPostController {

private final SnsPostDelegator snsPostDelegator;

@PostMapping("/{snsType}/upload")
public ResponseEntity<?> uploadVideo(@PathVariable SnsType snsType,
@RequestHeader("X-USER-ID") Long userId,
@RequestBody PostUploadRequest request) {
snsPostDelegator.upload(snsType, userId, request);
return ResponseEntity.ok().build();
}

@PutMapping("/{snsType}/update")
public ResponseEntity<?> updateVideo(@PathVariable SnsType snsType,
@RequestHeader("X-USER-ID") Long userId,
@RequestBody PostUpdateRequest request) {
snsPostDelegator.update(snsType, userId, request);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{snsType}/delete")
public ResponseEntity<?> deleteVideo(@PathVariable SnsType snsType,
@RequestHeader("X-USER-ID") Long userId,
@RequestBody PostDeleteRequest request) {
snsPostDelegator.delete(snsType, userId, request);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package kt.aivle.sns.adapter.in.web.dto.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CreateHashtagRequest {

@JsonProperty("post_title")
@NotBlank(message = "게시물 제목을 입력해주세요.")
private String postTitle;

@JsonProperty("post_content")
@NotBlank(message = "게시물 본문을 입력해주세요.")
private String postContent;

@JsonProperty("user_keywords")
private List<String> userKeywords;

@JsonProperty("sns_platform")
@NotBlank(message = "SNS 플랫폼을 입력해주세요.")
private String snsPlatform;

@JsonProperty("business_type")
@NotBlank(message = "업종을 입력해주세요.")
private String businessType;

private String location;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kt.aivle.sns.adapter.in.web.dto.response;

import java.util.List;

public record CreateHashtagResponse(List<String> hashtags) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package kt.aivle.sns.adapter.in.web.dto.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CreatePostRequest {

@JsonProperty("content_data")
@NotBlank(message = "콘텐츠 데이터를 입력해주세요.")
private String contentData;

@JsonProperty("user_keywords")
private List<String> userKeywords;

@JsonProperty("sns_platform")
@NotBlank(message = "SNS 플랫폼을 입력해주세요.")
private String snsPlatform;

@JsonProperty("business_type")
@NotBlank(message = "업종을 입력해주세요.")
private String businessType;

private String location;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kt.aivle.sns.adapter.in.web.dto.response;

import java.util.List;

public record CreatePostResponse(String title, String content, List<String> hashtags) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kt.aivle.sns.adapter.in.web.dto;

public class PostDeleteRequest {
private String postId;
private Long storeId;

public PostDeleteRequest() {};

public String getPostId() {
return postId;
}

public void setPostId(String postId) {
this.postId = postId;
}

public Long getStoreId() {
return storeId;
}
}
Loading