Skip to content

Commit f9e0658

Browse files
authored
Merge pull request #830 from elie222/fix/folder-exists-outlook
2 parents 5a75e16 + 163e0eb commit f9e0658

File tree

4 files changed

+50
-24
lines changed

4 files changed

+50
-24
lines changed

apps/web/utils/outlook/errors.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Helper functions for checking Outlook API errors
2+
3+
/**
4+
* Check if an error indicates that a resource already exists
5+
* (e.g., folder, filter, category, etc.)
6+
*/
7+
export function isAlreadyExistsError(error: unknown): boolean {
8+
// biome-ignore lint/suspicious/noExplicitAny: simplest
9+
const errorMessage = (error as any)?.message || "";
10+
return (
11+
errorMessage.includes("already exists") ||
12+
errorMessage.includes("duplicate") ||
13+
errorMessage.includes("conflict")
14+
);
15+
}

apps/web/utils/outlook/filter.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
OutlookCategory,
55
} from "@microsoft/microsoft-graph-types";
66
import { createScopedLogger } from "@/utils/logger";
7+
import { isAlreadyExistsError } from "./errors";
78

89
const logger = createScopedLogger("outlook/filter");
910

@@ -42,7 +43,7 @@ export async function createFilter(options: {
4243

4344
return { status: 201, data: response };
4445
} catch (error) {
45-
if (isFilterExistsError(error)) {
46+
if (isAlreadyExistsError(error)) {
4647
logger.warn("Filter already exists", { from });
4748
return { status: 200 };
4849
}
@@ -82,7 +83,7 @@ export async function createAutoArchiveFilter({
8283

8384
return { status: 201, data: response };
8485
} catch (error) {
85-
if (isFilterExistsError(error)) {
86+
if (isAlreadyExistsError(error)) {
8687
logger.warn("Auto-archive filter already exists", { from });
8788
return { status: 200 };
8889
}
@@ -127,17 +128,6 @@ export async function getFiltersList(options: { client: OutlookClient }) {
127128
}
128129
}
129130

130-
// Helper function to check if a filter already exists
131-
function isFilterExistsError(error: unknown) {
132-
// biome-ignore lint/suspicious/noExplicitAny: simplest
133-
const errorMessage = (error as any)?.message || "";
134-
return (
135-
errorMessage.includes("already exists") ||
136-
errorMessage.includes("duplicate") ||
137-
errorMessage.includes("conflict")
138-
);
139-
}
140-
141131
// Additional helper functions for Outlook-specific operations
142132

143133
export async function createCategoryFilter({
@@ -188,7 +178,7 @@ export async function createCategoryFilter({
188178
"Category created. Note: Categories must be applied to individual messages.",
189179
};
190180
} catch (error) {
191-
if (isFilterExistsError(error)) {
181+
if (isAlreadyExistsError(error)) {
192182
logger.warn("Category filter already exists", { from, categoryName });
193183
return { status: 200 };
194184
}

apps/web/utils/outlook/folders.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { MailFolder } from "@microsoft/microsoft-graph-types";
22
import type { OutlookClient } from "./client";
33
import { createScopedLogger } from "@/utils/logger";
4+
import { isAlreadyExistsError } from "./errors";
45

56
const logger = createScopedLogger("outlook/folders");
67

@@ -56,6 +57,14 @@ export async function getOutlookChildFolders(
5657
return response.value.map(convertMailFolderToOutlookFolder);
5758
}
5859

60+
async function findOutlookFolderByName(
61+
client: OutlookClient,
62+
folderName: string,
63+
): Promise<OutlookFolder | undefined> {
64+
const folders = await getOutlookRootFolders(client);
65+
return folders.find((folder) => folder.displayName === folderName);
66+
}
67+
5968
export async function getOutlookFolderTree(
6069
client: OutlookClient,
6170
expandLevels = 6,
@@ -97,18 +106,30 @@ export async function getOrCreateOutlookFolderIdByName(
97106
client: OutlookClient,
98107
folderName: string,
99108
): Promise<string> {
100-
const folders = await getOutlookRootFolders(client);
101-
const existingFolder = folders.find(
102-
(folder) => folder.displayName === folderName,
103-
);
109+
const existingFolder = await findOutlookFolderByName(client, folderName);
104110

105111
if (existingFolder) {
106112
return existingFolder.id;
107113
}
108114

109-
const response = await client.getClient().api("/me/mailFolders").post({
110-
displayName: folderName,
111-
});
112-
113-
return response.id;
115+
try {
116+
const response = await client.getClient().api("/me/mailFolders").post({
117+
displayName: folderName,
118+
});
119+
120+
return response.id;
121+
} catch (error) {
122+
// If folder already exists (race condition or created between check and create),
123+
// fetch folders again and return the existing folder ID
124+
if (isAlreadyExistsError(error)) {
125+
logger.info("Folder already exists, fetching existing folder", {
126+
folderName,
127+
});
128+
const folder = await findOutlookFolderByName(client, folderName);
129+
if (folder) {
130+
return folder.id;
131+
}
132+
}
133+
throw error;
134+
}
114135
}

version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v2.13.6
1+
v2.13.7

0 commit comments

Comments
 (0)