Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Members and member types in the Management API #15662

Merged
merged 17 commits into from Feb 5, 2024

Conversation

kjac
Copy link
Contributor

@kjac kjac commented Jan 31, 2024

Prerequisites

  • I have added steps to test this contribution in the description below

Description

This PR adds member type and member editing to the Management API. The feature set mimics what is possible in the current back-office, which is a subset of document and media capabilities.

Limitations

Sensitive properties are not handled by the API as a whole. This is yet to be added, so sensitive member data are treated as any other data at this point in time.

Authorization needs to be added. See FIXME in MemberControllerBase. A separate task will be created to amend this.

Testing this PR

Using Swagger it should be possible to perform all member and member type operations that are possible in the current back-office.

Members

Note that the member models have a fair few required properties that are not part of documents and media (e.g. username, email, password, ...).

Member types

  • Get
  • Create
  • Update
  • Delete
  • Copy
  • Use compositions

@kjac kjac marked this pull request as ready for review February 1, 2024 10:37
@bergmania
Copy link
Member

Seems like I can't determine the member ID from the outside. Even when I provide one, it is just ignored

@kjac
Copy link
Contributor Author

kjac commented Feb 1, 2024

You're absolutely right @bergmania ... the "regular" document/media creation is not reused for members, so the handling of explicit IDs during content creation is not applied here.

I have created a fix 😄

@bergmania
Copy link
Member

When I create a member, I have to consider a parent. That does not make sense, right?

Also, when I try to create a member with an id that already exists i just get:

{
  "type": "Error",
  "title": "Unknown error. Please see the log for more details.",
  "status": 500,
  "operationStatus": "Unknown"
}

