Skip to content

Commit

Permalink
feature(cli): Allow updating tags/lists from CLI (#211)
Browse files Browse the repository at this point in the history
* Improve the CLI #209
added the possibility to assign tags to bookmarks while creating
added the possibility to assign a newly created to a list right away
added the possibility to add and remove tags from bookmarks

* minor tweaks

---------

Co-authored-by: MohamedBassem <[email protected]>
  • Loading branch information
kamtschatka and MohamedBassem authored Jul 27, 2024
1 parent eb0a28e commit 5e4decb
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 47 deletions.
4 changes: 2 additions & 2 deletions apps/browser-extension/src/components/TagsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export function TagsSelector({ bookmarkId }: { bookmarkId: string }) {
const { mutate } = useUpdateBookmarkTags({
onMutate: (req) => {
req.attach.forEach((t) => currentlyUpdating.add(t.tagId ?? ""));
req.detach.forEach((t) => currentlyUpdating.add(t.tagId));
req.detach.forEach((t) => currentlyUpdating.add(t.tagId ?? ""));
},
onSettled: (_resp, _err, req) => {
if (!req) {
return;
}
req.attach.forEach((t) => currentlyUpdating.delete(t.tagId ?? ""));
req.detach.forEach((t) => currentlyUpdating.delete(t.tagId));
req.detach.forEach((t) => currentlyUpdating.delete(t.tagId ?? ""));
},
});

Expand Down
108 changes: 97 additions & 11 deletions apps/cli/src/commands/bookmarks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as fs from "node:fs";
import { addToList } from "@/commands/lists";
import {
printError,
printObject,
Expand All @@ -23,7 +24,11 @@ function collect<T>(val: T, acc: T[]) {
return acc;
}

function normalizeBookmark(bookmark: ZBookmark) {
type Bookmark = Omit<ZBookmark, "tags"> & {
tags: string[];
};

function normalizeBookmark(bookmark: ZBookmark): Bookmark {
const ret = {
...bookmark,
tags: bookmark.tags.map((t) => t.name),
Expand Down Expand Up @@ -58,10 +63,20 @@ bookmarkCmd
[],
)
.option("--stdin", "reads the data from stdin and store it as a note")
.option(
"--list-id <id>",
"if set, the bookmark(s) will be added to this list",
)
.option(
"--tag-name <tag>",
"if set, this tag will be added to the bookmark(s). Specify multiple times to add multiple tags",
collect<string>,
[],
)
.action(async (opts) => {
const api = getAPIClient();

const results: object[] = [];
const results: Bookmark[] = [];

const promises = [
...opts.link.map((url) =>
Expand Down Expand Up @@ -104,6 +119,13 @@ bookmarkCmd

await Promise.allSettled(promises);
printObject(results);

await Promise.allSettled(
results.flatMap((r) => [
updateTags(opts.tagName, [], r.id),
opts.listId ? addToList(opts.listId, r.id) : Promise.resolve(),
]),
);
});

bookmarkCmd
Expand All @@ -118,6 +140,48 @@ bookmarkCmd
.catch(printError(`Failed to get the bookmark with id "${id}"`));
});

function printTagMessage(
tags: { tagName: string }[],
bookmarkId: string,
action: "Added" | "Removed",
) {
tags.forEach((tag) => {
printStatusMessage(
true,
`${action} the tag ${tag.tagName} ${action === "Added" ? "to" : "from"} the bookmark with id ${bookmarkId}`,
);
});
}

async function updateTags(addTags: string[], removeTags: string[], id: string) {
const tagsToAdd = addTags.map((addTag) => {
return { tagName: addTag };
});

const tagsToRemove = removeTags.map((removeTag) => {
return { tagName: removeTag };
});

if (tagsToAdd.length > 0 || tagsToRemove.length > 0) {
const api = getAPIClient();
await api.bookmarks.updateTags
.mutate({
bookmarkId: id,
attach: tagsToAdd,
detach: tagsToRemove,
})
.then(() => {
printTagMessage(tagsToAdd, id, "Added");
printTagMessage(tagsToRemove, id, "Removed");
})
.catch(
printError(
`Failed to add/remove tags to/from bookmark with id "${id}"`,
),
);
}
}

bookmarkCmd
.command("update")
.description("update a bookmark")
Expand All @@ -127,18 +191,40 @@ bookmarkCmd
.option("--no-archive", "if set, the bookmark will be unarchived")
.option("--favourite", "if set, the bookmark will be favourited")
.option("--no-favourite", "if set, the bookmark will be unfavourited")
.option(
"--add-tag <tag>",
"if set, this tag will be added to the bookmark. Specify multiple times to add multiple tags",
collect<string>,
[],
)
.option(
"--remove-tag <tag>",
"if set, this tag will be removed from the bookmark. Specify multiple times to remove multiple tags",
collect<string>,
[],
)
.argument("<id>", "the id of the bookmark to get")
.action(async (id, opts) => {
const api = getAPIClient();
await api.bookmarks.updateBookmark
.mutate({
bookmarkId: id,
archived: opts.archive,
favourited: opts.favourite,
title: opts.title,
})
.then(printObject)
.catch(printError(`Failed to update bookmark with id "${id}"`));
await updateTags(opts.addTag, opts.removeTag, id);

if (
"archive" in opts ||
"favourite" in opts ||
"title" in opts ||
"note" in opts
) {
await api.bookmarks.updateBookmark
.mutate({
bookmarkId: id,
archived: opts.archive,
favourited: opts.favourite,
title: opts.title,
note: opts.note,
})
.then(printObject)
.catch(printError(`Failed to update bookmark with id "${id}"`));
}
});

bookmarkCmd
Expand Down
38 changes: 21 additions & 17 deletions apps/cli/src/commands/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,33 @@ listsCmd
.catch(printError(`Failed to delete list with id "${id}"`));
});

export async function addToList(listId: string, bookmarkId: string) {
const api = getAPIClient();

await api.lists.addToList
.mutate({
listId,
bookmarkId,
})
.then(
printSuccess(
`Successfully added bookmark "${bookmarkId}" to list with id "${listId}"`,
),
)
.catch(
printError(
`Failed to add bookmark "${bookmarkId}" to list with id "${listId}"`,
),
);
}

listsCmd
.command("add-bookmark")
.description("add a bookmark to list")
.requiredOption("--list <id>", "the id of the list")
.requiredOption("--bookmark <bookmark>", "the id of the bookmark")
.action(async (opts) => {
const api = getAPIClient();

await api.lists.addToList
.mutate({
listId: opts.list,
bookmarkId: opts.bookmark,
})
.then(
printSuccess(
`Successfully added bookmark "${opts.bookmark}" to list with id "${opts.list}"`,
),
)
.catch(
printError(
`Failed to add bookmark "${opts.bookmark}" to list with id "${opts.list}"`,
),
);
await addToList(opts.list, opts.bookmark);
});

listsCmd
Expand Down
24 changes: 19 additions & 5 deletions packages/trpc/routers/bookmarks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,23 +126,37 @@ describe("Bookmark Routes", () => {

await api.updateTags({
bookmarkId: createdBookmark.id,
attach: [{ tagName: "tag1" }, { tagName: "tag2" }],
attach: [
{ tagName: "tag1" },
{ tagName: "tag2" },
{ tagName: "tag3" },
{ tagName: "tag4" },
],
detach: [],
});

let bookmark = await api.getBookmark({ bookmarkId: createdBookmark.id });
expect(bookmark.tags.map((t) => t.name).sort()).toEqual(["tag1", "tag2"]);
expect(bookmark.tags.map((t) => t.name).sort()).toEqual([
"tag1",
"tag2",
"tag3",
"tag4",
]);

const tag1Id = bookmark.tags.filter((t) => t.name == "tag1")[0].id;

await api.updateTags({
bookmarkId: bookmark.id,
attach: [{ tagName: "tag3" }],
detach: [{ tagId: tag1Id }],
attach: [{ tagName: "tag5" }],
detach: [{ tagId: tag1Id }, { tagName: "tag4" }],
});

bookmark = await api.getBookmark({ bookmarkId: bookmark.id });
expect(bookmark.tags.map((t) => t.name).sort()).toEqual(["tag2", "tag3"]);
expect(bookmark.tags.map((t) => t.name).sort()).toEqual([
"tag2",
"tag3",
"tag5",
]);

await api.updateTags({
bookmarkId: bookmark.id,
Expand Down
55 changes: 43 additions & 12 deletions packages/trpc/routers/bookmarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,8 +665,13 @@ export const bookmarksAppRouter = router({
tagName: z.string().optional(),
}),
),
// Detach by tag ids
detach: z.array(z.object({ tagId: z.string() })),
detach: z.array(
z.object({
// At least one of the two must be set
tagId: z.string().optional(),
tagName: z.string().optional(), // Also allow removing by tagName, to make CLI usage easier
}),
),
}),
)
.output(
Expand All @@ -679,23 +684,49 @@ export const bookmarksAppRouter = router({
.mutation(async ({ input, ctx }) => {
return ctx.db.transaction(async (tx) => {
// Detaches
const idsToRemove: string[] = [];
if (input.detach.length > 0) {
await tx.delete(tagsOnBookmarks).where(
and(
eq(tagsOnBookmarks.bookmarkId, input.bookmarkId),
inArray(
tagsOnBookmarks.tagId,
input.detach.map((t) => t.tagId),
const namesToRemove: string[] = [];
input.detach.forEach((detachInfo) => {
if (detachInfo.tagId) {
idsToRemove.push(detachInfo.tagId);
}
if (detachInfo.tagName) {
namesToRemove.push(detachInfo.tagName);
}
});

if (namesToRemove.length > 0) {
(
await tx.query.bookmarkTags.findMany({
where: and(
eq(bookmarkTags.userId, ctx.user.id),
inArray(bookmarkTags.name, namesToRemove),
),
columns: {
id: true,
},
})
).forEach((tag) => {
idsToRemove.push(tag.id);
});
}

await tx
.delete(tagsOnBookmarks)
.where(
and(
eq(tagsOnBookmarks.bookmarkId, input.bookmarkId),
inArray(tagsOnBookmarks.tagId, idsToRemove),
),
),
);
);
}

if (input.attach.length == 0) {
return {
bookmarkId: input.bookmarkId,
attached: [],
detached: input.detach.map((t) => t.tagId),
detached: idsToRemove,
};
}

Expand Down Expand Up @@ -751,7 +782,7 @@ export const bookmarksAppRouter = router({
return {
bookmarkId: input.bookmarkId,
attached: allIds,
detached: input.detach.map((t) => t.tagId),
detached: idsToRemove,
};
});
}),
Expand Down

0 comments on commit 5e4decb

Please sign in to comment.