Skip to content

Commit 126660e

Browse files
authored
Merge pull request #339 from keboola/upstream/department-ownership
Fix repository ownership model for organization imports
2 parents 3948d72 + d152c6a commit 126660e

File tree

7 files changed

+369
-28
lines changed

7 files changed

+369
-28
lines changed

src/OpenDeepWiki.EFCore/MasterDbContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
108108
.HasForeignKey(department => department.ParentId);
109109

110110
modelBuilder.Entity<Repository>()
111-
.HasIndex(repository => new { repository.OwnerUserId, repository.OrgName, repository.RepoName })
111+
.HasIndex(repository => new { repository.OrgName, repository.RepoName })
112112
.IsUnique();
113113

114114
modelBuilder.Entity<RepositoryBranch>()

src/OpenDeepWiki.Entities/Repositories/Repository.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ public class Repository : AggregateRoot<string>
111111
/// </summary>
112112
public DateTime? LastUpdateCheckAt { get; set; }
113113

114+
/// <summary>
115+
/// Whether this repository is owned by a department (org import) rather than an individual user.
116+
/// When true, the repo appears in Organization view only, not in the importing user's "My Repos".
117+
/// </summary>
118+
public bool IsDepartmentOwned { get; set; } = false;
119+
114120
/// <summary>
115121
/// 所属用户导航属性
116122
/// </summary>

src/OpenDeepWiki/Services/Admin/AdminGitHubImportService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ public async Task<BatchImportResult> BatchImportAsync(
250250
Status = RepositoryStatus.Pending,
251251
PrimaryLanguage = repo.Language,
252252
StarCount = repo.StargazersCount,
253-
ForkCount = repo.ForksCount
253+
ForkCount = repo.ForksCount,
254+
IsDepartmentOwned = true
254255
};
255256

256257
_context.Repositories.Add(repoEntity);

src/OpenDeepWiki/Services/GitHub/UserGitHubImportService.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ public async Task<BatchImportResult> ImportAsync(
154154
}
155155

156156
// Create Repository entity
157+
// When importing into a department, mark as department-owned so it appears
158+
// in Organization view only, not in the importing user's "My Repos".
159+
var isDeptImport = !string.IsNullOrEmpty(request.DepartmentId);
157160
var repoEntity = new Repository
158161
{
159162
Id = Guid.NewGuid().ToString(),
@@ -165,7 +168,8 @@ public async Task<BatchImportResult> ImportAsync(
165168
Status = RepositoryStatus.Pending,
166169
PrimaryLanguage = repo.Language,
167170
StarCount = repo.StargazersCount,
168-
ForkCount = repo.ForksCount
171+
ForkCount = repo.ForksCount,
172+
IsDepartmentOwned = isDeptImport
169173
};
170174

171175
_context.Repositories.Add(repoEntity);

src/OpenDeepWiki/Services/Repositories/RepositoryService.cs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
using System.Security.Claims;
12
using Microsoft.AspNetCore.Mvc;
23
using Microsoft.EntityFrameworkCore;
34
using OpenDeepWiki.EFCore;
45
using OpenDeepWiki.Entities;
56
using OpenDeepWiki.Models;
67
using OpenDeepWiki.Services.Auth;
8+
using OpenDeepWiki.Services.GitHub;
9+
using OpenDeepWiki.Services.Organizations;
710

811
namespace OpenDeepWiki.Services.Repositories;
912

1013
[MiniApi(Route = "/api/v1/repositories")]
1114
[Tags("仓库")]
12-
public class RepositoryService(IContext context, IGitPlatformService gitPlatformService, IUserContext userContext)
15+
public class RepositoryService(IContext context, IGitPlatformService gitPlatformService, IUserContext userContext, IGitHubAppService gitHubAppService, IOrganizationService organizationService)
1316
{
1417
[HttpPost("/submit")]
1518
public async Task<Repository> SubmitAsync([FromBody] RepositorySubmitRequest request)
@@ -147,7 +150,7 @@ public async Task<RepositoryListResponse> GetListAsync(
147150

148151
if (!string.IsNullOrWhiteSpace(ownerId))
149152
{
150-
query = query.Where(r => r.OwnerUserId == ownerId);
153+
query = query.Where(r => r.OwnerUserId == ownerId && !r.IsDepartmentOwned);
151154
}
152155

153156
if (status.HasValue)
@@ -263,13 +266,31 @@ public async Task<IResult> UpdateVisibilityAsync([FromBody] UpdateVisibilityRequ
263266
// 验证所有权
264267
if (repository.OwnerUserId != currentUserId)
265268
{
266-
return Results.Json(new UpdateVisibilityResponse
269+
// Allow admin to manage department-owned repos in their departments
270+
var allowed = false;
271+
if (repository.IsDepartmentOwned)
267272
{
268-
Id = request.RepositoryId,
269-
IsPublic = repository.IsPublic,
270-
Success = false,
271-
ErrorMessage = "无权限修改此仓库"
272-
}, statusCode: StatusCodes.Status403Forbidden);
273+
var isAdmin = userContext.User?.IsInRole("Admin") == true;
274+
if (isAdmin)
275+
{
276+
var deptRepos = await organizationService.GetDepartmentRepositoriesAsync(currentUserId, includeRestricted: true);
277+
if (deptRepos.Any(r => r.RepositoryId == repository.Id))
278+
{
279+
allowed = true;
280+
}
281+
}
282+
}
283+
284+
if (!allowed)
285+
{
286+
return Results.Json(new UpdateVisibilityResponse
287+
{
288+
Id = request.RepositoryId,
289+
IsPublic = repository.IsPublic,
290+
Success = false,
291+
ErrorMessage = "无权限修改此仓库"
292+
}, statusCode: StatusCodes.Status403Forbidden);
293+
}
273294
}
274295

275296
// 无密码仓库不能设为私有
@@ -356,11 +377,29 @@ public async Task<RegenerateResponse> RegenerateAsync([FromBody] RegenerateReque
356377
// 验证所有权
357378
if (repository.OwnerUserId != currentUserId)
358379
{
359-
return new RegenerateResponse
380+
// Allow admin to manage department-owned repos in their departments
381+
var allowed = false;
382+
if (repository.IsDepartmentOwned)
360383
{
361-
Success = false,
362-
ErrorMessage = "无权限操作此仓库"
363-
};
384+
var isAdmin = userContext.User?.IsInRole("Admin") == true;
385+
if (isAdmin)
386+
{
387+
var deptRepos = await organizationService.GetDepartmentRepositoriesAsync(currentUserId, includeRestricted: true);
388+
if (deptRepos.Any(r => r.RepositoryId == repository.Id))
389+
{
390+
allowed = true;
391+
}
392+
}
393+
}
394+
395+
if (!allowed)
396+
{
397+
return new RegenerateResponse
398+
{
399+
Success = false,
400+
ErrorMessage = "无权限操作此仓库"
401+
};
402+
}
364403
}
365404

366405
// 只有失败或完成状态才能重新生成

web/app/(main)/private/github-import/page.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,17 @@ export default function UserGitHubImportPage() {
135135
<GitHubRepoBrowser
136136
installation={selectedInstallation}
137137
fetchRepos={getUserInstallationRepos}
138-
departments={departments.map(d => ({ id: d.id, name: d.name }))}
138+
departments={
139+
// When installation is linked to a department, only offer that department
140+
// (org imports always go to the org, no "Personal only" option)
141+
selectedInstallation.departmentId
142+
? departments
143+
.filter(d => d.id === selectedInstallation.departmentId)
144+
.map(d => ({ id: d.id, name: d.name }))
145+
: departments.map(d => ({ id: d.id, name: d.name }))
146+
}
139147
onImport={handleImport}
140-
showPersonalOption={true}
148+
showPersonalOption={!selectedInstallation.departmentId}
141149
/>
142150
)}
143151

0 commit comments

Comments
 (0)