The message in the log is as expected:

Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'UNIQUE constraint failed: umbracoNode.uniqueId'.
   at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
   at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader()
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteScalar()
   at Umbraco.Cms.Persistence.Sqlite.Services.SqlitePreferDeferredTransactionsConnection.CommandWrapper.ExecuteScalar() in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Cms.Persistence.Sqlite/Services/SqlitePreferDeferredTransactionsConnection.cs:line 107
   at StackExchange.Profiling.Data.ProfiledDbCommand.ExecuteScalar() in C:\projects\dotnet\src\MiniProfiler.Shared\Data\ProfiledDbCommand.cs:line 315
   at Umbraco.Cms.Infrastructure.Persistence.FaultHandling.FaultHandlingDbCommand.<ExecuteScalar>b__36_0() in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/FaultHandling/RetryDbConnection.cs:line 185
   at Umbraco.Cms.Infrastructure.Persistence.FaultHandling.FaultHandlingDbCommand.<>c__DisplayClass38_0`1.<Execute>b__0() in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/FaultHandling/RetryDbConnection.cs:line 193
   at Umbraco.Cms.Infrastructure.Persistence.FaultHandling.RetryPolicy.ExecuteAction[TResult](Func`1 func) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/FaultHandling/RetryPolicy.cs:line 179
   at Umbraco.Cms.Infrastructure.Persistence.FaultHandling.FaultHandlingDbCommand.Execute[T](Func`1 f) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/FaultHandling/RetryDbConnection.cs:line 190
   at Umbraco.Cms.Infrastructure.Persistence.FaultHandling.FaultHandlingDbCommand.ExecuteScalar() in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/FaultHandling/RetryDbConnection.cs:line 185
   at NPoco.Database.<>c__DisplayClass297_0.<ExecuteScalarHelper>b__0()
   at NPoco.Database.ExecutionHook[T](Func`1 action)
   at NPoco.Database.ExecuteScalarHelper(DbCommand cmd)
   at NPoco.DatabaseTypes.SQLiteDatabaseType.ExecuteInsert[T](Database db, DbCommand cmd, String primaryKeyName, Boolean useOutputClause, T poco, Object[] args)
   at NPoco.Database.InsertAsyncImp[T](PocoData pocoData, String tableName, String primaryKeyName, Boolean autoIncrement, T poco, Boolean sync)
   at NPoco.AsyncHelper.RunSync[T](Task`1 task)
   at NPoco.Database.Insert[T](String tableName, String primaryKeyName, Boolean autoIncrement, T poco)
   at NPoco.Database.Insert[T](T poco)
   at Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement.MemberRepository.PersistNewItem(IMember entity) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs:line 640
   at Umbraco.Cms.Core.Cache.DefaultRepositoryCachePolicy`2.Create(TEntity entity, Action`1 persistNew) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs:line 43
   at Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement.EntityRepositoryBase`2.Save(TEntity entity) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs:line 116
   at Umbraco.Cms.Core.Services.MemberService.Save(IMember member, Int32 userId) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Core/Services/MemberService.cs:line 775
   at Umbraco.Cms.Core.Security.MemberUserStore.CreateAsync(MemberIdentityUser user, CancellationToken cancellationToken) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Security/MemberUserStore.cs:line 130

@@ -745,7 +749,7 @@ public bool Exists(string username)
public void SetLastLogin(string username, DateTime date) => throw new NotImplementedException();

/// <inheritdoc />
public void Save(IMember member)
public Attempt<OperationResult?> Save(IMember member, int userId = Constants.Security.SuperUserId)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is the pattern often, but I do not like a default value to be the super admin. For new methods I think we should avoid having the default value.

Copy link
Contributor Author

@kjac kjac Feb 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm definitively not a fan of the pattern either, but the method signature originates from IContentServiceBase<IMember>, which is a requirement for using the generic content handling that powers the document and media APIs too. The userId parameter is defined as optional and with the super user default value in the interface (see source), so we can't really do much about it here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it could work just to have public Attempt<OperationResult?> Save(IMember member, int userId)?

@bergmania
Copy link
Member

Also able to get this error 500 when saving a member type

{
  "type": "Error",
  "title": "The property something cannot have variations of CultureAndSegment with the content type variations of Nothing",
  "status": 500,
  "detail": "   at Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement.ContentTypeRepositoryBase`1.ValidateVariations(IContentTypeComposition entity) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs:line 709\n   at Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement.ContentTypeRepositoryBase`1.PersistNewBaseContentType(IContentTypeComposition entity) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs:line 172\n   at Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement.MemberTypeRepository.PersistNewItem(IMemberType entity) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs:line 157\n   at Umbraco.Cms.Core.Cache.FullDataSetRepositoryCachePolicy`2.Create(TEntity entity, Action`1 persistNew) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs:line 48\n   at Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement.EntityRepositoryBase`2.Save(TEntity entity) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs:line 116\n   at Umbraco.Cms.Core.Services.ContentTypeServiceBase`2.Save(TItem item, Int32 userId) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs:line 539\n   at Umbraco.Cms.Core.Services.ContentTypeServiceBase`2.SaveAsync(TItem item, Guid performingUserKey) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs:line 499\n   at Umbraco.Cms.Core.Services.ContentTypeEditing.MemberTypeEditingService.CreateAsync(MemberTypeCreateModel model, Guid userKey) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Core/Services/ContentTypeEditing/MemberTypeEditingService.cs:line 27\n   at Umbraco.Cms.Api.Management.Controllers.MemberType.CreateMemberTypeController.Create(CreateMemberTypeRequestModel requestModel) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CreateMemberTypeController.cs:line 40\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()\n--- End of stack trace from previous location ---\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()\n--- End of stack trace from previous location ---\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)\n   at Umbraco.Cms.Web.Common.Middleware.BasicAuthenticationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Web.Website/Middleware/BasicAuthenticationMiddleware.cs:line 62\n   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()\n--- End of stack trace from previous location ---\n   at Umbraco.Cms.Web.BackOffice.Middleware.BackOfficeExternalLoginProviderErrorMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Web.BackOffice/Middleware/BackOfficeExternalLoginProviderErrorMiddleware.cs:line 50\n   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()\n--- End of stack trace from previous location ---\n   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)\n   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)\n   at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)\n   at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)\n   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)\n   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\n   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)\n   at StackExchange.Profiling.MiniProfilerMiddleware.Invoke(HttpContext context) in C:\\projects\\dotnet\\src\\MiniProfiler.AspNetCore\\MiniProfilerMiddleware.cs:line 112\n   at Umbraco.Cms.Web.Common.Middleware.UmbracoRequestMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs:line 139\n   at Umbraco.Cms.Web.Common.Middleware.UmbracoRequestMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs:line 145\n   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()\n--- End of stack trace from previous location ---\n   at Umbraco.Cms.Web.Common.Middleware.PreviewAuthenticationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Web.Common/Middleware/PreviewAuthenticationMiddleware.cs:line 82\n   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()\n--- End of stack trace from previous location ---\n   at Umbraco.Cms.Web.Common.Middleware.UmbracoRequestLoggingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in /Users/bjarkeberg/RiderProjects/Umbraco-CMS-V14/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs:line 42\n   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()\n--- End of stack trace from previous location ---\n   at SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware.Invoke(HttpContext httpContext, Boolean retry)\n   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|10_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)",
  "instance": "InvalidOperationException"
}

@kjac
Copy link
Contributor Author

kjac commented Feb 2, 2024

"Parent" for members and "Folder" for member types have been removed from the member API (whilst retained in the document and media APIs). Good catch 👍

As for creating entities using existing IDs, this is a problem across the all content APIs. I have added an explicit task to handle this.

The member type save validation error is caused by attempting variance for a member type. This is also a problem for media. I have added another task to handle this 😄

@kjac kjac merged commit 71b3076 into v14/dev Feb 5, 2024
14 of 15 checks passed
@kjac kjac deleted the v14/feature/members-and-member-types branch February 5, 2024 05:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants