Fix: Duplicate key error when reusing the name of a deleted application
This commit is contained in:
parent
b5d59f2a56
commit
0ad15b4cb6
|
|
@ -44,12 +44,21 @@ public abstract class BaseDomain implements Persistable<String> {
|
|||
@LastModifiedBy
|
||||
protected String modifiedBy;
|
||||
|
||||
// Deprecating this so we can move on to using `deletedAt` for all domain models.
|
||||
@Deprecated(forRemoval = true)
|
||||
protected Boolean deleted = false;
|
||||
|
||||
protected Instant deletedAt = null;
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return this.getId() == null;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isDeleted() {
|
||||
return this.getDeletedAt() != null || Boolean.TRUE.equals(getDeleted());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ public class FieldName {
|
|||
public static final String EMAIL = "email";
|
||||
public static final String ORGANIZATION_ID = "organizationId";
|
||||
public static final String DELETED = "deleted";
|
||||
public static final String DELETED_AT = "deletedAt";
|
||||
public static String ORGANIZATION = "organization";
|
||||
public static String ID = "id";
|
||||
public static String NAME = "name";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.appsmith.server.migrations;
|
||||
|
||||
import com.appsmith.server.domains.*;
|
||||
import com.appsmith.server.services.ApplicationService;
|
||||
import com.appsmith.server.services.OrganizationService;
|
||||
import com.github.cloudyrock.mongock.ChangeLog;
|
||||
import com.github.cloudyrock.mongock.ChangeSet;
|
||||
|
|
@ -49,6 +50,15 @@ public class DatabaseChangelog {
|
|||
}
|
||||
}
|
||||
|
||||
private static void dropIndexIfExists(MongoTemplate mongoTemplate, Class<?> entityClass, String name) {
|
||||
try {
|
||||
mongoTemplate.indexOps(entityClass).dropIndex(name);
|
||||
} catch (UncategorizedMongoDbException ignored) {
|
||||
// The index probably doesn't exist. This happens if the database is created after the @Indexed annotation
|
||||
// has been removed.
|
||||
}
|
||||
}
|
||||
|
||||
@ChangeSet(order = "001", id = "initial-plugins", author = "")
|
||||
public void initialPlugins(MongoTemplate mongoTemplate) {
|
||||
Plugin plugin1 = new Plugin();
|
||||
|
|
@ -78,12 +88,7 @@ public class DatabaseChangelog {
|
|||
|
||||
@ChangeSet(order = "002", id = "remove-org-name-index", author = "")
|
||||
public void removeOrgNameIndex(MongoTemplate mongoTemplate) {
|
||||
try {
|
||||
mongoTemplate.indexOps(Organization.class).dropIndex("name");
|
||||
} catch (UncategorizedMongoDbException ignored) {
|
||||
// The index probably doesn't exist. This happens if the database is created after the @Indexed annotation
|
||||
// has been removed.
|
||||
}
|
||||
dropIndexIfExists(mongoTemplate, Organization.class, "name");
|
||||
}
|
||||
|
||||
@ChangeSet(order = "003", id = "add-org-slugs", author = "")
|
||||
|
|
@ -188,4 +193,21 @@ public class DatabaseChangelog {
|
|||
);
|
||||
}
|
||||
|
||||
@ChangeSet(order = "005", id = "application-deleted-at", author = "")
|
||||
public void addApplicationDeletedAtFieldAndIndex(MongoTemplate mongoTemplate) {
|
||||
dropIndexIfExists(mongoTemplate, Application.class, "organization_application_compound_index");
|
||||
|
||||
ensureIndexes(mongoTemplate, Application.class,
|
||||
makeIndex("organizationId", "name", "deletedAt")
|
||||
.unique().named("organization_application_deleted_compound_index")
|
||||
);
|
||||
|
||||
for (Application application : mongoTemplate.findAll(Application.class)) {
|
||||
if (application.isDeleted()) {
|
||||
application.setDeletedAt(application.getUpdatedAt());
|
||||
mongoTemplate.save(application);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import reactor.core.publisher.Flux;
|
|||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
|
|
@ -48,9 +49,15 @@ public class BaseRepositoryImpl<T extends BaseDomain, ID extends Serializable> e
|
|||
}
|
||||
|
||||
private Criteria notDeleted() {
|
||||
return new Criteria().orOperator(
|
||||
where("deleted").exists(false),
|
||||
where("deleted").is(false)
|
||||
return new Criteria().andOperator(
|
||||
new Criteria().orOperator(
|
||||
where(FieldName.DELETED).exists(false),
|
||||
where(FieldName.DELETED).is(false)
|
||||
),
|
||||
new Criteria().orOperator(
|
||||
where(FieldName.DELETED_AT).exists(false),
|
||||
where(FieldName.DELETED_AT).is(null)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -79,9 +86,10 @@ public class BaseRepositoryImpl<T extends BaseDomain, ID extends Serializable> e
|
|||
public Mono<T> archive(T entity) {
|
||||
Assert.notNull(entity, "The given entity must not be null!");
|
||||
Assert.notNull(entity.getId(), "The given entity's id must not be null!");
|
||||
Assert.isTrue(!entity.getDeleted(), "The given entity is already deleted");
|
||||
Assert.isTrue(!entity.isDeleted(), "The given entity is already deleted");
|
||||
|
||||
entity.setDeleted(true);
|
||||
entity.setDeletedAt(Instant.now());
|
||||
return mongoOperations.save(entity, entityInformation.getCollectionName());
|
||||
}
|
||||
|
||||
|
|
@ -93,6 +101,7 @@ public class BaseRepositoryImpl<T extends BaseDomain, ID extends Serializable> e
|
|||
|
||||
Update update = new Update();
|
||||
update.set(FieldName.DELETED, true);
|
||||
update.set(FieldName.DELETED_AT, Instant.now());
|
||||
return mongoOperations.updateFirst(query, update, entityInformation.getJavaType())
|
||||
.map(result -> result.getModifiedCount() > 0 ? true : false);
|
||||
}
|
||||
|
|
@ -108,6 +117,7 @@ public class BaseRepositoryImpl<T extends BaseDomain, ID extends Serializable> e
|
|||
|
||||
Update update = new Update();
|
||||
update.set(FieldName.DELETED, true);
|
||||
update.set(FieldName.DELETED_AT, Instant.now());
|
||||
return mongoOperations.updateMulti(query, update, entityInformation.getJavaType())
|
||||
.map(result -> result.getModifiedCount() > 0 ? true : false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,4 +124,33 @@ public class ApplicationServiceTest {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void reuseDeletedAppName() {
|
||||
Application firstApp = new Application();
|
||||
firstApp.setName("Ghost app");
|
||||
|
||||
Application secondApp = new Application();
|
||||
secondApp.setName("Ghost app");
|
||||
|
||||
Mono<Application> firstAppDeletion = applicationPageService
|
||||
.createApplication(firstApp)
|
||||
.flatMap(app -> applicationService.archive(app))
|
||||
.cache();
|
||||
|
||||
Mono<Application> secondAppCreation = firstAppDeletion.then(
|
||||
applicationPageService.createApplication(secondApp));
|
||||
|
||||
StepVerifier
|
||||
.create(Mono.zip(firstAppDeletion, secondAppCreation))
|
||||
.assertNext(tuple2 -> {
|
||||
Application first = tuple2.getT1(), second = tuple2.getT2();
|
||||
assertThat(first.getName()).isEqualTo("Ghost app");
|
||||
assertThat(second.getName()).isEqualTo("Ghost app");
|
||||
assertThat(first.isDeleted()).isTrue();
|
||||
assertThat(second.isDeleted()).isFalse();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user