Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/커뮤니티 게시글 수정 #258 #259

Merged
merged 16 commits into from
Aug 14, 2023
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
59 changes: 51 additions & 8 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ on:
push:
branches:
- main
- test
pull_request:
branches:
- main
- test

workflow_dispatch:
inputs:
Expand All @@ -20,6 +22,7 @@ on:

env:
S3_BUCKET_NAME: wingle-ci-bucket
TEST_S3_BUCKET_NAME: wingle-test-ci-bucket
CODE_DEPLOY_APPLICATION_NAME: wingle-codedeploy-app
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: wingle-codedeploy-group

Expand All @@ -46,13 +49,24 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-

- name: make main application.yml
- name: 🚀 PROD - make main application.yml
if: github.ref == 'refs/heads/main'
run: |
sudo mkdir -p ./wingle/src/main/resources
sudo chmod 777 ./wingle/src/main/resources
cd ./wingle/src/main/resources
touch ./application.yml
echo "${{ secrets.MAIN_YML }}" > ./application.yml

- name: 👀 TEST - make stage application.yml
if: github.ref == 'refs/heads/test'
run: |
sudo mkdir -p ./wingle/src/main/resources
sudo chmod 777 ./wingle/src/main/resources
cd ./wingle/src/main/resources
touch ./application.yml
echo "${{ secrets.STAGE_YML }}" > ./application.yml

# 파일 없으면 빌드 에러
- uses: actions/upload-artifact@v3
with:
Expand Down Expand Up @@ -104,20 +118,20 @@ jobs:
shell: bash

- name: Make zip file
if: contains(github.ref, 'main')
# if: contains(github.ref, 'main')
run: zip -r ./$GITHUB_SHA.zip .
shell: bash

- name: Configure AWS credentials
if: contains(github.ref, 'main')
- name: 🚀 PROD - Configure AWS credentials
if: github.ref == 'refs/heads/main'
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Upload to AWS S3
if: contains(github.ref, 'main')
- name: 🚀 PROD - Upload to AWS S3
if: github.ref == 'refs/heads/main'
run: |
cd ./wingle
aws deploy push \
Expand All @@ -127,11 +141,40 @@ jobs:
--source .

# S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
- name: Deploy to AWS EC2 from S3
if: contains(github.ref, 'main')
- name: 🚀 PROD - Deploy to AWS EC2 from S3
if: github.ref == 'refs/heads/main'
run: |
aws deploy create-deployment \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,key=build/$GITHUB_SHA.zip,bundleType=zip


- name: 👀 TEST - Configure AWS credentials
if: github.ref == 'refs/heads/test'
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.TEST_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

- name: 👀 TEST - Upload to AWS S3
if: github.ref == 'refs/heads/test'
run: |
cd ./wingle
aws deploy push \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--ignore-hidden-files \
--s3-location s3://$TEST_S3_BUCKET_NAME/build/$GITHUB_SHA.zip \
--source .

# S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
- name: 👀 TEST - Deploy to AWS EC2 from S3
if: github.ref == 'refs/heads/test'
run: |
aws deploy create-deployment \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$TEST_S3_BUCKET_NAME,key=build/$GITHUB_SHA.zip,bundleType=zip
130 changes: 97 additions & 33 deletions wingle/src/main/java/kr/co/wingle/common/util/AES256Util.java
Original file line number Diff line number Diff line change
@@ -1,64 +1,128 @@
package kr.co.wingle.common.util;

import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import kr.co.wingle.common.constants.ErrorCode;
import kr.co.wingle.common.exception.InternalServerErrorException;

@Component
public class AES256Util {
//initial vector 설정
private static String iv = "0000000000000001";
private static Key keySpec;

public AES256Util() throws UnsupportedEncodingException {
iv = iv.substring(0, 16);
byte[] keyBytes = new byte[16];
byte[] b = iv.getBytes("UTF-8");
int len = b.length;
if (len > keyBytes.length) {
len = keyBytes.length;
}
System.arraycopy(b, 0, keyBytes, 0, len);
SecretKeySpec _keySpec = new SecretKeySpec(keyBytes, "AES");
keySpec = _keySpec;

private static String KEY;
private static byte[] SALT;
private static String IV;

@Value("${aes256.key}")
public void setKEY(String KEY) {
AES256Util.KEY = KEY;
}

@Value("${aes256.salt}")
public void setSALT(String SALT) throws DecoderException {
AES256Util.SALT = Hex.decodeHex(SALT.toCharArray());
}

@Value("${aes256.iv}")
public void setIV(String IV) {
AES256Util.IV = IV;
}

//암호화
public static String encrypt(String str) {
try {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec((iv.getBytes())));
byte[] encrypted = c.doFinal(str.getBytes("UTF-8"));
// String enStr = new String(Base64.encodeBase64(encrypted));
return new java.math.BigInteger(encrypted).toString(16);
} catch (GeneralSecurityException | UnsupportedEncodingException e) {
SecretKey key = generateKey(KEY);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, IV, str.getBytes("UTF-8"));
return encodeHex(encrypted);
} catch (Exception e) {
throw new InternalServerErrorException(ErrorCode.ENCRYPT_FAIL);
}
}

public static String encrypt(String str, String salt) {
try {
SecretKey key = generateKey(KEY, salt);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, IV, str.getBytes("UTF-8"));
return encodeHex(encrypted);
} catch (Exception e) {
throw new InternalServerErrorException(ErrorCode.ENCRYPT_FAIL);
}
}

