Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Openapi for team members #456

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d6e5ae7
feat: create member schema
VK-RED Jul 20, 2024
79dff66
feat: create getTeamMember route
VK-RED Jul 20, 2024
7fb0272
feat: create register function and call getOne
VK-RED Jul 20, 2024
52dc26b
Merge branch 'main' into feat/openapi-for-team-members
VK-RED Jul 24, 2024
ab4b041
feat: create getMany Members service
VK-RED Jul 24, 2024
7cbff8f
feat: create getMany members route
VK-RED Jul 24, 2024
70c7fb9
feat: add getMany Members route in index.ts
VK-RED Jul 24, 2024
fa76497
feat: create create-team-member and check-user-membership
VK-RED Jul 24, 2024
bdba422
chore: rename file
VK-RED Jul 24, 2024
739e1c6
chore: update import
VK-RED Jul 24, 2024
d967223
chore: use checkUserMembership and createTeamMember functions
VK-RED Jul 24, 2024
728c9ca
chore: reuse types
VK-RED Jul 24, 2024
657b020
chore: rename files
VK-RED Jul 25, 2024
4be51ca
feat: return additional member data
VK-RED Jul 25, 2024
f0d603c
feat: add createTeamMember function
VK-RED Jul 25, 2024
617c4c8
feat: add CreateMemberSchema
VK-RED Jul 25, 2024
f992d47
feat: create post method for member route
VK-RED Jul 25, 2024
3b3c687
feat: call create route in index.ts
VK-RED Jul 25, 2024
5ef4688
fix: post member route and createTeamMember error responses
VK-RED Jul 25, 2024
20dc9b7
feat: export ErrorCodes
VK-RED Jul 25, 2024
135aa1f
feat: add UpdateMemberSchema and update TeamMemberSchema
VK-RED Jul 25, 2024
96e203b
feat: create update member route
VK-RED Jul 25, 2024
19aa3a2
feat: call update member route
VK-RED Jul 25, 2024
17278ba
feat: add delete member endpoints
VK-RED Jul 25, 2024
f96fa5a
feat: call delete function in index.ts
VK-RED Jul 25, 2024
7fee741
feat: register teamMembers route
VK-RED Jul 25, 2024
c390cf4
Merge branch 'main' into feat/openapi-for-team-members
VK-RED Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/server/api/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { z } from "@hono/zod-openapi";

const log = logger.child({ module: "api-error" });

