Skip to content
Draft
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: 9 additions & 0 deletions .docker/linux/Mongo.R6.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM mongo:8.2.12@sha256:e0ce8c35124d4a9f9785532d1f268f39e9728ffa1cb38f46fa482436424c4bd3

ENV ARCHIVE=/home/r6.archive.gz

COPY .docker/linux/r6.archive.gz /home/
COPY .docker/linux/mongorestore.sh /docker-entrypoint-initdb.d/

RUN chmod +x /docker-entrypoint-initdb.d/mongorestore.sh

63 changes: 63 additions & 0 deletions .docker/linux/Spark.R6.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine@sha256:1b3a34768687d583ebdf16ca8dd9a21ec93de9cdf81bb424e3c1a706e2a453d7 AS base

RUN apk add --no-cache icu-libs
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
WORKDIR /app
ENV ASPNETCORE_URLS=http://+:80


FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine@sha256:d8ee39817ca03a3757288e83c37ed73cc969a286c603b827c7cbe33add1c2d1c AS build-deps

RUN apk add --no-cache nodejs npm


FROM build-deps AS npm-restore

WORKDIR /Applications/Spark.Web.Shared/app

COPY ["./Applications/Spark.Web.Shared/app/package.json", "./"]
COPY ["./Applications/Spark.Web.Shared/app/package-lock.json", "./"]

RUN npm ci


FROM build-deps AS dotnet-restore

WORKDIR /src

COPY ["./Directory.Build.props", "../Directory.Build.props"]
COPY ["./Libraries/Spark.Engine/Spark.Engine.csproj", "Libraries/Spark.Engine/Spark.Engine.csproj"]
COPY ["./Libraries/Spark.Store.MongoDB/Spark.Store.MongoDB.csproj", "Libraries/Spark.Store.MongoDB/Spark.Store.MongoDB.csproj"]
COPY ["./Libraries/Spark.Engine.R6/Spark.Engine.R6.csproj", "Libraries/Spark.Engine.R6/Spark.Engine.R6.csproj"]
COPY ["./Applications/Spark.Web.R6/Spark.Web.R6.csproj", "Applications/Spark.Web.R6/Spark.Web.R6.csproj"]

RUN dotnet restore "Applications/Spark.Web.R6/Spark.Web.R6.csproj"


FROM dotnet-restore AS build

WORKDIR /src

COPY --from=npm-restore /Applications/Spark.Web.Shared/app/node_modules ./Applications/Spark.Web.Shared/app/node_modules

COPY ["./Libraries/Spark.Engine/", "Libraries/Spark.Engine/"]
COPY ["./Libraries/Spark.Store.MongoDB/", "Libraries/Spark.Store.MongoDB/"]
COPY ["./Libraries/Spark.Engine.R6/", "Libraries/Spark.Engine.R6/"]
COPY ["./Libraries/Spark.Engine.Shared/", "Libraries/Spark.Engine.Shared/"]
COPY ["./Applications/Spark.Web.Shared/", "Applications/Spark.Web.Shared/"]
COPY ["./Applications/Spark.Web.R6/", "Applications/Spark.Web.R6/"]


FROM build AS publish


RUN dotnet publish "Applications/Spark.Web.R6/Spark.Web.R6.csproj" -c Release -o /app/publish --no-restore


FROM base AS final

WORKDIR /app

COPY --from=publish /app/publish .

ENTRYPOINT ["dotnet", "Spark.Web.R6.dll"]
7 changes: 7 additions & 0 deletions Applications/Spark.Web.R6/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
database.db
database.db-shm
database.db-wal

