Browse Source

Invitations.

pull/354/head
Sebastian Stehle 7 years ago
parent
commit
9d4f884efd
  1. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs
  2. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs
  3. 19
      src/Squidex.Domain.Apps.Entities/Apps/Invitiation/IInvitationEmailSender.cs
  4. 97
      src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InvitationEmailEventConsumer.cs
  5. 102
      src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InvitationEmailSender.cs
  6. 20
      src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InvitationEmailTextOptions.cs
  7. 6
      src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InviteUserCommandMiddleware.cs
  8. 2
      src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InvitedResult.cs
  9. 2
      src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  10. 3
      src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs
  11. 14
      src/Squidex.Domain.Apps.Entities/IEmailUrlGenerator.cs
  12. 2
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs
  13. 2
      src/Squidex.Domain.Apps.Events/Apps/AppContributorAssigned.cs
  14. 2
      src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Reader.cs
  15. 2
      src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs
  16. 16
      src/Squidex.Infrastructure/Email/IEmailSender.cs
  17. 33
      src/Squidex.Infrastructure/Email/SmptOptions.cs
  18. 42
      src/Squidex.Infrastructure/Email/SmtpEmailSender.cs
  19. 2
      src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs
  20. 10
      src/Squidex.Infrastructure/RefToken.cs
  21. 8
      src/Squidex.Web/Services/UrlGenerator.cs
  22. 1
      src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
  23. 6
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorAssignedDto.cs
  24. 23
      src/Squidex/Config/Domain/EntitiesServices.cs
  25. 61
      src/Squidex/appsettings.json
  26. 10
      tests/Squidex.Infrastructure.Tests/RefTokenTests.cs

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs

@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
user = null;
}
if (user == null && actor.Type.Equals(RefTokenType.Client, StringComparison.OrdinalIgnoreCase))
if (user == null && actor.IsClient)
{
user = new ClientUser(actor);
}

2
src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs

@ -18,5 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands
public bool IsRestore { get; set; }
public bool IsInviting { get; set; }
public bool IsCreated { get; set; }
}
}

19
src/Squidex.Domain.Apps.Entities/Apps/Invitiation/IInvitationEmailSender.cs

@ -0,0 +1,19 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps.Invitiation
{
public interface IInvitationEmailSender
{
Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName);
Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName);
}
}

97
src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InvitationEmailEventConsumer.cs

