Compare commits

...

3 Commits

58 changed files with 568 additions and 328 deletions

View File

@ -2,6 +2,8 @@ package com.pablotj.portfolio.application.contact;
import com.pablotj.portfolio.domain.contact.Contact; import com.pablotj.portfolio.domain.contact.Contact;
import com.pablotj.portfolio.domain.contact.port.ContactRepositoryPort; import com.pablotj.portfolio.domain.contact.port.ContactRepositoryPort;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public class CreateContactUseCase { public class CreateContactUseCase {
@ -14,13 +16,23 @@ public class CreateContactUseCase {
public Contact handle(Command cmd) { public Contact handle(Command cmd) {
var contact = Contact.builder() var contact = Contact.builder()
.id(null) .id(null)
.title(cmd.title()) .country(cmd.country())
.description(cmd.description()) .city(cmd.city())
.url(cmd.url()) .email(cmd.email())
.phone(cmd.phone())
.linkedin(cmd.linkedin())
.github(cmd.github())
.build(); .build();
return repository.save(contact); return repository.save(contact);
} }
public record Command(String title, String description, String url) { public record Command(
String country,
String city,
String email,
String phone,
String linkedin,
String github
) {
} }
} }

View File

@ -1,7 +1,13 @@
package com.pablotj.portfolio.application.experience; 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.Experience;
import com.pablotj.portfolio.domain.experience.Skill;
import com.pablotj.portfolio.domain.experience.port.ExperienceRepositoryPort; import com.pablotj.portfolio.domain.experience.port.ExperienceRepositoryPort;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class CreateExperienceUseCase { public class CreateExperienceUseCase {
@ -14,13 +20,35 @@ public class CreateExperienceUseCase {
public Experience handle(Command cmd) { public Experience handle(Command cmd) {
var experience = Experience.builder() var experience = Experience.builder()
.id(null) .id(null)
.title(cmd.title()) .position(cmd.position())
.company(cmd.company())
.startDate(cmd.startDate())
.endDate(cmd.endDate())
.city(cmd.city())
.region(cmd.region())
.country(cmd.country())
.remote(cmd.remote())
.description(cmd.description()) .description(cmd.description())
.url(cmd.url()) .skills(new ArrayList<>())
.achievements(new ArrayList<>())
.build(); .build();
cmd.skills.forEach(name -> experience.getSkills().add(Skill.builder().id(null).name(name).build()));
cmd.achievements.forEach(description -> experience.getAchievements().add(Achievement.builder().id(null).description(description).build()));
return repository.save(experience); return repository.save(experience);
} }
public record Command(String title, String description, String url) { public record Command(
String position,
String company,
LocalDate startDate,
LocalDate endDate,
String city,
String region,
String country,
Boolean remote,
String description,
List<String> skills,
List<String> achievements
) {
} }
} }

View File

@ -1,26 +0,0 @@
package com.pablotj.portfolio.application.home;
import com.pablotj.portfolio.domain.home.Home;
import com.pablotj.portfolio.domain.home.port.HomeRepositoryPort;
public class CreateHomeUseCase {
private final HomeRepositoryPort repository;
public CreateHomeUseCase(HomeRepositoryPort repository) {
this.repository = repository;
}
public Home handle(Command cmd) {
var home = Home.builder()
.id(null)
.title(cmd.title())
.description(cmd.description())
.url(cmd.url())
.build();
return repository.save(home);
}
public record Command(String title, String description, String url) {
}
}

View File

@ -1,24 +0,0 @@
package com.pablotj.portfolio.application.home;
import com.pablotj.portfolio.domain.home.Home;
import com.pablotj.portfolio.domain.home.HomeId;
import com.pablotj.portfolio.domain.home.port.HomeRepositoryPort;
import java.util.List;
import java.util.Optional;
public class GetHomeUseCase {
private final HomeRepositoryPort repository;
public GetHomeUseCase(HomeRepositoryPort repository) {
this.repository = repository;
}
public Optional<Home> byId(Long id) {
return repository.findById(new HomeId(id));
}
public List<Home> all() {
return repository.findAll();
}
}

View File

@ -0,0 +1,35 @@
package com.pablotj.portfolio.application.resume;
import com.pablotj.portfolio.domain.resume.Resume;
import com.pablotj.portfolio.domain.resume.port.ResumeRepositoryPort;
import jakarta.validation.constraints.NotBlank;
public class CreateResumeUseCase {
private final ResumeRepositoryPort repository;
public CreateResumeUseCase(ResumeRepositoryPort repository) {
this.repository = repository;
}
public Resume handle(Command cmd) {
var home = Resume.builder()
.id(null)
.name(cmd.title())
.surnames(cmd.surnames())
.title(cmd.title())
.summary(cmd.summary())
.icon(cmd.icon())
.build();
return repository.save(home);
}
public record Command(
String name,
String surnames,
String title,
String summary,
String icon
) {
}
}

View File

@ -0,0 +1,24 @@
package com.pablotj.portfolio.application.resume;
import com.pablotj.portfolio.domain.resume.Resume;
import com.pablotj.portfolio.domain.resume.ResumeId;
import com.pablotj.portfolio.domain.resume.port.ResumeRepositoryPort;
import java.util.List;
import java.util.Optional;
public class GetResumeUseCase {
private final ResumeRepositoryPort repository;
public GetResumeUseCase(ResumeRepositoryPort repository) {
this.repository = repository;
}
public Optional<Resume> byId(Long id) {
return repository.findById(new ResumeId(id));
}
public List<Resume> all() {
return repository.findAll();
}
}

View File

@ -1,21 +0,0 @@
package com.pablotj.portfolio.bootstrap.home;
import com.pablotj.portfolio.application.home.CreateHomeUseCase;
import com.pablotj.portfolio.application.home.GetHomeUseCase;
import com.pablotj.portfolio.domain.home.port.HomeRepositoryPort;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HomeApplicationConfig {
@Bean
public GetHomeUseCase getHomeUseCase(HomeRepositoryPort repo) {
return new GetHomeUseCase(repo);
}
@Bean
public CreateHomeUseCase createHomeUseCase(HomeRepositoryPort repo) {
return new CreateHomeUseCase(repo);
}
}

View File

@ -0,0 +1,21 @@
package com.pablotj.portfolio.bootstrap.resume;
import com.pablotj.portfolio.application.resume.CreateResumeUseCase;
import com.pablotj.portfolio.application.resume.GetResumeUseCase;
import com.pablotj.portfolio.domain.resume.port.ResumeRepositoryPort;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ResumeApplicationConfig {
@Bean
public GetResumeUseCase getHomeUseCase(ResumeRepositoryPort repo) {
return new GetResumeUseCase(repo);
}
@Bean
public CreateResumeUseCase createHomeUseCase(ResumeRepositoryPort repo) {
return new CreateResumeUseCase(repo);
}
}

View File

@ -11,6 +11,7 @@ spring:
properties: properties:
hibernate.transaction.jta.platform: org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform hibernate.transaction.jta.platform: org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform
hibernate: hibernate:
dialect: org.hibernate.dialect.H2Dialect
format_sql: true format_sql: true
show-sql: true show-sql: true
@ -37,7 +38,8 @@ spring:
on-profile: default on-profile: default
datasource: datasource:
url: jdbc:h2:mem:portfolio_db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE url: jdbc:h2:file:./portfolio-db
driver-class-name: org.h2.Driver
username: sa username: sa
password: password:

View File

@ -1,13 +1,19 @@
package com.pablotj.portfolio.domain.contact; package com.pablotj.portfolio.domain.contact;
import jakarta.validation.constraints.Email;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@Builder @Builder
public class Contact { public class Contact {
private final ContactId id; private final ContactId id;
private final String title; private final String country;
private final String description; private final String city;
private final String url; private final String email;
private final String phone;
private final String linkedin;
private final String github;
} }

View File

@ -0,0 +1,12 @@
package com.pablotj.portfolio.domain.experience;
import java.time.LocalDate;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class Achievement {
private final ArchivementId id;
private String description;
}

View File

@ -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");
}
}

