diff --git a/api/common.props b/api/common.props index 94a2ab4..d7a4d6d 100644 --- a/api/common.props +++ b/api/common.props @@ -19,7 +19,6 @@ - diff --git a/api/modules/Sozsoft.Languages/common.props b/api/modules/Sozsoft.Languages/common.props index 021ac2f..a7688d9 100644 --- a/api/modules/Sozsoft.Languages/common.props +++ b/api/modules/Sozsoft.Languages/common.props @@ -14,7 +14,6 @@ - diff --git a/api/modules/Sozsoft.MailQueue/Domain/MailTracking/MailTrackingManager.cs b/api/modules/Sozsoft.MailQueue/Domain/MailTracking/MailTrackingManager.cs index d061ebe..0163892 100644 --- a/api/modules/Sozsoft.MailQueue/Domain/MailTracking/MailTrackingManager.cs +++ b/api/modules/Sozsoft.MailQueue/Domain/MailTracking/MailTrackingManager.cs @@ -31,6 +31,8 @@ public class MailTrackingManager : DomainService /// public async Task StartAsync() { + // https://us-east-1.console.aws.amazon.com/iam/home?region=eu-central-1#/users + // https://eu-central-1.console.aws.amazon.com/ses/home?region=eu-central-1#/identities/system%40sozsoft.com?tabId=authentication var accessKey = configuration.GetValue(AmazonSesEmailSettingNames.AccessKey); var accessKeyId = configuration.GetValue(AmazonSesEmailSettingNames.AccessKeyId); var region = configuration.GetValue(AmazonSesEmailSettingNames.Region); diff --git a/api/modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj b/api/modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj index 76b5dbc..4e9c0ad 100644 --- a/api/modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj +++ b/api/modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj @@ -30,7 +30,6 @@ - diff --git a/api/modules/Sozsoft.Notifications/common.props b/api/modules/Sozsoft.Notifications/common.props index 021ac2f..a7688d9 100644 --- a/api/modules/Sozsoft.Notifications/common.props +++ b/api/modules/Sozsoft.Notifications/common.props @@ -14,7 +14,6 @@ - diff --git a/api/modules/Sozsoft.Sender/Mail/AmazonSes/AmazonSesEmailSender.cs b/api/modules/Sozsoft.Sender/Mail/AmazonSes/AmazonSesEmailSender.cs index d8fc18e..267d837 100644 --- a/api/modules/Sozsoft.Sender/Mail/AmazonSes/AmazonSesEmailSender.cs +++ b/api/modules/Sozsoft.Sender/Mail/AmazonSes/AmazonSesEmailSender.cs @@ -65,6 +65,7 @@ public class AmazonSesEmailSender : EmailSenderBase, ISozsoftEmailSender, ITrans await BackgroundJobManager.EnqueueAsync( new ErpBackgroundEmailSendingJobArgs { + TenantId = CurrentTenant.Id, To = to, Sender = sender, Params = @params, diff --git a/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJob.cs b/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJob.cs index 19ca782..9bd313c 100644 --- a/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJob.cs +++ b/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJob.cs @@ -1,5 +1,6 @@ -using Volo.Abp.BackgroundJobs; +using Volo.Abp.BackgroundJobs; using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; namespace Sozsoft.Sender.Mail; @@ -7,22 +8,28 @@ public class ErpBackgroundEmailSendingJob : AsyncBackgroundJob, ITransientDependency { protected ISozsoftEmailSender EmailSender { get; } + protected ICurrentTenant CurrentTenant { get; } - public ErpBackgroundEmailSendingJob(ISozsoftEmailSender emailSender) + public ErpBackgroundEmailSendingJob( + ISozsoftEmailSender emailSender, + ICurrentTenant currentTenant) { EmailSender = emailSender; + CurrentTenant = currentTenant; } public override async Task ExecuteAsync(ErpBackgroundEmailSendingJobArgs args) { - //await EmailSender.SendEmailAsync(args.Template, args.To, args.Params, args.Subject); - await EmailSender.SendEmailAsync( - args.To, - args.Sender, - args.Params, - args.TextContent, - args.Subject, - args.Attachments); + using (CurrentTenant.Change(args.TenantId)) + { + //await EmailSender.SendEmailAsync(args.Template, args.To, args.Params, args.Subject); + await EmailSender.SendEmailAsync( + args.To, + args.Sender, + args.Params, + args.TextContent, + args.Subject, + args.Attachments); + } } } - diff --git a/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJobArgs.cs b/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJobArgs.cs index 31626f1..c4a128e 100644 --- a/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJobArgs.cs +++ b/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJobArgs.cs @@ -1,8 +1,12 @@ -namespace Sozsoft.Sender.Mail; +using Volo.Abp.MultiTenancy; + +namespace Sozsoft.Sender.Mail; [Serializable] -public class ErpBackgroundEmailSendingJobArgs +public class ErpBackgroundEmailSendingJobArgs : IMultiTenant { + public Guid? TenantId { get; set; } + public string[] To { get; set; } public KeyValuePair? Sender { get; set; } @@ -14,6 +18,4 @@ public class ErpBackgroundEmailSendingJobArgs public Dictionary? Attachments { get; set; } public string? TextContent { get; set; } - } - diff --git a/api/modules/Sozsoft.Sender/Sozsoft.Sender.csproj b/api/modules/Sozsoft.Sender/Sozsoft.Sender.csproj index 151de32..7abf1ed 100644 --- a/api/modules/Sozsoft.Sender/Sozsoft.Sender.csproj +++ b/api/modules/Sozsoft.Sender/Sozsoft.Sender.csproj @@ -14,7 +14,6 @@ - diff --git a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs index 1e8d4cc..0a9c75f 100644 --- a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs +++ b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs @@ -5,10 +5,9 @@ using System.Threading.Tasks; using Sozsoft.Languages; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; using Volo.Abp.SettingManagement; using Volo.Abp.Settings; -using SettingDefinition = Sozsoft.Settings.Entities.SettingDefinition; namespace Sozsoft.Settings; @@ -20,17 +19,20 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService private readonly ISettingDefinitionManager settingDefinitionManager; private readonly ISettingManager settingManager; private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager; + private readonly ICurrentTenant currentTenant; public SettingUiAppService( ILanguageKeyIntegrationService languageKeyIntegrationService, ISettingDefinitionManager settingDefinitionManager, ISettingManager settingManager, - ErpSettingDefinitionManager ErpSettingDefinitionManager) + ErpSettingDefinitionManager ErpSettingDefinitionManager, + ICurrentTenant currentTenant) { this.languageKeyIntegrationService = languageKeyIntegrationService; this.settingDefinitionManager = settingDefinitionManager; this.settingManager = settingManager; this.ErpSettingDefinitionManager = ErpSettingDefinitionManager; + this.currentTenant = currentTenant; } public virtual async Task> GetListAsync() @@ -95,15 +97,18 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService { if (setting.Providers.IsNullOrEmpty()) { - await settingManager.SetForCurrentUserAsync(setting.Name, value); + if (currentTenant.Id.HasValue) + { + await settingManager.SetForCurrentTenantAsync(setting.Name, value); + } + else + { + await settingManager.SetGlobalAsync(setting.Name, value); + } } else { - if (setting.Providers.Any(p => p == UserSettingValueProvider.ProviderName)) - { - await settingManager.SetForCurrentUserAsync(setting.Name, value); - } - else if (setting.Providers.Any(p => p == TenantSettingValueProvider.ProviderName)) + if (setting.Providers.Any(p => p == TenantSettingValueProvider.ProviderName) && currentTenant.Id.HasValue) { await settingManager.SetForCurrentTenantAsync(setting.Name, value); } @@ -111,6 +116,10 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService { await settingManager.SetGlobalAsync(setting.Name, value); } + else if (setting.Providers.Any(p => p == UserSettingValueProvider.ProviderName)) + { + await settingManager.SetForCurrentUserAsync(setting.Name, value); + } } } diff --git a/api/modules/Sozsoft.Settings/common.props b/api/modules/Sozsoft.Settings/common.props index 021ac2f..a7688d9 100644 --- a/api/modules/Sozsoft.Settings/common.props +++ b/api/modules/Sozsoft.Settings/common.props @@ -14,7 +14,6 @@ - diff --git a/api/modules/Sozsoft.SqlQueryManager/common.props b/api/modules/Sozsoft.SqlQueryManager/common.props index 021ac2f..a7688d9 100644 --- a/api/modules/Sozsoft.SqlQueryManager/common.props +++ b/api/modules/Sozsoft.SqlQueryManager/common.props @@ -14,7 +14,6 @@ - diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json index 49c2975..5d9f4fc 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json @@ -258,7 +258,7 @@ "code": "App.SiteManagement.General.NewMemberNotificationEmails", "nameKey": "App.SiteManagement.General.NewMemberNotificationEmails", "descriptionKey": "App.SiteManagement.General.NewMemberNotificationEmails.Description", - "defaultValue": "SYSTEM@SOZSOFT.COM", + "defaultValue": "system@sozsoft.com", "isVisibleToClients": false, "providers": "G|D", "isInherited": false, @@ -274,7 +274,7 @@ "code": "App.SiteManagement.General.TimedLoginEmails", "nameKey": "App.SiteManagement.General.TimedLoginEmails", "descriptionKey": "App.SiteManagement.General.TimedLoginEmails.Description", - "defaultValue": "SYSTEM@SOZSOFT.COM", + "defaultValue": "system@sozsoft.com", "isVisibleToClients": false, "providers": "G|D", "isInherited": false, @@ -466,7 +466,7 @@ "code": "Abp.Mailing.DefaultFromAddress", "nameKey": "Abp.Mailing.DefaultFromAddress", "descriptionKey": "Abp.Mailing.DefaultFromAddress.Description", - "defaultValue": "SYSTEM@SOZSOFT.COM", + "defaultValue": "system@sozsoft.com", "isVisibleToClients": false, "providers": "T|G|D", "isInherited": false, @@ -482,7 +482,7 @@ "code": "Abp.Mailing.Smtp.UserName", "nameKey": "Abp.Mailing.Smtp.UserName", "descriptionKey": "Abp.Mailing.Smtp.UserName.Description", - "defaultValue": "SYSTEM@SOZSOFT.COM", + "defaultValue": "system@sozsoft.com", "isVisibleToClients": false, "providers": "T|G|D", "isInherited": false, @@ -610,7 +610,7 @@ "code": "Abp.Mailing.AWS.AccessKey", "nameKey": "Abp.Mailing.AWS.AccessKey", "descriptionKey": "Abp.Mailing.AWS.AccessKey.Description", - "defaultValue": "aXW8L21rP6dPO6Txj76Be2FCpWRBa25EMrSAVL76", + "defaultValue": "SibFBAMiSApvz+NChYmlgZmx25JNbximemIDOFps", "isVisibleToClients": false, "providers": "T|G|D", "isInherited": false, @@ -626,7 +626,7 @@ "code": "Abp.Mailing.AWS.AccessKeyId", "nameKey": "Abp.Mailing.AWS.AccessKeyId", "descriptionKey": "Abp.Mailing.AWS.AccessKeyId.Description", - "defaultValue": "AKIATULUYBLX4IY3S2P1", + "defaultValue": "AKIA5OCSDJB5KOQY74NV", "isVisibleToClients": false, "providers": "T|G|D", "isInherited": false, @@ -722,7 +722,7 @@ "code": "Abp.Account.Captcha.SiteKey", "nameKey": "Abp.Account.Captcha.SiteKey", "descriptionKey": "Abp.Account.Captcha.SiteKey.Description", - "defaultValue": "0x4AAAAAAAGadwQME-GSYuJU", + "defaultValue": "0x4AAAAAABdEjmiXxcl0j7jp", "isVisibleToClients": false, "providers": "G|D", "isInherited": false, @@ -738,7 +738,7 @@ "code": "Abp.Account.Captcha.SecretKey", "nameKey": "Abp.Account.Captcha.SecretKey", "descriptionKey": "Abp.Account.Captcha.SecretKey.Description", - "defaultValue": "0x4AAAAAAAGad_f_WP47IcNBs9FTu5DhNX8", + "defaultValue": "0x4AAAAAABdEjhw1A8sJZUvQX8-CgqvB3mE", "isVisibleToClients": false, "providers": "G|D", "isInherited": false, diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorkerTemplateDefinitionProvider.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorkerTemplateDefinitionProvider.cs index 967fe43..ec340b1 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorkerTemplateDefinitionProvider.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorkerTemplateDefinitionProvider.cs @@ -26,7 +26,7 @@ public class PlatformBackgroundWorkerTemplateDefinitionProvider : TemplateDefini foreach (var worker in workers.Where(a => a.IsActive && a.WorkerType == WorkerTypeEnum.MailQueueWorker && - a.Options != null && a.Options != string.Empty).ToList()) + a.Options != null).ToList().Where(a => !a.Options.IsNullOrWhiteSpace())) { var Options = JsonSerializer.Deserialize(worker.Options); diff --git a/ui/src/components/shared/Captcha.tsx b/ui/src/components/shared/Captcha.tsx index 13b462e..a3c3364 100644 --- a/ui/src/components/shared/Captcha.tsx +++ b/ui/src/components/shared/Captcha.tsx @@ -12,7 +12,6 @@ const Captcha = forwardRef((props: CaptchaProps, ref: Ref) => const { className, onError, onExpire, onSuccess } = props return ( - <> ) => onExpire={onExpire} onSuccess={onSuccess} /> - ) }) Captcha.displayName = 'Captcha' diff --git a/ui/src/components/shared/TenantSelector.tsx b/ui/src/components/shared/TenantSelector.tsx new file mode 100644 index 0000000..43e029f --- /dev/null +++ b/ui/src/components/shared/TenantSelector.tsx @@ -0,0 +1,148 @@ +import Input from '@/components/ui/Input' +import { getTenantByNameDetail } from '@/services/tenant.service' +import { useStoreActions, useStoreState } from '@/store' +import { useLocalization } from '@/utils/hooks/useLocalization' +import { defaultDomain, getSubdomain } from '@/utils/subdomain' +import type { CSSProperties } from 'react' +import { useCallback, useEffect, useRef } from 'react' + +const hiddenTenantStyle: CSSProperties = { + opacity: 0, + position: 'absolute', + pointerEvents: 'none', + height: 0, + margin: 0, + padding: 0, + border: 'none', +} + +const TenantSelector = () => { + const { translate } = useLocalization() + const isMultiTenant = useStoreState((state) => state.abpConfig.config?.multiTenancy.isEnabled) + const tenantName = useStoreState((state) => state.locale.currentTenantName) + const { setTenantName } = useStoreActions((actions) => actions.locale) + const { setTenant } = useStoreActions((actions) => actions.auth.tenant) + const { setWarning } = useStoreActions((actions) => actions.base.messages) + const requestIdRef = useRef(0) + const lastRequestedTenantNameRef = useRef() + + const subDomainName = getSubdomain() + const isSubdomainTenant = !!subDomainName && subDomainName !== defaultDomain + const tenantStyle = isSubdomainTenant ? hiddenTenantStyle : undefined + + const setWarningTimeout = useCallback( + (message: string) => { + setTimeout(() => { + setWarning(message) + }, 100) + }, + [setWarning], + ) + + const redirectToMainDomain = useCallback( + (name: string) => { + setTenantName(undefined) + const parts = window.location.hostname.split('.') + const mainDomain = parts.length >= 3 ? parts.slice(1).join('.') : window.location.hostname + setWarningTimeout( + `"${name}" kurumuna ait kayıt bulunamadı.\nAna sayfaya yönlendiriliyorsunuz...`, + ) + setTimeout(() => { + window.location.href = `${window.location.protocol}//${mainDomain}` + }, 3000) + }, + [setTenantName, setWarningTimeout], + ) + + const fetchDataByName = useCallback( + async (name: string, isSubdomain = false) => { + if (!isSubdomain && name === lastRequestedTenantNameRef.current) { + return + } + + lastRequestedTenantNameRef.current = name + const requestId = requestIdRef.current + 1 + requestIdRef.current = requestId + + if (name) { + try { + const response = await getTenantByNameDetail(name) + + if (requestId !== requestIdRef.current) { + return + } + + if (response.data) { + setTenant({ + tenantId: response.data.id, + tenantName: response.data.name, + menuGroup: response.data.menuGroup, + }) + } else { + setTenant(undefined) + if (isSubdomain) redirectToMainDomain(name) + } + } catch { + if (requestId !== requestIdRef.current) { + return + } + + setTenant(undefined) + if (isSubdomain) redirectToMainDomain(name) + } + } else { + setTenant(undefined) + } + }, + [redirectToMainDomain, setTenant], + ) + + const handleTenantNameChange = (value: string) => { + setTenantName(value) + } + + const handleTenantNameBlur = () => { + if (subDomainName) { + return + } + + fetchDataByName(tenantName || '') + } + + useEffect(() => { + if (!isMultiTenant) { + setTenant(undefined) + return + } + + if (subDomainName) { + setTenantName(subDomainName) + fetchDataByName(subDomainName, true) + } + }, [fetchDataByName, isMultiTenant, setTenant, setTenantName, subDomainName]) + + if (!isMultiTenant) { + return null + } + + return ( + <> + +
+ handleTenantNameChange(event.target.value)} + onBlur={handleTenantNameBlur} + style={tenantStyle} + aria-hidden={isSubdomainTenant ? 'true' : 'false'} + autoFocus={!isSubdomainTenant} + /> +
+ + ) +} + +export default TenantSelector diff --git a/ui/src/components/shared/index.ts b/ui/src/components/shared/index.ts index d89d398..826c710 100644 --- a/ui/src/components/shared/index.ts +++ b/ui/src/components/shared/index.ts @@ -19,6 +19,7 @@ export { default as SegmentItemOption } from './SegmentItemOption' export { default as StickyFooter } from './StickyFooter' export { default as SvgIcon } from './SvgIcon' export { default as TableRowSkeleton } from './loaders/TableRowSkeleton' +export { default as TenantSelector } from './TenantSelector' export { default as TextBlockSkeleton } from './loaders/TextBlockSkeleton' export { default as TextEllipsis } from './TextEllipsis' export { default as UsersAvatarGroup } from './UsersAvatarGroup' diff --git a/ui/src/views/admin/hr/OrgChart.tsx b/ui/src/views/admin/hr/OrgChart.tsx index dc03fbd..f32fca2 100644 --- a/ui/src/views/admin/hr/OrgChart.tsx +++ b/ui/src/views/admin/hr/OrgChart.tsx @@ -456,13 +456,15 @@ function OrgChartNode({ style={{ cursor: dragging ? 'grabbing' : 'grab' }} > {/* Header bar */} -
+
{mode === 'department' ? ( ) : ( )} - {node.name} + + {node.name} + {hasChildren && (
- {isMultiTenant && ( - <> - -
- setTenantName(e.target.value)} - style={tenantStyle} - aria-hidden={subDomainName && subDomainName !== defaultDomain ? 'true' : 'false'} - /> -
- - )} +
{ name="userName" placeholder={translate('::Abp.Account.EmailAddress')} component={Input} - inputClassName="dark:bg-gray-900 dark:text-gray-100" + className="dark:bg-gray-900 dark:text-gray-100" /> )} diff --git a/ui/src/views/auth/Register.tsx b/ui/src/views/auth/Register.tsx index c47beca..e59d7d7 100644 --- a/ui/src/views/auth/Register.tsx +++ b/ui/src/views/auth/Register.tsx @@ -1,5 +1,6 @@ import ActionLink from '@/components/shared/ActionLink' import PasswordInput from '@/components/shared/PasswordInput' +import TenantSelector from '@/components/shared/TenantSelector' import Alert from '@/components/ui/Alert' import Button from '@/components/ui/Button' import { FormContainer, FormItem } from '@/components/ui/Form' @@ -83,6 +84,7 @@ const Register = () => { {error} )} + { {error} )} +