Skip to content
Closed
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
10 changes: 8 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ configurations {
}
}

configurations.all {
exclude group: 'commons-logging', module: 'commons-logging'
} //추가(yum): Standard Commons Logging discovery in action with spring-jcl: please remove commons-logging.jar from classpath in order to avoid potential conflicts

repositories {
mavenCentral()
// SpringAI 관련 maven 설정
Expand All @@ -31,7 +35,7 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
//implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
Expand All @@ -54,7 +58,7 @@ dependencies {
annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3'
annotationProcessor 'org.projectlombok:lombok'

// Adds JTS Core and Hibernate Spatial for handling spatial data types and queries
// Adds JTS Core and Hibernate Spatial for handling spatial data types and queries //추가(yum) Info: [petty] [ restartedMain] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface io.github.petty.community.repository.CommentRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
implementation 'org.locationtech.jts:jts-core:1.20.0'
implementation 'org.hibernate.orm:hibernate-spatial:6.6.13.Final'

Expand All @@ -68,6 +72,8 @@ dependencies {
implementation 'software.amazon.awssdk:rekognition'
// ✅ SMTP
implementation 'org.springframework.boot:spring-boot-starter-mail'
// ✅ Oauth2
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

// SpringAI API
implementation 'io.grpc:grpc-xds:1.62.2'
Expand Down
4 changes: 0 additions & 4 deletions src/main/java/io/github/petty/community/Comments.java

This file was deleted.

This file was deleted.

This file was deleted.

4 changes: 0 additions & 4 deletions src/main/java/io/github/petty/community/CommunityService.java

This file was deleted.

4 changes: 0 additions & 4 deletions src/main/java/io/github/petty/community/PostLike.java

This file was deleted.

4 changes: 0 additions & 4 deletions src/main/java/io/github/petty/community/PostViews.java

This file was deleted.

4 changes: 0 additions & 4 deletions src/main/java/io/github/petty/community/Posts.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.github.petty.community.controller;

import io.github.petty.community.dto.CommentRequest;
import io.github.petty.community.dto.CommentResponse;
import io.github.petty.community.service.CommentService;
import io.github.petty.users.dto.CustomUserDetails;
import io.github.petty.users.entity.Users;
import io.github.petty.users.repository.UsersRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
public class CommentController {

private final CommentService commentService;
private final UsersRepository usersRepository;

@GetMapping("/api/posts/{postId}/comments")
public ResponseEntity<List<CommentResponse>> getComments(@PathVariable Long postId) {
return ResponseEntity.ok(commentService.getComments(postId));
}

@PostMapping("/api/posts/{postId}/comments")
public ResponseEntity<?> addComment(@PathVariable Long postId,
@RequestBody CommentRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
Long commentId = commentService.addComment(postId, request, user);
return ResponseEntity.ok().body(commentId);
}

@PutMapping("/api/comments/{commentId}")
public ResponseEntity<?> updateComment(@PathVariable Long commentId,
@RequestBody CommentRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
commentService.updateComment(commentId, request, user);
return ResponseEntity.ok().build();
}

@DeleteMapping("/api/comments/{commentId}")
public ResponseEntity<?> deleteComment(@PathVariable Long commentId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
commentService.deleteComment(commentId, user);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.github.petty.community.controller;

import io.github.petty.community.dto.PostDetailResponse;
import io.github.petty.community.dto.PostRequest;
import io.github.petty.community.entity.Post;
import io.github.petty.community.service.PostService;
import io.github.petty.users.dto.CustomUserDetails;
import io.github.petty.users.entity.Users;
import io.github.petty.users.repository.UsersRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

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

private final PostService postService;
private final UsersRepository usersRepository;

@PostMapping
public ResponseEntity<?> create(@RequestBody PostRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
Long id = postService.save(request, user);
return ResponseEntity.ok(Map.of("id", id));
}

@PutMapping("/{id}")
public ResponseEntity<?> update(@PathVariable Long id,
@RequestBody PostRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
postService.update(id, request, user);
System.out.println("📥 요청 들어옴: " + request);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{id}")
public ResponseEntity<?> delete(@PathVariable Long id,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
postService.delete(id, user);
return ResponseEntity.ok().build();
}

@GetMapping
public ResponseEntity<Page<?>> getAllByType(@RequestParam("type") Post.PostType type,
@PageableDefault(size = 9) Pageable pageable) {
Page<?> posts = postService.findAllByType(String.valueOf(type), pageable);
return ResponseEntity.ok(posts);
}

@GetMapping("/{id}")
public ResponseEntity<PostDetailResponse> getPost(@PathVariable Long id) {
PostDetailResponse post = postService.findById(id);
return ResponseEntity.ok(post);
}

@PostMapping("/{id}/like")
public ResponseEntity<?> likePost(@PathVariable Long id,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
int newCount = postService.toggleLike(id, user); // 좋아요 또는 취소
return ResponseEntity.ok(Map.of("likeCount", newCount));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package io.github.petty.community.controller;

import io.github.petty.community.util.SupabaseUploader;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/images")
@RequiredArgsConstructor
@Slf4j
public class PostImageUploadController {

private final SupabaseUploader supabaseUploader;
private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
private static final List<String> ALLOWED_EXTENSIONS = List.of("jpg", "jpeg", "png", "gif");

// ✅ 단일 이미지 업로드
@PostMapping("/upload")
public ResponseEntity<?> uploadImage(
@RequestParam("file") MultipartFile file,
@AuthenticationPrincipal UserDetails userDetails) {

if (userDetails == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", "로그인이 필요합니다"));
}

// 파일 유효성 검사
if (!isValidImage(file)) {
return ResponseEntity.badRequest().body(Map.of(
"error", "유효하지 않은 파일",
"message", "5MB 이하의 jpg, jpeg, png, gif 파일만 업로드 가능합니다"
));
}

try {
String imageUrl = supabaseUploader.upload(file);
return ResponseEntity.ok(Map.of("url", imageUrl));
} catch (IOException e) {
log.error("이미지 업로드 실패", e);
return ResponseEntity.internalServerError().body(Map.of("error", "업로드 실패", "message", e.getMessage()));
}
}

// ✅ 다중 이미지 업로드
@PostMapping("/upload/multi")
public ResponseEntity<?> uploadMultipleImages(
@RequestParam("files") List<MultipartFile> files,
@AuthenticationPrincipal UserDetails userDetails) {

if (userDetails == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", "로그인이 필요합니다"));
}

// 파일 수 제한
if (files.size() > 5) {
return ResponseEntity.badRequest().body(Map.of(
"error", "이미지 제한 초과",
"message", "최대 5개의 이미지만 업로드 가능합니다"
));
}

// 모든 파일의 유효성 검사
for (MultipartFile file : files) {
if (!isValidImage(file)) {
return ResponseEntity.badRequest().body(Map.of(
"error", "유효하지 않은 파일",
"message", "5MB 이하의 jpg, jpeg, png, gif 파일만 업로드 가능합니다"
));
}
}

List<Map<String, Object>> imageResponses = new ArrayList<>();

try {
int order = 0;
for (MultipartFile file : files) {
String url = supabaseUploader.upload(file);

Map<String, Object> imageMap = new HashMap<>();
imageMap.put("imageUrl", url);
imageMap.put("ordering", order++);
imageMap.put("isDeleted", false);

imageResponses.add(imageMap);
}
return ResponseEntity.ok(Map.of("images", imageResponses));
} catch (IOException e) {
log.error("이미지 업로드 실패", e);
return ResponseEntity.internalServerError().body(Map.of("error", "이미지 업로드 실패", "message", e.getMessage()));
}
}

private boolean isValidImage(MultipartFile file) {
if (file.isEmpty() || file.getSize() > MAX_FILE_SIZE) {
return false;
}

String originalFilename = file.getOriginalFilename();
if (originalFilename == null) {
return false;
}

String extension = originalFilename.substring(originalFilename.lastIndexOf('.') + 1).toLowerCase();
return ALLOWED_EXTENSIONS.contains(extension);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.github.petty.community.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PostViewController {

// 📌 후기 게시판
@GetMapping("/posts/review")
public String reviewListPage() {
return "post-review-list";
}

@GetMapping("/posts/review/new")
public String reviewFormPage() {
return "post-review-form";
}

@GetMapping("/posts/review/edit")
public String reviewEditPage() {
return "edit-review";
}

// 📌 자랑 게시판
@GetMapping("/posts/showoff")
public String showoffListPage() {
return "post-showoff-list";
}

@GetMapping("/posts/showoff/new")
public String showoffFormPage() {
return "post-showoff-form";
}

@GetMapping("/posts/showoff/edit")
public String showoffEditPage() {
return "edit-showoff";
}

// 📌 질문 게시판
@GetMapping("/posts/qna")
public String qnaListPage() {
return "post-qna-list";
}

@GetMapping("/posts/qna/new")
public String qnaFormPage() {
return "post-qna-form";
}

@GetMapping("/posts/qna/edit")
public String qnaEditPage() {
return "edit-qna";
}

// 📌 상세페이지 (공통)
@GetMapping("/posts/detail")
public String detailPage() {
return "post-detail";
}
}

Loading