Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion cms-api/src/main/java/com/condation/cms/api/db/Content.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,12 @@ public interface Content {

public <T> ContentQuery<T> query(final String startURI, final BiFunction<ContentNode, Integer, T> nodeMapper);

public List<ContentNode> searchByTitle (@NonNull String input);
default List<ContentNode> searchByTitle (@NonNull String input) {
return searchByTitle(input, VariantSearchMode.ALL);
}

List<ContentNode> searchByTitle(
@NonNull String input,
@NonNull VariantSearchMode variantSearchMode
);
}
52 changes: 50 additions & 2 deletions cms-api/src/main/java/com/condation/cms/api/db/ContentNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@
import com.condation.cms.api.request.RequestContext;
import com.condation.cms.api.request.RequestContextScope;
import com.condation.cms.api.utils.MapUtil;
import com.condation.cms.api.utils.PathUtil;
import com.condation.cms.api.utils.SectionUtil;
import com.google.common.math.DoubleMath;
import com.condation.cms.api.workflow.WFStatusProvider;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.HashMap;
Expand Down Expand Up @@ -127,6 +126,55 @@ public boolean isSectionEntry() {
return SectionUtil.isSectionEntry(name);
}

public boolean isVariant() {
return variantPathSegment() >= 0;
}

public Optional<String> variantId() {
var pathParts = normalizedPathParts();
var variantSegment = variantPathSegment(pathParts);
if (variantSegment < 0 || variantSegment + 2 >= pathParts.length) {
return Optional.empty();
}
return Optional.of(pathParts[variantSegment + 2]);
}

/**
* Returns the indexed path of the canonical page represented by this
* variant.
*/
public Optional<String> originalUri() {
var pathParts = normalizedPathParts();
var variantSegment = variantPathSegment(pathParts);
if (variantSegment < 0 || variantSegment + 1 >= pathParts.length) {
return Optional.empty();
}

var originalName = pathParts[variantSegment + 1] + ".md";
if (variantSegment == 0) {
return Optional.of(originalName);
}
return Optional.of(String.join("/", java.util.Arrays.copyOf(pathParts, variantSegment))
+ "/" + originalName);
}

private int variantPathSegment() {
return variantPathSegment(normalizedPathParts());
}

private int variantPathSegment(String[] pathParts) {
for (int index = 0; index < pathParts.length; index++) {
if (".variants".equals(pathParts[index])) {
return index;
}
}
return -1;
}

private String[] normalizedPathParts() {
return uri.replace('\\', '/').split("/");
}

