Skip to content

Commit

Permalink
Makes the GraphQL user type reusable. (#15691)
Browse files Browse the repository at this point in the history
  • Loading branch information
gvkries committed Apr 22, 2024
1 parent 2a628f7 commit 0140e68
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
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;

Expand All @@ -21,60 +17,40 @@ namespace OrchardCore.Users.GraphQL;
internal sealed class CurrentUserQuery : ISchemaBuilder
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IOptions<GraphQLContentOptions> _contentOptionsAccessor;
private 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)
public 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,
Type = typeof(UserType),
Resolver = new FuncFieldResolver<User>(async context =>
{
var userService = context.RequestServices!.GetRequiredService<IUserService>();
var userService = context.RequestServices.GetRequiredService<IUserService>();
var user = await userService.GetAuthenticatedUserAsync(((GraphQLUserContext)context.UserContext).User);
return user as User;
}),
};

schema.Query.AddField(currentUserField);

return Task.CompletedTask;
}

public Task<string> GetIdentifierAsync()
{
var contentDefinitionManager = _httpContextAccessor.HttpContext!.RequestServices.GetRequiredService<IContentDefinitionManager>();
var contentDefinitionManager = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IContentDefinitionManager>();
return contentDefinitionManager.GetIdentifierAsync();
}
}
47 changes: 22 additions & 25 deletions src/OrchardCore.Modules/OrchardCore.Users/GraphQL/UserType.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
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;

Expand All @@ -18,39 +16,38 @@ public UserType(IStringLocalizer<UserType> localizer)
S = localizer;

Name = "User";
Description = S["Represents the currently authenticated user."];
Description = S["Represents a 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)
public override void Initialize(ISchema schema)
{
var contentItemType = schema.AdditionalTypeInstances.SingleOrDefault(t => t.Name == typeDefinition.Name);

if (contentItemType == null)
// Add custom user settings by reusing previously registered content types with the
// stereotype "CustomUserSettings".
foreach (var contentItemType in schema.AdditionalTypeInstances.Where(t => t.Metadata.TryGetValue("Stereotype", out var stereotype) && stereotype as string == "CustomUserSettings"))
{
// This error would indicate that this graph type is build too early.
throw new InvalidOperationException("ContentTypeDefinition has not been registered in GraphQL");
}

var field = Field(typeDefinition.Name, contentItemType.GetType())
.Description(S["Custom user settings of {0}.", typeDefinition.DisplayName])
.ResolveAsync(static 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))
Field(contentItemType.Name, contentItemType)
.Description(S["Custom user settings of {0}.", contentItemType.Name])
.ResolveAsync(static async context =>
{
var customUserSettingsService = context.RequestServices!.GetRequiredService<CustomUserSettingsService>();
var settingsType = await customUserSettingsService.GetSettingsTypeAsync(context.FieldDefinition.ResolvedType.Name);
return await customUserSettingsService.GetSettingsAsync(user, settingsType);
}
// 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;
});
}

return null;
});
base.Initialize(schema);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ public async Task BuildAsync(ISchema schema)
// Register the content item type explicitly since it won't be discovered from the root 'query' type.
schema.RegisterType(typeType);
}

if (!string.IsNullOrEmpty(stereotype))
{
typeType.Metadata["Stereotype"] = stereotype;
}
}

foreach (var builder in contentTypeBuilders)
Expand Down

0 comments on commit 0140e68

Please sign in to comment.