Skip to content

Commit

Permalink
Implemented GraphQL query for retrieving current user data and its cu…
Browse files Browse the repository at this point in the history
…stom settings. (OrchardCMS#15215)
  • Loading branch information
gvkries authored and urbanit committed Mar 18, 2024
1 parent b3888e0 commit 49c1dad
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using GraphQL.Resolvers;
using GraphQL.Types;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using OrchardCore.Apis.GraphQL;
using OrchardCore.ContentManagement.GraphQL.Options;
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.ContentManagement.Metadata.Models;
using OrchardCore.Users.Models;
using OrchardCore.Users.Services;

namespace OrchardCore.Users.GraphQL;

/// <summary>
/// Registers the current user including its custom user settings as a query.
/// </summary>
internal class CurrentUserQuery : ISchemaBuilder
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IOptions<GraphQLContentOptions> _contentOptionsAccessor;
protected readonly IStringLocalizer S;

public CurrentUserQuery(
IHttpContextAccessor httpContextAccessor,
IOptions<GraphQLContentOptions> contentOptionsAccessor,
IStringLocalizer<CurrentUserQuery> localizer)
{
_httpContextAccessor = httpContextAccessor;
_contentOptionsAccessor = contentOptionsAccessor;
S = localizer;
}

public async Task BuildAsync(ISchema schema)
{
// Build a user type that includes all custom user settings.
var serviceProvider = _httpContextAccessor.HttpContext.RequestServices;
var contentDefinitionManager = serviceProvider.GetRequiredService<IContentDefinitionManager>();
var contentTypes = await contentDefinitionManager.ListTypeDefinitionsAsync();

var userType = serviceProvider.GetRequiredService<UserType>();

// Note: The content types are already added to GraphQL by the ContentTypeQuery. Just add them to the user type.
foreach (var typeDefinition in contentTypes.Where(t => t.StereotypeEquals("CustomUserSettings")))
{
// Skip hidden types
if (_contentOptionsAccessor.Value.ShouldHide(typeDefinition))
{
continue;
}

userType.AddField(schema, typeDefinition);
}

var currentUserField = new FieldType
{
Name = "me",
Description = S["Gets the currently authenticated user."],
ResolvedType = userType,
Resolver = new AsyncFieldResolver<User>(async context =>
{
var userService = context.RequestServices!.GetRequiredService<IUserService>();
var user = await userService.GetAuthenticatedUserAsync(((GraphQLUserContext)context.UserContext).User);
return user as User;
}),
};

schema.Query.AddField(currentUserField);
}

public Task<string> GetIdentifierAsync()
{
var contentDefinitionManager = _httpContextAccessor.HttpContext!.RequestServices.GetRequiredService<IContentDefinitionManager>();
return contentDefinitionManager.GetIdentifierAsync();
}
}
15 changes: 15 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Users/GraphQL/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Apis.GraphQL;
using OrchardCore.Modules;

namespace OrchardCore.Users.GraphQL;

[RequireFeatures("OrchardCore.Apis.GraphQL", "OrchardCore.Contents")]
public class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISchemaBuilder, CurrentUserQuery>();
services.AddTransient<UserType>();
}
}
54 changes: 54 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Users/GraphQL/UserType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Linq;
using GraphQL.Types;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using OrchardCore.ContentManagement.Metadata.Models;
using OrchardCore.Users.Models;
using OrchardCore.Users.Services;

namespace OrchardCore.Users.GraphQL;

public class UserType : ObjectGraphType<User>
{
protected readonly IStringLocalizer<UserType> S;

public UserType(IStringLocalizer<UserType> localizer)
{
S = localizer;

Name = "User";
Description = S["Represents the currently authenticated user."];

Field(u => u.UserId).Description(S["The id of the user."]);
Field(u => u.UserName).Description(S["The name of the user."]);
Field(u => u.Email, nullable: true).Description(S["The email of the user."]);
Field(u => u.PhoneNumber, nullable: true).Description(S["The phone number of the user."]);
}

// Adds a custom user settings field
internal void AddField(ISchema schema, ContentTypeDefinition typeDefinition)
{
var contentItemType = schema.AdditionalTypeInstances.SingleOrDefault(t => t.Name == typeDefinition.Name);
if (contentItemType == null)
{
// This error would indicate that this graph type is build too early.
throw new InvalidOperationException("ContentTypeDefinition has not been registered in GraphQL");
}

this.FieldAsync(typeDefinition.Name, contentItemType, S["Custom user settings of {0}.", typeDefinition.DisplayName], resolve: async context =>
{
// We don't want to create an empty content item if it does not exist.
if (context.Source is User user &&
user.Properties.ContainsKey(context.FieldDefinition.ResolvedType.Name))
{
var customUserSettingsService = context.RequestServices!.GetRequiredService<CustomUserSettingsService>();
var settingsType = await customUserSettingsService.GetSettingsTypeAsync(context.FieldDefinition.ResolvedType.Name);
return await customUserSettingsService.GetSettingsAsync(user, settingsType);
}
return null;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

<ItemGroup>
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Admin.Abstractions\OrchardCore.Admin.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Apis.GraphQL.Abstractions\OrchardCore.Apis.GraphQL.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.AuditTrail.Abstractions\OrchardCore.AuditTrail.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.ContentManagement.Display\OrchardCore.ContentManagement.Display.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.ContentManagement.GraphQL\OrchardCore.ContentManagement.GraphQL.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Data.Abstractions\OrchardCore.Data.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.DisplayManagement\OrchardCore.DisplayManagement.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Email.Abstractions\OrchardCore.Email.Abstractions.csproj" />
Expand Down

0 comments on commit 49c1dad

Please sign in to comment.