const ErrorCode = z.enum([
export const ErrorCode = z.enum([
"BAD_REQUEST",
"FORBIDDEN",
"INTERNAL_SERVER_ERROR",
Expand Down
2 changes: 2 additions & 0 deletions src/server/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { initMiddleware } from "./middlewares/init";
import { registerCompanyRoutes } from "./routes/company";
import { registerShareRoutes } from "./routes/company/share";
import { registerStakeholderRoutes } from "./routes/company/stakeholder";
import { registerTeamMemberRoutes } from "./routes/company/team-member";

const api = PublicAPI();

Expand All @@ -12,5 +13,6 @@ api.use("*", initMiddleware());
registerCompanyRoutes(api);
registerShareRoutes(api);
registerStakeholderRoutes(api);
registerTeamMemberRoutes(api);

export default api;
139 changes: 139 additions & 0 deletions src/server/api/routes/company/team-member/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { SendMemberInviteEmailJob } from "@/jobs/member-inivite-email";
import { generatePasswordResetToken } from "@/lib/token";
import type { Roles } from "@/prisma/enums";
import { withCompanyAuth } from "@/server/api/auth";
import { ApiError, ErrorResponses } from "@/server/api/error";
import type { PublicAPI } from "@/server/api/hono";
import {
CreateMemberSchema,
type TeamMember,
TeamMemberSchema,
} from "@/server/api/schema/team-member";
import { getIp } from "@/server/api/utils";
import { createTeamMember } from "@/server/services/team-members/create-team-member";
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({
message: z.string(),
data: TeamMemberSchema,
});

const route = createRoute({
method: "post",
path: "/v1/companies/{id}/teams",
summary: "Create Team Members",
description: "Create Team Members in a company.",
tags: ["Member"],
request: {
params: ParamsSchema,
body: {
content: {
"application/json": {
schema: CreateMemberSchema,
},
},
},
},
responses: {
200: {
content: {
"application/json": {
schema: ResponseSchema,
},
},
description: "Create Team Members",
},
...ErrorResponses,
},
});

const create = (app: PublicAPI) => {
app.openapi(route, async (c: Context) => {
const { company, user } = await withCompanyAuth(c);
const body = await c.req.json();

const email: string = body.email;
const title: string | null = body.title;
const role: Roles | null = body.role;
const customRoleId: string | null = body.customRoleId;

const requestIp = getIp(c.req);
const userAgent = c.req.header("User-Agent") || "";

const { data, success, message } = await createTeamMember({
companyId: company.id,
companyName: company.name,
email,
customRoleId,
name: user.name || "",
requestIp,
userAgent,
userId: user.id,
role,
title,
});

if (!success && !data) {
throw new ApiError({
code: "BAD_REQUEST",
message,
});
}

const verificationToken = data.verificationToken as string;
const member = data.member;

const currentTime = new Date().toISOString();

const responseData: TeamMember = {
companyId: member?.companyId || "",
customRoleId: member?.customRoleId || null,
isOnboarded: member?.isOnboarded || true,
role: member?.role || null,
status: member?.status || "",
title: member?.title || "",
userId: member?.userId || "",
workEmail: member?.workEmail || "",
id: member?.id || "",
createdAt: member?.createdAt.toString() || currentTime,
lastAccessed: member?.lastAccessed.toString() || currentTime,
updatedAt: member?.updatedAt.toString() || currentTime,
};

const { token: passwordResetToken } =
await generatePasswordResetToken(email);

const payload = {
verificationToken,
passwordResetToken,
email,
company,
user: {
email: user.email,
name: user.name,
},
};

await new SendMemberInviteEmailJob().emit(payload);

return c.json({ message, data: responseData });
});
};

export default create;
81 changes: 81 additions & 0 deletions src/server/api/routes/company/team-member/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { withCompanyAuth } from "@/server/api/auth";
import { ApiError, ErrorResponses } from "@/server/api/error";
import type { PublicAPI } from "@/server/api/hono";
import { getHonoUserAgent, getIp } from "@/server/api/utils";
import {
type DeleteMemberPayload,
deleteMember,
} from "@/server/services/team-members/delete-member";
import { createRoute, z } from "@hono/zod-openapi";
import type { Context } from "hono";
import { RequestParamsSchema } from "./update";

const ResponseSchema = z
.object({
message: z.string(),
})
.openapi({
description: "Delete a Team Member by ID",
});

const DeleteParamsSchema = RequestParamsSchema.openapi({
description: "Delete a Team Member by ID",
});

const route = createRoute({
method: "delete",
path: "/v1/companies/{id}/teams/{memberId}",
summary: "Delete a Team Member by ID",
description: "Delete a Team Member by ID",
tags: ["Member"],
request: {
params: DeleteParamsSchema,
},
responses: {
200: {
content: {
"application/json": {
schema: ResponseSchema,
},
},
description: "Delete a Share by ID",
},
...ErrorResponses,
},
});

const deleteOne = (app: PublicAPI) => {
app.openapi(route, async (c: Context) => {
const { company, user } = await withCompanyAuth(c);
const { memberId } = c.req.param();

const payload: DeleteMemberPayload = {
companyId: company.id,
memberId: memberId as string,
requestIp: getIp(c.req),
userAgent: getHonoUserAgent(c.req),
user: {
id: user.id,
name: user.name as string,
},
};

const { success, message } = await deleteMember(payload);

if (!success) {
throw new ApiError({
code: "BAD_REQUEST",
message,
});
}

return c.json(
{
message: message,
},
200,
);
});
};

export default deleteOne;
86 changes: 86 additions & 0 deletions src/server/api/routes/company/team-member/getMany.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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";
import { TeamMemberSchema } from "@/server/api/schema/team-member";
import { getPaginatedMembers } from "@/server/services/team-members/get-members";
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(TeamMemberSchema),
meta: PaginationResponseSchema,
})
.openapi({
description: "Get Team Members by Company ID",
});

const route = createRoute({
method: "get",
path: "/v1/companies/{id}/teams",
summary: "Get list of Team Members",
description: "Get list of Team Members for a company",
tags: ["Member"],
request: {
params: ParamsSchema,
query: PaginationQuerySchema,
},
responses: {
200: {
content: {
"application/json": {
schema: ResponseSchema,
},
},
description: "Retrieve Team Members 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 take = limit;

const { data, meta } = await getPaginatedMembers({
companyId: company.id,
take: Number(take || DEFAULT_PAGINATION_LIMIT),
cursor,
total: Number(total),
});

return c.json(
{
data,
meta,
},
200,
);
});
};

export default getMany;
Loading