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; /// /// 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. /// [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 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 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 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 }; } /// /// Gelistirme talebi Testing'e alinirken talebin urettigi wizard'larin modul dokumani bolumleri /// tamam olmalidir (lowcode §0.13): bolum var ve doldurulmamis (module-doc:todo) alt bolum yok. /// Talebi Testing'e alan tek yol update_row oldugu icin kural burada uygulanir. Wizard /// okuma yetkisi olmayan kullanici wizard uretemeyecegi icin kontrol ona uygulanmaz. /// 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]); } } /// /// Istegi ekranin bekledigi bicime cevirir. Data JSON metni olarak verilir; ayni /// bicim ekranin kopyalama yolunda da kullanilir. /// private async Task 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()), Keys = [.. (input.Keys ?? []).Select(key => key!)] }; } }