Skip to content
Merged
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
111 changes: 111 additions & 0 deletions src/stores/accounts/accounts-database.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,37 @@ describe("accounts-database", () => {
expect(parsed.accountComments).toHaveLength(1);
expect(parsed.accountComments[0].cid).toBe("ec1");
});

test("compacts legacy stored accountComments before export", async () => {
const acc = makeAccount({ id: "export-legacy-comments", name: "ExportLegacyComments" });
await accountsDatabase.addAccount(acc);
const commentsDb = createPerAccountDatabase("accountComments", acc.id);
await commentsDb.setItem("0", {
cid: "legacy-comment",
content: "legacy",
communityAddress: "sub",
timestamp: 1,
author: { address: "addr" },
replies: {
pages: {
best: {
comments: [{ cid: "reply-1", content: "reply" }],
},
},
pageCids: { best: "page-1" },
},
});
await commentsDb.setItem("length", 1);

const exported = JSON.parse(await accountsDatabase.getExportedAccountJson(acc.id));
const storedComment = await commentsDb.getItem<any>("0");

expect(exported.accountComments[0].replies?.pages).toBeUndefined();
expect(exported.accountComments[0].replies?.pageCids).toEqual({ best: "page-1" });
expect(storedComment.replies?.pages).toBeUndefined();
expect(storedComment.replies?.pageCids).toEqual({ best: "page-1" });
expect(await commentsDb.getItem("__storageVersion")).toBe(1);
});
});