# generated frontend files
wwwroot/assets/
wwwroot/index.html
2 changes: 2 additions & 0 deletions Applications/Spark.Web.R6/Properties/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
serviceDependencies.json
serviceDependencies.local.json
29 changes: 29 additions & 0 deletions Applications/Spark.Web.R6/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:54460",
"sslPort": 44305
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Spark.Web.R6": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}
22 changes: 22 additions & 0 deletions Applications/Spark.Web.R6/Settings/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"StoreSettings": {
"ConnectionString": "mongodb://localhost:27017/spark"
},
"SparkSettings": {
"Endpoint": "https://localhost:5001/fhir"
},
"GitHub": {
"ClientId": "",
"ClientSecret": "",
"AdminUsers": ["your-github-username"]
},
"ExamplesSettings": {
"FilePath": "Examples/R6/examples.zip"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
43 changes: 43 additions & 0 deletions Applications/Spark.Web.R6/Spark.Web.R6.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Spark.Web.R6</AssemblyName>
<RootNamespace>Spark.Web</RootNamespace>
<AspNetCoreModuleName>AspNetCoreModuleV2</AspNetCoreModuleName>
<UserSecretsId>a4d3c2a3-5edd-47d1-8407-62489d5568c5</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="10.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Libraries\Spark.Engine\Spark.Engine.csproj" />
<ProjectReference Include="..\..\Libraries\Spark.Engine.R6\Spark.Engine.R6.csproj" />
<ProjectReference Include="..\..\Libraries\Spark.Store.MongoDB\Spark.Store.MongoDB.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="..\Spark.Web.Shared\README.md" Link="README.md" />
<Compile Include="..\Spark.Web.Shared\**\*.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
<Content Include="..\Spark.Web.Shared\Views\**\*.cshtml" Link="Views\%(RecursiveDir)%(Filename)%(Extension)" />
<Content Include="..\Spark.Web.Shared\Examples\R6\examples.zip">
<Link>Examples/R6/examples.zip</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

<!-- Build React frontend -->
<Target Name="NpmInstall" BeforeTargets="NpmBuild"
Inputs="..\Spark.Web.Shared\app\package.json;..\Spark.Web.Shared\app\package-lock.json"
Outputs="..\Spark.Web.Shared\app\node_modules\.package-lock.json">
<Exec Command="npm install" WorkingDirectory="..\Spark.Web.Shared\app" />
</Target>

<Target Name="NpmBuild" BeforeTargets="Build">
<Exec Command="npm run build -- --outDir &quot;$(MSBuildProjectDirectory)/wwwroot&quot;" WorkingDirectory="..\Spark.Web.Shared\app" />
</Target>

</Project>
Binary file not shown.
97 changes: 97 additions & 0 deletions Libraries/Spark.Engine.R6/Core/FhirModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2015-2018, Firely <info@fire.ly>
* Copyright (c) 2017-2026, Incendi <info@incendi.no>
*
* SPDX-License-Identifier: BSD-3-Clause
*/

using Hl7.Fhir.Introspection;
using Hl7.Fhir.Model;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Spark.Engine.Core;

public class FhirModel : FhirModelBase
{
// This constructor is only supposed to be accessed by tests and is therefore marked as internal.
internal FhirModel(Dictionary<Type, string> resourceTypeToResourceTypeNameMapping, IEnumerable<SearchParamDefinition> searchParameters)
: base(resourceTypeToResourceTypeNameMapping, searchParameters) { }

public FhirModel() : base(ModelInfo.SearchParameters) { }

public FhirModel(IEnumerable<SearchParamDefinition> searchParameters) : base(searchParameters) { }

// FIXME: Related issue: https://github.com/FirelyTeam/firely-net-sdk/issues/3573
public override IReadOnlyList<string> SupportedResources =>
ModelInfo.SupportedResources
.Where(resource => resource != "ClinicalAssessment"
&& resource != "DeviceAlert"
&& resource != "InsuranceProduct"
&& resource != "MolecularDefinition"
&& resource != "PersonalRelationship")
.ToArray();

public override string FhirRelease => ModelInfo.Version;

public override ModelInspector GetModelInspector() => ModelInfo.ModelInspector;

public override Type GetTypeForFhirType(string typeName) => ModelInfo.GetTypeForFhirType(typeName);

public override string GetFhirTypeNameForType(Type type) => ModelInfo.GetFhirTypeNameForType(type);

protected override SearchParamDefinition[] GetGenericSearchParamDefinitions()
{
return [
new()
{
Resource = "Resource",
Name = "_id",
Type = SearchParamType.Token,
Expression = "Resource.id",
Path = ["Resource.id"]
},
new()
{
Resource = "Resource",
Name = "_lastUpdated",
Type = SearchParamType.Date,
Expression = "Resource.meta.lastUpdated",
Path = ["Resource.meta.lastUpdated"]
},
new()
{
Resource = "Resource",
Name = "_tag",
Type = SearchParamType.Token,
Expression = "Resource.meta.tag",
Path = ["Resource.meta.tag"]
},
new()
{
Resource = "Resource",
Name = "_profile",
Type = SearchParamType.Reference,
Expression = "Resource.meta.profile",
Path = ["Resource.meta.profile"]
},
new()
{
Resource = "Resource",
Name = "_security",
Type = SearchParamType.Token,
Expression = "Resource.meta.security",
Path = ["Resource.meta.security"]
},
new()
{
Resource = "Resource",
Name = "_source",
Type = SearchParamType.Uri,
Expression = "Resource.meta.source",
Path = ["Resource.meta.source"]
}
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2026, Incendi <info@incendi.no>
*
* SPDX-License-Identifier: BSD-3-Clause
*/

using Hl7.Fhir.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Spark.Engine.Core;
using Spark.Engine.Service.FhirServiceExtensions;
using Hl7.Fhir.Specification;
using Spark.Engine.Search;
using Spark.Engine.Service;
using System;

namespace Spark.Engine.Extensions;

public static class IServiceCollectionExtensions
{
public static IMvcBuilder AddFhirWithMvc(this IServiceCollection services, SparkSettings settings, Action<MvcOptions> setupAction = null)
{
services.TryAddSingleton<IFhirModel>(_ => new FhirModel(ModelInfo.SearchParameters));
services.TryAddSingleton<IFhirService, FhirServiceR6>();
services.TryAddTransient<IElementIndexer, ElementIndexer>();
services.TryAddSingleton<ICapabilityStatementService>(provider => new CapabilityStatementService(
provider.GetRequiredService<ILocalhost>(),
provider.GetRequiredService<IFhirModel>(),
provider.GetRequiredService<ServerVersion>(),
FHIRVersion.N6_0_0));
services.TryAddSingleton(provider =>
new ResourceResolver(
provider.GetRequiredService<IFhirModel>().SupportedResources,
new PocoStructureDefinitionSummaryProvider()
)
);
services.AddTransient((provider) => new IFhirServiceExtension[]
{
provider.GetRequiredService<SearchService>(),
provider.GetRequiredService<ITransactionService>(),
provider.GetRequiredService<HistoryService>(),
provider.GetRequiredService<PagingService>(),
provider.GetRequiredService<ResourceStorageService>(),
provider.GetRequiredService<ICapabilityStatementService>(),
provider.GetRequiredService<PatchService>(),
});

return services.AddFhirWithMvcInternal(settings, setupAction);
}
}
10 changes: 10 additions & 0 deletions Libraries/Spark.Engine.R6/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright (c) 2026, Incendi <info@incendi.no>
*
* SPDX-License-Identifier: BSD-3-Clause
*/

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Spark.Engine.R6.Tests")]
[assembly: InternalsVisibleTo("Spark.Store.MongoDB.Tests")]
52 changes: 52 additions & 0 deletions Libraries/Spark.Engine.R6/Search/ElementIndexer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2026, Incendi <info@incendi.no>
*
* SPDX-License-Identifier: BSD-3-Clause
*/

using System.Collections.Generic;
using Hl7.Fhir.Model;
using Spark.Engine.Core;
using Spark.Engine.Model;
using Spark.Engine.Search.Types;
using Expression = Spark.Engine.Search.Types.Expression;

namespace Spark.Engine.Search;

public class ElementIndexer : BaseElementIndexer
{
public ElementIndexer(IFhirModel fhirModel, IReferenceNormalizationService referenceNormalizationService = null)
: base(fhirModel, referenceNormalizationService)
{
}

protected List<Expression> ToExpressions(Age element)
{
return element == null ? null : ToExpressions(element as Quantity);
}

protected List<Expression> ToExpressions(Location.PositionComponent element)
{
if (element?.Latitude == null)
return null;
if (element.Longitude == null)
return null;

var position = new List<IndexValue>
{
new("latitude", new NumberValue(element.Latitude.Value)),
new("longitude", new NumberValue(element.Longitude.Value))
};

return ListOf(new CompositeValue(position));
}

protected List<Expression> ToExpressions(Timing element)
{
if (element?.Repeat?.Bounds == null)
return null;

// TODO: Should I handle Duration?
return ToExpressions(element.Repeat.Bounds as Period);
}
}
Loading