mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-05-10 04:37:29 +08:00
Merge pull request #84 from nagisa77/codex/implement-draft-saving-logic
Add draft post feature
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import PostEditor from '../components/PostEditor.vue'
|
import PostEditor from '../components/PostEditor.vue'
|
||||||
import CategorySelect from '../components/CategorySelect.vue'
|
import CategorySelect from '../components/CategorySelect.vue'
|
||||||
import TagSelect from '../components/TagSelect.vue'
|
import TagSelect from '../components/TagSelect.vue'
|
||||||
@@ -41,6 +41,55 @@ export default {
|
|||||||
const content = ref('')
|
const content = ref('')
|
||||||
const selectedCategory = ref('')
|
const selectedCategory = ref('')
|
||||||
const selectedTags = ref([])
|
const selectedTags = ref([])
|
||||||
|
|
||||||
|
const loadDraft = async () => {
|
||||||
|
const token = getToken()
|
||||||
|
if (!token) return
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/drafts/me`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
})
|
||||||
|
if (res.ok && res.status !== 204) {
|
||||||
|
const data = await res.json()
|
||||||
|
title.value = data.title || ''
|
||||||
|
content.value = data.content || ''
|
||||||
|
selectedCategory.value = data.categoryId || ''
|
||||||
|
selectedTags.value = data.tagIds || []
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadDraft)
|
||||||
|
|
||||||
|
const saveDraft = async () => {
|
||||||
|
const token = getToken()
|
||||||
|
if (!token) {
|
||||||
|
toast.error('请先登录')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/drafts`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
title: title.value,
|
||||||
|
content: content.value,
|
||||||
|
categoryId: selectedCategory.value || null,
|
||||||
|
tagIds: selectedTags.value
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
|
toast.success('草稿已保存')
|
||||||
|
} else {
|
||||||
|
toast.error('保存失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
toast.error('保存失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
const submitPost = async () => {
|
const submitPost = async () => {
|
||||||
if (!title.value.trim()) {
|
if (!title.value.trim()) {
|
||||||
toast.error('标题不能为空')
|
toast.error('标题不能为空')
|
||||||
@@ -86,7 +135,7 @@ export default {
|
|||||||
toast.error('发布失败')
|
toast.error('发布失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { title, content, selectedCategory, selectedTags, submitPost }
|
return { title, content, selectedCategory, selectedTags, submitPost, saveDraft }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
67
src/main/java/com/openisle/controller/DraftController.java
Normal file
67
src/main/java/com/openisle/controller/DraftController.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package com.openisle.controller;
|
||||||
|
|
||||||
|
import com.openisle.model.Draft;
|
||||||
|
import com.openisle.service.DraftService;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/drafts")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DraftController {
|
||||||
|
private final DraftService draftService;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<DraftDto> saveDraft(@RequestBody DraftRequest req, Authentication auth) {
|
||||||
|
Draft draft = draftService.saveDraft(auth.getName(), req.getCategoryId(), req.getTitle(), req.getContent(), req.getTagIds());
|
||||||
|
return ResponseEntity.ok(toDto(draft));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me")
|
||||||
|
public ResponseEntity<DraftDto> getMyDraft(Authentication auth) {
|
||||||
|
return draftService.getDraft(auth.getName())
|
||||||
|
.map(d -> ResponseEntity.ok(toDto(d)))
|
||||||
|
.orElseGet(() -> ResponseEntity.noContent().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/me")
|
||||||
|
public ResponseEntity<?> deleteMyDraft(Authentication auth) {
|
||||||
|
draftService.deleteDraft(auth.getName());
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DraftDto toDto(Draft draft) {
|
||||||
|
DraftDto dto = new DraftDto();
|
||||||
|
dto.setId(draft.getId());
|
||||||
|
dto.setTitle(draft.getTitle());
|
||||||
|
dto.setContent(draft.getContent());
|
||||||
|
if (draft.getCategory() != null) {
|
||||||
|
dto.setCategoryId(draft.getCategory().getId());
|
||||||
|
}
|
||||||
|
dto.setTagIds(draft.getTags().stream().map(com.openisle.model.Tag::getId).collect(Collectors.toList()));
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class DraftRequest {
|
||||||
|
private String title;
|
||||||
|
private String content;
|
||||||
|
private Long categoryId;
|
||||||
|
private List<Long> tagIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class DraftDto {
|
||||||
|
private Long id;
|
||||||
|
private String title;
|
||||||
|
private String content;
|
||||||
|
private Long categoryId;
|
||||||
|
private List<Long> tagIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import com.openisle.service.CommentService;
|
|||||||
import com.openisle.service.PostService;
|
import com.openisle.service.PostService;
|
||||||
import com.openisle.service.ReactionService;
|
import com.openisle.service.ReactionService;
|
||||||
import com.openisle.service.CaptchaService;
|
import com.openisle.service.CaptchaService;
|
||||||
|
import com.openisle.service.DraftService;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -26,6 +27,7 @@ public class PostController {
|
|||||||
private final CommentService commentService;
|
private final CommentService commentService;
|
||||||
private final ReactionService reactionService;
|
private final ReactionService reactionService;
|
||||||
private final CaptchaService captchaService;
|
private final CaptchaService captchaService;
|
||||||
|
private final DraftService draftService;
|
||||||
|
|
||||||
@Value("${app.captcha.enabled:false}")
|
@Value("${app.captcha.enabled:false}")
|
||||||
private boolean captchaEnabled;
|
private boolean captchaEnabled;
|
||||||
@@ -40,6 +42,7 @@ public class PostController {
|
|||||||
}
|
}
|
||||||
Post post = postService.createPost(auth.getName(), req.getCategoryId(),
|
Post post = postService.createPost(auth.getName(), req.getCategoryId(),
|
||||||
req.getTitle(), req.getContent(), req.getTagIds());
|
req.getTitle(), req.getContent(), req.getTagIds());
|
||||||
|
draftService.deleteDraft(auth.getName());
|
||||||
return ResponseEntity.ok(toDto(post));
|
return ResponseEntity.ok(toDto(post));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
41
src/main/java/com/openisle/model/Draft.java
Normal file
41
src/main/java/com/openisle/model/Draft.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package com.openisle.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Table(name = "drafts", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(columnNames = {"author_id"})
|
||||||
|
})
|
||||||
|
public class Draft {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@JoinColumn(name = "author_id")
|
||||||
|
private User author;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "category_id")
|
||||||
|
private Category category;
|
||||||
|
|
||||||
|
@ManyToMany(fetch = FetchType.LAZY)
|
||||||
|
@JoinTable(name = "draft_tags",
|
||||||
|
joinColumns = @JoinColumn(name = "draft_id"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "tag_id"))
|
||||||
|
private Set<Tag> tags = new HashSet<>();
|
||||||
|
}
|
||||||
12
src/main/java/com/openisle/repository/DraftRepository.java
Normal file
12
src/main/java/com/openisle/repository/DraftRepository.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package com.openisle.repository;
|
||||||
|
|
||||||
|
import com.openisle.model.Draft;
|
||||||
|
import com.openisle.model.User;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface DraftRepository extends JpaRepository<Draft, Long> {
|
||||||
|
Optional<Draft> findByAuthor(User author);
|
||||||
|
void deleteByAuthor(User author);
|
||||||
|
}
|
||||||
58
src/main/java/com/openisle/service/DraftService.java
Normal file
58
src/main/java/com/openisle/service/DraftService.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package com.openisle.service;
|
||||||
|
|
||||||
|
import com.openisle.model.Category;
|
||||||
|
import com.openisle.model.Draft;
|
||||||
|
import com.openisle.model.Tag;
|
||||||
|
import com.openisle.model.User;
|
||||||
|
import com.openisle.repository.CategoryRepository;
|
||||||
|
import com.openisle.repository.DraftRepository;
|
||||||
|
import com.openisle.repository.TagRepository;
|
||||||
|
import com.openisle.repository.UserRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DraftService {
|
||||||
|
private final DraftRepository draftRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final CategoryRepository categoryRepository;
|
||||||
|
private final TagRepository tagRepository;
|
||||||
|
|
||||||
|
public Draft saveDraft(String username, Long categoryId, String title, String content, List<Long> tagIds) {
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||||
|
Draft draft = draftRepository.findByAuthor(user).orElse(new Draft());
|
||||||
|
draft.setAuthor(user);
|
||||||
|
draft.setTitle(title);
|
||||||
|
draft.setContent(content);
|
||||||
|
if (categoryId != null) {
|
||||||
|
Category category = categoryRepository.findById(categoryId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Category not found"));
|
||||||
|
draft.setCategory(category);
|
||||||
|
} else {
|
||||||
|
draft.setCategory(null);
|
||||||
|
}
|
||||||
|
Set<Tag> tags = new HashSet<>();
|
||||||
|
if (tagIds != null && !tagIds.isEmpty()) {
|
||||||
|
tags.addAll(tagRepository.findAllById(tagIds));
|
||||||
|
}
|
||||||
|
draft.setTags(tags);
|
||||||
|
return draftRepository.save(draft);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Draft> getDraft(String username) {
|
||||||
|
return userRepository.findByUsername(username)
|
||||||
|
.flatMap(draftRepository::findByAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteDraft(String username) {
|
||||||
|
userRepository.findByUsername(username)
|
||||||
|
.ifPresent(draftRepository::deleteByAuthor);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user