View File

@ -1,5 +1,7 @@
package com.pablotj.portfolio.domain.experience; package com.pablotj.portfolio.domain.experience;
import java.time.LocalDate;
import java.util.List;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
@ -7,7 +9,15 @@ import lombok.Getter;
@Builder @Builder
public class Experience { public class Experience {
private final ExperienceId id; private final ExperienceId id;
private final String title; private final String position;
private final String company;
private final LocalDate startDate;
private final LocalDate endDate;
private final String city;
private final String region;
private final String country;
private final Boolean remote;
private final String description; private final String description;
private final String url; private final List<Skill> skills;
private final List<Achievement> achievements;
} }

View File

@ -0,0 +1,12 @@
package com.pablotj.portfolio.domain.experience;
import java.time.LocalDate;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class Skill {
private final SkillId id;
private String name;
}

View File

@ -0,0 +1,7 @@
package com.pablotj.portfolio.domain.experience;
public record SkillId(Long value) {
public SkillId {
if (value != null && value < 0) throw new IllegalArgumentException("SkillId must be positive");
}
}

View File

@ -1,13 +0,0 @@
package com.pablotj.portfolio.domain.home;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class Home {
private final HomeId id;
private final String title;
private final String description;
private final String url;
}

View File

@ -1,7 +0,0 @@
package com.pablotj.portfolio.domain.home;
public record HomeId(Long value) {
public HomeId {
if (value != null && value < 0) throw new IllegalArgumentException("HomeId must be positive");
}
}

