Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add first GitHub entities and automatically fetch them for the leaderboard #62

Merged
merged 57 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
8df40a6
(feat): GraphQL + dot Env
GODrums Aug 18, 2024
5ff5a8b
Create GraphQL connection relations
GODrums Aug 18, 2024
8e625b3
fix: add referenced column names
GODrums Aug 18, 2024
bdcca5b
Merge branch 'develop' into feature/github-graphql-artemis
FelixTJDietrich Aug 20, 2024
28c226c
Replace .env with application-local.yml
GODrums Aug 20, 2024
45a6140
fix: transient value error
GODrums Aug 20, 2024
45f228d
Rework for Github API
GODrums Aug 21, 2024
d20d7a5
Merge branch 'develop' into feature/github-graphql-artemis
FelixTJDietrich Aug 23, 2024
37d1d2b
Rework repo fetch
GODrums Aug 24, 2024
78db6d2
Add endpoints for entities
GODrums Aug 25, 2024
fbb09e8
Add entity constructors
GODrums Aug 26, 2024
bae8980
table names to singular
GODrums Aug 28, 2024
d5711f2
replace list with set
GODrums Aug 28, 2024
de9adc4
Add cascade types for actor relations
GODrums Aug 28, 2024
4a372f1
Directly init relation sets
GODrums Aug 28, 2024
85f9e4c
Improve toString printing
GODrums Aug 28, 2024
8a8381b
Replace toString with annotations
GODrums Aug 28, 2024
21d3f7f
Prefer nonnull over nullable column
GODrums Aug 28, 2024
7474b78
Rename Actor, Pullrequest and Comment
GODrums Aug 28, 2024
05ed344
Add user name attribute
GODrums Aug 28, 2024
5b03982
Introduce DTO for user
GODrums Aug 29, 2024
567df15
Replace logger classes
GODrums Aug 29, 2024
1faa0dc
Create GHIssueState enum
GODrums Aug 29, 2024
930a3ef
Deleted unused classes
GODrums Aug 29, 2024
f629070
Remove more unused code
GODrums Aug 29, 2024
6e6b166
Remove github_id from entities
GODrums Aug 29, 2024
7d8df65
Improve entity comments
GODrums Aug 29, 2024
9121e61
Add more DTOs and Superclass
GODrums Aug 30, 2024
322702e
Merge branch 'develop' into feature/github-graphql-artemis
FelixTJDietrich Aug 30, 2024
06528e8
fix indentiation of application.yml
FelixTJDietrich Aug 30, 2024
b58eaea
Merge branch 'develop' into feature/github-graphql-artemis
FelixTJDietrich Aug 30, 2024
bbb2ef7
Implement feedback from review
GODrums Aug 30, 2024
f6b9e7f
Merge branch 'feature/github-graphql-artemis' of https://github.com/l…
GODrums Aug 30, 2024
b93b665
Merge branch 'develop' into feature/github-graphql-artemis
FelixTJDietrich Aug 30, 2024
a9a2410
Improve Entity generation
GODrums Aug 30, 2024
698da27
Merge branch 'feature/github-graphql-artemis' of https://github.com/l…
GODrums Aug 30, 2024
eab0543
Refactor converts with BaseGitServiceEntityConverter
GODrums Aug 30, 2024
cf50f37
Add id to DTOs
GODrums Aug 30, 2024
f9af54a
Add more Repository attributes
GODrums Aug 31, 2024
79a1194
Refactor github sync with cron
GODrums Aug 31, 2024
3588ed7
rename files and pullrequest
FelixTJDietrich Aug 31, 2024
17e6528
Rename scheduler
GODrums Aug 31, 2024
22ead12
Change repositories to fetch
GODrums Aug 31, 2024
e23c516
Redo pullrequest naming
GODrums Sep 1, 2024
3571d40
Make converter services
GODrums Sep 1, 2024
87a2620
Delete EntityNotFoundException
GODrums Sep 1, 2024
5b99ac1
Change to @Component
GODrums Sep 1, 2024
827cb8a
Delete Pullrequest.java
GODrums Sep 2, 2024
8457374
Delete PullrequestRepository.java
GODrums Sep 2, 2024
55e0486
Recommit correct casing files
GODrums Sep 2, 2024
e12ed62
Add default value for ghAuthToken
GODrums Sep 2, 2024
ce402ec
Create a non-environement ghAuthToken
GODrums Sep 2, 2024
21704cf
New OpenAPI specs
GODrums Sep 2, 2024
1530549
Execute generate:api:application-server-client
GODrums Sep 2, 2024
40dfc87
Execute generate:api
GODrums Sep 2, 2024
9b6ca82
Regenerate API from Mac
GODrums Sep 2, 2024
2c822f4
Another openapi regenerate
GODrums Sep 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,6 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

