mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-03-02 10:00:54 +08:00
Compare commits
8 Commits
feature/mc
...
codex/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd2d6e7485 | ||
|
|
df59a9fd4b | ||
|
|
2e70a3d273 | ||
|
|
3dc6935d19 | ||
|
|
779bb2db78 | ||
|
|
b3b0b194a3 | ||
|
|
e21b2f42d2 | ||
|
|
05a5acee7e |
@@ -2,6 +2,7 @@
|
|||||||
SERVER_PORT=8080
|
SERVER_PORT=8080
|
||||||
FRONTEND_PORT=3000
|
FRONTEND_PORT=3000
|
||||||
WEBSOCKET_PORT=8082
|
WEBSOCKET_PORT=8082
|
||||||
|
OPENISLE_MCP_PORT=8085
|
||||||
MYSQL_PORT=3306
|
MYSQL_PORT=3306
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
RABBITMQ_PORT=5672
|
RABBITMQ_PORT=5672
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.openisle.controller;
|
package com.openisle.controller;
|
||||||
|
|
||||||
|
import com.openisle.dto.CommentContextDto;
|
||||||
import com.openisle.dto.CommentDto;
|
import com.openisle.dto.CommentDto;
|
||||||
import com.openisle.dto.CommentRequest;
|
import com.openisle.dto.CommentRequest;
|
||||||
import com.openisle.dto.PostChangeLogDto;
|
import com.openisle.dto.PostChangeLogDto;
|
||||||
import com.openisle.dto.TimelineItemDto;
|
import com.openisle.dto.TimelineItemDto;
|
||||||
import com.openisle.mapper.CommentMapper;
|
import com.openisle.mapper.CommentMapper;
|
||||||
import com.openisle.mapper.PostChangeLogMapper;
|
import com.openisle.mapper.PostChangeLogMapper;
|
||||||
|
import com.openisle.mapper.PostMapper;
|
||||||
import com.openisle.model.Comment;
|
import com.openisle.model.Comment;
|
||||||
import com.openisle.model.CommentSort;
|
import com.openisle.model.CommentSort;
|
||||||
import com.openisle.service.*;
|
import com.openisle.service.*;
|
||||||
@@ -40,6 +42,7 @@ public class CommentController {
|
|||||||
private final PointService pointService;
|
private final PointService pointService;
|
||||||
private final PostChangeLogService changeLogService;
|
private final PostChangeLogService changeLogService;
|
||||||
private final PostChangeLogMapper postChangeLogMapper;
|
private final PostChangeLogMapper postChangeLogMapper;
|
||||||
|
private final PostMapper postMapper;
|
||||||
|
|
||||||
@Value("${app.captcha.enabled:false}")
|
@Value("${app.captcha.enabled:false}")
|
||||||
private boolean captchaEnabled;
|
private boolean captchaEnabled;
|
||||||
@@ -184,6 +187,37 @@ public class CommentController {
|
|||||||
return itemDtoList;
|
return itemDtoList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/comments/{commentId}/context")
|
||||||
|
@Operation(
|
||||||
|
summary = "Comment context",
|
||||||
|
description = "Get a comment along with its previous comments and related post"
|
||||||
|
)
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Comment context",
|
||||||
|
content = @Content(schema = @Schema(implementation = CommentContextDto.class))
|
||||||
|
)
|
||||||
|
public ResponseEntity<CommentContextDto> getCommentContext(@PathVariable Long commentId) {
|
||||||
|
log.debug("getCommentContext called for comment {}", commentId);
|
||||||
|
Comment comment = commentService.getComment(commentId);
|
||||||
|
CommentContextDto dto = new CommentContextDto();
|
||||||
|
dto.setPost(postMapper.toSummaryDto(comment.getPost()));
|
||||||
|
dto.setTargetComment(commentMapper.toDtoWithReplies(comment));
|
||||||
|
dto.setPreviousComments(
|
||||||
|
commentService
|
||||||
|
.getCommentsBefore(comment)
|
||||||
|
.stream()
|
||||||
|
.map(commentMapper::toDtoWithReplies)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
log.debug(
|
||||||
|
"getCommentContext returning {} previous comments for comment {}",
|
||||||
|
dto.getPreviousComments().size(),
|
||||||
|
commentId
|
||||||
|
);
|
||||||
|
return ResponseEntity.ok(dto);
|
||||||
|
}
|
||||||
|
|
||||||
@DeleteMapping("/comments/{id}")
|
@DeleteMapping("/comments/{id}")
|
||||||
@Operation(summary = "Delete comment", description = "Delete a comment")
|
@Operation(summary = "Delete comment", description = "Delete a comment")
|
||||||
@ApiResponse(responseCode = "200", description = "Deleted")
|
@ApiResponse(responseCode = "200", description = "Deleted")
|
||||||
|
|||||||
@@ -224,6 +224,26 @@ public class PostController {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/recent")
|
||||||
|
@Operation(
|
||||||
|
summary = "Recent posts",
|
||||||
|
description = "List posts created within the specified number of minutes"
|
||||||
|
)
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Recent posts",
|
||||||
|
content = @Content(
|
||||||
|
array = @ArraySchema(schema = @Schema(implementation = PostSummaryDto.class))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
public List<PostSummaryDto> recentPosts(@RequestParam("minutes") int minutes) {
|
||||||
|
return postService
|
||||||
|
.listRecentPosts(minutes)
|
||||||
|
.stream()
|
||||||
|
.map(postMapper::toSummaryDto)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/ranking")
|
@GetMapping("/ranking")
|
||||||
@Operation(summary = "Ranking posts", description = "List posts by view rankings")
|
@Operation(summary = "Ranking posts", description = "List posts by view rankings")
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.openisle.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO representing the context of a comment including its post and previous comments.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CommentContextDto {
|
||||||
|
|
||||||
|
private PostSummaryDto post;
|
||||||
|
private CommentDto targetComment;
|
||||||
|
private List<CommentDto> previousComments;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.openisle.repository;
|
|||||||
import com.openisle.model.Comment;
|
import com.openisle.model.Comment;
|
||||||
import com.openisle.model.Post;
|
import com.openisle.model.Post;
|
||||||
import com.openisle.model.User;
|
import com.openisle.model.User;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
@@ -10,6 +11,10 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||||||
public interface CommentRepository extends JpaRepository<Comment, Long> {
|
public interface CommentRepository extends JpaRepository<Comment, Long> {
|
||||||
List<Comment> findByPostAndParentIsNullOrderByCreatedAtAsc(Post post);
|
List<Comment> findByPostAndParentIsNullOrderByCreatedAtAsc(Post post);
|
||||||
List<Comment> findByParentOrderByCreatedAtAsc(Comment parent);
|
List<Comment> findByParentOrderByCreatedAtAsc(Comment parent);
|
||||||
|
List<Comment> findByPostAndCreatedAtLessThanOrderByCreatedAtAsc(
|
||||||
|
Post post,
|
||||||
|
LocalDateTime createdAt
|
||||||
|
);
|
||||||
List<Comment> findByAuthorOrderByCreatedAtDesc(User author, Pageable pageable);
|
List<Comment> findByAuthorOrderByCreatedAtDesc(User author, Pageable pageable);
|
||||||
List<Comment> findByContentContainingIgnoreCase(String keyword);
|
List<Comment> findByContentContainingIgnoreCase(String keyword);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ public interface PostRepository extends JpaRepository<Post, Long> {
|
|||||||
List<Post> findByStatusOrderByCreatedAtDesc(PostStatus status, Pageable pageable);
|
List<Post> findByStatusOrderByCreatedAtDesc(PostStatus status, Pageable pageable);
|
||||||
List<Post> findByStatusOrderByViewsDesc(PostStatus status);
|
List<Post> findByStatusOrderByViewsDesc(PostStatus status);
|
||||||
List<Post> findByStatusOrderByViewsDesc(PostStatus status, Pageable pageable);
|
List<Post> findByStatusOrderByViewsDesc(PostStatus status, Pageable pageable);
|
||||||
|
List<Post> findByStatusAndCreatedAtGreaterThanEqualOrderByCreatedAtDesc(
|
||||||
|
PostStatus status,
|
||||||
|
LocalDateTime createdAt
|
||||||
|
);
|
||||||
List<Post> findByAuthorAndStatusOrderByCreatedAtDesc(
|
List<Post> findByAuthorAndStatusOrderByCreatedAtDesc(
|
||||||
User author,
|
User author,
|
||||||
PostStatus status,
|
PostStatus status,
|
||||||
|
|||||||
@@ -266,6 +266,27 @@ public class CommentService {
|
|||||||
return replies;
|
return replies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Comment getComment(Long commentId) {
|
||||||
|
log.debug("getComment called for id {}", commentId);
|
||||||
|
return commentRepository
|
||||||
|
.findById(commentId)
|
||||||
|
.orElseThrow(() -> new com.openisle.exception.NotFoundException("Comment not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Comment> getCommentsBefore(Comment comment) {
|
||||||
|
log.debug("getCommentsBefore called for comment {}", comment.getId());
|
||||||
|
List<Comment> comments = commentRepository.findByPostAndCreatedAtLessThanOrderByCreatedAtAsc(
|
||||||
|
comment.getPost(),
|
||||||
|
comment.getCreatedAt()
|
||||||
|
);
|
||||||
|
log.debug(
|
||||||
|
"getCommentsBefore returning {} comments for comment {}",
|
||||||
|
comments.size(),
|
||||||
|
comment.getId()
|
||||||
|
);
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Comment> getRecentCommentsByUser(String username, int limit) {
|
public List<Comment> getRecentCommentsByUser(String username, int limit) {
|
||||||
log.debug("getRecentCommentsByUser called for user {} with limit {}", username, limit);
|
log.debug("getRecentCommentsByUser called for user {} with limit {}", username, limit);
|
||||||
User user = userRepository
|
User user = userRepository
|
||||||
|
|||||||
@@ -770,6 +770,18 @@ public class PostService {
|
|||||||
return listPostsByCategories(null, null, null);
|
return listPostsByCategories(null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Post> listRecentPosts(int minutes) {
|
||||||
|
if (minutes <= 0) {
|
||||||
|
throw new IllegalArgumentException("Minutes must be positive");
|
||||||
|
}
|
||||||
|
LocalDateTime since = LocalDateTime.now().minusMinutes(minutes);
|
||||||
|
List<Post> posts = postRepository.findByStatusAndCreatedAtGreaterThanEqualOrderByCreatedAtDesc(
|
||||||
|
PostStatus.PUBLISHED,
|
||||||
|
since
|
||||||
|
);
|
||||||
|
return sortByPinnedAndCreated(posts);
|
||||||
|
}
|
||||||
|
|
||||||
public List<Post> listPostsByViews(Integer page, Integer pageSize) {
|
public List<Post> listPostsByViews(Integer page, Integer pageSize) {
|
||||||
return listPostsByViews(null, null, page, pageSize);
|
return listPostsByViews(null, null, page, pageSize);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- ${ENV_FILE:-../.env}
|
- ${ENV_FILE:-../.env}
|
||||||
environment:
|
environment:
|
||||||
OPENISLE_MCP_BACKEND_BASE_URL: ${OPENISLE_MCP_BACKEND_BASE_URL:-http://springboot:8080}
|
OPENISLE_MCP_BACKEND_BASE_URL: http://springboot:${SERVER_PORT:-8080}
|
||||||
OPENISLE_MCP_HOST: 0.0.0.0
|
OPENISLE_MCP_HOST: 0.0.0.0
|
||||||
OPENISLE_MCP_PORT: ${OPENISLE_MCP_PORT:-8085}
|
OPENISLE_MCP_PORT: ${OPENISLE_MCP_PORT:-8085}
|
||||||
OPENISLE_MCP_TRANSPORT: ${OPENISLE_MCP_TRANSPORT:-streamable-http}
|
OPENISLE_MCP_TRANSPORT: ${OPENISLE_MCP_TRANSPORT:-streamable-http}
|
||||||
|
|||||||
@@ -100,10 +100,28 @@ server {
|
|||||||
# auth_basic_user_file /etc/nginx/.htpasswd;
|
# auth_basic_user_file /etc/nginx/.htpasswd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# ---------- WEBSOCKET GATEWAY TO :8082 ----------
|
|
||||||
location ^~ /websocket/ {
|
location ^~ /websocket/ {
|
||||||
proxy_pass http://127.0.0.1:8084/;
|
proxy_pass http://127.0.0.1:8084/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_cache off;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /mcp {
|
||||||
|
proxy_pass http://127.0.0.1:8085;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
|||||||
@@ -8,11 +8,8 @@ server {
|
|||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name staging.open-isle.com www.staging.open-isle.com;
|
server_name staging.open-isle.com www.staging.open-isle.com;
|
||||||
|
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/staging.open-isle.com/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/staging.open-isle.com/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/staging.open-isle.com/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/staging.open-isle.com/privkey.pem;
|
||||||
# ssl_certificate /etc/letsencrypt/live/open-isle.com/fullchain.pem;
|
|
||||||
# ssl_certificate_key /etc/letsencrypt/live/open-isle.com/privkey.pem;
|
|
||||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||||
|
|
||||||
@@ -40,59 +37,13 @@ server {
|
|||||||
add_header X-Upstream $upstream_addr always;
|
add_header X-Upstream $upstream_addr always;
|
||||||
}
|
}
|
||||||
|
|
||||||
# 1) 原生 WebSocket
|
|
||||||
location ^~ /api/ws {
|
|
||||||
proxy_pass http://127.0.0.1:8081; # 不要尾随 /,保留原样 URI
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
# 升级所需
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection $connection_upgrade;
|
|
||||||
|
|
||||||
# 统一透传这些头(你在 /api/ 有,/api/ws 也要有)
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
|
|
||||||
proxy_read_timeout 300s;
|
|
||||||
proxy_send_timeout 300s;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_cache off;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 2) SockJS(包含 /info、/iframe.html、/.../websocket 等)
|
|
||||||
location ^~ /api/sockjs {
|
|
||||||
proxy_pass http://127.0.0.1:8081;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection $connection_upgrade;
|
|
||||||
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
|
|
||||||
proxy_read_timeout 300s;
|
|
||||||
proxy_send_timeout 300s;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_cache off;
|
|
||||||
|
|
||||||
# 如要同源 iframe 回退,下面两行二选一(或者交给 Spring Security 的 sameOrigin)
|
|
||||||
# proxy_hide_header X-Frame-Options;
|
|
||||||
# add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------- API ----------
|
# ---------- API ----------
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://127.0.0.1:8081/api/;
|
proxy_pass http://127.0.0.1:8081/api/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
|
||||||
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
@@ -109,7 +60,6 @@ server {
|
|||||||
proxy_cache_bypass 1;
|
proxy_cache_bypass 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- WEBSOCKET GATEWAY TO :8083 ----------
|
|
||||||
location ^~ /websocket/ {
|
location ^~ /websocket/ {
|
||||||
proxy_pass http://127.0.0.1:8083/;
|
proxy_pass http://127.0.0.1:8083/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
@@ -130,4 +80,24 @@ server {
|
|||||||
add_header Cache-Control "no-store" always;
|
add_header Cache-Control "no-store" always;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
location /mcp {
|
||||||
|
proxy_pass http://127.0.0.1:8086;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_cache off;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user