diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Identity/Dto/UserInfoViewModel.cs b/api/src/Sozsoft.Platform.Application.Contracts/Identity/Dto/UserInfoViewModel.cs index 6386aa6..7ea947e 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Identity/Dto/UserInfoViewModel.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Identity/Dto/UserInfoViewModel.cs @@ -34,6 +34,7 @@ public class UserInfoViewModel : ExtensibleObject public bool PhoneNumberConfirmed { get; set; } public int AccessFailedCount { get; set; } public bool ShouldChangePasswordOnNextLogin { get; set; } + public string Avatar { get; set; } public string RocketUsername { get; set; } public DateTimeOffset? CreationTime { get; set; } public DateTimeOffset? LastModificationTime { get; set; } diff --git a/api/src/Sozsoft.Platform.Application/Identity/AvatarProvider.cs b/api/src/Sozsoft.Platform.Application/Identity/AvatarProvider.cs new file mode 100644 index 0000000..cb92f00 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application/Identity/AvatarProvider.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Configuration; + +namespace Sozsoft.Platform.Identity; + +public static class AvatarProvider +{ + public static string GetAvatar( + IConfiguration configuration, + string? tenantId, + string? userId) + { + var tenantPart = !string.IsNullOrEmpty(tenantId) + ? $"tenants/{tenantId}" + : "host"; + + var baseUrl = configuration["App:CdnUrl"]?.TrimEnd('/'); + + return $"{baseUrl}/{tenantPart}/avatar/{userId ?? "default"}.jpg"; + } +} diff --git a/api/src/Sozsoft.Platform.Application/Identity/IdentityMappers.cs b/api/src/Sozsoft.Platform.Application/Identity/IdentityMappers.cs index a17eea4..90b218d 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/IdentityMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/IdentityMappers.cs @@ -18,6 +18,7 @@ public partial class IdentityUserToUserInfoViewModelMapper : MapperBase departmentRepository { get; } public IRepository jobPositionRepository { get; } public BlobManager BlobCdnManager { get; } + private readonly IConfiguration configuration; public PlatformIdentityAppService( IIdentityUserAppService identityUserAppService, @@ -53,6 +55,7 @@ public class PlatformIdentityAppService : ApplicationService IRepository departmentRepository, IRepository jobPositionRepository, BlobManager blobCdnManager, + IConfiguration configuration, IGuidGenerator guidGenerator ) { @@ -66,6 +69,7 @@ public class PlatformIdentityAppService : ApplicationService this.departmentRepository = departmentRepository; this.jobPositionRepository = jobPositionRepository; this.BlobCdnManager = blobCdnManager; + this.configuration = configuration; this.permissionRepository = permissionRepository; this.branchRepository = branchRepository; this.branchUsersRepository = branchUsersRepository; @@ -280,6 +284,7 @@ public class PlatformIdentityAppService : ApplicationService user.SetEducationLevel(UserInfo.EducationLevel); user.SetGraduationSchool(UserInfo.GraduationSchool); user.SetBloodType(UserInfo.BloodType); + user.SetAvatar(UserInfo.Avatar); await UserManager.UpdateAsync(user); } @@ -293,11 +298,15 @@ public class PlatformIdentityAppService : ApplicationService if (input.Avatar is null || input.Avatar.ContentLength == 0) { await BlobCdnManager.DeleteAsync(BlobContainerNames.Avatar, fileName); + user.SetAvatar(null); } else { await BlobCdnManager.SaveAsync(BlobContainerNames.Avatar, fileName, input.Avatar.GetStream()); + user.SetAvatar(AvatarProvider.GetAvatar(configuration, user.TenantId?.ToString(), user.Id.ToString())); } + + await UserManager.UpdateAsync(user); } public async Task> GetPermissionList() diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs index f28baf6..4844c14 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs @@ -4,7 +4,9 @@ using Sozsoft.Platform.BlobStoring; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; +using Sozsoft.Platform.Extensions; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.DependencyInjection; @@ -21,14 +23,17 @@ namespace Sozsoft.Platform.Identity; public class PlatformProfileAppService : ProfileAppService, IProfileAppService { public BlobManager _blobCdnManager { get; } + private readonly IConfiguration _configuration; public PlatformProfileAppService( IdentityUserManager userManager, IOptions identityOptions, - BlobManager blobCdnManager + BlobManager blobCdnManager, + IConfiguration configuration ) : base(userManager, identityOptions) { _blobCdnManager = blobCdnManager; + _configuration = configuration; } [RemoteService(false)] @@ -63,10 +68,12 @@ public class PlatformProfileAppService : ProfileAppService, IProfileAppService if (input.Avatar is null || input.Avatar.ContentLength == 0) { await _blobCdnManager.DeleteAsync(BlobContainerNames.Avatar, fileName); + user.SetAvatar(null); } else { await _blobCdnManager.SaveAsync(BlobContainerNames.Avatar, fileName, input.Avatar.GetStream()); + user.SetAvatar(AvatarProvider.GetAvatar(_configuration, user.TenantId?.ToString(), user.Id.ToString())); } user.Name = input.Name; diff --git a/api/src/Sozsoft.Platform.Application/Identity/UserInfoViewModelMappingExtensions.cs b/api/src/Sozsoft.Platform.Application/Identity/UserInfoViewModelMappingExtensions.cs index f73b750..bfca22a 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/UserInfoViewModelMappingExtensions.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/UserInfoViewModelMappingExtensions.cs @@ -13,6 +13,7 @@ public static class UserInfoViewModelMappingExtensions { userInfoViewModel.IsVerified = user.GetIsVerified(); userInfoViewModel.LoginEndDate = user.GetLoginEndDate(); + userInfoViewModel.Avatar = user.GetAvatar(); userInfoViewModel.RocketUsername = user.GetRocketUsername(); userInfoViewModel.WorkHour = user.GetWorkHour(); userInfoViewModel.DepartmentId = user.GetDepartmentId(); @@ -79,4 +80,4 @@ public static class UserInfoViewModelMappingExtensions return userInfoViewModel; } -} \ No newline at end of file +} diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 7a29a3c..b07e93f 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -4122,6 +4122,12 @@ "en": "Camera Off", "tr": "Kamera Kapalı" }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.Avatar", + "en": "Avatar", + "tr": "Avatar" + }, { "resourceName": "Platform", "key": "App.Listform.ListformField.RoomName", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs index 82c864a..fc16f8e 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs @@ -809,15 +809,16 @@ 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="Email", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order=2, DataField="Name", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order=3, DataField="Surname", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order=4, DataField="PhoneNumber", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order=5, DataField="WorkHour", ColSpan=1, EditorType2=EditorTypes.dxSelectBox, EditorOptions = EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order=6, DataField="DepartmentId", ColSpan=1, EditorType2=EditorTypes.dxSelectBox, EditorOptions = EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order=7, DataField="JobPositionId", ColSpan=1, EditorType2=EditorTypes.dxSelectBox, EditorOptions = EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order=8, DataField="Password", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order=9, DataField="IsActive", ColSpan=1, EditorType2=EditorTypes.dxCheckBox }, + new EditingFormItemDto { Order=1, DataField="Avatar", ColSpan=1, EditorType2=EditorTypes.dxImageViewer }, + 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 }, + new EditingFormItemDto { Order=5, DataField="PhoneNumber", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, + new EditingFormItemDto { Order=6, DataField="WorkHour", ColSpan=1, EditorType2=EditorTypes.dxSelectBox, EditorOptions = EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order=7, DataField="DepartmentId", ColSpan=1, EditorType2=EditorTypes.dxSelectBox, EditorOptions = EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order=8, DataField="JobPositionId", ColSpan=1, EditorType2=EditorTypes.dxSelectBox, EditorOptions = EditorOptionValues.ShowClearButton }, + new EditingFormItemDto { Order=9, DataField="Password", ColSpan=1, EditorType2=EditorTypes.dxTextBox }, + new EditingFormItemDto { Order=10, DataField="IsActive", ColSpan=1, EditorType2=EditorTypes.dxCheckBox }, ]} }), InsertServiceAddress = "list-form-dynamic-api/user-insert", @@ -906,7 +907,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep FieldName = "Id", CaptionName = "App.Listform.ListformField.Id", Width = 0, - ListOrderNo = 1, + ListOrderNo = 0, Visible = false, IsActive = true, @@ -915,6 +916,25 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep PivotSettingsJson = DefaultPivotSettingsJson }, new ListFormField + { + ListFormCode = listForm.ListFormCode, + RoleId = null, + UserId = null, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "Avatar", + CaptionName = "App.Listform.ListformField.Avatar", + Width = 0, + ListOrderNo = 1, + Visible = true, + IsActive = true, + + AllowSearch = true, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(PlatformConsts.IdentityPermissions.Users.Create, PlatformConsts.IdentityPermissions.Users.Default, PlatformConsts.IdentityPermissions.Users.Update, true, true, false), + PivotSettingsJson = DefaultPivotSettingsJson + }, + new ListFormField { ListFormCode = listForm.ListFormCode, RoleId = null, @@ -1005,7 +1025,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep FieldName = "WorkHour", CaptionName = "App.Listform.ListformField.WorkHour", Width = 0, - ListOrderNo = 6, + ListOrderNo = 7, LookupJson = LookupQueryValues.DefaultLookupQueryJson(nameof(TableNameEnum.WorkHour), "Name", "Name"), Visible = true, IsActive = true, @@ -1027,7 +1047,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep FieldName = "DepartmentId", CaptionName = "App.Listform.ListformField.DepartmentId", Width = 0, - ListOrderNo = 7, + ListOrderNo = 8, LookupJson = JsonSerializer.Serialize(new LookupDto { DataSourceType = UiLookupDataSourceTypeEnum.Query, DisplayExpr = "Name", @@ -1055,7 +1075,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep FieldName = "JobPositionId", CaptionName = "App.Listform.ListformField.JobPositionId", Width = 0, - ListOrderNo = 8, + ListOrderNo = 9, LookupJson = JsonSerializer.Serialize(new LookupDto { DataSourceType = UiLookupDataSourceTypeEnum.Query, DisplayExpr = "Name", @@ -1085,7 +1105,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep FieldName = "IsActive", CaptionName = "App.Listform.ListformField.IsActive", Width = 0, - ListOrderNo = 9, + ListOrderNo = 10, Visible = true, IsActive = true, @@ -1103,7 +1123,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep FieldName = "IsVerified", CaptionName = "App.Listform.ListformField.IsVerified", Width = 0, - ListOrderNo = 10, + ListOrderNo = 11, Visible = true, IsActive = true, @@ -1121,7 +1141,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep FieldName = "EmailConfirmed", CaptionName = "App.Listform.ListformField.EmailConfirmed", Width = 0, - ListOrderNo = 11, + ListOrderNo = 12, Visible = true, IsActive = true, @@ -1139,7 +1159,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep FieldName = "TwoFactorEnabled", CaptionName = "App.Listform.ListformField.TwoFactorEnabled", Width = 0, - ListOrderNo = 12, + ListOrderNo = 13, Visible = true, IsActive = true, @@ -1157,7 +1177,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep FieldName = "Password", CaptionName = "App.Listform.ListformField.Password", Width = 0, - ListOrderNo = 13, + ListOrderNo = 14, Visible = false, IsActive = false, diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PlatformIdentityDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PlatformIdentityDataSeeder.cs index 036dbb1..5bcc11f 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PlatformIdentityDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PlatformIdentityDataSeeder.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Sozsoft.Platform.Extensions; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.DependencyInjection; @@ -24,6 +25,7 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder private readonly ITenantRepository _tenantRepository; private readonly IRepository _branchRepository; private readonly IRepository _branchUsersRepository; + private readonly IConfiguration _configuration; public PlatformIdentityDataSeeder( IGuidGenerator guidGenerator, @@ -37,6 +39,7 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder IPermissionGrantRepository permissionGrantRepository, ICurrentTenant currentTenant, ITenantRepository tenantRepository, + IConfiguration configuration, IOptions identityOptions ) : base(guidGenerator, roleRepository, userRepository, lookupNormalizer, userManager, roleManager, currentTenant, identityOptions) { @@ -44,6 +47,7 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder this._tenantRepository = tenantRepository; this._branchRepository = branchRepository; this._branchUsersRepository = branchUsersRepository; + this._configuration = configuration; } public override async Task SeedAsync(string adminEmail, string adminPassword, Guid? tenantId = null, string adminUserName = null) @@ -142,6 +146,7 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder adminUser.SetWorkHour(PlatformConsts.AbpIdentity.User.AdminWorkHourDefaultValue); adminUser.SetNationality(PlatformConsts.AbpIdentity.User.AdminNationalityDefaultValue); adminUser.SetBloodType(PlatformConsts.AbpIdentity.User.AdminBloodTypeDefaultValue); + // adminUser.SetAvatar(GetAvatar(tenantId?.ToString(), adminUser.Id.ToString())); adminUser.SetEducationLevel(PlatformConsts.AbpIdentity.User.AdminEducationLevelDefaultValue); adminUser.SetGraduationSchool(PlatformConsts.AbpIdentity.User.AdminGraduationSchoolDefaultValue); adminUser.SetHomeAddress(PlatformConsts.AbpIdentity.User.AdminHomeAddressDefaultValue); @@ -195,5 +200,16 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder return result; } } + + // private string GetAvatar(string? tenantId, string? userId) + // { + // var tenantPart = !string.IsNullOrEmpty(tenantId) + // ? $"tenants/{tenantId}" + // : "host"; + + // var baseUrl = _configuration["App:CdnUrl"]?.TrimEnd('/'); + + // return $"{baseUrl}/{tenantPart}/avatar/{userId ?? "default"}.jpg"; + // } } diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index 1b6fdd9..0da7027 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -120,6 +120,7 @@ public static class PlatformConsts public const string LoginEndDate = "LoginEndDate"; public const string RoleNames = "RoleNames"; public const string IsVerified = "IsVerified"; + public const string Avatar = "Avatar"; public const string RocketUsername = "RocketUsername"; public const string HomeAddress = "HomeAddress"; public const string EducationLevel = "EducationLevel"; diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs index f941e4a..606af5d 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs @@ -69,6 +69,15 @@ public static class PlatformModuleExtensionConfigurator property.Configuration[IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit] = false; }); + user.AddOrUpdateProperty( + PlatformConsts.AbpIdentity.User.Avatar, + property => + { + property.DisplayName = new LocalizableString(typeof(PlatformResource), PlatformConsts.AbpIdentity.User.Avatar); + property.IsAvailableToClients = true; + property.Configuration[IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit] = false; + }); + user.AddOrUpdateProperty( PlatformConsts.AbpIdentity.User.RocketUsername, property => diff --git a/api/src/Sozsoft.Platform.Domain/Extensions/AbpIdentityUserExtensions.cs b/api/src/Sozsoft.Platform.Domain/Extensions/AbpIdentityUserExtensions.cs index 097522e..6e9326c 100644 --- a/api/src/Sozsoft.Platform.Domain/Extensions/AbpIdentityUserExtensions.cs +++ b/api/src/Sozsoft.Platform.Domain/Extensions/AbpIdentityUserExtensions.cs @@ -31,6 +31,16 @@ public static class AbpIdentityUserExtensions return user.GetProperty(PlatformConsts.AbpIdentity.User.LoginEndDate); } + //Avatar + public static void SetAvatar(this IdentityUser user, string value) + { + user.SetProperty(PlatformConsts.AbpIdentity.User.Avatar, value); + } + public static string GetAvatar(this IdentityUser user) + { + return user.GetProperty(PlatformConsts.AbpIdentity.User.Avatar); + } + //Rocket Username public static void SetRocketUsername(this IdentityUser user, string rocketUsername) { diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs index 8ffe540..13d4ec8 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs @@ -37,6 +37,15 @@ public static class PlatformEfCoreEntityExtensionMappings } ); + ObjectExtensionManager.Instance + .MapEfCoreProperty( + PlatformConsts.AbpIdentity.User.Avatar, + (entityBuilder, propertyBuilder) => + { + propertyBuilder.HasMaxLength(512).HasDefaultValue(null); + } + ); + ObjectExtensionManager.Instance .MapEfCoreProperty( PlatformConsts.AbpIdentity.User.RocketUsername, diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260606212623_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260610203148_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260606212623_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260610203148_Initial.Designer.cs index 0b60a87..e0885ec 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260606212623_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260610203148_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260606212623_Initial")] + [Migration("20260610203148_Initial")] partial class Initial { /// @@ -6870,6 +6870,10 @@ namespace Sozsoft.Platform.Migrations .HasDefaultValue(0) .HasColumnName("AccessFailedCount"); + b.Property("Avatar") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)"); + b.Property("BirthDate") .HasColumnType("datetime2"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260606212623_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260610203148_Initial.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260606212623_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260610203148_Initial.cs index d304248..8f6bec1 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260606212623_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260610203148_Initial.cs @@ -435,6 +435,7 @@ namespace Sozsoft.Platform.Migrations ShouldChangePasswordOnNextLogin = table.Column(type: "bit", nullable: false), EntityVersion = table.Column(type: "int", nullable: false), LastPasswordChangeTime = table.Column(type: "datetimeoffset", nullable: true), + Avatar = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: true), BirthDate = table.Column(type: "datetime2", nullable: true), BirthPlace = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), BloodType = table.Column(type: "nvarchar(16)", maxLength: 16, nullable: true), diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 2500ecf..aa0f849 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -6867,6 +6867,10 @@ namespace Sozsoft.Platform.Migrations .HasDefaultValue(0) .HasColumnName("AccessFailedCount"); + b.Property("Avatar") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)"); + b.Property("BirthDate") .HasColumnType("datetime2"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantIdentityDataSeeder.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantIdentityDataSeeder.cs index db7bbd3..1b16e6d 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantIdentityDataSeeder.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantIdentityDataSeeder.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Sozsoft.Platform.Extensions; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.DependencyInjection; @@ -24,6 +25,7 @@ public class TenantIdentityDataSeeder : IdentityDataSeeder private readonly IRepository _branchUsersRepository; private readonly IPermissionGrantRepository _permissionGrantRepository; private readonly ITenantRepository _tenantRepository; + private readonly IConfiguration _configuration; public TenantIdentityDataSeeder( IGuidGenerator guidGenerator, @@ -37,6 +39,7 @@ public class TenantIdentityDataSeeder : IdentityDataSeeder IPermissionGrantRepository permissionGrantRepository, ICurrentTenant currentTenant, ITenantRepository tenantRepository, + IConfiguration configuration, IOptions identityOptions ) : base(guidGenerator, roleRepository, userRepository, lookupNormalizer, userManager, roleManager, currentTenant, identityOptions) { @@ -44,6 +47,7 @@ public class TenantIdentityDataSeeder : IdentityDataSeeder this._branchUsersRepository = branchUsersRepository; this._permissionGrantRepository = permissionGrantRepository; this._tenantRepository = tenantRepository; + this._configuration = configuration; } public override async Task SeedAsync(string adminEmail, string adminPassword, Guid? tenantId = null, string adminUserName = null) @@ -140,6 +144,7 @@ public class TenantIdentityDataSeeder : IdentityDataSeeder adminUser.SetWorkHour(PlatformConsts.AbpIdentity.User.AdminWorkHourDefaultValue); adminUser.SetNationality(PlatformConsts.AbpIdentity.User.AdminNationalityDefaultValue); adminUser.SetBloodType(PlatformConsts.AbpIdentity.User.AdminBloodTypeDefaultValue); + // adminUser.SetAvatar(GetAvatar(tenantId?.ToString(), adminUser.Id.ToString())); adminUser.SetEducationLevel(PlatformConsts.AbpIdentity.User.AdminEducationLevelDefaultValue); adminUser.SetGraduationSchool(PlatformConsts.AbpIdentity.User.AdminGraduationSchoolDefaultValue); adminUser.SetHomeAddress(PlatformConsts.AbpIdentity.User.AdminHomeAddressDefaultValue); @@ -178,5 +183,16 @@ public class TenantIdentityDataSeeder : IdentityDataSeeder return result; } } + + // private string GetAvatar(string? tenantId, string? userId) + // { + // var tenantPart = !string.IsNullOrEmpty(tenantId) + // ? $"tenants/{tenantId}" + // : "host"; + + // var baseUrl = _configuration["App:CdnUrl"]?.TrimEnd('/'); + + // return $"{baseUrl}/{tenantPart}/avatar/{userId ?? "default"}.jpg"; + // } } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Dev.json b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Dev.json index b9be2db..8f71de3 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Dev.json +++ b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Dev.json @@ -5,6 +5,7 @@ "CorsOrigins": "https://dev.sozsoft.com", "RedirectAllowedUrls": "https://dev.sozsoft.com,https://dev.sozsoft.com/authentication/callback", "AttachmentsPath": "/etc/api/mail-queue/attachments", + "CdnUrl": "https://dev-cdn.sozsoft.com", "CdnPath": "/etc/api/cdn", "BaseDomain": "sozsoft.com", "BackupPath": "/var/opt/mssql/backup" diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json index 097eb3e..cf5e516 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json +++ b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json @@ -5,6 +5,7 @@ "CorsOrigins": "https://sozsoft.com", "RedirectAllowedUrls": "https://sozsoft.com,https://sozsoft.com/authentication/callback", "AttachmentsPath": "/etc/api/mail-queue/attachments", + "CdnUrl": "https://cdn.sozsoft.com", "CdnPath": "/etc/api/cdn", "BaseDomain": "sozsoft.com", "BackupPath": "/var/opt/mssql/backup" @@ -34,4 +35,4 @@ "Default": "Information" } } -} +} \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.json b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.json index ed04d28..8ac190d 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.json +++ b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.json @@ -5,6 +5,7 @@ "CorsOrigins": "http://localhost,http://localhost:3000,http://localhost:4200", "RedirectAllowedUrls": "http://localhost:4200,http://localhost:4200/authentication/callback", "AttachmentsPath": "C:\\Private\\Projects\\sozsoft-platform\\configs\\mail-queue\\attachments", + "CdnUrl": "https://localhost:44344", "CdnPath": "C:\\Private\\Projects\\sozsoft-platform\\configs\\docker\\cdn", "Version": "1.0.5", "BackupPath": "/var/opt/mssql/backup" diff --git a/ui/public/version.json b/ui/public/version.json index b4a345a..111c927 100644 --- a/ui/public/version.json +++ b/ui/public/version.json @@ -1,5 +1,5 @@ { - "commit": "c1e7f8c", + "commit": "6098758", "releases": [ { "version": "1.1.04", diff --git a/ui/src/views/form/editors/ImageViewerEditorComponent.tsx b/ui/src/views/form/editors/ImageViewerEditorComponent.tsx index f21f41f..759073c 100644 --- a/ui/src/views/form/editors/ImageViewerEditorComponent.tsx +++ b/ui/src/views/form/editors/ImageViewerEditorComponent.tsx @@ -141,9 +141,12 @@ const ImageViewerEditorComponent = ({ height: thumbH, objectFit: 'cover', borderRadius: 4, - border: '1px solid #ddd', display: 'block', }} + onError={({ currentTarget }) => { + currentTarget.onerror = null + currentTarget.src = '/img/others/default-profile.png' + }} /> ))} diff --git a/ui/src/views/list/editors/ImageViewerEditorComponent.tsx b/ui/src/views/list/editors/ImageViewerEditorComponent.tsx index d0f76cf..7b60b96 100644 --- a/ui/src/views/list/editors/ImageViewerEditorComponent.tsx +++ b/ui/src/views/list/editors/ImageViewerEditorComponent.tsx @@ -185,9 +185,12 @@ const ImageViewerEditorComponent = (templateData: any): ReactElement => { height: thumbH, objectFit: 'cover', borderRadius: 4, - border: '1px solid #ddd', display: 'block', }} + onError={({ currentTarget }) => { + currentTarget.onerror = null + currentTarget.src = '/img/others/default-profile.png' + }} /> ))} diff --git a/ui/src/views/list/useListFormColumns.ts b/ui/src/views/list/useListFormColumns.ts index 4286a0b..0acdede 100644 --- a/ui/src/views/list/useListFormColumns.ts +++ b/ui/src/views/list/useListFormColumns.ts @@ -20,6 +20,8 @@ import { } from '@/proxy/form/models' import { addCss } from './Utils' +const DEFAULT_PROFILE_IMAGE = '/img/others/default-profile.png' + const cellTemplateMultiValue = ( cellElement: HTMLElement, cellInfo: DataGridTypes.ColumnCellTemplateData, @@ -89,6 +91,8 @@ function getImgPreview(): HTMLDivElement { 'opacity:0', ].join(';') const img = document.createElement('img') + img.onerror = null + img.src = DEFAULT_PROFILE_IMAGE img.style.cssText = 'display:block;max-width:312px;max-height:312px;object-fit:contain;border-radius:4px;' el.appendChild(img) @@ -101,6 +105,10 @@ function getImgPreview(): HTMLDivElement { function showImgPreview(src: string, e: MouseEvent) { const el = getImgPreview() const imgEl = el.querySelector('img') as HTMLImageElement + imgEl.onerror = () => { + imgEl.onerror = null + imgEl.src = DEFAULT_PROFILE_IMAGE + } if (imgEl.src !== src) imgEl.src = src const GAP = 12 @@ -148,9 +156,13 @@ const cellTemplateImage = ( urls.forEach((url) => { const img = document.createElement('img') + img.onerror = () => { + img.onerror = null + img.src = DEFAULT_PROFILE_IMAGE + } img.src = url img.alt = '' - img.style.cssText = `width:${w}px;height:${h}px;object-fit:cover;border-radius:4px;border:1px solid #ddd;margin:2px;vertical-align:middle;display:inline-block;cursor:zoom-in;` + img.style.cssText = `width:${w}px;height:${h}px;object-fit:cover;border-radius:4px;margin:2px;vertical-align:middle;display:inline-block;cursor:zoom-in;` img.addEventListener('mouseenter', (e) => showImgPreview(url, e as MouseEvent)) img.addEventListener('mousemove', (e) => showImgPreview(url, e as MouseEvent))