diff --git a/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/CurrentUserQuery.cs b/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/CurrentUserQuery.cs new file mode 100644 index 00000000000..7acaf54d6e3 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/CurrentUserQuery.cs @@ -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; + +/// +/// Registers the current user including its custom user settings as a query. +/// +internal class CurrentUserQuery : ISchemaBuilder +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IOptions _contentOptionsAccessor; + protected readonly IStringLocalizer S; + + public CurrentUserQuery( + IHttpContextAccessor httpContextAccessor, + IOptions contentOptionsAccessor, + IStringLocalizer 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(); + var contentTypes = await contentDefinitionManager.ListTypeDefinitionsAsync(); + + var userType = serviceProvider.GetRequiredService(); + + // 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(async context => + { + var userService = context.RequestServices!.GetRequiredService(); + var user = await userService.GetAuthenticatedUserAsync(((GraphQLUserContext)context.UserContext).User); + + return user as User; + }), + }; + + schema.Query.AddField(currentUserField); + } + + public Task GetIdentifierAsync() + { + var contentDefinitionManager = _httpContextAccessor.HttpContext!.RequestServices.GetRequiredService(); + return contentDefinitionManager.GetIdentifierAsync(); + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/Startup.cs new file mode 100644 index 00000000000..4dba4a80de3 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/Startup.cs @@ -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(); + services.AddTransient(); + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/UserType.cs b/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/UserType.cs new file mode 100644 index 00000000000..9f43c89cc0f --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Users/GraphQL/UserType.cs @@ -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 +{ + protected readonly IStringLocalizer S; + + public UserType(IStringLocalizer 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(); + var settingsType = await customUserSettingsService.GetSettingsTypeAsync(context.FieldDefinition.ResolvedType.Name); + + return await customUserSettingsService.GetSettingsAsync(user, settingsType); + } + + return null; + }); + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Users/OrchardCore.Users.csproj b/src/OrchardCore.Modules/OrchardCore.Users/OrchardCore.Users.csproj index c074f55b1b5..eaaf839674a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/OrchardCore.Users.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Users/OrchardCore.Users.csproj @@ -18,8 +18,10 @@ + +