aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/api/routes/tags.ts12
-rw-r--r--packages/e2e_tests/tests/api/tags.test.ts27
-rw-r--r--packages/open-api/karakeep-openapi-spec.json69
-rw-r--r--packages/open-api/lib/tags.ts33
-rw-r--r--packages/sdk/src/karakeep-api.d.ts40
-rw-r--r--packages/shared/types/tags.ts4
-rw-r--r--packages/trpc/routers/tags.ts36
7 files changed, 172 insertions, 49 deletions
diff --git a/packages/api/routes/tags.ts b/packages/api/routes/tags.ts
index 6d4cf39d..816e58b4 100644
--- a/packages/api/routes/tags.ts
+++ b/packages/api/routes/tags.ts
@@ -1,7 +1,10 @@
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
-import { zUpdateTagRequestSchema } from "@karakeep/shared/types/tags";
+import {
+ zCreateTagRequestSchema,
+ zUpdateTagRequestSchema,
+} from "@karakeep/shared/types/tags";
import { authMiddleware } from "../middlewares/auth";
import { adaptPagination, zPagination } from "../utils/pagination";
@@ -16,6 +19,13 @@ const app = new Hono()
return c.json(tags, 200);
})
+ // POST /tags
+ .post("/", zValidator("json", zCreateTagRequestSchema), async (c) => {
+ const body = c.req.valid("json");
+ const tags = await c.var.api.tags.create(body);
+ return c.json(tags, 201);
+ })
+
// GET /tags/[tagId]
.get("/:tagId", async (c) => {
const tagId = c.req.param("tagId");
diff --git a/packages/e2e_tests/tests/api/tags.test.ts b/packages/e2e_tests/tests/api/tags.test.ts
index 3e3cacc0..6c387628 100644
--- a/packages/e2e_tests/tests/api/tags.test.ts
+++ b/packages/e2e_tests/tests/api/tags.test.ts
@@ -26,31 +26,16 @@ describe("Tags API", () => {
});
it("should get, update and delete a tag", async () => {
- // Create a bookmark first
- const { data: createdBookmark } = await client.POST("/bookmarks", {
+ // Create a tag by attaching it to the bookmark
+ const { data: tag } = await client.POST("/tags", {
body: {
- type: "text",
- title: "Test Bookmark",
- text: "This is a test bookmark",
+ name: "Test Tag",
},
});
+ expect(tag).toBeDefined();
+ expect(tag!.name).toBe("Test Tag");
- // Create a tag by attaching it to the bookmark
- const { data: addTagResponse } = await client.POST(
- "/bookmarks/{bookmarkId}/tags",
- {
- params: {
- path: {
- bookmarkId: createdBookmark!.id,
- },
- },
- body: {
- tags: [{ tagName: "Test Tag" }],
- },
- },
- );
-
- const tagId = addTagResponse!.attached[0];
+ const tagId = tag!.id;
// Get the tag
const { data: retrievedTag, response: getResponse } = await client.GET(
diff --git a/packages/open-api/karakeep-openapi-spec.json b/packages/open-api/karakeep-openapi-spec.json
index dbc2e5d0..15fa246b 100644
--- a/packages/open-api/karakeep-openapi-spec.json
+++ b/packages/open-api/karakeep-openapi-spec.json
@@ -2240,6 +2240,61 @@
}
}
}
+ },
+ "post": {
+ "description": "Create a new tag",
+ "summary": "Create a new tag",
+ "tags": [
+ "Tags"
+ ],
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "requestBody": {
+ "description": "The data to create the tag with.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "name"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "The created tag",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ]
+ }
+ }
+ }
+ }
+ }
}
},
"/tags/{tagId}": {
@@ -2375,7 +2430,19 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Tag"
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ]
}
}
}
diff --git a/packages/open-api/lib/tags.ts b/packages/open-api/lib/tags.ts
index b8136741..0a4f62cb 100644
--- a/packages/open-api/lib/tags.ts
+++ b/packages/open-api/lib/tags.ts
@@ -6,7 +6,9 @@ import { z } from "zod";
import { zSortOrder } from "@karakeep/shared/types/bookmarks";
import {
+ zCreateTagRequestSchema,
zGetTagResponseSchema,
+ zTagBasicSchema,
zUpdateTagRequestSchema,
} from "@karakeep/shared/types/tags";
@@ -57,6 +59,35 @@ registry.registerPath({
});
registry.registerPath({
+ method: "post",
+ path: "/tags",
+ description: "Create a new tag",
+ summary: "Create a new tag",
+ tags: ["Tags"],
+ security: [{ [BearerAuth.name]: [] }],
+ request: {
+ body: {
+ description: "The data to create the tag with.",
+ content: {
+ "application/json": {
+ schema: zCreateTagRequestSchema,
+ },
+ },
+ },
+ },
+ responses: {
+ 201: {
+ description: "The created tag",
+ content: {
+ "application/json": {
+ schema: zTagBasicSchema,
+ },
+ },
+ },
+ },
+});
+
+registry.registerPath({
method: "get",
path: "/tags/{tagId}",
description: "Get tag by its id",
@@ -135,7 +166,7 @@ registry.registerPath({
description: "The updated tag",
content: {
"application/json": {
- schema: TagSchema,
+ schema: zTagBasicSchema,
},
},
},
diff --git a/packages/sdk/src/karakeep-api.d.ts b/packages/sdk/src/karakeep-api.d.ts
index 1fa63dac..e8c048c4 100644
--- a/packages/sdk/src/karakeep-api.d.ts
+++ b/packages/sdk/src/karakeep-api.d.ts
@@ -1158,7 +1158,40 @@ export interface paths {
};
};
put?: never;
- post?: never;
+ /**
+ * Create a new tag
+ * @description Create a new tag
+ */
+ post: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** @description The data to create the tag with. */
+ requestBody?: {
+ content: {
+ "application/json": {
+ name: string;
+ };
+ };
+ };
+ responses: {
+ /** @description The created tag */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ id: string;
+ name: string;
+ };
+ };
+ };
+ };
+ };
delete?: never;
options?: never;
head?: never;
@@ -1278,7 +1311,10 @@ export interface paths {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["Tag"];
+ "application/json": {
+ id: string;
+ name: string;
+ };
};
};
/** @description Tag not found */
diff --git a/packages/shared/types/tags.ts b/packages/shared/types/tags.ts
index c2d8b28d..c7a0103e 100644
--- a/packages/shared/types/tags.ts
+++ b/packages/shared/types/tags.ts
@@ -1,5 +1,9 @@
import { z } from "zod";
+export const zCreateTagRequestSchema = z.object({
+ name: z.string().min(1),
+});
+
export const zAttachedByEnumSchema = z.enum(["ai", "human"]);
export type ZAttachedByEnum = z.infer<typeof zAttachedByEnumSchema>;
export const zBookmarkTagSchema = z.object({
diff --git a/packages/trpc/routers/tags.ts b/packages/trpc/routers/tags.ts
index 7f75c16e..cade4b45 100644
--- a/packages/trpc/routers/tags.ts
+++ b/packages/trpc/routers/tags.ts
@@ -7,7 +7,9 @@ import { SqliteError } from "@karakeep/db";
import { bookmarkTags, tagsOnBookmarks } from "@karakeep/db/schema";
import { triggerSearchReindex } from "@karakeep/shared/queues";
import {
+ zCreateTagRequestSchema,
zGetTagResponseSchema,
+ zTagBasicSchema,
zUpdateTagRequestSchema,
} from "@karakeep/shared/types/tags";
@@ -53,19 +55,8 @@ export const ensureTagOwnership = experimental_trpcMiddleware<{
export const tagsAppRouter = router({
create: authedProcedure
- .input(
- z.object({
- name: z.string().min(1), // Ensure the name is provided and not empty
- }),
- )
- .output(
- z.object({
- id: z.string(),
- name: z.string(),
- userId: z.string(),
- createdAt: z.date(),
- }),
- )
+ .input(zCreateTagRequestSchema)
+ .output(zTagBasicSchema)
.mutation(async ({ input, ctx }) => {
try {
const [newTag] = await ctx.db
@@ -76,7 +67,10 @@ export const tagsAppRouter = router({
})
.returning();
- return newTag;
+ return {
+ id: newTag.id,
+ name: newTag.name,
+ };
} catch (e) {
if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") {
throw new TRPCError({
@@ -195,14 +189,7 @@ export const tagsAppRouter = router({
}),
update: authedProcedure
.input(zUpdateTagRequestSchema)
- .output(
- z.object({
- id: z.string(),
- name: z.string(),
- userId: z.string(),
- createdAt: z.date(),
- }),
- )
+ .output(zTagBasicSchema)
.use(ensureTagOwnership)
.mutation(async ({ input, ctx }) => {
try {
@@ -242,7 +229,10 @@ export const tagsAppRouter = router({
console.error("Failed to reindex affected bookmarks", e);
}
- return res[0];
+ return {
+ id: res[0].id,
+ name: res[0].name,
+ };
} catch (e) {
if (e instanceof SqliteError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") {