up
This commit is contained in:
1392
managersystem.sql
1392
managersystem.sql
File diff suppressed because it is too large
Load Diff
@@ -88,8 +88,21 @@ public class BizSubModuleController extends BaseController
|
||||
{
|
||||
return AjaxResult.error("父模块不存在");
|
||||
}
|
||||
// 仅模块接取人可新增子模块
|
||||
if (!String.valueOf(getUserId()).equals(module.getAssignee()))
|
||||
// 仅模块接取人可新增子模块(兼容历史:assignee 可能存为 用户ID/用户名/昵称)
|
||||
Long currentUserId = getUserId();
|
||||
boolean isAssignee = false;
|
||||
if (module.getAssignee() != null)
|
||||
{
|
||||
if (String.valueOf(currentUserId).equals(module.getAssignee())) {
|
||||
isAssignee = true;
|
||||
} else {
|
||||
String username = getUsername();
|
||||
if (username != null && username.equals(module.getAssignee())) {
|
||||
isAssignee = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isAssignee)
|
||||
{
|
||||
return AjaxResult.error("仅接取人可在该模块下新增子模块");
|
||||
}
|
||||
@@ -98,7 +111,8 @@ public class BizSubModuleController extends BaseController
|
||||
{
|
||||
return AjaxResult.error("父模块未处于进行中,无法新增子模块");
|
||||
}
|
||||
subModule.setStatus("0");
|
||||
// 新增子模块默认进入进行中状态(与父模块接取人一致的执行中)
|
||||
subModule.setStatus("1");
|
||||
return toAjax(subService.insertBizSubModule(subModule));
|
||||
}
|
||||
|
||||
@@ -114,7 +128,18 @@ public class BizSubModuleController extends BaseController
|
||||
return AjaxResult.error("子模块不存在");
|
||||
}
|
||||
BizModule module = moduleService.selectBizModuleByModuleId(current.getModuleId());
|
||||
if (!String.valueOf(getUserId()).equals(module.getAssignee()))
|
||||
Long currentUserId = getUserId();
|
||||
String username = getUsername();
|
||||
boolean isAssignee = false;
|
||||
if (module != null && module.getAssignee() != null)
|
||||
{
|
||||
if (String.valueOf(currentUserId).equals(module.getAssignee())) {
|
||||
isAssignee = true;
|
||||
} else if (username != null && username.equals(module.getAssignee())) {
|
||||
isAssignee = true;
|
||||
}
|
||||
}
|
||||
if (!isAssignee)
|
||||
{
|
||||
return AjaxResult.error("仅接取人可修改子模块");
|
||||
}
|
||||
@@ -135,7 +160,18 @@ public class BizSubModuleController extends BaseController
|
||||
return AjaxResult.error("子模块不存在");
|
||||
}
|
||||
BizModule module = moduleService.selectBizModuleByModuleId(sub.getModuleId());
|
||||
if (!String.valueOf(getUserId()).equals(module.getAssignee()))
|
||||
Long currentUserId = getUserId();
|
||||
String username = getUsername();
|
||||
boolean isAssignee = false;
|
||||
if (module != null && module.getAssignee() != null)
|
||||
{
|
||||
if (String.valueOf(currentUserId).equals(module.getAssignee())) {
|
||||
isAssignee = true;
|
||||
} else if (username != null && username.equals(module.getAssignee())) {
|
||||
isAssignee = true;
|
||||
}
|
||||
}
|
||||
if (!isAssignee)
|
||||
{
|
||||
return AjaxResult.error("仅接取人可删除子模块");
|
||||
}
|
||||
|
@@ -7,6 +7,8 @@ import com.ruoyi.common.utils.SecurityUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.models.mapper.BizModuleMapper;
|
||||
import com.ruoyi.models.mapper.BizSubModuleMapper;
|
||||
import com.ruoyi.models.domain.BizSubModule;
|
||||
import com.ruoyi.models.domain.BizModule;
|
||||
import com.ruoyi.models.service.IBizModuleService;
|
||||
import com.ruoyi.project.domain.BizProject;
|
||||
@@ -31,6 +33,9 @@ public class BizModuleServiceImpl implements IBizModuleService {
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
|
||||
@Autowired
|
||||
private BizSubModuleMapper bizSubModuleMapper;
|
||||
|
||||
/**
|
||||
* 查询模块
|
||||
*
|
||||
@@ -157,6 +162,7 @@ public class BizModuleServiceImpl implements IBizModuleService {
|
||||
module.setAssignee(String.valueOf(userId));
|
||||
}
|
||||
module.setAssignTime(DateUtils.getNowDate());
|
||||
// 避免重复接取:若已有相同 moduleId 的进行中记录,这里只更新而不新增
|
||||
return updateBizModule(module);
|
||||
}
|
||||
|
||||
@@ -173,7 +179,7 @@ public class BizModuleServiceImpl implements IBizModuleService {
|
||||
throw new RuntimeException("模块不存在或状态异常");
|
||||
}
|
||||
|
||||
if (!String.valueOf(userId).equals(module.getAssignee())) {
|
||||
if (!isCurrentUserAssignee(module, userId)) {
|
||||
throw new RuntimeException("您不是此模块的接取人");
|
||||
}
|
||||
|
||||
@@ -206,10 +212,18 @@ public class BizModuleServiceImpl implements IBizModuleService {
|
||||
throw new RuntimeException("模块不存在或状态异常");
|
||||
}
|
||||
|
||||
if (!String.valueOf(userId).equals(module.getAssignee())) {
|
||||
if (!isCurrentUserAssignee(module, userId)) {
|
||||
throw new RuntimeException("您不是此模块的接取人");
|
||||
}
|
||||
|
||||
// 约束:父模块完成前,需所有子模块均为已完成(status = '2',且未被软删除)
|
||||
List<BizSubModule> subModules = bizSubModuleMapper.selectByModuleId(moduleId);
|
||||
boolean hasUnfinished = subModules.stream()
|
||||
.anyMatch(sm -> sm != null && !"2".equals(sm.getStatus()));
|
||||
if (hasUnfinished) {
|
||||
throw new RuntimeException("仍有未完成的子模块,无法完成父模块");
|
||||
}
|
||||
|
||||
module.setStatus("2"); // 已完成
|
||||
module.setFinishTime(DateUtils.getNowDate());
|
||||
int result = updateBizModule(module);
|
||||
@@ -220,6 +234,35 @@ public class BizModuleServiceImpl implements IBizModuleService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前用户是否为该模块的接取人(兼容历史:assignee 可能保存为 用户ID/用户名/昵称)
|
||||
*/
|
||||
private boolean isCurrentUserAssignee(BizModule module, Long userId) {
|
||||
if (module == null) {
|
||||
return false;
|
||||
}
|
||||
String assignee = module.getAssignee();
|
||||
if (assignee == null) {
|
||||
return false;
|
||||
}
|
||||
// 1) 与用户ID字符串相等
|
||||
if (String.valueOf(userId).equals(assignee)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
SysUser u = sysUserService.selectUserById(userId);
|
||||
if (u != null) {
|
||||
if (u.getUserName() != null && u.getUserName().equals(assignee)) {
|
||||
return true;
|
||||
}
|
||||
if (u.getNickName() != null && u.getNickName().equals(assignee)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查项目是否完成
|
||||
* @param projectId 项目ID
|
||||
|
@@ -3,6 +3,7 @@ package com.ruoyi.models.service.impl;
|
||||
import java.util.List;
|
||||
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.models.mapper.BizSubModuleMapper;
|
||||
@@ -82,7 +83,9 @@ public class BizSubModuleServiceImpl implements IBizSubModuleService
|
||||
{
|
||||
throw new RuntimeException("父模块不存在");
|
||||
}
|
||||
if (!String.valueOf(userId).equals(module.getAssignee()))
|
||||
String currentUsername = SecurityUtils.getUsername();
|
||||
if (!(String.valueOf(userId).equals(module.getAssignee())
|
||||
|| (module.getAssignee() != null && module.getAssignee().equals(currentUsername))))
|
||||
{
|
||||
throw new RuntimeException("仅父模块接取人可接取子模块");
|
||||
}
|
||||
@@ -107,7 +110,9 @@ public class BizSubModuleServiceImpl implements IBizSubModuleService
|
||||
{
|
||||
throw new RuntimeException("父模块不存在");
|
||||
}
|
||||
if (!String.valueOf(userId).equals(module.getAssignee()))
|
||||
String currentUsername2 = SecurityUtils.getUsername();
|
||||
if (!(String.valueOf(userId).equals(module.getAssignee())
|
||||
|| (module.getAssignee() != null && module.getAssignee().equals(currentUsername2))))
|
||||
{
|
||||
throw new RuntimeException("仅父模块接取人可完成子模块");
|
||||
}
|
||||
@@ -117,6 +122,8 @@ public class BizSubModuleServiceImpl implements IBizSubModuleService
|
||||
sub.setUpdateTime(DateUtils.getNowDate());
|
||||
return updateBizSubModule(sub);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -164,24 +164,9 @@ public class BizDocController extends BaseController
|
||||
Long currentUserId = getUserId();
|
||||
|
||||
if ("0".equals(kindType)) {
|
||||
// 项目文档:项目管理员 或 项目参与者(该项目下任一模块的接取人/被指派人)可上传
|
||||
// 项目文档:仅项目创建者可上传
|
||||
BizProject project = bizProjectService.selectBizProjectByProjectId(projectId);
|
||||
boolean isOwner = project != null && project.getOwnerId() != null && project.getOwnerId().equals(currentUserId);
|
||||
boolean isParticipant = false;
|
||||
if (!isOwner) {
|
||||
// 参与者判断:当前用户是否在该项目下拥有被指派的模块
|
||||
com.ruoyi.models.domain.BizModule query = new com.ruoyi.models.domain.BizModule();
|
||||
query.setProjectId(projectId);
|
||||
java.util.List<com.ruoyi.models.domain.BizModule> modules = bizModuleService.selectBizModuleList(query);
|
||||
if (modules != null) {
|
||||
for (com.ruoyi.models.domain.BizModule m : modules) {
|
||||
if (m.getAssignee() != null && (m.getAssignee().equals(String.valueOf(currentUserId)) || m.getAssignee().equals(getUsername()))) {
|
||||
isParticipant = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isOwner && !isParticipant) {
|
||||
if (project == null || project.getOwnerId() == null || !project.getOwnerId().equals(currentUserId)) {
|
||||
return AjaxResult.error("无权上传项目文档");
|
||||
}
|
||||
} else if ("1".equals(kindType)) {
|
||||
|
@@ -46,7 +46,9 @@ public class BizDocServiceImpl implements IBizDocService
|
||||
// - 项目文档:当前用户为项目所有者
|
||||
// - 模块文档:当前用户为该模块被指派人
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
String currentUsername = SecurityUtils.getUsername();
|
||||
bizDoc.getParams().put("currentUserId", currentUserId);
|
||||
bizDoc.getParams().put("currentUsername", currentUsername);
|
||||
|
||||
return bizDocMapper.selectBizDocList(bizDoc);
|
||||
}
|
||||
|
@@ -56,12 +56,12 @@ public class SysUserController extends BaseController {
|
||||
/**
|
||||
* 获取用户列表
|
||||
*/
|
||||
// 平台管理员独占用户管理权限
|
||||
@PreAuthorize("@ss.hasRole('platform_admin') and @ss.hasPermi('system:user:list')")
|
||||
// 用户查询:平台管理员与项目管理员均可用于业务指派选择(不暴露敏感信息)
|
||||
@PreAuthorize("(@ss.hasRole('platform_admin') or @ss.hasRole('project_admin')) and @ss.hasPermi('system:user:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(SysUser user) {
|
||||
startPage();
|
||||
// 平台管理员不受数据范围限制;其他无权访问已由 @PreAuthorize 拦截
|
||||
// 项目管理员仅用于指派下拉选择:读取基本字段
|
||||
List<SysUser> list = userService.selectUserListAll(user);
|
||||
return getDataTable(list);
|
||||
}
|
||||
@@ -195,7 +195,7 @@ public class SysUserController extends BaseController {
|
||||
/**
|
||||
* 根据用户编号获取授权角色
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:user:query')")
|
||||
@PreAuthorize("@ss.hasPermi('system:user:query') or @ss.hasRole('project_admin')")
|
||||
@GetMapping("/authRole/{userId}")
|
||||
public AjaxResult authRole(@PathVariable("userId") Long userId) {
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
|
@@ -154,7 +154,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</update>
|
||||
|
||||
<select id="selectAssignedModules" parameterType="Long" resultMap="BizModuleResult">
|
||||
select m.module_id,
|
||||
select DISTINCT m.module_id,
|
||||
m.project_id,
|
||||
m.module_name,
|
||||
m.status,
|
||||
|
@@ -53,23 +53,42 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="docName != null and docName != ''"> and d.file_name like concat('%', #{docName}, '%')</if>
|
||||
<if test="docPath != null and docPath != ''"> and d.file_url = #{docPath}</if>
|
||||
and d.del_flag = '0'
|
||||
<!-- 权限控制:
|
||||
- 项目文档(kind_type='0'):仅项目创建者可见
|
||||
- 模块文档(kind_type='1'):仅该模块被指派用户可见
|
||||
<!-- 权限控制(最终口径):
|
||||
- 项目文档(kind_type='0'):项目创建者可见全部;普通用户仅能看到自己上传的项目文档
|
||||
- 模块文档(kind_type='1'):项目创建者可见全部;普通用户可见:
|
||||
a) 自己上传的模块文档
|
||||
b) 自己是该模块接取人(assignee = 当前用户名 或 = 当前用户ID)
|
||||
c) 自己被指派(designated_user = 当前用户ID)
|
||||
-->
|
||||
<if test="params != null and params.currentUserId != null">
|
||||
and (
|
||||
-- 项目文档:仅项目创建者
|
||||
(kind_type = '0' and exists (
|
||||
-- 项目文档:仅项目创建者可见 或 自己上传的项目文档
|
||||
(kind_type = '0' and (
|
||||
exists (
|
||||
select 1 from biz_project p
|
||||
where p.project_id = d.project_id
|
||||
and p.owner_id = #{params.currentUserId}
|
||||
)
|
||||
or d.upload_by = #{params.currentUsername}
|
||||
))
|
||||
-- 模块文档:仅该模块指派用户
|
||||
or (kind_type = '1' and exists (
|
||||
-- 模块文档:项目创建者可见全部;普通用户可见与自己强相关的
|
||||
or (kind_type = '1' and (
|
||||
exists (
|
||||
select 1 from biz_project p
|
||||
where p.project_id = d.project_id
|
||||
and p.owner_id = #{params.currentUserId}
|
||||
)
|
||||
or d.upload_by = #{params.currentUsername}
|
||||
or exists (
|
||||
select 1 from biz_module m
|
||||
where m.module_id = d.module_id
|
||||
and m.designated_user = #{params.currentUserId}
|
||||
and m.del_flag = '0'
|
||||
and (
|
||||
m.designated_user = #{params.currentUserId}
|
||||
or CAST(m.assignee AS CHAR) = CAST(#{params.currentUserId} AS CHAR)
|
||||
or m.assignee = #{params.currentUsername}
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
</if>
|
||||
|
@@ -146,10 +146,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<select id="checkEmailUnique" parameterType="String" resultMap="SysUserResult">
|
||||
select user_id, email from sys_user where email = #{email} and del_flag = '0' limit 1
|
||||
</select>
|
||||
<select id="selectUserIdByUserCount" resultType="java.lang.Long">
|
||||
select user_id from sys_user <where>
|
||||
<if test="userName != null">user_name = #{userCount}</if>
|
||||
</where>
|
||||
<select id="selectUserIdByUserCount" parameterType="String" resultType="java.lang.Long">
|
||||
select user_id from sys_user where user_name = #{userCount} and del_flag = '0' limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
|
||||
|
@@ -174,6 +174,11 @@ export default {
|
||||
const roles = this.$store.state.user.roles || []
|
||||
const hasNormal = roles.some(r => (typeof r === 'string' ? r : r.roleKey) === 'normal_user')
|
||||
return hasNormal && !this.isProjectAdmin
|
||||
},
|
||||
// 当前用户ID(用于前端过滤:项目管理员仅看见自己创建的项目)
|
||||
currentUserId() {
|
||||
const u = this.$store.state.user || {}
|
||||
return u.userId || u.user_id || u.id || null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -248,27 +253,17 @@ export default {
|
||||
methods: {
|
||||
async initProjects() {
|
||||
try {
|
||||
// 并行获取:管理员可见项目 + 我参与的模块聚合项目,取并集,避免有时显示有时不显示
|
||||
const tasks = []
|
||||
if (this.isProjectAdmin) {
|
||||
tasks.push(listProject({ pageNum: 1, pageSize: 9999 }))
|
||||
// 项目管理员:仅显示自己创建的项目
|
||||
const projRes = await listProject({ pageNum: 1, pageSize: 9999 })
|
||||
const owned = (projRes && Array.isArray(projRes.rows))
|
||||
? projRes.rows.filter(p => String(p.ownerId) === String(this.currentUserId))
|
||||
: []
|
||||
this.projectOptions = owned.map(p => ({ projectId: p.projectId, projectName: p.projectName || (`项目#${p.projectId}`) }))
|
||||
} else {
|
||||
// 也许用户具备项目管理员角色但权限范围有限,这里仍保留 getMyModules 以保证兜底
|
||||
tasks.push(Promise.resolve({ rows: [] }))
|
||||
}
|
||||
tasks.push(getMyModules({ pageNum: 1, pageSize: 9999 }))
|
||||
|
||||
const [projRes, myModRes] = await Promise.all(tasks)
|
||||
// 普通用户:根据“我的模块”推导可见项目
|
||||
const myModRes = await getMyModules({ pageNum: 1, pageSize: 9999 })
|
||||
const map = new Map()
|
||||
// 管理员项目
|
||||
if (projRes && Array.isArray(projRes.rows)) {
|
||||
projRes.rows.forEach(p => {
|
||||
if (p.projectId && !map.has(p.projectId)) {
|
||||
map.set(p.projectId, { projectId: p.projectId, projectName: p.projectName || (`项目#${p.projectId}`) })
|
||||
}
|
||||
})
|
||||
}
|
||||
// 我参与的模块所在项目
|
||||
if (myModRes && Array.isArray(myModRes.rows)) {
|
||||
myModRes.rows.forEach(m => {
|
||||
if (m.projectId && !map.has(m.projectId)) {
|
||||
@@ -276,15 +271,17 @@ export default {
|
||||
}
|
||||
})
|
||||
}
|
||||
// 路由上下文一定补入
|
||||
// 注:普通用户可保留路由上下文补入,管理员不补入非本人项目
|
||||
if (this.currentContext.projectId && !map.has(this.currentContext.projectId)) {
|
||||
map.set(this.currentContext.projectId, { projectId: this.currentContext.projectId, projectName: this.currentContext.projectName || (`项目#${this.currentContext.projectId}`) })
|
||||
}
|
||||
this.projectOptions = Array.from(map.values())
|
||||
}
|
||||
|
||||
// 如果之前没选过,按上下文预选
|
||||
// 如果之前没选过,按上下文预选(仅当选项中存在)
|
||||
if (!this.upload.meta.projectId && this.currentContext.projectId) {
|
||||
this.upload.meta.projectId = this.currentContext.projectId
|
||||
const exists = this.projectOptions.some(p => String(p.projectId) === String(this.currentContext.projectId))
|
||||
this.upload.meta.projectId = exists ? this.currentContext.projectId : null
|
||||
}
|
||||
if (this.upload.meta.projectId) {
|
||||
await this.handleProjectChange(this.upload.meta.projectId)
|
||||
@@ -402,6 +399,10 @@ export default {
|
||||
}
|
||||
// 每次打开均刷新一次项目集合,避免角色/指派变更导致的偶发不显示
|
||||
await this.initProjects()
|
||||
// 普通用户默认选择“模块文档”,并禁用“项目文档”的单选
|
||||
if (this.isNormalUser && !this.isProjectAdmin) {
|
||||
this.upload.meta.kindType = 1
|
||||
}
|
||||
// 预选:优先使用路由上下文;否则选第一项
|
||||
if (this.currentContext.projectId) {
|
||||
this.upload.meta.projectId = this.currentContext.projectId
|
||||
|
@@ -156,11 +156,11 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="描述" prop="description" />
|
||||
<el-table-column label="操作" align="center" width="220">
|
||||
<el-table-column label="操作" align="center" width="260">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleEditSub(scope.row)" v-if="canOperateSub()">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDeleteSub(scope.row)" v-if="canOperateSub()">删除</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-check" @click="handleCompleteSub(scope.row)" v-if="canOperateSub() && scope.row.status==='1'">完成</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-check" @click="handleCompleteSub(scope.row)" v-if="canOperateSub() && scope.row.status!=='2'">完成</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
@@ -582,6 +582,11 @@ export default {
|
||||
// 添加/修改模块(根据是否存在 moduleId 判断)
|
||||
subMitModel() {
|
||||
this.modelsForm.projectId = this.projectId;
|
||||
// 表单校验(模块名称、截止日期等必填)
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
const isEdit = !!this.modelsForm.moduleId;
|
||||
if (!isEdit) {
|
||||
// 新增:默认待接取
|
||||
@@ -609,6 +614,7 @@ export default {
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
//接取模块
|
||||
@@ -635,7 +641,19 @@ export default {
|
||||
|
||||
//添加模块
|
||||
ModelsAdd() {
|
||||
// 打开模块添加对话框,并传递projectId
|
||||
// 打开模块添加对话框,并传递projectId;清空之前编辑残留,确保走新增
|
||||
this.modelsForm = {
|
||||
moduleId: null,
|
||||
projectId: this.projectId,
|
||||
moduleName: null,
|
||||
status: 0,
|
||||
assignee: null,
|
||||
assignTime: null,
|
||||
del_flag: null,
|
||||
create_by: null,
|
||||
finishTime: null,
|
||||
deadline: null
|
||||
}
|
||||
this.Models = true;
|
||||
this.title = "添加模块";
|
||||
},
|
||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user