Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-webmvc'

// JGit
implementation 'org.eclipse.jgit:org.eclipse.jgit:7.4.0.202509020913-r'

// Spring AI for LLM interaction
implementation "org.springframework.ai:spring-ai-starter-model-azure-openai:1.1.0"

Expand All @@ -91,6 +94,7 @@ dependencies {
}

annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

testImplementation 'org.springframework.boot:spring-boot-starter-actuator-test'
testImplementation 'org.springframework.boot:spring-boot-starter-batch-test'
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/de/tum/cit/aet/core/config/ArtemisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.tum.cit.aet.core.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Configuration
@ConfigurationProperties(prefix = "artemis")
@Getter
@Setter
public class ArtemisConfig {

private String baseUrl;
private String username;
private String password;
private String jwtToken;
private Long exerciseId;
private String gitRepoPath;
private Integer numThreads;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package de.tum.cit.aet.core.exceptions;

/**
* Custom exception for errors occurring during communication with the Artemis API.
* This class provides a more specific way to catch and handle external service failures
* related to the Artemis client functionality.
*/
public class ArtemisConnectionException extends RuntimeException {

/**
* Constructs a new ArtemisConnectionException with the specified detail message.
*
* @param message the detail message.
*/
public ArtemisConnectionException(String message) {
super(message);
}

/**
* Constructs a new ArtemisClientException with the specified detail message and cause.
*
* @param message the detail message.
* @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
*/
public ArtemisConnectionException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.tum.cit.aet.repositoryProcessing.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

Comment thread
az108 marked this conversation as resolved.
/**
* DTO for a Participant (e.g., a student or tutor).
* Used to convey basic information about an individual user.
*
* @param id The unique identifier of the participant.
* @param login The unique login name (e.g., username) of the participant.
* @param name The full name of the participant.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public record ParticipantDTO(
Long id,
String login,
String name
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.tum.cit.aet.repositoryProcessing.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

/**
* DTO for Artemis API participation response.
* Represents a team's participation in an exercise, linking the team to its repository.
*
* @param id The unique identifier of the participation record.
* @param repositoryUri The URI or URL of the code repository associated with this participation.
* @param team The team associated with this participation.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public record ParticipationDTO(
Long id,
String repositoryUri,
TeamDTO team
) {
}
25 changes: 25 additions & 0 deletions src/main/java/de/tum/cit/aet/repositoryProcessing/dto/TeamDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package de.tum.cit.aet.repositoryProcessing.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import java.util.List;

Comment thread
az108 marked this conversation as resolved.
/**
* DTO for a Team.
* Used to represent team information, including its members and owner.
*
* @param id The unique identifier of the team.
* @param name The full name of the team.
* @param shortName The shortened name for the team.
* @param students A list of the team members (participants).
* @param owner The tutor for the team.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public record TeamDTO(
Long id,
String name,
String shortName,
List<ParticipantDTO> students,
ParticipantDTO owner
) {
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
package de.tum.cit.aet.repositoryProcessing.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import de.tum.cit.aet.repositoryProcessing.domain.TeamRepository;

import java.util.UUID;

/**
* DTO representing a team's repository information and analysis results.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public record TeamRepositoryDTO(UUID teamRepositoryDTO) {
public record TeamRepositoryDTO(
ParticipationDTO participation,
String localPath,
Integer commitCount,
Boolean isCloned,
String error
) {
/**
* Creates a builder for TeamRepositoryDTO.
*/
public static TeamRepositoryDTOBuilder builder() {
return new TeamRepositoryDTOBuilder();
}

/**
* @return The teamRepositoryDTO from the teamRepository
* Creates a copy with updated commitCount.
*/
public static TeamRepositoryDTO getFromEntity(TeamRepository teamRepository) {
if (teamRepository == null) {
return null;
}
public TeamRepositoryDTO withCommitCount(Integer commitCount) {
return new TeamRepositoryDTO(participation, localPath, commitCount, isCloned, error);
}

return new TeamRepositoryDTO(
teamRepository.getTeamRepositoryId()
);
/**
* Creates a copy with updated error.
*/
public TeamRepositoryDTO withError(String error) {
return new TeamRepositoryDTO(participation, localPath, commitCount, isCloned, error);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package de.tum.cit.aet.repositoryProcessing.dto;

/**
* Builder for immutable TeamRepositoryDTO records.
*/
public class TeamRepositoryDTOBuilder {
private ParticipationDTO participation;
private String localPath;
private Integer commitCount;
private Boolean isCloned;
private String error;

public TeamRepositoryDTOBuilder participation(ParticipationDTO participation) {
this.participation = participation;
return this;
}

public TeamRepositoryDTOBuilder localPath(String localPath) {
this.localPath = localPath;
return this;
}

public TeamRepositoryDTOBuilder commitCount(Integer commitCount) {
this.commitCount = commitCount;
return this;
}

public TeamRepositoryDTOBuilder isCloned(Boolean isCloned) {
this.isCloned = isCloned;
return this;
}

public TeamRepositoryDTOBuilder error(String error) {
this.error = error;
return this;
}

public TeamRepositoryDTO build() {
return new TeamRepositoryDTO(participation, localPath, commitCount, isCloned, error);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package de.tum.cit.aet.repositoryProcessing.service;

import de.tum.cit.aet.core.config.ArtemisConfig;
import de.tum.cit.aet.core.exceptions.ArtemisConnectionException;
import de.tum.cit.aet.repositoryProcessing.dto.ParticipationDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

import java.util.List;

/**
* Service responsible for communicating with the Artemis API.
* Handles authentication and fetching of participation data.
*/
@Service
@Slf4j
public class ArtemisClientService {

private final ArtemisConfig artemisConfig;
private final RestClient restClient;

@Autowired
public ArtemisClientService(ArtemisConfig artemisConfig) {
this.artemisConfig = artemisConfig;
restClient = RestClient.builder()
.baseUrl(artemisConfig.getBaseUrl())
.defaultHeader("Cookie", "jwt=" + artemisConfig.getJwtToken())
.build();
}

/**
* Fetches all participations for the configured exercise from Artemis.
*
* @return List of participation DTOs containing team and repository information
*/
public List<ParticipationDTO> fetchParticipations() {
log.info("Fetching participations for exercise ID: {}", artemisConfig.getExerciseId());

String uri = String.format("/exercise/exercises/%d/participations?withLatestResults=false",
artemisConfig.getExerciseId());

try {
List<ParticipationDTO> participations = restClient.get()
.uri(uri)
.retrieve()
.body(new ParameterizedTypeReference<>() {
});

log.info("Successfully fetched {} participations",
participations != null ? participations.size() : 0);

return participations;

} catch (Exception e) {
log.error("Error fetching participations from Artemis", e);
throw new ArtemisConnectionException("Failed to fetch participations from Artemis", e);
}
}
}
Loading
Loading