chore: initial commit - project structure with domain, application, infrastructure, bootstrap, and basic dependencies

This commit is contained in:
2025-08-25 21:13:43 +02:00
parent a3fc504015
commit 5215bf8779
25 changed files with 1001 additions and 0 deletions

49
infrastructure/pom.xml Normal file
View File

@@ -0,0 +1,49 @@
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pablotj</groupId>
<artifactId>portfolio-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>infrastructure</artifactId>
<dependencies>
<dependency>
<groupId>com.pablotj</groupId>
<artifactId>domain</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.pablotj</groupId>
<artifactId>application</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- MapStruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,41 @@
package com.pablotj.portfolio.infrastructure.persistence.adapter;
import com.pablotj.portfolio.domain.project.Project;
import com.pablotj.portfolio.domain.project.ProjectId;
import com.pablotj.portfolio.domain.project.port.ProjectRepositoryPort;
import com.pablotj.portfolio.infrastructure.persistence.entity.ProjectJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.mapper.ProjectJpaMapper;
import com.pablotj.portfolio.infrastructure.persistence.repo.SpringDataProjectRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public class ProjectRepositoryAdapter implements ProjectRepositoryPort {
private final SpringDataProjectRepository repo;
private final ProjectJpaMapper mapper;
public ProjectRepositoryAdapter(SpringDataProjectRepository repo, ProjectJpaMapper mapper) {
this.repo = repo;
this.mapper = mapper;
}
@Override
public Project save(Project p) {
ProjectJpaEntity entity = mapper.toEntity(p);
ProjectJpaEntity saved = repo.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<Project> findById(ProjectId id) {
return repo.findById(id.value()).map(mapper::toDomain);
}
@Override
public List<Project> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
}
}

View File

@@ -0,0 +1,23 @@
package com.pablotj.portfolio.infrastructure.persistence.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "projects")
@Getter @Setter
public class ProjectJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column(columnDefinition = "text")
private String description;
private String url;
}

View File

@@ -0,0 +1,24 @@
package com.pablotj.portfolio.infrastructure.persistence.mapper;
import com.pablotj.portfolio.domain.project.Project;
import com.pablotj.portfolio.domain.project.ProjectId;
import com.pablotj.portfolio.infrastructure.persistence.entity.ProjectJpaEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface ProjectJpaMapper {
@Mapping(target = "id", ignore = true)
ProjectJpaEntity toEntity(Project domain);
default Project toDomain(ProjectJpaEntity e) {
if (e == null) return null;
return Project.builder()
.id(e.getId() == null ? null : new ProjectId(e.getId()))
.title(e.getTitle())
.description(e.getDescription())
.url(e.getUrl())
.build();
}
}

View File

@@ -0,0 +1,6 @@
package com.pablotj.portfolio.infrastructure.persistence.repo;
import com.pablotj.portfolio.infrastructure.persistence.entity.ProjectJpaEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SpringDataProjectRepository extends JpaRepository<ProjectJpaEntity, Long> {}

View File

@@ -0,0 +1,53 @@
package com.pablotj.portfolio.infrastructure.rest.controller;
import com.pablotj.portfolio.application.project.CreateProjectUseCase;
import com.pablotj.portfolio.application.project.GetProjectUseCase;
import com.pablotj.portfolio.infrastructure.rest.dto.CreateProjectRequest;
import com.pablotj.portfolio.infrastructure.rest.dto.ProjectDto;
import com.pablotj.portfolio.infrastructure.rest.mapper.ProjectRestMapper;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.util.List;
@RestController
@RequestMapping("/api/projects")
public class ProjectController {
private final CreateProjectUseCase createUC;
private final GetProjectUseCase getUC;
private final ProjectRestMapper mapper;
public ProjectController(CreateProjectUseCase createUC, GetProjectUseCase getUC, ProjectRestMapper mapper) {
this.createUC = createUC;
this.getUC = getUC;
this.mapper = mapper;
}
@GetMapping
public List<ProjectDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
}
@GetMapping("/{id}")
public ResponseEntity<ProjectDto> byId(@PathVariable Long id) {
return getUC.byId(id)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<ProjectDto> create(@Valid @RequestBody CreateProjectRequest request) {
var cmd = new CreateProjectUseCase.Command(
request.title(),
request.description(),
request.url()
);
var created = createUC.handle(cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/projects/" + body.id())).body(body);
}
}

View File

@@ -0,0 +1,9 @@
package com.pablotj.portfolio.infrastructure.rest.dto;
import jakarta.validation.constraints.NotBlank;
public record CreateProjectRequest(
@NotBlank String title,
String description,
String url
) {}

View File

@@ -0,0 +1,5 @@
package com.pablotj.portfolio.infrastructure.rest.dto;
import jakarta.validation.constraints.NotBlank;
public record ProjectDto(Long id, @NotBlank String title, String description, String url) {}

View File

@@ -0,0 +1,13 @@
package com.pablotj.portfolio.infrastructure.rest.mapper;
import com.pablotj.portfolio.domain.project.Project;
import com.pablotj.portfolio.infrastructure.rest.dto.ProjectDto;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface ProjectRestMapper {
@Mapping(target = "id", source = "id.value")
ProjectDto toDto(Project domain);
}