refactor(api): refactor endpoints, services, and domain logic

This commit is contained in:
Pablo de la Torre Jamardo 2025-09-09 19:53:42 +02:00
parent 9f5306545e
commit 1b55d9ab29
144 changed files with 1357 additions and 1221 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea
*.toml
*.db
target

View File

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

View File

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

View File

@ -1,26 +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 {
private final CertificationRepositoryPort repository;
CertificationRepositoryPort repository;
public CreateCertificationUseCase(CertificationRepositoryPort repository) {
this.repository = repository;
}
public Certification handle(Command cmd) {
public Certification handle(Long profileId, Command cmd) {
var certification = Certification.builder()
.id(null)
.title(cmd.title())
.description(cmd.description())
.url(cmd.url())
.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 title, String description, String url) {
public record Command(
String name,
String issuer,
String date,
String credentialId
) {
}
}

View File

@ -14,11 +14,11 @@ public class GetCertificationUseCase {
this.repository = repository;
}
public Optional<Certification> byId(Long id) {
return repository.findById(new CertificationId(id));
public Optional<Certification> byId(Long profileId, Long id) {
return repository.findById(new CertificationId(profileId, id));
}
public List<Certification> all() {
return repository.findAll();
public List<Certification> all(Long profileId) {
return repository.findAll(profileId);
}
}

View File

@ -1,38 +0,0 @@
package com.pablotj.portfolio.application.contact;
import com.pablotj.portfolio.domain.contact.Contact;
import com.pablotj.portfolio.domain.contact.port.ContactRepositoryPort;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public class CreateContactUseCase {
private final ContactRepositoryPort repository;
public CreateContactUseCase(ContactRepositoryPort repository) {
this.repository = repository;
}
public Contact handle(Command cmd) {
var contact = Contact.builder()
.id(null)
.country(cmd.country())
.city(cmd.city())
.email(cmd.email())
.phone(cmd.phone())
.linkedin(cmd.linkedin())
.github(cmd.github())
.build();
return repository.save(contact);
}
public record Command(
String country,
String city,
String email,
String phone,
String linkedin,
String github
) {
}
}

View File

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

View File

@ -1,6 +1,7 @@
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 {
@ -11,16 +12,24 @@ public class CreateEducationUseCase {
this.repository = repository;
}
public Education handle(Command cmd) {
public Education handle(Long profileId, Command cmd) {
var education = Education.builder()
.id(null)
.title(cmd.title())
.id(new EducationId(profileId))
.institution(cmd.institution())
.degree(cmd.degree())
.period(cmd.period())
.grade(cmd.grade())
.description(cmd.description())
.url(cmd.url())
.build();
return repository.save(education);
}
public record Command(String title, String description, String url) {
public record Command(
String institution,
String degree,
String period,
String grade,
String description
) {
}
}

View File

@ -14,11 +14,11 @@ public class GetEducationUseCase {
this.repository = repository;
}
public Optional<Education> byId(Long id) {
return repository.findById(new EducationId(id));
public Optional<Education> byId(Long profileId, Long id) {
return repository.findById(new EducationId(profileId, id));
}
public List<Education> all() {
return repository.findAll();
public List<Education> all(Long profileId) {
return repository.findAll(profileId);
}
}

View File

@ -2,10 +2,9 @@ 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.Skill;
import com.pablotj.portfolio.domain.experience.ExperienceId;
import com.pablotj.portfolio.domain.experience.Technology;
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;
@ -17,22 +16,18 @@ public class CreateExperienceUseCase {
this.repository = repository;
}
public Experience handle(Command cmd) {
public Experience handle(Long profileId, Command cmd) {
var experience = Experience.builder()
.id(null)
.position(cmd.position())
.id(new ExperienceId(profileId))
.company(cmd.company())
.startDate(cmd.startDate())
.endDate(cmd.endDate())
.city(cmd.city())
.region(cmd.region())
.country(cmd.country())
.remote(cmd.remote())
.position(cmd.position())
.period(cmd.period())
.location(cmd.location())
.description(cmd.description())
.skills(new ArrayList<>())
.technologies(new ArrayList<>())
.achievements(new ArrayList<>())
.build();
cmd.skills.forEach(name -> experience.getSkills().add(Skill.builder().id(null).name(name).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);
}
@ -40,14 +35,10 @@ public class CreateExperienceUseCase {
public record Command(
String position,
String company,
LocalDate startDate,
LocalDate endDate,
String city,
String region,
String country,
Boolean remote,
String period,
String location,
String description,
List<String> skills,
List<String> technologies,
List<String> achievements
) {
}

View File

@ -14,11 +14,11 @@ public class GetExperienceUseCase {
this.repository = repository;
}
public Optional<Experience> byId(Long id) {
return repository.findById(new ExperienceId(id));
public Optional<Experience> byId(Long profileId, Long id) {
return repository.findById(new ExperienceId(profileId, id));
}
public List<Experience> all() {
return repository.findAll();
public List<Experience> all(Long profileId) {
return repository.findAll(profileId);
}
}

View File

@ -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) {
}
}

View File

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

View File

@ -1,25 +1,44 @@
package com.pablotj.portfolio.application.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 java.util.ArrayList;
import java.util.List;
public class CreateProjectUseCase {
private final ProjectRepositoryPort repository;
public record Command(String title, String description, String url) {}
public CreateProjectUseCase(ProjectRepositoryPort repository) {
this.repository = repository;
}
public Project handle(Command cmd) {
public Project handle(Long profileId, Command cmd) {
var project = Project.builder()
.id(null)
.id(new ProjectId(profileId))
.title(cmd.title())
.description(cmd.description())
.url(cmd.url())
.image(cmd.image())
.technologies(new ArrayList<>())
.features(new ArrayList<>())
.demo(cmd.demo())
.repository(cmd.repository())
.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);
}
public record Command(String title,
String description,
String image,
List<String> technologies,
List<String> features,
String demo,
String repository) {
}
}

View File

@ -15,11 +15,11 @@ public class GetProjectUseCase {
this.repository = repository;
}
public Optional<Project> byId(Long id) {
return repository.findById(new ProjectId(id));
public Optional<Project> byId(Long profileId, Long id) {
return repository.findById(new ProjectId(profileId, id));
}
public List<Project> all() {
return repository.findAll();
public List<Project> all(Long profileId) {
return repository.findAll(profileId);
}
}

View File

@ -1,35 +0,0 @@
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

@ -1,24 +0,0 @@
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,7 +1,11 @@
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 {
@ -11,16 +15,29 @@ public class CreateSkillUseCase {
this.repository = repository;
}
public Skill handle(Command cmd) {
var skill = Skill.builder()
.id(null)
.title(cmd.title())
.description(cmd.description())
.url(cmd.url())
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();
return repository.save(skill);
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 Command(String title, String description, String url) {
public record CommandGroup(
String name,
String icon,
List<CommandSkill> skills
) {
}
public record CommandSkill(
String name,
Integer level,
Integer years
) {
}
}

View File

@ -1,7 +1,7 @@
package com.pablotj.portfolio.application.skill;
import com.pablotj.portfolio.domain.skill.Skill;
import com.pablotj.portfolio.domain.skill.SkillId;
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;
@ -14,11 +14,11 @@ public class GetSkillUseCase {
this.repository = repository;
}
public Optional<Skill> byId(Long id) {
return repository.findById(new SkillId(id));
public Optional<SkillGroup> byId(Long profileId, Long id) {
return repository.findById(new SkillGroupId(profileId, id));
}
public List<Skill> all() {
return repository.findAll();
public List<SkillGroup> all(Long profileId) {
return repository.findAll(profileId);
}
}

View File

@ -51,4 +51,20 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,21 +0,0 @@
package com.pablotj.portfolio.bootstrap.about;
import com.pablotj.portfolio.application.about.CreateAboutUseCase;
import com.pablotj.portfolio.application.about.GetAboutUseCase;
import com.pablotj.portfolio.domain.about.port.AboutRepositoryPort;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AboutApplicationConfig {
@Bean
public GetAboutUseCase getAboutUseCase(AboutRepositoryPort repo) {
return new GetAboutUseCase(repo);
}
@Bean
public CreateAboutUseCase createAboutUseCase(AboutRepositoryPort repo) {
return new CreateAboutUseCase(repo);
}
}

View File

@ -1,21 +0,0 @@
package com.pablotj.portfolio.bootstrap.contact;
import com.pablotj.portfolio.application.contact.CreateContactUseCase;
import com.pablotj.portfolio.application.contact.GetContactUseCase;
import com.pablotj.portfolio.domain.contact.port.ContactRepositoryPort;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ContactApplicationConfig {
@Bean
public GetContactUseCase getContactUseCase(ContactRepositoryPort repo) {
return new GetContactUseCase(repo);
}
@Bean
public CreateContactUseCase createContactUseCase(ContactRepositoryPort repo) {
return new CreateContactUseCase(repo);
}
}

View File

@ -0,0 +1,21 @@
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 org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ProfileApplicationConfig {
@Bean
public GetProfileUseCase getHomeUseCase(ProfileRepositoryPort repo) {
return new GetProfileUseCase(repo);
}
@Bean
public CreateProfileUseCase createHomeUseCase(ProfileRepositoryPort repo) {
return new CreateProfileUseCase(repo);
}
}

View File

@ -1,21 +0,0 @@
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

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

View File

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

View File

@ -1,15 +0,0 @@
package com.pablotj.portfolio.domain.about.port;
import com.pablotj.portfolio.domain.about.About;
import com.pablotj.portfolio.domain.about.AboutId;
import java.util.List;
import java.util.Optional;
public interface AboutRepositoryPort {
About save(About p);
Optional<About> findById(AboutId id);
List<About> findAll();
}

View File

@ -7,7 +7,8 @@ import lombok.Getter;
@Builder
public class Certification {
private final CertificationId id;
private final String title;
private final String description;
private final String url;
private final String name;
private final String issuer;
private final String date;
private final String credentialId;
}

View File

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

View File

@ -10,5 +10,5 @@ public interface CertificationRepositoryPort {
Optional<Certification> findById(CertificationId id);
List<Certification> findAll();
List<Certification> findAll(Long profileId);
}

View File

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

View File

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

View File

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

View File

@ -7,7 +7,9 @@ import lombok.Getter;
@Builder
public class Education {
private final EducationId id;
private final String title;
private final String institution;
private final String degree;
private final String period;
private final String grade;
private final String description;
private final String url;
}

View File

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

View File

@ -10,5 +10,5 @@ public interface EducationRepositoryPort {
Optional<Education> findById(EducationId id);
List<Education> findAll();
List<Education> findAll(Long profileId);
}

View File

@ -1,6 +1,5 @@
package com.pablotj.portfolio.domain.experience;
import java.time.LocalDate;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
@ -9,15 +8,11 @@ import lombok.Getter;
@Builder
public class Experience {
private final ExperienceId id;
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 position;
private final String period;
private final String location;
private final String description;
private final List<Skill> skills;
private final List<Technology> technologies;
private final List<Achievement> achievements;
}

View File

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

View File

@ -1,12 +1,11 @@
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;
public class Technology {
private final TechnologyId id;
private String name;
}

View File

@ -1,7 +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");
public record TechnologyId(Long value) {
public TechnologyId {
if (value != null && value < 0) throw new IllegalArgumentException("TechnologyId must be positive");
}
}

View File

@ -10,5 +10,6 @@ public interface ExperienceRepositoryPort {
Optional<Experience> findById(ExperienceId id);
List<Experience> findAll();
List<Experience> findAll(Long profileId);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package com.pablotj.portfolio.domain.project;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
@ -9,5 +10,9 @@ public class Project {
private final ProjectId id;
private final String title;
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;
}

View File

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

View File

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

View File

@ -1,7 +1,12 @@
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 {
if (value != null && value < 0) throw new IllegalArgumentException("ProjectId must be positive");
if (projectId != null && projectId < 0) throw new IllegalArgumentException("ProjectId must be positive");
}
}

View File

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

View File

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

View File

@ -9,5 +9,5 @@ import java.util.Optional;
public interface ProjectRepositoryPort {
Project save(Project p);
Optional<Project> findById(ProjectId id);
List<Project> findAll();
List<Project> findAll(Long profileId);
}

View File

@ -1,15 +0,0 @@
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

@ -1,7 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -7,7 +7,7 @@ import lombok.Getter;
@Builder
public class Skill {
private final SkillId id;
private final String title;
private final String description;
private final String url;
private final String name;
private final Integer level;
private final Integer years;
}

View File

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

View File

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

View File

@ -1,14 +1,14 @@
package com.pablotj.portfolio.domain.skill.port;
import com.pablotj.portfolio.domain.skill.Skill;
import com.pablotj.portfolio.domain.skill.SkillId;
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 {
Skill save(Skill p);
SkillGroup save(SkillGroup p);
Optional<Skill> findById(SkillId id);
Optional<SkillGroup> findById(SkillGroupId id);
List<Skill> findAll();
List<SkillGroup> findAll(Long profileId);
}

View File

@ -0,0 +1,17 @@
package com.pablotj.portfolio.infrastructure.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // todos los endpoints que comiencen con /api/
.allowedOrigins("http://127.0.0.1:3000", "http://localhost:3000", "https://pablotj.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*");
}
}

View File

@ -1,40 +0,0 @@
package com.pablotj.portfolio.infrastructure.persistence.about.adapter;
import com.pablotj.portfolio.domain.about.About;
import com.pablotj.portfolio.domain.about.AboutId;
import com.pablotj.portfolio.domain.about.port.AboutRepositoryPort;
import com.pablotj.portfolio.infrastructure.persistence.about.entity.AboutJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.about.mapper.AboutJpaMapper;
import com.pablotj.portfolio.infrastructure.persistence.about.repo.SpringDataAboutRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class AboutRepositoryAdapter implements AboutRepositoryPort {
private final SpringDataAboutRepository repo;
private final AboutJpaMapper mapper;
public AboutRepositoryAdapter(SpringDataAboutRepository repo, AboutJpaMapper mapper) {
this.repo = repo;
this.mapper = mapper;
}
@Override
public About save(About p) {
AboutJpaEntity entity = mapper.toEntity(p);
AboutJpaEntity saved = repo.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<About> findById(AboutId id) {
return repo.findById(id.value()).map(mapper::toDomain);
}
@Override
public List<About> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
}
}

View File

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

View File

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

View File

@ -30,11 +30,11 @@ public class CertificationRepositoryAdapter implements CertificationRepositoryPo
@Override
public Optional<Certification> findById(CertificationId id) {
return repo.findById(id.value()).map(mapper::toDomain);
return repo.findByProfileIdAndId(id.profileId(), id.certificationId()).map(mapper::toDomain);
}
@Override
public List<Certification> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
public List<Certification> findAll(Long profileId) {
return repo.findAllByProfileId(profileId).stream().map(mapper::toDomain).toList();
}
}

View File

@ -1,16 +1,20 @@
package com.pablotj.portfolio.infrastructure.persistence.certification.entity;
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileJpaEntity;
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 = "certifications")
@Table(name = "CERTIFICATION")
@Getter
@Setter
public class CertificationJpaEntity {
@ -19,11 +23,19 @@ public class CertificationJpaEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id", nullable = false)
private ProfileJpaEntity profile;
@Column(columnDefinition = "text")
private String description;
@Column
private String name;
private String url;
@Column
private String issuer;
@Column
private String date;
@Column
private String credentialId;
}

View File

@ -10,8 +10,9 @@ import org.mapstruct.Mapping;
public interface CertificationJpaMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "profile.id", source = "id.profileId")
CertificationJpaEntity toEntity(Certification domain);
@Mapping(target = "id.value", source = "id")
@Mapping(target = "id", expression = "java(new CertificationId(e.getProfile().getId(), e.getId()))")
Certification toDomain(CertificationJpaEntity e);
}

View File

@ -1,7 +1,14 @@
package com.pablotj.portfolio.infrastructure.persistence.certification.repo;
import com.pablotj.portfolio.infrastructure.persistence.certification.entity.CertificationJpaEntity;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SpringDataCertificationRepository extends JpaRepository<CertificationJpaEntity, Long> {
Optional<CertificationJpaEntity> findByProfileIdAndId(Long profileId, Long id);
List<CertificationJpaEntity> findAllByProfileId(Long profileId);
}

View File

@ -1,40 +0,0 @@
package com.pablotj.portfolio.infrastructure.persistence.contact.adapter;
import com.pablotj.portfolio.domain.contact.Contact;
import com.pablotj.portfolio.domain.contact.ContactId;
import com.pablotj.portfolio.domain.contact.port.ContactRepositoryPort;
import com.pablotj.portfolio.infrastructure.persistence.contact.entity.ContactJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.contact.mapper.ContactJpaMapper;
import com.pablotj.portfolio.infrastructure.persistence.contact.repo.SpringDataContactRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class ContactRepositoryAdapter implements ContactRepositoryPort {
private final SpringDataContactRepository repo;
private final ContactJpaMapper mapper;
public ContactRepositoryAdapter(SpringDataContactRepository repo, ContactJpaMapper mapper) {
this.repo = repo;
this.mapper = mapper;
}
@Override
public Contact save(Contact p) {
ContactJpaEntity entity = mapper.toEntity(p);
ContactJpaEntity saved = repo.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<Contact> findById(ContactId id) {
return repo.findById(id.value()).map(mapper::toDomain);
}
@Override
public List<Contact> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
}
}

View File

@ -1,43 +0,0 @@
package com.pablotj.portfolio.infrastructure.persistence.contact.entity;
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 jakarta.validation.constraints.Email;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "contacts")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class ContactJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String country;
private String city;
@Column(nullable = false)
@Email
private String email;
private String phone;
@Column(name = "linkedin_url")
private String linkedin;
@Column(name = "github_url")
private String github;
}

View File

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

View File

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

View File

@ -30,11 +30,11 @@ public class EducationRepositoryAdapter implements EducationRepositoryPort {
@Override
public Optional<Education> findById(EducationId id) {
return repo.findById(id.value()).map(mapper::toDomain);
return repo.findByProfileIdAndId(id.profileId(), id.educationId()).map(mapper::toDomain);
}
@Override
public List<Education> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
public List<Education> findAll(Long profileId) {
return repo.findAllByProfileId(profileId).stream().map(mapper::toDomain).toList();
}
}

View File

@ -1,16 +1,20 @@
package com.pablotj.portfolio.infrastructure.persistence.education.entity;
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileJpaEntity;
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 = "educations")
@Table(name = "EDUCATION")
@Getter
@Setter
public class EducationJpaEntity {
@ -19,11 +23,22 @@ public class EducationJpaEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id", nullable = false)
private ProfileJpaEntity profile;
@Column
private String institution;
@Column
private String degree;
@Column
private String period;
@Column
private String grade;
@Column(columnDefinition = "text")
private String description;
private String url;
}
}

View File

@ -10,8 +10,9 @@ import org.mapstruct.Mapping;
public interface EducationJpaMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "profile.id", source = "id.profileId")
EducationJpaEntity toEntity(Education domain);
@Mapping(target = "id.value", source = "id")
@Mapping(target = "id", expression = "java(new EducationId(e.getProfile().getId(), e.getId()))")
Education toDomain(EducationJpaEntity e);
}

