From 3c1ec3aa2f7d64932fd26c8cbcb1aee1e57861bd Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sun, 20 Oct 2024 17:36:02 +0000 Subject: chore: Define hoarder's rest API in zod format --- packages/open-api/lib/bookmarks.ts | 212 ++++++++++++++++++++++++++++++++++++ packages/open-api/lib/common.ts | 12 ++ packages/open-api/lib/lists.ts | 199 +++++++++++++++++++++++++++++++++ packages/open-api/lib/pagination.ts | 24 ++++ packages/open-api/lib/tags.ts | 139 +++++++++++++++++++++++ 5 files changed, 586 insertions(+) create mode 100644 packages/open-api/lib/bookmarks.ts create mode 100644 packages/open-api/lib/common.ts create mode 100644 packages/open-api/lib/lists.ts create mode 100644 packages/open-api/lib/pagination.ts create mode 100644 packages/open-api/lib/tags.ts (limited to 'packages/open-api/lib') diff --git a/packages/open-api/lib/bookmarks.ts b/packages/open-api/lib/bookmarks.ts new file mode 100644 index 00000000..28ef7e0d --- /dev/null +++ b/packages/open-api/lib/bookmarks.ts @@ -0,0 +1,212 @@ +import { + extendZodWithOpenApi, + OpenAPIRegistry, +} from "@asteasolutions/zod-to-openapi"; +import { z } from "zod"; + +import { + zBareBookmarkSchema, + zManipulatedTagSchema, + zNewBookmarkRequestSchema, + zUpdateBookmarksRequestSchema, +} from "@hoarder/shared/types/bookmarks"; + +import { BearerAuth } from "./common"; +import { + BookmarkSchema, + PaginatedBookmarksSchema, + PaginationSchema, +} from "./pagination"; +import { TagIdSchema } from "./tags"; + +export const registry = new OpenAPIRegistry(); +extendZodWithOpenApi(z); + +export const BookmarkIdSchema = registry.registerParameter( + "BookmarkId", + z.string().openapi({ + param: { + name: "bookmarkId", + in: "path", + }, + example: "ieidlxygmwj87oxz5hxttoc8", + }), +); + +registry.registerPath({ + method: "get", + path: "/bookmarks", + description: "Get all bookmarks", + summary: "Get all bookmarks", + security: [{ [BearerAuth.name]: [] }], + request: { + query: z + .object({ + archived: z.boolean().optional(), + favourited: z.boolean().optional(), + }) + .merge(PaginationSchema), + }, + responses: { + 200: { + description: "Object with all bookmarks data.", + content: { + "application/json": { + schema: PaginatedBookmarksSchema, + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "post", + path: "/bookmarks", + description: "Create a new bookmark", + summary: "Create a new bookmark", + security: [{ [BearerAuth.name]: [] }], + request: { + body: { + description: "The bookmark to create", + content: { + "application/json": { + schema: zNewBookmarkRequestSchema, + }, + }, + }, + }, + responses: { + 201: { + description: "The created bookmark", + content: { + "application/json": { + schema: BookmarkSchema, + }, + }, + }, + }, +}); +registry.registerPath({ + method: "get", + path: "/bookmarks/{bookmarkId}", + description: "Get bookmark by its id", + summary: "Get a single bookmark", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ bookmarkId: BookmarkIdSchema }), + }, + responses: { + 200: { + description: "Object with bookmark data.", + content: { + "application/json": { + schema: BookmarkSchema, + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "delete", + path: "/bookmarks/{bookmarkId}", + description: "Delete bookmark by its id", + summary: "Delete a bookmark", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ bookmarkId: BookmarkIdSchema }), + }, + responses: { + 204: { + description: "No content - the bookmark was deleted", + }, + }, +}); + +registry.registerPath({ + method: "patch", + path: "/bookmarks/{bookmarkId}", + description: "Update bookmark by its id", + summary: "Update a bookmark", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ bookmarkId: BookmarkIdSchema }), + body: { + description: + "The data to update. Only the fields you want to update need to be provided.", + content: { + "application/json": { + schema: zUpdateBookmarksRequestSchema.omit({ bookmarkId: true }), + }, + }, + }, + }, + responses: { + 200: { + description: "The updated bookmark", + content: { + "application/json": { + schema: zBareBookmarkSchema, + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "post", + path: "/bookmarks/{bookmarkId}/tags", + description: "Attach tags to a bookmark", + summary: "Attach tags to a bookmark", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ bookmarkId: BookmarkIdSchema }), + body: { + description: "The tags to attach.", + content: { + "application/json": { + schema: z.object({ tags: z.array(zManipulatedTagSchema) }), + }, + }, + }, + }, + responses: { + 200: { + description: "The list of attached tag ids", + content: { + "application/json": { + schema: z.object({ attached: z.array(TagIdSchema) }), + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "delete", + path: "/bookmarks/{bookmarkId}/tags", + description: "Detach tags from a bookmark", + summary: "Detach tags from a bookmark", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ bookmarkId: BookmarkIdSchema }), + body: { + description: "The tags to detach.", + content: { + "application/json": { + schema: z.object({ tags: z.array(zManipulatedTagSchema) }), + }, + }, + }, + }, + responses: { + 200: { + description: "The list of detached tag ids", + content: { + "application/json": { + schema: z.object({ detached: z.array(TagIdSchema) }), + }, + }, + }, + }, +}); diff --git a/packages/open-api/lib/common.ts b/packages/open-api/lib/common.ts new file mode 100644 index 00000000..d1ac43e1 --- /dev/null +++ b/packages/open-api/lib/common.ts @@ -0,0 +1,12 @@ +import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi"; + +export const registry = new OpenAPIRegistry(); +export const BearerAuth = registry.registerComponent( + "securitySchemes", + "bearerAuth", + { + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + }, +); diff --git a/packages/open-api/lib/lists.ts b/packages/open-api/lib/lists.ts new file mode 100644 index 00000000..27f458fc --- /dev/null +++ b/packages/open-api/lib/lists.ts @@ -0,0 +1,199 @@ +import { + extendZodWithOpenApi, + OpenAPIRegistry, +} from "@asteasolutions/zod-to-openapi"; +import { z } from "zod"; + +import { + zBookmarkListSchema, + zNewBookmarkListSchema, +} from "@hoarder/shared/types/lists"; + +import { BookmarkIdSchema } from "./bookmarks"; +import { BearerAuth } from "./common"; +import { PaginatedBookmarksSchema, PaginationSchema } from "./pagination"; + +export const registry = new OpenAPIRegistry(); +extendZodWithOpenApi(z); + +export const ListIdSchema = registry.registerParameter( + "ListId", + z.string().openapi({ + param: { + name: "listId", + in: "path", + }, + example: "ieidlxygmwj87oxz5hxttoc8", + }), +); + +export const ListSchema = zBookmarkListSchema.openapi("List"); + +registry.registerPath({ + method: "get", + path: "/lists", + description: "Get all lists", + summary: "Get all lists", + security: [{ [BearerAuth.name]: [] }], + request: {}, + responses: { + 200: { + description: "Object with all lists data.", + content: { + "application/json": { + schema: z.object({ + lists: z.array(ListSchema), + }), + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "post", + path: "/lists", + description: "Create a new list", + summary: "Create a new list", + security: [{ [BearerAuth.name]: [] }], + request: { + body: { + description: "The list to create", + content: { + "application/json": { + schema: zNewBookmarkListSchema, + }, + }, + }, + }, + responses: { + 201: { + description: "The created list", + content: { + "application/json": { + schema: ListSchema, + }, + }, + }, + }, +}); +registry.registerPath({ + method: "get", + path: "/lists/{listId}", + description: "Get list by its id", + summary: "Get a single list", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ listId: ListIdSchema }), + }, + responses: { + 200: { + description: "Object with list data.", + content: { + "application/json": { + schema: ListSchema, + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "delete", + path: "/lists/{listId}", + description: "Delete list by its id", + summary: "Delete a list", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ listId: ListIdSchema }), + }, + responses: { + 204: { + description: "No content - the bookmark was deleted", + }, + }, +}); + +registry.registerPath({ + method: "patch", + path: "/list/{listId}", + description: "Update list by its id", + summary: "Update a list", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ listId: ListIdSchema }), + body: { + description: + "The data to update. Only the fields you want to update need to be provided.", + content: { + "application/json": { + schema: zNewBookmarkListSchema.partial(), + }, + }, + }, + }, + responses: { + 200: { + description: "The updated list", + content: { + "application/json": { + schema: ListSchema, + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "get", + path: "/lists/{listId}/bookmarks", + description: "Get the bookmarks in a list", + summary: "Get a bookmarks in a list", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ listId: ListIdSchema }), + query: PaginationSchema, + }, + responses: { + 200: { + description: "Object with list data.", + content: { + "application/json": { + schema: PaginatedBookmarksSchema, + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "put", + path: "/lists/{listId}/bookmarks/{bookmarkId}", + description: "Add the bookmarks to a list", + summary: "Add a bookmark to a list", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ listId: ListIdSchema, bookmarkId: BookmarkIdSchema }), + }, + responses: { + 204: { + description: "No content - the bookmark was added", + }, + }, +}); + +registry.registerPath({ + method: "delete", + path: "/lists/{listId}/bookmarks/{bookmarkId}", + description: "Remove the bookmarks from a list", + summary: "Remove a bookmark from a list", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ listId: ListIdSchema, bookmarkId: BookmarkIdSchema }), + }, + responses: { + 204: { + description: "No content - the bookmark was added", + }, + }, +}); diff --git a/packages/open-api/lib/pagination.ts b/packages/open-api/lib/pagination.ts new file mode 100644 index 00000000..fe98cd62 --- /dev/null +++ b/packages/open-api/lib/pagination.ts @@ -0,0 +1,24 @@ +import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi"; +import { z } from "zod"; + +import { zBookmarkSchema } from "@hoarder/shared/types/bookmarks"; + +extendZodWithOpenApi(z); + +export const BookmarkSchema = zBookmarkSchema.openapi("Bookmark"); + +export const PaginatedBookmarksSchema = z + .object({ + bookmarks: z.array(BookmarkSchema), + nextCursor: z.string().nullable(), + }) + .openapi("PaginatedBookmarks"); + +export const CursorSchema = z.string().openapi("Cursor"); + +export const PaginationSchema = z + .object({ + limit: z.number().optional(), + cursor: CursorSchema.optional(), + }) + .openapi("Pagination"); diff --git a/packages/open-api/lib/tags.ts b/packages/open-api/lib/tags.ts new file mode 100644 index 00000000..e13b7c60 --- /dev/null +++ b/packages/open-api/lib/tags.ts @@ -0,0 +1,139 @@ +import { + extendZodWithOpenApi, + OpenAPIRegistry, +} from "@asteasolutions/zod-to-openapi"; +import { z } from "zod"; + +import { + zGetTagResponseSchema, + zUpdateTagRequestSchema, +} from "@hoarder/shared/types/tags"; + +import { BearerAuth } from "./common"; +import { PaginatedBookmarksSchema, PaginationSchema } from "./pagination"; + +export const registry = new OpenAPIRegistry(); +extendZodWithOpenApi(z); + +export const TagSchema = zGetTagResponseSchema.openapi("Tag"); + +export const TagIdSchema = registry.registerParameter( + "TagId", + z.string().openapi({ + param: { + name: "tagId", + in: "path", + }, + example: "ieidlxygmwj87oxz5hxttoc8", + }), +); + +registry.registerPath({ + method: "get", + path: "/tags", + description: "Get all tags", + summary: "Get all tags", + security: [{ [BearerAuth.name]: [] }], + request: {}, + responses: { + 200: { + description: "Object with all tags data.", + content: { + "application/json": { + schema: z.object({ + tags: z.array(TagSchema), + }), + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "get", + path: "/tags/{tagId}", + description: "Get tag by its id", + summary: "Get a single tag", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ tagId: TagIdSchema }), + }, + responses: { + 200: { + description: "Object with list data.", + content: { + "application/json": { + schema: TagSchema, + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "delete", + path: "/tags/{tagId}", + description: "Delete tag by its id", + summary: "Delete a tag", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ tagId: TagIdSchema }), + }, + responses: { + 204: { + description: "No content - the bookmark was deleted", + }, + }, +}); + +registry.registerPath({ + method: "patch", + path: "/tags/{tagId}", + description: "Update tag by its id", + summary: "Update a tag", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ tagId: TagIdSchema }), + body: { + description: + "The data to update. Only the fields you want to update need to be provided.", + content: { + "application/json": { + schema: zUpdateTagRequestSchema.omit({ tagId: true }), + }, + }, + }, + }, + responses: { + 200: { + description: "The updated tag", + content: { + "application/json": { + schema: TagSchema, + }, + }, + }, + }, +}); + +registry.registerPath({ + method: "get", + path: "/tags/{tagId}/bookmarks", + description: "Get the bookmarks with the tag", + summary: "Get a bookmarks with the tag", + security: [{ [BearerAuth.name]: [] }], + request: { + params: z.object({ tagId: TagIdSchema }), + query: PaginationSchema, + }, + responses: { + 200: { + description: "Object with list data.", + content: { + "application/json": { + schema: PaginatedBookmarksSchema, + }, + }, + }, + }, +}); -- cgit v1.2.3-70-g09d2