Skip to content
Open
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
14 changes: 7 additions & 7 deletions .github/workflows/module-ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# v3.800.24
# https://virtocommerce.atlassian.net/browse/VCST-4487
# v3.800.27
# https://virtocommerce.atlassian.net/browse/VCST-4746
name: Module CI

on:
Expand Down Expand Up @@ -324,12 +324,13 @@ jobs:
if: ${{ ((github.ref == 'refs/heads/dev') && (github.event_name == 'push')) ||
(github.event_name == 'workflow_dispatch') || ((github.base_ref == 'dev') && (github.event_name == 'pull_request')) }}
needs: 'ci'
uses: VirtoCommerce/.github/.github/workflows/ui-autotests.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/ui-autotests.yml@v3.800.27
with:
installModules: 'false'
installCustomModule: 'true'
customModuleId: ${{ needs.ci.outputs.moduleId }}
customModuleUrl: ${{ needs.ci.outputs.artifactUrl }}
requiredModulesListUrl: ${{ needs.ci.outputs.requiredModulesListUrl }}
runTests: ${{ needs.ci.outputs.run-ui-tests }}
vctestingRepoBranch: 'dev'
frontendZipUrl: 'latest'
Expand All @@ -340,9 +341,9 @@ jobs:

module-katalon-tests:
if: ${{ ((github.ref == 'refs/heads/dev') && (github.event_name == 'push') && (needs.ci.outputs.run-e2e == 'true')) ||
(github.event_name == 'workflow_dispatch') || (github.base_ref == 'dev') && (github.event_name == 'pull_request') }}
(github.event_name == 'workflow_dispatch') || ((github.base_ref == 'dev') && (github.event_name == 'pull_request')) }}
needs: 'ci'
uses: VirtoCommerce/.github/.github/workflows/e2e.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/e2e.yml@v3.800.27
with:
katalonRepo: 'VirtoCommerce/vc-quality-gate-katalon'
katalonRepoBranch: 'dev'
Expand All @@ -363,13 +364,12 @@ jobs:
&& github.event_name == 'push'
&& needs.ci.outputs.deployment-folder-exists == 'true'}}
needs: ci
uses: VirtoCommerce/.github/.github/workflows/deploy-cloud.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/deploy-cloud.yml@v3.800.27
with:
releaseSource: module
moduleId: ${{ needs.ci.outputs.moduleId }}
moduleVer: ${{ needs.ci.outputs.version }}
moduleBlob: ${{ needs.ci.outputs.blobId }}
jiraKeys: ${{ needs.ci.outputs.jira-keys }}
argoServer: 'argo.virtocommerce.cloud'
matrix: '{"include":${{ needs.ci.outputs.matrix }}}'
secrets: inherit
10 changes: 5 additions & 5 deletions .github/workflows/module-release-hotfix.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# v3.800.24
# https://virtocommerce.atlassian.net/browse/VCST-4487
# v3.800.27
# https://virtocommerce.atlassian.net/browse/VCST-4746
name: Release hotfix

on:
Expand All @@ -13,12 +13,12 @@ on:

jobs:
test:
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.27
secrets:
sonarToken: ${{ secrets.SONAR_TOKEN }}

build:
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.27
with:
uploadPackage: 'true'
uploadDocker: 'false'
Expand Down Expand Up @@ -46,7 +46,7 @@ jobs:
publish-github-release:
needs:
[build, test, get-metadata]
uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.27
with:
fullKey: ${{ needs.build.outputs.packageFullKey }}
changeLog: '${{ needs.get-metadata.outputs.changeLog }}'
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/publish-nugets.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# v3.800.24
# https://virtocommerce.atlassian.net/browse/VCST-4487
# v3.800.27
# https://virtocommerce.atlassian.net/browse/VCST-4746
name: Publish nuget

on:
Expand All @@ -13,12 +13,12 @@ on:

jobs:
test:
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.27
secrets:
sonarToken: ${{ secrets.SONAR_TOKEN }}

build:
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.27
with:
uploadPackage: 'true'
uploadDocker: 'false'
Expand All @@ -29,7 +29,7 @@ jobs:
publish-nuget:
needs:
[build, test]
uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.27
with:
fullKey: ${{ needs.build.outputs.packageFullKey }}
forceGithub: false
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# v3.800.24
# https://virtocommerce.atlassian.net/browse/VCST-4487
# v3.800.27
# https://virtocommerce.atlassian.net/browse/VCST-4746
name: Release

