update Journal
This commit is contained in:
@ -1,20 +1,20 @@
|
||||
package com.imyeyu.api.modules.journal.controller;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.imyeyu.api.bean.wechat.InitCodeResponse;
|
||||
import com.imyeyu.api.modules.common.bean.MediaAttach;
|
||||
import com.imyeyu.api.modules.common.bean.SettingKey;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.common.service.AttachmentService;
|
||||
import com.imyeyu.api.modules.common.service.SettingService;
|
||||
import com.imyeyu.api.modules.journal.bean.Location;
|
||||
import com.imyeyu.api.modules.journal.bean.Travel;
|
||||
import com.imyeyu.api.modules.journal.entity.Journal;
|
||||
import com.imyeyu.api.modules.journal.service.JournalService;
|
||||
import com.imyeyu.api.modules.journal.vo.AppendRequest;
|
||||
import com.imyeyu.api.modules.journal.vo.ArchiveRequest;
|
||||
import com.imyeyu.api.modules.journal.vo.JournalPage;
|
||||
import com.imyeyu.api.modules.journal.vo.JournalRequest;
|
||||
import com.imyeyu.api.modules.journal.vo.JournalResponse;
|
||||
import com.imyeyu.api.modules.journal.vo.UpdateRequest;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.network.ArgMap;
|
||||
@ -22,11 +22,14 @@ import com.imyeyu.network.GsonRequest;
|
||||
import com.imyeyu.spring.annotation.AOPLog;
|
||||
import com.imyeyu.spring.annotation.RequestRateLimit;
|
||||
import com.imyeyu.spring.annotation.RequestSingleParam;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -35,6 +38,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 微信小程序糕雨日记接口
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-09-26 15:55
|
||||
*/
|
||||
@ -44,11 +49,16 @@ import java.util.List;
|
||||
@RequestMapping("/journal")
|
||||
public class JournalController {
|
||||
|
||||
private final Gson gson;
|
||||
private final JournalService service;
|
||||
private final SettingService settingService;
|
||||
private final AttachmentService attachmentService;
|
||||
|
||||
/**
|
||||
* 初始化用户 OpenId
|
||||
*
|
||||
* @param code 微信授权码
|
||||
* @return OpenId
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/openid")
|
||||
@ -67,6 +77,31 @@ public class JournalController {
|
||||
}
|
||||
}
|
||||
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequestMapping("/{id}")
|
||||
public JournalResponse detail(@PathVariable Long id) {
|
||||
Journal journal = service.get(id);
|
||||
JournalResponse resp = new JournalResponse();
|
||||
Page<Attachment> attachPage = new Page<>();
|
||||
{
|
||||
Attachment example = new Attachment();
|
||||
example.setBizType(Attachment.BizType.JOURNAL);
|
||||
example.setBizId(journal.getId());
|
||||
attachPage.setEqualsExample(example);
|
||||
}
|
||||
attachPage.setIndex(0);
|
||||
attachPage.setSize(Long.MAX_VALUE);
|
||||
resp.setItems(attachmentService.page(attachPage).getList());
|
||||
BeanUtils.copyProperties(journal, resp);
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建记录
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/create")
|
||||
@ -74,17 +109,35 @@ public class JournalController {
|
||||
service.create(request);
|
||||
}
|
||||
|
||||
@PostMapping("/append")
|
||||
public void append(@RequestBody AppendRequest request) {
|
||||
service.appendItems(request);
|
||||
/**
|
||||
* 更新记录(支持附件差分保存)
|
||||
*
|
||||
* @param request 更新请求,包含 id、基本信息、保留的附件 ID 列表和新上传的临时文件 ID 列表
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/update")
|
||||
public void update(@RequestBody @Valid UpdateRequest request) {
|
||||
service.update(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除记录
|
||||
*
|
||||
* @param id 记录 ID
|
||||
*/
|
||||
@AOPLog
|
||||
@PostMapping("/delete")
|
||||
public void delete(@RequestBody Long thumbId) {
|
||||
attachmentService.deleteMedia(thumbId);
|
||||
public void delete(@RequestBody Long id) {
|
||||
service.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录列表
|
||||
*
|
||||
* @param page 查询页面
|
||||
* @return
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@RequestMapping("/list")
|
||||
@ -95,13 +148,30 @@ public class JournalController {
|
||||
result.setTotal(pageResult.getTotal());
|
||||
result.setList(pageResult.getList().stream().map(item -> {
|
||||
JournalResponse resp = new JournalResponse();
|
||||
resp.setItems(attachmentService.listByBizId(Attachment.BizType.JOURNAL, item.getId()));
|
||||
Page<Attachment> attachPage = new Page<>();
|
||||
{
|
||||
Attachment example = new Attachment();
|
||||
example.setBizType(Attachment.BizType.JOURNAL);
|
||||
example.setBizId(item.getId());
|
||||
attachPage.setEqualsExample(example);
|
||||
}
|
||||
attachPage.setIndex(0);
|
||||
attachPage.setSize(switch (page.getType()) {
|
||||
case NORMAL -> Long.MAX_VALUE;
|
||||
case PREVIEW -> 2; // 原图缩略图并存
|
||||
});
|
||||
resp.setItems(attachmentService.page(attachPage).getList());
|
||||
BeanUtils.copyProperties(item, resp);
|
||||
return resp;
|
||||
}).toList());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有记录日期列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@GetMapping("/list/date")
|
||||
@ -109,6 +179,22 @@ public class JournalController {
|
||||
return service.listDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有记录位置列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@RequestRateLimit
|
||||
@GetMapping("/list/location")
|
||||
public List<Location> listLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 已记录照片、视频数量
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@RequestRateLimit
|
||||
@GetMapping("/total")
|
||||
public long total() {
|
||||
@ -117,6 +203,11 @@ public class JournalController {
|
||||
return journal + journalTravel;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@RequestRateLimit
|
||||
@GetMapping("/travel")
|
||||
public Travel getTravel() {
|
||||
@ -130,6 +221,12 @@ public class JournalController {
|
||||
service.updateTravelLuggage(luggage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建瞬间(上传的临时文件持久化储存,微信限制单次上传数量)
|
||||
*
|
||||
* @param tempFileIds
|
||||
* @return
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/moment/create")
|
||||
@ -137,13 +234,34 @@ public class JournalController {
|
||||
return service.createMoment(tempFileIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 瞬间列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/moment/list")
|
||||
public List<Attachment> listMoment() {
|
||||
return service.listMoment();
|
||||
Page<Attachment> page = new Page<>();
|
||||
{
|
||||
Attachment example = new Attachment();
|
||||
example.setBizType(Attachment.BizType.JOURNAL_MOMENT);
|
||||
example.setBizId(0L);
|
||||
example.setAttachTypeValue(MediaAttach.Type.THUMB);
|
||||
page.setEqualsExample(example);
|
||||
}
|
||||
page.setIndex(0);
|
||||
page.setSize(Long.MAX_VALUE);
|
||||
return attachmentService.page(page).getList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查待上传文件是否已存在
|
||||
*
|
||||
* @param md5s
|
||||
* @return 不存在的文件 md5 列表
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/moment/filter")
|
||||
@ -151,6 +269,11 @@ public class JournalController {
|
||||
return service.filterExistMoment(md5s);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除瞬间
|
||||
*
|
||||
* @param thumbIds 缩略图附件 ID 列表
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/moment/delete")
|
||||
@ -158,6 +281,11 @@ public class JournalController {
|
||||
service.deleteMoment(thumbIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档瞬间,归档记录 ID 不为空时表示归档到指定记录
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
@AOPLog
|
||||
@RequestRateLimit
|
||||
@PostMapping("/moment/archive")
|
||||
|
||||
@ -4,20 +4,12 @@ import com.imyeyu.api.modules.journal.entity.Journal;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @since 2025-09-26 15:54
|
||||
*/
|
||||
public interface JournalMapper extends BaseMapper<Journal, Long> {
|
||||
|
||||
@Select("SELECT COUNT(1) FROM `journal` WHERE `type` = #{type}")
|
||||
long countByType(Journal.Type type);
|
||||
|
||||
@Select("SELECT * FROM `journal` WHERE `type` = #{type} AND `deleted_at` IS NULL ORDER BY `created_at` DESC LIMIT #{offset}, #{limit}")
|
||||
List<Journal> listByType(Journal.Type type, long offset, int limit);
|
||||
|
||||
@Select("SELECT created_at FROM `journal` WHERE `deleted_at` IS NULL")
|
||||
Long[] listDate();
|
||||
}
|
||||
|
||||
@ -3,13 +3,13 @@ package com.imyeyu.api.modules.journal.service;
|
||||
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||
import com.imyeyu.api.modules.journal.bean.Travel;
|
||||
import com.imyeyu.api.modules.journal.entity.Journal;
|
||||
import com.imyeyu.api.modules.journal.vo.AppendRequest;
|
||||
import com.imyeyu.api.modules.journal.vo.ArchiveRequest;
|
||||
import com.imyeyu.api.modules.journal.vo.JournalPage;
|
||||
import com.imyeyu.api.modules.journal.vo.JournalRequest;
|
||||
import com.imyeyu.api.modules.journal.vo.UpdateRequest;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import com.imyeyu.spring.service.DeletableService;
|
||||
import com.imyeyu.spring.service.GettableService;
|
||||
import com.imyeyu.spring.service.PageableService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -17,22 +17,25 @@ import java.util.List;
|
||||
* @author 夜雨
|
||||
* @since 2025-09-26 15:53
|
||||
*/
|
||||
public interface JournalService extends DeletableService<Long> {
|
||||
|
||||
PageResult<Journal> page(JournalPage page) throws TimiException;
|
||||
public interface JournalService extends PageableService<Journal>, GettableService<Journal, Long>, DeletableService<Long> {
|
||||
|
||||
Long[] listDate() throws TimiException;
|
||||
|
||||
void create(JournalRequest request) throws TimiException;
|
||||
|
||||
void appendItems(AppendRequest request) throws TimiException;
|
||||
void update(UpdateRequest request) throws TimiException;
|
||||
|
||||
/**
|
||||
* 过滤已存在瞬间
|
||||
*
|
||||
* @param md5s 原图文件 MD5 列表
|
||||
* @return 未持久化储存的 MD5 列表
|
||||
* @throws TimiException
|
||||
*/
|
||||
String[] filterExistMoment(String[] md5s) throws TimiException;
|
||||
|
||||
List<Attachment> createMoment(String[] tempFileIds) throws TimiException;
|
||||
|
||||
List<Attachment> listMoment() throws TimiException;
|
||||
|
||||
void deleteMoment(Long[] thumbIds) throws TimiException;
|
||||
|
||||
void archiveMoment(ArchiveRequest request) throws TimiException;
|
||||
|
||||
@ -15,16 +15,13 @@ import com.imyeyu.api.modules.journal.bean.Travel;
|
||||
import com.imyeyu.api.modules.journal.entity.Journal;
|
||||
import com.imyeyu.api.modules.journal.mapper.JournalMapper;
|
||||
import com.imyeyu.api.modules.journal.service.JournalService;
|
||||
import com.imyeyu.api.modules.journal.vo.AppendRequest;
|
||||
import com.imyeyu.api.modules.journal.vo.ArchiveRequest;
|
||||
import com.imyeyu.api.modules.journal.vo.JournalPage;
|
||||
import com.imyeyu.api.modules.journal.vo.JournalRequest;
|
||||
import com.imyeyu.api.modules.journal.vo.UpdateRequest;
|
||||
import com.imyeyu.io.IO;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import com.imyeyu.spring.mapper.BaseMapper;
|
||||
import com.imyeyu.spring.service.AbstractEntityService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -36,7 +33,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
@ -63,14 +59,6 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<Journal> page(JournalPage page) {
|
||||
PageResult<Journal> result = new PageResult<>();
|
||||
result.setTotal(mapper.countByType(page.getType()));
|
||||
result.setList(mapper.listByType(page.getType(), page.getOffset(), page.getLimit()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long[] listDate() throws TimiException {
|
||||
return mapper.listDate();
|
||||
@ -105,13 +93,28 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void appendItems(AppendRequest request) throws TimiException {
|
||||
TimiException.required(request.getTempFileIds(), "not found or empty tempFileIds");
|
||||
public void update(UpdateRequest request) throws TimiException {
|
||||
TimiException.required(request.getId(), "not found request.id");
|
||||
try {
|
||||
Journal journal = get(request.getId());
|
||||
BeanUtils.copyProperties(request, journal);
|
||||
super.update(journal);
|
||||
|
||||
// 删除
|
||||
Set<Long> dbAttachSet = attachmentService.listByBizId(Attachment.BizType.JOURNAL, journal.getId(), MediaAttach.Type.THUMB)
|
||||
.stream()
|
||||
.map(Attachment::getId)
|
||||
.collect(Collectors.toSet());
|
||||
Set<Long> retainIds = Set.of(TimiJava.firstNotEmpty(request.getAttachmentIds(), new Long[0]));
|
||||
dbAttachSet.removeAll(retainIds);
|
||||
for (Long removeId : dbAttachSet) {
|
||||
attachmentService.deleteMedia(removeId);
|
||||
}
|
||||
// 新增
|
||||
String[] tempFileIds = request.getTempFileIds();
|
||||
if (TimiJava.isNotEmpty(tempFileIds)) {
|
||||
for (int i = 0; i < tempFileIds.length; i++) {
|
||||
TempFileMetaData metadata = tempFileService.metadata(tempFileIds[i]);
|
||||
File file = tempFileService.get(tempFileIds[i]);
|
||||
@ -123,10 +126,18 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
|
||||
sourceAttach.setInputStream(IO.getInputStream(file));
|
||||
attachmentService.createMedia(sourceAttach);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("create journal error", e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("TODO create journal error");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("update journal error", e);
|
||||
throw new TimiException(TimiCode.ERROR).msgKey("TODO update journal error");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||
@Override
|
||||
public void delete(Long id) {
|
||||
super.delete(id);
|
||||
attachmentService.deleteByBizId(Attachment.BizType.JOURNAL, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -168,15 +179,6 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Attachment> listMoment() throws TimiException {
|
||||
Page page = new Page(0, Integer.MAX_VALUE);
|
||||
LinkedHashMap<String, BaseMapper.OrderType> orderMap = new LinkedHashMap<>();
|
||||
orderMap.put("created_at", BaseMapper.OrderType.DESC);
|
||||
page.setOrderMap(orderMap);
|
||||
return attachmentService.pageByBizId(Attachment.BizType.JOURNAL_MOMENT, 0L, List.of(MediaAttach.Type.THUMB), page).getList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMoment(Long[] thumbIds) throws TimiException {
|
||||
for (int i = 0; i < thumbIds.length; i++) {
|
||||
@ -187,10 +189,16 @@ public class JournalServiceImplement extends AbstractEntityService<Journal, Long
|
||||
@Override
|
||||
public void archiveMoment(ArchiveRequest request) throws TimiException {
|
||||
try {
|
||||
Journal journal = new Journal();
|
||||
Journal journal;
|
||||
if (request.getId() == null) {
|
||||
// 归档为新纪录
|
||||
journal = new Journal();
|
||||
BeanUtils.copyProperties(request, journal);
|
||||
super.create(journal);
|
||||
|
||||
} else {
|
||||
// 归档到指定记录
|
||||
journal = get(request.getId());
|
||||
}
|
||||
Long[] thumbIds = request.getThumbIds();
|
||||
if (TimiJava.isEmpty(thumbIds)) {
|
||||
return;
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
package com.imyeyu.api.modules.journal.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @since 2025-09-30 20:37
|
||||
*/
|
||||
@Data
|
||||
public class AppendRequest {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String[] tempFileIds;
|
||||
}
|
||||
@ -2,6 +2,8 @@ package com.imyeyu.api.modules.journal.vo;
|
||||
|
||||
import com.imyeyu.api.modules.journal.entity.Journal;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@ -10,8 +12,23 @@ import lombok.EqualsAndHashCode;
|
||||
* @since 2025-10-09 18:48
|
||||
*/
|
||||
@Data
|
||||
@Valid
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class JournalPage extends Page {
|
||||
public class JournalPage extends Page<Journal> {
|
||||
|
||||
private Journal.Type type;
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-12-08 16:01
|
||||
*/
|
||||
public enum Type {
|
||||
|
||||
NORMAL,
|
||||
|
||||
PREVIEW
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Type type;
|
||||
}
|
||||
@ -6,6 +6,8 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 创建日志记录
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-09-26 15:56
|
||||
*/
|
||||
@ -13,6 +15,7 @@ import lombok.EqualsAndHashCode;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class JournalRequest extends Journal {
|
||||
|
||||
/** 临时文件 ID 列表 */
|
||||
@Transient
|
||||
private String[] tempFileIds;
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package com.imyeyu.api.modules.journal.vo;
|
||||
|
||||
import com.imyeyu.api.modules.journal.entity.Journal;
|
||||
import com.imyeyu.spring.annotation.table.Transient;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 更新日志记录请求
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-12-08
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class UpdateRequest extends Journal {
|
||||
|
||||
/** 临时文件 ID 列表(新上传的附件) */
|
||||
@Transient
|
||||
private String[] tempFileIds;
|
||||
|
||||
/** 保留的附件 ID 列表(用于差分删除) */
|
||||
@Transient
|
||||
private Long[] attachmentIds;
|
||||
}
|
||||
Reference in New Issue
Block a user