describe("account comments", () => {
Expand Down Expand Up @@ -809,6 +840,86 @@ describe("accounts-database", () => {
expect(exported.accountComments[0].replies?.pages).toBeUndefined();
});

test("getAccountComments compacts legacy stored comments on read", async () => {
const acc = makeAccount({ id: "legacy-read-comments", name: "LegacyReadComments" });
await accountsDatabase.addAccount(acc);
const commentsDb = createPerAccountDatabase("accountComments", acc.id);
await commentsDb.setItem("0", {
cid: "legacy-read-comment",
content: "legacy",
communityAddress: "sub",
timestamp: 1,
author: { address: "addr" },
replies: {
pages: {
best: {
comments: [{ cid: "reply-1", content: "reply" }],
},
},
pageCids: { best: "page-1" },
},
});
await commentsDb.setItem("length", 1);

const comments = await accountsDatabase.getAccountComments(acc.id);
const storedComment = await commentsDb.getItem<any>("0");

expect(comments[0].replies?.pages).toBeUndefined();
expect(comments[0].replies?.pageCids).toEqual({ best: "page-1" });
expect(storedComment.replies?.pages).toBeUndefined();
expect(storedComment.replies?.pageCids).toEqual({ best: "page-1" });
expect(await commentsDb.getItem("__storageVersion")).toBe(1);
});

test("deleteAccountComment compacts legacy stored comments before mutating", async () => {
const acc = makeAccount({ id: "legacy-delete-comments", name: "LegacyDeleteComments" });
await accountsDatabase.addAccount(acc);
const commentsDb = createPerAccountDatabase("accountComments", acc.id);
await commentsDb.setItem("0", {
cid: "legacy-delete-comment-1",
content: "legacy-1",
communityAddress: "sub",
timestamp: 1,
author: { address: "addr" },
replies: {
pages: {
best: {
comments: [{ cid: "reply-1", content: "reply" }],
},
},
pageCids: { best: "page-1" },
},
});
await commentsDb.setItem("1", {
cid: "legacy-delete-comment-2",
content: "legacy-2",
communityAddress: "sub",
timestamp: 2,
author: { address: "addr" },
replies: {
pages: {
best: {
comments: [{ cid: "reply-2", content: "reply" }],
},
},
pageCids: { best: "page-2" },
},
});
await commentsDb.setItem("length", 2);

await accountsDatabase.deleteAccountComment(acc.id, 0);
const storedComment = await commentsDb.getItem<any>("0");
const exported = JSON.parse(await accountsDatabase.getExportedAccountJson(acc.id));

expect(storedComment.cid).toBe("legacy-delete-comment-2");
expect(storedComment.replies?.pages).toBeUndefined();
expect(storedComment.replies?.pageCids).toEqual({ best: "page-2" });
expect(exported.accountComments).toHaveLength(1);
expect(exported.accountComments[0].replies?.pages).toBeUndefined();
expect(exported.accountComments[0].replies?.pageCids).toEqual({ best: "page-2" });
expect(await commentsDb.getItem("__storageVersion")).toBe(1);
});

test("addAccountComment asserts accountCommentIndex < length", async () => {
const acc = makeAccount({ id: "edit-assert", name: "EditAssert" });
await accountsDatabase.addAccount(acc);
Expand Down
43 changes: 42 additions & 1 deletion src/stores/accounts/accounts-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const storageVersionKey = "__storageVersion";
const votesLatestIndexKey = "__commentCidToLatestIndex";
const editsTargetToIndicesKey = "__targetToIndices";
const editsSummaryKey = "__summary";
const commentStorageVersion = 1;
const voteStorageVersion = 1;
const editStorageVersion = 1;

Expand Down Expand Up @@ -187,6 +188,7 @@ const getExportedAccountJson = async (accountId: string) => {
const accountCommentsDatabase = getAccountCommentsDatabase(accountId);
const accountVotesDatabase = getAccountVotesDatabase(accountId);
const accountEditsDatabase = getAccountEditsDatabase(accountId);
await ensureAccountCommentsDatabaseLayout(accountId);
const [accountComments, accountVotes, accountEdits] = await Promise.all([
getDatabaseAsArray(accountCommentsDatabase),
getDatabaseAsArray(accountVotesDatabase),
Expand Down Expand Up @@ -348,6 +350,7 @@ const removeAccount = async (account: Account) => {
};

const accountsCommentsDatabases: any = {};
const accountCommentsLayoutMigrations: Record<string, Promise<void> | undefined> = {};
const getAccountCommentsDatabase = (accountId: string) => {
assert(
accountId && typeof accountId === "string",
Expand All @@ -361,8 +364,40 @@ const getAccountCommentsDatabase = (accountId: string) => {
return accountsCommentsDatabases[accountId];
};

const ensureAccountCommentsDatabaseLayout = async (accountId: string) => {
const accountCommentsDatabase = getAccountCommentsDatabase(accountId);
if ((await accountCommentsDatabase.getItem(storageVersionKey)) === commentStorageVersion) {
return;
}
if (!accountCommentsLayoutMigrations[accountId]) {
accountCommentsLayoutMigrations[accountId] = (async () => {
if ((await accountCommentsDatabase.getItem(storageVersionKey)) === commentStorageVersion) {
return;
}

const comments = await getDatabaseAsArray(accountCommentsDatabase);
const updatedComments = comments.map((comment) =>
comment ? sanitizeStoredAccountComment(comment) : comment,
);
const rewritePromises: Promise<void>[] = [];
for (const [index, updatedComment] of updatedComments.entries()) {
if (!isEqual(updatedComment, comments[index])) {
rewritePromises.push(accountCommentsDatabase.setItem(String(index), updatedComment));
}
}
await Promise.all(rewritePromises);
await accountCommentsDatabase.setItem(storageVersionKey, commentStorageVersion);
})().finally(() => {
delete accountCommentsLayoutMigrations[accountId];
});
}

await accountCommentsLayoutMigrations[accountId];
};

const deleteAccountComment = async (accountId: string, accountCommentIndex: number) => {
const accountCommentsDatabase = getAccountCommentsDatabase(accountId);
await ensureAccountCommentsDatabaseLayout(accountId);
const length = (await accountCommentsDatabase.getItem("length")) || 0;
assert(
accountCommentIndex >= 0 && accountCommentIndex < length,
Expand All @@ -386,24 +421,30 @@ const addAccountComment = async (
accountCommentIndex?: number,
) => {
const accountCommentsDatabase = getAccountCommentsDatabase(accountId);
await ensureAccountCommentsDatabaseLayout(accountId);
const length = (await accountCommentsDatabase.getItem("length")) || 0;
comment = sanitizeStoredAccountComment(comment);
if (typeof accountCommentIndex === "number") {
assert(
accountCommentIndex < length,
`addAccountComment cannot edit comment no comment in database at accountCommentIndex '${accountCommentIndex}'`,
);
await accountCommentsDatabase.setItem(String(accountCommentIndex), comment);
await Promise.all([
accountCommentsDatabase.setItem(String(accountCommentIndex), comment),
accountCommentsDatabase.setItem(storageVersionKey, commentStorageVersion),
]);
} else {
await Promise.all([
accountCommentsDatabase.setItem(String(length), comment),
accountCommentsDatabase.setItem(storageVersionKey, commentStorageVersion),
accountCommentsDatabase.setItem("length", length + 1),
]);
}
};

const getAccountComments = async (accountId: string) => {
const accountCommentsDatabase = getAccountCommentsDatabase(accountId);
await ensureAccountCommentsDatabaseLayout(accountId);
const length = (await accountCommentsDatabase.getItem("length")) || 0;
if (length === 0) {
return [];
Expand Down
Loading