Skip to content

Commit

Permalink
#61 Create new GitHub Projects Section (#63)
Browse files Browse the repository at this point in the history
* #61 Create new GitHub Projects Section

- [x] GET projects by GIT username endpoints (via request parameter or by path variable in URI)
- [x] Add new section to home page for Github projects
- Update project configurations & refactoring of package/project structure
- Unit tests for all new code

* #61 Adding username validation & unit tests, Unpack imports where wildcard used
  • Loading branch information
conorheffron authored Sep 14, 2024
1 parent f5ae0da commit b7a3ab8
Show file tree
Hide file tree
Showing 33 changed files with 1,231 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/aws.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
# Build a docker container and
# push it to ECR so that it can
# be deployed to ECS.
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker build --build-arg secret=${{ secrets.$GIT_API_TOKEN }} -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .s
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
Expand Down
3 changes: 0 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
FROM eclipse-temurin:21-jdk

VOLUME /tmp

#for aws
COPY target/*.war app.war
RUN sh -c 'touch /app.war'

Expand Down
12 changes: 11 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>conorheffron</groupId>
<artifactId>ironoc</artifactId>
<version>3.2.0</version>
<version>4.1.0</version>
<packaging>war</packaging>

<distributionManagement>
Expand Down Expand Up @@ -120,6 +120,16 @@
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.16.0</version>
</dependency>
</dependencies>

<build>
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/ironoc/portfolio/client/Client.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ironoc.portfolio.client;

import javax.net.ssl.HttpsURLConnection;
import java.io.IOException;
import java.io.InputStream;

public interface Client {

HttpsURLConnection createConn(String url) throws IOException;

InputStream readInputStream(HttpsURLConnection conn) throws IOException;

void closeConn(InputStream inputStream) throws IOException;
}
51 changes: 51 additions & 0 deletions src/main/java/com/ironoc/portfolio/client/GitClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.ironoc.portfolio.client;

import com.ironoc.portfolio.config.PropertyConfigI;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;

import javax.net.ssl.HttpsURLConnection;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

@Component
@Slf4j
public class GitClient implements Client {

private final PropertyConfigI propertyConfig;

public GitClient(PropertyConfigI propertyConfig) {
this.propertyConfig = propertyConfig;
}

@Override
public HttpsURLConnection createConn(String url) throws IOException {
URL apiUrlEndpoint = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) apiUrlEndpoint.openConnection();
String token = propertyConfig.getGitToken();
if (StringUtils.isBlank(token)) {
log.warn("GIT token not set, the lower request rate will apply");
} else {
conn.setRequestProperty("Authorization", token);
}
conn.setRequestMethod(HttpMethod.GET.name());
conn.setFollowRedirects(propertyConfig.getGitFollowRedirects());
conn.setConnectTimeout(propertyConfig.getGitTimeoutConnect());
conn.setReadTimeout(propertyConfig.getGitTimeoutRead());
conn.setInstanceFollowRedirects(propertyConfig.getGitInstanceFollowRedirects());
return conn;
}

@Override
public InputStream readInputStream(HttpsURLConnection conn) throws IOException {
return conn.getInputStream();
}

@Override
public void closeConn(InputStream inputStream) throws IOException {
inputStream.close();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ironoc.portfolio.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
Expand All @@ -15,7 +16,7 @@
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.ironoc.portfolio" })
public class IronocMvcConfig implements WebMvcConfigurer {
public class IronocConfiguration implements WebMvcConfigurer {

protected static final String RESOURCES_HANDLER = "/resources/**";
protected static final String FAV_ICON = "/favicon.ico";
Expand Down Expand Up @@ -50,4 +51,10 @@ public void configureDefaultServletHandling(DefaultServletHandlerConfigurer conf
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> enableDefaultServlet() {
return (factory) -> factory.setRegisterDefaultServlet(true);
}

@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper;
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/ironoc/portfolio/config/Properties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ironoc.portfolio.config;

import lombok.Getter;

@Getter
public enum Properties {

GIT_TOKEN("com.ironoc.portfolio.github.token"),
GIT_API_ENDPOINT("com.ironoc.portfolio.github.api.endpoint"),
GIT_REPOS_URI("com.ironoc.portfolio.github.uri.repos"),
GIT_TIMEOUT_CONNECT ("com.ironoc.portfolio.github.timeout.connect"),
GIT_TIMEOUT_READ("com.ironoc.portfolio.github.timeout.read"),
GIT_INSTANCE_FOLLOW_REDIRECTS("com.ironoc.portfolio.github.instance-follow-redirects"),
GIT_FOLLOW_REDIRECTS("com.ironoc.portfolio.github.follow-redirects");

private String key;

Properties(String key) {
this.key = key;
}
}
52 changes: 52 additions & 0 deletions src/main/java/com/ironoc/portfolio/config/PropertyConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.ironoc.portfolio.config;

import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class PropertyConfig implements PropertyConfigI {

private final Environment environment;

private final PropertyKeyI propertyKey;

public PropertyConfig(Environment environment, PropertyKeyI propertyKey) {
this.environment = environment;
this.propertyKey = propertyKey;
}

@Override
public String getGitApiEndpoint() {
return environment.getRequiredProperty(propertyKey.getGitApiEndpoint());
}

@Override
public String getGitReposUri() {
return environment.getRequiredProperty(propertyKey.getGitReposUri());
}

@Override
public Integer getGitTimeoutConnect() {
return Integer.valueOf(environment.getRequiredProperty(propertyKey.getGitTimeoutConnect()));
}

@Override
public Integer getGitTimeoutRead() {
return Integer.valueOf(environment.getRequiredProperty(propertyKey.getGitTimeoutRead()));
}

@Override
public Boolean getGitInstanceFollowRedirects() {
return Boolean.valueOf(environment.getRequiredProperty(propertyKey.getGitInstanceFollowRedirects()));
}

@Override
public Boolean getGitFollowRedirects() {
return Boolean.valueOf(environment.getRequiredProperty(propertyKey.getGitFollowRedirects()));
}

@Override
public String getGitToken() {
return environment.getProperty(propertyKey.getGitToken(), "");
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/ironoc/portfolio/config/PropertyConfigI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ironoc.portfolio.config;

public interface PropertyConfigI {

String getGitApiEndpoint();

String getGitReposUri();

Integer getGitTimeoutConnect();

Integer getGitTimeoutRead();

Boolean getGitInstanceFollowRedirects();

Boolean getGitFollowRedirects();

String getGitToken();
}
42 changes: 42 additions & 0 deletions src/main/java/com/ironoc/portfolio/config/PropertyKey.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.ironoc.portfolio.config;

import org.springframework.stereotype.Component;

@Component
public class PropertyKey implements PropertyKeyI {

@Override
public String getGitApiEndpoint() {
return Properties.GIT_API_ENDPOINT.getKey();
}

@Override
public String getGitReposUri() {
return Properties.GIT_REPOS_URI.getKey();
}

@Override
public String getGitTimeoutConnect() {
return Properties.GIT_TIMEOUT_CONNECT.getKey();
}

@Override
public String getGitTimeoutRead() {
return Properties.GIT_TIMEOUT_READ.getKey();
}

@Override
public String getGitInstanceFollowRedirects() {
return Properties.GIT_INSTANCE_FOLLOW_REDIRECTS.getKey();
}

@Override
public String getGitFollowRedirects() {
return Properties.GIT_FOLLOW_REDIRECTS.getKey();
}

@Override
public String getGitToken() {
return Properties.GIT_TOKEN.getKey();
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/ironoc/portfolio/config/PropertyKeyI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ironoc.portfolio.config;

public interface PropertyKeyI {

String getGitApiEndpoint();

String getGitReposUri();

String getGitTimeoutConnect();

String getGitTimeoutRead();

String getGitInstanceFollowRedirects();

String getGitFollowRedirects();

String getGitToken();
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
package com.ironoc.portfolio.controller;

import com.ironoc.portfolio.logger.AbstractLogger;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;

@RestController
@Slf4j
public class CustomErrorController implements ErrorController {
public class CustomErrorController extends AbstractLogger implements ErrorController {

protected static final String PATH = "/error";

@RequestMapping(value = PATH)
public RedirectView error(HttpServletRequest request) {
log.error("Unexpected error occurred. {}, The HTTP status is: {}",
error("Unexpected error occurred. {}, The HTTP status is: {}",
request.getAttribute(RequestDispatcher.ERROR_MESSAGE),
request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
RedirectView redirectView = new RedirectView("/", false);
log.error("Bad request for {}. Redirecting to home",
error("Bad request for {}. Redirecting to home",
request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI));
return redirectView;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.ironoc.portfolio.controller;

import com.ironoc.portfolio.domain.RepositoryDetailDomain;
import com.ironoc.portfolio.dto.RepositoryDetailDto;
import com.ironoc.portfolio.logger.AbstractLogger;
import com.ironoc.portfolio.service.GitDetailsService;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Collections;
import java.util.List;

@Controller
public class GitProjectsController extends AbstractLogger {

@Autowired
private final GitDetailsService gitDetailsService;

public GitProjectsController(GitDetailsService gitDetailsService) {
this.gitDetailsService = gitDetailsService;
}

@GetMapping(value = {"/get-repo-detail/{username}/"}, produces= MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<RepositoryDetailDomain>> getReposByUsernamePathVar(HttpServletRequest request,
@PathVariable(value = "username") String username) {
return getReposByUsername(request, username);
}

@GetMapping(value = {"/get-repo-detail"}, produces= MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<RepositoryDetailDomain>> getReposByUsernameReqParam(HttpServletRequest request,
@RequestParam(value = "username") String username) {
return getReposByUsername(request, username);
}

private ResponseEntity<List<RepositoryDetailDomain>> getReposByUsername(HttpServletRequest request,
String username) {
// username validation
if (StringUtils.isBlank(username) | !StringUtils.isAlphanumeric(username) | !StringUtils.isAlpha(username)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Collections.emptyList());
}

info("Github get repositories by username={} for request, host={}, uri={}, user-agent={}",
username,
request.getHeader("host"),
request.getRequestURI(),
request.getHeader("user-agent"));
List<RepositoryDetailDto> repositories = gitDetailsService.getRepoDetails(username);
info("The repository details for user={} are: {}", username, repositories);
return ResponseEntity.status(HttpStatus.OK)
.body(gitDetailsService.mapRepositoriesToResponse(repositories));
}
}
Loading

0 comments on commit b7a3ab8

Please sign in to comment.