Changes we've to do in the REST service
In the backend we've to create a new event UserProfilePictureChanged
.
The following files have to be changed or created:
JsonConverter
...
} else if (event instanceof UserProfilePictureChanged) {
UserProfilePictureChanged changedEvent = (UserProfilePictureChanged) event;
jsonEvent = jsonEvent
.add("profilePicture", changedEvent.getProfilePicture());
...
} else if (UserProfilePictureChanged.class.getName().equals(name)) {
String profilePicture = eventObj.getString("profilePicture");
UserProfilePictureChanged event = new UserProfilePictureChanged(id, profilePicture);
events.add(event);
...
UserResource
import com.airhacks.porcupine.execution.boundary.Dedicated;
import ninja.disruptor.battleapp.user.entity.User;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@Path("users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
@Dedicated
@Inject
ExecutorService usersPool;
@Inject
UserService service;
@POST
public void save(@Suspended AsyncResponse response, User user) {
CompletableFuture
.supplyAsync(service.save(user), usersPool)
.thenAccept(response::resume);
}
@PUT
@Path("{id}")
public void update(@Suspended AsyncResponse response, @PathParam("id") String id, User user) {
CompletableFuture
.supplyAsync(service.update(id, user), usersPool)
.thenAccept(response::resume);
}
}
UserService
import ninja.disruptor.battleapp.eventstore.control.EventStore;
import ninja.disruptor.battleapp.eventstore.control.EventStream;
import ninja.disruptor.battleapp.eventstore.entity.EventIdentity;
import ninja.disruptor.battleapp.user.entity.User;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.UserRepresentation;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.function.Supplier;
@Stateless
public class UserService {
private static final String KEYCLOAK_MASTER_CLIENT_ID = System.getenv("KEYCLOAK_MASTER_CLIENT_ID");
private static final String KEYCLOAK_MASTER_USER_NAME = System.getenv("KEYCLOAK_MASTER_USER_NAME");
private static final String KEYCLOAK_MASTER_PASSWORD = System.getenv("KEYCLOAK_MASTER_PASSWORD");
private static final String REALM_NAME = System.getenv("REALM_NAME");
private static final String AUTH_SERVER_URL = System.getenv("AUTH_SERVER_URL");
public static final String KEYCLOAK_MASTER_ADMIN_CLIENT_ID = "admin-cli";
@Inject
EventStore store;
public Supplier<Response> save(User user) {
user = updateUserWithLastAndFirstNameFromKeycloak(user);
User saved = create(user.getId());
saved = updateUser(user, saved);
final User postSaveUser = new User(saved);
return () -> Response
.status(Response.Status.CREATED)
.entity(postSaveUser)
.build();
}
public Supplier<Response> update(String id, User user) {
User updated = updateUser(id, user);
return () -> Response
.ok()
.entity(updated)
.build();
}
public User updateUser(User template, User toUpdate) {
if (template.getFirstName() != null) {
toUpdate = changeFirstName(toUpdate.getId(), template.getFirstName());
}
if (template.getLastName() != null) {
toUpdate = changeLastName(toUpdate.getId(), template.getLastName());
}
if (template.getNickname() != null) {
toUpdate = changeNickname(toUpdate.getId(), template.getNickname());
}
if (template.getProfilePicture() != null) {
toUpdate = changeProfilPicture(toUpdate.getId(), template.getProfilePicture());
}
return toUpdate;
}
public User updateUserWithLastAndFirstNameFromKeycloak(User user) {
User copy = new User(user);
if (copy.getLastName() == null || copy.getFirstName() == null) {
Keycloak keycloak = Keycloak.getInstance(
AUTH_SERVER_URL,
KEYCLOAK_MASTER_CLIENT_ID,
KEYCLOAK_MASTER_USER_NAME,
KEYCLOAK_MASTER_PASSWORD,
KEYCLOAK_MASTER_ADMIN_CLIENT_ID);
UsersResource users = keycloak
.realm(REALM_NAME)
.users();
UserRepresentation keycloakUser = users
.get(copy.getId())
.toRepresentation();
if (copy.getLastName() == null) {
copy.setLastName(keycloakUser.getLastName());
}
if (copy.getFirstName() == null) {
copy.setFirstName(keycloakUser.getFirstName());
}
}
return copy;
}
public User create(String id) {
User player = new User(new ArrayList<>());
player.create(id);
store.appendToStream(new EventIdentity(User.class, id), 0L, player.getChanges());
return player;
}
public User updateUser(String id, User user) {
EventStream stream = store.loadEventStream(new EventIdentity(User.class, id));
User loaded = new User(stream.getEvents());
return updateUser(user, loaded);
}
public User changeFirstName(String id, String firstName) {
EventStream stream = store.loadEventStream(new EventIdentity(User.class, id));
User user = new User(stream.getEvents());
user.changeFirstName(firstName);
store.appendToStream(new EventIdentity(User.class, id), stream.getVersion(), user.getChanges());
return user;
}
public User changeLastName(String id, String lastName) {
EventStream stream = store.loadEventStream(new EventIdentity(User.class, id));
User user = new User(stream.getEvents());
user.changeLastName(lastName);
store.appendToStream(new EventIdentity(User.class, id), stream.getVersion(), user.getChanges());
return user;
}
public User changeNickname(String id, String nickname) {
EventStream stream = store.loadEventStream(new EventIdentity(User.class, id));
User user = new User(stream.getEvents());
user.changeNickname(nickname);
store.appendToStream(new EventIdentity(User.class, id), stream.getVersion(), user.getChanges());
return user;
}
public User changeProfilPicture(String id, String profilePicture) {
EventStream stream = store.loadEventStream(new EventIdentity(User.class, id));
User user = new User(stream.getEvents());
user.changeProfilePicture(profilePicture);
store.appendToStream(new EventIdentity(User.class, id), stream.getVersion(), user.getChanges());
return user;
}
}
User
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import ninja.disruptor.battleapp.eventstore.entity.CoreEvent;
import ninja.disruptor.battleapp.user.entity.event.*;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlTransient;
import java.util.ArrayList;
import java.util.List;
@NoArgsConstructor
@ToString
@XmlAccessorType(XmlAccessType.FIELD)
@Data
public class User {
private String id;
private String nickname;
private String firstName;
private String lastName;
private String profilePicture;
@XmlTransient
private final List<CoreEvent> changes = new ArrayList<>();
public User(User copy) {
this.id = copy.getId();
this.nickname = copy.getNickname();
this.firstName = copy.getFirstName();
this.lastName = copy.getLastName();
this.profilePicture = copy.getProfilePicture();
}
public User(List<CoreEvent> events) {
for (CoreEvent event : events) {
mutate(event);
}
}
public void changeFirstName(String firstName) {
apply(new UserFirstNameChanged(id, firstName));
}
public void changeLastName(String lastName) {
apply(new UserLastNameChanged(id, lastName));
}
public void changeNickname(String nickname) {
apply(new UserNicknameChanged(id, nickname));
}
public void changeProfilePicture(String profilePicture) {
apply(new UserProfilePictureChanged(id, profilePicture));
}
public void create(String id) {
apply(new UserCreated(id));
}
public void delete() {
apply(new UserDeleted(id));
}
public void mutate(CoreEvent event) {
when(event);
}
public void apply(CoreEvent event) {
changes.add(event);
mutate(event);
}
public void when(CoreEvent event) {
if (event instanceof UserCreated) {
this.id = event.getId();
} else if (event instanceof UserFirstNameChanged) {
this.firstName = ((UserFirstNameChanged) event).getFirstName();
} else if (event instanceof UserLastNameChanged) {
this.lastName = ((UserLastNameChanged) event).getLastName();
} else if (event instanceof UserNicknameChanged) {
this.nickname = ((UserNicknameChanged) event).getNickname();
} else if (event instanceof UserProfilePictureChanged) {
this.profilePicture = ((UserProfilePictureChanged) event).getProfilePicture();
}
}
}
UserProfilePictureChanged
import lombok.Getter;
@Getter
public class UserProfilePictureChanged extends UserEvent {
private final String profilePicture;
public UserProfilePictureChanged(String id, String profilePicture) {
super(id);
this.profilePicture = profilePicture;
}
}