Skip to content

Commit cb5b2d4

Browse files
ddmukhsemantic-release-botFelixTJDietrichDmytro Dmukhiam-flo
authored
feat(application-server): implement workspace and organization domain (#401)
Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net> Co-authored-by: Felix T.J. Dietrich <felix_dietrich@gmx.de> Co-authored-by: Felix T.J. Dietrich <felixtj.dietrich@tum.de> Co-authored-by: Dmytro Dmukh <dmytro.dmukh@tum.de> Co-authored-by: Florian Ehrenstorfer <57101116+iam-flo@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 2ef6fbf commit cb5b2d4

43 files changed

Lines changed: 2430 additions & 383 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/contributor/erd/schema.mmd

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,25 @@ erDiagram
145145
BIGINT repository_id FK
146146
}
147147

148+
Organization {
149+
BIGINT id PK
150+
TIMESTAMPTZ created_at
151+
TIMESTAMPTZ updated_at
152+
VARCHAR(255) avatar_url
153+
BIGINT github_id UK "NOT NULL"
154+
VARCHAR(255) html_url
155+
BIGINT installation_id
156+
VARCHAR(255) login UK "NOT NULL"
157+
VARCHAR(255) name
158+
}
159+
160+
OrganizationMembership {
161+
BIGINT organization_id PK
162+
BIGINT user_id PK
163+
TIMESTAMPTZ joined_at
164+
VARCHAR(255) role
165+
}
166+
148167
PullRequestRequestedReviewer {
149168
BIGINT pull_request_id PK,FK
150169
BIGINT user_id PK,FK
@@ -220,6 +239,7 @@ erDiagram
220239
INTEGER stargazers_count "NOT NULL"
221240
VARCHAR(255) visibility
222241
INTEGER watchers_count "NOT NULL"
242+
BIGINT organization_id FK
223243
}
224244

