Add base post services

pull/755/head
johnniang 2019-12-11 23:10:01 +08:00
parent afdf94b898
commit bd2eebf1cd
14 changed files with 157 additions and 25 deletions

View File

@ -113,8 +113,10 @@ public class AdminController {
adminService.updateApplicationConfig(content); adminService.updateApplicationConfig(content);
} }
@PostMapping("/spring/restart") @PostMapping(value = {"halo/restart", "spring/restart"})
@ApiOperation("Restart halo server")
public void restartApplication() { public void restartApplication() {
Application.restart(); Application.restart();
} }
} }

View File

@ -88,5 +88,4 @@ public class MainController {
response.sendRedirect(favicon); response.sendRedirect(favicon);
} }
} }
} }

View File

@ -67,8 +67,6 @@ public class CommonController extends AbstractErrorController {
*/ */
@GetMapping @GetMapping
public String handleError(HttpServletRequest request, HttpServletResponse response, Model model) { public String handleError(HttpServletRequest request, HttpServletResponse response, Model model) {
HttpStatus status = getStatus(request);
log.error("Request URL: [{}], URI: [{}], Request Method: [{}], IP: [{}]", log.error("Request URL: [{}], URI: [{}], Request Method: [{}], IP: [{}]",
request.getRequestURL(), request.getRequestURL(),
request.getRequestURI(), request.getRequestURI(),
@ -82,6 +80,8 @@ public class CommonController extends AbstractErrorController {
log.debug("Error detail: [{}]", errorDetail); log.debug("Error detail: [{}]", errorDetail);
HttpStatus status = getStatus(request);
if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)) { if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
return contentInternalError(); return contentInternalError();
} else if (status.equals(HttpStatus.NOT_FOUND)) { } else if (status.equals(HttpStatus.NOT_FOUND)) {

View File

@ -1,5 +1,9 @@
package run.halo.app.event.post; package run.halo.app.event.post;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import run.halo.app.utils.ServiceUtils;
/** /**
* Post visit event. * Post visit event.
* *
@ -14,7 +18,8 @@ public class PostVisitEvent extends AbstractVisitEvent {
* @param source the object on which the event initially occurred (never {@code null}) * @param source the object on which the event initially occurred (never {@code null})
* @param postId post id must not be null * @param postId post id must not be null
*/ */
public PostVisitEvent(Object source, Integer postId) { public PostVisitEvent(Object source, @NonNull Integer postId) {
super(source, postId); super(source, postId);
Assert.isTrue(!ServiceUtils.isEmptyId(postId), "Post id must not be empty");
} }
} }

View File

@ -7,6 +7,8 @@ import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
import run.halo.app.config.properties.HaloProperties; import run.halo.app.config.properties.HaloProperties;
import run.halo.app.model.properties.PrimaryProperties; import run.halo.app.model.properties.PrimaryProperties;
@ -15,6 +17,7 @@ import run.halo.app.service.ThemeService;
import run.halo.app.service.UserService; import run.halo.app.service.UserService;
import run.halo.app.utils.FileUtils; import run.halo.app.utils.FileUtils;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.file.*; import java.nio.file.*;
import java.util.Collections; import java.util.Collections;
@ -76,8 +79,9 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
Path source; Path source;
if ("jar".equalsIgnoreCase(themeUri.getScheme())) { if ("jar".equalsIgnoreCase(themeUri.getScheme())) {
// Create new file system for jar // Create new file system for jar
FileSystem fileSystem = FileSystems.newFileSystem(themeUri, Collections.emptyMap()); FileSystem fileSystem = getFileSystem(themeUri);
source = fileSystem.getPath("/BOOT-INF/classes/" + ThemeService.THEME_FOLDER); source = fileSystem.getPath("/BOOT-INF/classes/" + ThemeService.THEME_FOLDER);
} else { } else {
source = Paths.get(themeUri); source = Paths.get(themeUri);
@ -98,4 +102,18 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
} }
} }
@NonNull
private FileSystem getFileSystem(@NonNull URI uri) throws IOException {
Assert.notNull(uri, "Uri must not be null");
FileSystem fileSystem;
try {
fileSystem = FileSystems.getFileSystem(uri);
} catch (FileSystemNotFoundException e) {
fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
}
return fileSystem;
}
} }

View File

@ -0,0 +1,13 @@
package run.halo.app.model.params;
import lombok.Data;
/**
* Post content param.
*
* @author johnniang
*/
@Data
public class PostContentParam {
private String content;
}

View File