@ -0,0 +1,97 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Tasks;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps.Invitiation
{
public sealed class InvitationEmailEventConsumer : IEventConsumer
{
private readonly IInvitationEmailSender emailSender;
private readonly IUserResolver userResolver;
private readonly ISemanticLog log;
public string Name
{
get { return "InvitationEmailSender"; }
}
public string EventsFilter
{
get { return "^app-"; }
}
public InvitationEmailEventConsumer(IInvitationEmailSender emailSender, IUserResolver userResolver, ISemanticLog log)
{
Guard.NotNull(emailSender, nameof(emailSender));
Guard.NotNull(userResolver, nameof(userResolver));
Guard.NotNull(log, nameof(log));
this.emailSender = emailSender;
this.userResolver = userResolver;
this.log = log;
}
public bool Handles(StoredEvent @event)
{
return true;
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
public async Task On(Envelope<IEvent> @event)
{
if (@event.Payload is AppContributorAssigned appContributorAssigned && appContributorAssigned.Actor.IsClient)
{
var assigner = await userResolver.FindByIdOrEmailAsync(appContributorAssigned.Actor.Identifier);
if (assigner == null)
{
LogWarning($"Assigner {appContributorAssigned.Actor.Identifier} not found");
return;
}
var assignee = await userResolver.FindByIdOrEmailAsync(appContributorAssigned.ContributorId);
if (assignee == null)
{
LogWarning($"Assignee {appContributorAssigned.ContributorId} not found");
return;
}
var appName = appContributorAssigned.AppId.Name;
if (appContributorAssigned.IsCreated)
{
await emailSender.SendNewUserEmailAsync(assigner, assignee, appName);
}
else
{
await emailSender.SendExistingUserEmailAsync(assigner, assignee, appName);
}
}
}
private void LogWarning(string reason)
{
log.LogWarning(w => w
.WriteProperty("action", "InviteUser")
.WriteProperty("status", "Failed")
.WriteProperty("reason", reason));
}
}
}

102
src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InvitationEmailSender.cs

@ -0,0 +1,102 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Email;
using Squidex.Infrastructure.Log;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps.Invitiation
{
public sealed class InvitationEmailSender : IInvitationEmailSender
{
private readonly IEmailSender emailSender;
private readonly IEmailUrlGenerator emailUrlGenerator;
private readonly ISemanticLog log;
private readonly InvitationEmailTextOptions texts;
public InvitationEmailSender(
IOptions<InvitationEmailTextOptions> texts,
IEmailSender emailSender,
IEmailUrlGenerator emailUrlGenerator,
ISemanticLog log)
{
Guard.NotNull(texts, nameof(texts));
Guard.NotNull(emailSender, nameof(emailSender));
Guard.NotNull(emailUrlGenerator, nameof(emailUrlGenerator));
Guard.NotNull(log, nameof(log));
this.texts = texts.Value;
this.emailSender = emailSender;
this.emailUrlGenerator = emailUrlGenerator;
this.log = log;
}
public Task SendExistingUserEmailAsync(IUser assigner, IUser assignee, string appName)
{
return SendEmailAsync(texts.NewUserSubject, texts.NewUserBody, assigner, assignee, appName);
}
public Task SendNewUserEmailAsync(IUser assigner, IUser assignee, string appName)
{
return SendEmailAsync(texts.ExistingUserBody, texts.ExistingUserSubject, assigner, assignee, appName);
}
private async Task SendEmailAsync(string emailBody, string emailSubj, IUser assigner, IUser assignee, string appName)
{
if (string.IsNullOrWhiteSpace(texts.NewUserSubject))
{
LogWarning("No email subject configured for new users");
return;
}
if (string.IsNullOrWhiteSpace(texts.NewUserBody))
{
LogWarning("No email body configured for new users");
return;
}
var appUrl = emailUrlGenerator.GenerateUIUrl();
emailSubj = Format(emailSubj, assigner, assignee, appUrl, appName);
emailBody = Format(emailBody, assigner, assignee, appUrl, appName);
await emailSender.SendAsync(assignee.Email, emailSubj, emailBody);
}
private void LogWarning(string reason)
{
log.LogWarning(w => w
.WriteProperty("action", "InviteUser")
.WriteProperty("status", "Failed")
.WriteProperty("reason", reason));
}
private string Format(string text, IUser assigner, IUser assignee, string uiUrl, string appName)
{
text = text.Replace("$APP_NAME", appName);
text = text.Replace("$UI_URL", uiUrl);
if (assigner != null)
{
text = text.Replace("$ASSIGNER_EMAIL", assigner.Email);
text = text.Replace("$ASSIGNER_NAME", assigner.DisplayName());
}
if (assignee != null)
{
text = text.Replace("$ASSIGNEE_EMAIL", assignee.Email);
text = text.Replace("$ASSIGNEE_NAME", assignee.DisplayName());
}
return text;
}
}
}

20
src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InvitationEmailTextOptions.cs

@ -0,0 +1,20 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Apps.Invitiation
{
public sealed class InvitationEmailTextOptions
{
public string NewUserSubject { get; set; }
public string NewUserBody { get; set; }
public string ExistingUserSubject { get; set; }
public string ExistingUserBody { get; set; }
}
}

6
src/Squidex.Domain.Apps.Entities/Apps/InviteUserCommandMiddleware.cs → src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InviteUserCommandMiddleware.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps
namespace Squidex.Domain.Apps.Entities.Apps.Invitiation
{
public sealed class InviteUserCommandMiddleware : ICommandMiddleware
{
@ -31,11 +31,11 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
if (assignContributor.IsInviting && assignContributor.ContributorId.IsEmail())
{
var isInvited = await userResolver.CreateUserIfNotExists(assignContributor.ContributorId);
assignContributor.IsCreated = await userResolver.CreateUserIfNotExists(assignContributor.ContributorId);
await next();
if (isInvited && context.PlainResult is EntityCreatedResult<string> id)
if (assignContributor.IsCreated && context.PlainResult is EntityCreatedResult<string> id)
{
context.Complete(new InvitedResult { Id = id });
}

2
src/Squidex.Domain.Apps.Entities/Apps/InvitedResult.cs → src/Squidex.Domain.Apps.Entities/Apps/Invitiation/InvitedResult.cs

@ -7,7 +7,7 @@
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Apps
namespace Squidex.Domain.Apps.Entities.Apps.Invitiation
{
public sealed class InvitedResult
{

2
src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -239,7 +239,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
var actor = CurrentJob.Actor;
if (string.Equals(actor?.Type, RefTokenType.Subject))
if (actor?.IsSubject == true)
{
await commandBus.PublishAsync(new AssignContributor
{

3
src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs

@ -17,6 +17,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents.Text
{
@ -48,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
public Task ClearAsync()
{
return Task.CompletedTask;
return TaskHelper.Done;
}
public async Task On(Envelope<IEvent> @event)

14
src/Squidex.Domain.Apps.Entities/IEmailUrlGenerator.cs

@ -0,0 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities
{
public interface IEmailUrlGenerator
{
string GenerateUIUrl();
}
}

2
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs

@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10));
RegisterTimer(x => CheckUsagesAsync(), null, TimeSpan.Zero, TimeSpan.FromMinutes(10));
return Task.CompletedTask;
return TaskHelper.Done;
}
public Task ActivateAsync()

2
src/Squidex.Domain.Apps.Events/Apps/AppContributorAssigned.cs

@ -15,5 +15,7 @@ namespace Squidex.Domain.Apps.Events.Apps
public string ContributorId { get; set; }
public string Role { get; set; }
public bool IsCreated { get; set; }
}
}

2
src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Reader.cs

@ -34,7 +34,7 @@ namespace Squidex.Infrastructure.EventSourcing
ThrowIfDisposed();
return Task.CompletedTask;
return TaskHelper.Done;
}
public async Task<IReadOnlyList<StoredEvent>> QueryAsync(string streamName, long streamPosition = 0)

2
src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs

@ -103,7 +103,7 @@ namespace Squidex.Infrastructure.EventSourcing
public Task OpenAsync(IChangeFeedObserverContext context)
{
return Task.CompletedTask;
return TaskHelper.Done;
}
public async Task ProcessChangesAsync(IChangeFeedObserverContext context, IReadOnlyList<Document> docs, CancellationToken cancellationToken)

16
src/Squidex.Infrastructure/Email/IEmailSender.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
namespace Squidex.Infrastructure.Email
{
public interface IEmailSender
{
Task SendAsync(string recipient, string subject, string text);
}
}

33
src/Squidex.Infrastructure/Email/SmptOptions.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Email
{
public sealed class SmptOptions
{
public string Server { get; set; }
public string Sender { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool EnableSsl { get; set; }
public int Port { get; set; } = 587;
public bool IsConfigured()
{
return
!string.IsNullOrWhiteSpace(Server) &&
!string.IsNullOrWhiteSpace(Sender) &&
!string.IsNullOrWhiteSpace(Username) &&
!string.IsNullOrWhiteSpace(Password);
}
}
}

42
src/Squidex.Infrastructure/Email/SmtpEmailSender.cs

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
namespace Squidex.Infrastructure.Email
{
public sealed class SmtpEmailSender : IEmailSender
{
private readonly SmtpClient smtpClient;
private readonly string sender;
public SmtpEmailSender(IOptions<SmptOptions> options)
{
Guard.NotNull(options, nameof(options));
var config = options.Value;
smtpClient = new SmtpClient(config.Server, config.Port)
{
Credentials = new NetworkCredential(
config.Username,
config.Password),
EnableSsl = config.EnableSsl
};
sender = config.Sender;
}
public Task SendAsync(string recipient, string subject, string body)
{
return smtpClient.SendMailAsync(sender, recipient, subject, body);
}
}
}

2
src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs

@ -53,7 +53,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
eventConsumer = eventConsumerFactory(key);
return Task.CompletedTask;
return TaskHelper.Done;
}
public Task<Immutable<EventConsumerInfo>> GetStateAsync()

10
src/Squidex.Infrastructure/RefToken.cs

@ -15,6 +15,16 @@ namespace Squidex.Infrastructure
public string Identifier { get; }
public bool IsClient
{
get { return string.Equals(Type, RefTokenType.Client, StringComparison.OrdinalIgnoreCase); }
}
public bool IsSubject
{
get { return string.Equals(Type, RefTokenType.Subject, StringComparison.OrdinalIgnoreCase); }
}
public RefToken(string type, string identifier)
{
Guard.NotNullOrEmpty(type, nameof(type));

8
src/Squidex.Web/Services/UrlGenerator.cs

@ -9,6 +9,7 @@ using System;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents;
@ -19,7 +20,7 @@ using Squidex.Infrastructure.Assets;
namespace Squidex.Web.Services
{
public sealed class UrlGenerator : IGraphQLUrlGenerator, IRuleUrlGenerator, IAssetUrlGenerator
public sealed class UrlGenerator : IGraphQLUrlGenerator, IRuleUrlGenerator, IAssetUrlGenerator, IEmailUrlGenerator
{
private readonly IAssetStore assetStore;
private readonly UrlsOptions urlsOptions;
@ -64,6 +65,11 @@ namespace Squidex.Web.Services
return urlsOptions.BuildUrl($"app/{appId.Name}/content/{schemaId.Name}/{contentId}/history");
}
public string GenerateUIUrl()
{
return urlsOptions.BuildUrl("app/");
}
public string GenerateAssetSourceUrl(IAppEntity app, IAssetEntity asset)
{
return assetStore.GeneratePublicUrl(asset.Id.ToString(), asset.FileVersion, null);

1
src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs

@ -11,6 +11,7 @@ using Microsoft.Net.Http.Headers;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Invitiation;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure.Commands;
using Squidex.Shared;

6
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorAssignedDto.cs

@ -20,11 +20,11 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// <summary>
/// Indicates if the user was created.
/// </summary>
public bool WasInvited { get; set; }
public bool IsCreated { get; set; }
public static ContributorAssignedDto FromId(string id, bool wasInvited)
public static ContributorAssignedDto FromId(string id, bool isCreated)
{
return new ContributorAssignedDto { ContributorId = id, WasInvited = wasInvited };
return new ContributorAssignedDto { ContributorId = id, IsCreated = isCreated };
}
}
}

23
src/Squidex/Config/Domain/EntitiesServices.cs

@ -22,6 +22,7 @@ using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Apps.Invitiation;
using Squidex.Domain.Apps.Entities.Apps.Templates;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Commands;
@ -44,6 +45,7 @@ using Squidex.Domain.Apps.Entities.Schemas.Indexes;
using Squidex.Domain.Apps.Entities.Tags;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Email;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.Orleans;
@ -63,7 +65,7 @@ namespace Squidex.Config.Domain
c.GetRequiredService<IOptions<UrlsOptions>>(),
c.GetRequiredService<IAssetStore>(),
exposeSourceUrl))
.As<IGraphQLUrlGenerator>().As<IRuleUrlGenerator>().As<IAssetUrlGenerator>();
.As<IGraphQLUrlGenerator>().As<IRuleUrlGenerator>().As<IAssetUrlGenerator>().As<IEmailUrlGenerator>();
services.AddSingletonAs<HistoryService>()
.As<IEventConsumer>().As<IHistoryService>();
@ -147,6 +149,25 @@ namespace Squidex.Config.Domain
return result;
});
var emailOptions = config.GetValue<SmptOptions>("email:smtp");
if (emailOptions.IsConfigured())
{
services.AddSingleton(Options.Create(emailOptions));
services.AddSingletonAs<SmtpEmailSender>()
.As<IEmailSender>();
services.AddSingletonAs<InvitationEmailEventConsumer>()
.As<IEventConsumer>();
services.AddSingletonAs<InvitationEmailSender>()
.AsOptional<IInvitationEmailSender>();
services.Configure<InvitationEmailTextOptions>(
config.GetSection("email:invitations"));
}
}
private static void AddCommandPipeline(this IServiceCollection services)