View File

@ -1,14 +0,0 @@
package com.pablotj.portfolio.domain.home.port;
import com.pablotj.portfolio.domain.home.Home;
import com.pablotj.portfolio.domain.home.HomeId;
import java.util.List;
import java.util.Optional;
public interface HomeRepositoryPort {
Home save(Home p);
Optional<Home> findById(HomeId id);
List<Home> findAll();
}

View File

@ -0,0 +1,15 @@
package com.pablotj.portfolio.domain.resume;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class Resume {
private final ResumeId id;
private final String name;
private final String surnames;
private final String title;
private final String summary;
private final String icon;
}

View File

@ -0,0 +1,7 @@
package com.pablotj.portfolio.domain.resume;
public record ResumeId(Long value) {
public ResumeId {
if (value != null && value < 0) throw new IllegalArgumentException("ResumeId must be positive");
}
}

View File

@ -0,0 +1,14 @@
package com.pablotj.portfolio.domain.resume.port;
import com.pablotj.portfolio.domain.resume.Resume;
import com.pablotj.portfolio.domain.resume.ResumeId;
import java.util.List;
import java.util.Optional;
public interface ResumeRepositoryPort {
Resume save(Resume p);
Optional<Resume> findById(ResumeId id);
List<Resume> findAll();
}

View File

@ -12,13 +12,6 @@ public interface AboutJpaMapper {
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
AboutJpaEntity toEntity(About domain); AboutJpaEntity toEntity(About domain);
default About toDomain(AboutJpaEntity e) { @Mapping(target = "id.value", source = "id")
if (e == null) return null; About toDomain(AboutJpaEntity e);
return About.builder()
.id(e.getId() == null ? null : new AboutId(e.getId()))
.title(e.getTitle())
.description(e.getDescription())
.url(e.getUrl())
.build();
}
} }

View File

@ -12,13 +12,6 @@ public interface CertificationJpaMapper {
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
CertificationJpaEntity toEntity(Certification domain); CertificationJpaEntity toEntity(Certification domain);
default Certification toDomain(CertificationJpaEntity e) { @Mapping(target = "id.value", source = "id")
if (e == null) return null; Certification toDomain(CertificationJpaEntity e);
return Certification.builder()
.id(e.getId() == null ? null : new CertificationId(e.getId()))
.title(e.getTitle())
.description(e.getDescription())
.url(e.getUrl())
.build();
}
} }

View File

