Enhance migration

pull/146/head
johnniang 2019-04-28 20:07:18 +08:00
parent 0ded87246a
commit c0b3f89727
13 changed files with 118 additions and 63 deletions

View File

@ -121,7 +121,11 @@ public class HaloConfiguration {
failureHandler.setObjectMapper(objectMapper); failureHandler.setObjectMapper(objectMapper);
// Config the admin filter // Config the admin filter
adminAuthenticationFilter.addExcludeUrlPatterns("/api/admin/login"); adminAuthenticationFilter.addExcludeUrlPatterns(
"/api/admin/login",
"/api/admin/installations",
"/api/admin/recoveries/migration/*"
);
adminAuthenticationFilter.addTryAuthUrlMethodPattern("/api/admin/comments", HttpMethod.POST.name()); adminAuthenticationFilter.addTryAuthUrlMethodPattern("/api/admin/comments", HttpMethod.POST.name());
adminAuthenticationFilter.addTryAuthUrlMethodPattern("/api/comments", HttpMethod.POST.name()); adminAuthenticationFilter.addTryAuthUrlMethodPattern("/api/comments", HttpMethod.POST.name());
adminAuthenticationFilter.setFailureHandler( adminAuthenticationFilter.setFailureHandler(

View File

@ -1,21 +1,23 @@
package run.halo.app.controller.content.api; package run.halo.app.controller.admin.api;
import freemarker.template.Configuration; import freemarker.template.Configuration;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.bind.annotation.ResponseBody;
import run.halo.app.event.logger.LogEvent; import run.halo.app.event.logger.LogEvent;
import run.halo.app.exception.BadRequestException; import run.halo.app.exception.BadRequestException;
import run.halo.app.model.entity.*; import run.halo.app.model.entity.*;
import run.halo.app.model.enums.AttachmentType; import run.halo.app.model.enums.AttachmentType;
import run.halo.app.model.enums.LogType; import run.halo.app.model.enums.LogType;
import run.halo.app.model.params.CategoryParam;
import run.halo.app.model.params.InstallParam; import run.halo.app.model.params.InstallParam;
import run.halo.app.model.properties.*; import run.halo.app.model.properties.*;
import run.halo.app.model.support.BaseResponse; import run.halo.app.model.support.BaseResponse;
import run.halo.app.service.*; import run.halo.app.service.*;
import run.halo.app.utils.ValidationUtils;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.HashMap; import java.util.HashMap;
@ -31,7 +33,7 @@ import static run.halo.app.model.support.HaloConst.DEFAULT_THEME_ID;
*/ */
@Slf4j @Slf4j
@Controller @Controller
@RequestMapping("/installations") @RequestMapping("/api/admin/installations")
public class InstallController { public class InstallController {
private final UserService userService; private final UserService userService;
@ -70,7 +72,7 @@ public class InstallController {
@PostMapping @PostMapping
@ResponseBody @ResponseBody
public BaseResponse<?> installBlog(@Valid InstallParam installParam) { public BaseResponse<String> installBlog(@RequestBody @Valid InstallParam installParam) {
// TODO Install blog. // TODO Install blog.
// Check is installed // Check is installed
boolean isInstalled = Boolean.parseBoolean(optionService.getByProperty(PrimaryProperties.IS_INSTALLED).orElse(Boolean.FALSE.toString())); boolean isInstalled = Boolean.parseBoolean(optionService.getByProperty(PrimaryProperties.IS_INSTALLED).orElse(Boolean.FALSE.toString()));
@ -136,14 +138,17 @@ public class InstallController {
return null; return null;
} }
@NonNull
private Category createDefaultCategory() { private Category createDefaultCategory() {
Category category = new Category(); CategoryParam category = new CategoryParam();
// TODO Multi level category
category.setName("未分类"); category.setName("未分类");
category.setSlugName("default"); category.setSlugName("default");
category.setDescription("未分类"); category.setDescription("未分类");
return categoryService.create(category);
ValidationUtils.validate(category);
return categoryService.create(category.convertTo());
} }
private User createDefaultUser(InstallParam installParam) { private User createDefaultUser(InstallParam installParam) {

View File

@ -7,6 +7,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import run.halo.app.exception.BadRequestException;
import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.service.OptionService;
import run.halo.app.service.RecoveryService; import run.halo.app.service.RecoveryService;
/** /**
@ -21,15 +24,23 @@ public class RecoveryController {
private final RecoveryService recoveryService; private final RecoveryService recoveryService;
public RecoveryController(RecoveryService recoveryService) { private final OptionService optionService;
public RecoveryController(RecoveryService recoveryService,
OptionService optionService) {
this.recoveryService = recoveryService; this.recoveryService = recoveryService;
this.optionService = optionService;
} }
@PostMapping @PostMapping("migrations/v0_4_3")
@ApiOperation("Migrate from halo v0.4.3") @ApiOperation("Migrates from halo v0.4.3")
public void migrateFromVersion_0_4_3( public void migrateFromVersion_0_4_3(
@ApiParam("This file content type should be json") @ApiParam("This file content type should be json")
@RequestPart("file") MultipartFile file) { @RequestPart("file") MultipartFile file) {
if (optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false)) {
throw new BadRequestException("You cannot migrate after blog installing");
}
recoveryService.migrateFromV0_4_3(file); recoveryService.migrateFromV0_4_3(file);
} }
} }

View File

@ -5,6 +5,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where; import org.hibernate.annotations.Where;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.persistence.*; import javax.persistence.*;
@ -54,6 +55,14 @@ public class Category extends BaseEntity {
public void prePersist() { public void prePersist() {
super.prePersist(); super.prePersist();
id = null; id = null;
if (description == null) {
description = "";
}
if (parentId == null || parentId < 0) {
parentId = 0;
}
} }
} }

View File

@ -2,6 +2,7 @@ package run.halo.app.model.params;
import run.halo.app.model.dto.base.InputConverter; import run.halo.app.model.dto.base.InputConverter;
import run.halo.app.model.entity.Category; import run.halo.app.model.entity.Category;
import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.SlugUtils; import run.halo.app.utils.SlugUtils;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -40,17 +41,17 @@ public class CategoryParam implements InputConverter<Category> {
/** /**
* Parent category. * Parent category.
*/ */
private Integer parentId; private Integer parentId = 0;
@Override @Override
public Category convertTo() { public Category convertTo() {
// Handle default value // Handle default value
if (StringUtils.isBlank(slugName)) { if (StringUtils.isBlank(slugName)) {
slugName = SlugUtils.slugify(name); slugName = SlugUtils.slugify(name);
}
if (parentId == null || parentId < 0) { if (StringUtils.isBlank(slugName)) {
parentId = 0; slugName = HaloUtils.initializeUrlIfBlank(slugName);
}
} }
return InputConverter.super.convertTo(); return InputConverter.super.convertTo();

View File

@ -19,8 +19,7 @@ public class InstallParam extends UserParam {
/** /**
* Blog locale. * Blog locale.
*/ */
@NotBlank(message = "Blog locale must not be blank") private String locale = "zh";
private String locale;
/** /**
* Blog title. * Blog title.

View File

@ -5,6 +5,8 @@ import run.halo.app.repository.base.BaseRepository;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import run.halo.app.repository.base.BaseRepository; import run.halo.app.repository.base.BaseRepository;
import java.util.Optional;
/** /**
* Menu repository. * Menu repository.
* *
@ -12,11 +14,7 @@ import run.halo.app.repository.base.BaseRepository;
*/ */
public interface MenuRepository extends BaseRepository<Menu, Integer> { public interface MenuRepository extends BaseRepository<Menu, Integer> {
/**
* Exists by menu name.
*
* @param name must not be blank
* @return true if exists; false otherwise
*/
boolean existsByName(@NonNull String name); boolean existsByName(@NonNull String name);
boolean existsByIdNotAndName(@NonNull Integer id, @NonNull String name);
} }

View File

@ -108,22 +108,9 @@ public interface BasePostRepository<POST extends BasePost> extends BaseRepositor
*/ */
long countByStatus(@NonNull PostStatus status); long countByStatus(@NonNull PostStatus status);
/** boolean countByUrl(@NonNull String title);
* Count by post url.
*
* @param url post url must not be blank
* @return the count
*/
long countByUrl(@NonNull String url);
/** boolean countByIdNotAndUrl(@NonNull Integer id, @NonNull String title);
* Count by not url and post id not in.
*
* @param id post id must not be null
* @param url post url must not be null
* @return the count
*/
long countByIdNotAndUrl(@NonNull Integer id, @NonNull String url);
/** /**
* Get post by url * Get post by url

View File

@ -55,7 +55,7 @@ public interface CrudService<DOMAIN, ID> {
* @return List * @return List
*/ */
@NonNull @NonNull
List<DOMAIN> listAllByIds(@NonNull Collection<ID> ids); List<DOMAIN> listAllByIds(@Nullable Collection<ID> ids);
/** /**
* List all by ids and sort * List all by ids and sort
@ -65,7 +65,7 @@ public interface CrudService<DOMAIN, ID> {
* @return List * @return List
*/ */
@NonNull @NonNull
List<DOMAIN> listAllByIds(@NonNull Collection<ID> ids, @NonNull Sort sort); List<DOMAIN> listAllByIds(@Nullable Collection<ID> ids, @NonNull Sort sort);
/** /**
* Fetch by id * Fetch by id

View File

@ -190,9 +190,6 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
public POST createOrUpdateBy(POST post) { public POST createOrUpdateBy(POST post) {
Assert.notNull(post, "Post must not be null"); Assert.notNull(post, "Post must not be null");
// Check url
urlMustNotExist(post);
// Render content // Render content
post.setFormatContent(MarkdownUtils.renderMarkdown(post.getOriginalContent())); post.setFormatContent(MarkdownUtils.renderMarkdown(post.getOriginalContent()));
@ -281,6 +278,22 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
return new BasePostDetailDTO().convertFrom(post); return new BasePostDetailDTO().convertFrom(post);
} }
@Override
public POST create(POST post) {
// Check title
urlMustNotExist(post);
return super.create(post);
}
@Override
public POST update(POST post) {
// Check title
urlMustNotExist(post);
return super.update(post);
}
/** /**
* Check if the url is exist. * Check if the url is exist.
* *
@ -288,21 +301,20 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
*/ */
protected void urlMustNotExist(@NonNull POST post) { protected void urlMustNotExist(@NonNull POST post) {
Assert.notNull(post, "Sheet must not be null"); Assert.notNull(post, "Sheet must not be null");
// TODO Refactor this method with BasePostService
// TODO May refactor these queries
// Get url count // Get url count
long count; boolean exist;
if (ServiceUtils.isEmptyId(post.getId())) { if (ServiceUtils.isEmptyId(post.getId())) {
// The sheet will be created // The sheet will be created
count = basePostRepository.countByUrl(post.getUrl()); exist = basePostRepository.countByUrl(post.getUrl());
} else { } else {
// The sheet will be updated // The sheet will be updated
count = basePostRepository.countByIdNotAndUrl(post.getId(), post.getUrl()); exist = basePostRepository.countByIdNotAndUrl(post.getId(), post.getUrl());
} }
if (count > 0) { if (exist) {
throw new AlreadyExistsException("The sheet url has been exist"); throw new AlreadyExistsException("The post url " + post.getUrl() + " has been exist");
} }
} }
} }

View File

@ -13,6 +13,7 @@ import run.halo.app.model.vo.MenuVO;
import run.halo.app.repository.MenuRepository; import run.halo.app.repository.MenuRepository;
import run.halo.app.service.MenuService; import run.halo.app.service.MenuService;
import run.halo.app.service.base.AbstractCrudService; import run.halo.app.service.base.AbstractCrudService;
import run.halo.app.utils.ServiceUtils;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
@ -46,13 +47,6 @@ public class MenuServiceImpl extends AbstractCrudService<Menu, Integer> implemen
public Menu createBy(MenuParam menuParam) { public Menu createBy(MenuParam menuParam) {
Assert.notNull(menuParam, "Menu param must not be null"); Assert.notNull(menuParam, "Menu param must not be null");
// Check the name
boolean exists = menuRepository.existsByName(menuParam.getName());
if (exists) {
throw new AlreadyExistsException("The menu name " + menuParam.getName() + " has already existed").setErrorData(menuParam.getName());
}
// Create an return // Create an return
return create(menuParam.convertTo()); return create(menuParam.convertTo());
} }
@ -83,11 +77,25 @@ public class MenuServiceImpl extends AbstractCrudService<Menu, Integer> implemen
return topLevelMenu.getChildren(); return topLevelMenu.getChildren();
} }
@Override
public Menu create(Menu menu) {
nameMustNotExist(menu);
return super.create(menu);
}
@Override
public Menu update(Menu menu) {
nameMustNotExist(menu);
return super.update(menu);
}
/** /**
* Concrete menu tree. * Concrete menu tree.
* *
* @param parentMenu parent menu vo must not be null * @param parentMenu parent menu vo must not be null
* @param menus a list of menu * @param menus a list of menu
*/ */
private void concreteTree(MenuVO parentMenu, List<Menu> menus) { private void concreteTree(MenuVO parentMenu, List<Menu> menus) {
Assert.notNull(parentMenu, "Parent menu must not be null"); Assert.notNull(parentMenu, "Parent menu must not be null");
@ -148,4 +156,22 @@ public class MenuServiceImpl extends AbstractCrudService<Menu, Integer> implemen
.map(menu -> new MenuDTO().<MenuDTO>convertFrom(menu)) .map(menu -> new MenuDTO().<MenuDTO>convertFrom(menu))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private void nameMustNotExist(@NonNull Menu menu) {
Assert.notNull(menu, "Menu must not be null");
boolean exist = false;
if (ServiceUtils.isEmptyId(menu.getId())) {
// Create action
exist = menuRepository.existsByName(menu.getName());
} else {
// Update action
exist = menuRepository.existsByIdNotAndName(menu.getId(), menu.getName());
}
if (exist) {
throw new AlreadyExistsException("The menu name " + menu.getName() + " already exists");
}
}
} }

View File

@ -181,7 +181,7 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
Assert.notNull(post, "Post param must not be null"); Assert.notNull(post, "Post param must not be null");
// Create or update post // Create or update post
post = createOrUpdateBy(post); post = super.createOrUpdateBy(post);
// List all tags // List all tags
List<Tag> tags = tagService.listAllByIds(tagIds); List<Tag> tags = tagService.listAllByIds(tagIds);

View File

@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -77,6 +78,7 @@ public class RecoveryServiceImpl implements RecoveryService {
} }
@Override @Override
@Async
public void migrateFromV0_4_3(MultipartFile file) { public void migrateFromV0_4_3(MultipartFile file) {
// TODO Async execution // TODO Async execution
// Get migration content // Get migration content
@ -198,7 +200,7 @@ public class RecoveryServiceImpl implements RecoveryService {
Post post = BeanUtils.transformFrom(basePost, Post.class); Post post = BeanUtils.transformFrom(basePost, Post.class);
// Create it // Create it
Post createdPost = postService.create(post); Post createdPost = postService.createOrUpdateBy(post);
Object commentsObject = postMap.get("comments"); Object commentsObject = postMap.get("comments");
// TODO Handle comments // TODO Handle comments
@ -210,6 +212,7 @@ public class RecoveryServiceImpl implements RecoveryService {
try { try {
// Create comments // Create comments
// TODO Don't use createInBatch method
List<PostComment> createdPostComments = postCommentService.createInBatch(postComments); List<PostComment> createdPostComments = postCommentService.createInBatch(postComments);
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to create post comments for post with id " + createdPost.getId(), e); log.warn("Failed to create post comments for post with id " + createdPost.getId(), e);
@ -224,7 +227,7 @@ public class RecoveryServiceImpl implements RecoveryService {
Sheet sheet = BeanUtils.transformFrom(basePost, Sheet.class); Sheet sheet = BeanUtils.transformFrom(basePost, Sheet.class);
// Create it // Create it
Sheet createdSheet = sheetService.create(sheet); Sheet createdSheet = sheetService.createOrUpdateBy(sheet);
Object commentsObject = postMap.get("comments"); Object commentsObject = postMap.get("comments");
// TODO Handle comments // TODO Handle comments