From b61141dbf32f62e618e14e9cf185948a2121376d Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Tue, 9 Jul 2024 13:54:24 +0530 Subject: [PATCH 01/40] feat: create schema for share --- src/server/api/schema/shares.ts | 130 ++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/server/api/schema/shares.ts diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts new file mode 100644 index 000000000..e733a609d --- /dev/null +++ b/src/server/api/schema/shares.ts @@ -0,0 +1,130 @@ +import { z } from "@hono/zod-openapi"; +import { + SecuritiesStatusEnum, + ShareLegendsEnum, + VestingScheduleEnum, +} from "@prisma/client"; + +const VestingScheduleArr = Object.values(VestingScheduleEnum) as [ + string, + ...string[], +]; +const ShareLegendsArr = Object.values(ShareLegendsEnum) as [ + string, + ...string[], +]; +const SecuritiesStatusArr = Object.values(SecuritiesStatusEnum) as [ + string, + ...string[], +]; + +export const ShareSchema = z + .object({ + id: z.string().cuid().openapi({ + description: "Share ID", + example: "cly13ipa40000i7ng42mv4x7b", + }), + + status: z.enum(SecuritiesStatusArr).openapi({ + description: "Security Status", + example: "DRAFT", + }), + + certificateId: z.string().cuid().openapi({ + description: "Certificate ID", + example: "cly13ipa40000i7ng42mv4x7b", + }), + + quantity: z.number().openapi({ + description: "Quantity of Shares", + example: 234, + }), + + pricePerShare: z.number().nullable().openapi({ + description: "Price Per Share", + example: 23.4, + }), + + capitalContribution: z.number().optional().openapi({ + description: "Total amount of money contributed", + example: 25.4, + }), + + ipContribution: z.number().optional().openapi({ + description: "Value of the intellectual property contributed", + example: 43.4, + }), + + debtCancelled: z.number().optional().openapi({ + description: "Amount of debt cancelled", + example: 54.54, + }), + + otherContributions: z.number().optional().openapi({ + description: "Other contributions", + example: 45.54, + }), + + vestingSchedule: z.enum(VestingScheduleArr).openapi({ + description: "Vesting Schedule", + example: "VESTING_0_0_0", + }), + + companyLegends: z + .enum(ShareLegendsArr) + .array() + .openapi({ + description: "Company Legends", + example: ["US_SECURITIES_ACT", "SALE_AND_ROFR"], + }), + + issueDate: z.string().datetime().openapi({ + description: "Issued Date", + example: "1970-01-01T00:00:00.000Z", + }), + + rule144Date: z.string().datetime().optional().openapi({ + description: "Rule 144 Date", + example: "1970-01-01T00:00:00.000Z", + }), + + vestingStartDate: z.string().datetime().optional().openapi({ + description: "Vesting Start Date", + example: "1970-01-01T00:00:00.000Z", + }), + + boardApprovalDate: z.string().datetime().optional().openapi({ + description: "Board Approval Date", + example: "1970-01-01T00:00:00.000Z", + }), + + stakeholderId: z.string().cuid().openapi({ + description: "StakeHolder ID", + example: "cly13ipa40000i7ng42mv4x7b", + }), + + companyId: z.string().cuid().openapi({ + description: "Company ID", + example: "cly13ipa40000i7ng42mv4x7b", + }), + + shareClassId: z.string().cuid().openapi({ + description: "ShareClass ID", + example: "cly13ipa40000i7ng42mv4x7b", + }), + + createdAt: z.string().datetime().optional().openapi({ + description: "Share Created at", + example: "1970-01-01T00:00:00.000Z", + }), + + updatedAt: z.string().datetime().optional().openapi({ + description: "Share Updated at", + example: "1970-01-01T00:00:00.000Z", + }), + }) + .openapi({ + description: "Get a Single Share by the ID", + }); + +export type TShareSchema = z.infer; From 5142219618e01631ca7c0aacda89af8960f9361d Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Tue, 9 Jul 2024 13:56:09 +0530 Subject: [PATCH 02/40] feat: add getShareById route --- src/server/api/routes/company/share/getOne.ts | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/server/api/routes/company/share/getOne.ts diff --git a/src/server/api/routes/company/share/getOne.ts b/src/server/api/routes/company/share/getOne.ts new file mode 100644 index 000000000..d87a516ba --- /dev/null +++ b/src/server/api/routes/company/share/getOne.ts @@ -0,0 +1,93 @@ +import { withCompanyAuth } from "@/server/api/auth"; +import { ApiError, ErrorResponses } from "@/server/api/error"; +import type { PublicAPI } from "@/server/api/hono"; +import { ShareSchema, type TShareSchema } from "@/server/api/schema/shares"; +import { db } from "@/server/db"; +import { createRoute, z } from "@hono/zod-openapi"; +import type { Context } from "hono"; + +const ParamsSchema = z.object({ + id: z + .string() + .cuid() + .openapi({ + description: "Company ID", + param: { + name: "id", + in: "path", + }, + + example: "clxwbok580000i7nge8nm1ry0", + }), + shareId: z + .string() + .cuid() + .openapi({ + description: "Share ID", + param: { + name: "shareId", + in: "path", + }, + + example: "clyd3i9sw000008ij619eabva", + }), +}); + +const ResponseSchema = z + .object({ + data: ShareSchema, + }) + .openapi({ + description: "Get a single Share by ID", + }); + +const route = createRoute({ + method: "get", + path: "/v1/companies/{id}/shares/{shareId}", + request: { + params: ParamsSchema, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Retrieve the share for the company", + }, + ...ErrorResponses, + }, +}); + +const getOne = (app: PublicAPI) => { + app.openapi(route, async (c: Context) => { + const { company } = await withCompanyAuth(c); + + // id destructured to companyId and shareId destructured to id + const { shareId: id } = c.req.param(); + + const share = (await db.share.findUnique({ + where: { + id, + companyId: company.id, + }, + })) as unknown as TShareSchema; + + if (!share) { + throw new ApiError({ + code: "NOT_FOUND", + message: "Share not found", + }); + } + + return c.json( + { + data: share, + }, + 200, + ); + }); +}; + +export default getOne; From b66d2c92c2cb40f98a098df7a6abc4e6335933e5 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Tue, 9 Jul 2024 13:58:44 +0530 Subject: [PATCH 03/40] feat: add getOne function in shareRoutes --- src/server/api/routes/company/share/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/server/api/routes/company/share/index.ts diff --git a/src/server/api/routes/company/share/index.ts b/src/server/api/routes/company/share/index.ts new file mode 100644 index 000000000..929b30ab7 --- /dev/null +++ b/src/server/api/routes/company/share/index.ts @@ -0,0 +1,8 @@ +import type { PublicAPI } from "@/server/api/hono"; +import getOne from "./getOne"; + +const shareRoutes = (api: PublicAPI) => { + getOne(api); +}; + +export default shareRoutes; From 397d50ee6992aebcb7b49dc508e56ba21b248024 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Tue, 9 Jul 2024 19:23:59 +0530 Subject: [PATCH 04/40] feat: add pagination query and response schema --- src/server/api/schema/pagination.ts | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/server/api/schema/pagination.ts diff --git a/src/server/api/schema/pagination.ts b/src/server/api/schema/pagination.ts new file mode 100644 index 000000000..aa5d10e1f --- /dev/null +++ b/src/server/api/schema/pagination.ts @@ -0,0 +1,65 @@ +import { z } from "zod"; + +export const PaginationQuerySchema = z.object({ + limit: z + .preprocess( + (val) => + val === undefined ? undefined : Number.parseInt(val as string, 10), + z.number().int().positive().min(1).max(50).default(10), + ) + .openapi({ + description: "Number of items to take", + param: { + name: "limit", + in: "query", + }, + example: 10, + default: 10, + minimum: 5, + maximum: 50, + }), + + cursor: z + .string() + .optional() + .openapi({ + description: "Cursor for the next page", + param: { + name: "cursor", + in: "query", + }, + example: "cly151kxq0000i7ngb3erchgo", // CUID of the last item + }), + + total: z + .number() + .int() + .positive() + .optional() + .openapi({ + description: "Total Number of Records", + param: { + name: "total", + in: "query", + }, + }), +}); + +export type TPaginationQuerySchema = z.infer; + +export const PaginationResponseSchema = z.object({ + count: z.number().int().positive().openapi({ + description: "Number of records returned", + example: 50, + }), + + total: z.number().int().positive().openapi({ + description: "Total number of records", + example: 100, + }), + + cursor: z.string().optional().openapi({ + description: "Next page cursor", + example: "cly151kxq0000i7ngb3erchgo", + }), +}); From ac3f4b9cff3cc067986b1f7563c31618f3e56eb3 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Tue, 9 Jul 2024 19:26:30 +0530 Subject: [PATCH 05/40] feat: add ProxyPrismaModel and proxy findMany method --- src/server/api/pagination/find-many.proxy.ts | 62 ++++++++++++++++++++ src/server/api/pagination/prisma-proxy.ts | 26 ++++++++ src/server/api/pagination/types.ts | 19 ++++++ 3 files changed, 107 insertions(+) create mode 100644 src/server/api/pagination/find-many.proxy.ts create mode 100644 src/server/api/pagination/prisma-proxy.ts create mode 100644 src/server/api/pagination/types.ts diff --git a/src/server/api/pagination/find-many.proxy.ts b/src/server/api/pagination/find-many.proxy.ts new file mode 100644 index 000000000..f1765fa98 --- /dev/null +++ b/src/server/api/pagination/find-many.proxy.ts @@ -0,0 +1,62 @@ +import type { PaginationData, ProxyFunctions } from "./types"; + +interface Paginated { + data: T[]; + count: number; + total: number; + cursor?: string; +} + +/** + * FindManyPaginated + * + * type of the findManyPaginated method + */ +export type FindManyPaginated = { + findManyPaginated: ( + data: Omit[0], "take" | "skip" | "cursor">, + pagination: PaginationData, + ) => Promise>[0]>>; +}; + +/** + * makeFindManyPaginated + * + * factory function that creates the findManyPaginated method. + * this method is used to paginate the results of a findMany method. + * this method implements js proxy to intercept the call to findMany and add the pagination logic. + */ +export function makeFindManyPaginated(model: ProxyFunctions) { + return new Proxy(model.findMany, { + apply: async (target, thisArg, [data, paginationInfo]) => { + const limit = paginationInfo?.limit; + const cursor = paginationInfo?.cursor; + + const query = data || {}; + query.take = limit; + if (cursor) { + query.cursor = { id: cursor }; // Assuming `id` is the cursor field + query.skip = 1; // Skip the cursor item itself + } + + const total = + paginationInfo?.total || + (await model.count({ + where: query.where, + })); + + //@ts-ignore + const docs = await target.apply(thisArg, [query]); + + const nextCursor = + docs.length === limit ? docs[docs.length - 1].id : null; // Assuming `id` is the cursor field + + return { + data: docs, + count: docs.length, + total, + cursor: nextCursor, + }; + }, + }); +} diff --git a/src/server/api/pagination/prisma-proxy.ts b/src/server/api/pagination/prisma-proxy.ts new file mode 100644 index 000000000..ec490a956 --- /dev/null +++ b/src/server/api/pagination/prisma-proxy.ts @@ -0,0 +1,26 @@ +import { + type FindManyPaginated, + makeFindManyPaginated, +} from "./find-many.proxy"; +import type { ProxyFunctions } from "./types"; + +/** + * ProxyPrismaModel + * + * type of a prisma model with custom methods. to date, only findManyPaginated is implemented + */ +type ProxyPrismaModel = F & FindManyPaginated; + +/** + * ProxyPrismaModel + * + * the factory function that creates a ProxyPrismaModel. to date, only findManyPaginated is implemented. + */ +export function ProxyPrismaModel( + model: F, +): ProxyPrismaModel { + Reflect.set(model, "findManyPaginated", makeFindManyPaginated(model)); + // Reflect.set(model, 'anotherProxiedMethod', makeAnotherProxyMethod(model)); + // ... + return model as ProxyPrismaModel; +} diff --git a/src/server/api/pagination/types.ts b/src/server/api/pagination/types.ts new file mode 100644 index 000000000..c6f6c70d3 --- /dev/null +++ b/src/server/api/pagination/types.ts @@ -0,0 +1,19 @@ +/** + * Pagination information + */ +export type PaginationData = { + limit: number; + cursor?: string; +}; + +/** + * Proxy functions + * + * used to create custom methods for prisma models + */ +export type ProxyFunctions = { + // biome-ignore lint/suspicious/noExplicitAny: + findMany: (params: any, pagination: PaginationData) => Promise; + // biome-ignore lint/suspicious/noExplicitAny: + count: (params: any) => Promise; +}; From 411b3bb1e752f592d6e3af2d58d9ee52d8a46ed4 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 11:28:38 +0530 Subject: [PATCH 06/40] fix: update type of total --- src/server/api/schema/pagination.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/server/api/schema/pagination.ts b/src/server/api/schema/pagination.ts index aa5d10e1f..baeba0e66 100644 --- a/src/server/api/schema/pagination.ts +++ b/src/server/api/schema/pagination.ts @@ -32,10 +32,11 @@ export const PaginationQuerySchema = z.object({ }), total: z - .number() - .int() - .positive() - .optional() + .preprocess( + (val) => + val === undefined ? undefined : Number.parseInt(val as string, 10), + z.number().int().positive().optional(), + ) .openapi({ description: "Total Number of Records", param: { From 764fd80822d0535e6e37f6d621f886018a602e98 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 11:52:16 +0530 Subject: [PATCH 07/40] fix: type of ShareSchema --- src/server/api/schema/shares.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index e733a609d..cc48d6275 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -45,22 +45,22 @@ export const ShareSchema = z example: 23.4, }), - capitalContribution: z.number().optional().openapi({ + capitalContribution: z.number().nullable().openapi({ description: "Total amount of money contributed", example: 25.4, }), - ipContribution: z.number().optional().openapi({ + ipContribution: z.number().nullable().openapi({ description: "Value of the intellectual property contributed", example: 43.4, }), - debtCancelled: z.number().optional().openapi({ + debtCancelled: z.number().nullable().openapi({ description: "Amount of debt cancelled", example: 54.54, }), - otherContributions: z.number().optional().openapi({ + otherContributions: z.number().nullable().openapi({ description: "Other contributions", example: 45.54, }), @@ -83,12 +83,12 @@ export const ShareSchema = z example: "1970-01-01T00:00:00.000Z", }), - rule144Date: z.string().datetime().optional().openapi({ + rule144Date: z.string().datetime().nullable().openapi({ description: "Rule 144 Date", example: "1970-01-01T00:00:00.000Z", }), - vestingStartDate: z.string().datetime().optional().openapi({ + vestingStartDate: z.string().datetime().nullable().openapi({ description: "Vesting Start Date", example: "1970-01-01T00:00:00.000Z", }), From 93e264a3ed1615afadeb07e3988bd9f1b8b5b16b Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 12:26:38 +0530 Subject: [PATCH 08/40] feat: create getPaginatedShares method --- src/server/services/shares/getShares.ts | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/server/services/shares/getShares.ts diff --git a/src/server/services/shares/getShares.ts b/src/server/services/shares/getShares.ts new file mode 100644 index 000000000..095f8aa3b --- /dev/null +++ b/src/server/services/shares/getShares.ts @@ -0,0 +1,42 @@ +import { ProxyPrismaModel } from "@/server/api/pagination/prisma-proxy"; +import { db } from "@/server/db"; + +type GetPaginatedShares = { + companyId: string; + limit: number; + cursor?: string; + total?: number; +}; + +export const getPaginatedShares = async (payload: GetPaginatedShares) => { + const queryCriteria = { + where: { + companyId: payload.companyId, + }, + orderBy: { + createdAt: "desc", + }, + }; + + const paginationData = { + limit: payload.limit, + cursor: payload.cursor, + total: payload.total, + }; + + const prismaModel = ProxyPrismaModel(db.share); + + const { data, count, total, cursor } = await prismaModel.findManyPaginated( + queryCriteria, + paginationData, + ); + + return { + data, + meta: { + count, + total, + cursor, + }, + }; +}; From edeb00f2ebe8f31781b071f7ca0f29a65d549ee5 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 12:27:39 +0530 Subject: [PATCH 09/40] feat: create getMany routes --- .../api/routes/company/share/getMany.ts | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/server/api/routes/company/share/getMany.ts diff --git a/src/server/api/routes/company/share/getMany.ts b/src/server/api/routes/company/share/getMany.ts new file mode 100644 index 000000000..1dedc58a6 --- /dev/null +++ b/src/server/api/routes/company/share/getMany.ts @@ -0,0 +1,80 @@ +import { withCompanyAuth } from "@/server/api/auth"; +import { ErrorResponses } from "@/server/api/error"; +import type { PublicAPI } from "@/server/api/hono"; +import { + PaginationQuerySchema, + PaginationResponseSchema, +} from "@/server/api/schema/pagination"; +import { ShareSchema } from "@/server/api/schema/shares"; +import { getPaginatedShares } from "@/server/services/shares/getShares"; +import { createRoute, z } from "@hono/zod-openapi"; +import type { Context } from "hono"; + +const ParamsSchema = z.object({ + id: z + .string() + .cuid() + .openapi({ + description: "Company ID", + param: { + name: "id", + in: "path", + }, + + example: "clxwbok580000i7nge8nm1ry0", + }), +}); + +const ResponseSchema = z + .object({ + data: z.array(ShareSchema), + meta: PaginationResponseSchema, + }) + .openapi({ + description: "Get Shares by Company ID", + }); + +const route = createRoute({ + method: "get", + path: "/v1/companies/{id}/shares", + request: { + params: ParamsSchema, + query: PaginationQuerySchema, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Retrieve the share for the company", + }, + ...ErrorResponses, + }, +}); + +const getMany = (app: PublicAPI) => { + app.openapi(route, async (c: Context) => { + const { company } = await withCompanyAuth(c); + + const { limit, cursor, total } = c.req.query(); + + const { data, meta } = await getPaginatedShares({ + companyId: company.id, + limit: Number(limit), + cursor, + total: Number(total), + }); + + return c.json( + { + data, + meta, + }, + 200, + ); + }); +}; + +export default getMany; From f8be63b8a9e6a7c20cb6d43d2b07a90b17b8d647 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 12:28:51 +0530 Subject: [PATCH 10/40] feat: integrate getMany route in shareRoutes --- src/server/api/routes/company/share/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/api/routes/company/share/index.ts b/src/server/api/routes/company/share/index.ts index 929b30ab7..e941a5229 100644 --- a/src/server/api/routes/company/share/index.ts +++ b/src/server/api/routes/company/share/index.ts @@ -1,8 +1,10 @@ import type { PublicAPI } from "@/server/api/hono"; +import getMany from "./getMany"; import getOne from "./getOne"; const shareRoutes = (api: PublicAPI) => { getOne(api); + getMany(api); }; export default shareRoutes; From fc14896ab29cda08d288d2683f7005e112159261 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 14:44:54 +0530 Subject: [PATCH 11/40] feat: create AddShareSchema --- src/server/api/schema/shares.ts | 103 ++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index cc48d6275..1f5ec353a 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -128,3 +128,106 @@ export const ShareSchema = z }); export type TShareSchema = z.infer; + +export const AddShareSchema = z + .object({ + status: z.enum(SecuritiesStatusArr).openapi({ + description: "Security Status", + example: "DRAFT", + }), + + certificateId: z.string().openapi({ + description: "Certificate ID", + example: "cly13ipa40000i7ng42mv4x7b", + }), + + quantity: z.number().min(0).openapi({ + description: "Quantity of Shares", + example: 234, + }), + + pricePerShare: z.number().min(0).openapi({ + description: "Price Per Share", + example: 23.4, + }), + + capitalContribution: z.number().min(0).openapi({ + description: "Total amount of money contributed", + example: 25.4, + }), + + ipContribution: z.number().min(0).openapi({ + description: "Value of the intellectual property contributed", + example: 43.4, + }), + + debtCancelled: z.number().min(0).openapi({ + description: "Amount of debt cancelled", + example: 54.54, + }), + + otherContributions: z.number().min(0).openapi({ + description: "Other contributions", + example: 45.54, + }), + + vestingSchedule: z.enum(VestingScheduleArr).openapi({ + description: "Vesting Schedule", + example: "VESTING_0_0_0", + }), + + companyLegends: z + .enum(ShareLegendsArr) + .array() + .openapi({ + description: "Company Legends", + example: ["US_SECURITIES_ACT", "SALE_AND_ROFR"], + }), + + issueDate: z.string().datetime().openapi({ + description: "Issued Date", + example: "1970-01-01T00:00:00.000Z", + }), + + rule144Date: z.string().datetime().openapi({ + description: "Rule 144 Date", + example: "1970-01-01T00:00:00.000Z", + }), + + vestingStartDate: z.string().datetime().openapi({ + description: "Vesting Start Date", + example: "1970-01-01T00:00:00.000Z", + }), + + boardApprovalDate: z.string().datetime().openapi({ + description: "Board Approval Date", + example: "1970-01-01T00:00:00.000Z", + }), + + stakeholderId: z.string().cuid().openapi({ + description: "StakeHolder ID", + example: "clydxglyl0000d94ff363egje", + }), + + shareClassId: z.string().cuid().openapi({ + description: "ShareClass ID", + example: "clycjzdqo000cjvsqr9rn6479", + }), + + documents: z + .array( + z.object({ + bucketId: z.string(), + name: z.string(), + }), + ) + .openapi({ + description: "Uploaded Documents", + example: [{ bucketId: "vbhjewhvjvj3", name: "Bucket Blue" }], + }), + }) + .openapi({ + description: "Create a Share", + }); + +export type TAddShareSchema = z.infer; From d92d789c9d2fa87244f8594278ff7c5397a2a2bd Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 14:45:54 +0530 Subject: [PATCH 12/40] feat: create addShare function --- src/server/services/shares/addShare.ts | 65 ++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/server/services/shares/addShare.ts diff --git a/src/server/services/shares/addShare.ts b/src/server/services/shares/addShare.ts new file mode 100644 index 000000000..dfd55e9f1 --- /dev/null +++ b/src/server/services/shares/addShare.ts @@ -0,0 +1,65 @@ +import { generatePublicId } from "@/common/id"; +import { db } from "@/server/db"; +import type { TypeZodAddShareMutationSchema } from "@/trpc/routers/securities-router/schema"; + +interface AddShareType extends TypeZodAddShareMutationSchema { + companyId: string; + memberId: string; +} + +export const addShare = async (input: AddShareType) => { + try { + await db.$transaction(async (tx) => { + const documents = input.documents; + + const data = { + companyId: input.companyId, + stakeholderId: input.stakeholderId, + shareClassId: input.shareClassId, + status: input.status, + certificateId: input.certificateId, + quantity: input.quantity, + pricePerShare: input.pricePerShare, + capitalContribution: input.capitalContribution, + ipContribution: input.ipContribution, + debtCancelled: input.debtCancelled, + otherContributions: input.otherContributions, + vestingSchedule: input.vestingSchedule, + companyLegends: input.companyLegends, + issueDate: new Date(input.issueDate), + rule144Date: new Date(input.rule144Date), + vestingStartDate: new Date(input.vestingStartDate), + boardApprovalDate: new Date(input.boardApprovalDate), + }; + + console.log("The data inside addShare is :", input); + + const share = await tx.share.create({ data }); + + const bulkDocuments = documents.map((doc) => ({ + companyId: input.companyId, + uploaderId: input.memberId, + publicId: generatePublicId(), + name: doc.name, + bucketId: doc.bucketId, + shareId: share.id, + })); + + await tx.document.createMany({ + data: bulkDocuments, + skipDuplicates: true, + }); + }); + + return { + success: true, + message: "🎉 Successfully added a share", + }; + } catch (error) { + console.error("Error adding shares: ", error); + return { + success: false, + message: "Please use unique Certificate Id.", + }; + } +}; From 8b7b57097bf99657ae71f1a4f142f0e1c9cf0a30 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 14:52:01 +0530 Subject: [PATCH 13/40] feat: add createShare route --- src/server/api/routes/company/share/create.ts | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/server/api/routes/company/share/create.ts diff --git a/src/server/api/routes/company/share/create.ts b/src/server/api/routes/company/share/create.ts new file mode 100644 index 000000000..00eee8c4e --- /dev/null +++ b/src/server/api/routes/company/share/create.ts @@ -0,0 +1,82 @@ +import { withCompanyAuth } from "@/server/api/auth"; +import { ApiError, ErrorResponses } from "@/server/api/error"; +import type { PublicAPI } from "@/server/api/hono"; +import { AddShareSchema } from "@/server/api/schema/shares"; +import { addShare } from "@/server/services/shares/addShare"; +import { createRoute, z } from "@hono/zod-openapi"; +import type { Context } from "hono"; + +const ParamsSchema = z.object({ + id: z + .string() + .cuid() + .openapi({ + description: "Company ID", + param: { + name: "id", + in: "path", + }, + + example: "clycjihpy0002c5fzcyf4gjjc", + }), +}); + +const ResponseSchema = z.object({ + data: z.string(), +}); + +const route = createRoute({ + method: "post", + path: "/v1/companies/{id}/shares", + request: { + params: ParamsSchema, + body: { + content: { + "application/json": { + schema: AddShareSchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Created the Share Successfully", + }, + ...ErrorResponses, + }, +}); + +const create = (app: PublicAPI) => { + app.openapi(route, async (c: Context) => { + const { company, member } = await withCompanyAuth(c); + + const body = await c.req.json(); + + const { success } = await addShare({ + ...body, + companyId: company.id, + memberId: member.id, + }); + + if (!success) { + throw new ApiError({ + code: "INTERNAL_SERVER_ERROR", + message: "Something went wrong, Please try again Later", + }); + } + + return c.json( + { + data: "Created the Share Successfully", + }, + 200, + ); + }); +}; + +export default create; From 7fab72647d459cac9200fee0ea506653c2d2b3cf Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 14:53:23 +0530 Subject: [PATCH 14/40] feat: integrate createShare route in shareRoutes --- src/server/api/routes/company/share/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/api/routes/company/share/index.ts b/src/server/api/routes/company/share/index.ts index e941a5229..1d6b8ef6c 100644 --- a/src/server/api/routes/company/share/index.ts +++ b/src/server/api/routes/company/share/index.ts @@ -1,10 +1,12 @@ import type { PublicAPI } from "@/server/api/hono"; +import create from "./create"; import getMany from "./getMany"; import getOne from "./getOne"; const shareRoutes = (api: PublicAPI) => { getOne(api); getMany(api); + create(api); }; export default shareRoutes; From 2b76e58a5fdea8117a9b99f1cca88c5bbc9e9269 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 16:57:39 +0530 Subject: [PATCH 15/40] feat: Audit the addShare action --- src/server/api/routes/company/share/create.ts | 12 ++++++++++- src/server/services/shares/addShare.ts | 20 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/server/api/routes/company/share/create.ts b/src/server/api/routes/company/share/create.ts index 00eee8c4e..a92ae0f3d 100644 --- a/src/server/api/routes/company/share/create.ts +++ b/src/server/api/routes/company/share/create.ts @@ -53,14 +53,24 @@ const route = createRoute({ const create = (app: PublicAPI) => { app.openapi(route, async (c: Context) => { - const { company, member } = await withCompanyAuth(c); + const { company, member, user } = await withCompanyAuth(c); const body = await c.req.json(); + const requestIP = + c.req.header("x-forwarded-for") || + c.req.header("remoteAddr") || + "Unknown IP"; + const userAgent = c.req.header("User-Agent") || ""; + const { success } = await addShare({ ...body, companyId: company.id, memberId: member.id, + requestIP, + userAgent, + userId: user.id, + userName: user.name, }); if (!success) { diff --git a/src/server/services/shares/addShare.ts b/src/server/services/shares/addShare.ts index dfd55e9f1..586669577 100644 --- a/src/server/services/shares/addShare.ts +++ b/src/server/services/shares/addShare.ts @@ -1,10 +1,15 @@ import { generatePublicId } from "@/common/id"; +import { Audit } from "@/server/audit"; import { db } from "@/server/db"; import type { TypeZodAddShareMutationSchema } from "@/trpc/routers/securities-router/schema"; interface AddShareType extends TypeZodAddShareMutationSchema { companyId: string; memberId: string; + requestIP: string; + userAgent: string; + userId: string; + userName: string; } export const addShare = async (input: AddShareType) => { @@ -49,6 +54,21 @@ export const addShare = async (input: AddShareType) => { data: bulkDocuments, skipDuplicates: true, }); + + await Audit.create( + { + action: "share.created", + companyId: input.companyId, + actor: { type: "user", id: input.userId }, + context: { + userAgent: input.userAgent, + requestIp: input.requestIP, + }, + target: [{ type: "share", id: share.id }], + summary: `${input.userName} added share for stakeholder ${input.stakeholderId}`, + }, + tx, + ); }); return { From c596d907dfb01c51041af782f29ff95fb36f8b49 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 18:45:25 +0530 Subject: [PATCH 16/40] feat: add UpdateShareSchema --- src/server/api/schema/shares.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index 1f5ec353a..c837e42cd 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -231,3 +231,8 @@ export const AddShareSchema = z }); export type TAddShareSchema = z.infer; + +export const UpdateShareSchema = AddShareSchema.omit({ + documents: true, +}).partial(); +export type TUpdateShareSchema = z.infer; From 74a5bc6773e0acbcd5ef0eaa2fa47134f1146f0b Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 18:46:17 +0530 Subject: [PATCH 17/40] feat: export AddShareType --- src/server/services/shares/addShare.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/services/shares/addShare.ts b/src/server/services/shares/addShare.ts index 586669577..4557e1a97 100644 --- a/src/server/services/shares/addShare.ts +++ b/src/server/services/shares/addShare.ts @@ -3,7 +3,7 @@ import { Audit } from "@/server/audit"; import { db } from "@/server/db"; import type { TypeZodAddShareMutationSchema } from "@/trpc/routers/securities-router/schema"; -interface AddShareType extends TypeZodAddShareMutationSchema { +export interface AddShareType extends TypeZodAddShareMutationSchema { companyId: string; memberId: string; requestIP: string; From 4a4b1d9abe123cc2c70c47531ab5c5ce819b16f1 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Wed, 10 Jul 2024 18:58:11 +0530 Subject: [PATCH 18/40] feat: create updateShare method --- src/server/services/shares/updateShare.ts | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/server/services/shares/updateShare.ts diff --git a/src/server/services/shares/updateShare.ts b/src/server/services/shares/updateShare.ts new file mode 100644 index 000000000..d96786fd3 --- /dev/null +++ b/src/server/services/shares/updateShare.ts @@ -0,0 +1,77 @@ +import { Audit } from "@/server/audit"; +import { db } from "@/server/db"; +import type { AddShareType } from "./addShare"; + +interface UpdateShareType extends AddShareType { + shareId: string; +} + +export const updateShare = async (input: UpdateShareType) => { + try { + const existingShare = await db.share.findUnique({ + where: { + id: input.shareId, + }, + }); + + if (!existingShare) { + return { + success: false, + message: "Enter Valid Share ID", + }; + } + + await db.$transaction(async (tx) => { + const data = { + companyId: input.companyId, + stakeholderId: input.stakeholderId, + shareClassId: input.shareClassId, + status: input.status, + certificateId: input.certificateId, + quantity: input.quantity, + pricePerShare: input.pricePerShare, + capitalContribution: input.capitalContribution, + ipContribution: input.ipContribution, + debtCancelled: input.debtCancelled, + otherContributions: input.otherContributions, + vestingSchedule: input.vestingSchedule, + companyLegends: input.companyLegends, + issueDate: new Date(input.issueDate), + rule144Date: new Date(input.rule144Date), + vestingStartDate: new Date(input.vestingStartDate), + boardApprovalDate: new Date(input.boardApprovalDate), + }; + + const share = await tx.share.update({ + where: { id: input.shareId }, + data, + }); + + await Audit.create( + { + action: "share.updated", + companyId: input.companyId, + actor: { type: "user", id: input.userId }, + context: { + userAgent: input.userAgent, + requestIp: input.requestIP, + }, + target: [{ type: "share", id: share.id }], + summary: `${input.userName} updated share for stakeholder ${input.stakeholderId}`, + }, + tx, + ); + }); + + return { + success: true, + message: "🎉 Successfully Updated the share", + }; + } catch (error) { + console.error("Error adding shares: ", error); + return { + success: false, + message: "Can't Update the Share now. Please try again later", + }; + } +}; From c3bd9e84557a3918da0571b46bd55808b3c004c5 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Thu, 11 Jul 2024 10:44:36 +0530 Subject: [PATCH 19/40] feat: add updateShare route --- src/server/api/routes/company/share/update.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/server/api/routes/company/share/update.ts diff --git a/src/server/api/routes/company/share/update.ts b/src/server/api/routes/company/share/update.ts new file mode 100644 index 000000000..f71d53bd7 --- /dev/null +++ b/src/server/api/routes/company/share/update.ts @@ -0,0 +1,116 @@ +import { withCompanyAuth } from "@/server/api/auth"; +import { ApiError, ErrorResponses } from "@/server/api/error"; +import type { PublicAPI } from "@/server/api/hono"; +import { UpdateShareSchema } from "@/server/api/schema/shares"; +import { updateShare } from "@/server/services/shares/updateShare"; +import { createRoute, z } from "@hono/zod-openapi"; +import type { Context } from "hono"; + +const ParamsSchema = z + .object({ + id: z + .string() + .cuid() + .openapi({ + description: "Company ID", + param: { + name: "id", + in: "path", + }, + + example: "clxwbok580000i7nge8nm1ry0", + }), + shareId: z + .string() + .cuid() + .openapi({ + description: "Share ID", + param: { + name: "shareId", + in: "path", + }, + + example: "clyd3i9sw000008ij619eabva", + }), + }) + .openapi({ + description: "Update a Share by ID", + }); + +const ResponseSchema = z + .object({ + data: z.string(), + }) + .openapi({ + description: "Update a Share by ID", + }); + +const route = createRoute({ + method: "put", + path: "/v1/companies/{id}/shares/{shareId}", + request: { + params: ParamsSchema, + body: { + content: { + "application/json": { + schema: UpdateShareSchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Retrieve the share for the company", + }, + ...ErrorResponses, + }, +}); + +const getOne = (app: PublicAPI) => { + app.openapi(route, async (c: Context) => { + //TODO : CHECK THIS PROPERLT + + const { company, user, member } = await withCompanyAuth(c); + + const { shareId: id } = c.req.param(); + + const body = await c.req.json(); + const requestIP = + c.req.header("x-forwarded-for") || + c.req.header("remoteAddr") || + "Unknown IP"; + const userAgent = c.req.header("User-Agent") || ""; + + const { success, message } = await updateShare({ + ...body, + companyId: company.id, + memberId: member.id, + requestIP, + userAgent, + userId: user.id, + userName: user.name, + shareId: id, + }); + + if (!success) { + throw new ApiError({ + code: "BAD_REQUEST", + message, + }); + } + + return c.json( + { + data: "Successfully Updated the Share", + }, + 200, + ); + }); +}; + +export default getOne; From 219eb01810e21d4d1b8822e3ccbdc72f3b65b153 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Thu, 11 Jul 2024 10:45:10 +0530 Subject: [PATCH 20/40] feat: integrate updateShare route --- src/server/api/routes/company/share/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/api/routes/company/share/index.ts b/src/server/api/routes/company/share/index.ts index 1d6b8ef6c..aae290e01 100644 --- a/src/server/api/routes/company/share/index.ts +++ b/src/server/api/routes/company/share/index.ts @@ -2,11 +2,13 @@ import type { PublicAPI } from "@/server/api/hono"; import create from "./create"; import getMany from "./getMany"; import getOne from "./getOne"; +import update from "./update"; const shareRoutes = (api: PublicAPI) => { getOne(api); getMany(api); create(api); + update(api); }; export default shareRoutes; From 786baa8525906fdae3d1d920052aede3f1c49acf Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Thu, 11 Jul 2024 11:37:35 +0530 Subject: [PATCH 21/40] feat: add deleteShare function --- src/server/services/shares/deleteShare.ts | 75 +++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/server/services/shares/deleteShare.ts diff --git a/src/server/services/shares/deleteShare.ts b/src/server/services/shares/deleteShare.ts new file mode 100644 index 000000000..3113baa84 --- /dev/null +++ b/src/server/services/shares/deleteShare.ts @@ -0,0 +1,75 @@ +import { Audit } from "@/server/audit"; +import { db } from "@/server/db"; + +interface DeleteShareType { + user: { + id: string; + name: string; + }; + shareId: string; + companyId: string; + userAgent: string; + requestIp: string; +} + +export const deleteShare = async ({ + shareId, + companyId, + requestIp, + userAgent, + user, +}: DeleteShareType) => { + try { + const existingShare = await db.share.findUnique({ + where: { + id: shareId, + }, + }); + + if (!existingShare) { + return { + success: false, + message: "Enter Valid Share ID", + }; + } + + const share = await db.$transaction(async (tx) => { + const share = await tx.share.delete({ + where: { + id: shareId, + }, + }); + + const { stakeholderId } = share; + + await Audit.create( + { + action: "share.deleted", + companyId, + actor: { type: "user", id: user.id }, + context: { + userAgent, + requestIp, + }, + target: [{ type: "share", id: shareId }], + summary: `${user.name} Deleted the share for stakeholder ${stakeholderId}`, + }, + tx, + ); + + return share; + }); + + return { + success: true, + message: "🎉 Successfully Deleted the share", + share, + }; + } catch (error) { + console.error("Error Deleting the share: ", error); + return { + success: false, + message: "Can't Delete the Share now. Please try again later", + }; + } +}; From 07fe412c9547e98c726bf99b670e11abfea0bb10 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Thu, 11 Jul 2024 11:38:00 +0530 Subject: [PATCH 22/40] feat: add deleteShare route --- src/server/api/routes/company/share/delete.ts | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/server/api/routes/company/share/delete.ts diff --git a/src/server/api/routes/company/share/delete.ts b/src/server/api/routes/company/share/delete.ts new file mode 100644 index 000000000..338c1fb46 --- /dev/null +++ b/src/server/api/routes/company/share/delete.ts @@ -0,0 +1,105 @@ +import { withCompanyAuth } from "@/server/api/auth"; +import { ApiError, ErrorResponses } from "@/server/api/error"; +import type { PublicAPI } from "@/server/api/hono"; +import { ShareSchema } from "@/server/api/schema/shares"; +import { deleteShare } from "@/server/services/shares/deleteShare"; +import { createRoute, z } from "@hono/zod-openapi"; +import type { Context } from "hono"; + +const ParamsSchema = z + .object({ + id: z + .string() + .cuid() + .openapi({ + description: "Company ID", + param: { + name: "id", + in: "path", + }, + + example: "clxwbok580000i7nge8nm1ry0", + }), + shareId: z + .string() + .cuid() + .openapi({ + description: "Share ID", + param: { + name: "shareId", + in: "path", + }, + + example: "clyd3i9sw000008ij619eabva", + }), + }) + .openapi({ + description: "Delete a Share by ID", + }); + +const ResponseSchema = z + .object({ + message: z.string(), + data: ShareSchema.optional(), + }) + .openapi({ + description: "Delete a Share by ID", + }); + +const route = createRoute({ + method: "delete", + path: "/v1/companies/{id}/shares/{shareId}", + request: { + params: ParamsSchema, + }, + responses: { + 200: { + content: { + "application/json": { + schema: ResponseSchema, + }, + }, + description: "Delete a Share by ID", + }, + ...ErrorResponses, + }, +}); + +const delete_ = (app: PublicAPI) => { + app.openapi(route, async (c: Context) => { + const { company, user } = await withCompanyAuth(c); + + const { shareId: id } = c.req.param(); + + const requestIP = + c.req.header("x-forwarded-for") || + c.req.header("remoteAddr") || + "Unknown IP"; + const userAgent = c.req.header("User-Agent") || ""; + + const { success, message, share } = await deleteShare({ + companyId: company.id, + requestIp: requestIP, + userAgent, + shareId: id as string, + user: { id: user.id, name: user.name || "" }, + }); + + if (!success && !share) { + throw new ApiError({ + code: "BAD_REQUEST", + message, + }); + } + + return c.json( + { + message: "Successfully Deleted the Share", + data: share, + }, + 200, + ); + }); +}; + +export default delete_; From 8d753ac7abde6cc4a44138883a780ed12ed67d0a Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Thu, 11 Jul 2024 11:38:57 +0530 Subject: [PATCH 23/40] feat: integrate deleteRoute in shareRoutes --- src/server/api/routes/company/share/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/api/routes/company/share/index.ts b/src/server/api/routes/company/share/index.ts index aae290e01..9450d7ef1 100644 --- a/src/server/api/routes/company/share/index.ts +++ b/src/server/api/routes/company/share/index.ts @@ -1,5 +1,6 @@ import type { PublicAPI } from "@/server/api/hono"; import create from "./create"; +import delete_ from "./delete"; import getMany from "./getMany"; import getOne from "./getOne"; import update from "./update"; @@ -9,6 +10,7 @@ const shareRoutes = (api: PublicAPI) => { getMany(api); create(api); update(api); + delete_(api); }; export default shareRoutes; From cce66e505e13654bdcadfe700b4f38dd96c4a404 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Thu, 11 Jul 2024 11:40:19 +0530 Subject: [PATCH 24/40] feat: call shareRoutes in index --- src/server/api/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server/api/index.ts b/src/server/api/index.ts index a6165b81d..08dfd5b5d 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -1,9 +1,13 @@ import { PublicAPI } from "./hono"; import companyRoutes from "./routes/company"; +import shareRoutes from "./routes/company/share"; export const api = PublicAPI(); // RESTful routes for company companyRoutes(api); +// RESTful routes for shares +shareRoutes(api); + export default api; From 1f7a611fedd246215c45415565c9d83e25fe4d34 Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Thu, 11 Jul 2024 12:45:16 +0530 Subject: [PATCH 25/40] chore: cleanup types --- src/server/api/routes/company/share/create.ts | 6 ++++-- src/server/api/routes/company/share/update.ts | 2 -- src/server/services/shares/addShare.ts | 10 ++++++---- src/server/services/shares/updateShare.ts | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/server/api/routes/company/share/create.ts b/src/server/api/routes/company/share/create.ts index a92ae0f3d..0d180d7ac 100644 --- a/src/server/api/routes/company/share/create.ts +++ b/src/server/api/routes/company/share/create.ts @@ -69,8 +69,10 @@ const create = (app: PublicAPI) => { memberId: member.id, requestIP, userAgent, - userId: user.id, - userName: user.name, + user: { + id: user.id, + name: user.name, + }, }); if (!success) { diff --git a/src/server/api/routes/company/share/update.ts b/src/server/api/routes/company/share/update.ts index f71d53bd7..40dae5313 100644 --- a/src/server/api/routes/company/share/update.ts +++ b/src/server/api/routes/company/share/update.ts @@ -73,8 +73,6 @@ const route = createRoute({ const getOne = (app: PublicAPI) => { app.openapi(route, async (c: Context) => { - //TODO : CHECK THIS PROPERLT - const { company, user, member } = await withCompanyAuth(c); const { shareId: id } = c.req.param(); diff --git a/src/server/services/shares/addShare.ts b/src/server/services/shares/addShare.ts index 4557e1a97..b46372fbb 100644 --- a/src/server/services/shares/addShare.ts +++ b/src/server/services/shares/addShare.ts @@ -8,8 +8,10 @@ export interface AddShareType extends TypeZodAddShareMutationSchema { memberId: string; requestIP: string; userAgent: string; - userId: string; - userName: string; + user: { + id: string; + name: string; + }; } export const addShare = async (input: AddShareType) => { @@ -59,13 +61,13 @@ export const addShare = async (input: AddShareType) => { { action: "share.created", companyId: input.companyId, - actor: { type: "user", id: input.userId }, + actor: { type: "user", id: input.user.id }, context: { userAgent: input.userAgent, requestIp: input.requestIP, }, target: [{ type: "share", id: share.id }], - summary: `${input.userName} added share for stakeholder ${input.stakeholderId}`, + summary: `${input.user.name} added share for stakeholder ${input.stakeholderId}`, }, tx, ); diff --git a/src/server/services/shares/updateShare.ts b/src/server/services/shares/updateShare.ts index d96786fd3..dfacf67ed 100644 --- a/src/server/services/shares/updateShare.ts +++ b/src/server/services/shares/updateShare.ts @@ -51,13 +51,13 @@ export const updateShare = async (input: UpdateShareType) => { { action: "share.updated", companyId: input.companyId, - actor: { type: "user", id: input.userId }, + actor: { type: "user", id: input.user.id }, context: { userAgent: input.userAgent, requestIp: input.requestIP, }, target: [{ type: "share", id: share.id }], - summary: `${input.userName} updated share for stakeholder ${input.stakeholderId}`, + summary: `${input.user.name} updated share for stakeholder ${input.stakeholderId}`, }, tx, ); From b6b135af321db95a426e06ded31cee188dd90c9e Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Thu, 11 Jul 2024 12:51:17 +0530 Subject: [PATCH 26/40] chore: minor cleanups --- src/server/api/routes/company/share/getMany.ts | 2 +- src/server/api/routes/company/share/update.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/api/routes/company/share/getMany.ts b/src/server/api/routes/company/share/getMany.ts index 1dedc58a6..b60423733 100644 --- a/src/server/api/routes/company/share/getMany.ts +++ b/src/server/api/routes/company/share/getMany.ts @@ -48,7 +48,7 @@ const route = createRoute({ schema: ResponseSchema, }, }, - description: "Retrieve the share for the company", + description: "Retrieve the shares for the company", }, ...ErrorResponses, }, diff --git a/src/server/api/routes/company/share/update.ts b/src/server/api/routes/company/share/update.ts index 40dae5313..b46e549ee 100644 --- a/src/server/api/routes/company/share/update.ts +++ b/src/server/api/routes/company/share/update.ts @@ -65,7 +65,7 @@ const route = createRoute({ schema: ResponseSchema, }, }, - description: "Retrieve the share for the company", + description: "Update the Share by ID", }, ...ErrorResponses, }, From 4f2c7da35d0adc0fe29b949f52121652a83bf65a Mon Sep 17 00:00:00 2001 From: Vishnu Kumar Date: Tue, 16 Jul 2024 15:03:53 +0530 Subject: [PATCH 27/40] feat: add share endpoints in postman/collections --- postman/collection.json | 164 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 154 insertions(+), 10 deletions(-) diff --git a/postman/collection.json b/postman/collection.json index 52173fe1a..4b4c695c5 100644 --- a/postman/collection.json +++ b/postman/collection.json @@ -1,9 +1,9 @@ { "info": { - "_postman_id": "209da849-6850-40d0-a4bf-1b7d004be7ce", + "_postman_id": "844d3aaf-c30d-4fc2-be1f-e2f41e9fc5e9", "name": "Captable API (v1)", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "36586471" + "_exporter_id": "28023741" }, "item": [ { @@ -90,7 +90,10 @@ "name": "Get a team member", "request": { "method": "GET", - "header": [] + "header": [], + "url": { + "raw": "" + } }, "response": [] }, @@ -98,7 +101,10 @@ "name": "List team members", "request": { "method": "GET", - "header": [] + "header": [], + "url": { + "raw": "" + } }, "response": [] }, @@ -106,7 +112,10 @@ "name": "Create a team member", "request": { "method": "GET", - "header": [] + "header": [], + "url": { + "raw": "" + } }, "response": [] }, @@ -114,7 +123,10 @@ "name": "Update a team member", "request": { "method": "GET", - "header": [] + "header": [], + "url": { + "raw": "" + } }, "response": [] } @@ -127,7 +139,10 @@ "name": "Get a stakeholder", "request": { "method": "GET", - "header": [] + "header": [], + "url": { + "raw": "" + } }, "response": [] }, @@ -135,7 +150,10 @@ "name": "List stakeholders", "request": { "method": "GET", - "header": [] + "header": [], + "url": { + "raw": "" + } }, "response": [] }, @@ -143,7 +161,10 @@ "name": "Create a stakeholder", "request": { "method": "GET", - "header": [] + "header": [], + "url": { + "raw": "" + } }, "response": [] }, @@ -151,7 +172,130 @@ "name": "Update a stakeholder", "request": { "method": "GET", - "header": [] + "header": [], + "url": { + "raw": "" + } + }, + "response": [] + } + ] + }, + { + "name": "Share", + "item": [ + { + "name": "Get a Share", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/companies/{{companyId}}/shares/{{shareId}}", + "host": ["{{baseUrl}}"], + "path": ["companies", "{{companyId}}", "shares", "{{shareId}}"] + } + }, + "response": [] + }, + { + "name": "List Shares", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/companies/{{companyId}}/shares", + "host": ["{{baseUrl}}"], + "path": ["companies", "{{companyId}}", "shares"] + } + }, + "response": [] + }, + { + "name": "Create Share", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "url": { + "raw": "{{baseUrl}}/companies/{{companyId}}/shares", + "host": ["{{baseUrl}}"], + "path": ["companies", "{{companyId}}", "shares"] + } + }, + "response": [] + }, + { + "name": "Update Share", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [], + "url": { + "raw": "{{baseUrl}}/companies/{{companyId}}/shares/{{shareId}}", + "host": ["{{baseUrl}}"], + "path": ["companies", "{{companyId}}", "shares", "{{shareId}}"] + } + }, + "response": [] + }, + { + "name": "Delete Share", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/companies/{{companyId}}/shares/{{shareId}}", + "host": ["{{baseUrl}}"], + "path": ["companies", "{{companyId}}", "shares", "{{shareId}}"] + } }, "response": [] } From 4b29db1bc2aa3ffdc7e94ecd9f3f9c757140af2f Mon Sep 17 00:00:00 2001 From: Puru D Date: Sat, 20 Jul 2024 02:14:35 -0500 Subject: [PATCH 28/40] feat: update api pagination stuff --- src/server/api/pagination/find-many.proxy.ts | 22 +++++++++----------- src/server/api/pagination/types.ts | 2 +- src/server/api/schema/pagination.ts | 21 ++++--------------- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/server/api/pagination/find-many.proxy.ts b/src/server/api/pagination/find-many.proxy.ts index f1765fa98..51fe96f12 100644 --- a/src/server/api/pagination/find-many.proxy.ts +++ b/src/server/api/pagination/find-many.proxy.ts @@ -3,8 +3,8 @@ import type { PaginationData, ProxyFunctions } from "./types"; interface Paginated { data: T[]; count: number; - total: number; - cursor?: string; + total: number | null; + cursor: string | null; } /** @@ -29,32 +29,30 @@ export type FindManyPaginated = { export function makeFindManyPaginated(model: ProxyFunctions) { return new Proxy(model.findMany, { apply: async (target, thisArg, [data, paginationInfo]) => { - const limit = paginationInfo?.limit; + const take = paginationInfo?.take; const cursor = paginationInfo?.cursor; const query = data || {}; - query.take = limit; + query.take = take; if (cursor) { query.cursor = { id: cursor }; // Assuming `id` is the cursor field query.skip = 1; // Skip the cursor item itself } - const total = - paginationInfo?.total || - (await model.count({ - where: query.where, - })); + const totalCount = await model.count({ + where: query.where, + }); //@ts-ignore const docs = await target.apply(thisArg, [query]); - const nextCursor = - docs.length === limit ? docs[docs.length - 1].id : null; // Assuming `id` is the cursor field + const nextCursor = docs.length === take ? docs[docs.length - 1].id : null; // Assuming `id` is the cursor field return { data: docs, count: docs.length, - total, + // Send totalCount only for first request, let client side manages it for subsequent requests. + total: totalCount, cursor: nextCursor, }; }, diff --git a/src/server/api/pagination/types.ts b/src/server/api/pagination/types.ts index c6f6c70d3..7a807219a 100644 --- a/src/server/api/pagination/types.ts +++ b/src/server/api/pagination/types.ts @@ -2,7 +2,7 @@ * Pagination information */ export type PaginationData = { - limit: number; + take: number; cursor?: string; }; diff --git a/src/server/api/schema/pagination.ts b/src/server/api/schema/pagination.ts index baeba0e66..a2ad992ab 100644 --- a/src/server/api/schema/pagination.ts +++ b/src/server/api/schema/pagination.ts @@ -15,12 +15,13 @@ export const PaginationQuerySchema = z.object({ }, example: 10, default: 10, - minimum: 5, + minimum: 1, maximum: 50, }), cursor: z .string() + .cuid() .optional() .openapi({ description: "Cursor for the next page", @@ -30,20 +31,6 @@ export const PaginationQuerySchema = z.object({ }, example: "cly151kxq0000i7ngb3erchgo", // CUID of the last item }), - - total: z - .preprocess( - (val) => - val === undefined ? undefined : Number.parseInt(val as string, 10), - z.number().int().positive().optional(), - ) - .openapi({ - description: "Total Number of Records", - param: { - name: "total", - in: "query", - }, - }), }); export type TPaginationQuerySchema = z.infer; @@ -54,12 +41,12 @@ export const PaginationResponseSchema = z.object({ example: 50, }), - total: z.number().int().positive().openapi({ + total: z.number().int().positive().nullable().openapi({ description: "Total number of records", example: 100, }), - cursor: z.string().optional().openapi({ + cursor: z.string().nullable().openapi({ description: "Next page cursor", example: "cly151kxq0000i7ngb3erchgo", }), From e0f62d84cc089de0e4b16b861e390cd792fed5f3 Mon Sep 17 00:00:00 2001 From: Puru D Date: Sat, 20 Jul 2024 01:58:16 -0500 Subject: [PATCH 29/40] feat: remove postman/collection --- postman/collection.json | 305 ---------------------------------------- 1 file changed, 305 deletions(-) delete mode 100644 postman/collection.json diff --git a/postman/collection.json b/postman/collection.json deleted file mode 100644 index 4b4c695c5..000000000 --- a/postman/collection.json +++ /dev/null @@ -1,305 +0,0 @@ -{ - "info": { - "_postman_id": "844d3aaf-c30d-4fc2-be1f-e2f41e9fc5e9", - "name": "Captable API (v1)", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "28023741" - }, - "item": [ - { - "name": "Company", - "item": [ - { - "name": "Get a company", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{bearerToken}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/companies/{{companyId}}", - "host": ["{{baseUrl}}"], - "path": ["companies", "{{companyId}}"] - } - }, - "response": [] - }, - { - "name": "List companies", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{bearerToken}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/companies", - "host": ["{{baseUrl}}"], - "path": ["companies"] - } - }, - "response": [] - }, - { - "name": "Create company", - "request": { - "method": "POST", - "header": [], - "url": { - "raw": "{{baseUrl}}/companies", - "host": ["{{baseUrl}}"], - "path": ["companies"] - } - }, - "response": [] - }, - { - "name": "Update company", - "request": { - "method": "PUT", - "header": [], - "url": { - "raw": "{{baseUrl}}/companies/{{companyId}}", - "host": ["{{baseUrl}}"], - "path": ["companies", "{{companyId}}"] - } - }, - "response": [] - } - ] - }, - { - "name": "Team", - "item": [ - { - "name": "Get a team member", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - }, - { - "name": "List team members", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - }, - { - "name": "Create a team member", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - }, - { - "name": "Update a team member", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - } - ] - }, - { - "name": "Stakeholder", - "item": [ - { - "name": "Get a stakeholder", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - }, - { - "name": "List stakeholders", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - }, - { - "name": "Create a stakeholder", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - }, - { - "name": "Update a stakeholder", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "" - } - }, - "response": [] - } - ] - }, - { - "name": "Share", - "item": [ - { - "name": "Get a Share", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{bearerToken}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/companies/{{companyId}}/shares/{{shareId}}", - "host": ["{{baseUrl}}"], - "path": ["companies", "{{companyId}}", "shares", "{{shareId}}"] - } - }, - "response": [] - }, - { - "name": "List Shares", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{bearerToken}}", - "type": "string" - } - ] - }, - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/companies/{{companyId}}/shares", - "host": ["{{baseUrl}}"], - "path": ["companies", "{{companyId}}", "shares"] - } - }, - "response": [] - }, - { - "name": "Create Share", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{bearerToken}}", - "type": "string" - } - ] - }, - "method": "POST", - "header": [], - "url": { - "raw": "{{baseUrl}}/companies/{{companyId}}/shares", - "host": ["{{baseUrl}}"], - "path": ["companies", "{{companyId}}", "shares"] - } - }, - "response": [] - }, - { - "name": "Update Share", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{bearerToken}}", - "type": "string" - } - ] - }, - "method": "PUT", - "header": [], - "url": { - "raw": "{{baseUrl}}/companies/{{companyId}}/shares/{{shareId}}", - "host": ["{{baseUrl}}"], - "path": ["companies", "{{companyId}}", "shares", "{{shareId}}"] - } - }, - "response": [] - }, - { - "name": "Delete Share", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{bearerToken}}", - "type": "string" - } - ] - }, - "method": "DELETE", - "header": [], - "url": { - "raw": "{{baseUrl}}/companies/{{companyId}}/shares/{{shareId}}", - "host": ["{{baseUrl}}"], - "path": ["companies", "{{companyId}}", "shares", "{{shareId}}"] - } - }, - "response": [] - } - ] - } - ] -} From e17e2fb93b50db18b1e4e38acebfa3c6d74da895 Mon Sep 17 00:00:00 2001 From: Puru D Date: Sat, 20 Jul 2024 04:31:22 -0500 Subject: [PATCH 30/40] fix: issues during issueing a share, and some openapi specs --- src/server/api/pagination/find-many.proxy.ts | 1 - src/server/api/routes/company/share/create.ts | 9 ++++--- src/server/api/routes/company/share/delete.ts | 3 +++ .../api/routes/company/share/getMany.ts | 3 +++ src/server/api/routes/company/share/getOne.ts | 3 +++ src/server/api/routes/company/share/update.ts | 3 +++ src/server/api/schema/shares.ts | 22 ++++++++-------- src/server/services/shares/addShare.ts | 26 ++++++++++--------- 8 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/server/api/pagination/find-many.proxy.ts b/src/server/api/pagination/find-many.proxy.ts index 51fe96f12..d1d3c3daa 100644 --- a/src/server/api/pagination/find-many.proxy.ts +++ b/src/server/api/pagination/find-many.proxy.ts @@ -51,7 +51,6 @@ export function makeFindManyPaginated(model: ProxyFunctions) { return { data: docs, count: docs.length, - // Send totalCount only for first request, let client side manages it for subsequent requests. total: totalCount, cursor: nextCursor, }; diff --git a/src/server/api/routes/company/share/create.ts b/src/server/api/routes/company/share/create.ts index 0d180d7ac..839673b59 100644 --- a/src/server/api/routes/company/share/create.ts +++ b/src/server/api/routes/company/share/create.ts @@ -28,6 +28,9 @@ const ResponseSchema = z.object({ const route = createRoute({ method: "post", path: "/v1/companies/{id}/shares", + summary: "Issue shares", + description: "Issue shares to a stakeholder in a company.", + tags: ["Shares"], request: { params: ParamsSchema, body: { @@ -45,7 +48,7 @@ const route = createRoute({ schema: ResponseSchema, }, }, - description: "Created the Share Successfully", + description: "Issue shares", }, ...ErrorResponses, }, @@ -78,13 +81,13 @@ const create = (app: PublicAPI) => { if (!success) { throw new ApiError({ code: "INTERNAL_SERVER_ERROR", - message: "Something went wrong, Please try again Later", + message: "Something went wrong, please try again later", }); } return c.json( { - data: "Created the Share Successfully", + data: "Successfully added the share", }, 200, ); diff --git a/src/server/api/routes/company/share/delete.ts b/src/server/api/routes/company/share/delete.ts index c395d279e..6ff216e21 100644 --- a/src/server/api/routes/company/share/delete.ts +++ b/src/server/api/routes/company/share/delete.ts @@ -49,6 +49,9 @@ const ResponseSchema = z const route = createRoute({ method: "delete", path: "/v1/companies/{id}/shares/{shareId}", + summary: "Delete issued shares", + description: "Delete a Share by ID", + tags: ["Shares"], request: { params: ParamsSchema, }, diff --git a/src/server/api/routes/company/share/getMany.ts b/src/server/api/routes/company/share/getMany.ts index b60423733..6082e56f0 100644 --- a/src/server/api/routes/company/share/getMany.ts +++ b/src/server/api/routes/company/share/getMany.ts @@ -37,6 +37,9 @@ const ResponseSchema = z const route = createRoute({ method: "get", path: "/v1/companies/{id}/shares", + summary: "Get list of issued shares", + description: "Get list of issued shares for a company", + tags: ["Shares"], request: { params: ParamsSchema, query: PaginationQuerySchema, diff --git a/src/server/api/routes/company/share/getOne.ts b/src/server/api/routes/company/share/getOne.ts index d87a516ba..223f565b2 100644 --- a/src/server/api/routes/company/share/getOne.ts +++ b/src/server/api/routes/company/share/getOne.ts @@ -44,6 +44,9 @@ const ResponseSchema = z const route = createRoute({ method: "get", path: "/v1/companies/{id}/shares/{shareId}", + summary: "Get an issued share by ID", + description: "Get a single issued share record by ID", + tags: ["Shares"], request: { params: ParamsSchema, }, diff --git a/src/server/api/routes/company/share/update.ts b/src/server/api/routes/company/share/update.ts index b46e549ee..e74904783 100644 --- a/src/server/api/routes/company/share/update.ts +++ b/src/server/api/routes/company/share/update.ts @@ -48,6 +48,9 @@ const ResponseSchema = z const route = createRoute({ method: "put", path: "/v1/companies/{id}/shares/{shareId}", + summary: "Update issued shares by ID", + description: "Update issued shares by share ID", + tags: ["Shares"], request: { params: ParamsSchema, body: { diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index c837e42cd..f3ed4f59f 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -214,17 +214,17 @@ export const AddShareSchema = z example: "clycjzdqo000cjvsqr9rn6479", }), - documents: z - .array( - z.object({ - bucketId: z.string(), - name: z.string(), - }), - ) - .openapi({ - description: "Uploaded Documents", - example: [{ bucketId: "vbhjewhvjvj3", name: "Bucket Blue" }], - }), + // documents: z + // .array( + // z.object({ + // bucketId: z.string(), + // name: z.string(), + // }), + // ) + // .openapi({ + // description: "Uploaded Documents", + // example: [{ bucketId: "vbhjewhvjvj3", name: "Bucket Blue" }], + // }), }) .openapi({ description: "Create a Share", diff --git a/src/server/services/shares/addShare.ts b/src/server/services/shares/addShare.ts index b46372fbb..0c859171b 100644 --- a/src/server/services/shares/addShare.ts +++ b/src/server/services/shares/addShare.ts @@ -43,19 +43,21 @@ export const addShare = async (input: AddShareType) => { const share = await tx.share.create({ data }); - const bulkDocuments = documents.map((doc) => ({ - companyId: input.companyId, - uploaderId: input.memberId, - publicId: generatePublicId(), - name: doc.name, - bucketId: doc.bucketId, - shareId: share.id, - })); + if (documents && documents.length > 0) { + const bulkDocuments = documents.map((doc) => ({ + companyId: input.companyId, + uploaderId: input.memberId, + publicId: generatePublicId(), + name: doc.name, + bucketId: doc.bucketId, + shareId: share.id, + })); - await tx.document.createMany({ - data: bulkDocuments, - skipDuplicates: true, - }); + await tx.document.createMany({ + data: bulkDocuments, + skipDuplicates: true, + }); + } await Audit.create( { From 761f0782517eb7b48714939471c744e05ce039c7 Mon Sep 17 00:00:00 2001 From: Puru D Date: Sat, 20 Jul 2024 04:38:39 -0500 Subject: [PATCH 31/40] feat: update share POST response with the data --- src/server/api/routes/company/share/create.ts | 12 ++++-------- src/server/services/shares/addShare.ts | 7 ++++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/server/api/routes/company/share/create.ts b/src/server/api/routes/company/share/create.ts index 839673b59..2013cb40f 100644 --- a/src/server/api/routes/company/share/create.ts +++ b/src/server/api/routes/company/share/create.ts @@ -22,7 +22,8 @@ const ParamsSchema = z.object({ }); const ResponseSchema = z.object({ - data: z.string(), + message: z.string(), + data: AddShareSchema, }); const route = createRoute({ @@ -66,7 +67,7 @@ const create = (app: PublicAPI) => { "Unknown IP"; const userAgent = c.req.header("User-Agent") || ""; - const { success } = await addShare({ + const { success, message, data } = await addShare({ ...body, companyId: company.id, memberId: member.id, @@ -85,12 +86,7 @@ const create = (app: PublicAPI) => { }); } - return c.json( - { - data: "Successfully added the share", - }, - 200, - ); + return c.json({ message, data }, 200); }); }; diff --git a/src/server/services/shares/addShare.ts b/src/server/services/shares/addShare.ts index 0c859171b..be322ba9c 100644 --- a/src/server/services/shares/addShare.ts +++ b/src/server/services/shares/addShare.ts @@ -16,7 +16,7 @@ export interface AddShareType extends TypeZodAddShareMutationSchema { export const addShare = async (input: AddShareType) => { try { - await db.$transaction(async (tx) => { + const share = await db.$transaction(async (tx) => { const documents = input.documents; const data = { @@ -39,8 +39,6 @@ export const addShare = async (input: AddShareType) => { boardApprovalDate: new Date(input.boardApprovalDate), }; - console.log("The data inside addShare is :", input); - const share = await tx.share.create({ data }); if (documents && documents.length > 0) { @@ -73,11 +71,14 @@ export const addShare = async (input: AddShareType) => { }, tx, ); + + return share; }); return { success: true, message: "🎉 Successfully added a share", + data: share, }; } catch (error) { console.error("Error adding shares: ", error); From eb40fd587be49ec1835ccd9793575c68ca16b21e Mon Sep 17 00:00:00 2001 From: Puru D Date: Sat, 20 Jul 2024 04:43:20 -0500 Subject: [PATCH 32/40] fix: some ts errors --- src/server/api/routes/company/share/create.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/server/api/routes/company/share/create.ts b/src/server/api/routes/company/share/create.ts index 2013cb40f..4970ddb6c 100644 --- a/src/server/api/routes/company/share/create.ts +++ b/src/server/api/routes/company/share/create.ts @@ -79,14 +79,34 @@ const create = (app: PublicAPI) => { }, }); - if (!success) { + if (!success || !data) { throw new ApiError({ code: "INTERNAL_SERVER_ERROR", message: "Something went wrong, please try again later", }); } - return c.json({ message, data }, 200); + // Ensure data matches ResponseSchema + const responseData = { + status: data.status as string, // Cast to string if necessary + certificateId: data.certificateId, + quantity: data.quantity, + pricePerShare: data.pricePerShare ?? 0, + capitalContribution: data.capitalContribution ?? 0, + ipContribution: data.ipContribution ?? 0, + debtCancelled: data.debtCancelled ?? 0, + otherContributions: data.otherContributions ?? 0, + vestingSchedule: data.vestingSchedule ?? "", + companyLegends: data.companyLegends ?? "", // Add missing fields + issueDate: data.issueDate ?? new Date().toISOString(), // Add missing fields + rule144Date: data.rule144Date ?? new Date().toISOString(), // Add missing fields + vestingStartDate: data.vestingStartDate ?? new Date().toISOString(), // Add missing fields + boardApprovalDate: data.boardApprovalDate ?? new Date().toISOString(), // Add boardApprovalDate + stakeholderId: data.stakeholderId ?? "", // Add stakeholderId + shareClassId: data.shareClassId, + }; + + return c.json({ message, data: responseData }, 200); }); }; From cc66f3f819cd13e03a7022a91db0bfdeab74dee3 Mon Sep 17 00:00:00 2001 From: Puru D Date: Sat, 20 Jul 2024 04:49:11 -0500 Subject: [PATCH 33/40] fix: build errors --- src/server/api/routes/company/share/getMany.ts | 4 ++-- src/server/api/schema/shares.ts | 4 +--- src/server/services/shares/getShares.ts | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/server/api/routes/company/share/getMany.ts b/src/server/api/routes/company/share/getMany.ts index 6082e56f0..132673eda 100644 --- a/src/server/api/routes/company/share/getMany.ts +++ b/src/server/api/routes/company/share/getMany.ts @@ -61,11 +61,11 @@ const getMany = (app: PublicAPI) => { app.openapi(route, async (c: Context) => { const { company } = await withCompanyAuth(c); - const { limit, cursor, total } = c.req.query(); + const { take, cursor, total } = c.req.query(); const { data, meta } = await getPaginatedShares({ companyId: company.id, - limit: Number(limit), + take: Number(take), cursor, total: Number(total), }); diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index f3ed4f59f..65b40f6ea 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -232,7 +232,5 @@ export const AddShareSchema = z export type TAddShareSchema = z.infer; -export const UpdateShareSchema = AddShareSchema.omit({ - documents: true, -}).partial(); +export const UpdateShareSchema = AddShareSchema.partial(); export type TUpdateShareSchema = z.infer; diff --git a/src/server/services/shares/getShares.ts b/src/server/services/shares/getShares.ts index 095f8aa3b..815ed07d9 100644 --- a/src/server/services/shares/getShares.ts +++ b/src/server/services/shares/getShares.ts @@ -3,7 +3,7 @@ import { db } from "@/server/db"; type GetPaginatedShares = { companyId: string; - limit: number; + take: number; cursor?: string; total?: number; }; @@ -19,7 +19,7 @@ export const getPaginatedShares = async (payload: GetPaginatedShares) => { }; const paginationData = { - limit: payload.limit, + take: payload.take, cursor: payload.cursor, total: payload.total, }; From 9cc3a33448495a0f2ccd317b92f902e8a3b5b755 Mon Sep 17 00:00:00 2001 From: Puru D Date: Sun, 21 Jul 2024 01:44:46 -0500 Subject: [PATCH 34/40] fix: pagination issues --- src/server/api/routes/company/share/getMany.ts | 3 ++- src/server/api/schema/pagination.ts | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/server/api/routes/company/share/getMany.ts b/src/server/api/routes/company/share/getMany.ts index 132673eda..7f84db132 100644 --- a/src/server/api/routes/company/share/getMany.ts +++ b/src/server/api/routes/company/share/getMany.ts @@ -2,6 +2,7 @@ import { withCompanyAuth } from "@/server/api/auth"; import { ErrorResponses } from "@/server/api/error"; import type { PublicAPI } from "@/server/api/hono"; import { + DEFAULT_PAGINATION_LIMIT, PaginationQuerySchema, PaginationResponseSchema, } from "@/server/api/schema/pagination"; @@ -65,7 +66,7 @@ const getMany = (app: PublicAPI) => { const { data, meta } = await getPaginatedShares({ companyId: company.id, - take: Number(take), + take: Number(take || DEFAULT_PAGINATION_LIMIT), cursor, total: Number(total), }); diff --git a/src/server/api/schema/pagination.ts b/src/server/api/schema/pagination.ts index a2ad992ab..f1dd39b4e 100644 --- a/src/server/api/schema/pagination.ts +++ b/src/server/api/schema/pagination.ts @@ -1,11 +1,19 @@ import { z } from "zod"; +export const DEFAULT_PAGINATION_LIMIT = 50; + export const PaginationQuerySchema = z.object({ limit: z .preprocess( (val) => val === undefined ? undefined : Number.parseInt(val as string, 10), - z.number().int().positive().min(1).max(50).default(10), + z + .number() + .int() + .positive() + .min(1) + .max(50) + .default(DEFAULT_PAGINATION_LIMIT), ) .openapi({ description: "Number of items to take", @@ -13,10 +21,10 @@ export const PaginationQuerySchema = z.object({ name: "limit", in: "query", }, - example: 10, - default: 10, + example: 25, + default: DEFAULT_PAGINATION_LIMIT, minimum: 1, - maximum: 50, + maximum: 250, }), cursor: z From 963fdbb4e6ea9b52332648f05d96a0a6caafd44c Mon Sep 17 00:00:00 2001 From: Puru D Date: Sun, 21 Jul 2024 01:59:37 -0500 Subject: [PATCH 35/40] chore: update file names --- src/components/ui/uploader.tsx | 4 +++- src/server/api/routes/company/share/create.ts | 4 ++-- src/server/api/routes/company/share/delete.ts | 2 +- src/server/api/routes/company/share/getMany.ts | 2 +- src/server/api/routes/company/share/update.ts | 2 +- .../services/shares/{addShare.ts => add-share.ts} | 0 .../shares/{deleteShare.ts => delete-share.ts} | 0 .../services/shares/{getShares.ts => get-shares.ts} | 0 .../shares/{updateShare.ts => update-share.ts} | 13 +++++++------ 9 files changed, 15 insertions(+), 12 deletions(-) rename src/server/services/shares/{addShare.ts => add-share.ts} (100%) rename src/server/services/shares/{deleteShare.ts => delete-share.ts} (100%) rename src/server/services/shares/{getShares.ts => get-shares.ts} (100%) rename src/server/services/shares/{updateShare.ts => update-share.ts} (87%) diff --git a/src/components/ui/uploader.tsx b/src/components/ui/uploader.tsx index 1158329c4..842bc4319 100644 --- a/src/components/ui/uploader.tsx +++ b/src/components/ui/uploader.tsx @@ -106,7 +106,9 @@ export function Uploader({ } } catch (error) { console.error("Error uploading file:", error); - toast.error("Uh oh! Something went wrong, please try again."); + toast.error( + "Uh oh! Something went wrong, please try again or contact support.", + ); } finally { setUploading(false); } diff --git a/src/server/api/routes/company/share/create.ts b/src/server/api/routes/company/share/create.ts index 4970ddb6c..7838be479 100644 --- a/src/server/api/routes/company/share/create.ts +++ b/src/server/api/routes/company/share/create.ts @@ -2,7 +2,7 @@ import { withCompanyAuth } from "@/server/api/auth"; import { ApiError, ErrorResponses } from "@/server/api/error"; import type { PublicAPI } from "@/server/api/hono"; import { AddShareSchema } from "@/server/api/schema/shares"; -import { addShare } from "@/server/services/shares/addShare"; +import { addShare } from "@/server/services/shares/add-share"; import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; @@ -82,7 +82,7 @@ const create = (app: PublicAPI) => { if (!success || !data) { throw new ApiError({ code: "INTERNAL_SERVER_ERROR", - message: "Something went wrong, please try again later", + message: "Something went wrong, please try again or contact support.", }); } diff --git a/src/server/api/routes/company/share/delete.ts b/src/server/api/routes/company/share/delete.ts index 6ff216e21..c2ce3f09f 100644 --- a/src/server/api/routes/company/share/delete.ts +++ b/src/server/api/routes/company/share/delete.ts @@ -2,7 +2,7 @@ import { withCompanyAuth } from "@/server/api/auth"; import { ApiError, ErrorResponses } from "@/server/api/error"; import type { PublicAPI } from "@/server/api/hono"; import { ShareSchema } from "@/server/api/schema/shares"; -import { deleteShare } from "@/server/services/shares/deleteShare"; +import { deleteShare } from "@/server/services/shares/delete-share"; import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; diff --git a/src/server/api/routes/company/share/getMany.ts b/src/server/api/routes/company/share/getMany.ts index 7f84db132..38cb8768a 100644 --- a/src/server/api/routes/company/share/getMany.ts +++ b/src/server/api/routes/company/share/getMany.ts @@ -7,7 +7,7 @@ import { PaginationResponseSchema, } from "@/server/api/schema/pagination"; import { ShareSchema } from "@/server/api/schema/shares"; -import { getPaginatedShares } from "@/server/services/shares/getShares"; +import { getPaginatedShares } from "@/server/services/shares/get-shares"; import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; diff --git a/src/server/api/routes/company/share/update.ts b/src/server/api/routes/company/share/update.ts index e74904783..f6bb48a36 100644 --- a/src/server/api/routes/company/share/update.ts +++ b/src/server/api/routes/company/share/update.ts @@ -2,7 +2,7 @@ import { withCompanyAuth } from "@/server/api/auth"; import { ApiError, ErrorResponses } from "@/server/api/error"; import type { PublicAPI } from "@/server/api/hono"; import { UpdateShareSchema } from "@/server/api/schema/shares"; -import { updateShare } from "@/server/services/shares/updateShare"; +import { updateShare } from "@/server/services/shares/update-share"; import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; diff --git a/src/server/services/shares/addShare.ts b/src/server/services/shares/add-share.ts similarity index 100% rename from src/server/services/shares/addShare.ts rename to src/server/services/shares/add-share.ts diff --git a/src/server/services/shares/deleteShare.ts b/src/server/services/shares/delete-share.ts similarity index 100% rename from src/server/services/shares/deleteShare.ts rename to src/server/services/shares/delete-share.ts diff --git a/src/server/services/shares/getShares.ts b/src/server/services/shares/get-shares.ts similarity index 100% rename from src/server/services/shares/getShares.ts rename to src/server/services/shares/get-shares.ts diff --git a/src/server/services/shares/updateShare.ts b/src/server/services/shares/update-share.ts similarity index 87% rename from src/server/services/shares/updateShare.ts rename to src/server/services/shares/update-share.ts index dfacf67ed..43d48dd01 100644 --- a/src/server/services/shares/updateShare.ts +++ b/src/server/services/shares/update-share.ts @@ -1,6 +1,7 @@ +import { ApiError } from "@/server/api/error"; import { Audit } from "@/server/audit"; import { db } from "@/server/db"; -import type { AddShareType } from "./addShare"; +import type { AddShareType } from "./add-share"; interface UpdateShareType extends AddShareType { shareId: string; @@ -68,10 +69,10 @@ export const updateShare = async (input: UpdateShareType) => { message: "🎉 Successfully Updated the share", }; } catch (error) { - console.error("Error adding shares: ", error); - return { - success: false, - message: "Can't Update the Share now. Please try again later", - }; + console.error("updateShare", error); + throw new ApiError({ + code: "INTERNAL_SERVER_ERROR", + message: "Something went wrong, please try again or contact support", + }); } }; From 3e6bf3ab8df8cdad3343860e1c36a777e7a3b575 Mon Sep 17 00:00:00 2001 From: Puru D Date: Sun, 21 Jul 2024 03:22:37 -0500 Subject: [PATCH 36/40] fix: issues with updating existing share & stakeholder --- src/server/api/routes/company/share/delete.ts | 34 +--------- src/server/api/routes/company/share/getOne.ts | 4 +- src/server/api/routes/company/share/update.ts | 46 ++++++------- .../api/routes/company/stakeholder/update.ts | 13 +--- src/server/api/schema/shares.ts | 43 +++++++++---- src/server/api/schema/stakeholder.ts | 15 ++++- src/server/services/shares/update-share.ts | 64 ++++++++----------- .../stakeholder/update-stakeholder.ts | 6 +- 8 files changed, 102 insertions(+), 123 deletions(-) diff --git a/src/server/api/routes/company/share/delete.ts b/src/server/api/routes/company/share/delete.ts index c2ce3f09f..0edeb35ba 100644 --- a/src/server/api/routes/company/share/delete.ts +++ b/src/server/api/routes/company/share/delete.ts @@ -5,37 +5,7 @@ import { ShareSchema } from "@/server/api/schema/shares"; import { deleteShare } from "@/server/services/shares/delete-share"; import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; - -const ParamsSchema = z - .object({ - id: z - .string() - .cuid() - .openapi({ - description: "Company ID", - param: { - name: "id", - in: "path", - }, - - example: "clxwbok580000i7nge8nm1ry0", - }), - shareId: z - .string() - .cuid() - .openapi({ - description: "Share ID", - param: { - name: "shareId", - in: "path", - }, - - example: "clyd3i9sw000008ij619eabva", - }), - }) - .openapi({ - description: "Delete a Share by ID", - }); +import { RequestParamsSchema } from "./update"; const ResponseSchema = z .object({ @@ -53,7 +23,7 @@ const route = createRoute({ description: "Delete a Share by ID", tags: ["Shares"], request: { - params: ParamsSchema, + params: RequestParamsSchema, }, responses: { 200: { diff --git a/src/server/api/routes/company/share/getOne.ts b/src/server/api/routes/company/share/getOne.ts index 223f565b2..a85ee6489 100644 --- a/src/server/api/routes/company/share/getOne.ts +++ b/src/server/api/routes/company/share/getOne.ts @@ -1,7 +1,7 @@ import { withCompanyAuth } from "@/server/api/auth"; import { ApiError, ErrorResponses } from "@/server/api/error"; import type { PublicAPI } from "@/server/api/hono"; -import { ShareSchema, type TShareSchema } from "@/server/api/schema/shares"; +import { ShareSchema, type ShareSchemaType } from "@/server/api/schema/shares"; import { db } from "@/server/db"; import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; @@ -75,7 +75,7 @@ const getOne = (app: PublicAPI) => { id, companyId: company.id, }, - })) as unknown as TShareSchema; + })) as unknown as ShareSchemaType; if (!share) { throw new ApiError({ diff --git a/src/server/api/routes/company/share/update.ts b/src/server/api/routes/company/share/update.ts index f6bb48a36..2bf9265ec 100644 --- a/src/server/api/routes/company/share/update.ts +++ b/src/server/api/routes/company/share/update.ts @@ -2,11 +2,13 @@ import { withCompanyAuth } from "@/server/api/auth"; import { ApiError, ErrorResponses } from "@/server/api/error"; import type { PublicAPI } from "@/server/api/hono"; import { UpdateShareSchema } from "@/server/api/schema/shares"; +import type { UpdateShareSchemaType } from "@/server/api/schema/shares"; +import { getHonoUserAgent, getIp } from "@/server/api/utils"; import { updateShare } from "@/server/services/shares/update-share"; import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; -const ParamsSchema = z +export const RequestParamsSchema = z .object({ id: z .string() @@ -39,7 +41,8 @@ const ParamsSchema = z const ResponseSchema = z .object({ - data: z.string(), + message: z.string(), + data: UpdateShareSchema, }) .openapi({ description: "Update a Share by ID", @@ -52,7 +55,7 @@ const route = createRoute({ description: "Update issued shares by share ID", tags: ["Shares"], request: { - params: ParamsSchema, + params: RequestParamsSchema, body: { content: { "application/json": { @@ -76,38 +79,35 @@ const route = createRoute({ const getOne = (app: PublicAPI) => { app.openapi(route, async (c: Context) => { - const { company, user, member } = await withCompanyAuth(c); - - const { shareId: id } = c.req.param(); - + const { company, user } = await withCompanyAuth(c); + const { shareId } = c.req.param(); const body = await c.req.json(); - const requestIP = - c.req.header("x-forwarded-for") || - c.req.header("remoteAddr") || - "Unknown IP"; - const userAgent = c.req.header("User-Agent") || ""; - const { success, message } = await updateShare({ - ...body, + const payload = { + shareId, companyId: company.id, - memberId: member.id, - requestIP, - userAgent, - userId: user.id, - userName: user.name, - shareId: id, - }); + requestIp: getIp(c.req), + userAgent: getHonoUserAgent(c.req), + data: body as UpdateShareSchemaType, + user: { + id: user.id, + name: user.name as string, + }, + }; + + const { success, message, data } = await updateShare(payload); if (!success) { throw new ApiError({ - code: "BAD_REQUEST", + code: "NOT_FOUND", message, }); } return c.json( { - data: "Successfully Updated the Share", + message, + data, }, 200, ); diff --git a/src/server/api/routes/company/stakeholder/update.ts b/src/server/api/routes/company/stakeholder/update.ts index da4ed5522..57968ece7 100644 --- a/src/server/api/routes/company/stakeholder/update.ts +++ b/src/server/api/routes/company/stakeholder/update.ts @@ -14,17 +14,6 @@ import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; import { RequestParamsSchema } from "./delete"; -const RequestBodySchema = UpdateStakeholderSchema.refine( - (data) => { - return Object.values(data).some((value) => value !== undefined); - }, - { - message: "At least one field must be provided to update.", - }, -).openapi({ - description: "Update a stakeholder by ID", -}); - const ResponseSchema = z .object({ message: z.string(), @@ -45,7 +34,7 @@ const route = createRoute({ body: { content: { "application/json": { - schema: RequestBodySchema, + schema: UpdateStakeholderSchema, }, }, }, diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index 65b40f6ea..a9e18c224 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -20,7 +20,7 @@ const SecuritiesStatusArr = Object.values(SecuritiesStatusEnum) as [ export const ShareSchema = z .object({ - id: z.string().cuid().openapi({ + id: z.string().cuid().optional().openapi({ description: "Share ID", example: "cly13ipa40000i7ng42mv4x7b", }), @@ -30,39 +30,39 @@ export const ShareSchema = z example: "DRAFT", }), - certificateId: z.string().cuid().openapi({ + certificateId: z.string().optional().openapi({ description: "Certificate ID", - example: "cly13ipa40000i7ng42mv4x7b", + example: "123", }), quantity: z.number().openapi({ description: "Quantity of Shares", - example: 234, + example: 5000, }), pricePerShare: z.number().nullable().openapi({ description: "Price Per Share", - example: 23.4, + example: 1.25, }), capitalContribution: z.number().nullable().openapi({ description: "Total amount of money contributed", - example: 25.4, + example: 250000, }), ipContribution: z.number().nullable().openapi({ description: "Value of the intellectual property contributed", - example: 43.4, + example: 0, }), debtCancelled: z.number().nullable().openapi({ description: "Amount of debt cancelled", - example: 54.54, + example: 0, }), otherContributions: z.number().nullable().openapi({ description: "Other contributions", - example: 45.54, + example: 0, }), vestingSchedule: z.enum(VestingScheduleArr).openapi({ @@ -127,8 +127,6 @@ export const ShareSchema = z description: "Get a Single Share by the ID", }); -export type TShareSchema = z.infer; - export const AddShareSchema = z .object({ status: z.enum(SecuritiesStatusArr).openapi({ @@ -230,7 +228,24 @@ export const AddShareSchema = z description: "Create a Share", }); -export type TAddShareSchema = z.infer; +export const UpdateShareSchema = ShareSchema.omit({ + id: true, + createdAt: true, + updatedAt: true, +}) + .partial() + .refine( + (data) => { + return Object.values(data).some((value) => value !== undefined); + }, + { + message: "At least one field must be provided to update.", + }, + ) + .openapi({ + description: "Update a share by ID", + }); -export const UpdateShareSchema = AddShareSchema.partial(); -export type TUpdateShareSchema = z.infer; +export type ShareSchemaType = z.infer; +export type AddShareSchemaType = z.infer; +export type UpdateShareSchemaType = z.infer; diff --git a/src/server/api/schema/stakeholder.ts b/src/server/api/schema/stakeholder.ts index c37cdc0f6..283e2fd73 100644 --- a/src/server/api/schema/stakeholder.ts +++ b/src/server/api/schema/stakeholder.ts @@ -80,12 +80,23 @@ export const StakeholderSchema = z.object({ }); export const AddStakeholderSchema = z.array(StakeholderSchema); - export const UpdateStakeholderSchema = StakeholderSchema.omit({ id: true, createdAt: true, updatedAt: true, -}); +}) + .partial() + .refine( + (data) => { + return Object.values(data).some((value) => value !== undefined); + }, + { + message: "At least one field must be provided to update.", + }, + ) + .openapi({ + description: "Update a stakeholder by ID", + }); export type TStakeholderSchema = z.infer; export type TAddStakeholderSchema = z.infer; diff --git a/src/server/services/shares/update-share.ts b/src/server/services/shares/update-share.ts index 43d48dd01..673078374 100644 --- a/src/server/services/shares/update-share.ts +++ b/src/server/services/shares/update-share.ts @@ -1,72 +1,64 @@ import { ApiError } from "@/server/api/error"; +import type { UpdateShareSchemaType } from "@/server/api/schema/shares"; import { Audit } from "@/server/audit"; import { db } from "@/server/db"; -import type { AddShareType } from "./add-share"; -interface UpdateShareType extends AddShareType { +export type UpdateSharePayloadType = { shareId: string; -} + companyId: string; + requestIp: string; + userAgent: string; + user: { + id: string; + name: string; + }; + data: UpdateShareSchemaType; +}; + +export const updateShare = async (payload: UpdateSharePayloadType) => { + const { shareId, companyId, requestIp, userAgent, user, data } = payload; -export const updateShare = async (input: UpdateShareType) => { try { const existingShare = await db.share.findUnique({ - where: { - id: input.shareId, - }, + where: { id: shareId }, }); if (!existingShare) { return { success: false, - message: "Enter Valid Share ID", + message: `Share with ID ${shareId} not be found`, }; } - await db.$transaction(async (tx) => { - const data = { - companyId: input.companyId, - stakeholderId: input.stakeholderId, - shareClassId: input.shareClassId, - status: input.status, - certificateId: input.certificateId, - quantity: input.quantity, - pricePerShare: input.pricePerShare, - capitalContribution: input.capitalContribution, - ipContribution: input.ipContribution, - debtCancelled: input.debtCancelled, - otherContributions: input.otherContributions, - vestingSchedule: input.vestingSchedule, - companyLegends: input.companyLegends, - issueDate: new Date(input.issueDate), - rule144Date: new Date(input.rule144Date), - vestingStartDate: new Date(input.vestingStartDate), - boardApprovalDate: new Date(input.boardApprovalDate), - }; - + console.log({ data }); + const share = await db.$transaction(async (tx) => { const share = await tx.share.update({ - where: { id: input.shareId }, + where: { id: shareId }, data, }); await Audit.create( { action: "share.updated", - companyId: input.companyId, - actor: { type: "user", id: input.user.id }, + companyId: companyId, + actor: { type: "user", id: user.id }, context: { - userAgent: input.userAgent, - requestIp: input.requestIP, + userAgent: userAgent, + requestIp: requestIp, }, target: [{ type: "share", id: share.id }], - summary: `${input.user.name} updated share for stakeholder ${input.stakeholderId}`, + summary: `${user.name} updated share the share ID ${shareId}`, }, tx, ); + + return share; }); return { success: true, - message: "🎉 Successfully Updated the share", + message: "🎉 Successfully updated share.", + data: share, }; } catch (error) { console.error("updateShare", error); diff --git a/src/server/services/stakeholder/update-stakeholder.ts b/src/server/services/stakeholder/update-stakeholder.ts index d8727b437..1b2dbb898 100644 --- a/src/server/services/stakeholder/update-stakeholder.ts +++ b/src/server/services/stakeholder/update-stakeholder.ts @@ -2,7 +2,7 @@ import type { TUpdateStakeholderSchema } from "@/server/api/schema/stakeholder"; import { Audit } from "@/server/audit"; import { db } from "@/server/db"; -export type UpdateStakeholderOptions = { +export type UpdateStakeholderPayloadType = { stakeholderId: string; companyId: string; requestIp: string; @@ -14,7 +14,9 @@ export type UpdateStakeholderOptions = { data: TUpdateStakeholderSchema; }; -export const updateStakeholder = async (payload: UpdateStakeholderOptions) => { +export const updateStakeholder = async ( + payload: UpdateStakeholderPayloadType, +) => { const { requestIp, userAgent, user } = payload; const data = payload.data; From 54964d798c98696c80ebf8fe0a61c7c5d8b8a1f8 Mon Sep 17 00:00:00 2001 From: Puru D Date: Sun, 21 Jul 2024 03:33:02 -0500 Subject: [PATCH 37/40] fix: delete share endpoint --- src/server/api/error.ts | 2 ++ src/server/api/routes/company/share/delete.ts | 29 ++++++++----------- src/server/services/shares/delete-share.ts | 6 ++-- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/server/api/error.ts b/src/server/api/error.ts index a32df05f8..f1c41392b 100644 --- a/src/server/api/error.ts +++ b/src/server/api/error.ts @@ -19,6 +19,8 @@ const ErrorCode = z.enum([ "METHOD_NOT_ALLOWED", ]); +export type ErrorCodeType = z.infer; + function errorSchemaFactory(code: z.ZodEnum<[z.infer]>) { return z.object({ error: z.object({ diff --git a/src/server/api/routes/company/share/delete.ts b/src/server/api/routes/company/share/delete.ts index 0edeb35ba..4e97eddf4 100644 --- a/src/server/api/routes/company/share/delete.ts +++ b/src/server/api/routes/company/share/delete.ts @@ -1,7 +1,11 @@ import { withCompanyAuth } from "@/server/api/auth"; -import { ApiError, ErrorResponses } from "@/server/api/error"; +import { + ApiError, + type ErrorCodeType, + ErrorResponses, +} from "@/server/api/error"; import type { PublicAPI } from "@/server/api/hono"; -import { ShareSchema } from "@/server/api/schema/shares"; +import { getHonoUserAgent, getIp } from "@/server/api/utils"; import { deleteShare } from "@/server/services/shares/delete-share"; import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; @@ -10,7 +14,6 @@ import { RequestParamsSchema } from "./update"; const ResponseSchema = z .object({ message: z.string(), - data: ShareSchema.optional(), }) .openapi({ description: "Delete a Share by ID", @@ -41,34 +44,26 @@ const route = createRoute({ const deleteOne = (app: PublicAPI) => { app.openapi(route, async (c: Context) => { const { company, user } = await withCompanyAuth(c); - const { shareId: id } = c.req.param(); - const requestIP = - c.req.header("x-forwarded-for") || - c.req.header("remoteAddr") || - "Unknown IP"; - const userAgent = c.req.header("User-Agent") || ""; - - const { success, message, share } = await deleteShare({ + const { success, code, message } = await deleteShare({ companyId: company.id, - requestIp: requestIP, - userAgent, + requestIp: getIp(c.req), + userAgent: getHonoUserAgent(c.req), shareId: id as string, user: { id: user.id, name: user.name || "" }, }); - if (!success && !share) { + if (!success) { throw new ApiError({ - code: "BAD_REQUEST", + code: code as ErrorCodeType, message, }); } return c.json( { - message: "Successfully Deleted the Share", - data: share, + message: message, }, 200, ); diff --git a/src/server/services/shares/delete-share.ts b/src/server/services/shares/delete-share.ts index 3113baa84..56bd05c4f 100644 --- a/src/server/services/shares/delete-share.ts +++ b/src/server/services/shares/delete-share.ts @@ -29,7 +29,8 @@ export const deleteShare = async ({ if (!existingShare) { return { success: false, - message: "Enter Valid Share ID", + code: "NOT_FOUND", + message: `Share with ID ${shareId} not found`, }; } @@ -69,7 +70,8 @@ export const deleteShare = async ({ console.error("Error Deleting the share: ", error); return { success: false, - message: "Can't Delete the Share now. Please try again later", + code: "INTERNAL_SERVER_ERROR", + message: "Error deleting the share, please try again or contact support.", }; } }; From f36b9fecd9c7349ffdea7e9b162e71750b360d6c Mon Sep 17 00:00:00 2001 From: Puru D Date: Sun, 21 Jul 2024 03:40:10 -0500 Subject: [PATCH 38/40] chore: some type safety issues --- src/server/api/routes/company/share/update.ts | 15 +++++++---- src/server/api/schema/shares.ts | 26 +++++++++---------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/server/api/routes/company/share/update.ts b/src/server/api/routes/company/share/update.ts index 2bf9265ec..467025a82 100644 --- a/src/server/api/routes/company/share/update.ts +++ b/src/server/api/routes/company/share/update.ts @@ -1,8 +1,10 @@ import { withCompanyAuth } from "@/server/api/auth"; import { ApiError, ErrorResponses } from "@/server/api/error"; import type { PublicAPI } from "@/server/api/hono"; -import { UpdateShareSchema } from "@/server/api/schema/shares"; -import type { UpdateShareSchemaType } from "@/server/api/schema/shares"; +import { + UpdateShareSchema, + type UpdateShareSchemaType, +} from "@/server/api/schema/shares"; import { getHonoUserAgent, getIp } from "@/server/api/utils"; import { updateShare } from "@/server/services/shares/update-share"; import { createRoute, z } from "@hono/zod-openapi"; @@ -84,7 +86,7 @@ const getOne = (app: PublicAPI) => { const body = await c.req.json(); const payload = { - shareId, + shareId: shareId as string, companyId: company.id, requestIp: getIp(c.req), userAgent: getHonoUserAgent(c.req), @@ -106,8 +108,11 @@ const getOne = (app: PublicAPI) => { return c.json( { - message, - data, + message: message, + data: { + ...data, + issueDate: data?.issueDate.toISOString(), // Convert Date to string + } as UpdateShareSchemaType, }, 200, ); diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index a9e18c224..52ba18a4f 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -22,7 +22,7 @@ export const ShareSchema = z .object({ id: z.string().cuid().optional().openapi({ description: "Share ID", - example: "cly13ipa40000i7ng42mv4x7b", + example: "clyvb2s8d0000f1ngd72y2cxw", }), status: z.enum(SecuritiesStatusArr).openapi({ @@ -80,22 +80,22 @@ export const ShareSchema = z issueDate: z.string().datetime().openapi({ description: "Issued Date", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), rule144Date: z.string().datetime().nullable().openapi({ description: "Rule 144 Date", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), vestingStartDate: z.string().datetime().nullable().openapi({ description: "Vesting Start Date", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), boardApprovalDate: z.string().datetime().optional().openapi({ description: "Board Approval Date", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), stakeholderId: z.string().cuid().openapi({ @@ -105,22 +105,22 @@ export const ShareSchema = z companyId: z.string().cuid().openapi({ description: "Company ID", - example: "cly13ipa40000i7ng42mv4x7b", + example: "clyvb28ak0000f1ngcn2i0p2m", }), shareClassId: z.string().cuid().openapi({ description: "ShareClass ID", - example: "cly13ipa40000i7ng42mv4x7b", + example: "clyvb2d8v0000f1ng1stpa38s", }), createdAt: z.string().datetime().optional().openapi({ description: "Share Created at", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), updatedAt: z.string().datetime().optional().openapi({ description: "Share Updated at", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), }) .openapi({ @@ -184,22 +184,22 @@ export const AddShareSchema = z issueDate: z.string().datetime().openapi({ description: "Issued Date", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), rule144Date: z.string().datetime().openapi({ description: "Rule 144 Date", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), vestingStartDate: z.string().datetime().openapi({ description: "Vesting Start Date", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), boardApprovalDate: z.string().datetime().openapi({ description: "Board Approval Date", - example: "1970-01-01T00:00:00.000Z", + example: "2024-01-01T00:00:00.000Z", }), stakeholderId: z.string().cuid().openapi({ From 00386818c12e62f43e96a2b3aa25c182c99b112d Mon Sep 17 00:00:00 2001 From: Puru D Date: Sun, 21 Jul 2024 03:53:27 -0500 Subject: [PATCH 39/40] fix: issues with share create endpoints --- src/server/api/routes/company/share/create.ts | 18 +-- src/server/api/schema/shares.ts | 111 ++---------------- 2 files changed, 16 insertions(+), 113 deletions(-) diff --git a/src/server/api/routes/company/share/create.ts b/src/server/api/routes/company/share/create.ts index 7838be479..d11c8231c 100644 --- a/src/server/api/routes/company/share/create.ts +++ b/src/server/api/routes/company/share/create.ts @@ -1,7 +1,8 @@ import { withCompanyAuth } from "@/server/api/auth"; import { ApiError, ErrorResponses } from "@/server/api/error"; import type { PublicAPI } from "@/server/api/hono"; -import { AddShareSchema } from "@/server/api/schema/shares"; +import { CreateShareSchema } from "@/server/api/schema/shares"; +import { getHonoUserAgent, getIp } from "@/server/api/utils"; import { addShare } from "@/server/services/shares/add-share"; import { createRoute, z } from "@hono/zod-openapi"; import type { Context } from "hono"; @@ -23,7 +24,7 @@ const ParamsSchema = z.object({ const ResponseSchema = z.object({ message: z.string(), - data: AddShareSchema, + data: CreateShareSchema, }); const route = createRoute({ @@ -37,7 +38,7 @@ const route = createRoute({ body: { content: { "application/json": { - schema: AddShareSchema, + schema: CreateShareSchema, }, }, }, @@ -58,21 +59,14 @@ const route = createRoute({ const create = (app: PublicAPI) => { app.openapi(route, async (c: Context) => { const { company, member, user } = await withCompanyAuth(c); - const body = await c.req.json(); - const requestIP = - c.req.header("x-forwarded-for") || - c.req.header("remoteAddr") || - "Unknown IP"; - const userAgent = c.req.header("User-Agent") || ""; - const { success, message, data } = await addShare({ ...body, companyId: company.id, memberId: member.id, - requestIP, - userAgent, + requestIP: getIp(c.req), + userAgent: getHonoUserAgent(c.req), user: { id: user.id, name: user.name, diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index 52ba18a4f..fa7013b70 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -4,6 +4,7 @@ import { ShareLegendsEnum, VestingScheduleEnum, } from "@prisma/client"; +import { Share } from "next/font/google"; const VestingScheduleArr = Object.values(VestingScheduleEnum) as [ string, @@ -127,106 +128,14 @@ export const ShareSchema = z description: "Get a Single Share by the ID", }); -export const AddShareSchema = z - .object({ - status: z.enum(SecuritiesStatusArr).openapi({ - description: "Security Status", - example: "DRAFT", - }), - - certificateId: z.string().openapi({ - description: "Certificate ID", - example: "cly13ipa40000i7ng42mv4x7b", - }), - - quantity: z.number().min(0).openapi({ - description: "Quantity of Shares", - example: 234, - }), - - pricePerShare: z.number().min(0).openapi({ - description: "Price Per Share", - example: 23.4, - }), - - capitalContribution: z.number().min(0).openapi({ - description: "Total amount of money contributed", - example: 25.4, - }), - - ipContribution: z.number().min(0).openapi({ - description: "Value of the intellectual property contributed", - example: 43.4, - }), - - debtCancelled: z.number().min(0).openapi({ - description: "Amount of debt cancelled", - example: 54.54, - }), - - otherContributions: z.number().min(0).openapi({ - description: "Other contributions", - example: 45.54, - }), - - vestingSchedule: z.enum(VestingScheduleArr).openapi({ - description: "Vesting Schedule", - example: "VESTING_0_0_0", - }), - - companyLegends: z - .enum(ShareLegendsArr) - .array() - .openapi({ - description: "Company Legends", - example: ["US_SECURITIES_ACT", "SALE_AND_ROFR"], - }), - - issueDate: z.string().datetime().openapi({ - description: "Issued Date", - example: "2024-01-01T00:00:00.000Z", - }), - - rule144Date: z.string().datetime().openapi({ - description: "Rule 144 Date", - example: "2024-01-01T00:00:00.000Z", - }), - - vestingStartDate: z.string().datetime().openapi({ - description: "Vesting Start Date", - example: "2024-01-01T00:00:00.000Z", - }), - - boardApprovalDate: z.string().datetime().openapi({ - description: "Board Approval Date", - example: "2024-01-01T00:00:00.000Z", - }), - - stakeholderId: z.string().cuid().openapi({ - description: "StakeHolder ID", - example: "clydxglyl0000d94ff363egje", - }), - - shareClassId: z.string().cuid().openapi({ - description: "ShareClass ID", - example: "clycjzdqo000cjvsqr9rn6479", - }), - - // documents: z - // .array( - // z.object({ - // bucketId: z.string(), - // name: z.string(), - // }), - // ) - // .openapi({ - // description: "Uploaded Documents", - // example: [{ bucketId: "vbhjewhvjvj3", name: "Bucket Blue" }], - // }), - }) - .openapi({ - description: "Create a Share", - }); +export const CreateShareSchema = ShareSchema.omit({ + id: true, + createdAt: true, + updatedAt: true, + companyId: true, +}).openapi({ + description: "Issue shares to a stakeholder in a company.", +}); export const UpdateShareSchema = ShareSchema.omit({ id: true, @@ -247,5 +156,5 @@ export const UpdateShareSchema = ShareSchema.omit({ }); export type ShareSchemaType = z.infer; -export type AddShareSchemaType = z.infer; +export type CreateShareSchemaType = z.infer; export type UpdateShareSchemaType = z.infer; From 9e2c6df44f67e12e864e8eed5e93efaf5612526f Mon Sep 17 00:00:00 2001 From: Puru D Date: Sun, 21 Jul 2024 04:23:40 -0500 Subject: [PATCH 40/40] fix: build error --- .../migration.sql | 2 ++ prisma/schema.prisma | 2 +- src/server/api/schema/shares.ts | 28 ++++++++++--------- src/server/services/shares/update-share.ts | 10 +++++-- 4 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 prisma/migrations/20240721090639_default_share_legends/migration.sql diff --git a/prisma/migrations/20240721090639_default_share_legends/migration.sql b/prisma/migrations/20240721090639_default_share_legends/migration.sql new file mode 100644 index 000000000..4139b2dd5 --- /dev/null +++ b/prisma/migrations/20240721090639_default_share_legends/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Share" ALTER COLUMN "companyLegends" SET DEFAULT ARRAY[]::"ShareLegendsEnum"[]; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dfe7f487e..79787279c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -701,7 +701,7 @@ model Share { vestingYears Int @default(0) // 0 means immediate vesting, 1 means vesting over 1 year vestingSchedule VestingScheduleEnum - companyLegends ShareLegendsEnum[] + companyLegends ShareLegendsEnum[] @default([]) issueDate DateTime rule144Date DateTime? diff --git a/src/server/api/schema/shares.ts b/src/server/api/schema/shares.ts index fa7013b70..f7984b18e 100644 --- a/src/server/api/schema/shares.ts +++ b/src/server/api/schema/shares.ts @@ -21,7 +21,7 @@ const SecuritiesStatusArr = Object.values(SecuritiesStatusEnum) as [ export const ShareSchema = z .object({ - id: z.string().cuid().optional().openapi({ + id: z.string().cuid().nullish().openapi({ description: "Share ID", example: "clyvb2s8d0000f1ngd72y2cxw", }), @@ -31,7 +31,7 @@ export const ShareSchema = z example: "DRAFT", }), - certificateId: z.string().optional().openapi({ + certificateId: z.string().nullish().openapi({ description: "Certificate ID", example: "123", }), @@ -41,27 +41,27 @@ export const ShareSchema = z example: 5000, }), - pricePerShare: z.number().nullable().openapi({ + pricePerShare: z.number().nullish().openapi({ description: "Price Per Share", example: 1.25, }), - capitalContribution: z.number().nullable().openapi({ + capitalContribution: z.number().nullish().openapi({ description: "Total amount of money contributed", example: 250000, }), - ipContribution: z.number().nullable().openapi({ + ipContribution: z.number().nullish().openapi({ description: "Value of the intellectual property contributed", example: 0, }), - debtCancelled: z.number().nullable().openapi({ + debtCancelled: z.number().nullish().openapi({ description: "Amount of debt cancelled", example: 0, }), - otherContributions: z.number().nullable().openapi({ + otherContributions: z.number().nullish().openapi({ description: "Other contributions", example: 0, }), @@ -74,27 +74,29 @@ export const ShareSchema = z companyLegends: z .enum(ShareLegendsArr) .array() + .default([]) + .nullish() .openapi({ description: "Company Legends", example: ["US_SECURITIES_ACT", "SALE_AND_ROFR"], }), - issueDate: z.string().datetime().openapi({ + issueDate: z.string().datetime().nullish().openapi({ description: "Issued Date", example: "2024-01-01T00:00:00.000Z", }), - rule144Date: z.string().datetime().nullable().openapi({ + rule144Date: z.string().datetime().nullish().openapi({ description: "Rule 144 Date", example: "2024-01-01T00:00:00.000Z", }), - vestingStartDate: z.string().datetime().nullable().openapi({ + vestingStartDate: z.string().datetime().nullish().openapi({ description: "Vesting Start Date", example: "2024-01-01T00:00:00.000Z", }), - boardApprovalDate: z.string().datetime().optional().openapi({ + boardApprovalDate: z.string().datetime().nullish().openapi({ description: "Board Approval Date", example: "2024-01-01T00:00:00.000Z", }), @@ -114,12 +116,12 @@ export const ShareSchema = z example: "clyvb2d8v0000f1ng1stpa38s", }), - createdAt: z.string().datetime().optional().openapi({ + createdAt: z.string().datetime().nullish().openapi({ description: "Share Created at", example: "2024-01-01T00:00:00.000Z", }), - updatedAt: z.string().datetime().optional().openapi({ + updatedAt: z.string().datetime().nullish().openapi({ description: "Share Updated at", example: "2024-01-01T00:00:00.000Z", }), diff --git a/src/server/services/shares/update-share.ts b/src/server/services/shares/update-share.ts index 673078374..4d7ca7cc4 100644 --- a/src/server/services/shares/update-share.ts +++ b/src/server/services/shares/update-share.ts @@ -2,6 +2,7 @@ import { ApiError } from "@/server/api/error"; import type { UpdateShareSchemaType } from "@/server/api/schema/shares"; import { Audit } from "@/server/audit"; import { db } from "@/server/db"; +import type { ShareLegendsEnum } from "@prisma/client"; export type UpdateSharePayloadType = { shareId: string; @@ -30,11 +31,16 @@ export const updateShare = async (payload: UpdateSharePayloadType) => { }; } - console.log({ data }); + const shareData = { + ...existingShare, + ...data, + }; + const share = await db.$transaction(async (tx) => { const share = await tx.share.update({ where: { id: shareId }, - data, + // @ts-ignore + data: shareData, }); await Audit.create(