public boolean isRedirect() {
return MapUtil.getValue(data, Constants.MetaFields.REDIRECT_LOCATION) != null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,10 @@ public interface DBFileSystem {
ReadOnlyFile contentBase();

ReadOnlyFile assetBase();

/**
* Processes all content changes that have already been queued for indexing
* and waits until the index has been updated.
*/
void flushContentChanges();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.condation.cms.api.db;

/*-
* #%L
* CMS Api
* %%
* Copyright (C) 2023 - 2026 CondationCMS
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/

/**
* Controls whether a content search includes canonical pages, variants, or
* both.
*/
public enum VariantSearchMode {
ORIGINAL,
VARIANT,
ALL
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.condation.cms.api.extensions;

/*-
* #%L
* CMS Api
* %%
* Copyright (C) 2023 - 2026 CondationCMS
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/

import com.condation.cms.api.variants.VariantSelector;

/**
* Extension point for automatic variant selection strategies.
*
* <p>Manager and explicit preview selection are handled by the CMS before an
* extension is invoked. Implementations therefore only select a variant for a
* normal public request.</p>
*/
public abstract class VariantSelectorExtensionPoint
extends AbstractExtensionPoint
implements VariantSelector {

public abstract String id();

public abstract String label();
}
30 changes: 30 additions & 0 deletions cms-api/src/main/java/com/condation/cms/api/variants/Variant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.condation.cms.api.variants;

/*-
* #%L
* CMS Api
* %%
* Copyright (C) 2023 - 2026 CondationCMS
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/

import com.condation.cms.api.db.ContentNode;

/**
* A named variant of a canonical content node.
*/
public record Variant(String id, ContentNode node) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.condation.cms.api.variants;

/*-
* #%L
* CMS Api
* %%
* Copyright (C) 2023 - 2026 CondationCMS
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/

import java.util.Optional;

/**
* Describes the result and origin of a variant selection.
*/
public record VariantSelection(Optional<Variant> variant, Source source) {

public static VariantSelection canonical() {
return new VariantSelection(Optional.empty(), Source.CANONICAL);
}

public static VariantSelection preview(Variant variant) {
return new VariantSelection(Optional.of(variant), Source.PREVIEW);
}

public static VariantSelection automatic(Variant variant) {
return new VariantSelection(Optional.of(variant), Source.AUTOMATIC);
}

public enum Source {
CANONICAL,
PREVIEW,
AUTOMATIC
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.condation.cms.api.variants;

/*-
* #%L
* CMS Api
* %%
* Copyright (C) 2023 - 2026 CondationCMS
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/

import com.condation.cms.api.db.ContentNode;
import com.condation.cms.api.request.RequestContext;
import java.util.List;

/**
* Selects the content variant to render for a request.
*/
public interface VariantSelector {

VariantSelection select(
ContentNode canonicalNode,
List<Variant> variants,
RequestContext context
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import com.condation.cms.api.Constants;
import com.condation.cms.api.db.ContentNode;
import com.condation.cms.api.utils.DateRange;
import java.time.Instant;
import java.util.Date;

/**
Expand All @@ -44,7 +43,7 @@ public boolean isPublished(ContentNode node) {
@Override
public Status status(ContentNode node) {
var published = isPublished(node);
var publish_date = (Date) node.data().getOrDefault(Constants.MetaFields.PUBLISH_DATE, Date.from(Instant.now()));
var publish_date = (Date) node.data().getOrDefault(Constants.MetaFields.PUBLISH_DATE, null);
var unpublish_date = (Date) node.data().getOrDefault(Constants.MetaFields.UNPUBLISH_DATE, null);

return new Status(published, publish_date, unpublish_date, DateRange.isNowWithin(publish_date, unpublish_date), statusValue(node));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import com.condation.cms.api.db.ContentNode;
import com.condation.cms.api.utils.DateRange;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Date;
import org.jspecify.annotations.NonNull;

/*-
* #%L
Expand Down Expand Up @@ -40,5 +37,5 @@ public interface WFStatusProvider {

String newNodeStatus ();

public static record Status (boolean published, @NonNull Date publish_date, Date unpublish_date, boolean withinSchedule, String currentStage){};
public static record Status (boolean published, Date publish_date, Date unpublish_date, boolean withinSchedule, String currentStage){};
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,32 @@ public void test_publish() {
Assertions.assertThat(NodeVisibility.isVisible(contentNode)).isFalse();
Assertions.assertThat(contentNode.isVisible()).isFalse();
}

@Test
void detectsVariantAndExposesVariantMetadata() {
var contentNode = new ContentNode(
"products/.variants/about/summer/about.md",
"/products/.variants/about/summer/about",
"about.md",
Map.of()
);

Assertions.assertThat(contentNode.isVariant()).isTrue();
Assertions.assertThat(contentNode.variantId()).contains("summer");
Assertions.assertThat(contentNode.originalUri()).contains("products/about.md");
}

@Test
void doesNotTreatSimilarFolderNameAsVariant() {
var contentNode = new ContentNode(
"products/my.variants/about.md",
"/products/my.variants/about",
"about.md",
Map.of()
);

Assertions.assertThat(contentNode.isVariant()).isFalse();
Assertions.assertThat(contentNode.variantId()).isEmpty();
Assertions.assertThat(contentNode.originalUri()).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@
* @author thorstenmarx
*/
public class DefaultWFStatusProviderTest {

@Test
public void missingPublishDateRemainsUnsetAndHasNoStartLimit() {
var contentNode = new ContentNode("", "", "", Map.of(
Constants.MetaFields.STATUS, DefaultWFStatusProvider.STATUS_PUBLISHED
));

var status = new DefaultWFStatusProvider().status(contentNode);

Assertions.assertThat(status.publish_date()).isNull();
Assertions.assertThat(status.withinSchedule()).isTrue();
}

@Test
public void test_publish_date_1_11_2023() {
Expand Down
Loading
Loading