@ -6,24 +6,38 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Email;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
@Entity @Entity
@Table(name = "contacts") @Table(name = "contacts")
@Getter @Getter
@Setter @NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class ContactJpaEntity { public class ContactJpaEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
private String country;
private String city;
@Column(nullable = false) @Column(nullable = false)
private String title; @Email
private String email;
@Column(columnDefinition = "text") private String phone;
private String description;
private String url; @Column(name = "linkedin_url")
private String linkedin;
@Column(name = "github_url")
private String github;
} }

View File

@ -12,13 +12,6 @@ public interface ContactJpaMapper {
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
ContactJpaEntity toEntity(Contact domain); ContactJpaEntity toEntity(Contact domain);
default Contact toDomain(ContactJpaEntity e) { @Mapping(target = "id.value", source = "id")
if (e == null) return null; Contact toDomain(ContactJpaEntity e);
return Contact.builder()
.id(e.getId() == null ? null : new ContactId(e.getId()))
.title(e.getTitle())
.description(e.getDescription())
.url(e.getUrl())
.build();
}
} }

View File

@ -12,13 +12,6 @@ public interface EducationJpaMapper {
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
EducationJpaEntity toEntity(Education domain); EducationJpaEntity toEntity(Education domain);
default Education toDomain(EducationJpaEntity e) { @Mapping(target = "id.value", source = "id")
if (e == null) return null; Education toDomain(EducationJpaEntity e);
return Education.builder()
.id(e.getId() == null ? null : new EducationId(e.getId()))
.title(e.getTitle())
.description(e.getDescription())
.url(e.getUrl())
.build();
}
} }

View File

@ -0,0 +1,21 @@
package com.pablotj.portfolio.infrastructure.persistence.experience.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "experience_achievements")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class ExperienceAchievementJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(columnDefinition = "text")
private String description;
}

View File

@ -1,29 +1,55 @@
package com.pablotj.portfolio.infrastructure.persistence.experience.entity; package com.pablotj.portfolio.infrastructure.persistence.experience.entity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.time.LocalDate;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
@Entity @Entity
@Table(name = "experiences") @Table(name = "experiences")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class ExperienceJpaEntity { public class ExperienceJpaEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(nullable = false) private String position;
private String title; private String company;
private LocalDate startDate;
private LocalDate endDate;
private String city;
private String region;
private String country;
private Boolean remote;
@Column(columnDefinition = "text") @Column(columnDefinition = "text")
private String description; private String description;
private String url; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "experience_id")
private List<ExperienceSkillJpaEntity> skills;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "experience_id")
private List<ExperienceAchievementJpaEntity> achievements;
} }

View File

@ -0,0 +1,20 @@
package com.pablotj.portfolio.infrastructure.persistence.experience.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "experience_skills")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class ExperienceSkillJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}

View File

@ -1,8 +1,11 @@
package com.pablotj.portfolio.infrastructure.persistence.experience.mapper; 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.Experience;
import com.pablotj.portfolio.domain.experience.ExperienceId; import com.pablotj.portfolio.domain.experience.Skill;
import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceAchievementJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceJpaEntity; import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceSkillJpaEntity;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
@ -12,13 +15,21 @@ public interface ExperienceJpaMapper {
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
ExperienceJpaEntity toEntity(Experience domain); ExperienceJpaEntity toEntity(Experience domain);
default Experience toDomain(ExperienceJpaEntity e) { @Mapping(target = "id.value", source = "id")
if (e == null) return null; Experience toDomain(ExperienceJpaEntity entity);
return Experience.builder()
.id(e.getId() == null ? null : new ExperienceId(e.getId()))
.title(e.getTitle()) @Mapping(target = "id", ignore = true)
.description(e.getDescription()) ExperienceAchievementJpaEntity toEntity(Achievement entity);
.url(e.getUrl())
.build(); @Mapping(target = "id.value", source = "id")
} Achievement toDomain(ExperienceAchievementJpaEntity entity);
@Mapping(target = "id", ignore = true)
ExperienceSkillJpaEntity toEntity(Skill entity);
@Mapping(target = "id.value", source = "id")
Skill toDomain(ExperienceSkillJpaEntity entity);
} }