View File

@ -1,7 +1,13 @@
package com.pablotj.portfolio.infrastructure.persistence.education.repo;
import com.pablotj.portfolio.infrastructure.persistence.certification.entity.CertificationJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.education.entity.EducationJpaEntity;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SpringDataEducationRepository extends JpaRepository<EducationJpaEntity, Long> {
Optional<EducationJpaEntity> findByProfileIdAndId(Long profileId, Long id);
List<EducationJpaEntity> findAllByProfileId(Long profileId);
}

View File

@ -30,11 +30,11 @@ public class ExperienceRepositoryAdapter implements ExperienceRepositoryPort {
@Override
public Optional<Experience> findById(ExperienceId id) {
return repo.findById(id.value()).map(mapper::toDomain);
return repo.findByProfileIdAndId(id.profileId(), id.experienceId()).map(mapper::toDomain);
}
@Override
public List<Experience> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
public List<Experience> findAll(Long profileId) {
return repo.findAllByProfileId(profileId).stream().map(mapper::toDomain).toList();
}
}

View File

@ -1,10 +1,20 @@
package com.pablotj.portfolio.infrastructure.persistence.experience.entity;
import jakarta.persistence.*;
import lombok.*;
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_achievements")
@Table(name = "EXPERIENCE_ACHIEVEMENT")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)

