Skip to content

Commit

Permalink
Merge pull request #12 from 9oormthon-univ/dev
Browse files Browse the repository at this point in the history
S3 api 테스트 완료 후 병합
  • Loading branch information
sumin220 authored Nov 20, 2024
2 parents b7319a7 + f125ca8 commit ce2fee3
Show file tree
Hide file tree
Showing 49 changed files with 1,847 additions and 7 deletions.
145 changes: 145 additions & 0 deletions .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# 필요한 Repo Secret 설정
#### CI
# ${{ secrets.SUBMODULE_ACCESS_TOKEN }} : 깃허브 액세스 토큰

#### CD
# ${{ secrets.DOCKER_ID }} : 도커허브 id
# ${{ secrets.DOCKER_PASSWORD }} : 도커허브 pw
# ${{ secrets.REMOTE_HOST_DEV }} : 배포 서버 HOSTNAME
# ${{ secrets.REMOTE_PORT_DEV }} : 배포 서버 PORT
# ${{ secrets.REMOTE_USERNAME_DEV }} : 배포 서버 USERNAME
# ${{ secrets.REMOTE_SSH_KEY_DEV }} : 배포 서버 연결을 위한 SSH KEY

name: Backend CI & CD (dev)

on:
pull_request:
branches: [main]
push:
branches: [main]

env:
CONTAINER_NAME: yesummit

jobs:
Continuous-Integration:
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
# CI 실행 (환경은 github 제공)
runs-on: ubuntu-20.04
steps:

# 소스코드 체크아웃
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.ACTION_TOKEN }}
ref: ${{ github.head_ref }}

- name: Install JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'zulu'
cache: 'gradle'

# Gradle Package Caching
- name: Caching Gradle packages
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}

- name: Grant execute permission for gradle
run: chmod +x ./gradlew

# develop 브랜치일 경우 dev 환경 빌드
# 현재 테스트 코드를 따로 작성하지 않아. test 없이 빌드함
- name: create build file
run: ./gradlew clean build -x test -i --no-daemon -Dspring.profiles.active=prod

