Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.theurl.bundle.application.command;

import com.neroyun.mediator.Command;
import lombok.Data;

@Data
public class BundleCreateCommand implements Command {
private final String type;
private final String vanity;
private String name;
private String description;
private String image;
private Long ownerId;
private String ownerName;

public BundleCreateCommand(String type, String vanity) {
this.type = type;
this.vanity = vanity;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.theurl.bundle.application.contract;

import io.theurl.bundle.application.dto.BundleCreateDto;
import io.theurl.bundle.application.dto.BundleUpdateDto;
import io.theurl.framework.application.ApplicationService;

import java.util.concurrent.CompletableFuture;

public interface BundleApplicationService extends ApplicationService {
CompletableFuture<String> createAsync(BundleCreateDto data);

CompletableFuture<Void> updateAsync(String vanity, BundleUpdateDto data);

CompletableFuture<Void> deleteAsync(String vanity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.theurl.bundle.application.dto;

import lombok.Data;

@Data
public abstract class BundleBaseDto {
private String name;
private String description;
private String image;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.theurl.bundle.application.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
* DTO for creating a bundle. It extends BundleBaseDto and includes additional fields specific to bundle creation.
* The fields include:
* - type: The type of the bundle.
* - vanity: A unique identifier for the bundle, often used in URLs.
* This DTO is used when a client wants to create a new bundle and needs to provide the necessary information for the creation process.
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class BundleCreateDto extends BundleBaseDto {
/**
* The type of the bundle. This field is used to categorize the bundle and can be used for filtering and searching bundles based on their type.
*/
private String type;
/**
* A unique identifier for the bundle, often used in URLs. This field is used to create a user-friendly and memorable URL for the bundle.
*/
private String vanity;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.theurl.bundle.application.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;

import java.time.LocalDateTime;

@EqualsAndHashCode(callSuper = true)
@Data
public class BundleListDto extends BundleBaseDto {
private String type;
private String vanity;
private int itemsCount;
private int favoriteCount;
private int commentCount;
private int visitCount;
private Long ownerId;
private String ownerName;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.theurl.bundle.application.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
* DTO for updating a bundle. It extends BundleBaseDto and does not add any new fields, but it can be used to differentiate between create and update operations.
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class BundleUpdateDto extends BundleBaseDto {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.theurl.bundle.application.handler;

import com.neroyun.mediator.Handler;
import com.neroyun.mediator.Mediator;
import com.neroyun.mediator.MessageContext;
import io.theurl.bundle.application.command.BundleCreateCommand;
import io.theurl.bundle.domain.aggregate.Bundle;
import io.theurl.bundle.domain.repository.BundleRepository;
import io.theurl.framework.core.BeanScope;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Objects;
import java.util.concurrent.CompletableFuture;

@Component
@Scope(BeanScope.PROTOTYPE)
public class BundleCreateCommandHandler implements Handler<BundleCreateCommand, Void> {
private final BundleRepository repository;
private final Mediator mediator;

public BundleCreateCommandHandler(BundleRepository repository, Mediator mediator) {
this.repository = repository;
this.mediator = mediator;
}

@Override
public CompletableFuture<Void> handleAsync(BundleCreateCommand message, MessageContext context) {
var userId = Long.getLong(Objects.requireNonNull(getRequest()).getUserPrincipal().getName());
var aggregate = Bundle.create(message.getType(), message.getVanity(), message.getName());
if (message.getDescription() != null) {
aggregate.setDescription(message.getDescription());
}
if (message.getImage() != null) {
aggregate.setImage(message.getImage());
}
aggregate.setOwner(message.getOwnerId(), message.getOwnerName());
repository.save(aggregate, userId);
return CompletableFuture.completedFuture(null);
}

private HttpServletRequest getRequest() {
var request = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

if (request == null) {
return null;
}

return request.getRequest();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.theurl.bundle.application.implement;

import io.theurl.bundle.application.command.BundleCreateCommand;
import io.theurl.bundle.application.contract.BundleApplicationService;
import io.theurl.bundle.application.dto.BundleCreateDto;
import io.theurl.bundle.application.dto.BundleUpdateDto;
import io.theurl.framework.application.BaseApplicationService;
import io.theurl.framework.utility.ShortUniqueId;
import org.modelmapper.ModelMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.web.context.annotation.RequestScope;

import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

@Service
@RequestScope
public class BundleApplicationServiceImpl extends BaseApplicationService implements BundleApplicationService {

private final ModelMapper mapper;

public BundleApplicationServiceImpl(ApplicationContext applicationContext, ModelMapper mapper) {
super(applicationContext);
this.mapper = mapper;
}

@Override
public CompletableFuture<String> createAsync(BundleCreateDto data) {
if (data.getVanity() == null || data.getVanity().isEmpty()) {
var uuid = UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8);
long number = 0;
for (int i = 0; i < 8 && i < uuid.length; i++) {
number = (number << 8) | (uuid[i] & 0xFF);
}

var shortId = ShortUniqueId.getDefault().encode(number);
data.setVanity(shortId);
}

var command = new BundleCreateCommand(data.getType(), data.getVanity());
command.setName(data.getName());
command.setDescription(data.getDescription());
command.setImage(data.getImage());

return mediator.sendAsync(command)
.thenApply(_ -> command.getVanity());
}

@Override
public CompletableFuture<Void> updateAsync(String vanity, BundleUpdateDto data) {
return null;
}

@Override
public CompletableFuture<Void> deleteAsync(String vanity) {
return null;
}
}
21 changes: 16 additions & 5 deletions bundle/src/main/java/io/theurl/bundle/domain/aggregate/Bundle.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.theurl.bundle.domain.aggregate;

import io.theurl.bundle.domain.event.BundleCreatedEvent;
import io.theurl.bundle.domain.event.BundleDeletedEvent;
import io.theurl.framework.domain.AggregateRoot;
import io.theurl.framework.utility.SnowflakeId;

Expand Down Expand Up @@ -32,13 +34,12 @@ public Bundle(long id) {
private BundleExtend extend;
private boolean deleted;

public static Bundle create(String type, String vanity, String name, Long ownerId, String ownerName) {
public static Bundle create(String type, String vanity, String name) {
var aggregate = new Bundle(SnowflakeId.getInstance().nextId());
aggregate.type = type;
aggregate.vanity = vanity;
aggregate.name = name;
aggregate.ownerId = ownerId;
aggregate.ownerName = ownerName;
aggregate.raiseEvent(new BundleCreatedEvent(type, vanity, name));
return aggregate;
}

Expand Down Expand Up @@ -90,8 +91,13 @@ public String getOwnerName() {
return ownerName;
}

public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
public void setOwner(Long ownerId, String ownerName) {
this.ownerId = ownerId;
if (ownerId == null) {
this.ownerName = "system";
} else {
this.ownerName = ownerName;
}
}

public List<BundleItem> getItems() {
Expand Down Expand Up @@ -132,4 +138,9 @@ public BundleExtend getExtend() {
public boolean isDeleted() {
return deleted;
}

public void delete() {
deleted = true;
raiseEvent(new BundleDeletedEvent(this.getId(), vanity, name, ownerId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.theurl.bundle.domain.event;

import com.neroyun.mediator.Event;
import io.theurl.framework.domain.DomainEvent;
import lombok.Getter;

@Getter
public class BundleCreatedEvent extends DomainEvent implements Event {
private final String type;
private final String vanity;
private final String name;

public BundleCreatedEvent(String type, String vanity, String name) {
this.type = type;
this.vanity = vanity;
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.theurl.bundle.domain.event;

import com.neroyun.mediator.Event;
import io.theurl.framework.domain.DomainEvent;
import lombok.Getter;

@Getter
public class BundleDeletedEvent extends DomainEvent implements Event {
private final long id;
private final String vanity;
private final String name;
private final Long ownerId;

public BundleDeletedEvent(long id, String vanity, String name, Long ownerId) {
this.id = id;
this.vanity = vanity;
this.name = name;
this.ownerId = ownerId;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.theurl.bundle.persistence.handler;

import com.neroyun.mediator.Handler;
import com.neroyun.mediator.MessageContext;
import io.theurl.bundle.persistence.model.BundleDetailModel;
import io.theurl.bundle.persistence.query.BundleDetailQuery;
import io.theurl.bundle.persistence.repository.JpaBundleRepository;
Expand All @@ -25,13 +26,13 @@ public BundleDetailQueryHandler(JpaBundleRepository repository, ModelMapper mapp
}

@Override
public CompletableFuture<BundleDetailModel> handleAsync(BundleDetailQuery message) {
public CompletableFuture<BundleDetailModel> handleAsync(BundleDetailQuery message, MessageContext context) {
var entity = repository.findByVanity(message.vanity())
.orElse(null);
if (entity == null || entity.isDeleted()) {
throw new EntityNotFoundException("Bundle not found with vanity: " + message.vanity());
}

var detail = mapper.map(entity, BundleDetailModel.class);
return CompletableFuture.completedFuture(detail);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public void configure() {
.addMappings(expression -> {
expression.map(Bundle::getType, (dest, value) -> setValue(dest, "type", value));
expression.map(Bundle::getVanity, (dest, value) -> setValue(dest, "vanity", value));
expression.map(Bundle::getOwnerId, (dest, value) -> setValue(dest, "ownerId", value));
expression.map(Bundle::getOwnerName, (dest, value) -> setValue(dest, "ownerName", value));
expression.map(Bundle::getName, io.theurl.bundle.domain.aggregate.Bundle::setName);
expression.map(Bundle::getDescription, io.theurl.bundle.domain.aggregate.Bundle::setDescription);
expression.map(Bundle::getImage, io.theurl.bundle.domain.aggregate.Bundle::setImage);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.theurl.identity.application.handler;

import com.neroyun.mediator.Handler;
import com.neroyun.mediator.MessageContext;
import io.theurl.framework.core.BeanScope;
import io.theurl.identity.application.command.AuthlogCreateCommand;
import io.theurl.identity.domain.repository.AuthlogRepository;
Expand All @@ -26,7 +27,7 @@ public AuthlogCreateCommandHandler(AuthlogRepository repository, ModelMapper map
}

@Override
public CompletableFuture<Void> handleAsync(AuthlogCreateCommand message) {
public CompletableFuture<Void> handleAsync(AuthlogCreateCommand message, MessageContext context) {
try {
var authlog = Authlog.create(message.getRequestId(), message.getUsername(), message.isSuccess());
mapper.map(message, authlog);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.neroyun.mediator.Event;
import com.neroyun.mediator.Handler;
import com.neroyun.mediator.Mediator;
import com.neroyun.mediator.MessageContext;
import io.theurl.framework.core.BeanScope;
import io.theurl.identity.application.command.OnetimePasswordCreateCommand;
import io.theurl.identity.domain.aggregate.OnetimePassword;
Expand All @@ -24,7 +25,7 @@ public OnetimePasswordCreateCommandHandler(OnetimePasswordRepository repository,
}

@Override
public CompletableFuture<Void> handleAsync(OnetimePasswordCreateCommand message) {
public CompletableFuture<Void> handleAsync(OnetimePasswordCreateCommand message, MessageContext context) {
var aggregate = OnetimePassword.create(message.getRequestId(), message.getRecipient(), message.getCode(), message.getDuration());
aggregate.setUsage(message.getUsage());
repository.save(aggregate);
Expand Down
Loading
Loading