feat(auth): conversation_view preference, on by default
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
67d0c8aa5a
commit
997dd8848f
5 files changed
+38
-7
No files matched your search
@@ -0,0 +1 @@
|
||||
ALTER TABLE mailboxes DROP COLUMN IF EXISTS conversation_view;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Whether the webmail lists mail as conversations. On by default: it is what
|
||||
-- people coming from other mail programs expect, and it is one switch away.
|
||||
ALTER TABLE mailboxes ADD COLUMN conversation_view BOOLEAN NOT NULL DEFAULT true;
|
||||
@@ -1195,21 +1195,32 @@ export async function deleteAlias(scope: AdminScope, aliasId: string): Promise<b
|
||||
|
||||
// ------------------------------------------------ user preferences & sieve
|
||||
|
||||
export async function getUserPreferences(mailboxId: string): Promise<{ signature: string; auto_save_drafts: boolean }> {
|
||||
const row = await queryOne<{ signature: string | null; auto_save_drafts: boolean | null }>(
|
||||
`SELECT signature, auto_save_drafts FROM mailboxes WHERE id = $1`,
|
||||
export async function getUserPreferences(
|
||||
mailboxId: string,
|
||||
): Promise<{ signature: string; auto_save_drafts: boolean; conversation_view: boolean }> {
|
||||
const row = await queryOne<{ signature: string | null; auto_save_drafts: boolean | null; conversation_view: boolean | null }>(
|
||||
`SELECT signature, auto_save_drafts, conversation_view FROM mailboxes WHERE id = $1`,
|
||||
[mailboxId],
|
||||
);
|
||||
return {
|
||||
signature: row?.signature ?? "",
|
||||
auto_save_drafts: row?.auto_save_drafts ?? true,
|
||||
conversation_view: row?.conversation_view ?? true,
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateUserPreferences(mailboxId: string, signature: string, autoSaveDrafts: boolean): Promise<boolean> {
|
||||
/** conversationView is left as it is when the caller does not say. */
|
||||
export async function updateUserPreferences(
|
||||
mailboxId: string,
|
||||
signature: string,
|
||||
autoSaveDrafts: boolean,
|
||||
conversationView: boolean | null = null,
|
||||
): Promise<boolean> {
|
||||
const rows = await query<{ id: string }>(
|
||||
`UPDATE mailboxes SET signature = $2, auto_save_drafts = $3, updated_at = NOW() WHERE id = $1 RETURNING id`,
|
||||
[mailboxId, signature, autoSaveDrafts],
|
||||
`UPDATE mailboxes SET signature = $2, auto_save_drafts = $3,
|
||||
conversation_view = COALESCE($4::boolean, conversation_view), updated_at = NOW()
|
||||
WHERE id = $1 RETURNING id`,
|
||||
[mailboxId, signature, autoSaveDrafts, conversationView],
|
||||
);
|
||||
return rows.length === 1;
|
||||
}
|
||||
|
||||
@@ -1218,6 +1218,21 @@ describeDb("moving between the surfaces", () => {
|
||||
expect((await send("GET", "/api/v1/user/trusted-senders")).body.senders).toEqual([]);
|
||||
}, 60000);
|
||||
|
||||
it("lists conversations by default and remembers a switch back, without being reset by other saves", async () => {
|
||||
const prefs = async () =>
|
||||
(await send("GET", "/api/v1/user/preferences")).body as { conversation_view: boolean; signature: string };
|
||||
expect((await prefs()).conversation_view).toBe(true);
|
||||
|
||||
await send("POST", "/api/v1/user/preferences", { signature: "x", auto_save_drafts: true, conversation_view: false });
|
||||
expect((await prefs()).conversation_view).toBe(false);
|
||||
|
||||
// A save from an older screen that does not mention it must not flip it back.
|
||||
await send("POST", "/api/v1/user/preferences", { signature: "y", auto_save_drafts: true });
|
||||
const after = await prefs();
|
||||
expect(after.conversation_view).toBe(false);
|
||||
expect(after.signature).toBe("y");
|
||||
}, 60000);
|
||||
|
||||
it("refuses something that is not an address", async () => {
|
||||
expect((await send("PUT", "/api/v1/user/trusted-senders", { address: "not an address" })).status).toBe(400);
|
||||
expect((await send("PUT", "/api/v1/user/trusted-senders", {})).status).toBe(400);
|
||||
|
||||
@@ -1224,7 +1224,8 @@ async function userUpdatePreferences(req: IncomingMessage, res: ServerResponse,
|
||||
const body = await parseJsonBody(req);
|
||||
const signature = typeof body?.signature === "string" ? body.signature : "";
|
||||
const autoSaveDrafts = body?.auto_save_drafts !== false;
|
||||
await repo.updateUserPreferences(session.sub, signature, autoSaveDrafts);
|
||||
const conversationView = typeof body?.conversation_view === "boolean" ? body.conversation_view : null;
|
||||
await repo.updateUserPreferences(session.sub, signature, autoSaveDrafts, conversationView);
|
||||
sendJson(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user