View File

@ -1,15 +1,17 @@
package com.pablotj.portfolio.infrastructure.persistence.experience.entity;
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileJpaEntity;
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.time.LocalDate;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
@ -19,7 +21,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "experiences")
@Table(name = "EXPERIENCE")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ -31,25 +33,30 @@ public class ExperienceJpaEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id", nullable = false)
private ProfileJpaEntity profile;
@Column
private String position;
@Column
private String company;
private LocalDate startDate;
private LocalDate endDate;
@Column
private String period;
private String city;
private String region;
private String country;
private Boolean remote;
@Column
private String location;
@Column(columnDefinition = "text")
private String description;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "experience_id")
private List<ExperienceSkillJpaEntity> skills;
@JoinColumn(name = "EXPERIENCE_ID")
private List<ExperienceSkillJpaEntity> technologies;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "experience_id")
@JoinColumn(name = "EXPERIENCE_ID")
private List<ExperienceAchievementJpaEntity> achievements;
}

View File

@ -1,10 +1,19 @@
package com.pablotj.portfolio.infrastructure.persistence.experience.entity;
import jakarta.persistence.*;
import lombok.*;
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_skills")
@Table(name = "EXPERIENCE_SKILL")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)

View File

@ -2,7 +2,7 @@ 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.Skill;
import com.pablotj.portfolio.domain.experience.Technology;
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.ExperienceSkillJpaEntity;
@ -13,12 +13,12 @@ import org.mapstruct.Mapping;
public interface ExperienceJpaMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "profile.id", source = "id.profileId")
ExperienceJpaEntity toEntity(Experience domain);
@Mapping(target = "id.value", source = "id")
@Mapping(target = "id", expression = "java(new ExperienceId(entity.getProfile().getId(), entity.getId()))")
Experience toDomain(ExperienceJpaEntity entity);
@Mapping(target = "id", ignore = true)
ExperienceAchievementJpaEntity toEntity(Achievement entity);
@ -26,10 +26,10 @@ public interface ExperienceJpaMapper {
Achievement toDomain(ExperienceAchievementJpaEntity entity);
@Mapping(target = "id", ignore = true)
ExperienceSkillJpaEntity toEntity(Skill entity);
ExperienceSkillJpaEntity toEntity(Technology entity);
@Mapping(target = "id.value", source = "id")
Skill toDomain(ExperienceSkillJpaEntity entity);
Technology toDomain(ExperienceSkillJpaEntity entity);
}

