Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/modules/email/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
EmailListResponse,
EmailSummary,
ListEmails,
MailQuota,
Mailbox,
MailboxType,
SearchEmailDto,
Expand Down Expand Up @@ -162,4 +163,8 @@ export class EmailService {
): Promise<DownloadAttachmentResponse> {
return this.mail.downloadAttachment(payload);
}

getQuota(userEmail: string): Promise<MailQuota> {
return this.mail.getQuota(userEmail);
}
}
5 changes: 5 additions & 0 deletions src/modules/email/email.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,8 @@ export interface SearchEmailFilter {
unread?: boolean;
hasAttachment?: boolean;
}

export interface MailQuota {
used: number;
limit: number;
}
2 changes: 2 additions & 0 deletions src/modules/email/mail-provider.port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
Email,
EmailListResponse,
ListEmails,
MailQuota,
Mailbox,
MailboxType,
SearchEmailDto,
Expand Down Expand Up @@ -54,4 +55,5 @@ export abstract class MailProvider {
abstract downloadAttachment(
payload: DownloadAttachmentPayload,
): Promise<DownloadAttachmentResponse>;
abstract getQuota(userEmail: string): Promise<MailQuota>;
}
31 changes: 31 additions & 0 deletions src/modules/infrastructure/jmap/jmap-mail.provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
newJmapMailbox,
newJmapEmail,
newJmapIdentity,
newJmapQuota,
newSendEmailDto,
newDraftEmailDto,
} from '../../../../test/fixtures.js';
Expand Down Expand Up @@ -584,4 +585,34 @@ describe('JmapMailProvider', () => {
expect(result).toBe(stored);
});
});

describe('getQuota', () => {
it('when quota exists, then returns used and limit from octets quota', async () => {
const quota = newJmapQuota({ used: 500_000, hardLimit: 1_000_000 });
jmapService.request.mockResolvedValue(jmapResponse({ list: [quota] }));

const result = await provider.getQuota('user@test.com');

expect(result).toEqual({ used: 500_000, limit: 1_000_000 });
});

it('when no octets quota exists, then returns zeros', async () => {
const nonOctetsQuota = newJmapQuota({ resourceType: 'count' });
jmapService.request.mockResolvedValue(
jmapResponse({ list: [nonOctetsQuota] }),
);

const result = await provider.getQuota('user@test.com');

expect(result).toEqual({ used: 0, limit: 0 });
});

it('when quota list is empty, then returns zeros', async () => {
jmapService.request.mockResolvedValue(jmapResponse({ list: [] }));

const result = await provider.getQuota('user@test.com');

expect(result).toEqual({ used: 0, limit: 0 });
});
});
});
22 changes: 21 additions & 1 deletion src/modules/infrastructure/jmap/jmap-mail.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import type {
Email,
EmailListResponse,
ListEmails,
MailQuota,
Mailbox,
MailboxType,
SearchEmailFilter,
SendEmailDto,
} from '../../email/email.types.js';
import { JmapService } from './jmap.service.js';
import { JMAP_QUOTA_CAPABILITIES, JmapService } from './jmap.service.js';
import type {
DownloadAttachmentPayload,
DownloadAttachmentResponse,
Email as JmapEmail,
Identity,
JmapQuota,
Mailbox as JmapMailbox,
JmapGetResponse,
JmapQueryResponse,
Expand Down Expand Up @@ -442,6 +444,24 @@ export class JmapMailProvider extends MailProvider {
return this.jmap.downloadAttachment(payload);
}

async getQuota(userEmail: string): Promise<MailQuota> {
const accountId = await this.jmap.getPrimaryAccountId(userEmail);

const response = await this.jmap.request<JmapGetResponse<JmapQuota>>(
userEmail,
[['Quota/get', { accountId }, 'r0']],
JMAP_QUOTA_CAPABILITIES,
);

const quotas = response.methodResponses[0]![1].list;
const bytesQuota = quotas.find((q) => q.resourceType === 'octets');

return {
used: bytesQuota?.used ?? 0,
limit: bytesQuota?.hardLimit ?? 0,
};
}

private async setKeyword(
userEmail: string,
id: string,
Expand Down
6 changes: 6 additions & 0 deletions src/modules/infrastructure/jmap/jmap.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ import type {
const JMAP_CAPABILITY_CORE = 'urn:ietf:params:jmap:core';
const JMAP_CAPABILITY_MAIL = 'urn:ietf:params:jmap:mail';
const JMAP_CAPABILITY_SUBMISSION = 'urn:ietf:params:jmap:submission';
export const JMAP_CAPABILITY_QUOTA = 'urn:ietf:params:jmap:quota';

const JMAP_MAIL_CAPABILITIES = [
JMAP_CAPABILITY_CORE,
JMAP_CAPABILITY_MAIL,
JMAP_CAPABILITY_SUBMISSION,
] as const;

export const JMAP_QUOTA_CAPABILITIES = [
JMAP_CAPABILITY_CORE,
JMAP_CAPABILITY_QUOTA,
] as const;

const SESSION_TTL_MS = 30_000;

interface CachedSession {
Expand Down
12 changes: 12 additions & 0 deletions src/modules/infrastructure/jmap/jmap.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,15 @@ export interface DeliveryStatus {
delivered: 'queued' | 'yes' | 'no' | 'unknown';
displayed: 'yes' | 'unknown';
}

export interface JmapQuota {
id: ID;
resourceType: string;
used: number;
hardLimit: number;
scope: string;
name: string;
description?: string;
warnLimit?: number;
softLimit?: number;
}
22 changes: 22 additions & 0 deletions test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
SearchEmailDto,
EncryptedWrappedKey,
EncryptionBlock,
} from '../src/modules/email/email.types.js';

Check warning on line 13 in test/fixtures.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'../src/modules/email/email.types.js' imported multiple times.

See more on https://sonarcloud.io/project/issues?id=internxt_mail-server&issues=AZ6Oc1SYJDVwm76J6sio&open=AZ6Oc1SYJDVwm76J6sio&pullRequest=57
import type { UserPayload } from '../src/modules/auth/jwt-payload.dto.js';
import {
type MailAccountAttributes,
Expand All @@ -33,8 +33,10 @@
EmailAddress as JmapEmailAddress,
MailboxRole,
Identity,
JmapQuota,
} from '../src/modules/infrastructure/jmap/jmap.types.js';
import type { DeepMocked } from '@golevelup/ts-vitest';
import type { MailQuota } from '../src/modules/email/email.types.js';

Check warning on line 39 in test/fixtures.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'../src/modules/email/email.types.js' imported multiple times.

See more on https://sonarcloud.io/project/issues?id=internxt_mail-server&issues=AZ6Oc1SYJDVwm76J6sip&open=AZ6Oc1SYJDVwm76J6sip&pullRequest=57

export type DeepPartial<T> =
T extends Array<infer U>
Expand Down Expand Up @@ -379,3 +381,23 @@
...attrs,
};
}

export function newJmapQuota(attrs?: Partial<JmapQuota>): JmapQuota {
return {
id: randomId(),
resourceType: 'octets',
used: random.natural({ min: 0, max: 1_000_000_000 }),
hardLimit: random.natural({ min: 1_000_000_000, max: 10_000_000_000 }),
scope: 'account',
name: 'Mail storage',
...attrs,
};
}

export function newMailQuota(attrs?: Partial<MailQuota>): MailQuota {
return {
used: random.natural({ min: 0, max: 1_000_000_000 }),
limit: random.natural({ min: 1_000_000_000, max: 10_000_000_000 }),
...attrs,
};
}
Loading