Skip to content

Commit

Permalink
Merge pull request #9 from harmlessprince/tenant
Browse files Browse the repository at this point in the history
tenant implemented with index
  • Loading branch information
harmlessprince authored Feb 1, 2025
2 parents 566f909 + c29fa7f commit 0f4847e
Show file tree
Hide file tree
Showing 33 changed files with 430 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.util.Optional;

public interface BrandRepository extends MongoRepository<Brand, String> {
Optional<Brand> findByName(String name);
Optional<Brand> findFirstByName(String name);
List<Brand> findAllByName(String name);
List<Brand> findByNameIn(List<String> names);
List<Brand> findBySlugIn(List<String> slugs);
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/harmlessprince/ecommerceApi/cart/Cart.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,6 @@ public class Cart extends BaseEntity {
@DBRef
Product product;

@Indexed
private String tenantId;
}
14 changes: 14 additions & 0 deletions src/main/java/com/harmlessprince/ecommerceApi/color/ColorDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.harmlessprince.ecommerceApi.color;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
@AllArgsConstructor
public class ColorDTO {
private String id;
private String name;
private String code;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import java.util.Optional;

public interface ColorRepository extends MongoRepository<Color, String> {
Optional<Color> findByName(String name);
Optional<Color> findByCode(String code);

Optional<Color> findFirstByName(String name);
Optional<Color> findFirstByCode(String code);
Optional<Color> findFirstById(String colorId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.harmlessprince.ecommerceApi.jwt.JwtService;
import com.harmlessprince.ecommerceApi.user.User;
import com.harmlessprince.ecommerceApi.user.UserService;
import com.harmlessprince.ecommerceApi.custom.AppConstants;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -13,7 +14,6 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
Expand All @@ -23,6 +23,7 @@
import java.io.IOException;
import java.util.Collection;


//https://github.com/jwtk/jjwt?tab=readme-ov-file#creating-a-jwt
@Component
@Slf4j
Expand All @@ -32,6 +33,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final UserService userService;


JwtAuthenticationFilter(HandlerExceptionResolver handlerExceptionResolver, JwtService jwtService, UserDetailsService userDetailsService, UserService userService) {
this.handlerExceptionResolver = handlerExceptionResolver;
this.jwtService = jwtService;
Expand All @@ -58,6 +60,15 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull H
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null && email != null) {
User userDetails = (User) userDetailsService.loadUserByUsername(email);
log.info("User found with tenant {}", userDetails.getTenant());
if (userDetails.getTenant() != null) {
request.setAttribute(AppConstants.CURRENT_USER_TENANT_ID, userDetails.getTenant().getId());
}
if (userDetails.getRole() != null) {
request.setAttribute(AppConstants.CURRENT_USER_ROLE_NAME, userDetails.getRole().getName());
request.setAttribute(AppConstants.CURRENT_USER_ROLE_ID, userDetails.getRole().getId());
}

Collection<? extends GrantedAuthority> userAuthorities = userService.getAuthorities(userDetails.getRole());
if (jwtService.isTokenValid(token, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userAuthorities);
Expand All @@ -71,4 +82,4 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull H
handlerExceptionResolver.resolveException(request, response, null, e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.harmlessprince.ecommerceApi.configs;

import com.harmlessprince.ecommerceApi.interceptors.TenantInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TenantInterceptor());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.harmlessprince.ecommerceApi.contexts;

import com.harmlessprince.ecommerceApi.tenant.TenantService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

@AllArgsConstructor
@Component
public class TenantContext {
private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
public static void setCurrentTenant(String tenantId) {
currentTenant.set(tenantId);
}

public static String getCurrentTenant() {
return currentTenant.get();
}

public static void clear() {
currentTenant.remove();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.harmlessprince.ecommerceApi.custom;

public class AppConstants {
public static final String CURRENT_USER_TENANT_ID = "CURRENT_USER_TENANT_ID";
public static final String CURRENT_TENANT_ID_FROM_HEADER = "X-Tenant-Id";
public static final String CURRENT_USER_ROLE_ID = "CURRENT_USER_ROLE_ID";
public static final String CURRENT_USER_ROLE_NAME = "CURRENT_USER_ROLE_NAME";

// ROLES
public static final String SUPER_ADMIN = "super_admin";
public static final String ADMIN = "admin";
public static final String CUSTOMER = "customer";
public static final String STORE_OWNER = "store_owner";


//PREFIX
public static final String TENANT_PREFIX = "tao_commerce_tenant_:";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.harmlessprince.ecommerceApi.interceptors;

import com.harmlessprince.ecommerceApi.contexts.TenantContext;
import com.harmlessprince.ecommerceApi.exceptions.CustomBadRequestException;
import com.harmlessprince.ecommerceApi.custom.AppConstants;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Slf4j
public class TenantInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String requestURI = request.getRequestURI();
if (isPathAllowed(requestURI)) {
return true;
}
String currentTenantFromAttribute = (String) request.getAttribute(AppConstants.CURRENT_USER_TENANT_ID);
String currentUserRole = (String) request.getAttribute(AppConstants.CURRENT_USER_ROLE_NAME);
String currentTenantFromHeader = request.getHeader(AppConstants.CURRENT_TENANT_ID_FROM_HEADER);
log.info("Current tenant id from currentTenantFromAttribute: {}", currentTenantFromAttribute);
log.info("Current tenant id from currentTenantFromHeader: {}", currentTenantFromHeader);
String finalTenantId = StringUtils.hasText(currentTenantFromAttribute)
? currentTenantFromAttribute
: currentTenantFromHeader;

if (!StringUtils.hasText(finalTenantId) && !Objects.equals(currentUserRole, AppConstants.SUPER_ADMIN)) {
throw new CustomBadRequestException("Tenant ID is missing from headers");
}
TenantContext.setCurrentTenant(finalTenantId);
return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
TenantContext.clear();
}

private List<String> allowedPaths(){
List<String> allowedPaths = new ArrayList<>();
allowedPaths.add("auth/login");
allowedPaths.add("auth/logout");
allowedPaths.add("auth/register");
allowedPaths.add("countries");
allowedPaths.add("countries");
allowedPaths.add("local-governments");
allowedPaths.add("states");
allowedPaths.add("payment-methods");
return allowedPaths;
}

private boolean isPathAllowed(String url) {
// Remove the version part (e.g., "/v1/") from the URL
String path = url.replaceFirst("^/v\\d+/","");

// Get the list of allowed paths
List<String> allowedPaths = allowedPaths();

// Check if the remaining path is in the allowed paths
for (String allowedPath : allowedPaths) {
if (path.contains(allowedPath)) {
return true; // Return early when a match is found
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.harmlessprince.ecommerceApi.user.User;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

Expand Down Expand Up @@ -39,4 +40,6 @@ public class Order extends BaseEntity {
private PromotionResource promotion;
private AnonymousUser anonymousUser;
private AddressResource address;
@Indexed
private String tenantId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.harmlessprince.ecommerceApi.user.User;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

Expand All @@ -28,4 +29,6 @@ public class OrderLine extends BaseEntity {
private User user;
@DBRef
private Order order;
@Indexed
private String tenantId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.context.annotation.Description;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.List;
Expand Down Expand Up @@ -69,8 +70,13 @@ public class Product extends BaseEntity {
private Integer rating;
private List<String> features;
private Set<ProductSpecificationDTO> specifications;

@DBRef
private Set<ProductColor> variations;

@Indexed
private String tenantId;

public void setNewPrice(Double price) {
if (this.discount != null && this.discount > 0) {
this.newPrice = Utils.roundTo(price - Utils.percentageOf(this.getDiscount(), price), 2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public Product toEntity(ProductRequest request, Optional<Brand> brand, Optional<
List<ProductColorDTO> variations = request.getVariations();
List<ProductColor> productColors = new ArrayList<>();
for (ProductColorDTO variation : variations) {
Optional<Color> color1 = colorRepository.findByName(variation.getColor());
Optional<Color> color1 = colorRepository.findFirstByName(variation.getColor());

ProductColor productColor = new ProductColor();
productColor.setImage(variation.getImage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public Product updateVariation(Product product, ProductColor variation, @Valid U

product.getVariations().forEach(v -> {
if (v.getId().equals(variation.getId())) {
Optional<Color> color1 = colorRepository.findByName(request.getColor());
Optional<Color> color1 = colorRepository.findFirstByName(request.getColor());
v.setImage(Optional.ofNullable(request.getImage()).orElse(variation.getImage()));
color1.ifPresent(v::setColor);
v.setCustomColor(Optional.ofNullable(request.getCustomColor()).orElse(variation.getCustomColor()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ public class ProductCategory extends BaseEntity {
@Indexed(unique = true)
private String slug;

@Indexed
private String tenantId;

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.harmlessprince.ecommerceApi.seeders;

import com.harmlessprince.ecommerceApi.Utils;
import com.harmlessprince.ecommerceApi.contracts.ISeeder;
import com.harmlessprince.ecommerceApi.brand.Brand;
import com.harmlessprince.ecommerceApi.brand.BrandRepository;
Expand Down Expand Up @@ -30,10 +31,11 @@ public void run() {
return;
}
Arrays.stream(brands).forEach(brand -> {
Optional<Brand> brandOptional = brandRepository.findByName(brand);
Optional<Brand> brandOptional = brandRepository.findFirstByName(brand);
if(brandOptional.isEmpty()) {
Brand brandToAdd = new Brand();
brandToAdd.setName(brand);
brandToAdd.setSlug(Utils.slugify(brand));
brandRepository.save(brandToAdd);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void run() {
for (String[] color : colors) {
String name = color[0];
String code = color[1];
if (colorRepository.findByName(name).isEmpty()) {
if (colorRepository.findFirstByName(name).isEmpty()) {
Color col = new Color();
col.setName(name);
col.setCode(code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,21 @@ public class DatabaseSeeder {
private final ColorSeeder colorSeeder;
private final RoleSeeder roleSeeder;

@PostConstruct
// @PostConstruct
private void seed() {
roleSeeder.run();
userSeeder.run();
// brandSeeder.run();
// countrySeeder.run();
// stateSeeder.run();
// colorSeeder.run();
// specificationSeeder.run();
// localGovernmentSeeder.run();
// paymentMethodSeeder.run();
// productCategorySeeder.run();
// variationSeeder.run();
// variationOptionSeeder.run();
// fileSeeder.run();
// productSeeder.run();
brandSeeder.run();
countrySeeder.run();
stateSeeder.run();
colorSeeder.run();
specificationSeeder.run();
localGovernmentSeeder.run();
paymentMethodSeeder.run();
productCategorySeeder.run();
variationSeeder.run();
variationOptionSeeder.run();
fileSeeder.run();
productSeeder.run();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ private void seedChildrenCategories(String[] categories) {
ProductCategory newChildItem = new ProductCategory();
newChildItem.setName(itemCategory);
newChildItem.setParentId(parentItem.get().getId());
newChildItem.setSlug(Utils.slugify(itemCategory));
productCategoryRepository.save(newChildItem);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class RoleSeeder implements ISeeder {
@Override
public void run() {

String[] roles = new String[]{"Customer", "Admin", "Super Admin",};
String[] roles = new String[]{"Customer", "Admin", "Super Admin", "Store Owner",};

for (String role : roles) {
Role newRole = new Role();
Expand Down
Loading

0 comments on commit 0f4847e

Please sign in to comment.