View File

@ -1,40 +0,0 @@
package com.pablotj.portfolio.infrastructure.persistence.home.adapter;
import com.pablotj.portfolio.domain.home.Home;
import com.pablotj.portfolio.domain.home.HomeId;
import com.pablotj.portfolio.domain.home.port.HomeRepositoryPort;
import com.pablotj.portfolio.infrastructure.persistence.home.entity.HomeJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.home.mapper.HomeJpaMapper;
import com.pablotj.portfolio.infrastructure.persistence.home.repo.SpringDataHomeRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class HomeRepositoryAdapter implements HomeRepositoryPort {
private final SpringDataHomeRepository repo;
private final HomeJpaMapper mapper;
public HomeRepositoryAdapter(SpringDataHomeRepository repo, HomeJpaMapper mapper) {
this.repo = repo;
this.mapper = mapper;
}
@Override
public Home save(Home p) {
HomeJpaEntity entity = mapper.toEntity(p);
HomeJpaEntity saved = repo.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<Home> findById(HomeId id) {
return repo.findById(id.value()).map(mapper::toDomain);
}
@Override
public List<Home> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
}
}

View File

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

View File

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

View File

@ -12,13 +12,6 @@ public interface ProjectJpaMapper {
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
ProjectJpaEntity toEntity(Project domain); ProjectJpaEntity toEntity(Project domain);
default Project toDomain(ProjectJpaEntity e) { @Mapping(target = "id.value", source = "id")
if (e == null) return null; Project toDomain(ProjectJpaEntity e);
return Project.builder()
.id(e.getId() == null ? null : new ProjectId(e.getId()))
.title(e.getTitle())
.description(e.getDescription())
.url(e.getUrl())
.build();
}
} }

View File

@ -0,0 +1,40 @@
package com.pablotj.portfolio.infrastructure.persistence.resume.adapter;
import com.pablotj.portfolio.domain.resume.Resume;
import com.pablotj.portfolio.domain.resume.ResumeId;
import com.pablotj.portfolio.domain.resume.port.ResumeRepositoryPort;
import com.pablotj.portfolio.infrastructure.persistence.resume.entity.ResumeJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.resume.mapper.ResumeJpaMapper;
import com.pablotj.portfolio.infrastructure.persistence.resume.repo.SpringDataResumeRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class ResumeRepositoryAdapter implements ResumeRepositoryPort {
private final SpringDataResumeRepository repo;
private final ResumeJpaMapper mapper;
public ResumeRepositoryAdapter(SpringDataResumeRepository repo, ResumeJpaMapper mapper) {
this.repo = repo;
this.mapper = mapper;
}
@Override
public Resume save(Resume p) {
ResumeJpaEntity entity = mapper.toEntity(p);
ResumeJpaEntity saved = repo.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<Resume> findById(ResumeId id) {
return repo.findById(id.value()).map(mapper::toDomain);
}
@Override
public List<Resume> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
}
}

View File

@ -1,4 +1,4 @@
package com.pablotj.portfolio.infrastructure.persistence.home.entity; package com.pablotj.portfolio.infrastructure.persistence.resume.entity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
@ -10,20 +10,28 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Entity @Entity
@Table(name = "homes") @Table(name = "resumes")
@Getter @Getter
@Setter @Setter
public class HomeJpaEntity { public class ResumeJpaEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(name = "first_name", nullable = false)
private String name;
@Column(name = "last_name", nullable = false)
private String surnames;
@Column(nullable = false) @Column(nullable = false)
private String title; private String title;
@Column(columnDefinition = "text") @Column(columnDefinition = "text", nullable = false)
private String description; private String summary;
@Column
private String icon;
private String url;
} }

View File

@ -0,0 +1,17 @@
package com.pablotj.portfolio.infrastructure.persistence.resume.mapper;
import com.pablotj.portfolio.domain.resume.Resume;
import com.pablotj.portfolio.domain.resume.ResumeId;
import com.pablotj.portfolio.infrastructure.persistence.resume.entity.ResumeJpaEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface ResumeJpaMapper {
@Mapping(target = "id", ignore = true)
ResumeJpaEntity toEntity(Resume domain);
@Mapping(target = "id.value", source = "id")
Resume toDomain(ResumeJpaEntity e);
}

