Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# github-stats

## Available commands

`github-stats` runs in an interactive shell where you can run commands that fetch Github data.

Command definition:

```
login - Log in to the Github API before making any requests.
Usage: login --token <TOKEN>

team - Get data from a specified team and its sub teams.
Usage: team -n <NAME> --from <FROM_DATE> --to <TO_DATE>
FROM_DATE: month and year in MM/yy format (starting Jan 2000)
TO_DATE: same format as FROM_DATE, accepts until current_month - 1

user - Get data from a specified user.
Usage: user -n <NAME> --from <FROM_DATE> --to <TO_DATE>
FROM_DATE: month and year in MM/yy format (starting Jan 2000)
TO_DATE: same format as FROM_DATE, accepts until current_month - 1
```

## Software stack
- Java 17
- Spring Boot 2.7.5
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.pakland.mdas.githubstats.infrastructure.shell.components;

import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellOption;

@ShellComponent
public class LoginComponent {

private boolean login(@ShellOption(value = {"token"}) String token) {
return token != null;
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.pakland.mdas.githubstats.infrastructure.shell.components;

import io.pakland.mdas.githubstats.infrastructure.shell.validation.DateValidator;
import io.pakland.mdas.githubstats.infrastructure.shell.validation.TeamNameValidator;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellOption;

@ShellComponent
public class TeamComponent {

private boolean team(
@ShellOption(value = {"n"}) String teamName,
@ShellOption(value = {"from"}) String fromDate,
@ShellOption(value = {"to"}) String toDate
) {
DateValidator dateValidator = new DateValidator();
TeamNameValidator teamNameValidator = new TeamNameValidator();

boolean isInputValid = dateValidator.validate(fromDate)
&& dateValidator.validate(toDate)
&& teamNameValidator.validate(teamName);

if (!isInputValid) {
// Alert user, and halt command
return false;
}

// ... Perform request ...

return true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.pakland.mdas.githubstats.infrastructure.shell.components;

import io.pakland.mdas.githubstats.infrastructure.shell.validation.DateValidator;
import io.pakland.mdas.githubstats.infrastructure.shell.validation.UserNameValidator;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellOption;

@ShellComponent
public class UserComponent {

private boolean user(
@ShellOption(value = {"n"}) String userName,
Comment thread
mikededo marked this conversation as resolved.
@ShellOption(value = {"from"}) String fromDate,
@ShellOption(value = {"to"}) String toDate
) {
DateValidator dateValidator = new DateValidator();
UserNameValidator userNameValidator = new UserNameValidator();

boolean isInputValid = dateValidator.validate(fromDate)
&& dateValidator.validate(toDate)
&& userNameValidator.validate(userName);

if (!isInputValid) {
// Alert user, and halt command
return false;
}

// ... Perform request ...

return true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package io.pakland.mdas.githubstats.infrastructure.shell.configuration;

import io.pakland.mdas.githubstats.infrastructure.shell.components.LoginComponent;
import io.pakland.mdas.githubstats.infrastructure.shell.components.TeamComponent;
import io.pakland.mdas.githubstats.infrastructure.shell.components.UserComponent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.command.CommandRegistration;

@Configuration
public class CommandConfiguration {

@Bean
public CommandRegistration buildUserCommand() {
UserComponent userComponent = new UserComponent();

return CommandRegistration.builder()
.command("user")
.description("Get data from a specified user.")
.withTarget()
.method(userComponent, "user")
.and()
.withOption()
.shortNames('n')
.longNames("name")
.label("USER_NAME")
.arity(CommandRegistration.OptionArity.EXACTLY_ONE)
.type(String.class)
.required()
.and()
.withOption()
.longNames("from")
.label("FROM_DATE")
.arity(CommandRegistration.OptionArity.EXACTLY_ONE)
.type(String.class)
.required()
.and()
.withOption()
.longNames("to")
.label("TO_DATE")
.arity(CommandRegistration.OptionArity.EXACTLY_ONE)
.type(String.class)
.required()
.and()
.build();
}

@Bean
public CommandRegistration buildTeamCommand() {
TeamComponent teamComponent = new TeamComponent();

return CommandRegistration.builder()
.command("team")
.description("Get data from a specified team and its sub teams.")
.withTarget()
.method(teamComponent, "team")
.and()
.withOption()
.shortNames('n')
.longNames("name")
.label("TEAM_NAME")
.arity(CommandRegistration.OptionArity.EXACTLY_ONE)
.type(String.class)
.required()
.and()
.withOption()
.longNames("from")
.label("FROM_DATE")
.arity(CommandRegistration.OptionArity.EXACTLY_ONE)
.type(String.class)
.required()
.and()
.withOption()
.longNames("to")
.label("TO_DATE")
.arity(CommandRegistration.OptionArity.EXACTLY_ONE)
.type(String.class)
.required()
.and()
.build();
}

@Bean
public CommandRegistration buildLoginCommand() {
LoginComponent loginComponent = new LoginComponent();

return CommandRegistration.builder()
.command("login")
.description("Log in to the Github API before making any requests.")
.withTarget()
.method(loginComponent, "login")
.and()
.withOption()
.longNames("token")
.label("TOKEN")
.type(String.class)
.required()
.and()
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.pakland.mdas.githubstats.infrastructure.shell.validation;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

public class DateValidator implements InputValidator<String> {

private final DateTimeFormatter formatter;

public DateValidator() {
String datePattern = "dd/MM/yy HH:mm";
formatter = DateTimeFormatter.ofPattern(datePattern);
}

/**
* @param input Must be MM/yy, and prior to the current month. Starting year: 2000 (01/01/99 parses to 01/01/2099)
*/
@Override
public boolean validate(String input) {
if (input == null || input.isBlank()) return false;

try {
LocalDateTime date = LocalDateTime.parse(getInitialDateFromMMYY(input), formatter);
LocalDateTime now = LocalDateTime.now();
return date.isBefore(now.minusMonths(1));
} catch (DateTimeParseException e) {
// Input was not properly formatted
return false;
}
}

private String getInitialDateFromMMYY(String input) {
return "01/" + input + " 00:00";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.pakland.mdas.githubstats.infrastructure.shell.validation;

public interface InputValidator<T> {

boolean validate(T input);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.pakland.mdas.githubstats.infrastructure.shell.validation;

public class TeamNameValidator implements InputValidator<String> {

public TeamNameValidator() {}

/**
* @param input Must pass the Github team name creation constraints:
* - Team name may only contain alphanumeric characters or hyphens.
* - Team name cannot have multiple consecutive hyphens.
* - Team name cannot begin or end with a hyphen.
* - Maximum is 39 characters.
*/
@Override
public boolean validate(String input) {
if (input == null || input.isBlank()) return false;
return !input.matches("^-.*")
&& !input.matches(".*-$")
&& input.matches("[a-zA-Z0-9\\-]{0,39}")
&& !input.matches(".*--.*");
}

}
Loading