Skip to content

Commit 6be48b3

Browse files
author
amvanbaren
committed
User settings extension delete feature
1 parent cfcfa7d commit 6be48b3

24 files changed

+863
-110
lines changed

server/src/main/java/org/eclipse/openvsx/ExtensionService.java

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,23 @@
99
********************************************************************************/
1010
package org.eclipse.openvsx;
1111

12+
import jakarta.persistence.EntityManager;
1213
import jakarta.transaction.Transactional;
1314
import jakarta.transaction.Transactional.TxType;
1415
import org.apache.commons.lang3.StringUtils;
16+
import org.eclipse.openvsx.admin.RemoveFileJobRequest;
1517
import org.eclipse.openvsx.cache.CacheService;
1618
import org.eclipse.openvsx.entities.*;
19+
import org.eclipse.openvsx.json.ResultJson;
20+
import org.eclipse.openvsx.json.TargetPlatformVersionJson;
1721
import org.eclipse.openvsx.publish.PublishExtensionVersionHandler;
1822
import org.eclipse.openvsx.repositories.RepositoryService;
1923
import org.eclipse.openvsx.search.SearchUtilService;
2024
import org.eclipse.openvsx.util.ErrorResultException;
25+
import org.eclipse.openvsx.util.NamingUtil;
2126
import org.eclipse.openvsx.util.TempFile;
2227
import org.eclipse.openvsx.util.TimeUtil;
28+
import org.jobrunr.scheduling.JobRequestScheduler;
2329
import org.springframework.beans.factory.annotation.Value;
2430
import org.springframework.http.HttpStatus;
2531
import org.springframework.stereotype.Component;
@@ -30,31 +36,41 @@
3036
import java.io.InputStream;
3137
import java.nio.file.Files;
3238
import java.time.LocalDateTime;
39+
import java.util.ArrayList;
3340
import java.util.LinkedHashSet;
41+
import java.util.List;
42+
import java.util.Objects;
43+
import java.util.stream.Collectors;
3444

