Skip to content

feat: Email Accounts per Store#680

Merged
KrzysztofPajak merged 12 commits into
developfrom
copilot/add-email-functionality-per-store
May 10, 2026
Merged

feat: Email Accounts per Store#680
KrzysztofPajak merged 12 commits into
developfrom
copilot/add-email-functionality-per-store

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 26, 2026

  • Fix failing test DeleteEmailAccount_InvokeExpectedMethods — replaced stale cache mock with _repository.Setup(r => r.Table).Returns(...) returning 2 accounts
  • Fix DeleteEmailAccount_ExistOnlyOneAccount_ThrowException — proper Table mock and awaited assertion
  • Fix SendTestEmail in Store EmailAccountController — catch Exception instead of only GrandException so SMTP errors (e.g. ArgumentException: host name cannot be empty) are shown as inline error notifications instead of the global error page, matching the Admin controller's pattern

Copilot AI and others added 2 commits April 26, 2026 18:47
- Add StoreId property to EmailAccount domain entity
- Add GetEmailAccountsByStore(storeId) method to IEmailAccountService and EmailAccountService
- Add EMAILACCOUNT_BY_STORE_KEY cache key
- Update MessageProviderService to use store-specific email account as fallback
- Update EmailAccountModel with StoreId and AvailableStores
- Update EmailAccountProfile mapper to include StoreId
- Update EmailAccountViewModelService to be async and populate available stores
- Add EmailAccountController to Grand.Web.Store with CRUD + store isolation
- Add EmailAccount views (List, Create, Edit, Partials/CreateOrUpdate) in Store area
- Add Messages namespace to Store _ViewImports.cshtml
- Update Admin EmailAccountController for async PrepareEmailAccountModel

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/094d04cc-7d75-4a00-af14-f5bb77f05736

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Comment thread src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs Fixed
Comment thread src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs Fixed
KrzysztofPajak and others added 2 commits April 27, 2026 05:51
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Comment thread src/Business/Grand.Business.Messages/Services/EmailAccountService.cs Outdated
Comment thread src/Business/Grand.Business.Messages/Services/MessageProviderService.cs Outdated
…vent dirty data

