140 lines
5.8 KiB
C#
140 lines
5.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Sozsoft.Mcp.Dtos;
|
|
using Sozsoft.Mcp.Services;
|
|
using Sozsoft.Platform;
|
|
using Sozsoft.Platform.Enums;
|
|
using Sozsoft.Platform.ListForms;
|
|
using Sozsoft.Platform.ListForms.Select;
|
|
using Volo.Abp;
|
|
|
|
namespace Sozsoft.Mcp;
|
|
|
|
/// <summary>
|
|
/// Veri yazma yuzeyi. Yazma her zaman bir ListForm ekraninin insert/update/delete
|
|
/// sozlesmesinden gecer; ekran yetkisi, tenant filtresi ve seed senkronizasyonu oradaki
|
|
/// yoldan aynen uygulanir.
|
|
/// </summary>
|
|
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Invoke)]
|
|
public class McpDataAppService(
|
|
IMcpServerConfigProvider serverConfigProvider,
|
|
IListFormDataAppService listFormDataAppService,
|
|
IMcpListFormAccessFilter accessFilter,
|
|
IMcpDevRequestTimesheetRecorder timesheetRecorder,
|
|
IModuleDocAppService moduleDocAppService)
|
|
: McpAppService(serverConfigProvider), IMcpDataAppService
|
|
{
|
|
private const string DevRequestStatusField = "Status";
|
|
private const string DevRequestTestingStatus = "Testing";
|
|
|
|
public async Task<McpWriteResultDto> InsertRowAsync(McpRowRequestDto input)
|
|
{
|
|
var request = await BuildRequestAsync(input, McpToolNames.InsertRow, AuthorizationTypeEnum.Create, requireKeys: false, requireValues: true);
|
|
var key = await listFormDataAppService.PostInsertAsync(request);
|
|
await timesheetRecorder.RecordAsync(McpTimesheetTexts.InsertRow, input.ListFormCode);
|
|
|
|
return new McpWriteResultDto { Key = ((object?)key)?.ToString() };
|
|
}
|
|
|
|
public async Task<McpWriteResultDto> UpdateRowAsync(McpRowRequestDto input)
|
|
{
|
|
var request = await BuildRequestAsync(input, McpToolNames.UpdateRow, AuthorizationTypeEnum.Update, requireKeys: true, requireValues: true);
|
|
await EnsureModuleDocsBeforeTestingAsync(input);
|
|
var affected = await listFormDataAppService.PostUpdateAsync(request);
|
|
await timesheetRecorder.RecordAsync(McpTimesheetTexts.UpdateRow, input.ListFormCode, string.Join(", ", input.Values.Keys));
|
|
|
|
return new McpWriteResultDto { AffectedRows = affected };
|
|
}
|
|
|
|
public async Task<McpWriteResultDto> DeleteRowAsync(McpRowRequestDto input)
|
|
{
|
|
var request = await BuildRequestAsync(input, McpToolNames.DeleteRow, AuthorizationTypeEnum.Delete, requireKeys: true, requireValues: false);
|
|
var affected = await listFormDataAppService.PostDeleteAsync(request);
|
|
await timesheetRecorder.RecordAsync(McpTimesheetTexts.DeleteRow, input.ListFormCode);
|
|
|
|
return new McpWriteResultDto { AffectedRows = affected };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gelistirme talebi Testing'e alinirken talebin urettigi wizard'larin modul dokumani bolumleri
|
|
/// tamam olmalidir (lowcode §0.13): bolum var ve doldurulmamis (<c>module-doc:todo</c>) alt bolum yok.
|
|
/// Talebi Testing'e alan tek yol <c>update_row</c> oldugu icin kural burada uygulanir. Wizard
|
|
/// okuma yetkisi olmayan kullanici wizard uretemeyecegi icin kontrol ona uygulanmaz.
|
|
/// </summary>
|
|
private async Task EnsureModuleDocsBeforeTestingAsync(McpRowRequestDto input)
|
|
{
|
|
if (!string.Equals(input.ListFormCode, PlatformConsts.AppCodes.DevRequests, StringComparison.OrdinalIgnoreCase))
|
|
return;
|
|
|
|
var status = input.Values?.FirstOrDefault(v => string.Equals(v.Key, DevRequestStatusField, StringComparison.OrdinalIgnoreCase)).Value?.ToString();
|
|
if (!string.Equals(status, DevRequestTestingStatus, StringComparison.OrdinalIgnoreCase))
|
|
return;
|
|
|
|
if (!await AuthorizationService.IsGrantedAsync(PlatformConsts.AppCodes.Listforms.Wizard))
|
|
return;
|
|
|
|
foreach (var key in input.Keys ?? [])
|
|
{
|
|
if (!Guid.TryParse(key?.ToString(), out var devRequestId))
|
|
continue;
|
|
|
|
var incomplete = await moduleDocAppService.GetIncompleteForDevRequestAsync(devRequestId);
|
|
if (incomplete.Count == 0)
|
|
continue;
|
|
|
|
var detail = string.Join("; ", incomplete.Select(doc => $"{doc.Path} ({string.Join(", ", doc.MissingSections)})"));
|
|
|
|
throw new UserFriendlyException(L[McpErrorCodes.DevRequestModuleDocsIncomplete, detail]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Istegi ekranin bekledigi bicime cevirir. <c>Data</c> JSON metni olarak verilir; ayni
|
|
/// bicim ekranin kopyalama yolunda da kullanilir.
|
|
/// </summary>
|
|
private async Task<DataRequestDto> BuildRequestAsync(
|
|
McpRowRequestDto input,
|
|
string toolName,
|
|
AuthorizationTypeEnum operation,
|
|
bool requireKeys,
|
|
bool requireValues)
|
|
{
|
|
Check.NotNull(input, nameof(input));
|
|
|
|
await EnsureToolAllowedAsync(toolName);
|
|
|
|
if (string.IsNullOrWhiteSpace(input.ListFormCode))
|
|
{
|
|
throw new UserFriendlyException(L[McpErrorCodes.ListFormCodeRequired]);
|
|
}
|
|
|
|
// Ekranin kendi kontrolu reddi adsiz bir hatayla bildirir; yetki burada adiyla istenir ki
|
|
// istemci hangi yetkinin eksik oldugunu gorsun.
|
|
var permission = await accessFilter.GetPermissionNameAsync(input.ListFormCode, operation);
|
|
if (permission is not null)
|
|
{
|
|
await AuthorizationService.CheckAsync(permission);
|
|
}
|
|
|
|
if (requireValues && (input.Values is null || input.Values.Count == 0))
|
|
{
|
|
throw new UserFriendlyException(L[McpErrorCodes.ValuesRequired]);
|
|
}
|
|
|
|
if (requireKeys && (input.Keys is null || input.Keys.Count == 0))
|
|
{
|
|
throw new UserFriendlyException(L[McpErrorCodes.KeysRequired]);
|
|
}
|
|
|
|
return new DataRequestDto
|
|
{
|
|
ListFormCode = input.ListFormCode,
|
|
Data = JsonSerializer.Serialize(input.Values ?? new Dictionary<string, object?>()),
|
|
Keys = [.. (input.Keys ?? []).Select(key => key!)]
|
|
};
|
|
}
|
|
}
|