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
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package pl.commit.craft.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import pl.commit.craft.service.CommitTranslateService;

@RestController
@RequestMapping("/api/v1/commit-translate")
@Tag(name = "Commit Translation Controller", description = "Translate commit wit deepl api")
public class CommitTranslateController {

private final CommitTranslateService commitTranslateService;
Expand All @@ -18,8 +24,19 @@ public CommitTranslateController(CommitTranslateService commitTranslateService)
summary = "Generate commit translation",
description = "Generates a translated commit message based on the provided request information."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Successfully generated commit translation",
content = @Content(mediaType = "text/plain", schema = @Schema(type = "string"))),
@ApiResponse(responseCode = "400", description = "Bad request, invalid input data",
content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "500", description = "Internal server error",
content = @Content(mediaType = "application/json"))
})
@PostMapping("/craft")
public String generateCommit(
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Commit translation request data",
required = true,
content = @Content(schema = @Schema(implementation = CommitTranslateRequest.class)))
@RequestBody CommitTranslateRequest commitTranslateRequest) {
return commitTranslateService.generateTranslateCommit(
commitTranslateRequest.major(),
Expand Down
20 changes: 19 additions & 1 deletion src/main/java/pl/commit/craft/flow/CommitFlowController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package pl.commit.craft.flow;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -9,6 +14,7 @@

@RestController
@RequestMapping("/api/v1/commit-flow")
@Tag(name = "Commit Flow Controller", description = "Flow commit")
public class CommitFlowController {

private final CommitTranslateService commitTranslateService;
Expand All @@ -21,8 +27,20 @@ public CommitFlowController(CommitTranslateService commitTranslateService) {
summary = "Generate a commit message based on the provided commit flow data",
description = "This endpoint receives commit flow details and generates the corresponding commit message."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Successfully generated commit message",
content = @Content(mediaType = "text/plain", schema = @Schema(type = "string"))),
@ApiResponse(responseCode = "400", description = "Bad request, invalid input data",
content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "500", description = "Internal server error",
content = @Content(mediaType = "application/json"))
})
@PostMapping("/craft")
public String generateCommit(@RequestBody CommitFlowRequest commitFlowRequest) {
public String generateCommit(
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Commit flow request data",
required = true,
content = @Content(schema = @Schema(implementation = CommitFlowRequest.class)))
@RequestBody CommitFlowRequest commitFlowRequest) {
return commitTranslateService.generateFlowCommit(
commitFlowRequest.major(),
commitFlowRequest.type(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package pl.commit.craft.template;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/v1/craft-template")
@RequiredArgsConstructor
@Tag(name = "Commit Template Controller", description = "Management template commit model")
public class CommitCraftTemplateController {

private final CommitTemplateService commitTemplateService;
Expand All @@ -20,6 +28,11 @@ public class CommitCraftTemplateController {
summary = "Get all commit templates",
description = "Fetches a list of all available commit craft templates."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Successfully fetched templates",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = CommitCraftTemplate.class)))
})
@GetMapping("/all")
public ResponseEntity<List<CommitCraftTemplate>> getAllTemplates() throws IOException {
List<CommitCraftTemplate> templates = commitTemplateService.getAllTemplates();
Expand All @@ -28,29 +41,40 @@ public ResponseEntity<List<CommitCraftTemplate>> getAllTemplates() throws IOExce

@Operation(
summary = "Create a dedicated commit template",
description = "Creates a new dedicated commit template if the pattern and model scope are valid.",
responses = {
@ApiResponse(responseCode = "200", description = "Template added successfully"),
@ApiResponse(responseCode = "400", description = "Template already exists")
}
description = "Creates a new dedicated commit template if the pattern and model scope are valid."
)
@ApiResponses({
@ApiResponse(responseCode = "201", description = "Template created successfully",
content = @Content(mediaType = "application/json")),
@ApiResponse(responseCode = "400", description = "Invalid template format or template already exists",
content = @Content(mediaType = "application/json"))
})
@PostMapping("/dedicated")
public ResponseEntity<String> createDedicatedTemplate(@RequestBody CommitCraftTemplate template) throws IOException {
boolean patternAndModelScope = CommitDedicatedTemplateValidator.validatePatternAndModelScope(template);
if (patternAndModelScope) {
commitTemplateService.createDedicatedTemplate(template);
return ResponseEntity.ok("Template added successfully.");
}
return ResponseEntity.badRequest().body("Template already exists.");
public ResponseEntity<Map<String, String>> createDedicatedTemplate(
@RequestBody(description = "Commit template data", required = true,
content = @Content(schema = @Schema(implementation = CommitCraftTemplate.class)))
@org.springframework.web.bind.annotation.RequestBody CommitCraftTemplate template) throws IOException {
TemplateOperationResult result = commitTemplateService.createDedicatedTemplate(template);

if (result.success()) {
return ResponseEntity
.status(HttpStatus.CREATED)
.body(Collections.singletonMap("message", result.message()));
}

return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(Collections.singletonMap("error", result.message()));
}

@Operation(
summary = "Remove a commit template",
description = "Removes a dedicated commit template by name.",
responses = {
@ApiResponse(responseCode = "200", description = "Template removed successfully")
}
description = "Removes a dedicated commit template by name."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Template removed successfully",
content = @Content(mediaType = "text/plain"))
})
@DeleteMapping("/removed/{name}")
public ResponseEntity<String> addTemplate(@PathVariable("name") String name) throws IOException {
commitTemplateService.removeDedicatedTemplate(name);
Expand All @@ -59,11 +83,12 @@ public ResponseEntity<String> addTemplate(@PathVariable("name") String name) thr

@Operation(
summary = "Generate commit template JSON",
description = "Generates a JSON representation of the commit template based on its name.",
responses = {
@ApiResponse(responseCode = "200", description = "JSON generated successfully")
}
description = "Generates a JSON representation of the commit template based on its name."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "JSON generated successfully",
content = @Content(mediaType = "application/json"))
})
@PostMapping("/generate-json/{name}")
public Map<String, Object> generateJson(@PathVariable String name) throws IOException {
CommitCraftJson commitCraftJson = commitTemplateService.prepareJsonByModel(name);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,79 @@
package pl.commit.craft.template;

import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
class CommitDedicatedTemplateValidator {
private static final String PATTERN_VALIDATE_MODEL = "\\{(\\w+)\\}(-\\{(\\w+)\\})*";
private static final String PLACEHOLDER_REGEX = "\\{(\\w+)\\}";
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile(PLACEHOLDER_REGEX);

static boolean validatePatternAndModelScope(CommitCraftTemplate template) {
log.info("Validating pattern and model scope starting");
String pattern = template.getPattern();
Map<String, Object> model = template.getModel();
/**
* For backward compatibility
*/
public static boolean validatePatternAndModelScope(CommitCraftTemplate template) {
return validatePatternAndModelScopeDetailed(template).isValid();
}

Set<String> modelKeys = model.keySet();
/**
* Validates that all keys in the model exist in the pattern and vice versa.
*
* @param template The commit template to validate
* @return result of validation with details about any mismatches
*/
public static ValidationResult validatePatternAndModelScopeDetailed(CommitCraftTemplate template) {
log.info("Validating pattern and model scope starting for template: {}", template.getName());

boolean matches = true;
for (String key : modelKeys) {
if (!pattern.contains(key)) {
log.warn("Pattern is missing key: {}", key);
matches = false;
}
}
Set<String> modelKeys = template.getModel().keySet();
Set<String> patternKeys = extractPlaceholdersFromPattern(template.getPattern());

String[] patternWords = pattern.split(PATTERN_VALIDATE_MODEL);
for (String word : patternWords) {
if (!modelKeys.contains(word)) {
log.warn("Pattern contains an extra key not in the model: {}", word);
matches = false;
}
}
Set<String> missingInPattern = findMissingKeys(modelKeys, patternKeys);
Set<String> extraInPattern = findMissingKeys(patternKeys, modelKeys);

boolean isValid = missingInPattern.isEmpty() && extraInPattern.isEmpty();

if (matches) {
if (isValid) {
log.info("Pattern matches the model keys.");
return ValidationResult.valid();
} else {
log.warn("Pattern does not match the model keys.");
if (!missingInPattern.isEmpty()) {
log.warn("Pattern is missing keys: {}", missingInPattern);
}
if (!extraInPattern.isEmpty()) {
log.warn("Pattern contains extra keys not in the model: {}", extraInPattern);
}
return ValidationResult.invalid(missingInPattern, extraInPattern);
}
}


/**
* Extracts all placeholder keys from the pattern string.
*
* @param pattern The pattern string containing placeholders
* @return A set of placeholder keys
*/
private static Set<String> extractPlaceholdersFromPattern(String pattern) {
Set<String> patternKeys = new HashSet<>();
Matcher matcher = PLACEHOLDER_PATTERN.matcher(pattern);

while (matcher.find()) {
patternKeys.add(matcher.group(1));
}

return patternKeys;
}

return matches;
/**
* Finds keys that are in the first set but not in the second set
*/
private static Set<String> findMissingKeys(Set<String> sourceKeys, Set<String> targetKeys) {
return sourceKeys.stream()
.filter(key -> !targetKeys.contains(key))
.collect(Collectors.toSet());
}
}
57 changes: 53 additions & 4 deletions src/main/java/pl/commit/craft/template/CommitTemplateService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
Expand All @@ -14,6 +15,7 @@
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
class CommitTemplateService {
Expand Down Expand Up @@ -78,10 +80,57 @@ public List<CommitCraftTemplate> readTemplates() throws IOException {
});
}

public void createDedicatedTemplate(CommitCraftTemplate newTemplate) throws IOException {
List<CommitCraftTemplate> templates = readTemplates();
templates.add(newTemplate);
saveTemplates(templates);
/**
* Creates a dedicated template after validating it.
*
* @param template The template to create
* @return Result containing success/failure status and a message
* @throws IOException If there's an issue accessing the template storage
*/
public TemplateOperationResult createDedicatedTemplate(CommitCraftTemplate template) throws IOException {
ValidationResult validationResult =
CommitDedicatedTemplateValidator.validatePatternAndModelScopeDetailed(template);

if (!validationResult.isValid()) {
String errorMessage = validationResult.getErrorMessage();
log.warn("Template validation failed: {}", errorMessage);
return new TemplateOperationResult(false, errorMessage);
}

if (templateExists(template.getName())) {
log.warn("Template with name '{}' already exists", template.getName());
return new TemplateOperationResult(false, "Template with name '" + template.getName() + "' already exists");
}

saveTemplate(template);
log.info("Template '{}' created successfully", template.getName());
return new TemplateOperationResult(true, "Template created successfully");
}

/**
* Checks if a template with the given name already exists
*
* @param templateName Name of the template to check
* @return true if the template exists, false otherwise
* @throws IOException If there's an issue accessing the template storage
*/
private boolean templateExists(String templateName) throws IOException {
List<CommitCraftTemplate> existingTemplates = getAllTemplates();
return existingTemplates.stream()
.anyMatch(template -> template.getName().equals(templateName));
}

/**
* Saves a new template to the dedicated templates file
*
* @param template The template to save
* @throws IOException If there's an issue accessing or writing to the template storage
*/
private void saveTemplate(CommitCraftTemplate template) throws IOException {
List<CommitCraftTemplate> existingTemplates = readTemplates();
existingTemplates.add(template);
saveTemplates(existingTemplates);
log.debug("Template saved successfully: {}", template.getName());
}

public void removeDedicatedTemplate(String dedicatedTemplateName) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package pl.commit.craft.template;

record TemplateOperationResult(boolean success, String message) {
}
Loading