View File

@ -1,7 +1,13 @@
package com.pablotj.portfolio.infrastructure.persistence.experience.repo;
import com.pablotj.portfolio.infrastructure.persistence.education.entity.EducationJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceJpaEntity;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SpringDataExperienceRepository extends JpaRepository<ExperienceJpaEntity, Long> {
Optional<ExperienceJpaEntity> findByProfileIdAndId(Long profileId, Long id);
List<ExperienceJpaEntity> findAllByProfileId(Long profileId);
}

View File

@ -0,0 +1,51 @@
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.ProfileJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileSocialLinkJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.profile.mapper.ProfileJpaMapper;
import com.pablotj.portfolio.infrastructure.persistence.profile.repo.SpringDataProfileRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
@Repository
public class ProfileRepositoryAdapter implements ProfileRepositoryPort {
private final SpringDataProfileRepository repo;
private final ProfileJpaMapper mapper;
public ProfileRepositoryAdapter(SpringDataProfileRepository repo, ProfileJpaMapper mapper) {
this.repo = repo;
this.mapper = mapper;
}
@Override
public Profile save(Profile p) {
ProfileJpaEntity entity = mapper.toEntity(p);
if (entity.getSocial() != null) {
for (ProfileSocialLinkJpaEntity socialLinkJpaEntity : entity.getSocial()) {
socialLinkJpaEntity.setProfile(entity);
}
}
ProfileJpaEntity saved = repo.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<Profile> findBySlug(ProfileId id) {
return repo.findBySlug(id.slug()).map(mapper::toDomain);
}
@Override
public Optional<Profile> findById(ProfileId id) {
return repo.findById(id.id()).map(mapper::toDomain);
}
@Override
public List<Profile> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
}
}