3545
@Component
3646
public class ExtensionService {
3747

3848
private static final int MAX_CONTENT_SIZE = 512 * 1024 * 1024;
3949

50+
private final EntityManager entityManager;
4051
private final RepositoryService repositories;
4152
private final SearchUtilService search;
4253
private final CacheService cache;
4354
private final PublishExtensionVersionHandler publishHandler;
55+
private final JobRequestScheduler scheduler;
4456

4557
@Value("${ovsx.publishing.require-license:false}")
4658
boolean requireLicense;
4759

4860
public ExtensionService(
61+
EntityManager entityManager,
4962
RepositoryService repositories,
5063
SearchUtilService search,
5164
CacheService cache,
52-
PublishExtensionVersionHandler publishHandler
65+
PublishExtensionVersionHandler publishHandler,
66+
JobRequestScheduler scheduler
5367
) {
68+
this.entityManager = entityManager;
5469
this.repositories = repositories;
5570
this.search = search;
5671
this.cache = cache;
5772
this.publishHandler = publishHandler;
73+
this.scheduler = scheduler;
5874
}
5975

6076
@Transactional
@@ -152,4 +168,85 @@ public void reactivateExtensions(UserData user) {
152168
updateExtension(extension);
153169
}
154170
}
171+
172+
@Transactional(rollbackOn = ErrorResultException.class)
173+
public ResultJson deleteExtension(
174+
String namespaceName,
175+
String extensionName,
176+
List<TargetPlatformVersionJson> targetVersions,
177+
UserData user
178+
) throws ErrorResultException {
179+
var results = new ArrayList<ResultJson>();
180+
if(repositories.isDeleteAllVersions(namespaceName, extensionName, targetVersions, user)) {
181+
var extension = repositories.findExtension(extensionName, namespaceName);
182+
results.add(deleteExtension(extension));
183+
} else {
184+
for (var targetVersion : targetVersions) {
185+
var extVersion = repositories.findVersion(user, targetVersion.version(), targetVersion.targetPlatform(), extensionName, namespaceName);
186+
if (extVersion == null) {
187+
var message = "Extension not found: " + NamingUtil.toLogFormat(namespaceName, extensionName, targetVersion.targetPlatform(), targetVersion.version());
188+
throw new ErrorResultException(message, HttpStatus.NOT_FOUND);
189+
}
190+
191+
return deleteExtension(extVersion);
192+
}
193+
}
194+
195+
var result = new ResultJson();
196+
result.setError(results.stream().map(ResultJson::getError).filter(Objects::nonNull).collect(Collectors.joining("\n")));
197+
result.setSuccess(results.stream().map(ResultJson::getSuccess).filter(Objects::nonNull).collect(Collectors.joining("\n")));
198+
return result;
199+
}
200+
201+
protected ResultJson deleteExtension(Extension extension) throws ErrorResultException {
202+
var bundledRefs = repositories.findBundledExtensionsReference(extension);
203+
if (!bundledRefs.isEmpty()) {
204+
throw new ErrorResultException("Extension " + NamingUtil.toExtensionId(extension)
205+
+ " is bundled by the following extension packs: "
206+
+ bundledRefs.stream()
207+
.map(NamingUtil::toFileFormat)
208+
.collect(Collectors.joining(", ")));
209+
}
210+
var dependRefs = repositories.findDependenciesReference(extension);
211+
if (!dependRefs.isEmpty()) {
212+
throw new ErrorResultException("The following extensions have a dependency on " + NamingUtil.toExtensionId(extension) + ": "
213+
+ dependRefs.stream()
214+
.map(NamingUtil::toFileFormat)
215+
.collect(Collectors.joining(", ")));
216+
}
217+
218+
cache.evictExtensionJsons(extension);
219+
for (var extVersion : repositories.findVersions(extension)) {
220+
removeExtensionVersion(extVersion);
221+
}
222+
for (var review : repositories.findAllReviews(extension)) {
223+
entityManager.remove(review);
224+
}
225+
226+
var deprecatedExtensions = repositories.findDeprecatedExtensions(extension);
227+
for(var deprecatedExtension : deprecatedExtensions) {
228+
deprecatedExtension.setReplacement(null);
229+
cache.evictExtensionJsons(deprecatedExtension);
230+
}
231+
232+
entityManager.remove(extension);
233+
search.removeSearchEntry(extension);
234+
235+
return ResultJson.success("Deleted " + NamingUtil.toExtensionId(extension));
236+
}
237+
238+
protected ResultJson deleteExtension(ExtensionVersion extVersion) {
239+
var extension = extVersion.getExtension();
240+
removeExtensionVersion(extVersion);
241+
extension.getVersions().remove(extVersion);
242+
updateExtension(extension);
243+
244+
return ResultJson.success("Deleted " + NamingUtil.toLogFormat(extVersion));
245+
}
246+
247+
private void removeExtensionVersion(ExtensionVersion extVersion) {
248+
repositories.findFiles(extVersion).map(RemoveFileJobRequest::new).forEach(scheduler::enqueue);
249+
repositories.deleteFiles(extVersion);
250+
entityManager.remove(extVersion);
251+
}
155252
}