@ -142,4 +142,22 @@ public interface BasePostRepository<POST extends BasePost> extends BaseRepositor
@Query("update BasePost p set p.likes = p.likes + :likes where p.id = :postId") @Query("update BasePost p set p.likes = p.likes + :likes where p.id = :postId")
int updateLikes(@Param("likes") long likes, @Param("postId") @NonNull Integer postId); int updateLikes(@Param("likes") long likes, @Param("postId") @NonNull Integer postId);
/**
* Updates post original content/
*
* @param content content could be blank but disallow to be null
* @param postId post id must not be null
* @return updated rows
*/
@Modifying
@Query("update BasePost p set p.originalContent = :content where p.id = :postId")
int updateOriginalContent(@Param("content") @NonNull String content, @Param("postId") @NonNull Integer postId);
@Modifying
@Query("update BasePost p set p.status = :status where p.id = :postId")
int updateStatus(@Param("status") @NonNull PostStatus status, @Param("postId") @NonNull Integer postId);
@Modifying
@Query("update BasePost p set p.formatContent = :formatContent where p.id = :postId")
int updateFormatContent(@Param("formatContent") @NonNull String formatContent, @Param("postId") @NonNull Integer postId);
} }

View File

@ -8,6 +8,7 @@ import run.halo.app.model.dto.post.BasePostDetailDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO; import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.dto.post.BasePostSimpleDTO; import run.halo.app.model.dto.post.BasePostSimpleDTO;
import run.halo.app.model.entity.BasePost; import run.halo.app.model.entity.BasePost;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostStatus; import run.halo.app.model.enums.PostStatus;
import java.util.Date; import java.util.Date;
@ -215,4 +216,24 @@ public interface BasePostService<POST extends BasePost> extends CrudService<POST
@NonNull @NonNull
BasePostDetailDTO convertToDetail(@NonNull POST post); BasePostDetailDTO convertToDetail(@NonNull POST post);
/**
* Updates draft content.
*
* @param content draft content could be blank
* @param postId post id must not be null
* @return updated post
*/
@NonNull
POST updateDraftContent(@Nullable String content, @NonNull Integer postId);
/**
* Updates post status.
*
* @param status post status must not be null
* @param postId post id must not be null
* @return updated post
*/
@NonNull
POST updateStatus(@NonNull PostStatus status, @NonNull Integer postId);
} }

View File

@ -112,14 +112,7 @@ public abstract class BaseMetaServiceImpl<META extends BaseMeta> extends Abstrac
@Override @Override
public Map<String, Object> convertToMap(List<META> metas) { public Map<String, Object> convertToMap(List<META> metas) {
if (CollectionUtils.isEmpty(metas)) { return ServiceUtils.convertToMap(metas, META::getKey, META::getValue);
return Collections.emptyMap();
}
Map<String, Object> result = new HashMap<>();
metas.forEach(meta -> result.put(meta.getKey(), meta.getValue()));
return result;
} }

View File