View File

@ -0,0 +1,56 @@
package com.pablotj.portfolio.infrastructure.persistence.profile.entity;
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 ProfileJpaEntity {
@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
private String bio;
@OneToMany(mappedBy = "profile", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProfileSocialLinkJpaEntity> social = new ArrayList<>();
}

View File

@ -0,0 +1,33 @@
package com.pablotj.portfolio.infrastructure.persistence.profile.entity;
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 ProfileSocialLinkJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id", nullable = false)
private ProfileJpaEntity profile;
@Column
private String url;
@Column
private String platform;
}

View File

@ -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.ProfileJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileSocialLinkJpaEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface ProfileJpaMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "social", source = "social")
ProfileJpaEntity toEntity(Profile domain);
@Mapping(target = "id.id", source = "id")
@Mapping(target = "social", source = "social")
Profile toDomain(ProfileJpaEntity e);
@Mapping(target = "id", ignore = true)
ProfileSocialLinkJpaEntity toEntitySocial(ProfileSocialLink entity);
@Mapping(target = "id.value", source = "id")
ProfileSocialLink toDomainSocial(ProfileSocialLinkJpaEntity entity);
}

View File

@ -0,0 +1,10 @@
package com.pablotj.portfolio.infrastructure.persistence.profile.repo;
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileJpaEntity;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SpringDataProfileRepository extends JpaRepository<ProfileJpaEntity, Long> {
Optional<ProfileJpaEntity> findBySlug(String slug);
}

