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

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

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

View File

@@ -1,7 +1,8 @@
package com.pablotj.portfolio.infrastructure.persistence.skill.mapper;
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.infrastructure.persistence.skill.entity.SkillGroupJpaEntity;
import com.pablotj.portfolio.infrastructure.persistence.skill.entity.SkillJpaEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@@ -10,15 +11,15 @@ import org.mapstruct.Mapping;
public interface SkillJpaMapper {
@Mapping(target = "id", ignore = true)
SkillJpaEntity toEntity(Skill domain);
@Mapping(target = "profile.id", source = "id.profileId")
SkillGroupJpaEntity toEntity(SkillGroup domain);
default Skill toDomain(SkillJpaEntity e) {
if (e == null) return null;
return Skill.builder()
.id(e.getId() == null ? null : new SkillId(e.getId()))
.title(e.getTitle())
.description(e.getDescription())
.url(e.getUrl())
.build();
}
@Mapping(target = "id", expression = "java(new SkillGroupId(entity.getProfile().getId(), entity.getId()))")
SkillGroup toDomain(SkillGroupJpaEntity entity);
@Mapping(target = "id", ignore = true)
SkillJpaEntity toEntity(Skill entity);
@Mapping(target = "id.value", source = "id")
Skill toDomain(SkillJpaEntity entity);
}

View File

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

View File

