diff --git a/pom.xml b/pom.xml index 846bf0c..8d02cfd 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,12 @@ spring-boot-starter-webmvc-test test + + + cn.hutool + hutool-core + 5.8.43 + diff --git a/src/main/java/dev/mednikov/social/users/config/IdentifierGeneratorConfig.java b/src/main/java/dev/mednikov/social/users/config/IdentifierGeneratorConfig.java new file mode 100644 index 0000000..35abd6c --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/config/IdentifierGeneratorConfig.java @@ -0,0 +1,16 @@ +package dev.mednikov.social.users.config; + +import dev.mednikov.social.users.repositories.IdentifierGenerator; +import dev.mednikov.social.users.repositories.IdentifierGeneratorImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class IdentifierGeneratorConfig { + + @Bean + public IdentifierGenerator identifierGenerator() { + return new IdentifierGeneratorImpl(); + } + +} diff --git a/src/main/java/dev/mednikov/social/users/config/ObjectMapperConfig.java b/src/main/java/dev/mednikov/social/users/config/ObjectMapperConfig.java index 5d1f809..6f47312 100644 --- a/src/main/java/dev/mednikov/social/users/config/ObjectMapperConfig.java +++ b/src/main/java/dev/mednikov/social/users/config/ObjectMapperConfig.java @@ -1,5 +1,7 @@ package dev.mednikov.social.users.config; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,6 +12,7 @@ public class ObjectMapperConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); return objectMapper; } } diff --git a/src/main/java/dev/mednikov/social/users/domain/OnboardRequestDto.java b/src/main/java/dev/mednikov/social/users/domain/OnboardRequestDto.java new file mode 100644 index 0000000..2d39992 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/domain/OnboardRequestDto.java @@ -0,0 +1,32 @@ +package dev.mednikov.social.users.domain; + +public final class OnboardRequestDto { + + private String organizationName; + private String description; + private boolean student; + + public String getOrganizationName() { + return organizationName; + } + + public void setOrganizationName(String organizationName) { + this.organizationName = organizationName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isStudent() { + return student; + } + + public void setStudent(boolean student) { + this.student = student; + } +} diff --git a/src/main/java/dev/mednikov/social/users/domain/UserProfileDto.java b/src/main/java/dev/mednikov/social/users/domain/UserProfileDto.java new file mode 100644 index 0000000..af18e5d --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/domain/UserProfileDto.java @@ -0,0 +1,86 @@ +package dev.mednikov.social.users.domain; + +public final class UserProfileDto { + + private String id; + private String firstName; + private String lastName; + private String email; + private boolean active; + private boolean onboarded; + private boolean emailVerified; + private String avatarUrl; + private String headline; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public boolean isOnboarded() { + return onboarded; + } + + public void setOnboarded(boolean onboarded) { + this.onboarded = onboarded; + } + + public boolean isEmailVerified() { + return emailVerified; + } + + public void setEmailVerified(boolean emailVerified) { + this.emailVerified = emailVerified; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getHeadline() { + return headline; + } + + public void setHeadline(String headline) { + this.headline = headline; + } +} diff --git a/src/main/java/dev/mednikov/social/users/domain/UserProfileDtoMapper.java b/src/main/java/dev/mednikov/social/users/domain/UserProfileDtoMapper.java new file mode 100644 index 0000000..90a6912 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/domain/UserProfileDtoMapper.java @@ -0,0 +1,24 @@ +package dev.mednikov.social.users.domain; + +import dev.mednikov.social.users.models.User; + +import java.util.function.Function; + +public final class UserProfileDtoMapper implements Function { + + @Override + public UserProfileDto apply(User user) { + UserProfileDto result = new UserProfileDto(); + result.setId(user.getId().toString()); + result.setFirstName(user.getFirstName()); + result.setLastName(user.getLastName()); + result.setEmail(user.getEmail()); + result.setHeadline(user.getHeadline()); + result.setAvatarUrl(user.getAvatarUrl()); + result.setActive(user.getActive()); + result.setEmailVerified(user.getEmailVerified()); + result.setOnboarded(user.getOnboarded()); + return result; + } + +} diff --git a/src/main/java/dev/mednikov/social/users/events/UserCreatedEvent.java b/src/main/java/dev/mednikov/social/users/events/UserCreatedEvent.java new file mode 100644 index 0000000..07f1904 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/events/UserCreatedEvent.java @@ -0,0 +1,18 @@ +package dev.mednikov.social.users.events; + +import dev.mednikov.social.users.models.User; +import org.springframework.context.ApplicationEvent; + +public final class UserCreatedEvent extends ApplicationEvent { + + private final User user; + + public UserCreatedEvent(Object source, User user) { + super(source); + this.user = user; + } + + public User getUser() { + return user; + } +} diff --git a/src/main/java/dev/mednikov/social/users/events/UserUpdatedEvent.java b/src/main/java/dev/mednikov/social/users/events/UserUpdatedEvent.java new file mode 100644 index 0000000..43c6603 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/events/UserUpdatedEvent.java @@ -0,0 +1,19 @@ +package dev.mednikov.social.users.events; + +import dev.mednikov.social.users.models.User; +import org.springframework.context.ApplicationEvent; + +public final class UserUpdatedEvent extends ApplicationEvent { + + private final User user; + + public UserUpdatedEvent(Object source, User user) { + super(source); + this.user = user; + } + + public User getUser() { + return user; + } + +} diff --git a/src/main/java/dev/mednikov/social/users/exceptions/UserAlreadyOnboardedException.java b/src/main/java/dev/mednikov/social/users/exceptions/UserAlreadyOnboardedException.java new file mode 100644 index 0000000..f1c3eec --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/exceptions/UserAlreadyOnboardedException.java @@ -0,0 +1,9 @@ +package dev.mednikov.social.users.exceptions; + +public class UserAlreadyOnboardedException extends ObjectAlreadyExistsException{ + + public UserAlreadyOnboardedException(Long userId) { + super("User id " + userId.toString() + " is already onboarded"); + } + +} diff --git a/src/main/java/dev/mednikov/social/users/messaging/MessageService.java b/src/main/java/dev/mednikov/social/users/messaging/MessageService.java new file mode 100644 index 0000000..4e6742c --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/messaging/MessageService.java @@ -0,0 +1,50 @@ +package dev.mednikov.social.users.messaging; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.mednikov.social.users.events.UserCreatedEvent; +import dev.mednikov.social.users.events.UserUpdatedEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +@Component +public class MessageService { + + private final static Logger logger = LoggerFactory.getLogger(MessageService.class); + + private final ObjectMapper objectMapper; + private final RabbitTemplate rabbitTemplate; + + public MessageService(ObjectMapper objectMapper, RabbitTemplate rabbitTemplate) { + this.objectMapper = objectMapper; + this.rabbitTemplate = rabbitTemplate; + } + + @EventListener + @Async + public void onUserCreatedEventListener (UserCreatedEvent event) { + UserPayload payload = new UserPayload(event.getUser()); + try { + String userEncoded = this.objectMapper.writeValueAsString(payload); + this.rabbitTemplate.convertAndSend("", "social_users_created", userEncoded); + } catch (Exception ex){ + logger.error(ex.getMessage()); + } + } + + @EventListener + @Async + public void onUserUpdatedEventListener (UserUpdatedEvent event) { + UserPayload payload = new UserPayload(event.getUser()); + try { + String userEncoded = this.objectMapper.writeValueAsString(payload); + this.rabbitTemplate.convertAndSend("", "social_users_updated", userEncoded); + } catch (Exception ex){ + logger.error(ex.getMessage()); + } + } + +} diff --git a/src/main/java/dev/mednikov/social/users/messaging/UserPayload.java b/src/main/java/dev/mednikov/social/users/messaging/UserPayload.java new file mode 100644 index 0000000..b7a069d --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/messaging/UserPayload.java @@ -0,0 +1,112 @@ +package dev.mednikov.social.users.messaging; + +import dev.mednikov.social.users.models.User; + +final class UserPayload { + + private String id; + private String firstName; + private String lastName; + private String email; + private String avatarUrl; + private boolean emailVerified; + private boolean active; + private boolean onboarded; + private String headline; + private Long version; + + UserPayload() {} + + UserPayload(User user) { + this.id = user.getId().toString(); + this.firstName = user.getFirstName(); + this.lastName = user.getLastName(); + this.email = user.getEmail(); + this.avatarUrl = user.getAvatarUrl(); + this.emailVerified = user.getEmailVerified(); + this.active = user.getActive(); + this.onboarded = user.getOnboarded(); + this.headline = user.getHeadline(); + this.version = user.getVersion(); + } + + String getId() { + return id; + } + + String getFirstName() { + return firstName; + } + + String getLastName() { + return lastName; + } + + String getEmail() { + return email; + } + + String getAvatarUrl() { + return avatarUrl; + } + + boolean isEmailVerified() { + return emailVerified; + } + + boolean isActive() { + return active; + } + + boolean isOnboarded() { + return onboarded; + } + + String getHeadline() { + return headline; + } + + Long getVersion() { + return version; + } + + public void setId(String id) { + this.id = id; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public void setEmailVerified(boolean emailVerified) { + this.emailVerified = emailVerified; + } + + public void setActive(boolean active) { + this.active = active; + } + + public void setOnboarded(boolean onboarded) { + this.onboarded = onboarded; + } + + public void setHeadline(String headline) { + this.headline = headline; + } + + public void setVersion(Long version) { + this.version = version; + } +} diff --git a/src/main/java/dev/mednikov/social/users/models/User.java b/src/main/java/dev/mednikov/social/users/models/User.java new file mode 100644 index 0000000..f737f05 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/models/User.java @@ -0,0 +1,144 @@ +package dev.mednikov.social.users.models; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@Table(name = "users_user") +public class User { + + @Id private Long id; + @Column(nullable = false, unique = true, name = "auth_id") private UUID authId; + @Column(nullable = false, name = "email", unique = true) private String email; + @Column(nullable = false, name = "first_name") private String firstName; + @Column(nullable = false, name = "last_name") private String lastName; + @Column(nullable = false, name = "avatar_url") private String avatarUrl; + @Column(nullable = false, name = "headline") private String headline; + @Column(nullable = false, name = "is_active") private Boolean active; + @Column(nullable = false, name = "is_email_verified") private Boolean emailVerified; + @Column(nullable = false, name = "is_onboarded") private Boolean onboarded; + @Column(nullable = false, name = "version") private Long version; + @Column(name = "created_at") @CreationTimestamp private LocalDateTime createdAt; + @Column(name = "updated_at") @UpdateTimestamp private LocalDateTime updatedAt; + + @Override + public final boolean equals(Object o) { + if (!(o instanceof User user)) return false; + + return authId.equals(user.authId) + && email.equals(user.email) + && firstName.equals(user.firstName) + && lastName.equals(user.lastName) + && active.equals(user.active) + && onboarded.equals(user.onboarded) + && version.equals(user.version); + } + + @Override + public int hashCode() { + int result = authId.hashCode(); + result = 31 * result + email.hashCode(); + result = 31 * result + firstName.hashCode(); + result = 31 * result + lastName.hashCode(); + result = 31 * result + active.hashCode(); + result = 31 * result + onboarded.hashCode(); + result = 31 * result + version.hashCode(); + return result; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public Boolean getOnboarded() { + return onboarded; + } + + public void setOnboarded(Boolean onboarded) { + this.onboarded = onboarded; + } + + public Boolean getEmailVerified() { + return emailVerified; + } + + public void setEmailVerified(Boolean emailVerified) { + this.emailVerified = emailVerified; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getHeadline() { + return headline; + } + + public void setHeadline(String headline) { + this.headline = headline; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public UUID getAuthId() { + return authId; + } + + public void setAuthId(UUID authId) { + this.authId = authId; + } + +} diff --git a/src/main/java/dev/mednikov/social/users/repositories/IdentifierGenerator.java b/src/main/java/dev/mednikov/social/users/repositories/IdentifierGenerator.java new file mode 100644 index 0000000..6b2f9d0 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/repositories/IdentifierGenerator.java @@ -0,0 +1,7 @@ +package dev.mednikov.social.users.repositories; + +public interface IdentifierGenerator { + + public Long getNextId(); + +} diff --git a/src/main/java/dev/mednikov/social/users/repositories/IdentifierGeneratorImpl.java b/src/main/java/dev/mednikov/social/users/repositories/IdentifierGeneratorImpl.java new file mode 100644 index 0000000..e84c15f --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/repositories/IdentifierGeneratorImpl.java @@ -0,0 +1,17 @@ +package dev.mednikov.social.users.repositories; + +import cn.hutool.core.lang.generator.SnowflakeGenerator; + +public class IdentifierGeneratorImpl implements IdentifierGenerator { + + private final SnowflakeGenerator snowflakeGenerator; + + public IdentifierGeneratorImpl() { + this.snowflakeGenerator = new SnowflakeGenerator(); + } + + @Override + public Long getNextId() { + return this.snowflakeGenerator.next(); + } +} diff --git a/src/main/java/dev/mednikov/social/users/repositories/UserRepository.java b/src/main/java/dev/mednikov/social/users/repositories/UserRepository.java new file mode 100644 index 0000000..4e96f75 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/repositories/UserRepository.java @@ -0,0 +1,13 @@ +package dev.mednikov.social.users.repositories; + +import dev.mednikov.social.users.models.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface UserRepository extends JpaRepository { + + Optional findByAuthId (UUID authId); + +} diff --git a/src/main/java/dev/mednikov/social/users/services/CurrentUserService.java b/src/main/java/dev/mednikov/social/users/services/CurrentUserService.java new file mode 100644 index 0000000..983e5e8 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/services/CurrentUserService.java @@ -0,0 +1,10 @@ +package dev.mednikov.social.users.services; + +import dev.mednikov.social.users.models.User; +import org.springframework.security.oauth2.jwt.Jwt; + +public interface CurrentUserService { + + User getCurrentUser (Jwt authPrincipal); + +} diff --git a/src/main/java/dev/mednikov/social/users/services/CurrentUserServiceImpl.java b/src/main/java/dev/mednikov/social/users/services/CurrentUserServiceImpl.java new file mode 100644 index 0000000..8396ad9 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/services/CurrentUserServiceImpl.java @@ -0,0 +1,90 @@ +package dev.mednikov.social.users.services; + +import dev.mednikov.social.users.events.UserCreatedEvent; +import dev.mednikov.social.users.events.UserUpdatedEvent; +import dev.mednikov.social.users.models.User; +import dev.mednikov.social.users.repositories.IdentifierGenerator; +import dev.mednikov.social.users.repositories.UserRepository; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.UUID; + +@Service +public class CurrentUserServiceImpl implements CurrentUserService { + + private final IdentifierGenerator identifierGenerator; + private final UserRepository userRepository; + private final ApplicationEventPublisher eventPublisher; + + public CurrentUserServiceImpl(IdentifierGenerator identifierGenerator, UserRepository userRepository, ApplicationEventPublisher eventPublisher) { + this.identifierGenerator = identifierGenerator; + this.userRepository = userRepository; + this.eventPublisher = eventPublisher; + } + + @Override + public User getCurrentUser(Jwt authPrincipal) { + UUID authId = UUID.fromString(authPrincipal.getSubject()); + Optional userResult = this.userRepository.findByAuthId(authId); + if (userResult.isEmpty()) { + // Create new user + User user = new User(); + user.setAuthId(authId); + user.setFirstName(authPrincipal.getClaim("given_name")); + user.setLastName(authPrincipal.getClaim("family_name")); + user.setEmailVerified(authPrincipal.getClaim("email_verified")); + user.setActive(true); + user.setOnboarded(false); + user.setId(this.identifierGenerator.getNextId()); + user.setVersion(1L); + user.setAvatarUrl(""); + user.setHeadline(""); + + // Set email + String email = authPrincipal.getClaim("email"); + user.setEmail(email); + + User result = this.userRepository.save(user); + UserCreatedEvent event = new UserCreatedEvent(this, result); + this.eventPublisher.publishEvent(event); + return result; + } else { + User user = userResult.get(); + // Check if updates are needed + if (requiresUpdate(user, authPrincipal)) { + // Handle update + String email = authPrincipal.getClaim("email"); + user.setEmail(email); + user.setAvatarUrl(""); + user.setFirstName(authPrincipal.getClaim("given_name")); + user.setLastName(authPrincipal.getClaim("family_name")); + user.setEmailVerified(authPrincipal.getClaim("email_verified")); + Long newVersion = user.getVersion() + 1; + user.setVersion(newVersion); + User result = this.userRepository.save(user); + UserUpdatedEvent event = new UserUpdatedEvent(this, result); + this.eventPublisher.publishEvent(event); + return result; + } + return user; + } + } + + boolean requiresUpdate (User savedUser, Jwt principal){ + boolean result = !savedUser.getFirstName().equals(principal.getClaim("given_name")); + if (!savedUser.getLastName().equals(principal.getClaim("family_name"))) { + result = true; + } + if (!savedUser.getEmail().equals(principal.getClaim("email"))) { + result = true; + } + if (!savedUser.getEmailVerified().equals(principal.getClaim("email_verified"))) { + result = true; + } + return result; + } + +} diff --git a/src/main/java/dev/mednikov/social/users/services/UserProfileService.java b/src/main/java/dev/mednikov/social/users/services/UserProfileService.java new file mode 100644 index 0000000..38d8cc7 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/services/UserProfileService.java @@ -0,0 +1,14 @@ +package dev.mednikov.social.users.services; + +import dev.mednikov.social.users.domain.OnboardRequestDto; +import dev.mednikov.social.users.domain.UserProfileDto; +import dev.mednikov.social.users.models.User; + +import java.util.Optional; + +public interface UserProfileService { + + UserProfileDto onboardCurrentUser (User currentUser, OnboardRequestDto request); + + Optional getUserProfileById (Long id); +} diff --git a/src/main/java/dev/mednikov/social/users/services/UserProfileServiceImpl.java b/src/main/java/dev/mednikov/social/users/services/UserProfileServiceImpl.java new file mode 100644 index 0000000..d4e2fcc --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/services/UserProfileServiceImpl.java @@ -0,0 +1,56 @@ +package dev.mednikov.social.users.services; + +import dev.mednikov.social.users.domain.OnboardRequestDto; +import dev.mednikov.social.users.domain.UserProfileDto; +import dev.mednikov.social.users.domain.UserProfileDtoMapper; +import dev.mednikov.social.users.events.UserUpdatedEvent; +import dev.mednikov.social.users.exceptions.UserAlreadyOnboardedException; +import dev.mednikov.social.users.models.User; +import dev.mednikov.social.users.repositories.UserRepository; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class UserProfileServiceImpl implements UserProfileService { + + private final static UserProfileDtoMapper mapper = new UserProfileDtoMapper(); + + private final UserRepository userRepository; + private final ApplicationEventPublisher eventPublisher; + + public UserProfileServiceImpl(UserRepository userRepository, ApplicationEventPublisher eventPublisher) { + this.userRepository = userRepository; + this.eventPublisher = eventPublisher; + } + + @Override + public UserProfileDto onboardCurrentUser(User currentUser, OnboardRequestDto request) { + if (currentUser.getOnboarded()){ + // User is already onboarded + throw new UserAlreadyOnboardedException(currentUser.getId()); + } + if (request.isStudent()){ + currentUser.setHeadline("Student @ " + request.getOrganizationName()); + } else { + currentUser.setHeadline(request.getDescription() + " @ " + request.getOrganizationName()); + } + currentUser.setOnboarded(true); + Long newVersion = currentUser.getVersion() + 1; + currentUser.setVersion(newVersion); + + User updatedUser = this.userRepository.save(currentUser); + + UserUpdatedEvent event = new UserUpdatedEvent(this, updatedUser); + this.eventPublisher.publishEvent(event); + + return mapper.apply(updatedUser); + } + + @Override + public Optional getUserProfileById(Long id) { + return this.userRepository.findById(id).map(mapper); + } + +} diff --git a/src/main/java/dev/mednikov/social/users/web/UserProfileRestController.java b/src/main/java/dev/mednikov/social/users/web/UserProfileRestController.java new file mode 100644 index 0000000..ee1bead --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/web/UserProfileRestController.java @@ -0,0 +1,38 @@ +package dev.mednikov.social.users.web; + +import dev.mednikov.social.users.domain.OnboardRequestDto; +import dev.mednikov.social.users.domain.UserProfileDto; +import dev.mednikov.social.users.models.User; +import dev.mednikov.social.users.services.CurrentUserService; +import dev.mednikov.social.users.services.UserProfileService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; + +@RestController +@RequestMapping("/profiles") +public class UserProfileRestController { + + private final UserProfileService userProfileService; + private final CurrentUserService currentUserService; + + public UserProfileRestController(UserProfileService userProfileService, CurrentUserService currentUserService) { + this.userProfileService = userProfileService; + this.currentUserService = currentUserService; + } + + @PostMapping("/onboard") + public @ResponseBody UserProfileDto onboardCurrentUser (@AuthenticationPrincipal Jwt jwt, @RequestBody OnboardRequestDto body){ + User currentUser = this.currentUserService.getCurrentUser(jwt); + return this.userProfileService.onboardCurrentUser(currentUser, body); + } + + @GetMapping("/user/{userId}") + public ResponseEntity getUserProfile (@PathVariable Long userId){ + Optional result = this.userProfileService.getUserProfileById(userId); + return ResponseEntity.of(result); + } +} diff --git a/src/main/java/dev/mednikov/social/users/web/UserRestController.java b/src/main/java/dev/mednikov/social/users/web/UserRestController.java new file mode 100644 index 0000000..c283703 --- /dev/null +++ b/src/main/java/dev/mednikov/social/users/web/UserRestController.java @@ -0,0 +1,32 @@ +package dev.mednikov.social.users.web; + +import dev.mednikov.social.users.domain.UserProfileDto; +import dev.mednikov.social.users.domain.UserProfileDtoMapper; +import dev.mednikov.social.users.models.User; +import dev.mednikov.social.users.services.CurrentUserService; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/users") +public class UserRestController { + + private final CurrentUserService currentUserService; + + public UserRestController(CurrentUserService currentUserService) { + this.currentUserService = currentUserService; + } + + @GetMapping("/current") + public @ResponseBody UserProfileDto getCurrentUser (@AuthenticationPrincipal Jwt jwt){ + User user = this.currentUserService.getCurrentUser(jwt); + UserProfileDtoMapper mapper = new UserProfileDtoMapper(); + return mapper.apply(user); + } + + +} diff --git a/src/main/resources/db/migration/V1__initial.sql b/src/main/resources/db/migration/V1__initial.sql new file mode 100644 index 0000000..2330f4f --- /dev/null +++ b/src/main/resources/db/migration/V1__initial.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS users_user ( + id BIGINT PRIMARY KEY, + auth_id UUID NOT NULL UNIQUE, + email VARCHAR(255) NOT NULL UNIQUE, + first_name VARCHAR(255) NOT NULL, + last_name VARCHAR(255) NOT NULL, + avatar_url VARCHAR(255) NOT NULL, + is_active BOOLEAN NOT NULL, + is_onboarded BOOLEAN NOT NULL, + is_email_verified BOOLEAN NOT NULL, + version BIGINT NOT NULL, + headline VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file