node_modules/
node_modules/

application-local.yml
58 changes: 36 additions & 22 deletions server/application-server/HELP.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
# Getting Started

### Local development

The Spring Boot Maven Plugin supports running your application with a local development environment. To do this, you can use the `spring-boot:run` goal:

```shell
mvn spring-boot:run
```

This will start your application in a local development environment. You can access the application at `http://localhost:8080`.

Setting environment variables works through profile-based `application.yml` files. For local development, create a `application-local.yml` file overwriting the original properties. See [Using the Plugin :: Spring Boot](https://docs.spring.io/spring-boot/maven-plugin/using.html#using.overriding-command-line) for more information.

### Reference Documentation

For further reference, please consider the following sections:

* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.3.2/maven-plugin/reference/html/)
* [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.3.2/maven-plugin/reference/html/#build-image)
* [Docker Compose Support](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#features.docker-compose)
* [Spring Boot DevTools](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#using.devtools)
* [Spring Web](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web)
* [Spring Security](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web.security)
* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#data.sql.jpa-and-spring-data)
* [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#actuator)
* [Liquibase Migration](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#howto.data-initialization.migration-tool.liquibase)
* [OAuth2 Client](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web.security.oauth2.client)
* [OAuth2 Resource Server](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web.security.oauth2.server)
- [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
- [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.3.2/maven-plugin/reference/html/)
- [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.3.2/maven-plugin/reference/html/#build-image)
- [Docker Compose Support](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#features.docker-compose)
- [Spring Boot DevTools](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#using.devtools)
- [Spring Web](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web)
- [Spring Security](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web.security)
- [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#data.sql.jpa-and-spring-data)
- [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#actuator)
- [Liquibase Migration](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#howto.data-initialization.migration-tool.liquibase)
- [OAuth2 Client](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web.security.oauth2.client)
- [OAuth2 Resource Server](https://docs.spring.io/spring-boot/docs/3.3.2/reference/htmlsingle/index.html#web.security.oauth2.server)

### Guides

The following guides illustrate how to use some features concretely:

* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/)
* [Securing a Web Application](https://spring.io/guides/gs/securing-web/)
* [Spring Boot and OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2/)
* [Authenticating a User with LDAP](https://spring.io/guides/gs/authenticating-ldap/)
* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)
* [Building a RESTful Web Service with Spring Boot Actuator](https://spring.io/guides/gs/actuator-service/)
- [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
- [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
- [Building REST services with Spring](https://spring.io/guides/tutorials/rest/)
- [Securing a Web Application](https://spring.io/guides/gs/securing-web/)
- [Spring Boot and OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2/)
- [Authenticating a User with LDAP](https://spring.io/guides/gs/authenticating-ldap/)
- [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)
- [Building a RESTful Web Service with Spring Boot Actuator](https://spring.io/guides/gs/actuator-service/)

### Docker Compose support

This project contains a Docker Compose file named `compose.yaml`.
In this file, the following services have been defined:

* postgres: [`postgres:latest`](https://hub.docker.com/_/postgres)
- postgres: [`postgres:latest`](https://hub.docker.com/_/postgres)

Please review the tags of the used images and set them to the same as you're running in production.

Expand All @@ -42,4 +57,3 @@ Due to Maven's design, elements are inherited from the parent POM to the project
While most of the inheritance is fine, it also inherits unwanted elements like `<license>` and `<developers>` from the parent.
To prevent this, the project POM contains empty overrides for these elements.
If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides.

8 changes: 8 additions & 0 deletions server/application-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
</scm>
<properties>
<java.version>21</java.version>
<app.profiles>local,dev</app.profiles>
GODrums marked this conversation as resolved.
Show resolved Hide resolved
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -120,6 +121,12 @@
<artifactId>spring-modulith-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>1.324</version>
</dependency>

</dependencies>

<dependencyManagement>
Expand Down Expand Up @@ -158,6 +165,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<profiles>${app.profiles}</profiles>
<jvmArguments>-Dspring.application.admin.enabled=true</jvmArguments>
<excludes>
<exclude>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
package de.tum.in.www1.hephaestus;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.modulith.Modulithic;

import de.tum.in.www1.hephaestus.codereview.CodeReviewService;
import de.tum.in.www1.hephaestus.codereview.repository.Repository;

@SpringBootApplication
@Modulithic(systemName = "Hephaestus")
@EnableConfigurationProperties(EnvConfig.class)
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Bean
CommandLineRunner commandLineRunner(CodeReviewService service) {
return args -> {
Repository repo = service.fetchRepository("ls1intum/hephaestus");
GODrums marked this conversation as resolved.
Show resolved Hide resolved
System.out.println("Got repo: " + repo);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.tum.in.www1.hephaestus;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "environment")
public class EnvConfig {
GODrums marked this conversation as resolved.
Show resolved Hide resolved

private String githubPat;

public EnvConfig(String githubPat) {
this.githubPat = githubPat;
}

public String getGithubPat() {
return githubPat;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package de.tum.in.www1.hephaestus;

import java.util.Arrays;
import java.util.List;

import org.springframework.context.annotation.Bean;
Expand All @@ -20,14 +19,13 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/admin/**").authenticated()
.anyRequest().permitAll()
)
.formLogin(Customizer.withDefaults())
.logout(Customizer.withDefaults());
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/admin/**").authenticated()
.anyRequest().permitAll())
.formLogin(Customizer.withDefaults())
.logout(Customizer.withDefaults());

return http.build();
}
Expand All @@ -38,7 +36,7 @@ CorsConfigurationSource corsConfigurationSource() {
configuration.applyPermitDefaultValues();
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",configuration);
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.tum.in.www1.hephaestus.codereview;

import java.io.IOException;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import de.tum.in.www1.hephaestus.codereview.repository.Repository;

@RestController
@RequestMapping("/codereview")
GODrums marked this conversation as resolved.
Show resolved Hide resolved
public class CodeReviewController {
private final CodeReviewService codeReviewService;

public CodeReviewController(CodeReviewService codeReviewService) {
this.codeReviewService = codeReviewService;
}

@PutMapping("/repository/{nameWithOwner}")
public Repository addRepository(@PathVariable String nameWithOwner) {
try {
return codeReviewService.fetchRepository(nameWithOwner);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package de.tum.in.www1.hephaestus.codereview;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.GitHubBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import de.tum.in.www1.hephaestus.EnvConfig;
import de.tum.in.www1.hephaestus.codereview.comment.IssueComment;
import de.tum.in.www1.hephaestus.codereview.comment.IssueCommentConverter;
import de.tum.in.www1.hephaestus.codereview.comment.IssueCommentRepository;
import de.tum.in.www1.hephaestus.codereview.pullrequest.PullRequest;
import de.tum.in.www1.hephaestus.codereview.pullrequest.PullRequestConverter;
import de.tum.in.www1.hephaestus.codereview.pullrequest.PullRequestRepository;
import de.tum.in.www1.hephaestus.codereview.repository.Repository;
import de.tum.in.www1.hephaestus.codereview.repository.RepositoryConverter;
import de.tum.in.www1.hephaestus.codereview.repository.RepositoryRepository;
import de.tum.in.www1.hephaestus.codereview.user.User;
import de.tum.in.www1.hephaestus.codereview.user.UserConverter;
import de.tum.in.www1.hephaestus.codereview.user.UserRepository;

@Service
public class CodeReviewService {
GODrums marked this conversation as resolved.
Show resolved Hide resolved

private static final Logger logger = LoggerFactory.getLogger(CodeReviewService.class);

private GitHub github;

private final RepositoryRepository repositoryRepository;
private final PullRequestRepository pullrequestRepository;
private final IssueCommentRepository commentRepository;
private final UserRepository userRepository;

public CodeReviewService(EnvConfig envConfig, RepositoryRepository repositoryRepository,
PullRequestRepository pullrequestRepository, IssueCommentRepository commentRepository,
UserRepository actorRepository) {
logger.info("Hello from CodeReviewService!");

this.repositoryRepository = repositoryRepository;
this.pullrequestRepository = pullrequestRepository;
this.commentRepository = commentRepository;
this.userRepository = actorRepository;

try {
github = new GitHubBuilder().withOAuthToken(envConfig.getGithubPat()).build();
} catch (IOException e) {
logger.error("Error while creating GitHub client: " + e.getMessage());
return;
}
}

/**
* Rest API implementation of fetching a Github repository.
*
* @return The repository corresponding to the given nameWithOwner.
* @throws IOException
*/
public Repository fetchRepository(String nameWithOwner) throws IOException {
if (github == null) {
logger.error("GitHub client not initialized correctly!");
return null;
}

// Avoid double fetching of the same repository
Repository existingRepository = repositoryRepository.findByNameWithOwner(nameWithOwner);
if (existingRepository != null) {
logger.info("Found existing repository: " + existingRepository);
return existingRepository;
}

GHRepository ghRepo = github.getRepository(nameWithOwner);
Repository repository = new RepositoryConverter().convert(ghRepo);
if (repository == null) {
logger.error("Error while fetching repository!");
return null;
}
// preliminary save to make it referenceable
repositoryRepository.save(repository);

PullRequestConverter prConverter = new PullRequestConverter();

// Retrieve PRs in pages of 10
Set<PullRequest> prs = ghRepo.queryPullRequests().list().withPageSize(10).toList().stream().map(pr -> {
PullRequest pullrequest = prConverter.convert(pr);
pullrequest.setRepository(repository);
pullrequestRepository.save(pullrequest);
try {
Set<IssueComment> comments = getCommentsFromGHPullRequest(pr, pullrequest);
pullrequest.setComments(comments);
commentRepository.saveAll(comments);
} catch (IOException e) {
logger.error("Error while fetching PR comments!");
pullrequest.setComments(new HashSet<>());
}
try {
pullrequest.setAuthor(getActorFromGHUser(pr.getUser()));
} catch (IOException e) {
logger.error("Error while fetching PR author!");
pullrequest.setAuthor(null);
}

return pullrequest;
}).collect(Collectors.toSet());
repository.setPullRequests(prs);
pullrequestRepository.saveAll(prs);
repositoryRepository.save(repository);
return repository;
}

/**
* Retrieves the comments of a given pull request.
*
* @param pr The GH pull request.
* @param pullrequest Stored PR to which the comments belong.
* @return The comments of the given pull request.
* @throws IOException
*/
private Set<IssueComment> getCommentsFromGHPullRequest(GHPullRequest pr, PullRequest pullrequest)
throws IOException {
IssueCommentConverter commentConverter = new IssueCommentConverter();
Set<IssueComment> comments = pr.queryComments().list().toList().stream()
.map(comment -> {
IssueComment c = commentConverter.convert(comment);
c.setPullrequest(pullrequest);
User author;
try {
author = getActorFromGHUser(comment.getUser());
author.addComment(c);
author.addPullrequest(pullrequest);
} catch (IOException e) {
logger.error("Error while fetching author!");
author = null;
}
c.setAuthor(author);
return c;
}).collect(Collectors.toSet());
return comments;
}

private User getActorFromGHUser(org.kohsuke.github.GHUser user) {
User ghUser = userRepository.findUser(user.getLogin()).orElse(null);
if (ghUser == null) {
ghUser = new UserConverter().convert(user);
userRepository.save(ghUser);
}
return ghUser;

}
}
Loading
Loading