225245
RepositoryToMonitor {
@@ -287,12 +307,20 @@ erDiagram
287307
Workspace {
288308
BIGINT id PK
289309
TIMESTAMP users_synced_at
310+
VARCHAR(255) account_login
311+
VARCHAR(255) git_provider_mode
312+
VARCHAR(255) github_repository_selection
313+
BIGINT installation_id
314+
TIMESTAMPTZ installation_linked_at
315+
BIGINT organization_id FK,UK
316+
TEXT personal_access_token
290317
}
291318

292319
%% Relationships
293320
%% One-to-One relationships
294321
ChatMessage ||--|| ChatMessagePart : has
295322
ChatMessage ||--|| ChatThread : references
323+
Organization ||--|| Workspace : has
296324

297325
%% One-to-Many relationships
298326
Issue ||--o{ BadPracticeDetection : references
@@ -317,6 +345,7 @@ erDiagram
317345
PullRequestReview ||--o{ PullRequestReviewComment : commented_on
318346
BadPracticeDetection ||--o{ PullRequestBadPractice : has
319347
Issue ||--o{ PullRequestBadPractice : references
348+
Organization ||--o{ Repository : has
320349
Workspace ||--o{ RepositoryToMonitor : monitors
321350

322351
%% Many-to-Many relationships

server/application-server/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@
7070
<groupId>org.springframework.boot</groupId>
7171
<artifactId>spring-boot-starter-web</artifactId>
7272
</dependency>
73+
<dependency>
74+
<groupId>com.auth0</groupId>
75+
<artifactId>java-jwt</artifactId>
76+
<version>4.4.0</version>
77+
</dependency>
7378
<dependency>
7479
<groupId>org.springframework.boot</groupId>
7580
<artifactId>spring-boot-starter-webflux</artifactId>

server/application-server/src/main/java/de/tum/in/www1/hephaestus/config/GitHubConfig.java

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package de.tum.in.www1.hephaestus.gitprovider.common.github;
2+
3+
import com.github.benmanes.caffeine.cache.Cache;
4+
import com.github.benmanes.caffeine.cache.Caffeine;
5+
import de.tum.in.www1.hephaestus.gitprovider.common.github.app.GitHubAppTokenService;
6+
import de.tum.in.www1.hephaestus.workspace.Workspace;
7+
import de.tum.in.www1.hephaestus.workspace.Workspace.GitProviderMode;
8+
import de.tum.in.www1.hephaestus.workspace.WorkspaceRepository;
9+
import jakarta.annotation.PreDestroy;
10+
import java.io.IOException;
11+
import java.io.UncheckedIOException;
12+
import java.time.Duration;
13+
import org.kohsuke.github.GitHub;
14+
import org.kohsuke.github.GitHubBuilder;
15+
import org.springframework.stereotype.Component;
16+
17+
@Component
18+
public class GitHubClientProvider {
19+
20+
private final WorkspaceRepository workspaceRepository;
21+
private final GitHubAppTokenService appTokens;
22+
23+
private final Cache<Long, GitHub> workspaceClients = Caffeine.newBuilder()
24+
.expireAfterWrite(Duration.ofMinutes(50))
25+
.maximumSize(10_000)
26+
.build();
27+
28+
public GitHubClientProvider(WorkspaceRepository workspaceRepository, GitHubAppTokenService appTokens) {
29+
this.workspaceRepository = workspaceRepository;
30+
this.appTokens = appTokens;
31+
}
32+
33+
/**
34+
* Return a cached Hub4J {@link GitHub} client for the given workspace id.
35+
* <p>
36+
* The provider transparently handles both GitHub App installations and PAT-backed workspaces,
37+
* creating short-lived clients on-demand and reusing them for up to 50 minutes to avoid hitting
38+
* rate limits or issuing unnecessary installation tokens.
39+
*/
40+
public GitHub forWorkspace(Long workspaceId) throws IOException {
41+
try {
42+
return workspaceClients.get(workspaceId, this::createClientForWorkspace);
43+
} catch (UncheckedIOException e) {
44+
throw e.getCause();
45+
}
46+
}
47+
48+
/**
49+
* Evict all cached clients associated with the supplied installation id.
50+
* <p>
51+
* This is primarily invoked when we update installation metadata (e.g. after rotating tokens),
52+
* ensuring that subsequent calls obtain a fresh client.
53+
*/
54+
public void invalidateInstallation(Long installationId) {
55+
if (installationId == null) {
56+
return;
57+
}
58+
workspaceRepository
59+
.findByInstallationId(installationId)
60+
.map(Workspace::getId)
61+
.ifPresent(workspaceClients::invalidate);
62+
}
63+
64+
/**
65+
* Evict the cached client for the given workspace, forcing the next lookup to build a new instance.
66+
*/
67+
public void invalidateWorkspace(Long workspaceId) {
68+
workspaceClients.invalidate(workspaceId);
69+
}
70+
71+
@PreDestroy
72+
public void shutdown() {
73+
workspaceClients.invalidateAll();
74+
}
75+
76+
private GitHub createClientForWorkspace(Long workspaceId) {
77+
try {
78+
Workspace workspace = workspaceRepository
79+
.findById(workspaceId)
80+
.orElseThrow(() -> new IllegalArgumentException("Workspace not found: " + workspaceId));
81+
82+
if (workspace.getGitProviderMode() == GitProviderMode.GITHUB_APP_INSTALLATION) {
83+
Long installationId = workspace.getInstallationId();
84+
if (installationId == null) {
85+
throw new IllegalStateException("Workspace " + workspaceId + " has no installation id.");
86+
}
87+
return appTokens.clientForInstallation(installationId);
88+
}
89+
90+
String token = workspace.getPersonalAccessToken();
91+
if (token == null || token.isBlank()) {
92+
throw new IllegalStateException(
93+
"Workspace " + workspaceId + " is configured for PAT access but no token is stored."
94+
);
95+
}
96+
97+
return new GitHubBuilder().withOAuthToken(token).build();
98+
} catch (IOException e) {
99+
throw new UncheckedIOException(e);
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)