diff --git a/Rnwood.Smtp4dev.Tests/Controllers/MessagesControllerTests.cs b/Rnwood.Smtp4dev.Tests/Controllers/MessagesControllerTests.cs index 22e6508d5..4c8a6323f 100644 --- a/Rnwood.Smtp4dev.Tests/Controllers/MessagesControllerTests.cs +++ b/Rnwood.Smtp4dev.Tests/Controllers/MessagesControllerTests.cs @@ -17,6 +17,7 @@ using Rnwood.Smtp4dev.Hubs; using Rnwood.Smtp4dev.Tests.DBMigrations.Helpers; using Xunit; +using Rnwood.Smtp4dev.Server.Settings; namespace Rnwood.Smtp4dev.Tests.Controllers { @@ -128,6 +129,8 @@ public async Task GetMessage_ValidMime() IMessage message = await memoryMessageBuilder.ToMessage(); var dbMessage = await new MessageConverter().ConvertAsync(message); + dbMessage.Mailbox = new DbModel.Mailbox { Name = MailboxOptions.DEFAULTNAME }; + return dbMessage; } diff --git a/Rnwood.Smtp4dev.Tests/Controllers/RelayMessageTests.cs b/Rnwood.Smtp4dev.Tests/Controllers/RelayMessageTests.cs index e311beb1e..2b6812f39 100644 --- a/Rnwood.Smtp4dev.Tests/Controllers/RelayMessageTests.cs +++ b/Rnwood.Smtp4dev.Tests/Controllers/RelayMessageTests.cs @@ -32,7 +32,7 @@ public RelayMessagesTests() var sqlLiteForTesting = new SqliteInMemory(); context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions); InitRepo(); - messagesRepository.GetMessages(Arg.Any()) + messagesRepository.GetMessages(Arg.Any(), Arg.Any()) .Returns(context.Messages); messagesRepository.TryGetMessageById(Arg.Any(), Arg.Any()) diff --git a/Rnwood.Smtp4dev.Tests/TestMessagesRepository.cs b/Rnwood.Smtp4dev.Tests/TestMessagesRepository.cs index 9c2cb12b7..b0d921dac 100644 --- a/Rnwood.Smtp4dev.Tests/TestMessagesRepository.cs +++ b/Rnwood.Smtp4dev.Tests/TestMessagesRepository.cs @@ -16,7 +16,7 @@ public TestMessagesRepository(params Message[] messages) public List Messages { get; } = new List(); - public Task DeleteAllMessages() + public Task DeleteAllMessages(string mailboxName) { Messages.Clear(); return Task.CompletedTask; @@ -30,12 +30,17 @@ public Task DeleteMessage(Guid id) return Task.CompletedTask; } - public IQueryable GetMessages(bool unTracked = true) + public IQueryable GetAllMessages(bool unTracked = true) { return Messages.AsQueryable(); } - public Task MarkAllMessagesRead() + public IQueryable GetMessages(string mailboxName, bool unTracked = true) + { + return Messages.AsQueryable(); + } + + public Task MarkAllMessagesRead(string mailboxName) { foreach (var msg in Messages) { diff --git a/Rnwood.Smtp4dev/ApiModel/Server.cs b/Rnwood.Smtp4dev/ApiModel/Server.cs index 4e9b063d0..58b688b03 100644 --- a/Rnwood.Smtp4dev/ApiModel/Server.cs +++ b/Rnwood.Smtp4dev/ApiModel/Server.cs @@ -36,7 +36,9 @@ public class Server public string RecipientValidationExpression { get; set; } public string MessageValidationExpression { get; set; } public bool DisableIPv6 { get; set; } - public User[] Users { get; set; } + public UserOptions[] Users { get; set; } + + public MailboxOptions[] Mailboxes { get; set; } public string RelaySmtpServer { get; set; } @@ -59,6 +61,10 @@ public class Server public string[] SmtpEnabledAuthTypesWhenSecureConnection { get; set; } public string[] SmtpEnabledAuthTypesWhenNotSecureConnection { get; set; } + + public string CurrentUserName { get; set; } + + public string CurrentUserDefaultMailboxName { get; set; } } } diff --git a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Mailbox.ts b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Mailbox.ts new file mode 100644 index 000000000..e3ba99d5a --- /dev/null +++ b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Mailbox.ts @@ -0,0 +1,13 @@ + +export default class Mailbox { + name: string; + recipients: string; + + constructor(name: string, recipients: string) { + + this.name = name; + this.recipients = recipients; + } + + +} diff --git a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/MailboxesController.ts b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/MailboxesController.ts new file mode 100644 index 000000000..2936cc7be --- /dev/null +++ b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/MailboxesController.ts @@ -0,0 +1,15 @@ +import axios from "axios"; +import Mailbox from "./Mailbox"; + +export default class MailboxesController { + constructor() {} + + public getAll_url(): string { + return `api/Mailboxes`; + } + + public async getAll(): Promise { + return (await axios.get(this.getAll_url(), null || undefined)) + .data as Mailbox[]; + } +} diff --git a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/MessagesController.ts b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/MessagesController.ts index ba2a9e41f..4891f5cec 100644 --- a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/MessagesController.ts +++ b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/MessagesController.ts @@ -12,22 +12,22 @@ export default class MessagesController { private apiBaseUrl = `api/Messages`; - public getNewSummaries_url(lastSeenMessageId: string|null, pageSize: number = 50): string { - return `${this.apiBaseUrl}/new?lastSeenMessageId=${encodeURIComponent(lastSeenMessageId ?? "")}&pageSize=${pageSize}`; + public getNewSummaries_url(lastSeenMessageId: string | null, mailboxName: string, pageSize: number = 50): string { + return `${this.apiBaseUrl}/new?mailboxName=${mailboxName}&lastSeenMessageId=${encodeURIComponent(lastSeenMessageId ?? "")}&pageSize=${pageSize}`; } - public async getNewSummaries(lastSeenMessageId: string|null, pageSize: number = 50): Promise { + public async getNewSummaries(lastSeenMessageId: string|null, mailboxName: string, pageSize: number = 50): Promise { - return (await axios.get(this.getNewSummaries_url(lastSeenMessageId, pageSize), null || undefined)).data as MessageSummary[]; + return (await axios.get(this.getNewSummaries_url(lastSeenMessageId, mailboxName, pageSize), null || undefined)).data as MessageSummary[]; } - public getSummaries_url(searchTerms: string, sortColumn: string, sortIsDescending: boolean, page: number = 1, pageSize: number = 25): string { - return `${this.apiBaseUrl}?searchTerms=${encodeURIComponent(searchTerms)}&sortColumn=${encodeURIComponent(sortColumn)}&sortIsDescending=${sortIsDescending}&page=${page}&pageSize=${pageSize}`; + public getSummaries_url(mailboxName: string, searchTerms: string, sortColumn: string, sortIsDescending: boolean, page: number = 1, pageSize: number = 25): string { + return `${this.apiBaseUrl}?mailboxName=${encodeURIComponent(mailboxName)}&searchTerms=${encodeURIComponent(searchTerms)}&sortColumn=${encodeURIComponent(sortColumn)}&sortIsDescending=${sortIsDescending}&page=${page}&pageSize=${pageSize}`; } - public async getSummaries(searchTerms: string, sortColumn: string, sortIsDescending: boolean, page: number = 1, pageSize: number = 25): Promise> { + public async getSummaries(mailboxName: string, searchTerms: string, sortColumn: string, sortIsDescending: boolean, page: number = 1, pageSize: number = 25): Promise> { - return (await axios.get(this.getSummaries_url(searchTerms, sortColumn, sortIsDescending, page, pageSize), null || undefined)).data as PagedResult; + return (await axios.get(this.getSummaries_url(mailboxName, searchTerms, sortColumn, sortIsDescending, page, pageSize), null || undefined)).data as PagedResult; } // get: api/Messages/${encodeURIComponent(id)} @@ -50,8 +50,8 @@ export default class MessagesController { return (await axios.post(this.markMessageRead_url(id), null || undefined)).data as void; } - public async markAllMessageRead(): Promise { - return await axios.post(`${this.apiBaseUrl}/markAllRead`); + public async markAllMessageRead(mailboxName: string): Promise { + return await axios.post(`${this.apiBaseUrl}/markAllRead?mailboxName=${encodeURIComponent(mailboxName)}`); } // get: api/Messages/${encodeURIComponent(id)}/download @@ -155,12 +155,12 @@ export default class MessagesController { } // delete: api/Messages/* - public deleteAll_url(): string { - return `${this.apiBaseUrl}/*`; + public deleteAll_url(mailboxName: string): string { + return `${this.apiBaseUrl}/*?mailboxName=${encodeURIComponent(mailboxName)}`; } - public async deleteAll(): Promise { + public async deleteAll(mailboxName: string): Promise { - return (await axios.delete(this.deleteAll_url(), null || undefined)).data as void; + return (await axios.delete(this.deleteAll_url(mailboxName), null || undefined)).data as void; } } \ No newline at end of file diff --git a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Server.ts b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Server.ts index d706d77d7..48327d5b6 100644 --- a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Server.ts +++ b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Server.ts @@ -1,15 +1,13 @@  +import Mailbox from './Mailbox'; import User from './User'; export default class Server { - webAuthenticationRequired: boolean; - smtpAllowAnyCredentials: boolean; - minimiseToTrayIcon: boolean; - isDesktopApp: boolean; - smtpEnabledAuthTypesWhenNotSecureConnection: string[]; - smtpEnabledAuthTypesWhenSecureConnection: string[]; + currentUserName: string; + currentUserDefaultMailboxName: string; - constructor(isRunning: boolean, exception: string, portNumber: number, hostName: string, allowRemoteConnections: boolean, numberOfMessagesToKeep: number, numberOfSessionsToKeep: number, imapPortNumber: number, settingsAreEditable: boolean, disableMessageSanitisation: boolean, automaticRelayExpression: string, tlsMode: string, credentialsValidationExpression: string, - authenticationRequired: boolean, + + constructor(isRunning: boolean, exception: string, portNumber: number, hostName: string, allowRemoteConnections: boolean, numberOfMessagesToKeep: number, numberOfSessionsToKeep: number, imapPortNumber: number, settingsAreEditable: boolean, disableMessageSanitisation: boolean, automaticRelayExpression: string, tlsMode: string, credentialsValidationExpression: string, + authenticationRequired: boolean, secureConnectionRequired: boolean, recipientValidationExpression: string, messageValidationExpression: string, disableIPv6: string, users: User[], relayTlsMode: string | undefined, relaySmtpServer: string, @@ -18,23 +16,25 @@ export default class Server { relaySenderAddress: string, relayLogin: string, relayPassword: string, - webAuthenticationRequired : boolean, - smtpAllowAnyCredentials: boolean, + webAuthenticationRequired: boolean, + smtpAllowAnyCredentials: boolean, lockedSettings: { [key: string]: string }, minimiseToTrayIcon: boolean, isDesktopApp: boolean, smtpEnabledAuthTypesWhenNotSecureConnection: string[], - smtpEnabledAuthTypesWhenSecureConnection: string[] - ) - { - - this.isRunning = isRunning; - this.exception = exception; - this.portNumber = portNumber; - this.hostName = hostName; - this.allowRemoteConnections = allowRemoteConnections; - this.numberOfMessagesToKeep = numberOfMessagesToKeep; - this.numberOfSessionsToKeep = numberOfSessionsToKeep; + smtpEnabledAuthTypesWhenSecureConnection: string[], + mailboxes: Mailbox[], + currentUserName: string, + currentUserDefaultMailboxName: string + ) { + + this.isRunning = isRunning; + this.exception = exception; + this.portNumber = portNumber; + this.hostName = hostName; + this.allowRemoteConnections = allowRemoteConnections; + this.numberOfMessagesToKeep = numberOfMessagesToKeep; + this.numberOfSessionsToKeep = numberOfSessionsToKeep; this.imapPortNumber = imapPortNumber; this.settingsAreEditable = settingsAreEditable; this.disableMessageSanitisation = disableMessageSanitisation; @@ -56,22 +56,24 @@ export default class Server { this.relayPassword = relayPassword; this.lockedSettings = lockedSettings; this.webAuthenticationRequired = webAuthenticationRequired; - this.smtpAllowAnyCredentials = smtpAllowAnyCredentials; + this.smtpAllowAnyCredentials = smtpAllowAnyCredentials; this.minimiseToTrayIcon = minimiseToTrayIcon; this.isDesktopApp = isDesktopApp; this.smtpEnabledAuthTypesWhenNotSecureConnection = smtpEnabledAuthTypesWhenNotSecureConnection; this.smtpEnabledAuthTypesWhenSecureConnection = smtpEnabledAuthTypesWhenSecureConnection; - + this.mailboxes = mailboxes; + this.currentUserName = currentUserName; + this.currentUserDefaultMailboxName = currentUserDefaultMailboxName; } - - isRunning: boolean; - exception: string; - portNumber: number; - hostName: string; - allowRemoteConnections: boolean; - numberOfMessagesToKeep: number; - numberOfSessionsToKeep: number; + + isRunning: boolean; + exception: string; + portNumber: number; + hostName: string; + allowRemoteConnections: boolean; + numberOfMessagesToKeep: number; + numberOfSessionsToKeep: number; imapPortNumber: number; settingsAreEditable: boolean; disableMessageSanitisation: boolean; @@ -92,4 +94,11 @@ export default class Server { relayLogin: string; relayPassword: string; lockedSettings: { [key: string]: string }; + webAuthenticationRequired: boolean; + smtpAllowAnyCredentials: boolean; + minimiseToTrayIcon: boolean; + isDesktopApp: boolean; + smtpEnabledAuthTypesWhenNotSecureConnection: string[]; + smtpEnabledAuthTypesWhenSecureConnection: string[]; + mailboxes: Mailbox[]; } diff --git a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Template.tst b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Template.tst deleted file mode 100644 index f17b61d60..000000000 --- a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/Template.tst +++ /dev/null @@ -1,62 +0,0 @@ -${ - using Typewriter.Extensions.WebApi; - - string ReturnType(Method m) => m.Type.Name == "IActionResult" ? "void" : m.Type.Name; - string ParamType(Parameter p) => p.Type.Name.Replace("Delta<", "Partial<"); - string ServiceName(Class c) => c.Name; - - string Imports(Class c){ - List neededImports = c.Properties - .Where(p => !p.Type.IsPrimitive && p.Type.Name.TrimEnd('[',']') != c.Name) - .Select(p => "//" + p.Type.Name + " from " + c.Name + "\nimport " + p.Type.Name.TrimEnd('[',']') + " from './" + p.Type.Name.TrimEnd('[',']') + "';").ToList(); - if (c.BaseClass != null) { - neededImports.Add("import " + c.BaseClass.Name +" from './" + c.BaseClass.Name + "';"); - } - return String.Join("\n", neededImports.Distinct()); - } - - string ControllerImports(Class c){ - List returnTypeImports = c.Methods - .Where(m => !m.Type.IsPrimitive && !m.Type.Name.Contains("void") && m.Type.Name != "IActionResult") - .Select(p => "import " + p.Type.Name.TrimEnd('[',']') + " from './" + p.Type.Name.TrimEnd('[',']') + "';").ToList(); - - List paramTypeImports = c.Methods - .SelectMany(m => m.Parameters) - .Where(p => !p.Type.IsPrimitive) - .Select(p => "import " + p.Type.Name.TrimEnd('[',']') + " from './" + p.Type.Name.TrimEnd('[',']') + "';").ToList(); - - - return String.Join("\n", returnTypeImports.Concat(paramTypeImports).Distinct()); - } -} -$Classes(c => c.Namespace == "Rnwood.Smtp4dev.ApiModel")[$Imports -export default class $Name$TypeParameters { - - constructor($Properties(p => !p.Attributes.Any(a => a.Name.Contains("JsonIgnoreAttribute")))[$name: $Type, ]) { - $Properties(p => !p.Attributes.Any(a => a.Name.Contains("JsonIgnoreAttribute")))[ - this.$name = $name;] - } - - $Properties(p => !p.Attributes.Any(a => a.Name.Contains("JsonIgnoreAttribute")))[ - $name: $Type;] -}] -$Classes(*Controller)[$ControllerImports -import axios from "axios"; - -export default class $ServiceName { - - constructor(){ - } - - $Methods[ - - // $HttpMethod: $Url - public $name_url($Parameters(p => p.Type.IsPrimitive)[$name: $Type][, ]): string { - return `$Url`; - } - - public async $name($Parameters[$name: $ParamType][, ]): Promise<$ReturnType> { - - return (await axios.$HttpMethod(this.$name_url($Parameters(p => p.Type.IsPrimitive)[$name][, ]), $RequestData || undefined)).data as $ReturnType; - }] -}] \ No newline at end of file diff --git a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/User.ts b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/User.ts index fff177950..29ed3c889 100644 --- a/Rnwood.Smtp4dev/ClientApp/src/ApiClient/User.ts +++ b/Rnwood.Smtp4dev/ClientApp/src/ApiClient/User.ts @@ -2,11 +2,13 @@ export default class User { username: string; password: string; + defaultMailbox: string; - constructor(username: string, password: string) { + constructor(username: string, password: string, defaultMailbox: string) { this.username = username; this.password = password; + this.defaultMailbox = defaultMailbox; } diff --git a/Rnwood.Smtp4dev/ClientApp/src/MessageNotificationManager.ts b/Rnwood.Smtp4dev/ClientApp/src/MessageNotificationManager.ts index b75f6a777..f798688c1 100644 --- a/Rnwood.Smtp4dev/ClientApp/src/MessageNotificationManager.ts +++ b/Rnwood.Smtp4dev/ClientApp/src/MessageNotificationManager.ts @@ -3,13 +3,15 @@ import { debounce } from 'ts-debounce'; import MessagesController from "./ApiClient/MessagesController"; export default class MessageNotificationManager { - constructor(onClick: (message: MessageSummary) => void) { + constructor(mailboxName: string|null, onClick: (message: MessageSummary) => void) { if (Notification.permission == "default") { Notification.requestPermission(); } + this.mailboxName = mailboxName; this.onClick = onClick; } + private readonly mailboxName: string|null; private lastNotifiedMessage: MessageSummary | null = null; private onClick: (message: MessageSummary) => void; private visibleNotificationCloseTimeout: any | null = null; @@ -20,8 +22,12 @@ export default class MessageNotificationManager { refresh = debounce(this.refreshInternal, 500); async refreshInternal(suppressNotifications: boolean) { - - const messagesByDate = await new MessagesController().getNewSummaries(this.lastNotifiedMessage?.id ?? ""); + + if (!this.mailboxName) { + return; + } + + const messagesByDate = await new MessagesController().getNewSummaries(this.lastNotifiedMessage?.id ?? "", this.mailboxName); const messagesToAdd = messagesByDate.filter(m => !this.unnotifiedMessages.find(um => um.id=== m.id)); diff --git a/Rnwood.Smtp4dev/ClientApp/src/components/home/home.vue b/Rnwood.Smtp4dev/ClientApp/src/components/home/home.vue index 5a4b4880f..e0e639da0 100644 --- a/Rnwood.Smtp4dev/ClientApp/src/components/home/home.vue +++ b/Rnwood.Smtp4dev/ClientApp/src/components/home/home.vue @@ -1,6 +1,6 @@