61
src/Squidex/appsettings.json

@ -58,22 +58,69 @@
*
* Supported: GoogleMaps, OSM
*/
"type": "OSM",
"googleMaps": {
/*
"type": "OSM",
"googleMaps": {
/*
* The optional google maps API key. CREATE YOUR OWN PLEASE.
*/
"key": "AIzaSyB_Z8l3nwUxZhMJykiDUJy6bSHXXlwcYMg"
"key": "AIzaSyB_Z8l3nwUxZhMJykiDUJy6bSHXXlwcYMg"
}
}
},
"email": {
"smtp": {
/*
* The host name to your email server.
*/
"server": "",
/*
* The sender email address.
*/
"sender": "hello@squidex.io",
/*
* The username to authenticate to your email server.
*/
"username": "",
/*
* The password to authenticate to your email server.
*/
"password": "",
/*
* Always use SSL if possible.
*/
"enableSsl": true,
/*
* The port to your email server.
*/
"port": 587
},
"invitiations": {
/*
* The email subject when a new user is added as contributor.
*/
"newUserSubject": "Welcome to Squidex, you have been invited to app $APP_NAME",
/*
* The email body when a new user is added as contributor.
*/
"newUserBody": "Welcome\n\nYou have been invited to Squidex CMS by $ASSIGNER_NAME ($ASSIGNER_EMAIL) and to app $",
/*
* The email subject when an existing user is added as contributor.
*/
"existingUserSubject": "[Squidex CMS] You have been invited to app $APP_NAME",
/*
* The email body when an existing user is added as contributor.
*/
"existingUserBody": ""
}
}
},
"robots": {
/*
* The text for the robots.txt file
*/
"text": "User-agent: *\nAllow: /api/assets/*"
},
"text": "User-agent: *\nAllow: /api/assets/*"
},
"healthz": {
"gc": {

10
tests/Squidex.Infrastructure.Tests/RefTokenTests.cs

@ -30,6 +30,16 @@ namespace Squidex.Infrastructure
Assert.Equal("client", token.Type);
Assert.Equal("client1", token.Identifier);
Assert.True(token.IsClient);
}
[Fact]
public void Should_instantiate_subject_token()
{
var token = new RefToken("subject", "client1");
Assert.True(token.IsSubject);
}
[Fact]

Loading…
Cancel
Save