@@ -8,10 +8,11 @@ import {
88 HTTPParam ,
99 Inject ,
1010} from '@eggjs/tegg' ;
11- import { NotFoundError , UnprocessableEntityError } from 'egg-errors' ;
11+ import { NotFoundError , ForbiddenError , UnprocessableEntityError } from 'egg-errors' ;
1212import { AbstractController } from './AbstractController' ;
1313import { OrgService } from '../../core/service/OrgService' ;
1414import { TeamService } from '../../core/service/TeamService' ;
15+ import { OrgRepository } from '../../repository/OrgRepository' ;
1516import { TeamRepository } from '../../repository/TeamRepository' ;
1617import { getScopeAndName } from '../../common/PackageUtil' ;
1718
@@ -23,6 +24,9 @@ export class TeamController extends AbstractController {
2324 @Inject ( )
2425 private readonly teamService : TeamService ;
2526
27+ @Inject ( )
28+ private readonly orgRepository : OrgRepository ;
29+
2630 @Inject ( )
2731 private readonly teamRepository : TeamRepository ;
2832
@@ -58,12 +62,44 @@ export class TeamController extends AbstractController {
5862 }
5963
6064 private async requireTeamWriteAccess ( ctx : EggContext , orgName : string , teamName : string ) {
61- const { org, authorizedUser } = await this . requireOrgWriteAccess ( ctx , orgName ) ;
65+ const authorizedUser = await this . userRoleManager . requiredAuthorizedUser ( ctx , 'setting' ) ;
66+ const isAdmin = await this . userRoleManager . isAdmin ( ctx ) ;
67+
68+ let org ;
69+ if ( this . isAllowScopeOrg ( orgName ) ) {
70+ org = await this . orgService . ensureOrgForScope ( `@${ orgName } ` ) ;
71+ } else {
72+ org = await this . orgService . findOrgByName ( orgName ) ;
73+ if ( ! org ) {
74+ throw new NotFoundError ( `Org "${ orgName } " not found` ) ;
75+ }
76+ }
77+
6278 const team = await this . teamRepository . findTeam ( org . orgId , teamName ) ;
6379 if ( ! team ) {
6480 throw new NotFoundError ( `Team "${ teamName } " not found` ) ;
6581 }
66- return { org, team, authorizedUser } ;
82+
83+ // Admin always has access
84+ if ( isAdmin ) {
85+ return { org, team, authorizedUser } ;
86+ }
87+
88+ // Org owner has access
89+ if ( ! this . isAllowScopeOrg ( orgName ) ) {
90+ const orgMember = await this . orgRepository . findMember ( org . orgId , authorizedUser . userId ) ;
91+ if ( orgMember && orgMember . role === 'owner' ) {
92+ return { org, team, authorizedUser } ;
93+ }
94+ }
95+
96+ // Team owner has access
97+ const teamMember = await this . teamRepository . findMember ( team . teamId , authorizedUser . userId ) ;
98+ if ( teamMember && teamMember . role === 'owner' ) {
99+ return { org, team, authorizedUser } ;
100+ }
101+
102+ throw new ForbiddenError ( 'Only team owner or admin can perform this action' ) ;
67103 }
68104
69105 // --- Team CRUD ---
@@ -75,12 +111,12 @@ export class TeamController extends AbstractController {
75111 } )
76112 async createTeam ( @Context ( ) ctx : EggContext , @HTTPParam ( ) orgName : string ,
77113 @HTTPBody ( ) body : { name : string ; description ?: string } ) {
78- const { org } = await this . requireOrgWriteAccess ( ctx , orgName ) ;
114+ const { org, authorizedUser } = await this . requireOrgWriteAccess ( ctx , orgName ) ;
79115
80116 if ( ! body . name ) {
81117 throw new UnprocessableEntityError ( 'name is required' ) ;
82118 }
83- await this . teamService . createTeam ( org . orgId , body . name , body . description ) ;
119+ await this . teamService . createTeam ( org . orgId , body . name , body . description , authorizedUser . userId ) ;
84120 return { ok : true } ;
85121 }
86122
@@ -138,6 +174,7 @@ export class TeamController extends AbstractController {
138174 // --- Team Members (npm uses "user") ---
139175
140176 // npm team ls @scope :team → GET /-/team/:orgName/:teamName/user
177+ // npm compatible: returns string array ["user1", "user2"]
141178 @HTTPMethod ( {
142179 path : '/-/team/:orgName/:teamName/user' ,
143180 method : HTTPMethodEnum . GET ,
@@ -158,6 +195,57 @@ export class TeamController extends AbstractController {
158195 return users . map ( u => u . displayName ) ;
159196 }
160197
198+ // Private API: GET /-/team/:orgName/:teamName/member
199+ // Returns [{user, role}] with team member role info
200+ @HTTPMethod ( {
201+ path : '/-/team/:orgName/:teamName/member' ,
202+ method : HTTPMethodEnum . GET ,
203+ } )
204+ async listTeamMembersWithRole ( @Context ( ) ctx : EggContext , @HTTPParam ( ) orgName : string ,
205+ @HTTPParam ( ) teamName : string ) {
206+ await this . userRoleManager . requiredAuthorizedUser ( ctx , 'read' ) ;
207+ const org = await this . findOrg ( orgName ) ;
208+ if ( ! org ) {
209+ throw new NotFoundError ( `Org "${ orgName } " not found` ) ;
210+ }
211+ const team = await this . teamRepository . findTeam ( org . orgId , teamName ) ;
212+ if ( ! team ) {
213+ throw new NotFoundError ( `Team "${ teamName } " not found` ) ;
214+ }
215+ const members = await this . teamService . listMembers ( team . teamId ) ;
216+ const users = await this . userRepository . findUsersByUserIds ( members . map ( m => m . userId ) ) ;
217+ const userMap = new Map ( users . map ( u => [ u . userId , u ] ) ) ;
218+ return members . map ( m => ( {
219+ user : userMap . get ( m . userId ) ?. displayName ?? '' ,
220+ role : m . role ,
221+ } ) ) ;
222+ }
223+
224+ // Private API: PATCH /-/team/:orgName/:teamName/member/:username
225+ // Update team member role
226+ @HTTPMethod ( {
227+ path : '/-/team/:orgName/:teamName/member/:username' ,
228+ method : HTTPMethodEnum . PATCH ,
229+ } )
230+ async updateTeamMemberRole ( @Context ( ) ctx : EggContext , @HTTPParam ( ) orgName : string ,
231+ @HTTPParam ( ) teamName : string , @HTTPParam ( ) username : string ,
232+ @HTTPBody ( ) body : { role : string } ) {
233+ const { team } = await this . requireTeamWriteAccess ( ctx , orgName , teamName ) ;
234+ if ( ! body . role || ( body . role !== 'owner' && body . role !== 'member' ) ) {
235+ throw new UnprocessableEntityError ( 'role is required and must be "owner" or "member"' ) ;
236+ }
237+ const targetUser = await this . userRepository . findUserByName ( username ) ;
238+ if ( ! targetUser ) {
239+ throw new NotFoundError ( `User "${ username } " not found` ) ;
240+ }
241+ const member = await this . teamRepository . findMember ( team . teamId , targetUser . userId ) ;
242+ if ( ! member ) {
243+ throw new NotFoundError ( `User "${ username } " is not a member of this team` ) ;
244+ }
245+ await this . teamService . addMember ( team . teamId , targetUser . userId , body . role as 'owner' | 'member' ) ;
246+ return { ok : true } ;
247+ }
248+
161249 // npm team add <user> @scope:team → PUT /-/team/:orgName/:teamName/user
162250 @HTTPMethod ( {
163251 path : '/-/team/:orgName/:teamName/user' ,
@@ -262,4 +350,5 @@ export class TeamController extends AbstractController {
262350 await this . teamService . revokePackageAccess ( team . teamId , pkg . packageId ) ;
263351 return { ok : true } ;
264352 }
353+
265354}
0 commit comments