on:
workflow_dispatch:

jobs:
release:
uses: VirtoCommerce/.github/.github/workflows/release.yml@v3.800.24
uses: VirtoCommerce/.github/.github/workflows/release.yml@v3.800.27
secrets:
envPAT: ${{ secrets.REPO_TOKEN }}
envPAT: ${{ secrets.REPO_TOKEN }}
80 changes: 65 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,88 @@ The Sanity module integrates [Sanity](https://www.sanity.io/) CMS with Virto Com

## Sanity Schema

Create a `virtoPage` document type in your [Sanity Studio](https://www.sanity.io/docs/sanity-studio-quickstart/setting-up-your-studio) project.
Create a document type in your [Sanity Studio](https://www.sanity.io/docs/sanity-studio-quickstart/setting-up-your-studio) project. The type name must match the `Sanity.PageType` store setting (default: `page`).

**`schemaTypes/virtoPageType.ts`:**
**`schemaTypes/pageType.ts`:**

```typescript
import { defineType, defineField } from 'sanity'

export const virtoPageType = defineType({
name: 'virtoPage',
title: 'Virto Page',
export const pageType = defineType({
name: 'page',
title: 'Page',
type: 'document',
fields: [
defineField({name: 'title', type: 'string', validation: (rule) => rule.required()}),
defineField({name: 'permalink', type: 'slug', options: {source: 'title'}, validation: (rule) => rule.required()}),
defineField({name: 'description', type: 'text'}),
defineField({name: 'content', type: 'array', of: [{type: 'block'}]}),
defineField({name: 'visibility', type: 'string', options: {list: ['Public', 'Private']}}),
defineField({name: 'userGroups', type: 'array', of: [{type: 'string'}]}),
defineField({name: 'startDate', type: 'datetime'}),
defineField({name: 'endDate', type: 'datetime'}),
defineField({ name: 'title', type: 'string', validation: (rule) => rule.required() }),
defineField({ name: 'permalink', type: 'slug', options: { source: 'title' }, validation: (rule) => rule.required() }),
defineField({ name: 'description', type: 'text' }),
defineField({ name: 'body', type: 'array', of: [{ type: 'block' }] }),
defineField({ name: 'storeId', type: 'string', title: 'Store ID' }),
defineField({ name: 'cultureName', type: 'string', title: 'Culture Name' }),
defineField({ name: 'visibility', type: 'string', options: { list: ['Public', 'Private'] } }),
defineField({ name: 'userGroups', type: 'array', of: [{ type: 'string' }] }),
defineField({ name: 'startDate', type: 'datetime' }),
defineField({ name: 'endDate', type: 'datetime' }),
],
})
```

Register the schema in `schemaTypes/index.ts`:

```typescript
import {virtoPageType} from './virtoPageType'
import { pageType } from './pageType'

export const schemaTypes = [virtoPageType]
export const schemaTypes = [pageType]
```

### Field Mapping

The following table shows how Sanity document fields map to VirtoCommerce `PageDocument` properties:

| Sanity Field | Type | PageDocument Property | Required | Notes |
|---|---|---|---|---|
| `_id` | system | `Id`, `OuterId` | auto | Set by Sanity. Prefix `drafts.` = Draft status |
| `_createdAt` | system | `CreatedDate` | auto | Set by Sanity |
| `_updatedAt` | system | `ModifiedDate` | auto | Set by Sanity |
| `title` | string | `Title` | yes | Page title |
| `permalink` | slug | `Permalink` | yes | Read from `permalink.current` |
| `description` | text | `Description` | no | Meta description |
| `body` | block[] | — | no | Stored as raw JSON in `Content` |
| `storeId` | string | `StoreId` | recommended | Required for index rebuild. Fallback: webhook query param |
| `cultureName` | string | `CultureName` | recommended | Required for index rebuild. Fallback: webhook query param |
| `visibility` | string | `Visibility` | no | `Public` or `Private`. Default: `Private` |
| `userGroups` | string[] | `UserGroups` | no | Restrict access to specific user groups |
| `startDate` | datetime | `StartDate` | no | Scheduled publishing start |
| `endDate` | datetime | `EndDate` | no | Scheduled publishing end |

The entire Sanity document JSON is stored in `PageDocument.Content` with `MimeType = "application/json"` and `Source = "sanity"`.

**Draft detection:** Documents with `_id` starting with `drafts.` are indexed with `Status = Draft`. All other documents use `Status = Published`.

## Pages Module Integration

The module integrates with [Virto Pages](https://github.com/VirtoCommerce/vc-module-pages) as a content provider (`IPageContentProvider`), enabling:

* **Index Rebuild** — full reindex of all Sanity pages from the admin UI
* **Scheduled Sync** — periodic synchronization of modified pages using `_updatedAt` filter
* **Webhook Push** — real-time page updates via `POST /api/pages/sanity` (existing functionality)

The content provider uses the [Sanity Content API (GROQ)](https://www.sanity.io/docs/http-query) to query pages. Configure the following store-level settings:

| Setting | Description | Default |
|---|---|---|
| **Sanity.Enabled** | Enable/disable Sanity for the store | `false` |
| **Sanity.ProjectId** | Sanity project ID | — |
| **Sanity.Dataset** | Dataset name | `production` |
| **Sanity.ApiToken** | API token (read access) | — |
| **Sanity.PageType** | Document type to index | `page` |

### References

* [Sanity Content API (GROQ)](https://www.sanity.io/docs/http-query)
* [Sanity HTTP API](https://www.sanity.io/docs/reference/http)
* [Sanity Studio Quickstart](https://www.sanity.io/docs/sanity-studio-quickstart/setting-up-your-studio)

## Webhook Configuration

The module exposes a single endpoint:
Expand Down
76 changes: 76 additions & 0 deletions src/VirtoCommerce.Sanity.Core/ModuleConstants.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using System.Collections.Generic;
using VirtoCommerce.Platform.Core.Settings;

namespace VirtoCommerce.Sanity.Core;

public static class ModuleConstants
{
private const string GroupName = "Sanity";

public static class Security
{
public static class Permissions
Expand All @@ -22,4 +27,75 @@ public static class Permissions
];
}
}

public static class Settings
{
public static class General
{
public static SettingDescriptor Enabled { get; } = new()
{
Name = $"{GroupName}.Enabled",
GroupName = $"CMS|{GroupName}",
ValueType = SettingValueType.Boolean,
IsPublic = true,
DefaultValue = false,
};

public static SettingDescriptor ProjectId { get; } = new()
{
Name = $"{GroupName}.ProjectId",
GroupName = $"CMS|{GroupName}",
ValueType = SettingValueType.ShortText,
DefaultValue = string.Empty,
};

public static SettingDescriptor Dataset { get; } = new()
{
Name = $"{GroupName}.Dataset",
GroupName = $"CMS|{GroupName}",
ValueType = SettingValueType.ShortText,
DefaultValue = "production",
};

public static SettingDescriptor ApiToken { get; } = new()
{
Name = $"{GroupName}.ApiToken",
GroupName = $"CMS|{GroupName}",
ValueType = SettingValueType.SecureString,
DefaultValue = string.Empty,
};

public static SettingDescriptor PageType { get; } = new()
{
Name = $"{GroupName}.PageType",
GroupName = $"CMS|{GroupName}",
ValueType = SettingValueType.ShortText,
DefaultValue = "page",
};
}

public static IEnumerable<SettingDescriptor> AllSettings
{
get
{
yield return General.Enabled;
yield return General.ProjectId;
yield return General.Dataset;
yield return General.ApiToken;
yield return General.PageType;
}
}

public static IEnumerable<SettingDescriptor> StoreLevelSettings
{
get
{
yield return General.Enabled;
yield return General.ProjectId;
yield return General.Dataset;
yield return General.ApiToken;
yield return General.PageType;
}
}
}
}
17 changes: 17 additions & 0 deletions src/VirtoCommerce.Sanity.Core/Services/ISanityApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

namespace VirtoCommerce.Sanity.Core.Services;

public interface ISanityApiClient
{
Task<SanityQueryResponse> QueryAsync(string projectId, string dataset, string apiToken, string groqQuery);
}

public class SanityQueryResponse
{
public IList<JObject> Results { get; set; } = [];
public int TotalCount { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<SonarQubeTestProject>false</SonarQubeTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="VirtoCommerce.Pages.Core" Version="3.1000.0" />
<PackageReference Include="VirtoCommerce.Pages.Core" Version="3.1004.0-alpha.53-vcst-4848" />
<PackageReference Include="VirtoCommerce.Platform.Core" Version="3.1002.0" />
<PackageReference Include="VirtoCommerce.StoreModule.Core" Version="3.1000.0" />
</ItemGroup>
</Project>
Loading
Loading