Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions deploy/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
DATASOURCE_URL=jdbc:mysql://mysql:3306/commercifydb?createDatabaseIfNotExist=true
DATASOURCE_USERNAME=commercifyapp
DATASOURCE_PASSWORD=password123!
STRIPE_SECRET_TEST_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_WEBHOOK_ENDPOINT=https://<insert_host>/api/v2/payments/webhooks/stripe/callback
JWT_SECRET_KEY=
ADMIN_EMAIL=admin@commercify.app
ADMIN_PASSWORD=admin
Expand All @@ -18,4 +15,5 @@ MOBILEPAY_WEBHOOK_CALLBACK=https://<insert_host>/api/v2/payments/webhooks/mobile
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_HOST=smtp.ethereal.email
MAIL_PORT=587
MAIL_PORT=587
FRONTEND_HOST=http://localhost:3000
11 changes: 5 additions & 6 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ services:
- MYSQL_PASSWORD=password123!
- MYSQL_ROOT_PASSWORD=rootpassword
ports:
- "3307:3306"
- "3306:3306"
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ]
timeout: 5s
Expand All @@ -26,11 +26,9 @@ services:
- "6091:6091"
environment:
- SPRING_PROFILES_ACTIVE=docker
- SPRING_DATASOURCE_URL=${DATASOURCE_URL}
- SPRING_DATASOURCE_USERNAME=${DATASOURCE_USERNAME}
- SPRING_DATASOURCE_PASSWORD=${DATASOURCE_PASSWORD}
- STRIPE_SECRET_TEST_KEY=${STRIPE_SECRET_TEST_KEY}
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
- SPRING_DATASOURCE_URL=jdbc:mysql://host.docker.internal:3306/commercifydb?createDatabaseIfNotExist=true
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=rootpassword
- JWT_SECRET_KEY=${JWT_SECRET_KEY}
- ADMIN_EMAIL=${ADMIN_EMAIL}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
Expand All @@ -45,6 +43,7 @@ services:
- MAIL_PORT=${MAIL_PORT}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- FRONTEND_URL=${FRONTEND_URL}
depends_on:
mysql:
condition: service_healthy
Expand Down
3 changes: 2 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@
<jsonLibrary>jackson2</jsonLibrary>
<outputKind>module</outputKind>
<classPatterns>
<pattern>com.zenfulcode.commercify.api.**.dto.**</pattern>
<!-- <pattern>cfiom.zenfulcode.commercify.api.*.dto.**</pattern>-->
<pattern>com.zenfulcode.commercify.**.dto.**</pattern>
</classPatterns>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import com.zenfulcode.commercify.auth.domain.exception.InvalidAuthenticationException;
import com.zenfulcode.commercify.auth.domain.model.AuthenticatedUser;
import com.zenfulcode.commercify.shared.interfaces.ApiResponse;
import com.zenfulcode.commercify.user.application.dto.response.UserProfileResponse;
import com.zenfulcode.commercify.user.application.service.UserApplicationService;
import com.zenfulcode.commercify.user.domain.model.User;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
Expand All @@ -24,6 +26,7 @@
public class AuthController {
private final AuthenticationApplicationService authService;
private final UserApplicationService userService;
private final UserApplicationService userApplicationService;

@PostMapping("/nextauth")
public ResponseEntity<ApiResponse<NextAuthResponse>> nextAuthSignIn(@RequestBody LoginRequest request) {
Expand Down Expand Up @@ -52,10 +55,26 @@ public ResponseEntity<ApiResponse<NextAuthResponse>> validateSession(@RequestHea
}
}

@GetMapping("/me")
public ResponseEntity<ApiResponse<UserProfileResponse>> getCurrentUser(@RequestHeader("Authorization") String authHeader) {
// Extract token using a domain service method
String token = authService.extractTokenFromHeader(authHeader).orElseThrow(() -> new InvalidAuthenticationException("Invalid authorization header"));

// Validate token through the application service
AuthenticatedUser authenticatedUser = authService.validateAccessToken(token);

// Fetch full user entity from the database
User user = userApplicationService.getUser(authenticatedUser.getUserId());

// Map to response DTO
UserProfileResponse response = UserProfileResponse.fromUser(user);

return ResponseEntity.ok(ApiResponse.success(response));
}

@PostMapping("/signin")
public ResponseEntity<ApiResponse<AuthResponse>> login(@RequestBody LoginRequest request) {
AuthenticationResult result = authService.authenticate(request.toCommand());

AuthResponse response = AuthResponse.from(result);
return ResponseEntity.ok(ApiResponse.success(response));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,18 @@
package com.zenfulcode.commercify.api.auth.dto.response;

import com.zenfulcode.commercify.auth.application.service.AuthenticationResult;
import com.zenfulcode.commercify.auth.domain.model.UserRole;

import java.util.Set;
import java.util.stream.Collectors;
import com.zenfulcode.commercify.user.application.dto.response.UserProfileResponse;

public record AuthResponse(
String accessToken,
String refreshToken,
String tokenType,
String userId,
String username,
String email,
Set<String> roles
UserProfileResponse user
) {
public static AuthResponse from(AuthenticationResult result) {
Set<String> roles = result.user().getRoles().stream()
.map(UserRole::name)
.collect(Collectors.toSet());

return new AuthResponse(
result.accessToken(),
result.refreshToken(),
"Bearer",
result.user().getUserId().toString(),
result.user().getUsername(),
result.user().getEmail(),
roles
UserProfileResponse.fromUser(result.userInfo())
);
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
package com.zenfulcode.commercify.api.auth.dto.response;

import com.zenfulcode.commercify.user.application.dto.response.UserProfileResponse;
import com.zenfulcode.commercify.auth.application.service.AuthenticationResult;
import com.zenfulcode.commercify.auth.domain.model.AuthenticatedUser;
import com.zenfulcode.commercify.auth.domain.model.UserRole;

import java.util.Set;

public record NextAuthResponse(
String id,
String name,
String email,
UserProfileResponse user,
String accessToken,
String refreshToken,
Set<UserRole> roles
) {
public static NextAuthResponse from(AuthenticationResult result) {
AuthenticatedUser user = result.user();
return new NextAuthResponse(
user.getUserId().toString(),
user.getUsername(),
user.getEmail(),
UserProfileResponse.fromUser(result.userInfo()),
result.accessToken(),
result.refreshToken(),
user.getRoles()
Expand All @@ -28,9 +25,7 @@ public static NextAuthResponse from(AuthenticationResult result) {

public static NextAuthResponse fromUser(AuthenticatedUser user) {
return new NextAuthResponse(
user.getUserId().toString(),
user.getUsername(),
user.getEmail(),
UserProfileResponse.fromAuthenticatedUser(user),
null,
null,
user.getRoles()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public ResponseEntity<ApiResponse<CreateOrderResponse>> createOrder(
return ResponseEntity.ok(ApiResponse.success(response));
}

// TODO: SUPER NOT SECURE AND NOT GOOD
@GetMapping("/{orderId}")
public ResponseEntity<ApiResponse<OrderDetailsResponse>> getOrder(
@PathVariable String orderId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public record OrderDetailsResponse(
CustomerDetailsResponse customerDetails,
AddressResponse shippingAddress,
AddressResponse billingAddress,
Instant createdAt
Instant createdAt,
Instant updatedAt
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public OrderDetailsResponse toResponse(OrderDetailsDTO dto) {
toAddressResponse(dto.shippingAddress()),
dto.billingAddress() != null ?
toAddressResponse(dto.billingAddress()) : null,
dto.createdAt()
dto.createdAt(),
dto.updatedAt()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
public record UpdateProductRequest(
String name,
String description,
String imageUrl,
Integer stock,
Money price,
Boolean active
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public record ProductSummaryResponse(
String description,
String imageUrl,
Money price,
int stock
int stock,
boolean isActive
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public UpdateProductCommand toCommand(ProductId productId, UpdateProductRequest
ProductUpdateSpec updateSpec = new ProductUpdateSpec(
request.name(),
request.description(),
request.imageUrl(),
request.stock(),
request.price(),
request.active()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ private ProductSummaryResponse toSummaryResponse(Product product) {
product.getDescription(),
product.getImageUrl(),
product.getPrice(),
product.getStock()
product.getStock(),
product.isActive()
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.zenfulcode.commercify.api.users;

import com.zenfulcode.commercify.api.product.dto.response.PageInfo;
import com.zenfulcode.commercify.shared.interfaces.ApiResponse;
import com.zenfulcode.commercify.user.application.dto.request.PagedUserResponse;
import com.zenfulcode.commercify.user.application.dto.response.UserProfileResponse;
import com.zenfulcode.commercify.user.application.service.UserApplicationService;
import com.zenfulcode.commercify.user.domain.model.User;
import com.zenfulcode.commercify.user.domain.valueobject.UserId;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/v2/users")
@RequiredArgsConstructor
public class UserController {
private final UserApplicationService userApplicationService;

@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<PagedUserResponse>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "DESC") String sortDirection) {

Sort.Direction direction = Sort.Direction.fromString(sortDirection.toUpperCase());
PageRequest pageRequest = PageRequest.of(page, size, Sort.by(direction, sortBy));

Page<User> usersPage = userApplicationService.getAllUsers(pageRequest);
PagedUserResponse response = toPagedResponse(usersPage);

return ResponseEntity.ok(ApiResponse.success(response));
}

@GetMapping("/{userId}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<UserProfileResponse>> getUserById(@PathVariable String userId) {
// Fetch user by ID from the database
User user = userApplicationService.getUser(UserId.of(userId));

// Map to response DTO
UserProfileResponse response = UserProfileResponse.fromUser(user);

return ResponseEntity.ok(ApiResponse.success(response));
}

private PagedUserResponse toPagedResponse(Page<User> userPage) {
List<UserProfileResponse> items = userPage.getContent()
.stream()
.map(UserProfileResponse::fromUser)
.toList();

PageInfo pageInfo = new PageInfo(
userPage.getNumber(),
userPage.getSize(),
userPage.getTotalElements(),
userPage.getTotalPages()
);

return new PagedUserResponse(items, pageInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,16 @@ public AuthenticationResult authenticate(LoginCommand command) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(email, password));
AuthenticatedUser authenticatedUser = (AuthenticatedUser) authentication.getPrincipal();

User user = userApplicationService.getUser(userId);

// Generate tokens
String accessToken = tokenService.generateAccessToken(authenticatedUser);
String refreshToken = tokenService.generateRefreshToken(authenticatedUser);

// Publish domain event
eventPublisher.publish(new UserAuthenticatedEvent(this, userId, email, command.isGuest()));

return new AuthenticationResult(accessToken, refreshToken, authenticatedUser);
return new AuthenticationResult(accessToken, refreshToken, authenticatedUser, user);
}

@Transactional(readOnly = true)
Expand All @@ -93,7 +95,7 @@ public AuthenticationResult refreshToken(String refreshToken) {
String newAccessToken = tokenService.generateAccessToken(authenticatedUser);
String newRefreshToken = tokenService.generateRefreshToken(authenticatedUser);

return new AuthenticationResult(newAccessToken, newRefreshToken, authenticatedUser);
return new AuthenticationResult(newAccessToken, newRefreshToken, authenticatedUser, user);
}

public Optional<String> extractTokenFromHeader(String authHeader) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.zenfulcode.commercify.auth.application.service;

import com.zenfulcode.commercify.auth.domain.model.AuthenticatedUser;
import com.zenfulcode.commercify.user.domain.model.User;

public record AuthenticationResult(
String accessToken,
String refreshToken,
AuthenticatedUser user
AuthenticatedUser user,
User userInfo
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
public enum UserRole {
ROLE_GUEST,
ROLE_USER,
ROLE_ADMIN,
ROLE_MANAGER
ROLE_ADMIN
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public record OrderDetailsDTO(OrderId id,
CustomerDetails customerDetails,
Address shippingAddress,
Address billingAddress,
Instant createdAt) {
Instant createdAt,
Instant updatedAt) {
public static OrderDetailsDTO fromOrder(Order order) {
return new OrderDetailsDTO(
order.getId(),
Expand All @@ -35,7 +36,8 @@ public static OrderDetailsDTO fromOrder(Order order) {
order.getOrderShippingInfo().toCustomerDetails(),
order.getOrderShippingInfo().toShippingAddress(),
order.getOrderShippingInfo().toBillingAddress(),
order.getCreatedAt()
order.getCreatedAt(),
order.getUpdatedAt()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,8 @@ public BigDecimal calculateTotalRevenue(CalculateTotalRevenueQuery query) {
public int countOrdersInPeriod(CountOrdersInPeriodQuery query) {
return orderDomainService.countOrdersInPeriod(query.startDate(), query.endDate());
}

public void canCaptureOrder(OrderId orderId) {
orderDomainService.canCaptureOrder(orderId);
}
}
Loading