View File

@ -31,11 +31,11 @@ public class ProjectRepositoryAdapter implements ProjectRepositoryPort {
@Override
public Optional<Project> findById(ProjectId id) {
return repo.findById(id.value()).map(mapper::toDomain);
return repo.findByProfileIdAndId(id.profileId(), id.projectId()).map(mapper::toDomain);
}
@Override
public List<Project> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
public List<Project> findAll(Long profileId) {
return repo.findAllByProfileId(profileId).stream().map(mapper::toDomain).toList();
}
}

View File

@ -1,4 +1,4 @@
package com.pablotj.portfolio.infrastructure.persistence.about.entity;
package com.pablotj.portfolio.infrastructure.persistence.project.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@ -10,20 +10,16 @@ import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "abouts")
@Table(name = "PROJECT_FEATURE")
@Getter
@Setter
public class AboutJpaEntity {
public class ProjectFeatureJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column
private String name;
@Column(columnDefinition = "text")
private String description;
private String url;
}

View File

@ -1,11 +1,23 @@
package com.pablotj.portfolio.infrastructure.persistence.project.entity;
import jakarta.persistence.*;
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileJpaEntity;
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 = "projects")
@Table(name = "PROJECT")
@Getter @Setter
public class ProjectJpaEntity {
@ -13,11 +25,31 @@ public class ProjectJpaEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id", nullable = false)
private ProfileJpaEntity profile;
@Column
private String title;
@Column(columnDefinition = "text")
@Column
private String description;
private String url;
@Column
private String image;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "PROJECT_ID")
private List<ProjectTechnologyJpaEntity> technologies;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "PROJECT_ID")
private List<ProjectFeatureJpaEntity> features;
@Column
private String demo;
@Column
private String repository;
}

