Compare commits
19 Commits
5215bf8779
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 16f10e101a | |||
| 83070ccbda | |||
| 4e72e6da77 | |||
| 5a26b299f2 | |||
| 6b3585da5e | |||
| e7b6712ad3 | |||
| ca41aee64f | |||
| 2959c68bf3 | |||
| 8b2795d518 | |||
| 8ef605ea2d | |||
|
|
ee1820960e | ||
| 1b55d9ab29 | |||
| 9f5306545e | |||
| ab40e9a497 | |||
| 9ff4b21dd9 | |||
| 7f12034174 | |||
| df24350bd3 | |||
| 5368425c1d | |||
| 1033c96d65 |
7
.env.example
Normal file
7
.env.example
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
APP_ALLOWED_ORIGINS='http://127.0.0.1:3000, http://localhost:3000'
|
||||||
|
|
||||||
|
DB_NAME=EXAMPLE_DB
|
||||||
|
DB_USER=EXAMPLE
|
||||||
|
DB_PASSWORD=SECRET
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=5432
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,3 +1,9 @@
|
|||||||
.idea
|
.idea
|
||||||
|
*.toml
|
||||||
|
*.db
|
||||||
target
|
target
|
||||||
|
.env
|
||||||
|
Icon?
|
||||||
|
.docker
|
||||||
|
tmp
|
||||||
|
logs
|
||||||
51
Dockerfile
Normal file
51
Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# --- Stage 1: Build & Dependencies ---
|
||||||
|
FROM maven:3.9-eclipse-temurin-21-alpine AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 1. Copiamos los archivos de configuración (Estructura de poms)
|
||||||
|
COPY pom.xml .
|
||||||
|
COPY application/pom.xml ./application/
|
||||||
|
COPY bootstrap/pom.xml ./bootstrap/
|
||||||
|
COPY domain/pom.xml ./domain/
|
||||||
|
COPY infrastructure/pom.xml ./infrastructure/
|
||||||
|
|
||||||
|
# 2. Descargamos dependencias (Cacheamos esta capa)
|
||||||
|
# Usamos install de los poms para que los módulos se reconozcan entre sí
|
||||||
|
RUN mvn dependency:go-offline -B
|
||||||
|
|
||||||
|
# 3. Copiamos el código fuente y compilamos
|
||||||
|
COPY application/src ./application/src
|
||||||
|
COPY bootstrap/src ./bootstrap/src
|
||||||
|
COPY domain/src ./domain/src
|
||||||
|
COPY infrastructure/src ./infrastructure/src
|
||||||
|
|
||||||
|
RUN mvn clean package -DskipTests -B
|
||||||
|
|
||||||
|
# --- Stage 2: Run (Imagen final ligera) ---
|
||||||
|
FROM eclipse-temurin:21-jre-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Seguridad: Usuario no-root
|
||||||
|
RUN addgroup -S app && adduser -S app -G app
|
||||||
|
|
||||||
|
RUN mkdir -p /app/tmp && chown app:app /app/tmp && chmod 777 /app/tmp
|
||||||
|
RUN mkdir -p /app/logs && chown app:app /app/logs && chmod 777 /app/logs
|
||||||
|
|
||||||
|
USER app:app
|
||||||
|
|
||||||
|
# Copiamos solo el JAR final (ajustado a tu módulo bootstrap)
|
||||||
|
COPY --from=build /app/bootstrap/target/bootstrap-*.jar app.jar
|
||||||
|
|
||||||
|
# Configuración de Memoria y Rendimiento para Microservicios
|
||||||
|
# -XX:+UseSerialGC: Menos consumo de RAM para apps < 1GB
|
||||||
|
# -XX:TieredStopAtLevel=1: Arranque más rápido y menos uso de RAM del compilador JIT
|
||||||
|
# -XX:MaxRAMPercentage: Se ajusta dinámicamente al límite de Docker
|
||||||
|
ENV JAVA_OPTS="-XX:+UseContainerSupport \
|
||||||
|
-XX:MaxRAMPercentage=75.0 \
|
||||||
|
-XX:+UseSerialGC \
|
||||||
|
-XX:TieredStopAtLevel=1 \
|
||||||
|
-Xms128m"
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
|
||||||
31
HELP.md
31
HELP.md
@@ -1,31 +0,0 @@
|
|||||||
# Getting Started
|
|
||||||
|
|
||||||
### Reference Documentation
|
|
||||||
For further reference, please consider the following sections:
|
|
||||||
|
|
||||||
* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
|
|
||||||
* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/4.0.0-SNAPSHOT/maven-plugin)
|
|
||||||
* [Create an OCI image](https://docs.spring.io/spring-boot/4.0.0-SNAPSHOT/maven-plugin/build-image.html)
|
|
||||||
* [Spring Web](https://docs.spring.io/spring-boot/4.0.0-SNAPSHOT/reference/web/servlet.html)
|
|
||||||
* [Spring Data JPA](https://docs.spring.io/spring-boot/4.0.0-SNAPSHOT/reference/data/sql.html#data.sql.jpa-and-spring-data)
|
|
||||||
* [Validation](https://docs.spring.io/spring-boot/4.0.0-SNAPSHOT/reference/io/validation.html)
|
|
||||||
* [Spring Boot DevTools](https://docs.spring.io/spring-boot/4.0.0-SNAPSHOT/reference/using/devtools.html)
|
|
||||||
* [Spring Boot Actuator](https://docs.spring.io/spring-boot/4.0.0-SNAPSHOT/reference/actuator/index.html)
|
|
||||||
|
|
||||||
### Guides
|
|
||||||
The following guides illustrate how to use some features concretely:
|
|
||||||
|
|
||||||
* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
|
|
||||||
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
|
|
||||||
* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/)
|
|
||||||
* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)
|
|
||||||
* [Validation](https://spring.io/guides/gs/validating-form-input/)
|
|
||||||
* [Building a RESTful Web Service with Spring Boot Actuator](https://spring.io/guides/gs/actuator-service/)
|
|
||||||
|
|
||||||
### Maven Parent overrides
|
|
||||||
|
|
||||||
Due to Maven's design, elements are inherited from the parent POM to the project POM.
|
|
||||||
While most of the inheritance is fine, it also inherits unwanted elements like `<license>` and `<developers>` from the parent.
|
|
||||||
To prevent this, the project POM contains empty overrides for these elements.
|
|
||||||
If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides.
|
|
||||||
|
|
||||||
77
Jenkinsfile
vendored
Normal file
77
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
REGISTRY_URL = "registry.pablotj.com"
|
||||||
|
USER = "andromeda"
|
||||||
|
PASS = credentials('docker-registry-password')
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Prepare Workspace & Checkout') {
|
||||||
|
steps {
|
||||||
|
echo "Cleaning workspace"
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
echo "Checking out repo..."
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Load Environment') {
|
||||||
|
steps {
|
||||||
|
echo "Loading .env secret from Jenkins..."
|
||||||
|
withCredentials([file(credentialsId: 'env', variable: 'SECRET_ENV')]) {
|
||||||
|
sh 'cp $SECRET_ENV .env'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build and Tag Docker Image') {
|
||||||
|
steps {
|
||||||
|
echo "Building Docker image..."
|
||||||
|
sh '''
|
||||||
|
make build
|
||||||
|
make tag
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('ush Docker Image') {
|
||||||
|
steps {
|
||||||
|
echo "Tagging and pushing Docker image to registry..."
|
||||||
|
sh '''
|
||||||
|
echo $PASS | docker login ${REGISTRY_URL} -u ${USER} --password-stdin
|
||||||
|
make push
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy Docker Container') {
|
||||||
|
steps {
|
||||||
|
echo "Stopping old container and running new container..."
|
||||||
|
sh '''
|
||||||
|
make stop
|
||||||
|
make run
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
success {
|
||||||
|
echo "✅ Deployment completed successfully!"
|
||||||
|
}
|
||||||
|
failure {
|
||||||
|
echo "❌ Pipeline failed!"
|
||||||
|
}
|
||||||
|
always {
|
||||||
|
deleteDir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Makefile
Normal file
37
Makefile
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
STACK=pablotj-portfolio
|
||||||
|
APP_NAME=pablotj-portfolio-api
|
||||||
|
IMAGE_NAME=$(APP_NAME)
|
||||||
|
|
||||||
|
REGISTRY_URL=registry.pablotj.com
|
||||||
|
NAMESPACE=andromeda
|
||||||
|
TAG?=latest
|
||||||
|
|
||||||
|
HOST_PORT=8181
|
||||||
|
CONTAINER_PORT=8080
|
||||||
|
|
||||||
|
IMAGE_FULL=$(REGISTRY_URL)/$(NAMESPACE)/$(IMAGE_NAME):$(TAG)
|
||||||
|
|
||||||
|
build:
|
||||||
|
docker build -t $(IMAGE_NAME):$(TAG) .
|
||||||
|
|
||||||
|
tag:
|
||||||
|
docker tag $(IMAGE_NAME):$(TAG) $(IMAGE_FULL)
|
||||||
|
|
||||||
|
push:
|
||||||
|
docker push $(IMAGE_FULL)
|
||||||
|
|
||||||
|
run:
|
||||||
|
docker run -d \
|
||||||
|
--name $(APP_NAME) \
|
||||||
|
--label com.docker.compose.service="$(APP_NAME)" \
|
||||||
|
--label com.docker.compose.project="$(STACK)" \
|
||||||
|
--network andromeda \
|
||||||
|
-p $(HOST_PORT):$(CONTAINER_PORT) \
|
||||||
|
--env-file .env \
|
||||||
|
$(IMAGE_FULL)
|
||||||
|
|
||||||
|
stop:
|
||||||
|
docker stop $(APP_NAME) || true
|
||||||
|
docker rm $(APP_NAME) || true
|
||||||
|
|
||||||
|
deploy: build tag push stop run
|
||||||
@@ -12,9 +12,17 @@
|
|||||||
<artifactId>domain</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.validation</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>jakarta.validation-api</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.36</version>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.pablotj.portfolio.application.certification;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.certification.Certification;
|
||||||
|
import com.pablotj.portfolio.domain.certification.CertificationId;
|
||||||
|
import com.pablotj.portfolio.domain.certification.port.CertificationRepositoryPort;
|
||||||
|
|
||||||
|
public class CreateCertificationUseCase {
|
||||||
|
|
||||||
|
CertificationRepositoryPort repository;
|
||||||
|
|
||||||
|
public CreateCertificationUseCase(CertificationRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Certification handle(Long profileId, Command cmd) {
|
||||||
|
var certification = Certification.builder()
|
||||||
|
.id(new CertificationId(profileId))
|
||||||
|
.name(cmd.name())
|
||||||
|
.issuer(cmd.issuer())
|
||||||
|
.date(cmd.date())
|
||||||
|
.credentialId(cmd.credentialId())
|
||||||
|
.build();
|
||||||
|
return repository.save(certification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Command(
|
||||||
|
String name,
|
||||||
|
String issuer,
|
||||||
|
String date,
|
||||||
|
String credentialId
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.pablotj.portfolio.application.certification;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.certification.Certification;
|
||||||
|
import com.pablotj.portfolio.domain.certification.CertificationId;
|
||||||
|
import com.pablotj.portfolio.domain.certification.port.CertificationRepositoryPort;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class GetCertificationUseCase {
|
||||||
|
|
||||||
|
private final CertificationRepositoryPort repository;
|
||||||
|
|
||||||
|
public GetCertificationUseCase(CertificationRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Certification> byId(Long profileId, Long id) {
|
||||||
|
return repository.findById(new CertificationId(profileId, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Certification> all(Long profileId) {
|
||||||
|
return repository.findAll(profileId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.pablotj.portfolio.application.education;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.education.Education;
|
||||||
|
import com.pablotj.portfolio.domain.education.EducationId;
|
||||||
|
import com.pablotj.portfolio.domain.education.port.EducationRepositoryPort;
|
||||||
|
|
||||||
|
public class CreateEducationUseCase {
|
||||||
|
|
||||||
|
private final EducationRepositoryPort repository;
|
||||||
|
|
||||||
|
public CreateEducationUseCase(EducationRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Education handle(Long profileId, Command cmd) {
|
||||||
|
var education = Education.builder()
|
||||||
|
.id(new EducationId(profileId))
|
||||||
|
.institution(cmd.institution())
|
||||||
|
.degree(cmd.degree())
|
||||||
|
.period(cmd.period())
|
||||||
|
.grade(cmd.grade())
|
||||||
|
.description(cmd.description())
|
||||||
|
.build();
|
||||||
|
return repository.save(education);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Command(
|
||||||
|
String institution,
|
||||||
|
String degree,
|
||||||
|
String period,
|
||||||
|
String grade,
|
||||||
|
String description
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.pablotj.portfolio.application.education;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.education.Education;
|
||||||
|
import com.pablotj.portfolio.domain.education.EducationId;
|
||||||
|
import com.pablotj.portfolio.domain.education.port.EducationRepositoryPort;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class GetEducationUseCase {
|
||||||
|
|
||||||
|
private final EducationRepositoryPort repository;
|
||||||
|
|
||||||
|
public GetEducationUseCase(EducationRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Education> byId(Long profileId, Long id) {
|
||||||
|
return repository.findById(new EducationId(profileId, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Education> all(Long profileId) {
|
||||||
|
return repository.findAll(profileId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.pablotj.portfolio.application.experience;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.experience.Achievement;
|
||||||
|
import com.pablotj.portfolio.domain.experience.Experience;
|
||||||
|
import com.pablotj.portfolio.domain.experience.ExperienceId;
|
||||||
|
import com.pablotj.portfolio.domain.experience.Technology;
|
||||||
|
import com.pablotj.portfolio.domain.experience.port.ExperienceRepositoryPort;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CreateExperienceUseCase {
|
||||||
|
|
||||||
|
private final ExperienceRepositoryPort repository;
|
||||||
|
|
||||||
|
public CreateExperienceUseCase(ExperienceRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Experience handle(Long profileId, Command cmd) {
|
||||||
|
var experience = Experience.builder()
|
||||||
|
.id(new ExperienceId(profileId))
|
||||||
|
.company(cmd.company())
|
||||||
|
.position(cmd.position())
|
||||||
|
.period(cmd.period())
|
||||||
|
.location(cmd.location())
|
||||||
|
.description(cmd.description())
|
||||||
|
.technologies(new ArrayList<>())
|
||||||
|
.achievements(new ArrayList<>())
|
||||||
|
.build();
|
||||||
|
cmd.technologies.forEach(name -> experience.getTechnologies().add(Technology.builder().id(null).name(name).build()));
|
||||||
|
cmd.achievements.forEach(description -> experience.getAchievements().add(Achievement.builder().id(null).description(description).build()));
|
||||||
|
return repository.save(experience);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Command(
|
||||||
|
String position,
|
||||||
|
String company,
|
||||||
|
String period,
|
||||||
|
String location,
|
||||||
|
String description,
|
||||||
|
List<String> technologies,
|
||||||
|
List<String> achievements
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.pablotj.portfolio.application.experience;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.experience.Experience;
|
||||||
|
import com.pablotj.portfolio.domain.experience.ExperienceId;
|
||||||
|
import com.pablotj.portfolio.domain.experience.port.ExperienceRepositoryPort;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class GetExperienceUseCase {
|
||||||
|
|
||||||
|
private final ExperienceRepositoryPort repository;
|
||||||
|
|
||||||
|
public GetExperienceUseCase(ExperienceRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Experience> byId(Long profileId, Long id) {
|
||||||
|
return repository.findById(new ExperienceId(profileId, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Experience> all(Long profileId) {
|
||||||
|
return repository.findAll(profileId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.pablotj.portfolio.application.profile;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.profile.Profile;
|
||||||
|
import com.pablotj.portfolio.domain.profile.ProfileSocialLink;
|
||||||
|
import com.pablotj.portfolio.domain.profile.port.ProfileRepositoryPort;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CreateProfileUseCase {
|
||||||
|
|
||||||
|
private final ProfileRepositoryPort repository;
|
||||||
|
|
||||||
|
public CreateProfileUseCase(ProfileRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Profile handle(Command cmd) {
|
||||||
|
var personalBuilder = Profile.builder()
|
||||||
|
.id(null)
|
||||||
|
.slug(cmd.slug())
|
||||||
|
.name(cmd.name())
|
||||||
|
.title(cmd.title())
|
||||||
|
.subtitle(cmd.subtitle())
|
||||||
|
.email(cmd.email())
|
||||||
|
.phone(cmd.phone())
|
||||||
|
.location(cmd.location())
|
||||||
|
.avatar(cmd.avatar())
|
||||||
|
.bio(cmd.bio());
|
||||||
|
if (cmd.socialLinks != null) {
|
||||||
|
cmd.socialLinks.forEach(l -> personalBuilder.social(ProfileSocialLink.builder().id(null).platform(l.platform()).url(l.url()).build()));
|
||||||
|
}
|
||||||
|
return repository.save(personalBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Command(
|
||||||
|
String slug,
|
||||||
|
String name,
|
||||||
|
String title,
|
||||||
|
String subtitle,
|
||||||
|
String email,
|
||||||
|
String phone,
|
||||||
|
String location,
|
||||||
|
String avatar,
|
||||||
|
String bio,
|
||||||
|
List<Link> socialLinks
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Link(String platform, String url) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.pablotj.portfolio.application.profile;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.profile.Profile;
|
||||||
|
import com.pablotj.portfolio.domain.profile.ProfileId;
|
||||||
|
import com.pablotj.portfolio.domain.profile.port.ProfileRepositoryPort;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class GetProfileUseCase {
|
||||||
|
|
||||||
|
private final ProfileRepositoryPort repository;
|
||||||
|
|
||||||
|
public GetProfileUseCase(ProfileRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Profile> byId(Long id) {
|
||||||
|
return repository.findById(new ProfileId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Profile> bySlug(String slug) {
|
||||||
|
return repository.findBySlug(new ProfileId(slug));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Profile> all() {
|
||||||
|
return repository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,25 +1,44 @@
|
|||||||
package com.pablotj.portfolio.application.project;
|
package com.pablotj.portfolio.application.project;
|
||||||
|
|
||||||
import com.pablotj.portfolio.domain.project.Project;
|
import com.pablotj.portfolio.domain.project.Project;
|
||||||
|
import com.pablotj.portfolio.domain.project.ProjectFeature;
|
||||||
|
import com.pablotj.portfolio.domain.project.ProjectId;
|
||||||
|
import com.pablotj.portfolio.domain.project.ProjectTechnology;
|
||||||
import com.pablotj.portfolio.domain.project.port.ProjectRepositoryPort;
|
import com.pablotj.portfolio.domain.project.port.ProjectRepositoryPort;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class CreateProjectUseCase {
|
public class CreateProjectUseCase {
|
||||||
|
|
||||||
private final ProjectRepositoryPort repository;
|
private final ProjectRepositoryPort repository;
|
||||||
|
|
||||||
public record Command(String title, String description, String url) {}
|
|
||||||
|
|
||||||
public CreateProjectUseCase(ProjectRepositoryPort repository) {
|
public CreateProjectUseCase(ProjectRepositoryPort repository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Project handle(Command cmd) {
|
public Project handle(Long profileId, Command cmd) {
|
||||||
var project = Project.builder()
|
var project = Project.builder()
|
||||||
.id(null)
|
.id(new ProjectId(profileId))
|
||||||
.title(cmd.title())
|
.title(cmd.title())
|
||||||
.description(cmd.description())
|
.description(cmd.description())
|
||||||
.url(cmd.url())
|
.image(cmd.image())
|
||||||
|
.technologies(new ArrayList<>())
|
||||||
|
.features(new ArrayList<>())
|
||||||
|
.demo(cmd.demo())
|
||||||
|
.repository(cmd.repository())
|
||||||
|
|
||||||
.build();
|
.build();
|
||||||
|
cmd.technologies.forEach(name -> project.getTechnologies().add(ProjectTechnology.builder().id(null).name(name).build()));
|
||||||
|
cmd.features.forEach(description -> project.getFeatures().add(ProjectFeature.builder().id(null).name(description).build()));
|
||||||
return repository.save(project);
|
return repository.save(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record Command(String title,
|
||||||
|
String description,
|
||||||
|
String image,
|
||||||
|
List<String> technologies,
|
||||||
|
List<String> features,
|
||||||
|
String demo,
|
||||||
|
String repository) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ package com.pablotj.portfolio.application.project;
|
|||||||
import com.pablotj.portfolio.domain.project.Project;
|
import com.pablotj.portfolio.domain.project.Project;
|
||||||
import com.pablotj.portfolio.domain.project.ProjectId;
|
import com.pablotj.portfolio.domain.project.ProjectId;
|
||||||
import com.pablotj.portfolio.domain.project.port.ProjectRepositoryPort;
|
import com.pablotj.portfolio.domain.project.port.ProjectRepositoryPort;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -15,11 +14,11 @@ public class GetProjectUseCase {
|
|||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Project> byId(Long id) {
|
public Optional<Project> byId(Long profileId, Long id) {
|
||||||
return repository.findById(new ProjectId(id));
|
return repository.findById(new ProjectId(profileId, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Project> all() {
|
public List<Project> all(Long profileId) {
|
||||||
return repository.findAll();
|
return repository.findAll(profileId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.pablotj.portfolio.application.skill;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.skill.Skill;
|
||||||
|
import com.pablotj.portfolio.domain.skill.SkillGroup;
|
||||||
|
import com.pablotj.portfolio.domain.skill.SkillGroupId;
|
||||||
|
import com.pablotj.portfolio.domain.skill.port.SkillRepositoryPort;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CreateSkillUseCase {
|
||||||
|
|
||||||
|
private final SkillRepositoryPort repository;
|
||||||
|
|
||||||
|
public CreateSkillUseCase(SkillRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SkillGroup handle(Long profileId, CommandGroup cmd) {
|
||||||
|
var skillGroup = SkillGroup.builder()
|
||||||
|
.id(new SkillGroupId(profileId))
|
||||||
|
.name(cmd.name())
|
||||||
|
.icon(cmd.icon())
|
||||||
|
.skills(new ArrayList<>())
|
||||||
|
.build();
|
||||||
|
cmd.skills.forEach(s -> skillGroup.getSkills().add(
|
||||||
|
Skill.builder().id(null).name(s.name).level(s.level).years(s.years).build()));
|
||||||
|
return repository.save(skillGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CommandGroup(
|
||||||
|
String name,
|
||||||
|
String icon,
|
||||||
|
List<CommandSkill> skills
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CommandSkill(
|
||||||
|
String name,
|
||||||
|
Integer level,
|
||||||
|
Integer years
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.pablotj.portfolio.application.skill;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.skill.SkillGroup;
|
||||||
|
import com.pablotj.portfolio.domain.skill.SkillGroupId;
|
||||||
|
import com.pablotj.portfolio.domain.skill.port.SkillRepositoryPort;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class GetSkillUseCase {
|
||||||
|
|
||||||
|
private final SkillRepositoryPort repository;
|
||||||
|
|
||||||
|
public GetSkillUseCase(SkillRepositoryPort repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<SkillGroup> byId(Long profileId, Long id) {
|
||||||
|
return repository.findById(new SkillGroupId(profileId, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SkillGroup> all(Long profileId) {
|
||||||
|
return repository.findAll(profileId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
<project>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.pablotj</groupId>
|
<groupId>com.pablotj</groupId>
|
||||||
<artifactId>portfolio-api</artifactId>
|
<artifactId>portfolio-api</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>bootstrap</artifactId>
|
<artifactId>bootstrap</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -14,41 +18,85 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Drivers DB -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
<scope>runtime</scope>
|
<version>${slf4j.version}</version>
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.h2database</groupId>
|
|
||||||
<artifactId>h2</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Swagger/OpenAPI -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springdoc</groupId>
|
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
|
||||||
<version>${springdoc.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Actuator -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Test -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.testcontainers</groupId>
|
|
||||||
<artifactId>postgresql</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-maven-plugin</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>openapi</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>com.pablotj.portfolio.bootstrap.PortfolioApplication</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>jooby-shade</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
|
<filters>
|
||||||
|
<filter>
|
||||||
|
<artifact>*:*</artifact>
|
||||||
|
<excludes>
|
||||||
|
<exclude>META-INF/*.SF</exclude>
|
||||||
|
<exclude>META-INF/*.DSA</exclude>
|
||||||
|
<exclude>META-INF/*.RSA</exclude>
|
||||||
|
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||||
|
<exclude>META-INF/DEPENDENCIES*</exclude>
|
||||||
|
<exclude>META-INF/LICENSE*</exclude>
|
||||||
|
<exclude>META-INF/NOTICE*</exclude>
|
||||||
|
<exclude>META-INF/*.txt</exclude>
|
||||||
|
<exclude>META-INF/*.md</exclude>
|
||||||
|
|
||||||
|
<exclude>META-INF/io.netty.versions.properties</exclude>
|
||||||
|
<exclude>draftv3/schema</exclude>
|
||||||
|
<exclude>draftv4/schema</exclude>
|
||||||
|
<exclude>**/module-info.class</exclude>
|
||||||
|
<exclude>META-INF/versions/**</exclude>
|
||||||
|
|
||||||
|
<exclude>LICENSE*</exclude>
|
||||||
|
<exclude>NOTICE*</exclude>
|
||||||
|
</excludes>
|
||||||
|
</filter>
|
||||||
|
</filters>
|
||||||
|
<transformers>
|
||||||
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||||
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||||
|
<resource>META-INF/ebean.mf</resource>
|
||||||
|
</transformer>
|
||||||
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>com.pablotj.portfolio.bootstrap.PortfolioApplication</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,16 +1,67 @@
|
|||||||
package com.pablotj.portfolio.bootstrap;
|
package com.pablotj.portfolio.bootstrap;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import com.pablotj.portfolio.bootstrap.certification.CertificationApplicationConfig;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import com.pablotj.portfolio.bootstrap.education.EducationApplicationConfig;
|
||||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
import com.pablotj.portfolio.bootstrap.experience.ExperienceApplicationConfig;
|
||||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
import com.pablotj.portfolio.bootstrap.profile.ProfileApplicationConfig;
|
||||||
|
import com.pablotj.portfolio.bootstrap.project.ProjectApplicationConfig;
|
||||||
|
import com.pablotj.portfolio.bootstrap.skill.SkillApplicationConfig;
|
||||||
|
import com.pablotj.portfolio.infrastructure.config.CorsConfig;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.api.ApiErrorController;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.api.ApiRootController;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.certification.CertificationController;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.education.EducationController;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.experience.ExperienceController;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.profile.ProfileController;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.project.ProjectController;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.skill.SkillController;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import io.jooby.OpenAPIModule;
|
||||||
|
import io.jooby.ebean.EbeanModule;
|
||||||
|
import io.jooby.flyway.FlywayModule;
|
||||||
|
import io.jooby.guice.GuiceModule;
|
||||||
|
import io.jooby.hibernate.validator.HibernateValidatorModule;
|
||||||
|
import io.jooby.hikari.HikariModule;
|
||||||
|
import io.jooby.jackson.JacksonModule;
|
||||||
|
import io.jooby.netty.NettyServer;
|
||||||
|
|
||||||
@SpringBootApplication(scanBasePackages = "com.pablotj")
|
public class PortfolioApplication extends Jooby {
|
||||||
@EnableJpaRepositories(basePackages = "com.pablotj.portfolio.infrastructure.persistence.repo")
|
|
||||||
@EntityScan(basePackages = "com.pablotj.portfolio.infrastructure.persistence.entity")
|
|
||||||
public class PortfolioApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
{
|
||||||
SpringApplication.run(PortfolioApplication.class, args);
|
install(new NettyServer());
|
||||||
|
|
||||||
|
install(new JacksonModule());
|
||||||
|
install(new HibernateValidatorModule());
|
||||||
|
install(new HikariModule());
|
||||||
|
install(new FlywayModule());
|
||||||
|
install(new EbeanModule());
|
||||||
|
install(new CorsConfig());
|
||||||
|
|
||||||
|
error(ApiErrorController.getHandler());
|
||||||
|
|
||||||
|
install(new ProfileApplicationConfig());
|
||||||
|
install(new ProjectApplicationConfig());
|
||||||
|
install(new CertificationApplicationConfig());
|
||||||
|
install(new EducationApplicationConfig());
|
||||||
|
install(new ExperienceApplicationConfig());
|
||||||
|
install(new SkillApplicationConfig());
|
||||||
|
|
||||||
|
install(new GuiceModule());
|
||||||
|
|
||||||
|
path("/api", () -> {
|
||||||
|
mvc(ApiRootController.class);
|
||||||
|
mvc(CertificationController.class);
|
||||||
|
mvc(EducationController.class);
|
||||||
|
mvc(ExperienceController.class);
|
||||||
|
mvc(ProfileController.class);
|
||||||
|
mvc(ProjectController.class);
|
||||||
|
mvc(SkillController.class);
|
||||||
|
});
|
||||||
|
install(new OpenAPIModule().swaggerUI("/docs"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(final String[] args) {
|
||||||
|
runApp(args, PortfolioApplication::new);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.pablotj.portfolio.bootstrap.certification;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.application.certification.CreateCertificationUseCase;
|
||||||
|
import com.pablotj.portfolio.application.certification.GetCertificationUseCase;
|
||||||
|
import com.pablotj.portfolio.domain.certification.port.CertificationRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.certification.adapter.CertificationRepositoryAdapter;
|
||||||
|
import io.jooby.Extension;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class CertificationApplicationConfig implements Extension {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void install(@Nonnull Jooby app) {
|
||||||
|
CertificationRepositoryAdapter adapter = new CertificationRepositoryAdapter();
|
||||||
|
|
||||||
|
app.getServices().put(CertificationRepositoryPort.class, adapter);
|
||||||
|
|
||||||
|
app.getServices().put(GetCertificationUseCase.class, new GetCertificationUseCase(adapter));
|
||||||
|
app.getServices().put(CreateCertificationUseCase.class, new CreateCertificationUseCase(adapter));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.pablotj.portfolio.bootstrap.education;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.application.education.CreateEducationUseCase;
|
||||||
|
import com.pablotj.portfolio.application.education.GetEducationUseCase;
|
||||||
|
import com.pablotj.portfolio.domain.education.port.EducationRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.education.adapter.EducationRepositoryAdapter;
|
||||||
|
import io.jooby.Extension;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class EducationApplicationConfig implements Extension {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void install(@Nonnull Jooby app) {
|
||||||
|
EducationRepositoryAdapter adapter = new EducationRepositoryAdapter();
|
||||||
|
|
||||||
|
app.getServices().put(EducationRepositoryPort.class, adapter);
|
||||||
|
|
||||||
|
app.getServices().put(GetEducationUseCase.class, new GetEducationUseCase(adapter));
|
||||||
|
app.getServices().put(CreateEducationUseCase.class, new CreateEducationUseCase(adapter));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.pablotj.portfolio.bootstrap.experience;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.application.experience.CreateExperienceUseCase;
|
||||||
|
import com.pablotj.portfolio.application.experience.GetExperienceUseCase;
|
||||||
|
import com.pablotj.portfolio.domain.experience.port.ExperienceRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.experience.adapter.ExperienceRepositoryAdapter;
|
||||||
|
import io.jooby.Extension;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class ExperienceApplicationConfig implements Extension {
|
||||||
|
@Override
|
||||||
|
public void install(@Nonnull Jooby app) {
|
||||||
|
|
||||||
|
ExperienceRepositoryAdapter adapter = new ExperienceRepositoryAdapter();
|
||||||
|
|
||||||
|
app.getServices().put(ExperienceRepositoryPort.class, adapter);
|
||||||
|
|
||||||
|
app.getServices().put(GetExperienceUseCase.class, new GetExperienceUseCase(adapter));
|
||||||
|
app.getServices().put(CreateExperienceUseCase.class, new CreateExperienceUseCase(adapter));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.pablotj.portfolio.bootstrap.profile;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.application.profile.CreateProfileUseCase;
|
||||||
|
import com.pablotj.portfolio.application.profile.GetProfileUseCase;
|
||||||
|
import com.pablotj.portfolio.domain.profile.port.ProfileRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.adapter.ProfileRepositoryAdapter;
|
||||||
|
import io.jooby.Extension;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class ProfileApplicationConfig implements Extension {
|
||||||
|
@Override
|
||||||
|
public void install(@Nonnull Jooby app) {
|
||||||
|
|
||||||
|
ProfileRepositoryAdapter adapter = new ProfileRepositoryAdapter();
|
||||||
|
|
||||||
|
app.getServices().put(ProfileRepositoryPort.class, adapter);
|
||||||
|
|
||||||
|
app.getServices().put(GetProfileUseCase.class, new GetProfileUseCase(adapter));
|
||||||
|
app.getServices().put(CreateProfileUseCase.class, new CreateProfileUseCase(adapter));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,19 +3,20 @@ package com.pablotj.portfolio.bootstrap.project;
|
|||||||
import com.pablotj.portfolio.application.project.CreateProjectUseCase;
|
import com.pablotj.portfolio.application.project.CreateProjectUseCase;
|
||||||
import com.pablotj.portfolio.application.project.GetProjectUseCase;
|
import com.pablotj.portfolio.application.project.GetProjectUseCase;
|
||||||
import com.pablotj.portfolio.domain.project.port.ProjectRepositoryPort;
|
import com.pablotj.portfolio.domain.project.port.ProjectRepositoryPort;
|
||||||
import org.springframework.context.annotation.Bean;
|
import com.pablotj.portfolio.infrastructure.persistence.project.adapter.ProjectRepositoryAdapter;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import io.jooby.Extension;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
|
||||||
@Configuration
|
public class ProjectApplicationConfig implements Extension {
|
||||||
public class ProjectApplicationConfig {
|
@Override
|
||||||
|
public void install(@Nonnull Jooby app) {
|
||||||
|
|
||||||
@Bean
|
ProjectRepositoryAdapter adapter = new ProjectRepositoryAdapter();
|
||||||
public CreateProjectUseCase createProjectUseCase(ProjectRepositoryPort repo) {
|
|
||||||
return new CreateProjectUseCase(repo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
app.getServices().put(ProjectRepositoryPort.class, adapter);
|
||||||
public GetProjectUseCase getProjectUseCase(ProjectRepositoryPort repo) {
|
|
||||||
return new GetProjectUseCase(repo);
|
app.getServices().put(CreateProjectUseCase.class, new CreateProjectUseCase(adapter));
|
||||||
|
app.getServices().put(GetProjectUseCase.class, new GetProjectUseCase(adapter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.pablotj.portfolio.bootstrap.skill;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.application.skill.CreateSkillUseCase;
|
||||||
|
import com.pablotj.portfolio.application.skill.GetSkillUseCase;
|
||||||
|
import com.pablotj.portfolio.domain.skill.port.SkillRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.skill.adapter.SkillRepositoryAdapter;
|
||||||
|
import io.jooby.Extension;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class SkillApplicationConfig implements Extension {
|
||||||
|
@Override
|
||||||
|
public void install(@Nonnull Jooby app) {
|
||||||
|
|
||||||
|
SkillRepositoryAdapter adapter = new SkillRepositoryAdapter();
|
||||||
|
|
||||||
|
app.getServices().put(SkillRepositoryPort.class, adapter);
|
||||||
|
|
||||||
|
app.getServices().put(GetSkillUseCase.class, new GetSkillUseCase(adapter));
|
||||||
|
app.getServices().put(CreateSkillUseCase.class, new CreateSkillUseCase(adapter));
|
||||||
|
}
|
||||||
|
}
|
||||||
60
bootstrap/src/main/resources/application.conf
Normal file
60
bootstrap/src/main/resources/application.conf
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Configuración de la App (Sustituye a info y app)
|
||||||
|
info {
|
||||||
|
app {
|
||||||
|
version = "0.0.1-SNAPSHOT" # Maven no rellena esto automáticamente en Jooby sin plugins extra, mejor ponlo fijo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app {
|
||||||
|
cors {
|
||||||
|
allowed-origins = ${?APP_ALLOWED_ORIGINS}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configuración del Servidor
|
||||||
|
server {
|
||||||
|
port = 8080
|
||||||
|
# En Jooby, el context-path se define habitualmente en la clase App,
|
||||||
|
# pero puedes usar esta propiedad si la gestionas manualmente.
|
||||||
|
}
|
||||||
|
|
||||||
|
# Base de Datos (Ebean usa estas propiedades automáticamente)
|
||||||
|
db {
|
||||||
|
url = "jdbc:postgresql://"${?DB_HOST}":"${?DB_PORT}"/"${?DB_NAME}
|
||||||
|
user = ${?DB_USER}
|
||||||
|
password = ${?DB_PASSWORD}
|
||||||
|
driverClassName = org.postgresql.Driver
|
||||||
|
|
||||||
|
# HikariCP (Configuración de Pool)
|
||||||
|
hikari {
|
||||||
|
maximumPoolSize = 3
|
||||||
|
minimumIdle = 1
|
||||||
|
idleTimeout = 30000
|
||||||
|
connectionTimeout = 10000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flyway {
|
||||||
|
# Ubicación de tus scripts .sql
|
||||||
|
locations = ["classpath:db/migration"]
|
||||||
|
cleanDisabled = true
|
||||||
|
baselineOnMigrate = true
|
||||||
|
baselineVersion = ${?FLYWAY_BASELINE_VERSION}
|
||||||
|
|
||||||
|
# Si quieres que se ejecute siempre al arrancar
|
||||||
|
run = [migrate]
|
||||||
|
}
|
||||||
|
|
||||||
|
ebean {
|
||||||
|
ddl {
|
||||||
|
generate = false
|
||||||
|
run = false
|
||||||
|
}
|
||||||
|
# Mostrar SQL en consola
|
||||||
|
debug = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Jackson
|
||||||
|
jackson {
|
||||||
|
indent_output = true
|
||||||
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
spring:
|
|
||||||
application:
|
|
||||||
name: portfolio-api
|
|
||||||
|
|
||||||
jpa:
|
|
||||||
hibernate:
|
|
||||||
ddl-auto: update
|
|
||||||
properties:
|
|
||||||
hibernate.transaction.jta.platform: org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform
|
|
||||||
hibernate:
|
|
||||||
format_sql: true
|
|
||||||
show-sql: true
|
|
||||||
|
|
||||||
server:
|
|
||||||
port: 8080
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
spring:
|
|
||||||
config:
|
|
||||||
activate:
|
|
||||||
on-profile: default
|
|
||||||
|
|
||||||
datasource:
|
|
||||||
url: jdbc:h2:mem:portfolio_db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
|
||||||
username: sa
|
|
||||||
password:
|
|
||||||
|
|
||||||
h2:
|
|
||||||
console:
|
|
||||||
enabled: true
|
|
||||||
path: /h2-console
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
spring:
|
|
||||||
config:
|
|
||||||
activate:
|
|
||||||
on-profile: prod
|
|
||||||
|
|
||||||
datasource:
|
|
||||||
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:portfolio}
|
|
||||||
username: ${DB_USER:postgres}
|
|
||||||
password: ${DB_PASSWORD:postgres}
|
|
||||||
driver-class-name: org.postgresql.Driver
|
|
||||||
|
|
||||||
jpa:
|
|
||||||
hibernate:
|
|
||||||
ddl-auto: validate
|
|
||||||
38
docker-compose.yml
Normal file
38
docker-compose.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${DB_NAME}
|
||||||
|
POSTGRES_USER: ${DB_USER}
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
networks:
|
||||||
|
network:
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
api:
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8095:8080"
|
||||||
|
networks:
|
||||||
|
network:
|
||||||
|
environment:
|
||||||
|
DB_NAME: ${DB_NAME}
|
||||||
|
DB_HOST: ${DB_HOST}
|
||||||
|
DB_PORT: ${DB_PORT}
|
||||||
|
DB_USER: ${DB_USER}
|
||||||
|
DB_PASSWORD: ${DB_PASSWORD}
|
||||||
|
APP_ALLOWED_ORIGINS: ${APP_ALLOWED_ORIGINS}
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
network:
|
||||||
|
driver: bridge
|
||||||
@@ -10,11 +10,14 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<version>1.18.36</version>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.validation</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>jakarta.validation-api</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.pablotj.portfolio.domain.certification;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class Certification {
|
||||||
|
private final CertificationId id;
|
||||||
|
private final String name;
|
||||||
|
private final String issuer;
|
||||||
|
private final String date;
|
||||||
|
private final String credentialId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.pablotj.portfolio.domain.certification;
|
||||||
|
|
||||||
|
public record CertificationId(Long profileId, Long certificationId) {
|
||||||
|
|
||||||
|
public CertificationId(Long profileId) {
|
||||||
|
this(profileId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertificationId {
|
||||||
|
if (certificationId != null && certificationId < 0)
|
||||||
|
throw new IllegalArgumentException("CertificationId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.pablotj.portfolio.domain.certification.port;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.certification.Certification;
|
||||||
|
import com.pablotj.portfolio.domain.certification.CertificationId;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface CertificationRepositoryPort {
|
||||||
|
Certification save(Certification p);
|
||||||
|
|
||||||
|
Optional<Certification> findById(CertificationId id);
|
||||||
|
|
||||||
|
List<Certification> findAll(Long profileId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.pablotj.portfolio.domain.education;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class Education {
|
||||||
|
private final EducationId id;
|
||||||
|
private final String institution;
|
||||||
|
private final String degree;
|
||||||
|
private final String period;
|
||||||
|
private final String grade;
|
||||||
|
private final String description;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.pablotj.portfolio.domain.education;
|
||||||
|
|
||||||
|
public record EducationId(Long profileId, Long educationId) {
|
||||||
|
public EducationId(Long profileId) {
|
||||||
|
this(profileId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EducationId {
|
||||||
|
if (educationId != null && educationId < 0) throw new IllegalArgumentException("EducationId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.pablotj.portfolio.domain.education.port;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.education.Education;
|
||||||
|
import com.pablotj.portfolio.domain.education.EducationId;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface EducationRepositoryPort {
|
||||||
|
Education save(Education p);
|
||||||
|
|
||||||
|
Optional<Education> findById(EducationId id);
|
||||||
|
|
||||||
|
List<Education> findAll(Long profileId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.pablotj.portfolio.domain.experience;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class Achievement {
|
||||||
|
private final ArchivementId id;
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.pablotj.portfolio.domain.experience;
|
||||||
|
|
||||||
|
public record ArchivementId(Long value) {
|
||||||
|
public ArchivementId {
|
||||||
|
if (value != null && value < 0) throw new IllegalArgumentException("ArchivementId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.pablotj.portfolio.domain.experience;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class Experience {
|
||||||
|
private final ExperienceId id;
|
||||||
|
private final String company;
|
||||||
|
private final String position;
|
||||||
|
private final String period;
|
||||||
|
private final String location;
|
||||||
|
private final String description;
|
||||||
|
private final List<Technology> technologies;
|
||||||
|
private final List<Achievement> achievements;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.pablotj.portfolio.domain.experience;
|
||||||
|
|
||||||
|
public record ExperienceId(Long profileId, Long experienceId) {
|
||||||
|
|
||||||
|
public ExperienceId(Long profileId) {
|
||||||
|
this(profileId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExperienceId {
|
||||||
|
if (experienceId != null && experienceId < 0)
|
||||||
|
throw new IllegalArgumentException("ProfileSocialLinkId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.pablotj.portfolio.domain.experience;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class Technology {
|
||||||
|
private final TechnologyId id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.pablotj.portfolio.domain.experience;
|
||||||
|
|
||||||
|
public record TechnologyId(Long value) {
|
||||||
|
public TechnologyId {
|
||||||
|
if (value != null && value < 0) throw new IllegalArgumentException("TechnologyId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.pablotj.portfolio.domain.experience.port;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.experience.Experience;
|
||||||
|
import com.pablotj.portfolio.domain.experience.ExperienceId;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface ExperienceRepositoryPort {
|
||||||
|
Experience save(Experience p);
|
||||||
|
|
||||||
|
Optional<Experience> findById(ExperienceId id);
|
||||||
|
|
||||||
|
List<Experience> findAll(Long profileId);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.pablotj.portfolio.domain.profile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Singular;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class Profile {
|
||||||
|
private final ProfileId id;
|
||||||
|
private final String slug;
|
||||||
|
private final String name;
|
||||||
|
private final String title;
|
||||||
|
private final String subtitle;
|
||||||
|
private final String email;
|
||||||
|
private final String phone;
|
||||||
|
private final String location;
|
||||||
|
private final String avatar;
|
||||||
|
private final String bio;
|
||||||
|
@Singular("social")
|
||||||
|
private final List<ProfileSocialLink> social;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.pablotj.portfolio.domain.profile;
|
||||||
|
|
||||||
|
public record ProfileId(Long id, String slug) {
|
||||||
|
|
||||||
|
public ProfileId(Long id) {
|
||||||
|
this(id, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfileId(String slug) {
|
||||||
|
this(null, slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfileId {
|
||||||
|
if (id != null && id < 0) throw new IllegalArgumentException("ProfileId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.pablotj.portfolio.domain.profile;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class ProfileSocialLink {
|
||||||
|
private final ProfileSocialLinkId id;
|
||||||
|
private final String url;
|
||||||
|
private final String platform;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.pablotj.portfolio.domain.profile;
|
||||||
|
|
||||||
|
public record ProfileSocialLinkId(Long value) {
|
||||||
|
public ProfileSocialLinkId {
|
||||||
|
if (value != null && value < 0) throw new IllegalArgumentException("ProfileSocialLinkId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.pablotj.portfolio.domain.profile.port;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.profile.Profile;
|
||||||
|
import com.pablotj.portfolio.domain.profile.ProfileId;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface ProfileRepositoryPort {
|
||||||
|
Profile save(Profile p);
|
||||||
|
|
||||||
|
Optional<Profile> findBySlug(ProfileId id);
|
||||||
|
|
||||||
|
Optional<Profile> findById(ProfileId id);
|
||||||
|
|
||||||
|
List<Profile> findAll();
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.pablotj.portfolio.domain.project;
|
package com.pablotj.portfolio.domain.project;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@@ -9,5 +10,9 @@ public class Project {
|
|||||||
private final ProjectId id;
|
private final ProjectId id;
|
||||||
private final String title;
|
private final String title;
|
||||||
private final String description;
|
private final String description;
|
||||||
private final String url;
|
private final String image;
|
||||||
|
private final List<ProjectTechnology> technologies;
|
||||||
|
private final List<ProjectFeature> features;
|
||||||
|
private final String demo;
|
||||||
|
private final String repository;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.pablotj.portfolio.domain.project;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class ProjectFeature {
|
||||||
|
private final ProjectFeatureId id;
|
||||||
|
private final String name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.pablotj.portfolio.domain.project;
|
||||||
|
|
||||||
|
public record ProjectFeatureId(Long value) {
|
||||||
|
public ProjectFeatureId {
|
||||||
|
if (value != null && value < 0) throw new IllegalArgumentException("ProjectFeatureId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
package com.pablotj.portfolio.domain.project;
|
package com.pablotj.portfolio.domain.project;
|
||||||
|
|
||||||
public record ProjectId(Long value) {
|
public record ProjectId(Long profileId, Long projectId) {
|
||||||
|
|
||||||
|
public ProjectId(Long profileId) {
|
||||||
|
this(profileId, null);
|
||||||
|
}
|
||||||
|
|
||||||
public ProjectId {
|
public ProjectId {
|
||||||
if (value != null && value < 0) throw new IllegalArgumentException("ProjectId must be positive");
|
if (projectId != null && projectId < 0) throw new IllegalArgumentException("ProjectId must be positive");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.pablotj.portfolio.domain.project;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class ProjectTechnology {
|
||||||
|
private final ProjectTechnologyId id;
|
||||||
|
private final String name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.pablotj.portfolio.domain.project;
|
||||||
|
|
||||||
|
public record ProjectTechnologyId(Long value) {
|
||||||
|
public ProjectTechnologyId {
|
||||||
|
if (value != null && value < 0) throw new IllegalArgumentException("ProjectTechnologyId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,13 @@ package com.pablotj.portfolio.domain.project.port;
|
|||||||
|
|
||||||
import com.pablotj.portfolio.domain.project.Project;
|
import com.pablotj.portfolio.domain.project.Project;
|
||||||
import com.pablotj.portfolio.domain.project.ProjectId;
|
import com.pablotj.portfolio.domain.project.ProjectId;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface ProjectRepositoryPort {
|
public interface ProjectRepositoryPort {
|
||||||
Project save(Project p);
|
Project save(Project p);
|
||||||
|
|
||||||
Optional<Project> findById(ProjectId id);
|
Optional<Project> findById(ProjectId id);
|
||||||
List<Project> findAll();
|
|
||||||
|
List<Project> findAll(Long profileId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.pablotj.portfolio.domain.skill;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class Skill {
|
||||||
|
private final SkillId id;
|
||||||
|
private final String name;
|
||||||
|
private final Integer level;
|
||||||
|
private final Integer years;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.pablotj.portfolio.domain.skill;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public class SkillGroup {
|
||||||
|
private final SkillGroupId id;
|
||||||
|
private final String name;
|
||||||
|
private final String icon;
|
||||||
|
private final List<Skill> skills;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.pablotj.portfolio.domain.skill;
|
||||||
|
|
||||||
|
public record SkillGroupId(Long profileId, Long skillGroupId) {
|
||||||
|
|
||||||
|
public SkillGroupId(Long profileId) {
|
||||||
|
this(profileId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SkillGroupId {
|
||||||
|
if (skillGroupId != null && skillGroupId < 0) throw new IllegalArgumentException("SkillId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.pablotj.portfolio.domain.skill;
|
||||||
|
|
||||||
|
public record SkillId(Long value) {
|
||||||
|
public SkillId {
|
||||||
|
if (value != null && value < 0) throw new IllegalArgumentException("SkillId must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.pablotj.portfolio.domain.skill.port;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.skill.SkillGroup;
|
||||||
|
import com.pablotj.portfolio.domain.skill.SkillGroupId;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface SkillRepositoryPort {
|
||||||
|
SkillGroup save(SkillGroup p);
|
||||||
|
|
||||||
|
Optional<SkillGroup> findById(SkillGroupId id);
|
||||||
|
|
||||||
|
List<SkillGroup> findAll(Long profileId);
|
||||||
|
}
|
||||||
@@ -1,49 +1,158 @@
|
|||||||
<project>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>com.pablotj</groupId>
|
<groupId>com.pablotj</groupId>
|
||||||
<artifactId>portfolio-api</artifactId>
|
<artifactId>portfolio-api</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>infrastructure</artifactId>
|
<artifactId>infrastructure</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<ebean.version>15.8.0</ebean.version>
|
||||||
|
<postgresql.version>42.7.2</postgresql.version>
|
||||||
|
<mapstruct.version>1.6.3</mapstruct.version>
|
||||||
|
<lombok.version>1.18.36</lombok.version>
|
||||||
|
<testcontainers.version>1.19.7</testcontainers.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>com.pablotj</groupId>
|
|
||||||
<artifactId>domain</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.pablotj</groupId>
|
<groupId>com.pablotj</groupId>
|
||||||
<artifactId>application</artifactId>
|
<artifactId>application</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Boot -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>io.jooby</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>jooby</artifactId>
|
||||||
</dependency>
|
<version>${jooby.version}</version>
|
||||||
<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>
|
</dependency>
|
||||||
|
|
||||||
<!-- MapStruct -->
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-core</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-slf4j2-impl</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-netty</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-ebean</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-hikari</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-flyway</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>${postgresql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-jackson</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-hibernate-validator</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.validation</groupId>
|
||||||
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.annotation</groupId>
|
||||||
|
<artifactId>jakarta.annotation-api</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-openapi</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-swagger-ui</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jooby</groupId>
|
||||||
|
<artifactId>jooby-guice</artifactId>
|
||||||
|
<version>${jooby.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mapstruct</groupId>
|
<groupId>org.mapstruct</groupId>
|
||||||
<artifactId>mapstruct</artifactId>
|
<artifactId>mapstruct</artifactId>
|
||||||
|
<version>${mapstruct.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Lombok -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<version>${lombok.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>${testcontainers.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.ebean</groupId>
|
||||||
|
<artifactId>ebean-maven-plugin</artifactId>
|
||||||
|
<version>${ebean.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>process-classes</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>enhance</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.config;
|
||||||
|
|
||||||
|
import io.jooby.Extension;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import io.jooby.handler.Cors;
|
||||||
|
import io.jooby.handler.CorsHandler;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CorsConfig implements Extension {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void install(@Nonnull Jooby app) {
|
||||||
|
String allowedOriginsString = app.getConfig().getString("app.cors.allowed-origins");
|
||||||
|
List<String> allowedOrigins = Arrays.asList(allowedOriginsString.split(","));
|
||||||
|
|
||||||
|
Cors cors = new Cors()
|
||||||
|
.setOrigin(allowedOrigins)
|
||||||
|
.setMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"))
|
||||||
|
.setHeaders(Arrays.asList("Content-Type", "Authorization", "X-Requested-With"))
|
||||||
|
.setUseCredentials(true);
|
||||||
|
|
||||||
|
app.use(new CorsHandler(cors));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.config;
|
||||||
|
|
||||||
|
import io.jooby.Extension;
|
||||||
|
import io.jooby.Jooby;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class SecurityConfig implements Extension {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void install(@Nonnull Jooby app) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.certification.adapter;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.certification.Certification;
|
||||||
|
import com.pablotj.portfolio.domain.certification.CertificationId;
|
||||||
|
import com.pablotj.portfolio.domain.certification.port.CertificationRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.certification.entity.CertificationEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.certification.mapper.CertificationEntityMapper;
|
||||||
|
import io.ebean.DB;
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class CertificationRepositoryAdapter implements CertificationRepositoryPort {
|
||||||
|
|
||||||
|
private static final CertificationEntityMapper MAPPER = Mappers.getMapper(CertificationEntityMapper.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certification save(Certification p) {
|
||||||
|
CertificationEntity entity = MAPPER.toEntity(p);
|
||||||
|
DB.save(entity);
|
||||||
|
return MAPPER.toDomain(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Certification> findById(CertificationId id) {
|
||||||
|
CertificationEntity entity = DB.find(CertificationEntity.class)
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", id.profileId())
|
||||||
|
.eq("id", id.certificationId())
|
||||||
|
.findOne();
|
||||||
|
|
||||||
|
return Optional.ofNullable(entity).map(MAPPER::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Certification> findAll(Long profileId) {
|
||||||
|
return DB.find(CertificationEntity.class)
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", profileId)
|
||||||
|
.findList()
|
||||||
|
.stream()
|
||||||
|
.map(MAPPER::toDomain)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.certification.entity;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileEntity;
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "CERTIFICATION")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class CertificationEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "profile_id", nullable = false)
|
||||||
|
private ProfileEntity profile;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String issuer;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String date;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String credentialId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.certification.mapper;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.certification.Certification;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.certification.entity.CertificationEntity;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface CertificationEntityMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
@Mapping(target = "profile.id", source = "id.profileId")
|
||||||
|
CertificationEntity toEntity(Certification domain);
|
||||||
|
|
||||||
|
@Mapping(target = "id", expression = "java(new CertificationId(e.getProfile().getId(), e.getId()))")
|
||||||
|
Certification toDomain(CertificationEntity e);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.education.adapter;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.education.Education;
|
||||||
|
import com.pablotj.portfolio.domain.education.EducationId;
|
||||||
|
import com.pablotj.portfolio.domain.education.port.EducationRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.education.entity.EducationEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.education.mapper.EducationEntityMapper;
|
||||||
|
import io.ebean.DB;
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class EducationRepositoryAdapter implements EducationRepositoryPort {
|
||||||
|
|
||||||
|
private static final EducationEntityMapper MAPPER = Mappers.getMapper(EducationEntityMapper.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Education save(Education p) {
|
||||||
|
EducationEntity entity = MAPPER.toEntity(p);
|
||||||
|
DB.save(entity);
|
||||||
|
return MAPPER.toDomain(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Education> findById(EducationId id) {
|
||||||
|
EducationEntity entity = DB.find(EducationEntity.class)
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", id.profileId())
|
||||||
|
.eq("id", id.educationId())
|
||||||
|
.findOne();
|
||||||
|
|
||||||
|
return Optional.ofNullable(entity).map(MAPPER::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Education> findAll(Long profileId) {
|
||||||
|
return DB.find(EducationEntity.class)
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", profileId)
|
||||||
|
.findList()
|
||||||
|
.stream()
|
||||||
|
.map(MAPPER::toDomain)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.education.entity;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileEntity;
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "EDUCATION")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class EducationEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "profile_id", nullable = false)
|
||||||
|
private ProfileEntity profile;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String institution;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String degree;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String period;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String grade;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "text")
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.education.mapper;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.education.Education;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.education.entity.EducationEntity;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface EducationEntityMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
@Mapping(target = "profile.id", source = "id.profileId")
|
||||||
|
EducationEntity toEntity(Education domain);
|
||||||
|
|
||||||
|
@Mapping(target = "id", expression = "java(new EducationId(e.getProfile().getId(), e.getId()))")
|
||||||
|
Education toDomain(EducationEntity e);
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.experience.adapter;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.experience.Experience;
|
||||||
|
import com.pablotj.portfolio.domain.experience.ExperienceId;
|
||||||
|
import com.pablotj.portfolio.domain.experience.port.ExperienceRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.experience.mapper.ExperienceEntityMapper;
|
||||||
|
import io.ebean.DB;
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ExperienceRepositoryAdapter implements ExperienceRepositoryPort {
|
||||||
|
|
||||||
|
private static final ExperienceEntityMapper MAPPER = Mappers.getMapper(ExperienceEntityMapper.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Experience save(Experience p) {
|
||||||
|
ExperienceEntity entity = MAPPER.toEntity(p);
|
||||||
|
DB.save(entity);
|
||||||
|
return MAPPER.toDomain(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Experience> findById(ExperienceId id) {
|
||||||
|
ExperienceEntity entity = DB.find(ExperienceEntity.class)
|
||||||
|
.fetch("technologies")
|
||||||
|
.fetch("achievements")
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", id.profileId())
|
||||||
|
.eq("id", id.experienceId())
|
||||||
|
.findOne();
|
||||||
|
|
||||||
|
return Optional.ofNullable(entity).map(MAPPER::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Experience> findAll(Long profileId) {
|
||||||
|
return DB.find(ExperienceEntity.class)
|
||||||
|
.fetch("technologies")
|
||||||
|
.fetch("achievements")
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", profileId)
|
||||||
|
.findList()
|
||||||
|
.stream()
|
||||||
|
.map(MAPPER::toDomain)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.experience.entity;
|
||||||
|
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "EXPERIENCE_ACHIEVEMENT")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@Builder
|
||||||
|
public class ExperienceAchievementEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "text")
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.experience.entity;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileEntity;
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "EXPERIENCE")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@Builder
|
||||||
|
public class ExperienceEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "profile_id", nullable = false)
|
||||||
|
private ProfileEntity profile;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String position;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String company;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String period;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String location;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "text")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
// Ebean soporta perfectamente CascadeType.ALL y orphanRemoval
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
@JoinColumn(name = "EXPERIENCE_ID")
|
||||||
|
private List<ExperienceSkillEntity> technologies;
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
@JoinColumn(name = "EXPERIENCE_ID")
|
||||||
|
private List<ExperienceAchievementEntity> achievements;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.experience.entity;
|
||||||
|
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "EXPERIENCE_SKILL")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@Builder
|
||||||
|
public class ExperienceSkillEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.experience.mapper;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.experience.Achievement;
|
||||||
|
import com.pablotj.portfolio.domain.experience.Experience;
|
||||||
|
import com.pablotj.portfolio.domain.experience.Technology;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceAchievementEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceSkillEntity;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface ExperienceEntityMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
@Mapping(target = "profile.id", source = "id.profileId")
|
||||||
|
ExperienceEntity toEntity(Experience domain);
|
||||||
|
|
||||||
|
@Mapping(target = "id", expression = "java(new ExperienceId(entity.getProfile().getId(), entity.getId()))")
|
||||||
|
Experience toDomain(ExperienceEntity entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
ExperienceAchievementEntity toEntity(Achievement entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id.value", source = "id")
|
||||||
|
Achievement toDomain(ExperienceAchievementEntity entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
ExperienceSkillEntity toEntity(Technology entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id.value", source = "id")
|
||||||
|
Technology toDomain(ExperienceSkillEntity entity);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.profile.adapter;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.profile.Profile;
|
||||||
|
import com.pablotj.portfolio.domain.profile.ProfileId;
|
||||||
|
import com.pablotj.portfolio.domain.profile.port.ProfileRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.mapper.ProfileEntityMapper;
|
||||||
|
import io.ebean.DB;
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ProfileRepositoryAdapter implements ProfileRepositoryPort {
|
||||||
|
|
||||||
|
private static final ProfileEntityMapper MAPPER = Mappers.getMapper(ProfileEntityMapper.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Profile save(Profile p) {
|
||||||
|
ProfileEntity entity = MAPPER.toEntity(p);
|
||||||
|
DB.save(entity);
|
||||||
|
|
||||||
|
return MAPPER.toDomain(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Profile> findBySlug(ProfileId id) {
|
||||||
|
ProfileEntity entity = DB.find(ProfileEntity.class)
|
||||||
|
.fetch("social")
|
||||||
|
.where()
|
||||||
|
.eq("slug", id.slug())
|
||||||
|
.findOne();
|
||||||
|
|
||||||
|
return Optional.ofNullable(entity).map(MAPPER::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Profile> findById(ProfileId id) {
|
||||||
|
ProfileEntity entity = DB.find(ProfileEntity.class)
|
||||||
|
.fetch("social")
|
||||||
|
.where()
|
||||||
|
.eq("id", id.id())
|
||||||
|
.findOne();
|
||||||
|
|
||||||
|
return Optional.ofNullable(entity).map(MAPPER::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Profile> findAll() {
|
||||||
|
return DB.find(ProfileEntity.class)
|
||||||
|
.fetch("social")
|
||||||
|
.findList()
|
||||||
|
.stream()
|
||||||
|
.map(MAPPER::toDomain)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.profile.entity;
|
||||||
|
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "profile")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ProfileEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String slug;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String subtitle;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String location;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "text")
|
||||||
|
private String bio;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
private List<ProfileSocialLinkEntity> social = new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.profile.entity;
|
||||||
|
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "profile_social_link")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ProfileSocialLinkEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "profile_id", nullable = false)
|
||||||
|
private ProfileEntity profile;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String platform;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.profile.mapper;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.profile.Profile;
|
||||||
|
import com.pablotj.portfolio.domain.profile.ProfileSocialLink;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileSocialLinkEntity;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface ProfileEntityMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
@Mapping(target = "social", source = "social")
|
||||||
|
ProfileEntity toEntity(Profile domain);
|
||||||
|
|
||||||
|
@Mapping(target = "id.id", source = "id")
|
||||||
|
@Mapping(target = "social", source = "social")
|
||||||
|
Profile toDomain(ProfileEntity e);
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
ProfileSocialLinkEntity toEntitySocial(ProfileSocialLink entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id.value", source = "id")
|
||||||
|
ProfileSocialLink toDomainSocial(ProfileSocialLinkEntity entity);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.project.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.project.entity.ProjectEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.project.mapper.ProjectEntityMapper;
|
||||||
|
import io.ebean.DB;
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ProjectRepositoryAdapter implements ProjectRepositoryPort {
|
||||||
|
|
||||||
|
private static final ProjectEntityMapper MAPPER = Mappers.getMapper(ProjectEntityMapper.class);
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Project save(Project p) {
|
||||||
|
ProjectEntity entity = MAPPER.toEntity(p);
|
||||||
|
DB.save(entity);
|
||||||
|
return MAPPER.toDomain(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Project> findById(ProjectId id) {
|
||||||
|
ProjectEntity entity = DB.find(ProjectEntity.class)
|
||||||
|
.fetch("technologies")
|
||||||
|
.fetch("features")
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", id.profileId())
|
||||||
|
.eq("id", id.projectId())
|
||||||
|
.findOne();
|
||||||
|
|
||||||
|
return Optional.ofNullable(entity).map(MAPPER::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Project> findAll(Long profileId) {
|
||||||
|
return DB.find(ProjectEntity.class)
|
||||||
|
.fetch("technologies")
|
||||||
|
.fetch("features")
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", profileId)
|
||||||
|
.findList()
|
||||||
|
.stream()
|
||||||
|
.map(MAPPER::toDomain)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.project.entity;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileEntity;
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "PROJECT")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ProjectEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "profile_id", nullable = false)
|
||||||
|
private ProfileEntity profile;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "text")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
@JoinColumn(name = "PROJECT_ID")
|
||||||
|
private List<ProjectTechnologyEntity> technologies;
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
@JoinColumn(name = "PROJECT_ID")
|
||||||
|
private List<ProjectFeatureEntity> features;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String demo;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String repository;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.project.entity;
|
||||||
|
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "PROJECT_FEATURE")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ProjectFeatureEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.project.entity;
|
||||||
|
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "PROJECT_FEATURE_TECHNOLOGY")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ProjectTechnologyEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.project.mapper;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.project.Project;
|
||||||
|
import com.pablotj.portfolio.domain.project.ProjectFeature;
|
||||||
|
import com.pablotj.portfolio.domain.project.ProjectTechnology;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.project.entity.ProjectEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.project.entity.ProjectFeatureEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.project.entity.ProjectTechnologyEntity;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface ProjectEntityMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
@Mapping(target = "profile.id", source = "id.profileId")
|
||||||
|
ProjectEntity toEntity(Project domain);
|
||||||
|
|
||||||
|
@Mapping(target = "id", expression = "java(new ProjectId(e.getProfile().getId(), e.getId()))")
|
||||||
|
Project toDomain(ProjectEntity e);
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
ProjectTechnologyEntity toEntity(ProjectTechnology entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id.value", source = "id")
|
||||||
|
ProjectTechnology toDomain(ProjectTechnologyEntity entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
ProjectFeatureEntity toEntity(ProjectFeature entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id.value", source = "id")
|
||||||
|
ProjectFeature toDomain(ProjectFeatureEntity entity);
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
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> {}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.skill.adapter;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.skill.SkillGroup;
|
||||||
|
import com.pablotj.portfolio.domain.skill.SkillGroupId;
|
||||||
|
import com.pablotj.portfolio.domain.skill.port.SkillRepositoryPort;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.skill.entity.SkillGroupEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.skill.mapper.SkillMapper;
|
||||||
|
import io.ebean.DB;
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class SkillRepositoryAdapter implements SkillRepositoryPort {
|
||||||
|
|
||||||
|
private static final SkillMapper MAPPER = Mappers.getMapper(SkillMapper.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SkillGroup save(SkillGroup p) {
|
||||||
|
SkillGroupEntity entity = MAPPER.toEntity(p);
|
||||||
|
DB.save(entity);
|
||||||
|
return MAPPER.toDomain(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<SkillGroup> findById(SkillGroupId id) {
|
||||||
|
SkillGroupEntity entity = DB.find(SkillGroupEntity.class)
|
||||||
|
.fetch("skills") // Carga inmediata de la lista de habilidades
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", id.profileId())
|
||||||
|
.eq("id", id.skillGroupId())
|
||||||
|
.findOne();
|
||||||
|
|
||||||
|
return Optional.ofNullable(entity).map(MAPPER::toDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SkillGroup> findAll(Long profileId) {
|
||||||
|
return DB.find(SkillGroupEntity.class)
|
||||||
|
.fetch("skills")
|
||||||
|
.where()
|
||||||
|
.eq("profile.id", profileId)
|
||||||
|
.findList()
|
||||||
|
.stream()
|
||||||
|
.map(MAPPER::toDomain)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.skill.entity;
|
||||||
|
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "SKILL")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class SkillEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private Integer years;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.skill.entity;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileEntity;
|
||||||
|
import io.ebean.Model;
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "SKILL_GROUP")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class SkillGroupEntity extends Model {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "profile_id", nullable = false)
|
||||||
|
private ProfileEntity profile;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private String icon;
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
@JoinColumn(name = "SKILL_ID")
|
||||||
|
private List<SkillEntity> skills;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.persistence.skill.mapper;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.skill.Skill;
|
||||||
|
import com.pablotj.portfolio.domain.skill.SkillGroup;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.skill.entity.SkillEntity;
|
||||||
|
import com.pablotj.portfolio.infrastructure.persistence.skill.entity.SkillGroupEntity;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface SkillMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
@Mapping(target = "profile.id", source = "id.profileId")
|
||||||
|
SkillGroupEntity toEntity(SkillGroup domain);
|
||||||
|
|
||||||
|
@Mapping(target = "id", expression = "java(new SkillGroupId(entity.getProfile().getId(), entity.getId()))")
|
||||||
|
SkillGroup toDomain(SkillGroupEntity entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id", ignore = true)
|
||||||
|
SkillEntity toEntity(Skill entity);
|
||||||
|
|
||||||
|
@Mapping(target = "id.value", source = "id")
|
||||||
|
Skill toDomain(SkillEntity entity);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.rest.api;
|
||||||
|
|
||||||
|
import io.jooby.ErrorHandler;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ApiErrorController {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ApiErrorController.class);
|
||||||
|
|
||||||
|
public static ErrorHandler getHandler() {
|
||||||
|
return (ctx, cause, statusCode) -> {
|
||||||
|
log.error("Error en la API: {}", cause.getMessage(), cause);
|
||||||
|
|
||||||
|
Map<String, Object> errorAttributes = new LinkedHashMap<>();
|
||||||
|
errorAttributes.put("timestamp", System.currentTimeMillis());
|
||||||
|
errorAttributes.put("status", statusCode.value());
|
||||||
|
errorAttributes.put("error", statusCode.reason());
|
||||||
|
errorAttributes.put("message", cause.getMessage());
|
||||||
|
errorAttributes.put("path", ctx.getRequestPath());
|
||||||
|
|
||||||
|
ctx.setResponseCode(statusCode)
|
||||||
|
.render(errorAttributes);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.rest.api;
|
||||||
|
|
||||||
|
import io.jooby.annotation.GET;
|
||||||
|
import io.jooby.annotation.Path;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.inject.Named;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Path("/")
|
||||||
|
public class ApiRootController {
|
||||||
|
|
||||||
|
private final String appVersion;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ApiRootController(@Named("info.app.version") String appVersion) {
|
||||||
|
this.appVersion = appVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
public Map<String, Object> root() {
|
||||||
|
return Map.of(
|
||||||
|
"api", "Portfolio API",
|
||||||
|
"version", appVersion,
|
||||||
|
"doc", "/v3/api-docs",
|
||||||
|
"swagger", "/swagger-ui",
|
||||||
|
"endpoints", List.of(
|
||||||
|
Map.of("path", "/v1/homes", "description", "Manage home details"),
|
||||||
|
Map.of("path", "/v1/certifications", "description", "Manage certifications"),
|
||||||
|
Map.of("path", "/v1/projects", "description", "Manage projects"),
|
||||||
|
Map.of("path", "/v1/contacts", "description", "Manage contact info"),
|
||||||
|
Map.of("path", "/v1/educations", "description", "Manage education"),
|
||||||
|
Map.of("path", "/v1/experiences", "description", "Manage experience"),
|
||||||
|
Map.of("path", "/v1/skills", "description", "Manage skills"),
|
||||||
|
Map.of("path", "/v1/technologies", "description", "Manage technologies")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.rest.certification;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.application.certification.CreateCertificationUseCase;
|
||||||
|
import com.pablotj.portfolio.application.certification.GetCertificationUseCase;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.certification.dto.CertificationDto;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.certification.dto.CreateCertificationRequest;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.certification.mapper.CertificationRestMapper;
|
||||||
|
import io.jooby.annotation.GET;
|
||||||
|
import io.jooby.annotation.POST;
|
||||||
|
import io.jooby.annotation.Path;
|
||||||
|
import io.jooby.annotation.PathParam;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Path("/v1/profiles/{profileId}/certifications")
|
||||||
|
public class CertificationController {
|
||||||
|
private static final CertificationRestMapper MAPPER = Mappers.getMapper(CertificationRestMapper.class);
|
||||||
|
|
||||||
|
private final CreateCertificationUseCase createUC;
|
||||||
|
private final GetCertificationUseCase getUC;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CertificationController(CreateCertificationUseCase createUC, GetCertificationUseCase getUC) {
|
||||||
|
this.createUC = createUC;
|
||||||
|
this.getUC = getUC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
public List<CertificationDto> all(@PathParam Long profileId) {
|
||||||
|
return getUC.all(profileId).stream()
|
||||||
|
.map(MAPPER::toDto)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET("/{id}")
|
||||||
|
public CertificationDto byId(@PathParam Long profileId, @PathParam Long id) {
|
||||||
|
// En Jooby, si devuelves un Optional vacío, automáticamente lanza un 404
|
||||||
|
return getUC.byId(profileId, id)
|
||||||
|
.map(MAPPER::toDto)
|
||||||
|
.orElseThrow(() -> new io.jooby.exception.NotFoundException("Certification not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
public CertificationDto create(@PathParam Long profileId, @Valid CreateCertificationRequest request) {
|
||||||
|
var cmd = new CreateCertificationUseCase.Command(
|
||||||
|
request.name(),
|
||||||
|
request.issuer(),
|
||||||
|
request.date(),
|
||||||
|
request.credentialId()
|
||||||
|
);
|
||||||
|
|
||||||
|
var created = createUC.handle(profileId, cmd);
|
||||||
|
return MAPPER.toDto(created);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.rest.certification.dto;
|
||||||
|
|
||||||
|
public record CertificationDto(Long id,
|
||||||
|
String name,
|
||||||
|
String issuer,
|
||||||
|
String date,
|
||||||
|
String credentialId) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.rest.certification.dto;
|
||||||
|
|
||||||
|
public record CreateCertificationRequest(
|
||||||
|
String name,
|
||||||
|
String issuer,
|
||||||
|
String date,
|
||||||
|
String credentialId
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.pablotj.portfolio.infrastructure.rest.certification.mapper;
|
||||||
|
|
||||||
|
import com.pablotj.portfolio.domain.certification.Certification;
|
||||||
|
import com.pablotj.portfolio.infrastructure.rest.certification.dto.CertificationDto;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface CertificationRestMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "id", source = "id.certificationId")
|
||||||
|
CertificationDto toDto(Certification domain);
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.pablotj.portfolio.infrastructure.rest.dto;
|
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
|
|
||||||
public record CreateProjectRequest(
|
|
||||||
@NotBlank String title,
|
|
||||||
String description,
|
|
||||||
String url
|
|
||||||
) {}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user