Implementing Self-Media Article Management System
Objectives
- Implement self-media article listing functionality
- Enable article publishing capabilities
- Support article retrieval by ID
- Implement article deletion
- Add article publish/unpublish features
1. Self-Media Article Listing
1.1 Requirements Analysis
1.2 Database Schema and Entity Classes
The content_articles table stores self-media article information.
Article Entity Class
package com.example.model.media.entities;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.apache.ibatis.type.Alias;
import java.io.Serializable;
import java.util.Date;
/**
* Self-media article content information entity
*/
@Data
@TableName("content_articles")
public class ContentArticle implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Primary key
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* Content creator ID
*/
@TableField("creator_id")
private Integer creatorId;
/**
* Article title
*/
@TableField("title")
private String title;
/**
* Article content
*/
@TableField("content")
private String content;
/**
* Article layout type
* 0 - No image article
* 1 - Single image article
* 3 - Multiple image article
*/
@TableField("layout_type")
private Short layoutType;
/**
* Content channel ID
*/
@TableField("channel_id")
private Integer channelId;
@TableField("tags")
private String tags;
/**
* Creation time
*/
@TableField("created_time")
private Date createdTime;
/**
* Submission time
*/
@TableField("submitted_time")
private Date submittedTime;
/**
* Current status
* 0 - Draft
* 1 - Submitted (pending review)
* 2 - Review failed
* 3 - Manual review
* 4 - Manual review approved
* 8 - Approved (pending publication)
* 9 - Published
*/
@TableField("status")
private Short status;
/**
* Scheduled publication time (empty if not scheduled)
*/
@TableField("publish_time")
private Date publishTime;
/**
* Rejection reason
*/
@TableField("rejection_reason")
private String rejectionReason;
/**
* Published article ID
*/
@TableField("published_id")
private Integer publishedId;
/**
* Images separated by commas
*/
@TableField("images")
private String images;
@TableField("is_active")
private Short isActive;
// Status enumeration
@Alias("ArticleStatus")
public enum Status{
DRAFT((short)0), SUBMITTED((short)1), REJECTED((short)2), MANUAL_REVIEW((short)3),
MANUAL_APPROVED((short)4), APPROVED((short)8), PUBLISHED((short)9);
short code;
Status(short code){
this.code = code;
}
public short getCode(){
return this.code;
}
}
}
1.3 Interface Definition
Define interfaces in the API project example-leadnews-apis:
package com.example.api.content;
import com.example.model.common.dtos.ResponseResult;
/**
* Content article interface
*/
public interface ArticleControllerApi {
/**
* Paginated search with filters for content articles
* @param searchRequest
* @return
*/
public ResponseResult findAll(ArticleSearchRequest searchRequest);
}
ArticleSearchRequest
package com.example.model.media.dtos;
import com.example.model.common.dtos.PageRequestDto;
import lombok.Data;
import java.util.Date;
@Data
public class ArticleSearchRequest extends PageRequestDto {
private Short status; // Status
private Date startDate; // Start date
private Date endDate; // End date
private Integer channelId; // Channel ID
private String keyword; // Search keyword
}
1.4 Mapper Implementation
Create com.example.content.mapper.ArticleMapper:
package com.example.content.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.model.media.entities.ContentArticle;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ArticleMapper extends BaseMapper<ContentArticle> {
}
1.5 Service Implementation
Define ArticleService interface: com.example.content.service.ArticleService
package com.example.content.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.model.common.dtos.ResponseResult;
import com.example.model.media.dtos.ArticleSearchRequest;
import com.example.model.media.entities.ContentArticle;
public interface ArticleService extends IService<ContentArticle> {
/**
* Search all content articles
* @return
*/
public ResponseResult findAll(ArticleSearchRequest searchRequest);
}
Define implementation class com.example.content.service.impl.ArticleServiceImpl:
package com.example.content.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.model.common.dtos.PageResponseResult;
import com.example.model.common.dtos.ResponseResult;
import com.example.model.common.enums.AppHttpCodeEnum;
import com.example.model.content.dtos.ArticleSearchRequest;
import com.example.model.content.entities.ContentArticle;
import com.example.model.content.entities.ContentUser;
import com.example.utils.threadlocal.ContentThreadLocalUtils;
import com.example.content.mapper.ArticleMapper;
import com.example.content.service.ArticleService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class ArticleServiceImpl extends ServiceImpl implements ArticleService {
@Value("${file.server.url}")
private String fileServerUrl;
@Override
public ResponseResult findAll(ArticleSearchRequest searchRequest) {
//1. Parameter validation
if(searchRequest == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
// Pagination parameter validation
searchRequest.validateParams();
//2. Paginated search with filters
IPage pageParam = new Page(searchRequest.getPage(), searchRequest.getSize());
LambdaQueryWrapper<ContentArticle> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// Status exact match
if(searchRequest.getStatus() != null){
lambdaQueryWrapper.eq(ContentArticle::getStatus, searchRequest.getStatus());
}
// Channel ID exact match
if(null != searchRequest.getChannelId()){
lambdaQueryWrapper.eq(ContentArticle::getChannelId, searchRequest.getChannelId());
}
// Date range search
if(searchRequest.getStartDate()!=null && searchRequest.getEndDate()!=null){
lambdaQueryWrapper.between(ContentArticle::getPublishTime, searchRequest.getStartDate(), searchRequest.getEndDate());
}
// Keyword search
if(null != searchRequest.getKeyword()){
lambdaQueryWrapper.like(ContentArticle::getTitle, searchRequest.getKeyword());
}
// Get current logged-in user info
ContentUser user = ContentThreadLocalUtils.getUser();
if(user==null){
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
lambdaQueryWrapper.eq(ContentArticle::getCreatorId, user.getId());
// Order by creation date descending
lambdaQueryWrapper.orderByDesc(ContentArticle::getCreatedTime);
IPage pageResult = page(pageParam, lambdaQueryWrapper);
//3. Package and return results
PageResponseResult responseResult = new PageResponseResult(searchRequest.getPage(), searchRequest.getSize(), (int)pageResult.getTotal());
responseResult.setData(pageResult.getRecords());
responseResult.setHost(fileServerUrl);
return responseResult;
}
}
1.6 Controller Implementation
Define controller in the content microservice: com.example.content.controller.ArticleController
package com.example.content.controller.v1;
import com.example.apis.content.ArticleControllerApi;
import com.example.model.common.dtos.ResponseResult;
import com.example.model.content.dtos.ArticleSearchRequest;
import com.example.content.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/articles")
public class ArticleController implements ArticleControllerApi {
@Autowired
private ArticleService articleService;
@PostMapping("/list")
@Override
public ResponseResult findAll(@RequestBody ArticleSearchRequest searchRequest){
return articleService.findAll(searchRequest);
}
}
1.7 Testing
Use Postman for testing, which requires login first to obtain a token before accessing the endpoint.
2. Channel List Display
2.1 Requirements Analysis
When the article list page is loaded, it should automatically fetch and display channel data.
2.2 Interface Definition
Add a method to the ChannelControllerApi interface to query all channel data:
/**
* Query all channels
* @return
*/
public ResponseResult findAllChannels();
2.3 Implementation
In the admin module, modify the ChannelController class and add the following method. The mapper and service were already defined previously:
@GetMapping("/channels")
@Override
public ResponseResult findAllChannels() {
List<Channel> channelList = channelService.list();
return ResponseResult.okResult(channelList);
}
2.4 Modify Content Gateway
In the content gateway's yml configuration file, add the following configuration:
- id: admin
uri: lb://leadnews-admin
predicates:
- Path=/admin/**
filters:
- StripPrefix= 1
2.5 Testing
Test using Postman or directly through the web interface.
3. Article Publishing, Editing, and Saving as Draft
3.1 Requirements Analysis
To save an article, in addition to the content_articles table, we need two additional tables:
content_media- Media assets tablearticle_media_relations- Article-media relationship table
3.2 Approach Analysis
- This functionality is common for saving, editing (if ID exists), and saving as draft
- If editing an article (ID exists), first delete all media relationships
- If no ID, save the article
- Associate images in content with media assets
- Associate cover images with media assets
- If automatic cover selection is chosen, extract images from content as cover images:
- If content has ≤2 images, use single image cover
- If content has ≥3 images, use multiple image cover (3 images)
- If no images in content, use no-image cover
Sample Request Data
{
"title":"Background of Our News Platform",
"type":"1", // 0 - no image, 1 - single image, 3 - multiple images, -1 - auto
"tags":"Technology News",
"publishTime":"2023-05-15T14:30:00.000Z",
"channelId":1,
"images":[
"http://file-server.example.com/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
],
"status":1,
"content":"[
{
\"type\":\"text\",
\"value\":\"With the popularity of smartphones, people are more accustomed to reading news on their phones. Due to the accelerating pace of life, many people can only use fragmented time to get information, so the demand for mobile news clients is also increasing. Our news platform was developed in this context.\"
},
{
\"type\":\"image\",
\"value\":\"http://file-server.example.com/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png\"
}
]"
}
3.3 Interface Definition
Add a method for article submission in the interface: com.example.api.content.ArticleControllerApi
/**
* Submit article for publication
* @param articleDto
* @return
*/
ResponseResult submitArticle(ArticleDto articleDto);
ArticleDto
package com.example.model.media.dtos;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class ArticleDto {
private Integer id;
/**
* Article title
*/
private String title;
/**
* Channel ID
*/
private Integer channelId;
/**
* Tags
*/
private String tags;
/**
* Publication time
*/
private Date publishTime;
/**
* Article content
*/
private String content;
/**
* Cover type: 0 - no image, 1 - single image, 3 - multiple images, -1 - auto
*/
private Short coverType;
/**
* Active status: 0 - inactive, 1 - active
*/
private Short isActive;
/**
* Submission time
*/
private Date submittedTime;
/**
* Status: 1 - submitted, 0 - draft
*/
private Short status;
/**
* Rejection reason
*/
private String rejectionReason;
/**
* Cover image list (multiple images separated by commas)
*/
private List<String> coverImages;
}
3.4 Mapper Definition
Modify ArticleMediaMapper to add a method for batch insertion, used to associate media assets with articles:
void saveRelationsByContent(@Param("mediaUrls") List<String> mediaUrls, @Param("articleId") Integer articleId, @Param("relationType") int relationType);
Create new file resources/mapper/ArticleMediaMapper.xml:
<mapper namespace="com.example.content.mapper.ArticleMediaMapper">
<insert id="saveRelationsByContent">
insert into article_media_relations (media_id, article_id, relation_type, sort_order)
values
<foreach item="mediaUrl" index="sortOrder" collection="mediaUrls" separator="," >
(#{mediaUrl}, #{articleId}, #{relationType}, #{sortOrder})
</foreach>
</insert>
</mapper>
3.5 Business Layer Code
Prepare constants class: com.example.common.constants.content.ContentConstants
package com.example.common.constants.content;
public class ContentConstants {
public static final Short COLLECT_MEDIA = 1; // Collect
public static final Short CANCEL_COLLECT_MEDIA = 0; // Cancel collection
public static final String CONTENT_TYPE_IMAGE = "image";
public static final Short COVER_TYPE_NONE = 0;
public static final Short COVER_TYPE_SINGLE = 1;
public static final Short COVER_TYPE_MULTIPLE = 3;
public static final Short COVER_TYPE_AUTO = -1;
public static final Short RELATION_CONTENT = 0;
public static final Short RELATION_COVER = 1;
}
Add method to ArticleService class:
/**
* Publish or save draft of content article
* @param articleDto
* @param isSubmit 1 for submission, 0 for draft
* @return
*/
ResponseResult saveArticle(ArticleDto articleDto, Short isSubmit);
Implementation class:
@Override
public ResponseResult saveArticle(ArticleDto articleDto, Short isSubmit) {
//1. Parameter validation
if (articleDto == null || StringUtils.isBlank(articleDto.getContent())) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2. Save or update article
ContentArticle article = new ContentArticle();
BeanUtils.copyProperties(articleDto, article);
if (ContentConstants.COVER_TYPE_AUTO.equals(articleDto.getCoverType())) {
article.setCoverType(null);
}
if (articleDto.getCoverImages() != null && articleDto.getCoverImages().size() > 0) {
//[image1.jpg,image2.jpg]
article.setImages(articleDto.getCoverImages().toString().replace("[", ")
.replace("]", "").replace(fileServerUrl, ")
.replace(" ", ""));
}
// Save or update article
saveOrUpdateArticle(article, isSubmit);
//3. Associate article with media assets
String content = articleDto.getContent();
List<Map> contentElements = JSON.parseArray(content, Map.class);
List<String> mediaUrls = extractMediaUrls(contentElements);
//3.1 Associate images in content with media assets
if (isSubmit == ContentArticle.Status.SUBMITTED.getCode() && mediaUrls.size() != 0) {
ResponseResult responseResult = saveContentRelations(mediaUrls, article.getId());
if (responseResult != null) {
return responseResult;
}
}
//3.2 Associate cover images with media assets, set article cover type to auto
if (isSubmit == ContentArticle.Status.SUBMITTED.getCode()) {
ResponseResult responseResult = saveCoverRelations(articleDto, mediaUrls, article);
if (responseResult != null) {
return responseResult;
}
}
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* Set cover image relationships
*
* @param articleDto
* @param mediaUrls
* @param article
* @return
*/
private ResponseResult saveCoverRelations(ArticleDto articleDto, List<String> mediaUrls, ContentArticle article) {
List<String> coverImages = articleDto.getCoverImages();
// Auto match cover
if (articleDto.getCoverType().equals(ContentConstants.COVER_TYPE_AUTO)) {
// Content has ≤2 images, set as single image
if (mediaUrls.size() > 0 && mediaUrls.size() <= 2) {
article.setCoverType(ContentConstants.COVER_TYPE_SINGLE);
coverImages = mediaUrls.stream().limit(1).collect(Collectors.toList());
} else if (mediaUrls.size() > 2) {
// Content has >2 images, set as multiple images
article.setCoverType(ContentConstants.COVER_TYPE_MULTIPLE);
coverImages = mediaUrls.stream().limit(3).collect(Collectors.toList());
} else {
// No images in content, set as no image
article.setCoverType(ContentConstants.COVER_TYPE_NONE);
}
// Update article information
if (coverImages != null && coverImages.size() > 0) {
article.setImages(coverImages.toString().replace("[", ")
.replace("]", "").replace(fileServerUrl, ")
.replace(" ", ""));
}
updateById(article);
}
// Save cover image relationships
if (coverImages != null && coverImages.size() > 0) {
ResponseResult responseResult = saveCoverImageRelations(coverImages, article.getId());
if (responseResult != null) {
return responseResult;
}
}
return null;
}
/**
* @param coverImages
* @param articleId
* @return
*/
private ResponseResult saveCoverImageRelations(List<String> coverImages, Integer articleId) {
List<String> mediaUrls = new ArrayList<>();
for (String image : coverImages) {
mediaUrls.add(image.replace(fileServerUrl,""));
}
return saveMediaRelations(mediaUrls, articleId, ContentConstants.RELATION_COVER);
}
/**
* Save relationships between media assets and article content
*
* @param mediaUrls
* @param articleId
* @return
*/
private ResponseResult saveContentRelations(List<String> mediaUrls, Integer articleId) {
return saveMediaRelations(mediaUrls, articleId, ContentConstants.RELATION_CONTENT);
}
@Autowired
private MediaMapper mediaMapper;
/**
* Save media relationships
*
* @param mediaUrls
* @param articleId
* @param relationType
* @return
*/
private ResponseResult saveMediaRelations(List<String> mediaUrls, Integer articleId, Short relationType) {
//1. Get media asset information from database
LambdaQueryWrapper<Media> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(Media::getUrl, mediaUrls);
lambdaQueryWrapper.eq(Media::getUserId, ContentThreadLocalUtils.getUser().getId());
List<Media> dbMedia = mediaMapper.selectList(lambdaQueryWrapper);
//2. Get media IDs from URLs
List<String> mediaIds = new ArrayList<>();
if (dbMedia != null && dbMedia.size() > 0) {
Map urlIdMap = dbMedia.stream().collect(Collectors.toMap(Media::getUrl, Media::getId));
for (String url : mediaUrls) {
String mediaId = String.valueOf(urlIdMap.get(url));
// Not found
if ("null".equals(mediaId)) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "Image asset is invalid");
}
// Found
mediaIds.add(mediaId);
}
}
//3. Batch save data
articleMediaMapper.saveRelations(mediaIds, articleId, relationType);
return null;
}
/**
* Extract media URLs from content
*
* @param contentElements
* @return
*/
private List<String> extractMediaUrls(List<Map> contentElements) {
List<String> mediaUrls = new ArrayList<>();
for (Map element : contentElements) {
if (element.get("type").equals(ContentConstants.CONTENT_TYPE_IMAGE)) {
String imgUrl = (String) element.get("value");
imgUrl = imgUrl.replace(fileServerUrl, "");
mediaUrls.add(imgUrl);
}
}
return mediaUrls;
}
@Autowired
private ArticleMediaMapper articleMediaMapper;
/**
* Save or update article
*
* @param article
* @param isSubmit
*/
private void saveOrUpdateArticle(ContentArticle article, Short isSubmit) {
article.setStatus(isSubmit);
article.setCreatorId(ContentThreadLocalUtils.getUser().getId());
article.setCreatedTime(new Date());
article.setSubmittedTime(new Date());
article.setIsActive((short) 1);
if (article.getId() == null) {
save(article);
} else {
// If updating, first delete media relationships
LambdaQueryWrapper<ArticleMedia> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ArticleMedia::getArticleId, article.getId());
articleMediaMapper.delete(queryWrapper);
updateById(article);
}
}
3.6 Controller Layer
Add method to ArticleController class:
@PostMapping("/submit")
@Override
public ResponseResult submitArticle(@RequestBody ArticleDto articleDto) {
if(articleDto.getStatus() == ContentArticle.Status.SUBMITTED.getCode()){
// Submit article
return articleService.saveArticle(articleDto, ContentArticle.Status.SUBMITTED.getCode());
}else{
// Save as draft
return articleService.saveArticle(articleDto, ContentArticle.Status.DRAFT.getCode());
}
}
3.7 Testing
Test using Postman or integrate with frontend application.
4. Query Article by ID
4.1 Requirements Analysis
When editing an article, retrieve the article data by ID to display in the edit page.
4.2 Interface Definition
Add method in ArticleControllerApi interface:
/**
* Get article information by ID
* @return
*/
ResponseResult findArticleById(Integer id);
4.3 Business Layer Implementation
Add method to ArticleService interface to query article by ID:
/**
* Query article by ID
* @return
*/
ResponseResult findArticleById(Integer id);
Implementation class:
@Override
public ResponseResult findArticleById(Integer id) {
//1. Parameter validation
if(id == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "Article ID is required");
}
//2. Query data
ContentArticle article = getById(id);
if(article == null){
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_FOUND, "Article does not exist");
}
//3. Return result
ResponseResult responseResult = ResponseResult.okResult(article);
responseResult.setHost(fileServerUrl);
return responseResult;
}
4.4 Controller Layer
Add query method to ArticleController:
@GetMapping("/one/{id}")
@Override
public ResponseResult findArticleById(@PathVariable("id") Integer id) {
return articleService.findArticleById(id);
}
4.5 Testing
Test through the web interface.
5. Article Deletion
5.1 Requirements Analysis
- Articles with status 9 (published) and active status 1 (published) cannot be deleted. Deactivated articles can be deleted.
- Before deleting an article, media relationships must be removed first.
5.2 Interface Definition
Add delete method to ArticleControllerApi:
/**
* Delete article
* @return
*/
ResponseResult deleteArticle(Integer id);
5.3 Business Layer
Add delete method to ArticleService:
/**
* Delete article
* @return
*/
ResponseResult deleteArticle(Integer id);
Implementation class:
@Override
public ResponseResult deleteArticle(Integer id) {
//1. Parameter validation
if(id == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "Article ID is required");
}
//2. Get article data
ContentArticle article = getById(id);
if(article == null){
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_FOUND, "Article does not exist");
}
//3. Check article status - status=9 and isActive=1
if(article.getStatus().equals(ContentArticle.Status.PUBLISHED.getCode()) && article.getIsActive().equals(ContentConstants.CONTENT_ACTIVE)){
return ResponseResult.errorResult(AppHttpCodeEnum.OPERATION_FORBIDDEN, "Cannot delete published article");
}
//4. Remove media relationships
articleMediaMapper.delete(Wrappers.<ArticleMedia>lambdaQuery().eq(ArticleMedia::getArticleId, article.getId()));
//5. Delete article
removeById(article.getId());
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
5.4 Controller Layer
Add delete method to ArticleController:
@GetMapping("/delete/{id}")
@Override
public ResponseResult deleteArticle(@PathVariable("id") Integer id) {
return articleService.deleteArticle(id);
}
6. Article Publishing and Unpublishing
6.1 Requirements Analysis
- Published articles (status 9) can be published (isActive = 1) or unpublished (isActive = 0)
- When publishing/unpublishing, article configuration in the app should be synchronized (not implemented yet, will be added later when covering article review)
6.2 Interface Definition
Add method to ArticleControllerApi:
/**
* Publish or unpublish article
* @param articleDto
* @return
*/
ResponseResult publishOrUnpublish(ArticleDto articleDto);
6.3 Business Layer
Add method to ArticleService:
/**
* Publish or unpublish article
* @param articleDto
* @return
*/
ResponseResult publishOrUnpublish(ArticleDto articleDto);
Implementation class:
@Override
public ResponseResult publishOrUnpublish(ArticleDto articleDto) {
//1. Parameter validation
if(articleDto == null || articleDto.getId() == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2. Query article
ContentArticle article = getById(articleDto.getId());
if(article == null){
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_FOUND, "Article does not exist");
}
//3. Check if article is published
if(!article.getStatus().equals(ContentArticle.Status.PUBLISHED.getCode())){
return ResponseResult.errorResult(AppHttpCodeEnum.OPERATION_FORBIDDEN, "Only published articles can be published or unpublished");
}
//4. Update article status, sync to app (TODO for later implementation)
if(articleDto.getIsActive() != null && articleDto.getIsActive() > -1 && articleDto.getIsActive() < 2){
update(Wrappers.<ContentArticle>lambdaUpdate()
.eq(ContentArticle::getId, articleDto.getId())
.set(ContentArticle::getIsActive, articleDto.getIsActive()));
}
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
6.4 Controller Layer
@PostMapping("/publish_or_unpublish")
@Override
public ResponseResult publishOrUnpublish(@RequestBody ArticleDto articleDto) {
return articleService.publishOrUnpublish(articleDto);
}