@@ -66,7 +66,8 @@ internal sealed class CrmService(
|
||||
config.ExamType = NormalizeOptional(command.ExamType);
|
||||
config.TimeoutSeconds = command.TimeoutSeconds;
|
||||
config.DelaySeconds = command.DelaySeconds;
|
||||
config.AssignmentMode = ParseEnum(command.AssignmentMode, ReferralAssignmentMode.None, "invalid_assignment_mode");
|
||||
config.AssignmentMode =
|
||||
ParseEnum(command.AssignmentMode, ReferralAssignmentMode.None, "invalid_assignment_mode");
|
||||
config.AssignmentPool = EnsureArray(command.AssignmentPool);
|
||||
config.AssignmentConfig = EnsureObject(command.AssignmentConfig);
|
||||
|
||||
@@ -107,9 +108,8 @@ internal sealed class CrmService(
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Status))
|
||||
{
|
||||
deadLetters = deadLetters.Where(item => item.Status == ParseEnum(query.Status, CrmWebhookQueueStatus.Failed, "invalid_crm_queue_status"));
|
||||
}
|
||||
deadLetters = deadLetters.Where(item =>
|
||||
item.Status == ParseEnum(query.Status, CrmWebhookQueueStatus.Failed, "invalid_crm_queue_status"));
|
||||
|
||||
var items = await deadLetters
|
||||
.OrderBy(item => item.CreatedAt)
|
||||
@@ -144,11 +144,11 @@ internal sealed class CrmService(
|
||||
if (query.QueueId.HasValue)
|
||||
{
|
||||
var queue = await dbContext.CrmWebhookQueue
|
||||
.AsNoTracking()
|
||||
.Where(item => item.TenantId == actor.TenantId && item.Id == query.QueueId.Value)
|
||||
.Select(item => new { item.Id, item.RecordId })
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
?? throw new CrmException("CRM queue item was not found.", "crm_queue_not_found");
|
||||
.AsNoTracking()
|
||||
.Where(item => item.TenantId == actor.TenantId && item.Id == query.QueueId.Value)
|
||||
.Select(item => new { item.Id, item.RecordId })
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
?? throw new CrmException("CRM queue item was not found.", "crm_queue_not_found");
|
||||
logs = logs.Where(item => item.RecordId == queue.RecordId || item.RecordId == queue.Id.ToString());
|
||||
}
|
||||
|
||||
@@ -166,15 +166,15 @@ internal sealed class CrmService(
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var item = await dbContext.CrmWebhookQueue
|
||||
.FirstOrDefaultAsync(entry => entry.TenantId == actor.TenantId && entry.Id == command.QueueId, cancellationToken)
|
||||
?? throw new CrmException("CRM queue item was not found.", "crm_queue_not_found");
|
||||
.FirstOrDefaultAsync(entry => entry.TenantId == actor.TenantId && entry.Id == command.QueueId,
|
||||
cancellationToken)
|
||||
?? throw new CrmException("CRM queue item was not found.", "crm_queue_not_found");
|
||||
var action = NormalizeOptional(command.Action) ?? "retry";
|
||||
if (action.Equals("retry", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (item.Status is not (CrmWebhookQueueStatus.Failed or CrmWebhookQueueStatus.Discarded or CrmWebhookQueueStatus.Retrying))
|
||||
{
|
||||
if (item.Status is not (CrmWebhookQueueStatus.Failed or CrmWebhookQueueStatus.Discarded
|
||||
or CrmWebhookQueueStatus.Retrying))
|
||||
throw new CrmException("CRM queue item cannot be retried.", "crm_queue_status_invalid");
|
||||
}
|
||||
|
||||
item.Status = CrmWebhookQueueStatus.Pending;
|
||||
item.NextAttemptAt = DateTimeOffset.UtcNow;
|
||||
@@ -183,9 +183,7 @@ internal sealed class CrmService(
|
||||
else if (action.Equals("ignore", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (item.Status is CrmWebhookQueueStatus.Sent)
|
||||
{
|
||||
throw new CrmException("CRM queue item cannot be ignored.", "crm_queue_status_invalid");
|
||||
}
|
||||
|
||||
item.Status = CrmWebhookQueueStatus.Discarded;
|
||||
item.LastError = NormalizeOptional(command.Note) ?? item.LastError;
|
||||
@@ -215,15 +213,11 @@ internal sealed class CrmService(
|
||||
var items = dbContext.CrmWebhookQueue
|
||||
.AsNoTracking()
|
||||
.Where(item => item.TenantId == tenantId);
|
||||
if (query.QueueId.HasValue)
|
||||
{
|
||||
items = items.Where(item => item.Id == query.QueueId.Value);
|
||||
}
|
||||
if (query.QueueId.HasValue) items = items.Where(item => item.Id == query.QueueId.Value);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Status))
|
||||
{
|
||||
items = items.Where(item => item.Status == ParseEnum(query.Status, CrmWebhookQueueStatus.Pending, "invalid_crm_queue_status"));
|
||||
}
|
||||
items = items.Where(item =>
|
||||
item.Status == ParseEnum(query.Status, CrmWebhookQueueStatus.Pending, "invalid_crm_queue_status"));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Source))
|
||||
{
|
||||
@@ -241,7 +235,8 @@ internal sealed class CrmService(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var item = await dbContext.TenantSecrets
|
||||
.FirstOrDefaultAsync(secretItem => secretItem.TenantId == tenantId && secretItem.SecretRef == secretRef, cancellationToken);
|
||||
.FirstOrDefaultAsync(secretItem => secretItem.TenantId == tenantId && secretItem.SecretRef == secretRef,
|
||||
cancellationToken);
|
||||
if (item is null)
|
||||
{
|
||||
item = new TenantSecret
|
||||
@@ -278,9 +273,7 @@ internal sealed class CrmService(
|
||||
access.TenantId != actor.TenantId ||
|
||||
!access.HasTenantPermission(BackendPermissions.TenantCrmManage) ||
|
||||
access.DataScope.Mode != DataScopeMode.All)
|
||||
{
|
||||
throw new CrmException("CRM admin access was denied.", "crm_access_denied");
|
||||
}
|
||||
}
|
||||
|
||||
private static CrmConfigItem ToConfigItem(CrmConfig item)
|
||||
@@ -334,14 +327,10 @@ internal sealed class CrmService(
|
||||
private static JsonElement EnsureObject(JsonElement? value)
|
||||
{
|
||||
if (value is null || value.Value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null)
|
||||
{
|
||||
return JsonDefaults.Object();
|
||||
}
|
||||
|
||||
if (value.Value.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
throw new CrmException("CRM JSON value must be an object.", "invalid_json_payload");
|
||||
}
|
||||
|
||||
return value.Value.Clone();
|
||||
}
|
||||
@@ -349,21 +338,17 @@ internal sealed class CrmService(
|
||||
private static JsonElement EnsureArray(JsonElement? value)
|
||||
{
|
||||
if (value is null || value.Value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null)
|
||||
{
|
||||
return JsonDefaults.Array();
|
||||
}
|
||||
|
||||
if (value.Value.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
throw new CrmException("CRM JSON value must be an array.", "invalid_json_payload");
|
||||
}
|
||||
|
||||
return value.Value.Clone();
|
||||
}
|
||||
|
||||
private static JsonElement Redact(JsonElement value)
|
||||
{
|
||||
object? converted = RedactValue(value);
|
||||
var converted = RedactValue(value);
|
||||
return JsonSerializer.SerializeToElement(converted);
|
||||
}
|
||||
|
||||
@@ -387,16 +372,10 @@ internal sealed class CrmService(
|
||||
|
||||
private static string? RedactText(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(value)) return value;
|
||||
|
||||
var result = value;
|
||||
foreach (var key in SensitiveKeys)
|
||||
{
|
||||
result = result.Replace(key, "***", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
foreach (var key in SensitiveKeys) result = result.Replace(key, "***", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -404,19 +383,12 @@ internal sealed class CrmService(
|
||||
private static TEnum ParseEnum<TEnum>(string? value, TEnum defaultValue, string errorCode)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(value)) return defaultValue;
|
||||
|
||||
var normalized = value.Replace("_", string.Empty, StringComparison.Ordinal);
|
||||
foreach (var enumValue in Enum.GetValues<TEnum>())
|
||||
{
|
||||
if (string.Equals(enumValue.ToString(), normalized, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return enumValue;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CrmException("CRM enum value was invalid.", errorCode);
|
||||
}
|
||||
@@ -425,4 +397,4 @@ internal sealed class CrmService(
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user