Instead of caching per-store results separately (which could hold stale/mutated
objects when an account's StoreId changes), filter directly from the already
correctly-invalidated GetAllEmailAccounts cache. Also removes the now-unused
EMAILACCOUNT_BY_STORE_KEY constant.

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/5697bb0d-b979-44be-8f1a-2f8c98ad8b5e

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot Grand.Web.Store should not see all available store

…lableStores/PrepareEmailAccountModel calls

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/d1fe358f-d64b-47c9-af25-cea72fae9de0

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 27, 2026

@copilot Grand.Web.Store should not see all available store

Fixed in 84fdff0. The Store controller was calling PrepareEmailAccountModel() and PrepareAvailableStores() which both invoke GetAllStores() internally. Since the Store view already renders StoreId as a hidden field (not a dropdown) and the value is always locked to CurrentStoreId, all those calls have been removed. The Create GET action now builds the model directly (just sets Port = 25 and StoreId = CurrentStoreId) without touching the store service at all.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot check my comments

…tEmailAccountsByStore, pass store?.Id to GetEmailAccountOfMessageTemplate callers

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/dba495a6-918d-4de2-8bc5-ae0409bfb8ff

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 29, 2026

@copilot check my comments

Both comments addressed in 5994a60:

  1. GetAllEmailAccounts extended with optional storeIdGetEmailAccountsByStore has been removed. IEmailAccountService.GetAllEmailAccounts(string storeId = "") now handles both cases: no argument returns all accounts, a non-empty storeId filters in-memory from the same cache entry.

  2. storeId = "" never used — all 34 call sites of GetEmailAccountOfMessageTemplate now pass store?.Id as the third argument, so the store-specific email account fallback is actually reachable.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • admin\@admin.com
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/grandnode2/grandnode2/src/Tests/Grand.Business.Messages.Tests/bin/Debug/net10.0/Grand.Business.Messages.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/grandnode2/grandnode2/src/Tests/Grand.Business.Messages.Tests/bin/Debug/net10.0/Grand.Business.Messages.Tests.deps.json /home/REDACTED/work/grandnode2/grandnode2/src/Tests/Grand.Business.Messages.Tests/bin/Debug/net10.0/testhost.dll --port 38021 --endpoint 127.0.0.1:038021 --role client --parentprocessid 5147 --telemetryoptedin false (dns block)
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/grandnode2/grandnode2/src/Tests/Grand.Business.Messages.Tests/bin/Debug/net10.0/Grand.Business.Messages.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/grandnode2/grandnode2/src/Tests/Grand.Business.Messages.Tests/bin/Debug/net10.0/Grand.Business.Messages.Tests.deps.json /home/REDACTED/work/grandnode2/grandnode2/src/Tests/Grand.Business.Messages.Tests/bin/Debug/net10.0/testhost.dll --port 37459 --endpoint 127.0.0.1:037459 --role client --parentprocessid 5397 --telemetryoptedin false (dns block)
  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot GetEmailAccountOfMessageTemplate remove param storeId

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 30, 2026

@copilot GetEmailAccountOfMessageTemplate remove param storeId

Done in 50dfcbc. GetEmailAccountOfMessageTemplate now takes only (MessageTemplate, languageId) — the storeId parameter and the store-specific fallback branch have been removed. The resolution order is: template's email account ID → default account from settings → first available account.

@KrzysztofPajak KrzysztofPajak marked this pull request as ready for review May 2, 2026 17:43
Copilot AI review requested due to automatic review settings May 2, 2026 17:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds store-scoped email accounts across domain/service layers and exposes CRUD management in the Store area UI, while updating Admin UI to support selecting a store scope for each email account.

Changes:

  • Introduces StoreId on EmailAccount and extends GetAllEmailAccounts with an optional store filter.
  • Updates AdminShared model/mapping and view-model service to support store selection (available stores list).
  • Adds Store-area EmailAccountController plus corresponding views for store-isolated management.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs New Store-area CRUD controller for email accounts filtered to current staff store
src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml Adds messages model namespace for Store-area views
src/Web/Grand.Web.Store/Areas/Store/Views/EmailAccount/Partials/CreateOrUpdate.cshtml New create/edit partial for Store area (hidden StoreId)
src/Web/Grand.Web.Store/Areas/Store/Views/EmailAccount/List.cshtml New Kendo grid list view for Store area
src/Web/Grand.Web.Store/Areas/Store/Views/EmailAccount/Edit.cshtml New Store-area edit view
src/Web/Grand.Web.Store/Areas/Store/Views/EmailAccount/Create.cshtml New Store-area create view
src/Web/Grand.Web.AdminShared/Services/EmailAccountViewModelService.cs Makes model preparation async and populates available stores for selection
src/Web/Grand.Web.AdminShared/Models/Messages/EmailAccountModel.cs Adds StoreId + AvailableStores to the model
src/Web/Grand.Web.AdminShared/Mapper/EmailAccountProfile.cs Updates mapping to ignore AvailableStores
src/Web/Grand.Web.AdminShared/Interfaces/IEmailAccountViewModelService.cs Makes PrepareEmailAccountModel async; adds PrepareAvailableStores
src/Web/Grand.Web.Admin/Controllers/EmailAccountController.cs Uses async model preparation and populates stores on Edit GET
src/Web/Grand.Web.Admin/Areas/Admin/Views/EmailAccount/Partials/CreateOrUpdate.cshtml Adds Store selector dropdown to Admin email account editor
src/Tests/Grand.Business.Messages.Tests/Services/MessageProviderServiceTest.cs Updates mock setup for the new GetAllEmailAccounts signature
src/Core/Grand.Domain/Messages/EmailAccount.cs Adds StoreId to the domain entity
src/Business/Grand.Business.Messages/Services/MessageProviderService.cs Simplifies email account resolution for message templates
src/Business/Grand.Business.Messages/Services/EmailAccountService.cs Adds optional store filter to GetAllEmailAccounts
src/Business/Grand.Business.Core/Interfaces/Messages/IEmailAccountService.cs Updates interface signature/documentation for store-filtered retrieval

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Business/Grand.Business.Messages/Services/EmailAccountService.cs Outdated
Comment thread src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs Outdated
Comment thread src/Core/Grand.Domain/Messages/EmailAccount.cs
Comment thread src/Web/Grand.Web.Store/Areas/Store/Views/EmailAccount/List.cshtml
Comment thread src/Business/Grand.Business.Messages/Services/EmailAccountService.cs Outdated
Comment thread src/Business/Grand.Business.Messages/Services/MessageProviderService.cs Outdated
Comment thread src/Business/Grand.Business.Messages/Services/EmailAccountService.cs Outdated
@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot Do not cache email accounts, add paggination from db

…ailAccounts

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/4d8b440d-2667-411f-b545-771aa6e92352

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • admin\@admin.com
    • Triggering command: /usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/grandnode2/grandnode2/src/Tests/Grand.Business.Messages.Tests/bin/Debug/net10.0/Grand.Business.Messages.Tests.runtimeconfig.json --depsfile /home/REDACTED/work/grandnode2/grandnode2/src/Tests/Grand.Business.Messages.Tests/bin/Debug/net10.0/Grand.Business.Messages.Tests.deps.json /home/REDACTED/work/grandnode2/grandnode2/src/Tests/Grand.Business.Messages.Tests/bin/Debug/net10.0/testhost.dll --port 39353 --endpoint 127.0.0.1:039353 --role client --parentprocessid 7869 --telemetryoptedin false (dns block)
  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Admin/Grand.Web.Admin.csproj -v q (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Admin/Grand.Web.Admin.csproj -v q (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Admin/Grand.Web.Admin.csproj -v q (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Admin/Grand.Web.Admin.csproj -v q (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot Do not cache email accounts, add paggination from db

Already done in 779107e:

  • GetAllEmailAccounts now queries the DB directly on every call (no list cache). Only GetEmailAccountById retains a by-ID cache.
  • EMAILACCOUNT_ALL_KEY constant removed.
  • Pagination added: GetAllEmailAccounts(string storeId = "", int pageIndex = 0, int pageSize = int.MaxValue) returns IPagedList<EmailAccount> using PagedList<EmailAccount>.Create(query, pageIndex, pageSize).
  • Both Admin and Store list actions pass command.Page - 1 / command.PageSize and report TotalCount to the grid.

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot Grand.Business.Messages.Tests.Services.EmailAccountServiceTests.DeleteEmailAccount_InvokeExpectedMethods
Test method Grand.Business.Messages.Tests.Services.EmailAccountServiceTests.DeleteEmailAccount_InvokeExpectedMethods threw exception:
Grand.SharedKernel.GrandException: You cannot delete this email account. At least one account is required.

…elete checks

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/9732f29d-bac6-466a-b11d-52f4683a11e7

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot Grand.Business.Messages.Tests.Services.EmailAccountServiceTests.DeleteEmailAccount_InvokeExpectedMethods
Test method Grand.Business.Messages.Tests.Services.EmailAccountServiceTests.DeleteEma...

Fixed in ce8ffbc. The two DeleteEmailAccount tests were still mocking ICacheBase.GetAsync() (the old cached-list approach), but DeleteEmailAccount now queries _emailAccountRepository.Table directly. Updated both tests to mock _repository.Setup(r => r.Table).Returns(...) instead:

  • DeleteEmailAccount_InvokeExpectedMethods → returns 2 accounts (guard passes, delete proceeds)
  • DeleteEmailAccount_ExistOnlyOneAccount_ThrowException → returns 1 account (guard throws GrandException); also fixed the method signature from void to async Task so the ThrowsExactlyAsync result is properly awaited

All 4 EmailAccountServiceTests now pass (4/4).

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot when I click Send test email I get excepion page instead errors
Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
 Executed endpoint 'Grand.Web.Store.Controllers.EmailAccountController.SendTestEmail (Grand.Web.Store)'
fail: UseExceptionHandler[0]
 The host name cannot be empty. (Parameter 'host')
 System.ArgumentException: The host name cannot be empty. (Parameter 'host')
 at MailKit.Net.Smtp.SmtpClient.ValidateArguments(String host, Int32 port)
 at MailKit.Net.Smtp.SmtpClient.ConnectAsync(String host, Int32 port, SecureSocketOptions options, CancellationToken cancellationToken)
 at Grand.Business.Messages.Services.EmailSender.SendEmail(EmailAccount emailAccount, String subject, String body, String fromAddress, String fromName, String toAddress, String toName, String replyToAddress, String replyToName, IEnumerable1 bccAddresses, IEnumerable1 ccAddresses, String attachmentFilePath, String attachmentFileName, IEnumerable`1 attachedDownloads) in C:\Projects\Grandnode\grandnode2\src\Business\Grand.Business.Messages\Services\EmailSender.cs:line 122
 at Grand.Web.AdminShared.Services.EmailAccountViewModelService.SendTestEmail(EmailAccount emailAccount, EmailAccountModel model) in C:\Projects\Grandnode\grandnode2\src\Web\Grand.Web.AdminShared\Services\EmailAccountViewModelService.cs:line 66
 at Grand.Web.Store.Controllers.EmailAccountController.SendTestEmail(EmailAccountModel model) in C:\Projects\Grandnode\grandnode2\src\Web\Grand.Web.Store\Controllers\EmailAccountController.cs:line 125
 at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
 at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Logged|12_1(ControllerActionInvoker invoker)
 at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
 at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
 at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
 at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 10, 2026

@copilot when I click Send test email I get excepion page instead errors
Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
 Executed endpoint 'Grand.Web.Store.Controllers.EmailAccountContro...

Fixed in e9e6d0a. The Store SendTestEmail was catching only GrandException, so the System.ArgumentException thrown by MailKit (The host name cannot be empty) escaped to the global exception handler and showed the error page. Changed the catch to Exception — matching the Admin controller's pattern — so any error from the SMTP client is caught and displayed as an inline error notification.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Comment on lines +134 to +137
catch (Exception exc)
{
Error(exc.Message);
}
@KrzysztofPajak KrzysztofPajak merged commit 3f116e8 into develop May 10, 2026
6 checks passed
@KrzysztofPajak KrzysztofPajak deleted the copilot/add-email-functionality-per-store branch May 10, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants