Skip to content

Commit

Permalink
Several improvements in server and webui
Browse files Browse the repository at this point in the history
  • Loading branch information
spoenemann committed Jun 12, 2020
1 parent 599b2ec commit 9515a19
Show file tree
Hide file tree
Showing 33 changed files with 341 additions and 295 deletions.
1 change: 1 addition & 0 deletions .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ports:
- port: 3000
# Server (API)
- port: 8080
onOpen: ignore
# PostgreSQL
- port: 5432
onOpen: ignore
Expand Down
14 changes: 8 additions & 6 deletions server/src/main/java/org/eclipse/openvsx/AdminAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
********************************************************************************/
package org.eclipse.openvsx;

import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.format.DateTimeParseException;
import java.util.stream.Collectors;

Expand All @@ -24,6 +22,7 @@
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.search.SearchService;
import org.eclipse.openvsx.util.ErrorResultException;
import org.eclipse.openvsx.util.TimeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.util.Streamable;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -88,7 +87,7 @@ public String getLog(@RequestParam("token") String tokenValue,
} else {
try {
var period = Period.parse(periodString);
var now = LocalDateTime.now(ZoneId.of("UTC"));
var now = TimeUtil.getCurrentUTC();
logs = repositories.findPersistedLogsAfter(now.minus(period));
} catch (DateTimeParseException exc) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid period");
Expand Down Expand Up @@ -118,7 +117,10 @@ public ResultJson updateSearchIndex(@RequestParam("token") String tokenValue) {
}

search.updateSearchIndex();
return admins.logAdminAction(token.getUser(), "Updated search index");

var result = ResultJson.success("Updated search index");
admins.logAdminAction(token.getUser(), result);
return result;
}

@PostMapping(
Expand All @@ -129,7 +131,7 @@ public ResultJson editNamespaceMember(@RequestParam("token") String tokenValue,
@RequestParam("namespace") String namespaceName,
@RequestParam("user") String userName,
@RequestParam(required = false) String provider,
@RequestParam(required = false) String role) {
@RequestParam String role) {
var token = users.useAccessToken(tokenValue);
if (token == null) {
return ResultJson.error("Invalid access token.");
Expand All @@ -138,7 +140,7 @@ public ResultJson editNamespaceMember(@RequestParam("token") String tokenValue,
return ResultJson.error("Administration role is required.");
}
try {
return users.editNamespaceMember(namespaceName, userName, provider, role, token.getUser());
return admins.editNamespaceMember(namespaceName, userName, provider, role, token.getUser());
} catch (ErrorResultException exc) {
return ResultJson.error(exc.getMessage());
}
Expand Down
84 changes: 65 additions & 19 deletions server/src/main/java/org/eclipse/openvsx/AdminService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
********************************************************************************/
package org.eclipse.openvsx;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.stream.Collectors;

import javax.persistence.EntityManager;
Expand All @@ -24,8 +22,10 @@
import org.eclipse.openvsx.entities.UserData;
import org.eclipse.openvsx.json.ResultJson;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.search.SearchService;
import org.eclipse.openvsx.util.ErrorResultException;
import org.eclipse.openvsx.util.SemanticVersion;
import org.eclipse.openvsx.util.TimeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

Expand All @@ -38,6 +38,12 @@ public class AdminService {
@Autowired
EntityManager entityManager;

@Autowired
UserService users;

@Autowired
SearchService search;

@Transactional(rollbackOn = ErrorResultException.class)
public ResultJson deleteExtension(String namespaceName, String extensionName, String version, UserData admin)
throws ErrorResultException {
Expand Down Expand Up @@ -74,14 +80,19 @@ protected ResultJson deleteExtension(Extension extension, UserData admin) throws
.collect(Collectors.joining(", ")));
}
extension.setLatest(null);
extension.setPreview(null);
for (var extVersion : extension.getVersions()) {
removeExtensionVersion(extVersion);
}
for (var review : repositories.findAllReviews(extension)) {
entityManager.remove(review);
}
entityManager.remove(extension);
return logAdminAction(admin, "Deleted " + namespace.getName() + "." + extension.getName());
search.removeSearchEntry(extension);

var result = ResultJson.success("Deleted " + namespace.getName() + "." + extension.getName());
logAdminAction(admin, result);
return result;
}

protected ResultJson deleteExtension(ExtensionVersion extVersion, UserData admin) {
Expand All @@ -93,40 +104,75 @@ protected ResultJson deleteExtension(ExtensionVersion extVersion, UserData admin
if (extVersion.equals(extension.getLatest())) {
var versions = extension.getVersions();
versions.remove(extVersion);
extension.setLatest(getLatestVersion(versions));
extension.setLatest(getLatestVersion(versions, false));
if (extension.getLatest() == null)
extension.setLatest(getLatestVersion(versions, true));
}
return logAdminAction(admin, "Deleted " + extension.getNamespace().getName() + "." + extension.getName() + " version " + extVersion.getVersion());
if (extVersion.equals(extension.getPreview())) {
var versions = extension.getVersions();
versions.remove(extVersion);
extension.setPreview(getLatestVersion(versions, true));
}

var result = ResultJson.success("Deleted " + extension.getNamespace().getName() + "." + extension.getName() + " version " + extVersion.getVersion());
logAdminAction(admin, result);
return result;
}

private void removeExtensionVersion(ExtensionVersion extVersion) {
repositories.findFiles(extVersion).forEach(file -> entityManager.remove(file));
entityManager.remove(extVersion);
}

private ExtensionVersion getLatestVersion(Iterable<ExtensionVersion> versions) {
private ExtensionVersion getLatestVersion(Iterable<ExtensionVersion> versions, boolean preview) {
ExtensionVersion latest = null;
SemanticVersion latestSemver = null;
for (var extVer : versions) {
var semver = new SemanticVersion(extVer.getVersion());
if (latestSemver == null || latestSemver.compareTo(semver) < 0) {
latest = extVer;
latestSemver = semver;
if (extVer.isPreview() == preview) {
var semver = new SemanticVersion(extVer.getVersion());
if (latestSemver == null || latestSemver.compareTo(semver) < 0) {
latest = extVer;
latestSemver = semver;
}
}
}
return latest;
}

@Transactional(rollbackOn = ErrorResultException.class)
public ResultJson editNamespaceMember(String namespaceName, String userName, String provider, String role, UserData admin)
throws ErrorResultException {
var namespace = repositories.findNamespace(namespaceName);
if (namespace == null) {
throw new ErrorResultException("Namespace not found: " + namespaceName);
}
if (Strings.isNullOrEmpty(provider)) {
provider = "github";
}
var user = repositories.findUserByLoginName(provider, userName);
if (user == null) {
throw new ErrorResultException("User not found: " + provider + "/" + userName);
}

ResultJson result;
if (role.equals("remove")) {
result = users.removeNamespaceMember(namespace, user);
} else {
result = users.addNamespaceMember(namespace, user, role);
}
logAdminAction(admin, result);
return result;
}

@Transactional
public ResultJson logAdminAction(UserData admin, String message) {
if(admin == null) {
return null;
public void logAdminAction(UserData admin, ResultJson result) {
if (result.success != null) {
var log = new PersistedLog();
log.setUser(admin);
log.setTimestamp(TimeUtil.getCurrentUTC());
log.setMessage(result.success);
entityManager.persist(log);
}
var log = new PersistedLog();
log.setUser(admin);
log.setTimestamp(LocalDateTime.now(ZoneId.of("UTC")));
log.setMessage(message);
entityManager.persist(log);
return ResultJson.success(message);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
import static org.eclipse.openvsx.util.UrlUtil.createApiUrl;

import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -49,6 +47,7 @@
import org.eclipse.openvsx.util.ErrorResultException;
import org.eclipse.openvsx.util.NotFoundException;
import org.eclipse.openvsx.util.SemanticVersion;
import org.eclipse.openvsx.util.TimeUtil;
import org.eclipse.openvsx.util.UrlUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -269,7 +268,7 @@ private ExtensionVersion createExtensionVersion(ExtensionProcessor processor, Us
if (extVersion.getDisplayName() != null && extVersion.getDisplayName().trim().isEmpty()) {
extVersion.setDisplayName(null);
}
extVersion.setTimestamp(LocalDateTime.now(ZoneId.of("UTC")));
extVersion.setTimestamp(TimeUtil.getCurrentUTC());
extVersion.setPublishedWith(token);
entityManager.persist(extVersion);

Expand Down Expand Up @@ -392,7 +391,7 @@ public ResultJson postReview(ReviewJson review, String namespace, String extensi
var extReview = new ExtensionReview();
extReview.setExtension(extension);
extReview.setActive(true);
extReview.setTimestamp(LocalDateTime.now(ZoneId.of("UTC")));
extReview.setTimestamp(TimeUtil.getCurrentUTC());
extReview.setUser(user);
extReview.setTitle(review.title);
extReview.setComment(review.comment);
Expand Down
69 changes: 23 additions & 46 deletions server/src/main/java/org/eclipse/openvsx/UserAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

import static org.eclipse.openvsx.util.UrlUtil.createApiUrl;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.LinkedHashMap;
import java.util.List;

Expand All @@ -30,6 +28,8 @@
import org.eclipse.openvsx.json.UserJson;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.util.CollectionUtil;
import org.eclipse.openvsx.util.ErrorResultException;
import org.eclipse.openvsx.util.TimeUtil;
import org.eclipse.openvsx.util.UrlUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -127,7 +127,7 @@ public AccessTokenJson createAccessToken(@RequestParam(required = false) String
token.setUser(user);
token.setValue(users.generateTokenValue());
token.setActive(true);
token.setCreatedTimestamp(LocalDateTime.now(ZoneId.of("UTC")));
token.setCreatedTimestamp(TimeUtil.getCurrentUTC());
token.setDescription(description);
entityManager.persist(token);
var json = token.toAccessTokenJson();
Expand Down Expand Up @@ -181,9 +181,8 @@ public List<NamespaceJson> getOwnNamespaces() {
json.extensions.put(ext.getName(), url);
}
json.access = NamespaceJson.RESTRICTED_ACCESS;
json.addMembershipUrl = createApiUrl(serverUrl, "user", "namespace", namespace.getName(), "member", "add");
json.getMembersUrl = createApiUrl(serverUrl, "user", "namespace", namespace.getName(), "members");

json.membersUrl = createApiUrl(serverUrl, "user", "namespace", namespace.getName(), "members");
json.roleUrl = createApiUrl(serverUrl, "user", "namespace", namespace.getName(), "role");
return json;
}).toList();
}
Expand All @@ -208,60 +207,38 @@ public List<NamespaceMembershipJson> getNamespaceMembers(@PathVariable String na
}
}

@GetMapping(
path = "/user/search/{name}",
@PostMapping(
path = "/user/namespace/{namespace}/role",
produces = MediaType.APPLICATION_JSON_VALUE
)
public List<UserJson> getUsersStartWith(@PathVariable String name) {
var principal = users.getOAuth2Principal();
if (principal == null) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}

var users = repositories.findUsersByLoginNameStartingWith(name)
.map(user -> user.toUserJson());
return CollectionUtil.limit(users, 5);
}

@PostMapping(path = "/user/namespace/{namespaceName}/member/{setMethod}/{userLogin}", produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional(rollbackOn = ResponseStatusException.class)
public ResultJson setNamespaceMember(@PathVariable String namespaceName, @PathVariable String userLogin,
@PathVariable String setMethod, @RequestParam(required = false) String provider) {
public ResultJson setNamespaceMember(@PathVariable String namespace, @RequestParam String user,
@RequestParam String role, @RequestParam(required = false) String provider) {
var principal = users.getOAuth2Principal();
if (principal == null) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
var user = users.updateUser(principal);
var namespace = repositories.findNamespace(namespaceName);
var userMembership = repositories.findMembership(user, namespace);
var member = repositories.findUserByLoginName(provider, userLogin);

if (userMembership != null && userMembership.getRole().equals(NamespaceMembership.ROLE_OWNER)) {
if (setMethod.equalsIgnoreCase("add")) {
users.addNamespaceMember(namespace, member, NamespaceMembership.ROLE_CONTRIBUTOR, user);
return ResultJson.success("Added " + userLogin + " as member to namespace " + namespace);
} else if (setMethod.equalsIgnoreCase("remove")) {
users.removeNamespaceMember(namespace, member, user);
return ResultJson.success("Removed " + userLogin + " from namespace " + namespace);
} else {
return ResultJson.error("Set method is not correct. Must be either 'add' or 'remove'.");
}
try {
var requestingUser = users.updateUser(principal);
return users.setNamespaceMember(requestingUser, namespace, provider, user, role);
} catch (ErrorResultException exc) {
return ResultJson.error(exc.getMessage());
}

return ResultJson.error("You must be an owner of this namespace.");
}

@PostMapping(path = "/user/namespace/{namespaceName}/role/{userLogin}/{role}", produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional(rollbackOn = ResponseStatusException.class)
public ResultJson setNamespaceMemberRole(@PathVariable String namespaceName, @PathVariable String userLogin,
@PathVariable String role, @RequestParam(required = false) String provider) {
@GetMapping(
path = "/user/search/{name}",
produces = MediaType.APPLICATION_JSON_VALUE
)
public List<UserJson> getUsersStartWith(@PathVariable String name) {
var principal = users.getOAuth2Principal();
if (principal == null) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
var user = users.updateUser(principal);
users.editNamespaceMember(namespaceName, userLogin, provider, role, user);

return ResultJson.success("Changed role for " + userLogin + " in namespace " + namespaceName + " to " + role);
var users = repositories.findUsersByLoginNameStartingWith(name)
.map(user -> user.toUserJson());
return CollectionUtil.limit(users, 5);
}

}
Loading

0 comments on commit 9515a19

Please sign in to comment.