server/src/main/java/org/eclipse/openvsx/UserAPI.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.eclipse.openvsx.security.CodedAuthException;
1919
import org.eclipse.openvsx.storage.StorageUtilService;
2020
import org.eclipse.openvsx.util.ErrorResultException;
21+
import org.eclipse.openvsx.util.NamingUtil;
2122
import org.eclipse.openvsx.util.NotFoundException;
2223
import org.eclipse.openvsx.util.UrlUtil;
2324
import org.slf4j.Logger;
@@ -51,17 +52,23 @@ public class UserAPI {
5152
private final UserService users;
5253
private final EclipseService eclipse;
5354
private final StorageUtilService storageUtil;
55+
private final LocalRegistryService local;
56+
private final ExtensionService extensions;
5457

5558
public UserAPI(
5659
RepositoryService repositories,
5760
UserService users,
5861
EclipseService eclipse,
59-
StorageUtilService storageUtil
62+
StorageUtilService storageUtil,
63+
LocalRegistryService local,
64+
ExtensionService extensions
6065
) {
6166
this.repositories = repositories;
6267
this.users = users;
6368
this.eclipse = eclipse;
6469
this.storageUtil = storageUtil;
70+
this.local = local;
71+
this.extensions = extensions;
6572
}
6673

6774
@GetMapping(
@@ -206,6 +213,54 @@ public List<ExtensionJson> getOwnExtensions() {
206213
.toList();
207214
}
208215

216+
@GetMapping(
217+
path = "/user/extension/{namespaceName}/{extensionName}",
218+
produces = MediaType.APPLICATION_JSON_VALUE
219+
)
220+
public ResponseEntity<ExtensionJson> getOwnExtension(@PathVariable String namespaceName, @PathVariable String extensionName) {
221+
var user = users.findLoggedInUser();
222+
if (user == null) {
223+
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
224+
}
225+
226+
try {
227+
ExtensionJson json;
228+
var latest = repositories.findLatestVersion(user, namespaceName, extensionName);
229+
if (latest != null) {
230+
json = local.toExtensionVersionJson(latest, null, false);
231+
json.setAllTargetPlatformVersions(repositories.findTargetPlatformsGroupedByVersion(latest.getExtension(), user));
232+
json.setActive(latest.getExtension().isActive());
233+
} else {
234+
var error = "Extension not found: " + NamingUtil.toExtensionId(namespaceName, extensionName);
235+
throw new ErrorResultException(error, HttpStatus.NOT_FOUND);
236+
}
237+
return ResponseEntity.ok(json);
238+
} catch (ErrorResultException exc) {
239+
return exc.toResponseEntity(ExtensionJson.class);
240+
}
241+
}
242+
243+
@PostMapping(
244+
path = "/user/extension/{namespaceName}/{extensionName}/delete",
245+
produces = MediaType.APPLICATION_JSON_VALUE
246+
)
247+
public ResponseEntity<ResultJson> deleteExtension(
248+
@PathVariable String namespaceName,
249+
@PathVariable String extensionName,
250+
@RequestBody List<TargetPlatformVersionJson> targetVersions
251+
) {
252+
var user = users.findLoggedInUser();
253+
if (user == null) {
254+
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
255+
}
256+
try {
257+
var result = extensions.deleteExtension(namespaceName, extensionName, targetVersions, user);
258+
return ResponseEntity.ok(result);
259+
} catch (ErrorResultException exc) {
260+
return exc.toResponseEntity();
261+
}
262+
}
263+
209264
@GetMapping(
210265
path = "/user/namespaces",
211266
produces = MediaType.APPLICATION_JSON_VALUE

server/src/main/java/org/eclipse/openvsx/admin/AdminAPI.java

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.eclipse.openvsx.entities.AdminStatistics;
2020
import org.eclipse.openvsx.entities.NamespaceMembership;
2121
import org.eclipse.openvsx.entities.PersistedLog;
22-
import org.eclipse.openvsx.entities.UserData;
2322
import org.eclipse.openvsx.json.*;
2423
import org.eclipse.openvsx.repositories.RepositoryService;
2524
import org.eclipse.openvsx.search.SearchUtilService;
@@ -34,10 +33,8 @@
3433
import java.net.URI;
3534
import java.time.Period;
3635
import java.time.format.DateTimeParseException;
37-
import java.util.ArrayList;
3836
import java.util.Collections;
3937
import java.util.List;
40-
import java.util.Objects;
4138
import java.util.stream.Collectors;
4239

4340
@RestController
@@ -255,7 +252,8 @@ public ResponseEntity<ResultJson> deleteExtension(
255252
) {
256253
try {
257254
var adminUser = admins.checkAdminUser(tokenValue);
258-
return deleteExtension(adminUser, namespaceName, extensionName, targetVersions);
255+
var result = admins.deleteExtension(adminUser, namespaceName, extensionName, targetVersions);
256+
return ResponseEntity.ok(result);
259257
} catch (ErrorResultException exc) {
260258
return exc.toResponseEntity();
261259
}
@@ -268,39 +266,17 @@ public ResponseEntity<ResultJson> deleteExtension(
268266
public ResponseEntity<ResultJson> deleteExtension(
269267
@PathVariable String namespaceName,
270268
@PathVariable String extensionName,
271-
@RequestBody(required = false) List<TargetPlatformVersionJson> targetVersions
269+
@RequestBody List<TargetPlatformVersionJson> targetVersions
272270
) {
273271
try {
274272
var adminUser = admins.checkAdminUser();
275-
return deleteExtension(adminUser, namespaceName, extensionName, targetVersions);
273+
var result = admins.deleteExtension(adminUser, namespaceName, extensionName, targetVersions);
274+
return ResponseEntity.ok(result);
276275
} catch (ErrorResultException exc) {
277276
return exc.toResponseEntity();
278277
}
279278
}
280279

281-
private ResponseEntity<ResultJson> deleteExtension(
282-
UserData adminUser,
283-
String namespaceName,
284-
String extensionName,
285-
List<TargetPlatformVersionJson> targetVersions
286-
) {
287-
ResultJson result;
288-
if(targetVersions == null) {
289-
result = admins.deleteExtension(namespaceName, extensionName, adminUser);
290-
} else {
291-
var results = new ArrayList<ResultJson>();
292-
for(var targetVersion : targetVersions) {
293-
results.add(admins.deleteExtension(namespaceName, extensionName, targetVersion.targetPlatform(), targetVersion.version(), adminUser));
294-
}
295-
296-
result = new ResultJson();
297-
result.setError(results.stream().map(ResultJson::getError).filter(Objects::nonNull).collect(Collectors.joining("\n")));
298-
result.setSuccess(results.stream().map(ResultJson::getSuccess).filter(Objects::nonNull).collect(Collectors.joining("\n")));
299-
}
300-
301-
return ResponseEntity.ok(result);
302-
}
303-
304280
@GetMapping(
305281
path = "/admin/namespace/{namespaceName}",
306282
produces = MediaType.APPLICATION_JSON_VALUE

server/src/main/java/org/eclipse/openvsx/admin/AdminService.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@
3232
import org.springframework.stereotype.Component;
3333

3434
import java.time.ZoneId;
35-
import java.util.Comparator;
36-
import java.util.LinkedHashSet;
37-
import java.util.Optional;
35+
import java.util.*;
3836
import java.util.stream.Collectors;
3937

4038
import static org.eclipse.openvsx.entities.FileResource.*;
@@ -130,7 +128,7 @@ public void deleteExtensionAndDependencies(Extension extension, UserData admin,
130128

131129
protected void deleteExtensionAndDependencies(ExtensionVersion extVersion, UserData admin, int depth) {
132130
var extension = extVersion.getExtension();
133-
if (repositories.countVersions(extension) == 1) {
131+
if (repositories.countVersions(extension.getNamespace().getName(), extension.getName()) == 1) {
134132
deleteExtensionAndDependencies(extension, admin, depth + 1);
135133
return;
136134
}
@@ -141,6 +139,28 @@ protected void deleteExtensionAndDependencies(ExtensionVersion extVersion, UserD
141139
logAdminAction(admin, ResultJson.success("Deleted " + NamingUtil.toLogFormat(extVersion)));
142140
}
143141

142+
@Transactional(rollbackOn = ErrorResultException.class)
143+
public ResultJson deleteExtension(
144+
UserData adminUser,
145+
String namespaceName,
146+
String extensionName,
147+
List<TargetPlatformVersionJson> targetVersions
148+
) {
149+
if(targetVersions == null || repositories.countVersions(namespaceName, extensionName) == targetVersions.size()) {
150+
return deleteExtension(namespaceName, extensionName, adminUser);
151+
}
152+
153+
var results = new ArrayList<ResultJson>();
154+
for(var targetVersion : targetVersions) {
155+
results.add(deleteExtension(namespaceName, extensionName, targetVersion.targetPlatform(), targetVersion.version(), adminUser));
156+
}
157+
158+
var result = new ResultJson();
159+
result.setError(results.stream().map(ResultJson::getError).filter(Objects::nonNull).collect(Collectors.joining("\n")));
160+
result.setSuccess(results.stream().map(ResultJson::getSuccess).filter(Objects::nonNull).collect(Collectors.joining("\n")));
161+
return result;
162+
}
163+
144164
@Transactional(rollbackOn = ErrorResultException.class)
145165
public ResultJson deleteExtension(String namespaceName, String extensionName, UserData admin)
146166
throws ErrorResultException {
@@ -206,10 +226,6 @@ protected ResultJson deleteExtension(Extension extension, UserData admin) throws
206226

207227
protected ResultJson deleteExtension(ExtensionVersion extVersion, UserData admin) {
208228
var extension = extVersion.getExtension();
209-
if (repositories.countVersions(extension) == 1) {
210-
return deleteExtension(extension, admin);
211-
}
212-
213229
removeExtensionVersion(extVersion);
214230
extension.getVersions().remove(extVersion);
215231
extensions.updateExtension(extension);

0 commit comments

Comments
 (0)