@ -13,6 +13,7 @@ import org.springframework.util.CollectionUtils;
import run.halo.app.exception.AlreadyExistsException; import run.halo.app.exception.AlreadyExistsException;
import run.halo.app.exception.BadRequestException; import run.halo.app.exception.BadRequestException;
import run.halo.app.exception.NotFoundException; import run.halo.app.exception.NotFoundException;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.dto.post.BasePostDetailDTO; import run.halo.app.model.dto.post.BasePostDetailDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO; import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.dto.post.BasePostSimpleDTO; import run.halo.app.model.dto.post.BasePostSimpleDTO;
@ -320,6 +321,65 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
return new BasePostDetailDTO().convertFrom(post); return new BasePostDetailDTO().convertFrom(post);
} }
@Override
@Transactional
public POST updateDraftContent(String content, Integer postId) {
Assert.isTrue(!ServiceUtils.isEmptyId(postId), "Post id must not be empty");
if (content == null) {
content = "";
}
POST post = getById(postId);
if (!StringUtils.equals(content, post.getOriginalContent())) {
// If content is different with database, then update database
int updatedRows = basePostRepository.updateOriginalContent(content, postId);
if (updatedRows != 1) {
throw new ServiceException("Failed to update original content of post with id " + postId);
}
// Set the content
post.setOriginalContent(content);
}
return post;
}
@Override
@Transactional
public POST updateStatus(PostStatus status, Integer postId) {
Assert.notNull(status, "Post status must not be null");
Assert.isTrue(!ServiceUtils.isEmptyId(postId), "Post id must not be empty");
// Get post
POST post = getById(postId);
if (!status.equals(post.getStatus())) {
// Update post
int updatedRows = basePostRepository.updateStatus(status, postId);
if (updatedRows != 1) {
throw new ServiceException("Failed to update post status of post with id " + postId);
}
post.setStatus(status);
}
// Sync content
if (PostStatus.PUBLISHED.equals(status)) {
// If publish this post, then convert the formatted content
String formatContent = MarkdownUtils.renderHtml(post.getOriginalContent());
int updatedRows = basePostRepository.updateFormatContent(formatContent, postId);
if (updatedRows != 1) {
throw new ServiceException("Failed to update post format content of post with id " + postId);
}
post.setFormatContent(formatContent);
}
return post;
}
@Override @Override
public POST create(POST post) { public POST create(POST post) {
// Check title // Check title

View File

@ -67,7 +67,7 @@ public class MarkdownUtils {
/** /**
* Render Markdown content * Render Markdown content
* *
* @param content content * @param markdown content
* @return String * @return String
*/ */
public static String renderHtml(String markdown) { public static String renderHtml(String markdown) {
@ -108,7 +108,7 @@ public class MarkdownUtils {
/** /**
* Get front-matter * Get front-matter
* *
* @param content content * @param markdown markdown
* @return Map * @return Map
*/ */
public static Map<String, List<String>> getFrontMatter(String markdown) { public static Map<String, List<String>> getFrontMatter(String markdown) {

View File

@ -99,8 +99,9 @@ public class ServiceUtils {
* @return a map which key from list data and value is data * @return a map which key from list data and value is data
*/ */
@NonNull @NonNull
public static <ID, D, V> Map<ID, V> convertToMap(Collection<D> list, Function<D, ID> keyFunction, Function<D, V> valueFunction) { public static <ID, D, V> Map<ID, V> convertToMap(@Nullable Collection<D> list, @NonNull Function<D, ID> keyFunction, @NonNull Function<D, V> valueFunction) {
Assert.notNull(keyFunction, "mapping function must not be null"); Assert.notNull(keyFunction, "Key function must not be null");
Assert.notNull(valueFunction, "Value function must not be null");
if (CollectionUtils.isEmpty(list)) { if (CollectionUtils.isEmpty(list)) {
return Collections.emptyMap(); return Collections.emptyMap();

View File

@ -14,10 +14,10 @@ spring:
type: com.zaxxer.hikari.HikariDataSource type: com.zaxxer.hikari.HikariDataSource
# H2 Database 配置 # H2 Database 配置
driver-class-name: org.h2.Driver # driver-class-name: org.h2.Driver
url: jdbc:h2:file:~/halo-test/db/halo # url: jdbc:h2:file:~/halo-test/db/halo
username: admin # username: admin
password: 123456 # password: 123456
# MySQL 配置 # MySQL 配置
# driver-class-name: com.mysql.cj.jdbc.Driver # driver-class-name: com.mysql.cj.jdbc.Driver

View File

@ -7,13 +7,14 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest @SpringBootTest
@ActiveProfiles("dev") @ActiveProfiles("test")
public class PostServiceImplTest { public class PostServiceImplTest {
String standardMdContent = "---\n" + private String standardMdContent = "---\n" +
"title: springfox-swagger2配置成功但无法访问/swagger-ui.html\n" + "title: springfox-swagger2配置成功但无法访问/swagger-ui.html\n" +
"tags:\n" + "tags:\n" +
" - spring boot\n" + " - spring boot\n" +
@ -26,7 +27,7 @@ public class PostServiceImplTest {
"\n" + "\n" +
"在前后端分离项目中,通常需要用到 API 文档springfox 开发的 **[SpringFox](https://github.com/springfox/springfox)** 可以实现自动化 json API 文档。"; "在前后端分离项目中,通常需要用到 API 文档springfox 开发的 **[SpringFox](https://github.com/springfox/springfox)** 可以实现自动化 json API 文档。";
String nonStandardMdContent = "---\n" + private String nonStandardMdContent = "---\n" +
"title: Basic concepts of JPA\n" + "title: Basic concepts of JPA\n" +
"date: 2018-08-03 11:57:00\n" + "date: 2018-08-03 11:57:00\n" +
"tags: ['spring', 'jpa', 'database', 'concept']\n" + "tags: ['spring', 'jpa', 'database', 'concept']\n" +
@ -45,6 +46,7 @@ public class PostServiceImplTest {
} }
@Test @Test
@Transactional
public void markdownImportTest() { public void markdownImportTest() {
postService.importMarkdown(standardMdContent, "standard"); postService.importMarkdown(standardMdContent, "standard");
postService.importMarkdown(nonStandardMdContent, "nonStandard"); postService.importMarkdown(nonStandardMdContent, "nonStandard");