using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; using Kurs.Platform.DynamicData; using Kurs.Platform.Entities; using Kurs.Platform.Extensions; using Kurs.Platform.Queries; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; using Volo.Abp.Tracing; using Volo.Abp.Uow; namespace Kurs.Platform.CustomEndpoints; [Authorize] [Route("api/app/custom-endpoint")] public class CustomEndpointAppService : PlatformAppService { private readonly IRepository repo; private readonly IHttpContextAccessor httpContextAccessor; private readonly IDataSourceManager dataSourceManager; private readonly IDynamicDataManager dynamicDataManager; private readonly DefaultValueHelper defaultValueHelper; public CustomEndpointAppService( IRepository repo, IHttpContextAccessor httpContextAccessor, IDataSourceManager dataSourceManager, IDynamicDataManager dynamicDataManager, DefaultValueHelper defaultValueHelper) { this.repo = repo; this.httpContextAccessor = httpContextAccessor; this.dataSourceManager = dataSourceManager; this.dynamicDataManager = dynamicDataManager; this.defaultValueHelper = defaultValueHelper; } [HttpGet("{**path}")] [Authorize(PlatformConsts.AppCodes.CustomEndpoints.Get)] public async Task GetAsync() { return await Execute("GET"); } [HttpPost("{**path}")] [Authorize(PlatformConsts.AppCodes.CustomEndpoints.Post)] public async Task PostAsync() { return await Execute("POST"); } private async Task Execute(string method) { using var uow = UnitOfWorkManager.Begin(new AbpUnitOfWorkOptions(false), true); try { // Request.Path = /api/app/custom-endpoint/yxcdfn/8 var rawPath = httpContextAccessor.HttpContext.Request.Path.ToString(); var decodedPath = Uri.UnescapeDataString(rawPath); // URL decode işlemi var path = decodedPath .Replace("/api/app/custom-endpoint", "", StringComparison.OrdinalIgnoreCase) .EnsureStartsWith('/') .EnsureEndsWith('/'); Logger.LogInformation("Custom Endpoint çağrısı. Kullanıcı:{user} Path:[{method}]{path}", CurrentUser.UserName, "GET", path); var api = await repo.FirstOrDefaultAsync(a => path.StartsWith(a.Url) && a.Method == method); if (api is null) { Logger.LogInformation("Custom Endpoint bulunamadı"); return new NotFoundResult(); } Logger.LogInformation("Custom Endpoint bulundu. {api}", api.Name); var canUse = api.Permissions.Any(a => (a.ResourceType == "User" && a.ResourceId == CurrentUser.UserName) || (a.ResourceType == "Role" && CurrentUser.Roles.Contains(a.ResourceId)) || (a.ResourceType == "Global")); if (!canUse) { Logger.LogWarning("Custom Endpoint yetki yok"); return new UnauthorizedResult(); } Dictionary param = []; // Parametreler: // 1- Statik foreach (var item in api.Parameters.Where(a => a.Type == PlatformConsts.CustomEndpointConsts.ParameterTypes.Static)) { var value = defaultValueHelper.GetDefaultValue(item.DefaultValue); param.Add(item.Name, value); } // 2- Query var queryParams = httpContextAccessor.HttpContext.Request.Query; foreach (var item in api.Parameters.Where(a => a.Type == PlatformConsts.CustomEndpointConsts.ParameterTypes.Query)) { if (queryParams.TryGetValue(item.Name, out var value)) { param.Add(item.Name, value); } else { if (item.IsRequired) { throw new Volo.Abp.UserFriendlyException(L[PlatformConsts.AppErrorCodes.ParameterNotValid, item.Name]); } else { param.Add(item.Name, defaultValueHelper.GetDefaultValue(item.DefaultValue)); } } } // 3- Path foreach (var item in api.Parameters.Where(a => a.Type == PlatformConsts.CustomEndpointConsts.ParameterTypes.Path && !a.Path.IsNullOrWhiteSpace())) { var itemPath = item.Path.EnsureStartsWith('/').EnsureEndsWith('/'); var index = itemPath.IndexOf($"/:{item.Name}/"); if (index == -1) { throw new Volo.Abp.UserFriendlyException(L[PlatformConsts.AppErrorCodes.ParameterNotValid, item.Name]); } var segmentCount = itemPath[..(index + 1)].Count(a => a == '/'); var value = path.GetSegment('/', segmentCount); param.Add(item.Name, value ?? defaultValueHelper.GetDefaultValue(item.DefaultValue)); } // 4- Body if (method == "POST") { var body = await httpContextAccessor.HttpContext.Request.ReadFormAsync(); foreach (var item in api.Parameters.Where(a => a.Type == PlatformConsts.CustomEndpointConsts.ParameterTypes.Body)) { if (body.TryGetValue(item.Name, out var value)) { param.Add(item.Name, value); } else { if (item.IsRequired) { throw new Volo.Abp.UserFriendlyException(L[PlatformConsts.AppErrorCodes.ParameterNotValid, item.Name]); } else { param.Add(item.Name, defaultValueHelper.GetDefaultValue(item.DefaultValue)); } } } } Logger.LogInformation("Parametreler: {param}", param); var (dynamicDataRepository, connectionString, _) = await dynamicDataManager.GetAsync(api.DataSourceCode == "!Tenant", api.DataSourceCode); var result = await dynamicDataRepository.QueryAsync(api.Sql, connectionString, param); Logger.LogInformation("Sonuç başarılı"); Logger.LogInformation("{result}", result); await uow.CompleteAsync(); return new ObjectResult(result); } catch (Volo.Abp.UserFriendlyException ex) { Logger.LogException(ex); await uow.RollbackAsync(); return new JsonResult(new { Message = ex.Message, CorrelationId = LazyServiceProvider.GetRequiredService().Get(), }) { StatusCode = 500, }; } catch (Exception ex) { Logger.LogException(ex); await uow.RollbackAsync(); return new JsonResult(new { Message = L[PlatformConsts.AppErrorCodes.InternalError].Value, CorrelationId = LazyServiceProvider.GetRequiredService().Get() }) { StatusCode = 500, }; } } } //TODO: Custom Endpoint rol, permission seed /* Token İsteği Örnek: POST /connect/token HTTP/1.1 Host: localhost:44344 Content-Type: application/x-www-form-urlencoded username=system%40sozsoft.com &password=... &grant_type=password &client_id=Platform_PublicApi &scope=offline_access%20Platform Custom Endpoint Seed: SELECT * FROM PLanguage WHERE IsEnabled = @IsEnabled AND CultureName = @CultureName INSERT INTO [dbo].[Orders] ([CustomerName] ,[ProductName] ,[OrderDate] ,[Quantity]) OUTPUT Inserted.* VALUES (@CustomerName, @ProductName, @OrderDate, @Quantity) SELECT * FROM Orders WHERE Id = SCOPE_IDENTITY() [{ "Type": "S", "Name": "CultureName", "DefaultValue": "ar" }] [{ "Type": "Q", "Name": "CultureName", "DefaultValue": "ar" }] [ { "Type": "P", "Name": "IsEnabled", "DefaultValue": "en", "Path": "/yxcdfn/8/:IsEnabled/:CultureName/" }, { "Type": "P", "Name": "CultureName", "DefaultValue": "en", "Path": "/yxcdfn/8/:IsEnabled/:CultureName/" } ] [ { "Type": "B", "Name": "CustomerName", "DefaultValue": "" }, { "Type": "B", "Name": "ProductName", "DefaultValue": "" }, { "Type": "B", "Name": "OrderDate", "DefaultValue": "@NOW" }, { "Type": "B", "Name": "Quantity", "DefaultValue": "" } ] Guid? TenantId string Name string Description string Url -> https://api.sozsoft.com/api/app/dinamik/yxfgu string Method -> GET string Params = [ { Type: 'Static', Name: 'StartDate', DefaultValue: '234' }, { Type: 'Query', Name: 'StartDate', DefaultValue: '', IsRequired: false }, { Type: 'Path', Name: 'FaturaId', DefaultValue: '', Path: '/yxfgu/fatura/:FaturaId/kalem/357' }, { Type: 'Path', Name: 'KalemId', DefaultValue: '', Path: '/yxfgu/fatura/xxx/kalem/:KalemId' }, { Type: 'Body', Name: 'StartDate', DefaultValue: '' }, ] string Sql -> SELECT * FROM VSatislar WHERE MusteriId = @MusteriId AND StartDate >= @StartDate string Permissions = [ { ResourceType: 'User', ResourceId: 'system' }, { ResourceType: 'User', ResourceId: 'sedat' }, ] Query Parameter URL: https://api.sozsoft.com/api/app/dinamik/yxfgu Method: GET Parameters: ?StartDate=2024-12-31&EndDate=2025-12-31 Path Parameter URL: https://api.sozsoft.com/api/app/dinamik/yxfgu/fatura/467/kalem/357 Method: GET Parameters: FaturaId=467 Body Parameter URL: https://api.sozsoft.com/api/app/dinamik/yxfgu?UrunId=5 Method: POST Parameters: ?StartDate=2024-12-31&EndDate=2025-12-31 Body: { Tutar: 2000, Tarih: '2024-12-31' } */