mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-21 05:50:18 +08:00
JSONCache invalidation to some events
This commit is contained in:
parent
06d4d2fef2
commit
fbdf6dbb45
@ -17,6 +17,8 @@
|
||||
package com.djrapitops.plan.gathering.listeners.bukkit;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.Nickname;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONCache;
|
||||
import com.djrapitops.plan.extension.CallEvents;
|
||||
import com.djrapitops.plan.extension.ExtensionServiceImplementation;
|
||||
import com.djrapitops.plan.gathering.cache.GeolocationCache;
|
||||
@ -147,7 +149,9 @@ public class PlayerOnlineListener implements Listener {
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
UUID serverUUID = serverInfo.getServerUUID();
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
JSONCache.invalidate(DataID.SERVER_OVERVIEW, serverUUID);
|
||||
JSONCache.invalidate(DataID.GRAPH_PERFORMANCE, serverUUID);
|
||||
|
||||
BukkitAFKListener.AFK_TRACKER.performedAction(playerUUID, time);
|
||||
|
||||
String world = player.getWorld().getName();
|
||||
@ -202,6 +206,9 @@ public class PlayerOnlineListener implements Listener {
|
||||
long time = System.currentTimeMillis();
|
||||
Player player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
UUID serverUUID = serverInfo.getServerUUID();
|
||||
JSONCache.invalidate(DataID.SERVER_OVERVIEW, serverUUID);
|
||||
JSONCache.invalidate(DataID.GRAPH_PERFORMANCE, serverUUID);
|
||||
|
||||
BukkitAFKListener.AFK_TRACKER.loggedOut(playerUUID, time);
|
||||
|
||||
|
@ -17,6 +17,8 @@
|
||||
package com.djrapitops.plan.gathering.listeners.bungee;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.keys.SessionKeys;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONCache;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.PageId;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.ResponseCache;
|
||||
import com.djrapitops.plan.extension.CallEvents;
|
||||
@ -90,33 +92,41 @@ public class PlayerOnlineListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
try {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
String playerName = player.getName();
|
||||
InetAddress address = player.getAddress().getAddress();
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
Session session = new Session(playerUUID, serverInfo.getServerUUID(), time, null, null);
|
||||
session.putRawData(SessionKeys.SERVER_NAME, "Proxy Server");
|
||||
sessionCache.cacheSession(playerUUID, session);
|
||||
Database database = dbSystem.getDatabase();
|
||||
|
||||
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
|
||||
if (gatheringGeolocations) {
|
||||
database.executeTransaction(
|
||||
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
|
||||
);
|
||||
}
|
||||
|
||||
database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, playerName));
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
|
||||
ResponseCache.clearResponse(PageId.SERVER.of(serverInfo.getServerUUID())); // TODO Swap to clearing data after creating JSON cache.
|
||||
actOnLogin(event);
|
||||
} catch (Exception e) {
|
||||
errorHandler.log(L.WARN, this.getClass(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void actOnLogin(PostLoginEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
String playerName = player.getName();
|
||||
InetAddress address = player.getAddress().getAddress();
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
Session session = new Session(playerUUID, serverInfo.getServerUUID(), time, null, null);
|
||||
session.putRawData(SessionKeys.SERVER_NAME, "Proxy Server");
|
||||
sessionCache.cacheSession(playerUUID, session);
|
||||
Database database = dbSystem.getDatabase();
|
||||
|
||||
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
|
||||
if (gatheringGeolocations) {
|
||||
database.executeTransaction(
|
||||
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
|
||||
);
|
||||
}
|
||||
|
||||
database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, playerName));
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
|
||||
|
||||
UUID serverUUID = serverInfo.getServerUUID();
|
||||
JSONCache.invalidateMatching(DataID.SERVER_OVERVIEW);
|
||||
JSONCache.invalidate(DataID.GRAPH_ONLINE, serverUUID);
|
||||
JSONCache.invalidate(DataID.SERVERS);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL)
|
||||
public void beforeLogout(PlayerDisconnectEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
@ -128,31 +138,59 @@ public class PlayerOnlineListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onLogout(PlayerDisconnectEvent event) {
|
||||
try {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
sessionCache.endSession(playerUUID, System.currentTimeMillis());
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
ResponseCache.clearResponse(PageId.SERVER.of(serverInfo.getServerUUID())); // TODO Swap to clearing data after creating JSON cache.
|
||||
actOnLogout(event);
|
||||
} catch (Exception e) {
|
||||
errorHandler.log(L.WARN, this.getClass(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void actOnLogout(PlayerDisconnectEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
sessionCache.endSession(playerUUID, System.currentTimeMillis());
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
ResponseCache.clearResponse(PageId.SERVER.of(serverInfo.getServerUUID())); // TODO Swap to clearing data after creating JSON cache.
|
||||
|
||||
processing.submit(() -> {
|
||||
JSONCache.invalidateMatching(
|
||||
DataID.SERVER_OVERVIEW,
|
||||
DataID.SESSIONS,
|
||||
DataID.GRAPH_WORLD_PIE,
|
||||
DataID.GRAPH_PUNCHCARD,
|
||||
DataID.KILLS,
|
||||
DataID.ONLINE_OVERVIEW,
|
||||
DataID.SESSIONS_OVERVIEW,
|
||||
DataID.PVP_PVE,
|
||||
DataID.GRAPH_UNIQUE_NEW,
|
||||
DataID.GRAPH_CALENDAR
|
||||
);
|
||||
UUID serverUUID = serverInfo.getServerUUID();
|
||||
JSONCache.invalidate(DataID.GRAPH_ONLINE, serverUUID);
|
||||
JSONCache.invalidate(DataID.SERVERS);
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onServerSwitch(ServerSwitchEvent event) {
|
||||
try {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
// Replaces the current session in the cache.
|
||||
Session session = new Session(playerUUID, serverInfo.getServerUUID(), time, null, null);
|
||||
session.putRawData(SessionKeys.SERVER_NAME, "Proxy Server");
|
||||
sessionCache.cacheSession(playerUUID, session);
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
actOnServerSwitch(event);
|
||||
} catch (Exception e) {
|
||||
errorHandler.log(L.WARN, this.getClass(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void actOnServerSwitch(ServerSwitchEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
// Replaces the current session in the cache.
|
||||
Session session = new Session(playerUUID, serverInfo.getServerUUID(), time, null, null);
|
||||
session.putRawData(SessionKeys.SERVER_NAME, "Proxy Server");
|
||||
sessionCache.cacheSession(playerUUID, session);
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
|
||||
JSONCache.invalidate(DataID.SERVERS);
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,12 @@ import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Cache for any JSON data sent via {@link com.djrapitops.plan.delivery.webserver.pages.json.RootJSONHandler}.
|
||||
@ -59,8 +62,31 @@ public class JSONCache {
|
||||
cache.invalidate(identifier);
|
||||
}
|
||||
|
||||
public static void invalidate(DataID dataID) {
|
||||
invalidate(dataID.name());
|
||||
}
|
||||
|
||||
public static void invalidate(UUID serverUUID, DataID... dataIDs) {
|
||||
for (DataID dataID : dataIDs) {
|
||||
invalidate(dataID.of(serverUUID));
|
||||
}
|
||||
}
|
||||
|
||||
public static void invalidate(DataID dataID, UUID serverUUID) {
|
||||
cache.invalidate(dataID.of(serverUUID));
|
||||
invalidate(dataID.of(serverUUID));
|
||||
}
|
||||
|
||||
public static void invalidateMatching(DataID... dataIDs) {
|
||||
Set<String> toInvalidate = Arrays.stream(dataIDs)
|
||||
.map(DataID::name)
|
||||
.collect(Collectors.toSet());
|
||||
for (String identifier : cache.asMap().keySet()) {
|
||||
for (String identifierToInvalidate : toInvalidate) {
|
||||
if (StringUtils.startsWith(identifier, identifierToInvalidate)) {
|
||||
invalidate(identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void invalidateMatching(DataID dataID) {
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.storage.database.transactions.events;
|
||||
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONCache;
|
||||
import com.djrapitops.plan.gathering.cache.SessionCache;
|
||||
import com.djrapitops.plan.storage.database.queries.DataStoreQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.PlayerFetchQueries;
|
||||
@ -54,5 +56,7 @@ public class PlayerRegisterTransaction extends Transaction {
|
||||
SessionCache.getCachedSession(playerUUID).ifPresent(session -> session.setAsFirstSessionIfMatches(registerDate));
|
||||
}
|
||||
execute(DataStoreQueries.updatePlayerName(playerUUID, playerName));
|
||||
|
||||
JSONCache.invalidateMatching(DataID.PLAYERS);
|
||||
}
|
||||
}
|
@ -16,6 +16,9 @@
|
||||
*/
|
||||
package com.djrapitops.plan.storage.database.transactions.events;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.keys.SessionKeys;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONCache;
|
||||
import com.djrapitops.plan.gathering.domain.Session;
|
||||
import com.djrapitops.plan.storage.database.queries.DataStoreQueries;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
@ -36,5 +39,19 @@ public class SessionEndTransaction extends Transaction {
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
execute(DataStoreQueries.storeSession(session));
|
||||
|
||||
session.getValue(SessionKeys.SERVER_UUID)
|
||||
.ifPresent(serverUUID -> JSONCache.invalidate(
|
||||
serverUUID,
|
||||
DataID.SESSIONS,
|
||||
DataID.GRAPH_WORLD_PIE,
|
||||
DataID.GRAPH_PUNCHCARD,
|
||||
DataID.KILLS,
|
||||
DataID.ONLINE_OVERVIEW,
|
||||
DataID.SESSIONS_OVERVIEW,
|
||||
DataID.PVP_PVE,
|
||||
DataID.GRAPH_UNIQUE_NEW,
|
||||
DataID.GRAPH_CALENDAR
|
||||
));
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@
|
||||
package com.djrapitops.plan.gathering.listeners.sponge;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.Nickname;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONCache;
|
||||
import com.djrapitops.plan.extension.CallEvents;
|
||||
import com.djrapitops.plan.extension.ExtensionServiceImplementation;
|
||||
import com.djrapitops.plan.gathering.cache.GeolocationCache;
|
||||
@ -150,7 +152,9 @@ public class PlayerOnlineListener {
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
UUID serverUUID = serverInfo.getServerUUID();
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
JSONCache.invalidate(DataID.SERVER_OVERVIEW, serverUUID);
|
||||
JSONCache.invalidate(DataID.GRAPH_PERFORMANCE, serverUUID);
|
||||
|
||||
SpongeAFKListener.AFK_TRACKER.performedAction(playerUUID, time);
|
||||
|
||||
String world = player.getWorld().getName();
|
||||
@ -206,7 +210,10 @@ public class PlayerOnlineListener {
|
||||
long time = System.currentTimeMillis();
|
||||
Player player = event.getTargetEntity();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
UUID serverUUID = serverInfo.getServerUUID();
|
||||
JSONCache.invalidate(DataID.SERVER_OVERVIEW, serverUUID);
|
||||
JSONCache.invalidate(DataID.GRAPH_PERFORMANCE, serverUUID);
|
||||
|
||||
SpongeAFKListener.AFK_TRACKER.loggedOut(playerUUID, time);
|
||||
|
||||
nicknameCache.removeDisplayName(playerUUID);
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.gathering.listeners.velocity;
|
||||
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONCache;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.PageId;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.ResponseCache;
|
||||
import com.djrapitops.plan.extension.CallEvents;
|
||||
@ -92,32 +94,41 @@ public class PlayerOnlineListener {
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
try {
|
||||
Player player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
String playerName = player.getUsername();
|
||||
InetAddress address = player.getRemoteAddress().getAddress();
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
sessionCache.cacheSession(playerUUID, new Session(playerUUID, serverInfo.getServerUUID(), time, null, null));
|
||||
|
||||
Database database = dbSystem.getDatabase();
|
||||
|
||||
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
|
||||
if (gatheringGeolocations) {
|
||||
database.executeTransaction(
|
||||
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
|
||||
);
|
||||
}
|
||||
|
||||
database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, playerName));
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
|
||||
ResponseCache.clearResponse(PageId.SERVER.of(serverInfo.getServerUUID()));
|
||||
actOnLogin(event);
|
||||
} catch (Exception e) {
|
||||
errorHandler.log(L.WARN, this.getClass(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void actOnLogin(PostLoginEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
String playerName = player.getUsername();
|
||||
InetAddress address = player.getRemoteAddress().getAddress();
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
sessionCache.cacheSession(playerUUID, new Session(playerUUID, serverInfo.getServerUUID(), time, null, null));
|
||||
|
||||
Database database = dbSystem.getDatabase();
|
||||
|
||||
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
|
||||
if (gatheringGeolocations) {
|
||||
database.executeTransaction(
|
||||
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
|
||||
);
|
||||
}
|
||||
|
||||
database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, playerName));
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
|
||||
ResponseCache.clearResponse(PageId.SERVER.of(serverInfo.getServerUUID()));
|
||||
|
||||
UUID serverUUID = serverInfo.getServerUUID();
|
||||
JSONCache.invalidateMatching(DataID.SERVER_OVERVIEW);
|
||||
JSONCache.invalidate(DataID.GRAPH_ONLINE, serverUUID);
|
||||
JSONCache.invalidate(DataID.SERVERS);
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.NORMAL)
|
||||
public void beforeLogout(DisconnectEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
@ -129,30 +140,58 @@ public class PlayerOnlineListener {
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
public void onLogout(DisconnectEvent event) {
|
||||
try {
|
||||
Player player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
sessionCache.endSession(playerUUID, System.currentTimeMillis());
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
ResponseCache.clearResponse(PageId.SERVER.of(serverInfo.getServerUUID()));
|
||||
actOnLogout(event);
|
||||
} catch (Exception e) {
|
||||
errorHandler.log(L.WARN, this.getClass(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void actOnLogout(DisconnectEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
sessionCache.endSession(playerUUID, System.currentTimeMillis());
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
ResponseCache.clearResponse(PageId.SERVER.of(serverInfo.getServerUUID()));
|
||||
|
||||
processing.submit(() -> {
|
||||
JSONCache.invalidateMatching(
|
||||
DataID.SERVER_OVERVIEW,
|
||||
DataID.SESSIONS,
|
||||
DataID.GRAPH_WORLD_PIE,
|
||||
DataID.GRAPH_PUNCHCARD,
|
||||
DataID.KILLS,
|
||||
DataID.ONLINE_OVERVIEW,
|
||||
DataID.SESSIONS_OVERVIEW,
|
||||
DataID.PVP_PVE,
|
||||
DataID.GRAPH_UNIQUE_NEW,
|
||||
DataID.GRAPH_CALENDAR
|
||||
);
|
||||
UUID serverUUID = serverInfo.getServerUUID();
|
||||
JSONCache.invalidate(DataID.GRAPH_ONLINE, serverUUID);
|
||||
JSONCache.invalidate(DataID.SERVERS);
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
public void onServerSwitch(ServerConnectedEvent event) {
|
||||
try {
|
||||
Player player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
// Replaces the current session in the cache.
|
||||
sessionCache.cacheSession(playerUUID, new Session(playerUUID, serverInfo.getServerUUID(), time, null, null));
|
||||
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
actOnServerSwitch(event);
|
||||
} catch (Exception e) {
|
||||
errorHandler.log(L.WARN, this.getClass(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void actOnServerSwitch(ServerConnectedEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
// Replaces the current session in the cache.
|
||||
sessionCache.cacheSession(playerUUID, new Session(playerUUID, serverInfo.getServerUUID(), time, null, null));
|
||||
|
||||
processing.submit(processors.info().playerPageUpdateProcessor(playerUUID));
|
||||
|
||||
JSONCache.invalidate(DataID.SERVERS);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user