Skip to content

Commit

Permalink
feat(general): Add multiple mailboxes (#1439)
Browse files Browse the repository at this point in the history
  • Loading branch information
rnwood authored Apr 27, 2024
1 parent 918cc34 commit 6a02795
Show file tree
Hide file tree
Showing 44 changed files with 1,077 additions and 348 deletions.
3 changes: 3 additions & 0 deletions Rnwood.Smtp4dev.Tests/Controllers/MessagesControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion Rnwood.Smtp4dev.Tests/Controllers/RelayMessageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public RelayMessagesTests()
var sqlLiteForTesting = new SqliteInMemory();
context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);
InitRepo();
messagesRepository.GetMessages(Arg.Any<bool>())
messagesRepository.GetMessages(Arg.Any<string>(), Arg.Any<bool>())
.Returns(context.Messages);
messagesRepository.TryGetMessageById(Arg.Any<Guid>(), Arg.Any<bool>())

Expand Down
11 changes: 8 additions & 3 deletions Rnwood.Smtp4dev.Tests/TestMessagesRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public TestMessagesRepository(params Message[] messages)

public List<DbModel.Message> Messages { get; } = new List<Message>();

public Task DeleteAllMessages()
public Task DeleteAllMessages(string mailboxName)
{
Messages.Clear();
return Task.CompletedTask;
Expand All @@ -30,12 +30,17 @@ public Task DeleteMessage(Guid id)
return Task.CompletedTask;
}

public IQueryable<Message> GetMessages(bool unTracked = true)
public IQueryable<Message> GetAllMessages(bool unTracked = true)
{
return Messages.AsQueryable();
}

public Task MarkAllMessagesRead()
public IQueryable<Message> GetMessages(string mailboxName, bool unTracked = true)
{
return Messages.AsQueryable();
}

public Task MarkAllMessagesRead(string mailboxName)
{
foreach (var msg in Messages)
{
Expand Down
8 changes: 7 additions & 1 deletion Rnwood.Smtp4dev/ApiModel/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand All @@ -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; }
}

}
13 changes: 13 additions & 0 deletions Rnwood.Smtp4dev/ClientApp/src/ApiClient/Mailbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

export default class Mailbox {
name: string;
recipients: string;

constructor(name: string, recipients: string) {

this.name = name;
this.recipients = recipients;
}


}
15 changes: 15 additions & 0 deletions Rnwood.Smtp4dev/ClientApp/src/ApiClient/MailboxesController.ts
Original file line number Diff line number Diff line change
@@ -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<Mailbox[]> {
return (await axios.get(this.getAll_url(), null || undefined))
.data as Mailbox[];
}
}
28 changes: 14 additions & 14 deletions Rnwood.Smtp4dev/ClientApp/src/ApiClient/MessagesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MessageSummary[]> {
public async getNewSummaries(lastSeenMessageId: string|null, mailboxName: string, pageSize: number = 50): Promise<MessageSummary[]> {

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<PagedResult<MessageSummary>> {
public async getSummaries(mailboxName: string, searchTerms: string, sortColumn: string, sortIsDescending: boolean, page: number = 1, pageSize: number = 25): Promise<PagedResult<MessageSummary>> {

return (await axios.get(this.getSummaries_url(searchTerms, sortColumn, sortIsDescending, page, pageSize), null || undefined)).data as PagedResult<MessageSummary>;
return (await axios.get(this.getSummaries_url(mailboxName, searchTerms, sortColumn, sortIsDescending, page, pageSize), null || undefined)).data as PagedResult<MessageSummary>;
}

// get: api/Messages/${encodeURIComponent(id)}
Expand All @@ -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<void> {
return await axios.post(`${this.apiBaseUrl}/markAllRead`);
public async markAllMessageRead(mailboxName: string): Promise<void> {
return await axios.post(`${this.apiBaseUrl}/markAllRead?mailboxName=${encodeURIComponent(mailboxName)}`);
}

// get: api/Messages/${encodeURIComponent(id)}/download
Expand Down Expand Up @@ -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<void> {
public async deleteAll(mailboxName: string): Promise<void> {

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;
}
}
71 changes: 40 additions & 31 deletions Rnwood.Smtp4dev/ClientApp/src/ApiClient/Server.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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[];
}
62 changes: 0 additions & 62 deletions Rnwood.Smtp4dev/ClientApp/src/ApiClient/Template.tst

This file was deleted.

4 changes: 3 additions & 1 deletion Rnwood.Smtp4dev/ClientApp/src/ApiClient/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}


Expand Down
12 changes: 9 additions & 3 deletions Rnwood.Smtp4dev/ClientApp/src/MessageNotificationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));


Expand Down
Loading

0 comments on commit 6a02795

Please sign in to comment.