View File

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

View File

@ -1,4 +1,4 @@
package com.pablotj.portfolio.infrastructure.rest.about.controller; package com.pablotj.portfolio.infrastructure.rest.about;
import com.pablotj.portfolio.application.about.CreateAboutUseCase; import com.pablotj.portfolio.application.about.CreateAboutUseCase;
import com.pablotj.portfolio.application.about.GetAboutUseCase; import com.pablotj.portfolio.application.about.GetAboutUseCase;

View File

@ -1,4 +1,4 @@
package com.pablotj.portfolio.infrastructure.rest.certification.controller; package com.pablotj.portfolio.infrastructure.rest.certification;
import com.pablotj.portfolio.application.certification.CreateCertificationUseCase; import com.pablotj.portfolio.application.certification.CreateCertificationUseCase;
import com.pablotj.portfolio.application.certification.GetCertificationUseCase; import com.pablotj.portfolio.application.certification.GetCertificationUseCase;

View File

@ -1,4 +1,4 @@
package com.pablotj.portfolio.infrastructure.rest.contact.controller; package com.pablotj.portfolio.infrastructure.rest.contact;
import com.pablotj.portfolio.application.contact.CreateContactUseCase; import com.pablotj.portfolio.application.contact.CreateContactUseCase;
import com.pablotj.portfolio.application.contact.GetContactUseCase; import com.pablotj.portfolio.application.contact.GetContactUseCase;
@ -46,9 +46,12 @@ public class ContactController {
@PostMapping @PostMapping
public ResponseEntity<ContactDto> create(@Valid @RequestBody CreateContactRequest request) { public ResponseEntity<ContactDto> create(@Valid @RequestBody CreateContactRequest request) {
var cmd = new CreateContactUseCase.Command( var cmd = new CreateContactUseCase.Command(
request.title(), request.country(),
request.description(), request.city(),
request.url() request.email(),
request.phone(),
request.linkedin(),
request.github()
); );
var created = createUC.handle(cmd); var created = createUC.handle(cmd);
var body = mapper.toDto(created); var body = mapper.toDto(created);

View File

@ -1,6 +1,15 @@
package com.pablotj.portfolio.infrastructure.rest.contact.dto; package com.pablotj.portfolio.infrastructure.rest.contact.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
public record ContactDto(Long id, @NotBlank String title, String description, String url) { public record ContactDto(
Long id,
String country,
String city,
@NotBlank @Email String email,
String phone,
String linkedin,
String github
) {
} }

View File

@ -1,10 +1,14 @@
package com.pablotj.portfolio.infrastructure.rest.contact.dto; package com.pablotj.portfolio.infrastructure.rest.contact.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
public record CreateContactRequest( public record CreateContactRequest(
@NotBlank String title, String country,
String description, String city,
String url @NotBlank @Email String email,
String phone,
String linkedin,
String github
) { ) {
} }

View File

@ -1,4 +1,4 @@
package com.pablotj.portfolio.infrastructure.rest.education.controller; package com.pablotj.portfolio.infrastructure.rest.education;
import com.pablotj.portfolio.application.education.CreateEducationUseCase; import com.pablotj.portfolio.application.education.CreateEducationUseCase;
import com.pablotj.portfolio.application.education.GetEducationUseCase; import com.pablotj.portfolio.application.education.GetEducationUseCase;

View File

@ -1,4 +1,4 @@
package com.pablotj.portfolio.infrastructure.rest.experience.controller; package com.pablotj.portfolio.infrastructure.rest.experience;
import com.pablotj.portfolio.application.experience.CreateExperienceUseCase; import com.pablotj.portfolio.application.experience.CreateExperienceUseCase;
import com.pablotj.portfolio.application.experience.GetExperienceUseCase; import com.pablotj.portfolio.application.experience.GetExperienceUseCase;
@ -46,9 +46,17 @@ public class ExperienceController {
@PostMapping @PostMapping
public ResponseEntity<ExperienceDto> create(@Valid @RequestBody CreateExperienceRequest request) { public ResponseEntity<ExperienceDto> create(@Valid @RequestBody CreateExperienceRequest request) {
var cmd = new CreateExperienceUseCase.Command( var cmd = new CreateExperienceUseCase.Command(
request.title(), request.position(),
request.company(),
request.startDate(),
request.endDate(),
request.city(),
request.region(),
request.country(),
request.remote(),
request.description(), request.description(),
request.url() request.skills(),
request.achievements()
); );
var created = createUC.handle(cmd); var created = createUC.handle(cmd);
var body = mapper.toDto(created); var body = mapper.toDto(created);

View File

@ -1,10 +1,19 @@
package com.pablotj.portfolio.infrastructure.rest.experience.dto; package com.pablotj.portfolio.infrastructure.rest.experience.dto;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.time.LocalDate;
import java.util.List;
public record CreateExperienceRequest( public record CreateExperienceRequest(
@NotBlank String title, @NotBlank String position,
String company,
LocalDate startDate,
LocalDate endDate,
String city,
String region,
String country,
Boolean remote,
String description, String description,
String url List<String> skills,
) { List<String> achievements
} ) {}

View File

@ -1,6 +1,20 @@
package com.pablotj.portfolio.infrastructure.rest.experience.dto; package com.pablotj.portfolio.infrastructure.rest.experience.dto;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.time.LocalDate;
import java.util.List;
public record ExperienceDto(Long id, @NotBlank String title, String description, String url) { public record ExperienceDto(
} Long id,
@NotBlank String position,
String company,
LocalDate startDate,
LocalDate endDate,
String city,
String region,
String country,
Boolean remote,
String description,
List<String> skills,
List<String> achievements
) {}

View File

@ -1,7 +1,10 @@
package com.pablotj.portfolio.infrastructure.rest.experience.mapper; package com.pablotj.portfolio.infrastructure.rest.experience.mapper;
import com.pablotj.portfolio.domain.experience.Achievement;
import com.pablotj.portfolio.domain.experience.Experience; import com.pablotj.portfolio.domain.experience.Experience;
import com.pablotj.portfolio.domain.experience.Skill;
import com.pablotj.portfolio.infrastructure.rest.experience.dto.ExperienceDto; import com.pablotj.portfolio.infrastructure.rest.experience.dto.ExperienceDto;
import java.util.List;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
@ -9,5 +12,19 @@ import org.mapstruct.Mapping;
public interface ExperienceRestMapper { public interface ExperienceRestMapper {
@Mapping(target = "id", source = "id.value") @Mapping(target = "id", source = "id.value")
@Mapping(target = "skills", source = "skills")
@Mapping(target = "achievements", source = "achievements")
ExperienceDto toDto(Experience domain); ExperienceDto toDto(Experience domain);
default List<String> mapSkills(List<Skill> skills) {
return skills == null ? List.of() : skills.stream()
.map(Skill::getName)
.toList();
}
default List<String> mapAchievements(List<Achievement> achievements) {
return achievements == null ? List.of() : achievements.stream()
.map(Achievement::getDescription)
.toList();
}
} }

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package com.pablotj.portfolio.infrastructure.rest.project.controller; package com.pablotj.portfolio.infrastructure.rest.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;

View File

@ -1,10 +1,10 @@
package com.pablotj.portfolio.infrastructure.rest.home.controller; package com.pablotj.portfolio.infrastructure.rest.resume;
import com.pablotj.portfolio.application.home.CreateHomeUseCase; import com.pablotj.portfolio.application.resume.CreateResumeUseCase;
import com.pablotj.portfolio.application.home.GetHomeUseCase; import com.pablotj.portfolio.application.resume.GetResumeUseCase;
import com.pablotj.portfolio.infrastructure.rest.home.dto.CreateHomeRequest; import com.pablotj.portfolio.infrastructure.rest.resume.dto.ResumeCreateRequest;
import com.pablotj.portfolio.infrastructure.rest.home.dto.HomeDto; import com.pablotj.portfolio.infrastructure.rest.resume.dto.ResumeDto;
import com.pablotj.portfolio.infrastructure.rest.home.mapper.HomeRestMapper; import com.pablotj.portfolio.infrastructure.rest.resume.mapper.ResumeRestMapper;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
@ -18,25 +18,25 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/v1/homes") @RequestMapping("/v1/homes")
public class HomeController { public class ResumeController {
private final CreateHomeUseCase createUC; private final CreateResumeUseCase createUC;
private final GetHomeUseCase getUC; private final GetResumeUseCase getUC;
private final HomeRestMapper mapper; private final ResumeRestMapper mapper;
public HomeController(CreateHomeUseCase createUC, GetHomeUseCase getUC, HomeRestMapper mapper) { public ResumeController(CreateResumeUseCase createUC, GetResumeUseCase getUC, ResumeRestMapper mapper) {
this.createUC = createUC; this.createUC = createUC;
this.getUC = getUC; this.getUC = getUC;
this.mapper = mapper; this.mapper = mapper;
} }
@GetMapping @GetMapping
public List<HomeDto> all() { public List<ResumeDto> all() {
return getUC.all().stream().map(mapper::toDto).toList(); return getUC.all().stream().map(mapper::toDto).toList();
} }
@GetMapping("/{id}") @GetMapping("/{id}")
public ResponseEntity<HomeDto> byId(@PathVariable Long id) { public ResponseEntity<ResumeDto> byId(@PathVariable Long id) {
return getUC.byId(id) return getUC.byId(id)
.map(mapper::toDto) .map(mapper::toDto)
.map(ResponseEntity::ok) .map(ResponseEntity::ok)
@ -44,11 +44,13 @@ public class HomeController {
} }
@PostMapping @PostMapping
public ResponseEntity<HomeDto> create(@Valid @RequestBody CreateHomeRequest request) { public ResponseEntity<ResumeDto> create(@Valid @RequestBody ResumeCreateRequest request) {
var cmd = new CreateHomeUseCase.Command( var cmd = new CreateResumeUseCase.Command(
request.name(),
request.surnames(),
request.title(), request.title(),
request.description(), request.summary(),
request.url() request.icon()
); );
var created = createUC.handle(cmd); var created = createUC.handle(cmd);
var body = mapper.toDto(created); var body = mapper.toDto(created);

View File

@ -0,0 +1,12 @@
package com.pablotj.portfolio.infrastructure.rest.resume.dto;
import jakarta.validation.constraints.NotBlank;
public record ResumeCreateRequest(
@NotBlank String name,
@NotBlank String surnames,
@NotBlank String title,
@NotBlank String summary,
String icon
) {
}

View File

@ -0,0 +1,13 @@
package com.pablotj.portfolio.infrastructure.rest.resume.dto;
import jakarta.validation.constraints.NotBlank;
public record ResumeDto(
Long id,
@NotBlank String name,
@NotBlank String surnames,
@NotBlank String title,
@NotBlank String summary,
String icon
) {
}

View File

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

View File

@ -1,4 +1,4 @@
package com.pablotj.portfolio.infrastructure.rest.skill.controller; package com.pablotj.portfolio.infrastructure.rest.skill;
import com.pablotj.portfolio.application.skill.CreateSkillUseCase; import com.pablotj.portfolio.application.skill.CreateSkillUseCase;
import com.pablotj.portfolio.application.skill.GetSkillUseCase; import com.pablotj.portfolio.application.skill.GetSkillUseCase;

4
mvnw vendored
View File

@ -23,7 +23,7 @@
# #
# Optional ENV vars # Optional ENV vars
# ----------------- # -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source # JAVA_HOME - location of a JDK resume dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution # MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
@ -134,7 +134,7 @@ maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
esac esac
# apply MVNW_REPOURL and calculate MAVEN_HOME # apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash> # maven resume pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}" distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}" distributionUrlNameMain="${distributionUrlName%.*}"