feat(权限): 开发者也支持项目级权限,创建项目自动获得权限
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:
zyc 2026-04-14 11:33:01 +08:00
parent 10ed4f090d
commit feb305c454
3 changed files with 21 additions and 9 deletions

View File

@ -3,7 +3,7 @@ import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { v4 as uuid } from 'uuid';
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 { requireRole } from '../middleware/role';
import { AppError } from '../middleware/error-handler';
@ -44,6 +44,7 @@ projectRoutes.post('/projects',
requireRole('admin', 'manager', 'developer'),
zValidator('json', createProjectSchema),
async (c) => {
const user = c.get('user');
const data = c.req.valid('json');
const id = uuid();
const now = new Date();
@ -55,6 +56,17 @@ projectRoutes.post('/projects',
createdAt: 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);
}
);

View File

@ -4,13 +4,13 @@ import { userProjectPermissions } from '../db/schema';
import type { JWTPayload } from '../middleware/auth';
/**
* viewer ID
* - viewer user_project_permissions ID
* - null
* ID
* - admin / manager null
* - viewer / developer ID
*/
export async function getAllowedProjectIds(user: JWTPayload): Promise<string[] | null> {
if (user.role !== 'viewer') {
return null; // 非 viewer不做项目级过滤
if (user.role !== 'viewer' && user.role !== 'developer') {
return null; // admin / manager 不做项目级过滤
}
const perms = await db.select({ projectId: userProjectPermissions.projectId })

View File

@ -68,7 +68,7 @@ const userColumns = [
{
title: '可查看项目', key: 'allowedProjects', width: 120,
render: (row: any) => {
if (row.role !== 'viewer') return '-';
if (row.role !== 'viewer' && row.role !== 'developer') return '-';
const count = (row.allowedProjectIds || []).length;
return count > 0 ? `${count} 个项目` : '未分配';
},
@ -81,7 +81,7 @@ const userColumns = [
title: '操作', key: 'actions', width: 180,
render: (row: any) => {
const buttons = [];
if (row.role === 'viewer') {
if (row.role === 'viewer' || row.role === 'developer') {
buttons.push(h(
NButton,
{ 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">
<div style="margin-bottom: 12px; color: var(--color-text-tertiary); font-size: 13px;">
选择该观察者可查看的项目未分配项目的观察者将无法查看任何数据
选择该用户可查看的项目未分配项目的用户将无法查看任何数据开发者自己创建的项目会自动获得权限
</div>
<NSelect
v-model:value="permSelectedProjects"