// Path: src/main/java/com/membaza/api/users/persistence/Role.java
// @Data
// @Document(collection = "roles")
// public final class Role {
// 
//     @Id
//     private String id;
//     private String name;
//     private Set<String> privileges;
// 
//     @JsonIgnore
//     public String getId() {
//         return id;
//     }
// }
// 
// Path: src/main/java/com/membaza/api/users/persistence/User.java
// @Data
// @Document(collection = "users")
// public final class User {
// 
//     @Id
//     private String id;
// 
//     private @NotNull String firstname;
//     private @NotNull String lastname;
// 
//     @Indexed(unique=true)
//     private @NotNull String email;
//     private @NotNull String password;
//     private @NotNull Set<Role> roles;
//     private @NotNull Set<String> privileges;
//     private @NotNull Date dateRegistered;
//     private boolean confirmed;
//     private boolean enabled;
// 
//     @Indexed(unique=true)
//     private String userCreationCode;
//     private Set<EmailChange> emailChanges;
//     private Set<PasswordChange> passwordChanges;
//     private Set<UserDeletion> userDeletions;
// 
//     @JsonIgnore
//     public String getPassword(){
//         return password;
//     }
// 
//     @JsonIgnore
//     public String getUserCreationCode(){
//         return userCreationCode;
//     }
// }
// 
// Path: src/main/java/com/membaza/api/users/security/JwtAuthentication.java
// public final class JwtAuthentication
// implements Authentication {
// 
//     private final JwtToken rawToken;
//     private final JwtUserDetails user;
// 
//     public JwtAuthentication(JwtToken rawToken, JwtUserDetails user) {
//         this.rawToken = requireNonNull(rawToken);
//         this.user     = user;
//     }
// 
//     @Override
//     public JwtToken getCredentials() {
//         return rawToken;
//     }
// 
//     @Override
//     public JwtUserDetails getPrincipal() {
//         return user;
//     }
// 
//     @Override
//     public String getName() {
//         return user.getUsername();
//     }
// 
//     @Override
//     public Collection<? extends GrantedAuthority> getAuthorities() {
//         return user.getAuthorities();
//     }
// 
//     @Override
//     public Object getDetails() {
//         return null;
//     }
// 
//     @Override
//     public boolean isAuthenticated() {
//         return true;
//     }
// 
//     @Override
//     public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
//         throw new UnsupportedOperationException(
//             "Authentication must be done using privileges."
//         );
//     }
// }
// 
// Path: src/main/java/com/membaza/api/users/throwable/OperationNotAllowedException.java
// @ResponseStatus(value = UNAUTHORIZED, reason = "Not authorized to perform this operation.")
// public final class OperationNotAllowedException extends RuntimeException {
// 
// }
// 
// Path: src/main/java/com/membaza/api/users/throwable/RoleNotFoundException.java
// @ResponseStatus(value = NOT_FOUND, reason = "Specified role not found.")
// public final class RoleNotFoundException extends RuntimeException {}
// 
// Path: src/main/java/com/membaza/api/users/throwable/UserNotFoundException.java
// @ResponseStatus(value = NOT_FOUND, reason = "Specified user not found.")
// public final class UserNotFoundException extends RuntimeException {}
// 
// Path: src/main/java/com/membaza/api/users/util/PredicateUtil.java
// public static <T> Predicate<T> either(Predicate<T> first, Predicate<T> second) {
//     return first.or(second);
// }


import com.membaza.api.users.persistence.Role;
import com.membaza.api.users.persistence.User;
import com.membaza.api.users.security.JwtAuthentication;
import com.membaza.api.users.throwable.OperationNotAllowedException;
import com.membaza.api.users.throwable.RoleNotFoundException;
import com.membaza.api.users.throwable.UserNotFoundException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.web.bind.annotation.*;
import static com.membaza.api.users.util.PredicateUtil.either;
import static java.util.Objects.requireNonNull;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;

    @DeleteMapping("/users/{userId}/roles/{role}")
    void revokeRole(@PathVariable String userId,
                    @PathVariable String role,
                    Authentication auth) {

        if (can(auth, "REVOKE", role)) {
            final Role found = findRoleByName(role);
            if (found == null) throw new RoleNotFoundException();

            if (!mongo.updateFirst(
                query(where("id").is(userId)),
                new Update().pull("roles", found),
                User.class
            ).isUpdateOfExisting()) {
                throw new UserNotFoundException();
            }
        } else {
            throw new OperationNotAllowedException();
        }
    }
    
    private boolean can(Authentication auth, String action, String role) {
        final JwtAuthentication jwtAuth = (JwtAuthentication) auth;
        final String privilege = action + "_" + role + "_PRIVILEGE";
        return jwtAuth.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .anyMatch(either("ADMIN_ROLE"::equals, privilege::equals));
    }

// Path: src/main/java/com/membaza/api/users/persistence/Role.java
// @Data
// @Document(collection = "roles")
// public final class Role {
// 
//     @Id
//     private String id;
//     private String name;
//     private Set<String> privileges;
// 
//     @JsonIgnore
//     public String getId() {
//         return id;
//     }
// }
// 
// Path: src/main/java/com/membaza/api/users/persistence/User.java
// @Data
// @Document(collection = "users")
// public final class User {
// 
//     @Id
//     private String id;
// 
//     private @NotNull String firstname;
//     private @NotNull String lastname;
// 
//     @Indexed(unique=true)
//     private @NotNull String email;
//     private @NotNull String password;
//     private @NotNull Set<Role> roles;
//     private @NotNull Set<String> privileges;
//     private @NotNull Date dateRegistered;
//     private boolean confirmed;
//     private boolean enabled;
// 
//     @Indexed(unique=true)
//     private String userCreationCode;
//     private Set<EmailChange> emailChanges;
//     private Set<PasswordChange> passwordChanges;
//     private Set<UserDeletion> userDeletions;
// 
//     @JsonIgnore
//     public String getPassword(){
//         return password;
//     }
// 
//     @JsonIgnore
//     public String getUserCreationCode(){
//         return userCreationCode;
//     }
// }
// 
// Path: src/main/java/com/membaza/api/users/security/JwtAuthentication.java
// public final class JwtAuthentication
// implements Authentication {
// 
//     private final JwtToken rawToken;
//     private final JwtUserDetails user;
// 
//     public JwtAuthentication(JwtToken rawToken, JwtUserDetails user) {
//         this.rawToken = requireNonNull(rawToken);
//         this.user     = user;
//     }
// 
//     @Override
//     public JwtToken getCredentials() {
//         return rawToken;
//     }
// 
//     @Override
//     public JwtUserDetails getPrincipal() {
//         return user;
//     }
// 
//     @Override
//     public String getName() {
//         return user.getUsername();
//     }
// 
//     @Override
//     public Collection<? extends GrantedAuthority> getAuthorities() {
//         return user.getAuthorities();
//     }
// 
//     @Override
//     public Object getDetails() {
//         return null;
//     }
// 
//     @Override
//     public boolean isAuthenticated() {
//         return true;
//     }
// 
//     @Override
//     public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
//         throw new UnsupportedOperationException(
//             "Authentication must be done using privileges."
//         );
//     }
// }
// 
// Path: src/main/java/com/membaza/api/users/throwable/OperationNotAllowedException.java
// @ResponseStatus(value = UNAUTHORIZED, reason = "Not authorized to perform this operation.")
// public final class OperationNotAllowedException extends RuntimeException {
// 
// }
// 
// Path: src/main/java/com/membaza/api/users/throwable/RoleNotFoundException.java
// @ResponseStatus(value = NOT_FOUND, reason = "Specified role not found.")
// public final class RoleNotFoundException extends RuntimeException {}
// 
// Path: src/main/java/com/membaza/api/users/throwable/UserNotFoundException.java
// @ResponseStatus(value = NOT_FOUND, reason = "Specified user not found.")
// public final class UserNotFoundException extends RuntimeException {}
// 
// Path: src/main/java/com/membaza/api/users/util/PredicateUtil.java
// public static <T> Predicate<T> either(Predicate<T> first, Predicate<T> second) {
//     return first.or(second);
// }


// Path: src/main/java/com/membaza/api/users/controller/RoleController.java
import com.membaza.api.users.persistence.Role;
import com.membaza.api.users.persistence.User;
import com.membaza.api.users.security.JwtAuthentication;
import com.membaza.api.users.throwable.OperationNotAllowedException;
import com.membaza.api.users.throwable.RoleNotFoundException;
import com.membaza.api.users.throwable.UserNotFoundException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.web.bind.annotation.*;
import static com.membaza.api.users.util.PredicateUtil.either;
import static java.util.Objects.requireNonNull;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;


    @DeleteMapping("/users/{userId}/roles/{role}")
    void revokeRole(@PathVariable String userId,
                    @PathVariable String role,
                    Authentication auth) {

        if (can(auth, "REVOKE", role)) {
            final Role found = findRoleByName(role);
            if (found == null) throw new RoleNotFoundException();

            if (!mongo.updateFirst(
                query(where("id").is(userId)),
                new Update().pull("roles", found),
                User.class
            ).isUpdateOfExisting()) {
                throw new UserNotFoundException();
            }
        } else {
            throw new OperationNotAllowedException();
        }
    }
    
    private boolean can(Authentication auth, String action, String role) {
        final JwtAuthentication jwtAuth = (JwtAuthentication) auth;
        final String privilege = action + "_" + role + "_PRIVILEGE";
        return jwtAuth.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .anyMatch(either("ADMIN_ROLE"::equals, privilege::equals));
    }

    private Role findRoleByName(String name) {