//복호화
public static String decrypt(String str) {
try {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes()));
// byte[] byteStr = Base64.decodeBase64(str.getBytes());
byte[] byteStr = new java.math.BigInteger(str, 16).toByteArray();
return new String(c.doFinal(byteStr), "UTF-8");
} catch (GeneralSecurityException | UnsupportedEncodingException e) {
SecretKey key = generateKey(KEY);
byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, IV, decodeBase64(str));
return new String(decrypted, "UTF-8");
} catch (Exception e) {
throw new InternalServerErrorException(ErrorCode.DECRYPT_FAIL);
}
}

public static String decrypt(String str, String salt) {
try {
SecretKey key = generateKey(KEY, salt);
byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, IV, decodeBase64(str));
return new String(decrypted, "UTF-8");
} catch (Exception e) {
throw new InternalServerErrorException(ErrorCode.DECRYPT_FAIL);
}
}

public static Long userIdDecrypt(String userId) {
return Long.parseLong(decrypt(userId));
}

private static byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(encryptMode, key, new IvParameterSpec(decodeHex(iv)));
return cipher.doFinal(bytes);
}

private static SecretKey generateKey(String passPhrase) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

// generate key with salt
PBEKeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), SALT, 3000, 256);
SecretKey key = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), "AES");

return key;
}

private static SecretKey generateKey(String passPhrase, String salt) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

// generate custom salt
PBEKeySpec saltSpec = new PBEKeySpec(salt.toCharArray(), SALT, 3000, 128);
SecretKey saltKey = new SecretKeySpec(factory.generateSecret(saltSpec).getEncoded(), "AES");

// generate key with custom salt
PBEKeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), saltKey.toString().getBytes(), 3000, 256);
SecretKey key = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), "AES");

return key;
}

private static String encodeHex(byte[] bytes) {
return Hex.encodeHexString(bytes);
}

private static byte[] decodeHex(String str) throws Exception {
return Hex.decodeHex(str.toCharArray());
}

private static byte[] decodeBase64(String str) {
byte[] decodeByte = Base64.decodeBase64(str);
return Base64.decodeBase64(decodeByte);
}

}
9 changes: 7 additions & 2 deletions wingle/src/main/java/kr/co/wingle/common/util/S3Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ public String profileImageUpload(MultipartFile file) {
}
}

public String articleImageUpload(MultipartFile file) throws IOException {
return upload(file, "article");
public String articleImageUpload(MultipartFile file) {
try {
return upload(file, "article");
} catch (IOException e) {
log.warn(e.getMessage());
throw new InternalServerErrorException(ErrorCode.FILE_UPLOAD_FAIL);
}
}

public void delete(String url) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
Expand Down Expand Up @@ -63,4 +64,11 @@ public ApiResponse<Long> delete(@PathVariable String forumId,
articleService.delete(StringUtil.StringToLong(forumId), StringUtil.StringToLong(articleId)));
}

@PutMapping("/{forumId}/articles/{articleId}")
public ApiResponse<ArticleResponseDto> editArticle(@PathVariable String forumId,
@PathVariable String articleId, @ModelAttribute @Valid ArticleEditRequestDto articleEditRequestDto) {
return ApiResponse.success(SuccessCode.GET_SUCCESS,
articleService.editArticle(StringUtil.StringToLong(forumId), StringUtil.StringToLong(articleId),
articleEditRequestDto));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kr.co.wingle.community.article;

import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import kr.co.wingle.common.validator.LengthWithoutCR;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class ArticleEditRequestDto {

@LengthWithoutCR(min = 1, max = 3000, message = "내용은 1자 이상 3000자 이하만 가능합니다.")
private String content;

private List<String> originImages;
private List<MultipartFile> newImages;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package kr.co.wingle.community.article;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ManyToOne;

import org.springframework.util.Assert;

import kr.co.wingle.common.entity.BaseEntity;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ArticleImage extends BaseEntity {

@ManyToOne(fetch = FetchType.LAZY)
private Article article;

@Column(nullable = false)
private String imageUrl;

@Column(nullable = false)
private int orderNumber;

@Builder
ArticleImage(Article article, String imageUrl, int orderNumber) {
Assert.notNull(article, "article must not be null");
Assert.notNull(imageUrl, "imageUrl must not be null");

this.article = article;
this.imageUrl = imageUrl;
this.orderNumber = orderNumber;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package kr.co.wingle.community.article;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ArticleImageRepository extends JpaRepository<ArticleImage, Long> {

List<ArticleImage> getArticleImagesByArticleIdAndIsDeletedOrderByOrderNumber(Long articleId, boolean isDeleted);

List<ArticleImage> findAllByArticleId(Long articleId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public ArticleResponseDto toResponseDto(Article article, List<String> images) {
articleResponseDto.images(new ArrayList<String>(list));
}
articleResponseDto.isMine(processedPersonalInformation.isMine());
articleResponseDto.userId(AES256Util.encrypt(processedPersonalInformation.getProcessedMemberId().toString()));
articleResponseDto.userId(
AES256Util.encrypt(article.getMember().getId().toString(), article.getId().toString()));
articleResponseDto.userImage(profileService.getProfileByMemberId(article.getMember().getId()).getImageUrl());
articleResponseDto.userNation(profileService.getProfileByMemberId(article.getMember().getId()).getNation());
articleResponseDto.userSchoolName(processedPersonalInformation.getSchoolName());
Expand Down
Loading