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
6 changes: 6 additions & 0 deletions bundle/src/main/java/io/theurl/bundle/BundleApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@SpringBootApplication
@ComponentScans({
@ComponentScan("io.theurl.framework"),
@ComponentScan("io.theurl.bundle")
})
public class BundleApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.theurl.bundle.application.command;

import com.neroyun.mediator.Command;

public record BundleDeleteCommand(String vanity) implements Command {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.theurl.bundle.application.command;

import com.neroyun.mediator.Command;

@SuppressWarnings({"LombokGetterMayBeUsed", "LombokSetterMayBeUsed"})
public class BundleUpdateCommand implements Command {
private final String vanity;

private String name;
private String description;
private String image;

public BundleUpdateCommand(String vanity) {
this.vanity = vanity;
}

public String getVanity() {
return vanity;
}

public String getName() {
return name;
}

public String getDescription() {
return description;
}

public String getImage() {
return image;
}

public void setName(String name) {
this.name = name;
}

public void setDescription(String description) {
this.description = description;
}

public void setImage(String image) {
this.image = image;
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,64 @@
package io.theurl.bundle.application.contract;

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

import java.util.concurrent.CompletableFuture;

public interface BundleApplicationService extends ApplicationService {

/**
* Creates a new bundle with the provided data. If the vanity is not provided, it will be generated automatically.
*
* @param data The data for the new bundle.
* @return A CompletableFuture that will complete with the vanity of the created bundle.
*/
CompletableFuture<String> createAsync(BundleCreateDto data);

/**
* Updates an existing bundle identified by the vanity with the provided data.
*
* @param vanity The vanity of the bundle to update.
* @param data The data to update the bundle with.
* @return A CompletableFuture that will complete when the update is done.
*/
CompletableFuture<Void> updateAsync(String vanity, BundleUpdateDto data);

/**
* Deletes the bundle identified by the vanity.
*
* @param vanity The vanity of the bundle to delete.
* @return A CompletableFuture that will complete when the deletion is done.
*/
CompletableFuture<Void> deleteAsync(String vanity);

/**
* Appends an item to the bundle identified by the vanity with the provided data.
*
* @param vanity The vanity of the bundle to append the item to.
* @param data The data of the item to append.
* @return A CompletableFuture that will complete when the item is appended.
*/
CompletableFuture<Void> appendItemAsync(String vanity, BundleItemEditDto data);

/**
* Updates an item in the bundle identified by the vanity with the provided data.
*
* @param vanity The vanity of the bundle containing the item to update.
* @param itemId The ID of the item to update.
* @param data The data to update the item with.
* @return A CompletableFuture that will complete when the item is updated.
*/
CompletableFuture<Void> updateItemAsync(String vanity, long itemId, BundleItemEditDto data);

/**
* Removes an item from the bundle identified by the vanity.
*
* @param vanity The vanity of the bundle to remove the item from.
* @param itemId The ID of the item to remove.
* @return A CompletableFuture that will complete when the item is removed.
*/
CompletableFuture<Void> removeItemAsync(String vanity, long itemId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@ public class BundleCreateDto extends BundleBaseDto {
* 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;

/**
* Indicates whether the bundle is shared or not.
* If true, the bundle can be accessed by anyone with the link.
* If false, the bundle is private and can only be accessed by the owner.
*/
private boolean shared;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.theurl.bundle.application.dto;

import lombok.Data;

@Data
public class BundleItemEditDto {
private String url;
private String title;
private String description;
private String image;
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
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.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

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

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

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

@Override
Expand All @@ -39,6 +38,7 @@ public CompletableFuture<Void> handleAsync(BundleCreateCommand message, MessageC
}
aggregate.setOwner(message.getOwnerId(), message.getOwnerName());
repository.save(aggregate, userId);
context.onComplete(List.copyOf(aggregate.getEvents()));
return CompletableFuture.completedFuture(null);
Comment on lines 38 to 42
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.theurl.bundle.application.handler;

import com.neroyun.mediator.Handler;
import com.neroyun.mediator.MessageContext;
import io.theurl.bundle.application.command.BundleDeleteCommand;
import io.theurl.bundle.domain.repository.BundleRepository;
import io.theurl.framework.core.BeanScope;
import io.theurl.framework.security.UnauthorizedAccessException;
import jakarta.persistence.EntityNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
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(value = BeanScope.REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class BundleDeleteCommandHandler implements Handler<BundleDeleteCommand, Void> {
private final BundleRepository repository;

public BundleDeleteCommandHandler(BundleRepository repository) {
this.repository = repository;
}

@Override
public CompletableFuture<Void> handleAsync(BundleDeleteCommand message, MessageContext context) {
var aggregate = repository.findByVanity(message.vanity());
if (aggregate == null) {
throw new EntityNotFoundException("Bundle with vanity " + message.vanity() + " not found");
}

var userId = Long.getLong(Objects.requireNonNull(getRequest()).getUserPrincipal().getName());

if (aggregate.getOwnerId() > 0) {
if (!Objects.equals(aggregate.getOwnerId(), userId)) {
throw new UnauthorizedAccessException("You are not the owner of this bundle");
}
} else {
if (!getRequest().isUserInRole("ADMIN")) {
throw new UnauthorizedAccessException("You are not the owner of this bundle");
}
}
Comment on lines +36 to +46
Comment on lines +36 to +46
Comment on lines +36 to +46

aggregate.delete();
repository.save(aggregate, userId);
context.onComplete(aggregate.getEvents());
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,64 @@
package io.theurl.bundle.application.handler;

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

import java.util.concurrent.CompletableFuture;

@Component
@Scope(value = BeanScope.REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class BundleUpdateCommandHandler implements Handler<BundleUpdateCommand, Void> {

private final BundleRepository repository;

public BundleUpdateCommandHandler(BundleRepository repository) {
this.repository = repository;
}

@Override
public CompletableFuture<Void> handleAsync(BundleUpdateCommand message, MessageContext context) {
var aggregate = repository.findByVanity(message.getVanity());

if (aggregate == null) {
throw new EntityNotFoundException("Bundle with vanity '" + message.getVanity() + "' not found.");
}

aggregate.setName(message.getName());
aggregate.setDescription(message.getDescription());
aggregate.setImage(message.getImage());

repository.save(aggregate, getUserId());

return CompletableFuture.completedFuture(null);
Comment on lines +29 to +42
Comment on lines +29 to +42
Comment on lines +29 to +42
}

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

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

return request.getRequest();
}

private long getUserId() {
var request = getRequest();

if (request == null || request.getUserPrincipal() == null) {
return 0;
}

return Long.parseLong(request.getUserPrincipal().getName());
}
}
Loading