View File

@ -0,0 +1,25 @@
package com.pablotj.portfolio.infrastructure.persistence.project.entity;
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 ProjectTechnologyJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String name;
}

View File

@ -1,8 +1,11 @@
package com.pablotj.portfolio.infrastructure.persistence.project.mapper;
import com.pablotj.portfolio.domain.project.Project;
import com.pablotj.portfolio.domain.project.ProjectId;
import com.pablotj.portfolio.domain.project.ProjectFeature;
import com.pablotj.portfolio.domain.project.ProjectTechnology;
import com.pablotj.portfolio.infrastructure.persistence.project.entity.ProjectFeatureJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.project.entity.ProjectJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.project.entity.ProjectTechnologyJpaEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@ -10,8 +13,21 @@ import org.mapstruct.Mapping;
public interface ProjectJpaMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "profile.id", source = "id.profileId")
ProjectJpaEntity toEntity(Project domain);
@Mapping(target = "id.value", source = "id")
@Mapping(target = "id", expression = "java(new ProjectId(e.getProfile().getId(), e.getId()))")
Project toDomain(ProjectJpaEntity e);
@Mapping(target = "id", ignore = true)
ProjectTechnologyJpaEntity toEntity(ProjectTechnology entity);
@Mapping(target = "id.value", source = "id")
ProjectTechnology toDomain(ProjectTechnologyJpaEntity entity);
@Mapping(target = "id", ignore = true)
ProjectFeatureJpaEntity toEntity(ProjectFeature entity);
@Mapping(target = "id.value", source = "id")
ProjectFeature toDomain(ProjectFeatureJpaEntity entity);
}

