Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
public interface SubscriptionRepository {

List<SubscriptionModel> getAllSubscriptions();

SubscriptionModel getByPlanCode(String planCode);

List<SubscriptionModel> findByPlanType(String planType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public class SubscriptionModel {

private boolean isPopular;

private String planType;
private Integer maxGarments;
private boolean hasAnalytics;
private boolean hasAdvancedReports;

public String getName() {
return name;
}
Expand Down Expand Up @@ -109,7 +114,36 @@ public boolean isPopular() {
public void setPopular(boolean popular) {
isPopular = popular;
}
}

public String getPlanType() {
return planType;
}

public void setPlanType(String planType) {
this.planType = planType;
}

public Integer getMaxGarments() {
return maxGarments;
}

public void setMaxGarments(Integer maxGarments) {
this.maxGarments = maxGarments;
}

public boolean isHasAnalytics() {
return hasAnalytics;
}

public void setHasAnalytics(boolean hasAnalytics) {
this.hasAnalytics = hasAnalytics;
}

public boolean isHasAdvancedReports() {
return hasAdvancedReports;
}

public void setHasAdvancedReports(boolean hasAdvancedReports) {
this.hasAdvancedReports = hasAdvancedReports;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,28 @@ public class UserSubscriptionModel {
private Long id;
private String userEmail;
private String planCode;

// Contadores de uso
private int combinationsUsed; // Total creadas en el período
private int favoritesCount; // Favoritos actuales en BD
private int modelsGenerated; // Total generados en el período

private int combinationsUsed; // Total creadas en el período
private int favoritesCount; // Favoritos actuales en BD
private int modelsGenerated; // Total generados en el período
private int downloadsCount; // Total descargas 2D
private int garmentsUploaded; // Total prendas subidas (marcas)

// Límites del plan (denormalizados para performance)
private Integer maxCombinations; // null = ilimitado
private Integer maxFavorites; // null = ilimitado
private Integer maxModels; // null = ilimitado

private Integer maxCombinations; // null = ilimitado
private Integer maxFavorites; // null = ilimitado
private Integer maxModels; // null = ilimitado
private Integer maxDownloads; // null = ilimitado
private Integer maxGarments; // null = ilimitado (para marcas)

// Metadata
private LocalDateTime subscriptionStart;
private LocalDateTime subscriptionEnd;
private String status; // ACTIVE, CANCELLED, EXPIRED
private String status; // ACTIVE, CANCELLED, EXPIRED
private LocalDateTime createdAt;
private LocalDateTime updatedAt;

public UserSubscriptionModel() {}
public UserSubscriptionModel() {
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,78 @@
package com.outfitlab.project.domain.useCases.fashn;

import com.outfitlab.project.domain.exceptions.FashnApiException;
import com.outfitlab.project.domain.exceptions.PlanLimitExceededException;
import com.outfitlab.project.domain.exceptions.PredictionFailedException;
import com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException;
import com.outfitlab.project.domain.interfaces.repositories.FashnRepository;
import com.outfitlab.project.domain.model.dto.CombineRequestDTO;
import com.outfitlab.project.domain.useCases.subscription.CheckUserPlanLimit;
import com.outfitlab.project.domain.useCases.subscription.IncrementUsageCounter;
import org.springframework.security.core.userdetails.UserDetails;

public class CombinePrendas {

private final String TOPS = "tops";
private final String BOTTOMS = "bottoms";
private final FashnRepository iFashnRepository;
private final CheckUserPlanLimit checkUserPlanLimit;
private final IncrementUsageCounter incrementUsageCounter;

public CombinePrendas(FashnRepository iFashnRepository) {
public CombinePrendas(FashnRepository iFashnRepository,
CheckUserPlanLimit checkUserPlanLimit,
IncrementUsageCounter incrementUsageCounter) {
this.iFashnRepository = iFashnRepository;
this.checkUserPlanLimit = checkUserPlanLimit;
this.incrementUsageCounter = incrementUsageCounter;
}

public String execute(CombineRequestDTO request, UserDetails user) throws FashnApiException, PredictionFailedException {
public String execute(CombineRequestDTO request, UserDetails user)
throws FashnApiException, PredictionFailedException, PlanLimitExceededException,
SubscriptionNotFoundException {
System.out.println(request.toString());

String userEmail = user.getUsername();

// Validar límite de combinaciones
checkUserPlanLimit.execute(userEmail, "combinations");

checkRequestCombine(request.getTop(), request.getBottom());

if (isOnlyTop(request.getTop(), request.getBottom())) return combine(request.getTop(), TOPS, request.getAvatarType(), user);
if (isOnlyBotton(request.getBottom(), request.getTop())) return combine(request.getBottom(), BOTTOMS, request.getAvatarType(), user);
String result;
if (isOnlyTop(request.getTop(), request.getBottom())) {
result = combine(request.getTop(), TOPS, request.getAvatarType(), user);
} else if (isOnlyBotton(request.getBottom(), request.getTop())) {
result = combine(request.getBottom(), BOTTOMS, request.getAvatarType(), user);
} else {
result = combineTopAndBottom(request.getTop(), request.getBottom(), request.getAvatarType(), user);
}

// Incrementar contador de combinaciones después de éxito
incrementUsageCounter.execute(userEmail, "combinations");

return combineTopAndBottom(request.getTop(), request.getBottom(), request.getAvatarType(), user);
return result;
}

private boolean isOnlyTop(String top, String bottom) {
return top != null && (bottom == null || bottom.isBlank());
}

private void checkRequestCombine(String top, String bottom) throws FashnApiException {
if ((top == null || top.isBlank()) && (bottom == null || bottom.isBlank())) throw new FashnApiException("Debe proporcionarse al menos una prenda (superior o inferior).");
if ((top == null || top.isBlank()) && (bottom == null || bottom.isBlank()))
throw new FashnApiException("Debe proporcionarse al menos una prenda (superior o inferior).");
}

private boolean isOnlyBotton(String bottom, String top) {
return bottom != null && (top == null || top.isBlank());
}

private String combine(String garmentUrl, String category, String avatarType, UserDetails user) throws FashnApiException, PredictionFailedException {
private String combine(String garmentUrl, String category, String avatarType, UserDetails user)
throws FashnApiException, PredictionFailedException {
return this.iFashnRepository.pollStatus(this.iFashnRepository.combine(garmentUrl, category, avatarType, user));
}

private String combineTopAndBottom(String top, String bottom, String avatarType, UserDetails user) throws FashnApiException, PredictionFailedException {
private String combineTopAndBottom(String top, String bottom, String avatarType, UserDetails user)
throws FashnApiException, PredictionFailedException {
return this.iFashnRepository.combineTopAndBottom(top, bottom, avatarType, user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@
public class AssignFreePlanToUser {
private final UserSubscriptionRepository userSubscriptionRepository;
private final SubscriptionRepository subscriptionRepository;
private static final String FREE_PLAN_CODE_USER = "user-free-monthly";
private static final String FREE_PLAN_CODE_BRAND = "brand-free-monthly";
private static final String USER_FREE_PLAN_CODE = "user-free-monthly";
private static final String BRAND_FREE_PLAN_CODE = "brand-free-monthly";

public AssignFreePlanToUser(UserSubscriptionRepository userSubscriptionRepository,
SubscriptionRepository subscriptionRepository) {
SubscriptionRepository subscriptionRepository) {
this.userSubscriptionRepository = userSubscriptionRepository;
this.subscriptionRepository = subscriptionRepository;
}

public void execute(String userEmail, boolean isBrand) {
SubscriptionModel freePlan =
subscriptionRepository.getByPlanCode(isBrand ? FREE_PLAN_CODE_USER : FREE_PLAN_CODE_BRAND);
String planCode = isBrand ? BRAND_FREE_PLAN_CODE : USER_FREE_PLAN_CODE;
SubscriptionModel freePlan = subscriptionRepository.getByPlanCode(planCode);

UserSubscriptionModel userSubscription = new UserSubscriptionModel();
userSubscription.setUserEmail(userEmail);
userSubscription.setPlanCode(freePlan.getPlanCode());
Expand All @@ -33,26 +33,26 @@ public void execute(String userEmail, boolean isBrand) {
userSubscription.setMaxModels(parseLimitFromFeature(freePlan.getFeature3()));
userSubscription.setStatus("ACTIVE");
userSubscription.setSubscriptionStart(LocalDateTime.now());

userSubscriptionRepository.save(userSubscription);
}

private Integer parseLimitFromFeature(String feature) {
if (feature == null) {
return null;
}
if (feature.toLowerCase().contains("ilimitado") ||
feature.toLowerCase().contains("ilimitada")) {

if (feature.toLowerCase().contains("ilimitado") ||
feature.toLowerCase().contains("ilimitada")) {
return null;
}

Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher(feature);
if (matcher.find()) {
return Integer.parseInt(matcher.group());
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,51 @@

public class CheckUserPlanLimit {
private final UserSubscriptionRepository userSubscriptionRepository;

public CheckUserPlanLimit(UserSubscriptionRepository userSubscriptionRepository) {
this.userSubscriptionRepository = userSubscriptionRepository;
}
public void execute(String userEmail, String limitType)

public void execute(String userEmail, String limitType)
throws PlanLimitExceededException, SubscriptionNotFoundException {
UserSubscriptionModel subscription =
userSubscriptionRepository.findByUserEmail(userEmail);

switch(limitType) {
UserSubscriptionModel subscription = userSubscriptionRepository.findByUserEmail(userEmail);

switch (limitType) {
case "combinations":
checkLimit(subscription.getCombinationsUsed(),
subscription.getMaxCombinations(),
"combinaciones diarias");
checkLimit(subscription.getCombinationsUsed(),
subscription.getMaxCombinations(),
"combinaciones diarias");
break;
case "favorites":
checkLimit(subscription.getFavoritesCount(),
subscription.getMaxFavorites(),
"favoritos");
checkLimit(subscription.getFavoritesCount(),
subscription.getMaxFavorites(),
"favoritos");
break;
case "3d_models":
checkLimit(subscription.getModelsGenerated(),
subscription.getMaxModels(),
"modelos 3D");
checkLimit(subscription.getModelsGenerated(),
subscription.getMaxModels(),
"modelos 3D");
break;
case "downloads":
checkLimit(subscription.getDownloadsCount(),
subscription.getMaxDownloads(),
"descargas 2D");
break;
}
}
private void checkLimit(int current, Integer max, String feature)

private void checkLimit(int current, Integer max, String feature)
throws PlanLimitExceededException {
if (max != null && current >= max) {
// Si max es null o -1, es ilimitado
if (max == null || max == -1) {
return;
}

if (current >= max) {
throw new PlanLimitExceededException(
"Has alcanzado el límite de " + feature +
" para tu plan. Actualiza a PRO para acceso ilimitado.",
feature, current, max
);
"Has alcanzado el límite de " + feature +
" para tu plan. Actualiza a PRO para acceso ilimitado.",
feature, current, max);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
package com.outfitlab.project.domain.useCases.subscription;

import com.outfitlab.project.domain.model.SubscriptionModel;
import com.outfitlab.project.domain.interfaces.repositories.UserRepository;
import com.outfitlab.project.domain.interfaces.repositories.SubscriptionRepository;
import com.outfitlab.project.domain.model.SubscriptionModel;
import com.outfitlab.project.domain.model.UserModel;
import com.outfitlab.project.domain.exceptions.UserNotFoundException;

import java.util.List;

public class GetAllSubscription {

private final SubscriptionRepository subscriptionRepository;
public GetAllSubscription(SubscriptionRepository subscriptionRepository){
this.subscriptionRepository = subscriptionRepository;
}
private final UserRepository userRepository;

public List<SubscriptionModel> execute(){
return subscriptionRepository.getAllSubscriptions();
public GetAllSubscription(SubscriptionRepository subscriptionRepository, UserRepository userRepository) {
this.subscriptionRepository = subscriptionRepository;
this.userRepository = userRepository;
}

public List<SubscriptionModel> execute(String userEmail) {
if (userEmail == null || userEmail.isEmpty()) {
return subscriptionRepository.findByPlanType("USER");
}

try {
UserModel user = userRepository.findUserByEmail(userEmail);
String planType = (user.getBrand() != null) ? "BRAND" : "USER";
return subscriptionRepository.findByPlanType(planType);
} catch (UserNotFoundException e) {
return subscriptionRepository.findByPlanType("USER");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@
import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.mail.MailException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;

public class RegisterUser {

private static final Logger log = LoggerFactory.getLogger(RegisterUser.class);

private final UserRepository userRepository;
private final UserJpaRepository userJpaRepository;
private final TokenRepository tokenRepository;
Expand Down Expand Up @@ -66,7 +71,12 @@ public UserModel execute(RegisterDTO request) throws UserAlreadyExistsException
+ "<p>Haz click en el enlace para verificar tu cuenta:</p>"
+ "<a href=\"" + verificationLink + "\">Verificar cuenta </a>";

gmailGateway.sendEmail(request.getEmail(), "Verificación de cuenta de Outfit Lab", emailBody);
try {
gmailGateway.sendEmail(request.getEmail(), "Verificación de cuenta de Outfit Lab", emailBody);
} catch (MailException ex) {
// No bloquear el registro si el correo falla (ej. puerto SMTP bloqueado)
log.warn("No se pudo enviar email de verificación a {}: {}", request.getEmail(), ex.getMessage());
}

UserModel newUserModel = new UserModel(
request.getEmail(),
Expand Down
Loading