chore: minimum required roles guard api-v2 (#15576)
* chore: minimum required roles guard api-v2 * fixup! chore: minimum required roles guard api-v2 * fixup! fixup! chore: minimum required roles guard api-v2 * fixup! Merge branch 'chore-roles-guard-api-v2' of github.com:calcom/cal.com into chore-roles-guard-api-v2 * fixup! Merge branch 'chore-roles-guard-api-v2' of github.com:calcom/cal.com into chore-roles-guard-api-v2
This commit is contained in:
1 parent
6632c26063
commit
7f1d45bb5d
12 files changed
+291
-26
No files matched your search
@@ -0,0 +1,13 @@
|
||||
import { MembershipRole } from "@prisma/client";
|
||||
|
||||
export const SYSTEM_ADMIN_ROLE = "SYSADMIN";
|
||||
export const ORG_ROLES = [
|
||||
`ORG_${MembershipRole.OWNER}`,
|
||||
`ORG_${MembershipRole.ADMIN}`,
|
||||
`ORG_${MembershipRole.MEMBER}`,
|
||||
] as const;
|
||||
export const TEAM_ROLES = [
|
||||
`TEAM_${MembershipRole.OWNER}`,
|
||||
`TEAM_${MembershipRole.ADMIN}`,
|
||||
`TEAM_${MembershipRole.MEMBER}`,
|
||||
] as const;
|
||||
@@ -0,0 +1,4 @@
|
||||
import { Reflector } from "@nestjs/core";
|
||||
import { MembershipRole } from "@prisma/client";
|
||||
|
||||
export const MembershipRoles = Reflector.createDecorator<MembershipRole[]>();
|
||||
@@ -1,4 +1,6 @@
|
||||
import { SYSTEM_ADMIN_ROLE, ORG_ROLES, TEAM_ROLES } from "@/lib/roles/constants";
|
||||
import { Reflector } from "@nestjs/core";
|
||||
import { MembershipRole } from "@prisma/client";
|
||||
|
||||
export const Roles = Reflector.createDecorator<MembershipRole[]>();
|
||||
export const Roles = Reflector.createDecorator<
|
||||
(typeof ORG_ROLES)[number] | (typeof TEAM_ROLES)[number] | typeof SYSTEM_ADMIN_ROLE
|
||||
>();
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { MembershipRoles } from "@/modules/auth/decorators/roles/membership-roles.decorator";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { OrganizationsService } from "@/modules/organizations/services/organizations.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
@@ -27,7 +27,7 @@ export class OrganizationRolesGuard implements CanActivate {
|
||||
await this.isPlatform(organizationId);
|
||||
|
||||
const membership = await this.membershipRepository.findOrgUserMembership(organizationId, user.id);
|
||||
const allowedRoles = this.reflector.get(Roles, context.getHandler());
|
||||
const allowedRoles = this.reflector.get(MembershipRoles, context.getHandler());
|
||||
|
||||
this.isMembershipAccepted(membership.accepted);
|
||||
this.isRoleAllowed(membership.role, allowedRoles);
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { ORG_ROLES, TEAM_ROLES, SYSTEM_ADMIN_ROLE } from "@/lib/roles/constants";
|
||||
import { GetUserReturnType } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException, Logger } from "@nestjs/common";
|
||||
import { Reflector } from "@nestjs/core";
|
||||
import { Request } from "express";
|
||||
|
||||
import { Team } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
private readonly logger = new Logger("RolesGuard Logger");
|
||||
constructor(private reflector: Reflector, private membershipRepository: MembershipsRepository) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request & { team: Team }>();
|
||||
const teamId = request.params.teamId as string;
|
||||
const orgId = request.params.orgId as string;
|
||||
const user = request.user as GetUserReturnType;
|
||||
const allowedRole = this.reflector.get(Roles, context.getHandler());
|
||||
|
||||
// User is not authenticated
|
||||
if (!user) {
|
||||
this.logger.log("User is not authenticated, denying access.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// System admin can access everything
|
||||
if (user.isSystemAdmin) {
|
||||
this.logger.log(`User (${user.id}) is system admin, allowing access.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// if the required role is SYSTEM_ADMIN_ROLE but user is not system admin, return false
|
||||
if (allowedRole === SYSTEM_ADMIN_ROLE && !user.isSystemAdmin) {
|
||||
this.logger.log(`User (${user.id}) is not system admin, denying access.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checking the role of the user within the organization
|
||||
if (Boolean(orgId) && !Boolean(teamId)) {
|
||||
const membership = await this.membershipRepository.findMembershipByOrgId(Number(orgId), user.id);
|
||||
if (!membership) {
|
||||
this.logger.log(`User (${user.id}) is not a member of the organization (${orgId}), denying access.`);
|
||||
throw new ForbiddenException(`User is not a member of the organization.`);
|
||||
}
|
||||
|
||||
if (ORG_ROLES.includes(allowedRole as unknown as (typeof ORG_ROLES)[number])) {
|
||||
return hasMinimumRole({
|
||||
checkRole: `ORG_${membership.role}`,
|
||||
minimumRole: allowedRole,
|
||||
roles: ORG_ROLES,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Checking the role of the user within the team
|
||||
if (Boolean(teamId) && !Boolean(orgId)) {
|
||||
const membership = await this.membershipRepository.findMembershipByTeamId(Number(teamId), user.id);
|
||||
if (!membership) {
|
||||
this.logger.log(`User (${user.id}) is not a member of the team (${teamId}), denying access.`);
|
||||
throw new ForbiddenException(`User is not a member of the team.`);
|
||||
}
|
||||
if (TEAM_ROLES.includes(allowedRole as unknown as (typeof TEAM_ROLES)[number])) {
|
||||
return hasMinimumRole({
|
||||
checkRole: `TEAM_${membership.role}`,
|
||||
minimumRole: allowedRole,
|
||||
roles: TEAM_ROLES,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Checking the role for team and org, org is above team in term of permissions
|
||||
if (Boolean(teamId) && Boolean(orgId)) {
|
||||
const teamMembership = await this.membershipRepository.findMembershipByTeamId(Number(teamId), user.id);
|
||||
const orgMembership = await this.membershipRepository.findMembershipByOrgId(Number(orgId), user.id);
|
||||
|
||||
if (!orgMembership) {
|
||||
this.logger.log(`User (${user.id}) is not part of the organization (${orgId}), denying access.`);
|
||||
throw new ForbiddenException(`User is not part of the organization.`);
|
||||
}
|
||||
|
||||
// if the role checked is a TEAM role
|
||||
if (TEAM_ROLES.includes(allowedRole as unknown as (typeof TEAM_ROLES)[number])) {
|
||||
// if the user is admin or owner of org, allow request because org > team
|
||||
if (`ORG_${orgMembership.role}` === "ORG_ADMIN" || `ORG_${orgMembership.role}` === "ORG_OWNER") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!teamMembership) {
|
||||
this.logger.log(
|
||||
`User (${user.id}) is not part of the team (${teamId}) and/or, is not an admin nor an owner of the organization (${orgId}).`
|
||||
);
|
||||
throw new ForbiddenException(
|
||||
"User is not part of the team and/or, is not an admin nor an owner of the organization."
|
||||
);
|
||||
}
|
||||
|
||||
// if user is not admin nor an owner of org, and is part of the team, then check user team membership role
|
||||
return hasMinimumRole({
|
||||
checkRole: `TEAM_${teamMembership.role}`,
|
||||
minimumRole: allowedRole,
|
||||
roles: TEAM_ROLES,
|
||||
});
|
||||
}
|
||||
|
||||
// if allowed role is a ORG ROLE, check org membersip role
|
||||
if (ORG_ROLES.includes(allowedRole as unknown as (typeof ORG_ROLES)[number])) {
|
||||
return hasMinimumRole({
|
||||
checkRole: `ORG_${orgMembership.role}`,
|
||||
minimumRole: allowedRole,
|
||||
roles: ORG_ROLES,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
type Roles = (typeof ORG_ROLES)[number] | (typeof TEAM_ROLES)[number];
|
||||
|
||||
type HasMinimumTeamRoleProp = {
|
||||
checkRole: (typeof TEAM_ROLES)[number];
|
||||
minimumRole: string;
|
||||
roles: typeof TEAM_ROLES;
|
||||
};
|
||||
|
||||
type HasMinimumOrgRoleProp = {
|
||||
checkRole: (typeof ORG_ROLES)[number];
|
||||
minimumRole: string;
|
||||
roles: typeof ORG_ROLES;
|
||||
};
|
||||
|
||||
type HasMinimumRoleProp = HasMinimumTeamRoleProp | HasMinimumOrgRoleProp;
|
||||
|
||||
export function hasMinimumRole(props: HasMinimumRoleProp): boolean {
|
||||
const checkedRoleIndex = props.roles.indexOf(props.checkRole as never);
|
||||
const requiredRoleIndex = props.roles.indexOf(props.minimumRole as never);
|
||||
|
||||
// minimum role given does not exist
|
||||
if (checkedRoleIndex === -1 || requiredRoleIndex === -1) {
|
||||
throw new Error("Invalid role");
|
||||
}
|
||||
|
||||
return checkedRoleIndex <= requiredRoleIndex;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AppConfig } from "@/config/type";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { MembershipRoles } from "@/modules/auth/decorators/roles/membership-roles.decorator";
|
||||
import { NextAuthGuard } from "@/modules/auth/guards/next-auth/next-auth.guard";
|
||||
import { OrganizationRolesGuard } from "@/modules/auth/guards/organization-roles/organization-roles.guard";
|
||||
import { SubscribeToPlanInput } from "@/modules/billing/controllers/inputs/subscribe-to-plan.input";
|
||||
@@ -47,7 +47,7 @@ export class BillingController {
|
||||
|
||||
@Get("/:teamId/check")
|
||||
@UseGuards(NextAuthGuard, OrganizationRolesGuard)
|
||||
@Roles(["OWNER", "ADMIN", "MEMBER"])
|
||||
@MembershipRoles(["OWNER", "ADMIN", "MEMBER"])
|
||||
async checkTeamBilling(
|
||||
@Param("teamId") teamId: number
|
||||
): Promise<ApiResponse<CheckPlatformBillingResponseDto>> {
|
||||
@@ -64,7 +64,7 @@ export class BillingController {
|
||||
|
||||
@Post("/:teamId/subscribe")
|
||||
@UseGuards(NextAuthGuard, OrganizationRolesGuard)
|
||||
@Roles(["OWNER", "ADMIN"])
|
||||
@MembershipRoles(["OWNER", "ADMIN"])
|
||||
async subscribeTeamToStripe(
|
||||
@Param("teamId") teamId: number,
|
||||
@Body() input: SubscribeToPlanInput
|
||||
|
||||
@@ -18,6 +18,23 @@ export class MembershipsRepository {
|
||||
return membership;
|
||||
}
|
||||
|
||||
async findMembershipByTeamId(teamId: number, userId: number) {
|
||||
const membership = await this.dbRead.prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_teamId: {
|
||||
userId: userId,
|
||||
teamId: teamId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
async findMembershipByOrgId(orgId: number, userId: number) {
|
||||
return this.findMembershipByTeamId(orgId, userId);
|
||||
}
|
||||
|
||||
async isUserOrganizationAdmin(userId: number, organizationId: number) {
|
||||
const adminMembership = await this.dbRead.prisma.membership.findFirst({
|
||||
where: {
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
import { getEnv } from "@/env";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { MembershipRoles } from "@/modules/auth/decorators/roles/membership-roles.decorator";
|
||||
import { NextAuthGuard } from "@/modules/auth/guards/next-auth/next-auth.guard";
|
||||
import { OrganizationRolesGuard } from "@/modules/auth/guards/organization-roles/organization-roles.guard";
|
||||
import { GetManagedUsersOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/get-managed-users.output";
|
||||
@@ -64,7 +64,7 @@ export class OAuthClientsController {
|
||||
|
||||
@Post("/")
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@Roles([MembershipRole.ADMIN, MembershipRole.OWNER])
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
@DocsCreatedResponse({
|
||||
description: "Create an OAuth client",
|
||||
@@ -96,7 +96,7 @@ export class OAuthClientsController {
|
||||
|
||||
@Get("/")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Roles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
async getOAuthClients(@GetUser() user: UserWithProfile): Promise<GetOAuthClientsResponseDto> {
|
||||
const organizationId = (user.movedToProfile?.organizationId ?? user.organizationId) as number;
|
||||
@@ -107,7 +107,7 @@ export class OAuthClientsController {
|
||||
|
||||
@Get("/:clientId")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Roles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
async getOAuthClientById(@Param("clientId") clientId: string): Promise<GetOAuthClientResponseDto> {
|
||||
const client = await this.oauthClientRepository.getOAuthClient(clientId);
|
||||
@@ -119,7 +119,7 @@ export class OAuthClientsController {
|
||||
|
||||
@Get("/:clientId/managed-users")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Roles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
async getOAuthClientManagedUsersById(
|
||||
@Param("clientId") clientId: string,
|
||||
@@ -137,7 +137,7 @@ export class OAuthClientsController {
|
||||
|
||||
@Patch("/:clientId")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Roles([MembershipRole.ADMIN, MembershipRole.OWNER])
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
async updateOAuthClient(
|
||||
@Param("clientId") clientId: string,
|
||||
@@ -150,7 +150,7 @@ export class OAuthClientsController {
|
||||
|
||||
@Delete("/:clientId")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Roles([MembershipRole.ADMIN, MembershipRole.OWNER])
|
||||
@MembershipRoles([MembershipRole.ADMIN, MembershipRole.OWNER])
|
||||
@DocsOperation({ description: AUTH_DOCUMENTATION })
|
||||
async deleteOAuthClient(@Param("clientId") clientId: string): Promise<GetOAuthClientResponseDto> {
|
||||
this.logger.log(`Deleting OAuth Client with ID: ${clientId}`);
|
||||
|
||||
+9
@@ -8,6 +8,7 @@ import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
@@ -23,6 +24,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let organizationsRepositoryFixture: TeamRepositoryFixture;
|
||||
let teamsRepositoryFixture: TeamRepositoryFixture;
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
|
||||
let org: Team;
|
||||
let team: Team;
|
||||
@@ -41,6 +43,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
@@ -52,6 +55,12 @@ describe("Organizations Team Endpoints", () => {
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "ADMIN",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: "Test org team",
|
||||
isOrganization: false,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { GetOrg } from "@/modules/auth/decorators/get-org/get-org.decorator";
|
||||
import { GetTeam } from "@/modules/auth/decorators/get-team/get-team.decorator";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
|
||||
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
|
||||
import { Controller, UseGuards, Get, Param, ParseIntPipe } from "@nestjs/common";
|
||||
import { ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
@@ -31,7 +33,8 @@ export class OrganizationsTeamsController {
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(IsTeamInOrg)
|
||||
@UseGuards(IsTeamInOrg, RolesGuard)
|
||||
@Roles("ORG_ADMIN")
|
||||
@Get("/:teamId")
|
||||
async getTeam(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { OrganizationsTeamsController } from "@/modules/organizations/controllers/organizations-teams.controller";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
|
||||
import { OrganizationsService } from "@/modules/organizations/services/organizations.service";
|
||||
@@ -7,7 +8,7 @@ import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, StripeModule],
|
||||
providers: [OrganizationsRepository, OrganizationsService],
|
||||
providers: [OrganizationsRepository, OrganizationsService, MembershipsRepository],
|
||||
exports: [OrganizationsService, OrganizationsRepository],
|
||||
controllers: [OrganizationsTeamsController],
|
||||
})
|
||||
|
||||
@@ -806,6 +806,74 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsController_getAllTeams",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Organizations Teams"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams/{teamId}": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsController_getTeam",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Organizations Teams"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/schedules": {
|
||||
"post": {
|
||||
"operationId": "SchedulesController_2024_06_11_createSchedule",
|
||||
@@ -2185,11 +2253,11 @@
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
],
|
||||
"example": "success"
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/EventTypeOutput_2024_06_14"
|
||||
@@ -2205,11 +2273,11 @@
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
],
|
||||
"example": "success"
|
||||
},
|
||||
"data": {
|
||||
"nullable": true,
|
||||
@@ -2230,11 +2298,11 @@
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
],
|
||||
"example": "success"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
@@ -2257,11 +2325,11 @@
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
],
|
||||
"example": "success"
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/EventTypeOutput_2024_06_14"
|
||||
@@ -2303,11 +2371,11 @@
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
],
|
||||
"example": "success"
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/DeleteData_2024_06_14"
|
||||
|
||||
Reference in new issue
Block a user