@@ -1,56 +0,0 @@
package com.pablotj.portfolio.infrastructure.rest.about;
import com.pablotj.portfolio.application.about.CreateAboutUseCase;
import com.pablotj.portfolio.application.about.GetAboutUseCase;
import com.pablotj.portfolio.infrastructure.rest.about.dto.AboutDto;
import com.pablotj.portfolio.infrastructure.rest.about.mapper.AboutRestMapper;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/abouts")
public class AboutController {
private final CreateAboutUseCase createUC;
private final GetAboutUseCase getUC;
private final AboutRestMapper mapper;
public AboutController(CreateAboutUseCase createUC, GetAboutUseCase getUC, AboutRestMapper mapper) {
this.createUC = createUC;
this.getUC = getUC;
this.mapper = mapper;
}
@GetMapping
public List<AboutDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
}
@GetMapping("/{id}")
public ResponseEntity<AboutDto> byId(@PathVariable Long id) {
return getUC.byId(id)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<AboutDto> create(@Valid @RequestBody com.pablotj.portfolio.infrastructure.rest.about.dto.CreateAboutRequest request) {
var cmd = new CreateAboutUseCase.Command(
request.title(),
request.description(),
request.url()
);
var created = createUC.handle(cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/abouts/" + body.id())).body(body);
}
}

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package com.pablotj.portfolio.infrastructure.rest.api;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -11,11 +12,14 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping
public class ApiRootController {
@Value("${info.app.version}")
private String appVersion;
@GetMapping
public ResponseEntity<Map<String, Object>> root() {
Map<String, Object> response = Map.of(
"api", "Portfolio API",
"version", "1.0",
"version", appVersion,
"doc", "/v3/api-docs",
"swagger", "/swagger-ui",
"endpoints", List.of(
@@ -26,7 +30,7 @@ public class ApiRootController {
Map.of("path", "/v1/educations", "description", "Manage projects"),
Map.of("path", "/v1/experiences", "description", "Manage projects"),
Map.of("path", "/v1/projects", "description", "Manage projects"),
Map.of("path", "/v1/skills", "description", "Experience entries")
Map.of("path", "/v1/technologies", "description", "ProfileSocialLink entries")
)
);

View File

@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/certifications")
@RequestMapping("/v1/profiles/{profileId}/certifications")
public class CertificationController {
private final CreateCertificationUseCase createUC;
@@ -31,26 +31,27 @@ public class CertificationController {
}
@GetMapping
public List<CertificationDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
public List<CertificationDto> all(@PathVariable Long profileId) {
return getUC.all(profileId).stream().map(mapper::toDto).toList();
}
@GetMapping("/{id}")
public ResponseEntity<CertificationDto> byId(@PathVariable Long id) {
return getUC.byId(id)
public ResponseEntity<CertificationDto> byId(@PathVariable Long profileId, @PathVariable Long id) {
return getUC.byId(profileId, id)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<CertificationDto> create(@Valid @RequestBody CreateCertificationRequest request) {
public ResponseEntity<CertificationDto> create(@PathVariable Long profileId, @Valid @RequestBody CreateCertificationRequest request) {
var cmd = new CreateCertificationUseCase.Command(
request.title(),
request.description(),
request.url()
request.name(),
request.issuer(),
request.date(),
request.credentialId()
);
var created = createUC.handle(cmd);
var created = createUC.handle(profileId, cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/certifications/" + body.id())).body(body);
}

View File

@@ -1,6 +1,8 @@
package com.pablotj.portfolio.infrastructure.rest.certification.dto;
import jakarta.validation.constraints.NotBlank;
public record CertificationDto(Long id, @NotBlank String title, String description, String url) {
public record CertificationDto(Long id,
String name,
String issuer,
String date,
String credentialId) {
}

View File

@@ -1,10 +1,9 @@
package com.pablotj.portfolio.infrastructure.rest.certification.dto;
import jakarta.validation.constraints.NotBlank;
public record CreateCertificationRequest(
@NotBlank String title,
String description,
String url
String name,
String issuer,
String date,
String credentialId
) {
}

View File

@@ -8,6 +8,6 @@ import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface CertificationRestMapper {
@Mapping(target = "id", source = "id.value")
@Mapping(target = "id", source = "id.certificationId")
CertificationDto toDto(Certification domain);
}

View File

@@ -1,60 +0,0 @@
package com.pablotj.portfolio.infrastructure.rest.contact;
import com.pablotj.portfolio.application.contact.CreateContactUseCase;
import com.pablotj.portfolio.application.contact.GetContactUseCase;
import com.pablotj.portfolio.infrastructure.rest.contact.dto.ContactDto;
import com.pablotj.portfolio.infrastructure.rest.contact.dto.CreateContactRequest;
import com.pablotj.portfolio.infrastructure.rest.contact.mapper.ContactRestMapper;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/contacts")
public class ContactController {
private final CreateContactUseCase createUC;
private final GetContactUseCase getUC;
private final ContactRestMapper mapper;
public ContactController(CreateContactUseCase createUC, GetContactUseCase getUC, ContactRestMapper mapper) {
this.createUC = createUC;
this.getUC = getUC;
this.mapper = mapper;
}
@GetMapping
public List<ContactDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
}
@GetMapping("/{id}")
public ResponseEntity<ContactDto> byId(@PathVariable Long id) {
return getUC.byId(id)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<ContactDto> create(@Valid @RequestBody CreateContactRequest request) {
var cmd = new CreateContactUseCase.Command(
request.country(),
request.city(),
request.email(),
request.phone(),
request.linkedin(),
request.github()
);
var created = createUC.handle(cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/contacts/" + body.id())).body(body);
}
}

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/educations")
@RequestMapping("/v1/profiles/{profileId}/education")
public class EducationController {
private final CreateEducationUseCase createUC;
@@ -31,26 +31,28 @@ public class EducationController {
}
@GetMapping
public List<EducationDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
public List<EducationDto> all(@PathVariable Long profileId) {
return getUC.all(profileId).stream().map(mapper::toDto).toList();
}
@GetMapping("/{id}")
public ResponseEntity<EducationDto> byId(@PathVariable Long id) {
return getUC.byId(id)
public ResponseEntity<EducationDto> byId(@PathVariable Long profileId, @PathVariable Long id) {
return getUC.byId(profileId, id)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<EducationDto> create(@Valid @RequestBody CreateEducationRequest request) {
public ResponseEntity<EducationDto> create(@PathVariable Long profileId, @Valid @RequestBody CreateEducationRequest request) {
var cmd = new CreateEducationUseCase.Command(
request.title(),
request.description(),
request.url()
request.institution(),
request.degree(),
request.period(),
request.grade(),
request.description()
);
var created = createUC.handle(cmd);
var created = createUC.handle(profileId, cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/educations/" + body.id())).body(body);
}

View File

@@ -1,10 +1,10 @@
package com.pablotj.portfolio.infrastructure.rest.education.dto;
import jakarta.validation.constraints.NotBlank;
public record CreateEducationRequest(
@NotBlank String title,
String description,
String url
String institution,
String degree,
String period,
String grade,
String description
) {
}

View File

@@ -1,6 +1,10 @@
package com.pablotj.portfolio.infrastructure.rest.education.dto;
import jakarta.validation.constraints.NotBlank;
public record EducationDto(Long id, @NotBlank String title, String description, String url) {
public record EducationDto(
Long id,
String institution,
String degree,
String period,
String grade,
String description) {
}

View File

@@ -8,6 +8,6 @@ import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface EducationRestMapper {
@Mapping(target = "id", source = "id.value")
@Mapping(target = "id", source = "id.educationId")
EducationDto toDto(Education domain);
}

View File

@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/experiences")
@RequestMapping("/v1/profiles/{profileId}/experience")
public class ExperienceController {
private final CreateExperienceUseCase createUC;
@@ -31,34 +31,30 @@ public class ExperienceController {
}
@GetMapping
public List<ExperienceDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
public List<ExperienceDto> all(@PathVariable Long profileId) {
return getUC.all(profileId).stream().map(mapper::toDto).toList();
}
@GetMapping("/{id}")
public ResponseEntity<ExperienceDto> byId(@PathVariable Long id) {
return getUC.byId(id)
public ResponseEntity<ExperienceDto> byId(@PathVariable Long profileId, @PathVariable Long id) {
return getUC.byId(profileId, id)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<ExperienceDto> create(@Valid @RequestBody CreateExperienceRequest request) {
public ResponseEntity<ExperienceDto> create(@PathVariable Long profileId, @Valid @RequestBody CreateExperienceRequest request) {
var cmd = new CreateExperienceUseCase.Command(
request.position(),
request.company(),
request.startDate(),
request.endDate(),
request.city(),
request.region(),
request.country(),
request.remote(),
request.position(),
request.period(),
request.location(),
request.description(),
request.skills(),
request.technologies(),
request.achievements()
);
var created = createUC.handle(cmd);
var created = createUC.handle(profileId, cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/experiences/" + body.id())).body(body);
}

View File

@@ -1,19 +1,14 @@
package com.pablotj.portfolio.infrastructure.rest.experience.dto;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDate;
import java.util.List;
public record CreateExperienceRequest(
@NotBlank 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

@@ -1,20 +1,15 @@
package com.pablotj.portfolio.infrastructure.rest.experience.dto;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDate;
import java.util.List;
public record ExperienceDto(
Long id,
@NotBlank 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

@@ -2,7 +2,7 @@ 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.Skill;
import com.pablotj.portfolio.domain.experience.Technology;
import com.pablotj.portfolio.infrastructure.rest.experience.dto.ExperienceDto;
import java.util.List;
import org.mapstruct.Mapper;
@@ -11,14 +11,14 @@ import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface ExperienceRestMapper {
@Mapping(target = "id", source = "id.value")
@Mapping(target = "skills", source = "skills")
@Mapping(target = "id", source = "id.experienceId")
@Mapping(target = "technologies", source = "technologies")
@Mapping(target = "achievements", source = "achievements")
ExperienceDto toDto(Experience domain);
default List<String> mapSkills(List<Skill> skills) {
default List<String> mapSkills(List<Technology> skills) {
return skills == null ? List.of() : skills.stream()
.map(Skill::getName)
.map(Technology::getName)
.toList();
}

View File

@@ -0,0 +1,65 @@
package com.pablotj.portfolio.infrastructure.rest.profile;
import com.pablotj.portfolio.application.profile.CreateProfileUseCase;
import com.pablotj.portfolio.application.profile.GetProfileUseCase;
import com.pablotj.portfolio.infrastructure.rest.profile.dto.ProfileCreateRequest;
import com.pablotj.portfolio.infrastructure.rest.profile.dto.ProfileDto;
import com.pablotj.portfolio.infrastructure.rest.profile.mapper.ProfileRestMapper;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/profiles")
public class ProfileController {
private final CreateProfileUseCase createUC;
private final GetProfileUseCase getUC;
private final ProfileRestMapper mapper;
public ProfileController(CreateProfileUseCase createUC, GetProfileUseCase getUC, ProfileRestMapper mapper) {
this.createUC = createUC;
this.getUC = getUC;
this.mapper = mapper;
}
@GetMapping
public List<ProfileDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
}
@GetMapping("/{slug}")
public ResponseEntity<ProfileDto> byId(@PathVariable String slug) {
return getUC.bySlug(slug)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<ProfileDto> create(@Valid @RequestBody ProfileCreateRequest request) {
var cmd = new CreateProfileUseCase.Command(
request.slug(),
request.name(),
request.title(),
request.subtitle(),
request.email(),
request.phone(),
request.location(),
request.avatar(),
request.bio(),
request.social().stream().map(l -> new CreateProfileUseCase.Link(l.platform(), l.url())).toList()
);
var created = createUC.handle(cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/homes/" + body.id())).body(body);
}
}

View File

@@ -0,0 +1,17 @@
package com.pablotj.portfolio.infrastructure.rest.profile.dto;
import java.util.List;
public record ProfileCreateRequest(
String slug,
String name,
String title,
String subtitle,
String email,
String phone,
String location,
String avatar,
String bio,
List<ProfileSocialLinkDto> social
) {
}

View File

@@ -0,0 +1,18 @@
package com.pablotj.portfolio.infrastructure.rest.profile.dto;
import java.util.List;
public record ProfileDto(
Long id,
String slug,
String name,
String title,
String subtitle,
String email,
String phone,
String location,
String avatar,
String bio,
List<ProfileSocialLinkDto> social
) {
}

View File

@@ -0,0 +1,4 @@
package com.pablotj.portfolio.infrastructure.rest.profile.dto;
public record ProfileSocialLinkDto(String platform, String url) {
}

View File

@@ -0,0 +1,24 @@
package com.pablotj.portfolio.infrastructure.rest.profile.mapper;
import com.pablotj.portfolio.domain.profile.Profile;
import com.pablotj.portfolio.domain.profile.ProfileSocialLink;
import com.pablotj.portfolio.infrastructure.rest.profile.dto.ProfileDto;
import com.pablotj.portfolio.infrastructure.rest.profile.dto.ProfileSocialLinkDto;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface ProfileRestMapper {
@Mapping(target = "id", source = "id.id")
@Mapping(target = "social", source = "social")
ProfileDto toDto(Profile domain);
default List<ProfileSocialLinkDto> mapSocialLinks(List<ProfileSocialLink> skills) {
return skills == null ? List.of() : skills.stream()
.map(s -> new ProfileSocialLinkDto(s.getPlatform(), s.getUrl()))
.toList();
}
}

View File

@@ -6,14 +6,18 @@ import com.pablotj.portfolio.infrastructure.rest.project.dto.CreateProjectReques
import com.pablotj.portfolio.infrastructure.rest.project.dto.ProjectDto;
import com.pablotj.portfolio.infrastructure.rest.project.mapper.ProjectRestMapper;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/projects")
@RequestMapping("/v1/profiles/{profileId}/projects")
public class ProjectController {
private final CreateProjectUseCase createUC;
@@ -27,26 +31,30 @@ public class ProjectController {
}
@GetMapping
public List<ProjectDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
public List<ProjectDto> all(@PathVariable Long profileId) {
return getUC.all(profileId).stream().map(mapper::toDto).toList();
}
@GetMapping("/{id}")
public ResponseEntity<ProjectDto> byId(@PathVariable Long id) {
return getUC.byId(id)
public ResponseEntity<ProjectDto> byId(@PathVariable Long profileId, @PathVariable Long id) {
return getUC.byId(profileId, id)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<ProjectDto> create(@Valid @RequestBody CreateProjectRequest request) {
public ResponseEntity<ProjectDto> create(@PathVariable Long profileId, @Valid @RequestBody CreateProjectRequest request) {
var cmd = new CreateProjectUseCase.Command(
request.title(),
request.description(),
request.url()
request.image(),
request.technologies(),
request.features(),
request.demo(),
request.repository()
);
var created = createUC.handle(cmd);
var created = createUC.handle(profileId, cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/projects/" + body.id())).body(body);
}

View File

@@ -1,9 +1,13 @@
package com.pablotj.portfolio.infrastructure.rest.project.dto;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
public record CreateProjectRequest(
@NotBlank String title,
String title,
String description,
String url
String image,
List<String> technologies,
List<String> features,
String demo,
String repository
) {}

View File

@@ -1,5 +1,15 @@
package com.pablotj.portfolio.infrastructure.rest.project.dto;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
public record ProjectDto(Long id, @NotBlank String title, String description, String url) {}
public record ProjectDto(
Long id,
String title,
String description,
String image,
List<String> technologies,
List<String> features,
String demo,
String repository
) {
}

View File

@@ -1,13 +1,30 @@
package com.pablotj.portfolio.infrastructure.rest.project.mapper;
import com.pablotj.portfolio.domain.project.Project;
import com.pablotj.portfolio.domain.project.ProjectFeature;
import com.pablotj.portfolio.domain.project.ProjectTechnology;
import com.pablotj.portfolio.infrastructure.rest.project.dto.ProjectDto;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface ProjectRestMapper {
@Mapping(target = "id", source = "id.value")
@Mapping(target = "id", source = "id.projectId")
@Mapping(target = "technologies", source = "technologies")
@Mapping(target = "features", source = "features")
ProjectDto toDto(Project domain);
default List<String> mapTechnologies(List<ProjectTechnology> technologies) {
return technologies == null ? List.of() : technologies.stream()
.map(ProjectTechnology::getName)
.toList();
}
default List<String> mapFeatures(List<ProjectFeature> features) {
return features == null ? List.of() : features.stream()
.map(ProjectFeature::getName)
.toList();
}
}

View File

@@ -1,59 +0,0 @@
package com.pablotj.portfolio.infrastructure.rest.resume;
import com.pablotj.portfolio.application.resume.CreateResumeUseCase;
import com.pablotj.portfolio.application.resume.GetResumeUseCase;
import com.pablotj.portfolio.infrastructure.rest.resume.dto.ResumeCreateRequest;
import com.pablotj.portfolio.infrastructure.rest.resume.dto.ResumeDto;
import com.pablotj.portfolio.infrastructure.rest.resume.mapper.ResumeRestMapper;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/homes")
public class ResumeController {
private final CreateResumeUseCase createUC;
private final GetResumeUseCase getUC;
private final ResumeRestMapper mapper;
public ResumeController(CreateResumeUseCase createUC, GetResumeUseCase getUC, ResumeRestMapper mapper) {
this.createUC = createUC;
this.getUC = getUC;
this.mapper = mapper;
}
@GetMapping
public List<ResumeDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
}
@GetMapping("/{id}")
public ResponseEntity<ResumeDto> byId(@PathVariable Long id) {
return getUC.byId(id)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<ResumeDto> create(@Valid @RequestBody ResumeCreateRequest request) {
var cmd = new CreateResumeUseCase.Command(
request.name(),
request.surnames(),
request.title(),
request.summary(),
request.icon()
);
var created = createUC.handle(cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/homes/" + body.id())).body(body);
}
}

View File

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

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

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

@@ -2,8 +2,8 @@ package com.pablotj.portfolio.infrastructure.rest.skill;
import com.pablotj.portfolio.application.skill.CreateSkillUseCase;
import com.pablotj.portfolio.application.skill.GetSkillUseCase;
import com.pablotj.portfolio.infrastructure.rest.skill.dto.CreateSkillRequest;
import com.pablotj.portfolio.infrastructure.rest.skill.dto.SkillDto;
import com.pablotj.portfolio.infrastructure.rest.skill.dto.CreateSkillGroupRequest;
import com.pablotj.portfolio.infrastructure.rest.skill.dto.SkillGroupDto;
import com.pablotj.portfolio.infrastructure.rest.skill.mapper.SkillRestMapper;
import jakarta.validation.Valid;
import java.net.URI;
@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/v1/skills")
@RequestMapping("/v1/profiles/{profileId}/skills")
public class SkillController {
private final CreateSkillUseCase createUC;
@@ -31,26 +31,26 @@ public class SkillController {
}
@GetMapping
public List<SkillDto> all() {
return getUC.all().stream().map(mapper::toDto).toList();
public List<SkillGroupDto> all(@PathVariable Long profileId) {
return getUC.all(profileId).stream().map(mapper::toDto).toList();
}
@GetMapping("/{id}")
public ResponseEntity<SkillDto> byId(@PathVariable Long id) {
return getUC.byId(id)
public ResponseEntity<SkillGroupDto> byId(@PathVariable Long profileId, @PathVariable Long id) {
return getUC.byId(profileId, id)
.map(mapper::toDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<SkillDto> create(@Valid @RequestBody CreateSkillRequest request) {
var cmd = new CreateSkillUseCase.Command(
request.title(),
request.description(),
request.url()
public ResponseEntity<SkillGroupDto> create(@PathVariable Long profileId, @Valid @RequestBody CreateSkillGroupRequest request) {
var cmd = new CreateSkillUseCase.CommandGroup(
request.name(),
request.icon(),
request.skills().stream().map(s -> new CreateSkillUseCase.CommandSkill(s.name(), s.level(), s.years())).toList()
);
var created = createUC.handle(cmd);
var created = createUC.handle(profileId, cmd);
var body = mapper.toDto(created);
return ResponseEntity.created(URI.create("/api/skills/" + body.id())).body(body);
}

View File

@@ -0,0 +1,8 @@
package com.pablotj.portfolio.infrastructure.rest.skill.dto;
import java.util.List;
public record CreateSkillGroupRequest(
String name, String icon, List<CreateSkillRequest> skills
) {
}

View File

@@ -1,10 +1,8 @@
package com.pablotj.portfolio.infrastructure.rest.skill.dto;
import jakarta.validation.constraints.NotBlank;
public record CreateSkillRequest(
@NotBlank String title,
String description,
String url
String name,
Integer years,
Integer level
) {
}

View File

@@ -1,6 +1,4 @@
package com.pablotj.portfolio.infrastructure.rest.skill.dto;
import jakarta.validation.constraints.NotBlank;
public record SkillDto(Long id, @NotBlank String title, String description, String url) {
public record SkillDto(Long id, String name, Integer years, Integer level) {
}

View File

@@ -0,0 +1,6 @@
package com.pablotj.portfolio.infrastructure.rest.skill.dto;
import java.util.List;
public record SkillGroupDto(Long id, String name, String icon, List<SkillDto> skills) {
}

View File

@@ -1,13 +1,18 @@
package com.pablotj.portfolio.infrastructure.rest.skill.mapper;
import com.pablotj.portfolio.domain.skill.Skill;
import com.pablotj.portfolio.domain.skill.SkillGroup;
import com.pablotj.portfolio.infrastructure.rest.skill.dto.SkillDto;
import com.pablotj.portfolio.infrastructure.rest.skill.dto.SkillGroupDto;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface SkillRestMapper {
@Mapping(target = "id", source = "id.skillGroupId")
SkillGroupDto toDto(SkillGroup domain);
@Mapping(target = "id", source = "id.value")
SkillDto toDto(Skill domain);
}

View File

@@ -0,0 +1,145 @@
create table certification
(
id bigint generated by default as identity,
credential_id varchar(255),
date varchar(255),
issuer varchar(255),
name varchar(255),
primary key (id)
);
create table education
(
id bigint generated by default as identity,
degree varchar(255),
description text,
grade varchar(255),
institution varchar(255),
period varchar(255),
primary key (id)
);
create table experience
(
id bigint generated by default as identity,
company varchar(255),
description text,
location varchar(255),
period varchar(255),
position varchar(255),
primary key (id)
);
create table experience_achievement
(
id bigint generated by default as identity,
description text,
experience_id bigint,
primary key (id)
);
create table experience_skill
(
id bigint generated by default as identity,
name varchar(255),
experience_id bigint,
primary key (id)
);
create table personal
(
id bigint generated by default as identity,
avatar varchar(255),
bio varchar(255),
email varchar(255),
location varchar(255),
name varchar(255),
phone varchar(255),
subtitle varchar(255),
title varchar(255),
primary key (id)
);
create table personal_social_link
(
id bigint generated by default as identity,
platform varchar(255),
url varchar(255),
personal_id bigint not null,
primary key (id)
);
create table project
(
id bigint generated by default as identity,
demo varchar(255),
description varchar(255),
image varchar(255),
repository varchar(255),
title varchar(255),
primary key (id)
);
create table project_feature
(
id bigint generated by default as identity,
name varchar(255),
project_id bigint,
primary key (id)
);
create table project_feature_technology
(
id bigint generated by default as identity,
name varchar(255),
project_id bigint,
primary key (id)
);
create table skill
(
id bigint generated by default as identity,
level integer,
name varchar(255),
years integer,
skill_id bigint,
primary key (id)
);
create table skill_group
(
id bigint generated by default as identity,
icon varchar(255),
name varchar(255),
primary key (id)
);
alter table if exists experience_achievement
add constraint FK94xrk6stofkung8skwplo29nd
foreign key (experience_id)
references experience;
alter table if exists experience_skill
add constraint FKpr3jdfjjlaubuayoafpwyx2al
foreign key (experience_id)
references experience;
alter table if exists profile_social_link
add constraint FKfh1pbfvvg3palcr1yip6jffik
foreign key (PROFILE_ID)
references profile;
alter table if exists project_feature
add constraint FKdifppyvrfito5in15ox4db0up
foreign key (project_id)
references project;
alter table if exists project_feature_technology
add constraint FK15krsajtovetpg5vsaqj3icwf
foreign key (project_id)
references project;
alter table if exists skill
add constraint FKi819li5g5cp5qbsyenhr3kmef
foreign key (skill_id)
references skill_group;