feat(权限): 开发者也支持项目级权限,创建项目自动获得权限
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m31s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m31s
- 开发者与观察者统一逻辑:未分配项目则无法查看数据 - 开发者创建项目时自动获得该项目的查看权限 - 管理员可在用户管理页面为开发者分配项目 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
10ed4f090d
commit
feb305c454
@ -3,7 +3,7 @@ import { zValidator } from '@hono/zod-validator';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { db } from '../db/index';
|
import { db } from '../db/index';
|
||||||
import { projects, sprintSnapshots, milestones, taskSnapshots, gitCommits, gitPRs, users, objectives, keyResults, projectRepos, krLogs } from '../db/schema';
|
import { projects, sprintSnapshots, milestones, taskSnapshots, gitCommits, gitPRs, users, objectives, keyResults, projectRepos, krLogs, userProjectPermissions } from '../db/schema';
|
||||||
import { eq, and, desc, gte, inArray } from 'drizzle-orm';
|
import { eq, and, desc, gte, inArray } from 'drizzle-orm';
|
||||||
import { requireRole } from '../middleware/role';
|
import { requireRole } from '../middleware/role';
|
||||||
import { AppError } from '../middleware/error-handler';
|
import { AppError } from '../middleware/error-handler';
|
||||||
@ -44,6 +44,7 @@ projectRoutes.post('/projects',
|
|||||||
requireRole('admin', 'manager', 'developer'),
|
requireRole('admin', 'manager', 'developer'),
|
||||||
zValidator('json', createProjectSchema),
|
zValidator('json', createProjectSchema),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
|
const user = c.get('user');
|
||||||
const data = c.req.valid('json');
|
const data = c.req.valid('json');
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@ -55,6 +56,17 @@ projectRoutes.post('/projects',
|
|||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 开发者创建项目时自动获得该项目的查看权限
|
||||||
|
if (user.role === 'developer') {
|
||||||
|
await db.insert(userProjectPermissions).values({
|
||||||
|
id: uuid(),
|
||||||
|
userId: user.sub,
|
||||||
|
projectId: id,
|
||||||
|
createdAt: now,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return c.json({ code: 0, data: { id }, message: 'success' }, 201);
|
return c.json({ code: 0, data: { id }, message: 'success' }, 201);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,13 +4,13 @@ import { userProjectPermissions } from '../db/schema';
|
|||||||
import type { JWTPayload } from '../middleware/auth';
|
import type { JWTPayload } from '../middleware/auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取观察者(viewer)可查看的项目 ID 列表。
|
* 获取用户可查看的项目 ID 列表。
|
||||||
* - viewer 角色:返回 user_project_permissions 中分配的项目 ID 列表
|
* - admin / manager:返回 null,表示可查看所有项目
|
||||||
* - 其他角色:返回 null,表示可查看所有项目
|
* - viewer / developer:返回已分配的项目 ID 列表(未分配则为空数组,即无权查看)
|
||||||
*/
|
*/
|
||||||
export async function getAllowedProjectIds(user: JWTPayload): Promise<string[] | null> {
|
export async function getAllowedProjectIds(user: JWTPayload): Promise<string[] | null> {
|
||||||
if (user.role !== 'viewer') {
|
if (user.role !== 'viewer' && user.role !== 'developer') {
|
||||||
return null; // 非 viewer,不做项目级过滤
|
return null; // admin / manager 不做项目级过滤
|
||||||
}
|
}
|
||||||
|
|
||||||
const perms = await db.select({ projectId: userProjectPermissions.projectId })
|
const perms = await db.select({ projectId: userProjectPermissions.projectId })
|
||||||
|
|||||||
@ -68,7 +68,7 @@ const userColumns = [
|
|||||||
{
|
{
|
||||||
title: '可查看项目', key: 'allowedProjects', width: 120,
|
title: '可查看项目', key: 'allowedProjects', width: 120,
|
||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
if (row.role !== 'viewer') return '-';
|
if (row.role !== 'viewer' && row.role !== 'developer') return '-';
|
||||||
const count = (row.allowedProjectIds || []).length;
|
const count = (row.allowedProjectIds || []).length;
|
||||||
return count > 0 ? `${count} 个项目` : '未分配';
|
return count > 0 ? `${count} 个项目` : '未分配';
|
||||||
},
|
},
|
||||||
@ -81,7 +81,7 @@ const userColumns = [
|
|||||||
title: '操作', key: 'actions', width: 180,
|
title: '操作', key: 'actions', width: 180,
|
||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
if (row.role === 'viewer') {
|
if (row.role === 'viewer' || row.role === 'developer') {
|
||||||
buttons.push(h(
|
buttons.push(h(
|
||||||
NButton,
|
NButton,
|
||||||
{ size: 'tiny', type: 'info', onClick: () => openProjectPermModal(row), style: 'margin-right: 8px' },
|
{ size: 'tiny', type: 'info', onClick: () => openProjectPermModal(row), style: 'margin-right: 8px' },
|
||||||
@ -340,7 +340,7 @@ const roleOptions = [
|
|||||||
<!-- 分配项目权限弹窗 -->
|
<!-- 分配项目权限弹窗 -->
|
||||||
<NModal v-model:show="showProjectPermModal" :title="`分配项目权限 - ${permEditUserName}`" preset="dialog" positive-text="保存" @positive-click="handleSaveProjectPerms">
|
<NModal v-model:show="showProjectPermModal" :title="`分配项目权限 - ${permEditUserName}`" preset="dialog" positive-text="保存" @positive-click="handleSaveProjectPerms">
|
||||||
<div style="margin-bottom: 12px; color: var(--color-text-tertiary); font-size: 13px;">
|
<div style="margin-bottom: 12px; color: var(--color-text-tertiary); font-size: 13px;">
|
||||||
选择该观察者可查看的项目。未分配项目的观察者将无法查看任何数据。
|
选择该用户可查看的项目。未分配项目的用户将无法查看任何数据。开发者自己创建的项目会自动获得权限。
|
||||||
</div>
|
</div>
|
||||||
<NSelect
|
<NSelect
|
||||||
v-model:value="permSelectedProjects"
|
v-model:value="permSelectedProjects"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user