View File

@ -1,6 +1,13 @@
package com.pablotj.portfolio.infrastructure.persistence.project.repo;
import com.pablotj.portfolio.infrastructure.persistence.experience.entity.ExperienceJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.project.entity.ProjectJpaEntity;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SpringDataProjectRepository extends JpaRepository<ProjectJpaEntity, Long> {}
public interface SpringDataProjectRepository extends JpaRepository<ProjectJpaEntity, Long> {
Optional<ProjectJpaEntity> findByProfileIdAndId(Long profileId, Long id);
List<ProjectJpaEntity> findAllByProfileId(Long profileId);
}

View File

@ -1,40 +0,0 @@
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,37 +0,0 @@
package com.pablotj.portfolio.infrastructure.persistence.resume.entity;
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 = "resumes")
@Getter
@Setter
public class ResumeJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name", nullable = false)
private String name;
@Column(name = "last_name", nullable = false)
private String surnames;
@Column(nullable = false)
private String title;
@Column(columnDefinition = "text", nullable = false)
private String summary;
@Column
private String icon;
}

View File

@ -1,17 +0,0 @@
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

@ -1,7 +0,0 @@
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,9 +1,9 @@
package com.pablotj.portfolio.infrastructure.persistence.skill.adapter;
import com.pablotj.portfolio.domain.skill.Skill;
import com.pablotj.portfolio.domain.skill.SkillId;
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.SkillJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.skill.entity.SkillGroupJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.skill.mapper.SkillJpaMapper;
import com.pablotj.portfolio.infrastructure.persistence.skill.repo.SpringDataSkillRepository;
import java.util.List;
@ -22,19 +22,19 @@ public class SkillRepositoryAdapter implements SkillRepositoryPort {
}
@Override
public Skill save(Skill p) {
SkillJpaEntity entity = mapper.toEntity(p);
SkillJpaEntity saved = repo.save(entity);
public SkillGroup save(SkillGroup p) {
SkillGroupJpaEntity entity = mapper.toEntity(p);
SkillGroupJpaEntity saved = repo.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<Skill> findById(SkillId id) {
return repo.findById(id.value()).map(mapper::toDomain);
public Optional<SkillGroup> findById(SkillGroupId id) {
return repo.findByProfileIdAndId(id.profileId(), id.skillGroupId()).map(mapper::toDomain);
}
@Override
public List<Skill> findAll() {
return repo.findAll().stream().map(mapper::toDomain).toList();
public List<SkillGroup> findAll(Long profileId) {
return repo.findAllByProfileId(profileId).stream().map(mapper::toDomain).toList();
}
}

View File

@ -0,0 +1,42 @@
package com.pablotj.portfolio.infrastructure.persistence.skill.entity;
import com.pablotj.portfolio.infrastructure.persistence.profile.entity.ProfileJpaEntity;
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 SkillGroupJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id", nullable = false)
private ProfileJpaEntity profile;
@Column
private String name;
@Column
private String icon;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "SKILL_ID")
private List<SkillJpaEntity> skills;
}

View File

@ -10,7 +10,7 @@ import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "skills")
@Table(name = "SKILL")
@Getter
@Setter
public class SkillJpaEntity {
@ -19,11 +19,12 @@ public class SkillJpaEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column
private String name;
@Column(columnDefinition = "text")
private String description;
@Column
private Integer level;
private String url;
@Column
private Integer years;
}

Some files were not shown because too many files have changed in this diff Show More