# push event일 경우 CD job에 jar file 업로드
- name: (Push) Archive production artifacts
if: github.event_name == 'push'
uses: actions/upload-artifact@v4
with:
name: build
path: build/libs/*.jar

Continuous-Deploy:
# push 하는 경우에만 배포 JOB 실행
if: github.event_name == 'push'
needs: Continuous-Integration
runs-on: ubuntu-latest
steps:

# 소스코드 가져오기
- name: Checkout source code
uses: actions/checkout@v4

# 이전 Job에서 업로드한 Jar file 다운로드
- name : Download a built Jar File
uses: actions/download-artifact@v4
with:
name: build
path: build/libs

# Docker Buildx Setting
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# Docker Login
- name: Docker Login
uses: docker/[email protected]
with:
# Username used to log against the Docker registry
username: ${{ secrets.DOCKER_ID }}
# Password or personal access token used to log against the Docker registry
password: ${{ secrets.DOCKER_PASSWORD }}

# Docker Build & Push
- name: Docker Build and push
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile-dev
platforms: linux/amd64
push: true
tags: |
${{ secrets.DOCKER_ID }}/${{ env.CONTAINER_NAME }}:${{github.run_number}}
${{ secrets.DOCKER_ID }}/${{ env.CONTAINER_NAME }}:latest
cache-from: type=gha # gha=Github Action Cache
cache-to: type=gha,mode=max

- name: Create and execute deploy script
run: |
echo '#!/bin/bash' > deploy.sh
echo 'sudo docker rm -f ${{ env.CONTAINER_NAME }}' >> deploy.sh
echo 'sudo docker rmi ${{ secrets.DOCKER_ID }}/${{ env.CONTAINER_NAME }}' >> deploy.sh
echo 'sudo docker pull ${{ secrets.DOCKER_ID }}/${{ env.CONTAINER_NAME }}' >> deploy.sh
echo 'sudo docker run -d -p 8080:8080 --add-host host.docker.internal:host-gateway --restart=unless-stopped --log-opt max-size=10m --log-opt max-file=3 --name ${{ env.CONTAINER_NAME }} ${{ secrets.DOCKER_ID }}/${{ env.CONTAINER_NAME }}' >> deploy.sh
- name: Transfer Deploy Script use SCP
uses: appleboy/scp-action@master
with:
host: ${{ secrets.REMOTE_HOST_DEV }}
port: ${{ secrets.REMOTE_PORT_DEV }}
username: ${{ secrets.REMOTE_USERNAME_DEV }}
key: ${{ secrets.REMOTE_SSH_KEY_DEV }}
source: deploy.sh
target: /home/${{ secrets.REMOTE_USERNAME_DEV }}/deploy

# SSH Connect
- name: Execute Server Init Script
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.REMOTE_HOST_DEV }}
port: ${{ secrets.REMOTE_PORT_DEV }}
username: ${{ secrets.REMOTE_USERNAME_DEV }}
key: ${{ secrets.REMOTE_SSH_KEY_DEV }}
script_stop: true
script: |
chmod +x /home/${{ secrets.REMOTE_USERNAME_DEV }}/deploy/deploy.sh && sh /home/${{ secrets.REMOTE_USERNAME_DEV }}/deploy/deploy.sh
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
application.yml
application-*.yml

### STS ###
.apt_generated
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "config"]
path = config
url = https://github.com/YE-Summit/config.git
10 changes: 10 additions & 0 deletions Dockerfile-dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM eclipse-temurin:17-jdk

VOLUME /tmp

ARG JAR_FILE=build/libs/*.jar

COPY ${JAR_FILE} app.jar

# 시간대 및 Spring 프로필 설정
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "-Duser.timezone=Asia/Seoul", "/app.jar"]
46 changes: 40 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,61 @@ java {
}
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'

// DB
implementation 'mysql:mysql-connector-java:8.0.33'
runtimeOnly 'com.h2database:h2'

// p6spy 적용
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.7.1'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// Swagger3 적용
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4'

// JWT
implementation 'com.auth0:java-jwt:4.4.0'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'

}

tasks.named('test') {
useJUnitPlatform()
}

//plain jar 생성 방지
tasks.named("jar") {
enabled = false
}

tasks.register('copyExternalProperties', Copy) {
from "./config"
include "*.yml"
into "src/main/resources"
}

processResources.dependsOn('copyExternalProperties')
processTestResources.dependsOn('copyExternalProperties')

test {
systemProperty "spring.profiles.active", "prod"
}
1 change: 1 addition & 0 deletions config
Submodule config added at a9020d
2 changes: 2 additions & 0 deletions src/main/java/univ/yesummit/YesummitApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class YesummitApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package univ.yesummit.domain.feed.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import univ.yesummit.domain.feed.dto.FeedListResponse;
import univ.yesummit.domain.feed.dto.FeedResponse;
import univ.yesummit.domain.feed.service.FeedService;
import univ.yesummit.global.exception.dto.ResponseVO;
import univ.yesummit.global.resolver.LoginUser;
import univ.yesummit.global.resolver.User;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/v1/api/feed")
public class FeedController {

private final FeedService feedService;


@Operation(summary = "S3 버킷에 이미지를 업로드합니다.", description = "이미지 업로드 완료 후, 각각 이미지의 URL LIST를 JSON 형식으로 반환합니다.")
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseVO<FeedResponse> uploadImage(
@Parameter(description = "multipart/form-data 형식의 이미지 리스트를 input으로 받습니다. 이때 key 값은 files입니다.")
@RequestParam("files") List<MultipartFile> files,
@User LoginUser loginUser) {
Long memberId = loginUser.getMemberId();

List<String> imageUrls = feedService.uploadImagesToS3(files, memberId);
return new ResponseVO<>(new FeedResponse(imageUrls));
}

@GetMapping
@Operation(summary = "사용자의 모든 피드 조회", description = "사용자의 모든 피드를 조회하고 해당 리스트를 반환합니다.")
public ResponseVO<FeedListResponse> getAllFeeds(@User LoginUser loginUser) {
Long memberId = loginUser.getMemberId();
FeedListResponse feedListResponse = feedService.getAllFeedsOfMember(memberId);
return new ResponseVO<>(feedListResponse);
}

@DeleteMapping("/{feedId}")
@Operation(summary = "피드 및 이미지 삭제", description = "피드와 해당 이미지를 삭제합니다.")
public ResponseVO<String> deleteFeed(@PathVariable Long feedId, @User LoginUser loginUser) {
Long memberId = loginUser.getMemberId();
feedService.deleteFeed(feedId, memberId);
return new ResponseVO<>("피드가 성공적으로 삭제되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package univ.yesummit.domain.feed.dto;

import java.util.List;

public record FeedListResponse(
List<FeedResponse> feeds
) {
}
8 changes: 8 additions & 0 deletions src/main/java/univ/yesummit/domain/feed/dto/FeedResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package univ.yesummit.domain.feed.dto;

import java.util.List;

public record FeedResponse(
List<String> imageUrls
) {
}
31 changes: 31 additions & 0 deletions src/main/java/univ/yesummit/domain/feed/entity/Feed.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package univ.yesummit.domain.feed.entity;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import univ.yesummit.domain.member.entity.Member;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Feed {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "feed_id")
private Long id;

@Column(name = "feed_image")
private String image;

@ManyToOne
@JoinColumn(name = "member_id")
private Member member;

@Builder
public Feed(String image, Member member) {
this.image = image;
this.member = member;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package univ.yesummit.domain.feed.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import univ.yesummit.domain.feed.entity.Feed;
import univ.yesummit.domain.member.entity.Member;

import java.util.List;

@Repository
public interface FeedRepository extends JpaRepository<Feed, Long> {

List<Feed> findAllByMember(Member member);
}
16 changes: 16 additions & 0 deletions src/main/java/univ/yesummit/domain/feed/service/FeedService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package univ.yesummit.domain.feed.service;

import org.springframework.web.multipart.MultipartFile;
import univ.yesummit.domain.feed.dto.FeedListResponse;
import univ.yesummit.domain.member.entity.Member;

import java.util.List;

public interface FeedService {

List<String> uploadImagesToS3(List<MultipartFile> files, Long memberId);

FeedListResponse getAllFeedsOfMember(Long memberId);

void deleteFeed(Long feedId, Long memberId);
}
Loading

0 comments on commit ce2fee3

Please sign in to comment.