From 0c202ece244ae81c9537f19a6f12e0630f643794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:52:35 +0300 Subject: [PATCH] =?UTF-8?q?User=20Insert=20ve=20Update=20i=C3=A7in=20Avata?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ListForms/ListFormDynamicApiAppService.cs | 59 ++++++++++++++++-- .../Seeds/ListFormSeeder_Administration.cs | 8 +-- .../PlatformConsts.cs | 2 +- ui/public/img/others/no-image.png | Bin 0 -> 3923 bytes ui/src/proxy/admin/models.ts | 1 + .../editors/ImageViewerEditorComponent.tsx | 2 +- .../editors/ImageViewerEditorComponent.tsx | 2 +- ui/src/views/list/useListFormColumns.ts | 8 +-- 8 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 ui/public/img/others/no-image.png diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs index 2dd169a..2a13713 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Sozsoft.Platform.Extensions; using Microsoft.AspNetCore.Authorization; @@ -10,17 +12,26 @@ using Volo.Abp.Domain.Entities; using Volo.Abp.Identity; using Volo.Abp.TenantManagement; using IdentityUser = Volo.Abp.Identity.IdentityUser; +using Sozsoft.Platform.BlobStoring; +using Sozsoft.Platform.Identity; +using Microsoft.Extensions.Configuration; namespace Sozsoft.Platform.ListForms.DynamicApi; [Authorize] public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamicApiAppService { + private static readonly Regex DataUrlRegex = new( + @"^data:(?image\/[a-zA-Z0-9.+-]+);base64,(?.+)$", + RegexOptions.Compiled); + private readonly ITenantRepository tenantRepository; private readonly ITenantManager tenantManager; private readonly IIdentityUserAppService identityUserAppService; private readonly IIdentityRoleAppService identityRoleAppService; private readonly IdentityUserManager userManager; + private readonly BlobManager blobCdnManager; + private readonly IConfiguration configuration; private readonly IOptions identityOptions; public ListFormDynamicApiAppService( @@ -29,6 +40,8 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic IIdentityUserAppService identityUserAppService, IIdentityRoleAppService identityRoleAppService, IdentityUserManager userManager, + BlobManager blobCdnManager, + IConfiguration configuration, IOptions identityOptions) { this.tenantRepository = tenantRepository; @@ -36,6 +49,8 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic this.identityUserAppService = identityUserAppService; this.identityRoleAppService = identityRoleAppService; this.userManager = userManager; + this.blobCdnManager = blobCdnManager; + this.configuration = configuration; this.identityOptions = identityOptions; } @@ -44,6 +59,41 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic return Guid.TryParse(value, out var id) ? id : Guid.Empty; } + private async Task SaveAvatarAsync(IdentityUser user, string avatar) + { + if (avatar.IsNullOrWhiteSpace()) + { + return; + } + + var base64 = avatar.Trim(); + var match = DataUrlRegex.Match(base64); + if (match.Success) + { + base64 = match.Groups["data"].Value; + } + else if (avatar.StartsWith("http", StringComparison.OrdinalIgnoreCase) || + avatar.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + byte[] bytes; + try + { + bytes = Convert.FromBase64String(base64); + } + catch (FormatException) + { + return; + } + + var fileName = $"{user.Id}.jpg"; + await using var stream = new MemoryStream(bytes); + await blobCdnManager.SaveAsync(BlobContainerNames.Avatar, fileName, stream); + user.SetAvatar(AvatarProvider.GetAvatar(configuration, user.TenantId?.ToString(), user.Id.ToString())); + } + [Authorize(IdentityPermissions.Users.Create)] public async Task PostUserInsertAsync(DynamicApiBaseInput input) { @@ -68,7 +118,8 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic user.SetDepartmentId(ParseGuid(input.Data.DepartmentId)); user.SetJobPositionId(ParseGuid(input.Data.JobPositionId)); user.SetIsVerified(verify); - user.SetAvatar(input.Data.Avatar); + + await SaveAvatarAsync(user, input.Data.Avatar); (await userManager.CreateAsync(user, input.Data.Password)).CheckErrors(); await userManager.SetLockoutEnabledAsync(user, true); @@ -139,11 +190,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic user.SetJobPositionId(ParseGuid(input.Data.JobPositionId)); } - if (input.Data.Avatar != null) - { - user.SetJobPositionId(ParseGuid(input.Data.Avatar)); - } - + await SaveAvatarAsync(user, input.Data.Avatar); (await userManager.UpdateAsync(user)).CheckErrors(); } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs index fc16f8e..96e1e24 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs @@ -809,7 +809,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 710, true, true, true, true, false), EditingFormJson = JsonSerializer.Serialize(new List() { new () { Order=1,ColCount=1,ColSpan=1,ItemType="group",Items=[ - new EditingFormItemDto { Order=1, DataField="Avatar", ColSpan=1, EditorType2=EditorTypes.dxImageViewer }, + new EditingFormItemDto { Order=1, DataField="Avatar", ColSpan=1, EditorType2=EditorTypes.dxImageViewer, EditorOptions=EditorOptionValues.ImageUploadOptions(false) }, new EditingFormItemDto { Order=2, DataField="Email", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order=3, DataField="Name", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order=4, DataField="Surname", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, @@ -2703,7 +2703,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.Announcement)), DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(), PagerOptionJson = DefaultPagerOptionJson, - EditingOptionJson = DefaultEditingOptionJson(listFormName, 750, 600, true, true, true, true, false), + EditingOptionJson = DefaultEditingOptionJson(listFormName, 750, 700, true, true, true, true, false), InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(), EditingFormJson = JsonSerializer.Serialize(new List() { @@ -2717,7 +2717,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep new EditingFormItemDto { Order = 6, DataField = "PublishDate", ColSpan=1, EditorType2 = EditorTypes.dxDateBox }, new EditingFormItemDto { Order = 7, DataField = "ExpiryDate", ColSpan=1, EditorType2 = EditorTypes.dxDateBox }, new EditingFormItemDto { Order = 8, DataField = "IsPinned", ColSpan=1, EditorType2 = EditorTypes.dxCheckBox }, - new EditingFormItemDto { Order = 9, DataField = "ImageUrl", ColSpan=1, EditorType2 = EditorTypes.dxImageUpload, EditorOptions = EditorOptionValues.ImageUploadOptions}, + new EditingFormItemDto { Order = 9, DataField = "ImageUrl", ColSpan=1, EditorType2 = EditorTypes.dxImageUpload, EditorOptions = EditorOptionValues.ImageUploadOptions()}, ]} }), FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] @@ -4218,7 +4218,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep new EditingFormItemDto { Order = 7, DataField = "Status", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, new EditingFormItemDto { Order = 8, DataField = "ParticipantsCount", ColSpan = 1, EditorType2 = EditorTypes.dxNumberBox }, new EditingFormItemDto { Order = 9, DataField = "Description", ColSpan = 2, EditorType2 = EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 10, DataField = "Photos", ColSpan = 1, EditorType2 = EditorTypes.dxImageUpload, EditorOptions = EditorOptionValues.ImageUploadOptions }, + new EditingFormItemDto { Order = 10, DataField = "Photos", ColSpan = 1, EditorType2 = EditorTypes.dxImageUpload, EditorOptions = EditorOptionValues.ImageUploadOptions() }, ]} }), InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(), diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index 0da7027..f67c5a0 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -27,7 +27,7 @@ public static class PlatformConsts public static string DateFormat = "{ \"format\": \"dd/MM/yyyy\", \"displayFormat\" : \"dd/MM/yyyy\" }"; public static string DateTimeFormat = "{ \"format\": \"dd/MM/yyyy HH:mm\", \"displayFormat\" : \"dd/MM/yyyy HH:mm\" }"; public static string SliderOptions = "{\"tooltip\": { \"enabled\": true }}"; - public static string ImageUploadOptions = "{\"width\": 80, \"height\": 80, \"multiple\": true}"; + public static string ImageUploadOptions(bool multiple = true) => $"{{\"width\": 80, \"height\": 80, \"multiple\": {multiple.ToString().ToLower()}}}"; } public static class EditorScriptValues diff --git a/ui/public/img/others/no-image.png b/ui/public/img/others/no-image.png new file mode 100644 index 0000000000000000000000000000000000000000..ebb9b8f7f9ee149008f4f55f31380a23399d4bc7 GIT binary patch literal 3923 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 Yw1Msvqd;el>Hj$!>znG;={lkR4~%zIh5!Hn literal 0 HcmV?d00001 diff --git a/ui/src/proxy/admin/models.ts b/ui/src/proxy/admin/models.ts index b58e50a..58c2415 100644 --- a/ui/src/proxy/admin/models.ts +++ b/ui/src/proxy/admin/models.ts @@ -137,6 +137,7 @@ export interface UserInfoViewModel extends ExtensibleObject { phoneNumberConfirmed: boolean accessFailedCount: number shouldChangePasswordOnNextLogin: boolean + avatar: string rocketUsername?: string creationTime: Date | string lastModificationTime: Date | string diff --git a/ui/src/views/form/editors/ImageViewerEditorComponent.tsx b/ui/src/views/form/editors/ImageViewerEditorComponent.tsx index 759073c..d321faf 100644 --- a/ui/src/views/form/editors/ImageViewerEditorComponent.tsx +++ b/ui/src/views/form/editors/ImageViewerEditorComponent.tsx @@ -145,7 +145,7 @@ const ImageViewerEditorComponent = ({ }} onError={({ currentTarget }) => { currentTarget.onerror = null - currentTarget.src = '/img/others/default-profile.png' + currentTarget.src = '/img/others/no-image.png' }} /> diff --git a/ui/src/views/list/editors/ImageViewerEditorComponent.tsx b/ui/src/views/list/editors/ImageViewerEditorComponent.tsx index 7b60b96..85bbdef 100644 --- a/ui/src/views/list/editors/ImageViewerEditorComponent.tsx +++ b/ui/src/views/list/editors/ImageViewerEditorComponent.tsx @@ -189,7 +189,7 @@ const ImageViewerEditorComponent = (templateData: any): ReactElement => { }} onError={({ currentTarget }) => { currentTarget.onerror = null - currentTarget.src = '/img/others/default-profile.png' + currentTarget.src = '/img/others/no-image.png' }} /> diff --git a/ui/src/views/list/useListFormColumns.ts b/ui/src/views/list/useListFormColumns.ts index 0acdede..52be303 100644 --- a/ui/src/views/list/useListFormColumns.ts +++ b/ui/src/views/list/useListFormColumns.ts @@ -20,7 +20,7 @@ import { } from '@/proxy/form/models' import { addCss } from './Utils' -const DEFAULT_PROFILE_IMAGE = '/img/others/default-profile.png' +const NO_IMAGE = '/img/others/no-image.png' const cellTemplateMultiValue = ( cellElement: HTMLElement, @@ -92,7 +92,7 @@ function getImgPreview(): HTMLDivElement { ].join(';') const img = document.createElement('img') img.onerror = null - img.src = DEFAULT_PROFILE_IMAGE + img.src = NO_IMAGE img.style.cssText = 'display:block;max-width:312px;max-height:312px;object-fit:contain;border-radius:4px;' el.appendChild(img) @@ -107,7 +107,7 @@ function showImgPreview(src: string, e: MouseEvent) { const imgEl = el.querySelector('img') as HTMLImageElement imgEl.onerror = () => { imgEl.onerror = null - imgEl.src = DEFAULT_PROFILE_IMAGE + imgEl.src = NO_IMAGE } if (imgEl.src !== src) imgEl.src = src @@ -158,7 +158,7 @@ const cellTemplateImage = ( const img = document.createElement('img') img.onerror = () => { img.onerror = null - img.src = DEFAULT_PROFILE_IMAGE + img.src = NO_IMAGE } img.src = url img.alt = ''