GH-9 - Rename core, public abstractions from Module to ApplicationModule.
Remove obsolete @since tags and deprecations.
This commit is contained in:
@@ -31,8 +31,8 @@ import org.springframework.modulith.docs.Documenter.CanvasOptions.Groupings;
|
||||
import org.springframework.modulith.model.ArchitecturallyEvidentType;
|
||||
import org.springframework.modulith.model.EventType;
|
||||
import org.springframework.modulith.model.FormatableJavaClass;
|
||||
import org.springframework.modulith.model.Module;
|
||||
import org.springframework.modulith.model.Modules;
|
||||
import org.springframework.modulith.model.ApplicationModule;
|
||||
import org.springframework.modulith.model.ApplicationModules;
|
||||
import org.springframework.modulith.model.Source;
|
||||
import org.springframework.modulith.model.SpringBean;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -51,11 +51,11 @@ class Asciidoctor {
|
||||
private static String PLACEHOLDER = "¯\\_(ツ)_/¯";
|
||||
private static final Pattern JAVADOC_CODE = Pattern.compile("\\{\\@(?>link|code|literal)\\s(.*)\\}");
|
||||
|
||||
private final Modules modules;
|
||||
private final ApplicationModules modules;
|
||||
private final String javaDocBase;
|
||||
private final Optional<DocumentationSource> docSource;
|
||||
|
||||
private Asciidoctor(Modules modules, String javaDocBase) {
|
||||
private Asciidoctor(ApplicationModules modules, String javaDocBase) {
|
||||
|
||||
Assert.notNull(modules, "Modules must not be null!");
|
||||
Assert.hasText(javaDocBase, "Javadoc base must not be null or empty!");
|
||||
@@ -69,23 +69,23 @@ class Asciidoctor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Asciidoctor} instance for the given {@link Modules} and Javadoc base URI.
|
||||
* Creates a new {@link Asciidoctor} instance for the given {@link ApplicationModules} and Javadoc base URI.
|
||||
*
|
||||
* @param modules must not be {@literal null}.
|
||||
* @param javadocBase can be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static Asciidoctor withJavadocBase(Modules modules, @Nullable String javadocBase) {
|
||||
public static Asciidoctor withJavadocBase(ApplicationModules modules, @Nullable String javadocBase) {
|
||||
return new Asciidoctor(modules, javadocBase == null ? PLACEHOLDER : javadocBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Asciidoctor} instance for the given {@link Modules}.
|
||||
* Creates a new {@link Asciidoctor} instance for the given {@link ApplicationModules}.
|
||||
*
|
||||
* @param modules must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static Asciidoctor withoutJavadocBase(Modules modules) {
|
||||
public static Asciidoctor withoutJavadocBase(ApplicationModules modules) {
|
||||
return new Asciidoctor(modules, PLACEHOLDER);
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ class Asciidoctor {
|
||||
return String.format("%s implementing %s", base, interfacesAsString);
|
||||
}
|
||||
|
||||
public String renderSpringBeans(CanvasOptions options, Module module) {
|
||||
public String renderSpringBeans(CanvasOptions options, ApplicationModule module) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Groupings groupings = options.groupBeans(module);
|
||||
@@ -162,7 +162,7 @@ class Asciidoctor {
|
||||
return builder.length() == 0 ? "None" : builder.toString();
|
||||
}
|
||||
|
||||
public String renderEvents(Module module) {
|
||||
public String renderEvents(ApplicationModule module) {
|
||||
|
||||
List<EventType> events = module.getPublishedEvents();
|
||||
|
||||
@@ -194,7 +194,7 @@ class Asciidoctor {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public String renderConfigurationProperties(Module module, List<ModuleProperty> properties) {
|
||||
public String renderConfigurationProperties(ApplicationModule module, List<ModuleProperty> properties) {
|
||||
|
||||
if (properties.isEmpty()) {
|
||||
return "none";
|
||||
@@ -255,7 +255,7 @@ class Asciidoctor {
|
||||
|
||||
private String toOptionalLink(JavaClass source, Optional<String> methodSignature) {
|
||||
|
||||
Module module = modules.getModuleByType(source).orElse(null);
|
||||
ApplicationModule module = modules.getModuleByType(source).orElse(null);
|
||||
String typeAndMethod = toCode(
|
||||
toTypeAndMethod(FormatableJavaClass.of(source).getAbbreviatedFullName(module), methodSignature));
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.tngtech.archunit.core.domain.JavaMethod;
|
||||
* references
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 1.1
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class CodeReplacingDocumentationSource implements DocumentationSource {
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.modulith.docs.ConfigurationProperties.ConfigurationProperty;
|
||||
import org.springframework.modulith.model.Module;
|
||||
import org.springframework.modulith.model.ApplicationModule;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -42,7 +42,6 @@ import com.tngtech.archunit.core.domain.JavaType;
|
||||
* Represents all {@link ConfigurationProperty} instances found for the current project.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 1.3
|
||||
*/
|
||||
class ConfigurationProperties implements Iterable<ConfigurationProperty> {
|
||||
|
||||
@@ -71,12 +70,12 @@ class ConfigurationProperties implements Iterable<ConfigurationProperty> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link ModuleProperty} instances for the given {@link Module}.
|
||||
* Returns all {@link ModuleProperty} instances for the given {@link ApplicationModule}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<ModuleProperty> getModuleProperties(Module module) {
|
||||
public List<ModuleProperty> getModuleProperties(ApplicationModule module) {
|
||||
|
||||
Assert.notNull(module, "Module must not be null!");
|
||||
|
||||
@@ -94,7 +93,7 @@ class ConfigurationProperties implements Iterable<ConfigurationProperty> {
|
||||
return properties.iterator();
|
||||
}
|
||||
|
||||
private Stream<ModuleProperty> getModuleProperty(Module module,
|
||||
private Stream<ModuleProperty> getModuleProperty(ApplicationModule module,
|
||||
ConfigurationProperty property) {
|
||||
|
||||
return module.getType(property.getSourceType())
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.tngtech.archunit.core.domain.JavaMethod;
|
||||
* Interface to abstract different ways of looking up documentation for code abstractions.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 1.1
|
||||
*/
|
||||
interface DocumentationSource {
|
||||
|
||||
|
||||
@@ -43,10 +43,10 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.modulith.model.Module;
|
||||
import org.springframework.modulith.model.Module.DependencyDepth;
|
||||
import org.springframework.modulith.model.Module.DependencyType;
|
||||
import org.springframework.modulith.model.Modules;
|
||||
import org.springframework.modulith.model.ApplicationModule;
|
||||
import org.springframework.modulith.model.ApplicationModule.DependencyDepth;
|
||||
import org.springframework.modulith.model.ApplicationModule.DependencyType;
|
||||
import org.springframework.modulith.model.ApplicationModules;
|
||||
import org.springframework.modulith.model.SpringBean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
@@ -72,7 +72,7 @@ import com.structurizr.view.View;
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
|
||||
/**
|
||||
* API to create documentation for {@link Modules}.
|
||||
* API to create documentation for {@link ApplicationModules}.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@@ -88,33 +88,33 @@ public class Documenter {
|
||||
DEPENDENCY_DESCRIPTIONS.put(DependencyType.DEFAULT, "depends on");
|
||||
}
|
||||
|
||||
private final @Getter Modules modules;
|
||||
private final @Getter ApplicationModules modules;
|
||||
private final Workspace workspace;
|
||||
private final Container container;
|
||||
private final ConfigurationProperties properties;
|
||||
private final String outputFolder;
|
||||
|
||||
private Map<Module, Component> components;
|
||||
private Map<ApplicationModule, Component> components;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Documenter} for the {@link Modules} created for the given modulith type.
|
||||
* Creates a new {@link Documenter} for the {@link ApplicationModules} created for the given modulith type.
|
||||
*
|
||||
* @param modulithType must not be {@literal null}.
|
||||
*/
|
||||
public Documenter(Class<?> modulithType) {
|
||||
this(Modules.of(modulithType));
|
||||
this(ApplicationModules.of(modulithType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Documenter} for the given {@link Modules} instance.
|
||||
* Creates a new {@link Documenter} for the given {@link ApplicationModules} instance.
|
||||
*
|
||||
* @param modules must not be {@literal null}.
|
||||
*/
|
||||
public Documenter(Modules modules) {
|
||||
public Documenter(ApplicationModules modules) {
|
||||
this(modules, getDefaultOutputDirectory());
|
||||
}
|
||||
|
||||
private Documenter(Modules modules, String outputFolder) {
|
||||
private Documenter(ApplicationModules modules, String outputFolder) {
|
||||
|
||||
Assert.notNull(modules, "Modules must not be null!");
|
||||
Assert.hasText(outputFolder, "Output folder must not be null or empty!");
|
||||
@@ -137,7 +137,7 @@ public class Documenter {
|
||||
this.properties = new ConfigurationProperties();
|
||||
}
|
||||
|
||||
private Map<Module, Component> getComponents(Options options) {
|
||||
private Map<ApplicationModule, Component> getComponents(Options options) {
|
||||
|
||||
if (components == null) {
|
||||
|
||||
@@ -174,7 +174,6 @@ public class Documenter {
|
||||
* @param canvasOptions must not be {@literal null}, use {@link CanvasOptions#defaults()} for default.
|
||||
* @return the current instance, will never be {@literal null}.
|
||||
* @throws IOException
|
||||
* @since 1.1
|
||||
*/
|
||||
public Documenter writeDocumentation(Options options, CanvasOptions canvasOptions) throws IOException {
|
||||
|
||||
@@ -184,7 +183,7 @@ public class Documenter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the PlantUML component diagram for all {@link Modules}.
|
||||
* Writes the PlantUML component diagram for all {@link ApplicationModules}.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @throws IOException
|
||||
@@ -207,7 +206,6 @@ public class Documenter {
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return the current instance, will never be {@literal null}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public Documenter writeIndividualModulesAsPlantUml(Options options) {
|
||||
|
||||
@@ -217,12 +215,12 @@ public class Documenter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the PlantUML component diagram for the given {@link Module}.
|
||||
* Writes the PlantUML component diagram for the given {@link ApplicationModule}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @return the current instance, will never be {@literal null}.
|
||||
*/
|
||||
public Documenter writeModuleAsPlantUml(Module module) {
|
||||
public Documenter writeModuleAsPlantUml(ApplicationModule module) {
|
||||
|
||||
Assert.notNull(module, "Module must not be null!");
|
||||
|
||||
@@ -230,13 +228,13 @@ public class Documenter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the PlantUML component diagram for the given {@link Module} with the given rendering {@link Options}.
|
||||
* Writes the PlantUML component diagram for the given {@link ApplicationModule} with the given rendering {@link Options}.
|
||||
*
|
||||
* @param module must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return the current instance, will never be {@literal null}.
|
||||
*/
|
||||
public Documenter writeModuleAsPlantUml(Module module, Options options) {
|
||||
public Documenter writeModuleAsPlantUml(ApplicationModule module, Options options) {
|
||||
|
||||
Assert.notNull(module, "Module must not be null!");
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
@@ -281,24 +279,15 @@ public class Documenter {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param javadocBase
|
||||
* @deprecated since 1.1, use {@link #writeModuleCanvases(CanvasOptions)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public Documenter writeModuleCanvases(String javadocBase) {
|
||||
return writeModuleCanvases(CanvasOptions.defaults().withApiBase(javadocBase));
|
||||
}
|
||||
|
||||
public String toModuleCanvas(Module module) {
|
||||
public String toModuleCanvas(ApplicationModule module) {
|
||||
return toModuleCanvas(module, CanvasOptions.defaults());
|
||||
}
|
||||
|
||||
public String toModuleCanvas(Module module, String apiBase) {
|
||||
public String toModuleCanvas(ApplicationModule module, String apiBase) {
|
||||
return toModuleCanvas(module, CanvasOptions.defaults().withApiBase(apiBase));
|
||||
}
|
||||
|
||||
public String toModuleCanvas(Module module, CanvasOptions options) {
|
||||
public String toModuleCanvas(ApplicationModule module, CanvasOptions options) {
|
||||
|
||||
Asciidoctor asciidoctor = Asciidoctor.withJavadocBase(modules, options.getApiBase());
|
||||
Function<List<JavaClass>, String> mapper = asciidoctor::typesToBulletPoints;
|
||||
@@ -325,7 +314,7 @@ public class Documenter {
|
||||
return createPlantUml(Options.defaults());
|
||||
}
|
||||
|
||||
private void addDependencies(Module module, Component component, Options options) {
|
||||
private void addDependencies(ApplicationModule module, Component component, Options options) {
|
||||
|
||||
DEPENDENCY_DESCRIPTIONS.entrySet().stream().forEach(entry -> {
|
||||
|
||||
@@ -346,23 +335,23 @@ public class Documenter {
|
||||
});
|
||||
}
|
||||
|
||||
private void addComponentsToView(Module module, ComponentView view, Options options) {
|
||||
private void addComponentsToView(ApplicationModule module, ComponentView view, Options options) {
|
||||
|
||||
Supplier<Stream<Module>> bootstrapDependencies = () -> module.getBootstrapDependencies(modules,
|
||||
Supplier<Stream<ApplicationModule>> bootstrapDependencies = () -> module.getBootstrapDependencies(modules,
|
||||
options.getDependencyDepth());
|
||||
Supplier<Stream<Module>> otherDependencies = () -> options.getDependencyTypes()
|
||||
Supplier<Stream<ApplicationModule>> otherDependencies = () -> options.getDependencyTypes()
|
||||
.flatMap(it -> module.getDependencies(modules, it).stream());
|
||||
|
||||
Supplier<Stream<Module>> dependencies = () -> Stream.concat(bootstrapDependencies.get(), otherDependencies.get());
|
||||
Supplier<Stream<ApplicationModule>> dependencies = () -> Stream.concat(bootstrapDependencies.get(), otherDependencies.get());
|
||||
|
||||
addComponentsToView(dependencies, view, options, it -> it.add(getComponents(options).get(module)));
|
||||
}
|
||||
|
||||
private void addComponentsToView(Supplier<Stream<Module>> modules, ComponentView view, Options options,
|
||||
private void addComponentsToView(Supplier<Stream<ApplicationModule>> modules, ComponentView view, Options options,
|
||||
Consumer<ComponentView> afterCleanup) {
|
||||
|
||||
Styles styles = view.getViewSet().getConfiguration().getStyles();
|
||||
Map<Module, Component> components = getComponents(options);
|
||||
Map<ApplicationModule, Component> components = getComponents(options);
|
||||
|
||||
modules.get() //
|
||||
.distinct()
|
||||
@@ -417,11 +406,11 @@ public class Documenter {
|
||||
.findFirst().ifPresent(view::remove);
|
||||
}
|
||||
|
||||
private static Component applyBackgroundColor(Module module, Map<Module, Component> components, Options options,
|
||||
private static Component applyBackgroundColor(ApplicationModule module, Map<ApplicationModule, Component> components, Options options,
|
||||
Styles styles) {
|
||||
|
||||
Component component = components.get(module);
|
||||
Function<Module, Optional<String>> selector = options.getColorSelector();
|
||||
Function<ApplicationModule, Optional<String>> selector = options.getColorSelector();
|
||||
|
||||
// Apply custom color if configured
|
||||
selector.apply(module).ifPresent(color -> {
|
||||
@@ -491,7 +480,7 @@ public class Documenter {
|
||||
return createComponentView(options, null);
|
||||
}
|
||||
|
||||
private ComponentView createComponentView(Options options, @Nullable Module module) {
|
||||
private ComponentView createComponentView(Options options, @Nullable ApplicationModule module) {
|
||||
|
||||
String prefix = module == null ? "modules-" : module.getName();
|
||||
|
||||
@@ -554,7 +543,7 @@ public class Documenter {
|
||||
/**
|
||||
* A {@link Predicate} to define the which modules to exclude from the diagram to be created.
|
||||
*/
|
||||
private final @With Predicate<Module> exclusions;
|
||||
private final @With Predicate<ApplicationModule> exclusions;
|
||||
|
||||
/**
|
||||
* A {@link Predicate} to define which Structurizr {@link Component}s to be included in the diagram to be created.
|
||||
@@ -566,7 +555,7 @@ public class Documenter {
|
||||
* relationships are going to be hidden from the rendered view. Modules that have no incoming relationships will
|
||||
* entirely be removed from the view.
|
||||
*/
|
||||
private final @With Predicate<Module> targetOnly;
|
||||
private final @With Predicate<ApplicationModule> targetOnly;
|
||||
|
||||
/**
|
||||
* The target file name to be used for the diagram to be created. For individual module diagrams this needs to
|
||||
@@ -575,15 +564,15 @@ public class Documenter {
|
||||
private final @With @Nullable String targetFileName;
|
||||
|
||||
/**
|
||||
* A callback to return a hex-encoded color per {@link Module}.
|
||||
* A callback to return a hex-encoded color per {@link ApplicationModule}.
|
||||
*/
|
||||
private final @With Function<Module, Optional<String>> colorSelector;
|
||||
private final @With Function<ApplicationModule, Optional<String>> colorSelector;
|
||||
|
||||
/**
|
||||
* A callback to return a default display names for a given {@link Module}. Default implementation just forwards to
|
||||
* {@link Module#getDisplayName()}.
|
||||
* A callback to return a default display names for a given {@link ApplicationModule}. Default implementation just forwards to
|
||||
* {@link ApplicationModule#getDisplayName()}.
|
||||
*/
|
||||
private final @With Function<Module, String> defaultDisplayName;
|
||||
private final @With Function<ApplicationModule, String> defaultDisplayName;
|
||||
|
||||
/**
|
||||
* Which style to render the diagram in. Defaults to {@value DiagramStyle#UML}.
|
||||
@@ -602,7 +591,7 @@ public class Documenter {
|
||||
|
||||
/**
|
||||
* Creates a new default {@link Options} instance configured to use all dependency types, list immediate
|
||||
* dependencies for individual module instances, not applying any kind of {@link Module} or {@link Component}
|
||||
* dependencies for individual module instances, not applying any kind of {@link ApplicationModule} or {@link Component}
|
||||
* filters and default file names.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
@@ -711,7 +700,7 @@ public class Documenter {
|
||||
return groupingBy(Grouping.of(name, null, filter));
|
||||
}
|
||||
|
||||
Groupings groupBeans(Module module) {
|
||||
Groupings groupBeans(ApplicationModule module) {
|
||||
|
||||
List<Grouping> sources = new ArrayList<>(groupers);
|
||||
sources.add(FALLBACK_GROUP);
|
||||
@@ -741,7 +730,7 @@ public class Documenter {
|
||||
return Optional.ofNullable(targetFileName);
|
||||
}
|
||||
|
||||
private static List<SpringBean> getMatchingBeans(Module module, Grouping filter, List<SpringBean> alreadyMapped) {
|
||||
private static List<SpringBean> getMatchingBeans(ApplicationModule module, Grouping filter, List<SpringBean> alreadyMapped) {
|
||||
|
||||
return module.getSpringBeans().stream()
|
||||
.filter(it -> !alreadyMapped.contains(it))
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.tngtech.archunit.core.domain.JavaMethod;
|
||||
* A {@link DocumentationSource} that uses metadata generated by Spring Auto REST Docs' Javadoc Doclet.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 1.1
|
||||
*/
|
||||
class SpringAutoRestDocsDocumentationSource implements DocumentationSource {
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.modulith.model.Modules;
|
||||
import org.springframework.modulith.model.ApplicationModules;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
@@ -29,7 +29,7 @@ import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
*/
|
||||
class AsciidoctorUnitTests {
|
||||
|
||||
Asciidoctor asciidoctor = Asciidoctor.withJavadocBase(Modules.of("org.springframework.modulith"), "{javadoc}");
|
||||
Asciidoctor asciidoctor = Asciidoctor.withJavadocBase(ApplicationModules.of("org.springframework.modulith"), "{javadoc}");
|
||||
|
||||
@Test
|
||||
void formatsInlineCode() {
|
||||
|
||||
@@ -27,8 +27,8 @@ import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.modulith.docs.Documenter.Options;
|
||||
import org.springframework.modulith.model.Module;
|
||||
import org.springframework.modulith.model.Module.DependencyType;
|
||||
import org.springframework.modulith.model.ApplicationModule;
|
||||
import org.springframework.modulith.model.ApplicationModule.DependencyType;
|
||||
|
||||
import com.acme.myproject.Application;
|
||||
|
||||
@@ -49,7 +49,7 @@ class DocumenterTest {
|
||||
@Test
|
||||
void writesSingleModuleDocumentation() throws IOException {
|
||||
|
||||
Module module = documenter.getModules().getModuleByName("moduleB") //
|
||||
ApplicationModule module = documenter.getModules().getModuleByName("moduleB") //
|
||||
.orElseThrow(() -> new IllegalArgumentException());
|
||||
|
||||
documenter.writeModuleAsPlantUml(module, Options.defaults() //
|
||||
|
||||
Reference in New Issue
Block a user