mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-13 08:26:40 +00:00
Add invitation management
This commit is contained in:
@@ -32,6 +32,8 @@ export enum ActionsEnum {
|
||||
listRoles = "listRoles",
|
||||
updateRole = "updateRole",
|
||||
inviteUser = "inviteUser",
|
||||
listInvitations = "listInvitations",
|
||||
removeInvitation = "removeInvitation",
|
||||
removeUser = "removeUser",
|
||||
listUsers = "listUsers",
|
||||
listSiteRoles = "listSiteRoles",
|
||||
@@ -63,7 +65,7 @@ export enum ActionsEnum {
|
||||
listResourceRules = "listResourceRules",
|
||||
updateResourceRule = "updateResourceRule",
|
||||
listOrgDomains = "listOrgDomains",
|
||||
createNewt = "createNewt",
|
||||
createNewt = "createNewt"
|
||||
}
|
||||
|
||||
export async function checkUserActionPermission(
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum OpenAPITags {
|
||||
Resource = "Resource",
|
||||
Role = "Role",
|
||||
User = "User",
|
||||
Invitation = "Invitation",
|
||||
Target = "Target",
|
||||
Rule = "Rule",
|
||||
AccessToken = "Access Token"
|
||||
|
||||
@@ -143,6 +143,27 @@ authenticated.get(
|
||||
domain.listDomains
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/invitations",
|
||||
verifyOrgAccess,
|
||||
verifyUserHasAction(ActionsEnum.listInvitations),
|
||||
user.listInvitations
|
||||
);
|
||||
|
||||
authenticated.delete(
|
||||
"/org/:orgId/invitations/:inviteId",
|
||||
verifyOrgAccess,
|
||||
verifyUserHasAction(ActionsEnum.removeInvitation),
|
||||
user.removeInvitation
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/users",
|
||||
verifyOrgAccess,
|
||||
verifyUserHasAction(ActionsEnum.listUsers),
|
||||
user.listUsers
|
||||
);
|
||||
|
||||
authenticated.post(
|
||||
"/org/:orgId/create-invite",
|
||||
verifyOrgAccess,
|
||||
@@ -567,7 +588,4 @@ authRouter.post(
|
||||
resource.authWithAccessToken
|
||||
);
|
||||
|
||||
authRouter.post(
|
||||
"/access-token",
|
||||
resource.authWithAccessToken
|
||||
);
|
||||
authRouter.post("/access-token", resource.authWithAccessToken);
|
||||
|
||||
@@ -7,3 +7,5 @@ export * from "./acceptInvite";
|
||||
export * from "./getOrgUser";
|
||||
export * from "./adminListUsers";
|
||||
export * from "./adminRemoveUser";
|
||||
export * from "./listInvitations";
|
||||
export * from "./removeInvitation";
|
||||
|
||||
129
server/routers/user/listInvitations.ts
Normal file
129
server/routers/user/listInvitations.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { userInvites, roles } from "@server/db/schemas";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import { sql } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import { fromZodError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
|
||||
const listInvitationsParamsSchema = z
|
||||
.object({
|
||||
orgId: z.string()
|
||||
})
|
||||
.strict();
|
||||
|
||||
const listInvitationsQuerySchema = z
|
||||
.object({
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.number().int().nonnegative())
|
||||
})
|
||||
.strict();
|
||||
|
||||
async function queryInvitations(orgId: string, limit: number, offset: number) {
|
||||
return await db
|
||||
.select({
|
||||
inviteId: userInvites.inviteId,
|
||||
email: userInvites.email,
|
||||
expiresAt: userInvites.expiresAt,
|
||||
roleId: userInvites.roleId,
|
||||
roleName: roles.name
|
||||
})
|
||||
.from(userInvites)
|
||||
.leftJoin(roles, sql`${userInvites.roleId} = ${roles.roleId}`)
|
||||
.where(sql`${userInvites.orgId} = ${orgId}`)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
}
|
||||
|
||||
export type ListInvitationsResponse = {
|
||||
invitations: NonNullable<Awaited<ReturnType<typeof queryInvitations>>>;
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
};
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/org/{orgId}/invitations",
|
||||
description: "List invitations in an organization.",
|
||||
tags: [OpenAPITags.Org, OpenAPITags.Invitation],
|
||||
request: {
|
||||
params: listInvitationsParamsSchema,
|
||||
query: listInvitationsQuerySchema
|
||||
},
|
||||
responses: {}
|
||||
});
|
||||
|
||||
export async function listInvitations(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
// Validate query parameters
|
||||
const parsedQuery = listInvitationsQuerySchema.safeParse(req.query);
|
||||
if (!parsedQuery.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromZodError(parsedQuery.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
const { limit, offset } = parsedQuery.data;
|
||||
|
||||
// Validate path parameters
|
||||
const parsedParams = listInvitationsParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromZodError(parsedParams.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
const { orgId } = parsedParams.data;
|
||||
|
||||
// Query invitations
|
||||
const invitations = await queryInvitations(orgId, limit, offset);
|
||||
|
||||
// Get total count of invitations
|
||||
const [{ count }] = await db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(userInvites)
|
||||
.where(sql`${userInvites.orgId} = ${orgId}`);
|
||||
|
||||
// Return response
|
||||
return response<ListInvitationsResponse>(res, {
|
||||
data: {
|
||||
invitations,
|
||||
pagination: {
|
||||
total: count,
|
||||
limit,
|
||||
offset
|
||||
}
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Invitations retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
73
server/routers/user/removeInvitation.ts
Normal file
73
server/routers/user/removeInvitation.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { userInvites } from "@server/db/schemas";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const removeInvitationParamsSchema = z
|
||||
.object({
|
||||
orgId: z.string(),
|
||||
inviteId: z.string()
|
||||
})
|
||||
.strict();
|
||||
|
||||
export async function removeInvitation(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
// Validate path parameters
|
||||
const parsedParams = removeInvitationParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error).toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { orgId, inviteId } = parsedParams.data;
|
||||
|
||||
// Delete the invitation from the database
|
||||
const deletedInvitation = await db
|
||||
.delete(userInvites)
|
||||
.where(
|
||||
and(
|
||||
eq(userInvites.orgId, orgId),
|
||||
eq(userInvites.inviteId, inviteId)
|
||||
)
|
||||
)
|
||||
.returning();
|
||||
|
||||
// If no rows were deleted, the invitation was not found
|
||||
if (deletedInvitation.length === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Invitation with ID ${inviteId} not found in organization ${orgId}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Return success response
|
||||
return response(res, {
|
||||
data: null,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Invitation removed successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user