Skip to content

Commit 3fd7704

Browse files
feat: add concurrency control to getEntityInfo
1 parent baf09c4 commit 3fd7704

9 files changed

Lines changed: 90 additions & 20 deletions

File tree

openapi.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ openapi: 3.0.0
22
info:
33
title: Workflow Approval System API
44
description: API for a SaaS platform that allows customers to manage approvals for generic workflows, users, and groups.
5-
version: 0.0.41
5+
version: 0.0.42
66
tags:
77
- name: Authentication
88
description: OIDC authentication and token management

openapi/auth/schemas.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ GetEntityInfoUserResponse:
9090
- groups
9191
- roles
9292
- orgRole
93+
- concurrencyControl
9394
properties:
9495
id:
9596
type: string
@@ -115,6 +116,8 @@ GetEntityInfoUserResponse:
115116
- admin
116117
- member
117118
description: Role assigned to the user within the organization.
119+
concurrencyControl:
120+
$ref: ../shared/schemas.yaml#/ConcurrencyControl
118121

119122
GetEntityInfoAgentResponse:
120123
type: object

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@approvio/api",
33
"author": "Giovanni Baratta",
44
"license": "MIT",
5-
"version": "0.0.47",
5+
"version": "0.0.48",
66
"private": false,
77
"type": "module",
88
"main": "./dist/src/index.cjs",
@@ -71,4 +71,4 @@
7171
"peerDependencies": {
7272
"fp-ts": "^2.0.0"
7373
}
74-
}
74+
}

src/validators/common.validators.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "../../generated/openapi/model/models"
1010
import {validateGroupInfo} from "./groups.validators"
1111
import {validateRoleOperationItem} from "./users.validators"
12+
import {validateConcurrencyControl} from "./concurrency-control"
1213

1314
export type PaginationValidationError =
1415
| "malformed_object"
@@ -117,6 +118,7 @@ export type GetEntityInfo200ResponseValidationError =
117118
| "invalid_roles"
118119
| "missing_org_role"
119120
| "invalid_org_role"
121+
| "invalid_concurrency_control"
120122

121123
export function validateGetEntityInfo200Response(
122124
object: unknown
@@ -153,12 +155,17 @@ export function validateGetEntityInfo200Response(
153155
if (!hasOwnProperty(object, "orgRole")) return left("missing_org_role")
154156
if (object.orgRole !== "admin" && object.orgRole !== "member") return left("invalid_org_role")
155157

158+
if (!hasOwnProperty(object, "concurrencyControl")) return left("invalid_concurrency_control")
159+
const concurrencyControlValidation = validateConcurrencyControl(object.concurrencyControl)
160+
if (isLeft(concurrencyControlValidation)) return left("invalid_concurrency_control")
161+
156162
return right({
157163
entityType: "user" as const,
158164
id: object.id,
159165
groups,
160166
roles,
161-
orgRole: object.orgRole
167+
orgRole: object.orgRole,
168+
concurrencyControl: concurrencyControlValidation.right
162169
})
163170
} else if (object.entityType === "agent") {
164171
return right({
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {Either, left, right} from "fp-ts/lib/Either"
2+
import {hasOwnProperty, isStringBigInt} from "../utils/validation.utils"
3+
import {ConcurrencyControl} from "../../generated/openapi/model/concurrency-control"
4+
5+
export function validateConcurrencyControl(object: unknown): Either<"invalid_concurrency_control", ConcurrencyControl> {
6+
if (typeof object !== "object" || object === null) return left("invalid_concurrency_control")
7+
if (!hasOwnProperty(object, "version") || !isStringBigInt(object.version)) return left("invalid_concurrency_control")
8+
return right({version: object.version})
9+
}

src/validators/users.validators.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import {
1111
GroupInfo
1212
} from "../../generated/openapi/model/models"
1313
import {Either, left, right, isLeft, isRight} from "fp-ts/Either"
14-
import {hasOwnProperty, isNonEmptyString, isArray} from "../utils/validation.utils"
14+
import {hasOwnProperty, isNonEmptyString, isArray, isValidUUID} from "../utils/validation.utils"
1515
import {validatePagination, validateSharedListParams} from "./common.validators"
1616
import {validateGroupInfo} from "./groups.validators"
17-
import {validateConcurrencyControl} from "./workflow-templates.validators"
17+
import {validateConcurrencyControl} from "./concurrency-control"
1818

1919
export type UserValidationError =
2020
| "malformed_object"
@@ -212,15 +212,21 @@ export function validateRoleScope(object: unknown): Either<RoleScopeValidationEr
212212
return right({type: "org"})
213213
} else if (object.type === "space") {
214214
if (!hasOwnProperty(object, "spaceId")) return left("missing_space_id")
215-
if (!isNonEmptyString(object.spaceId)) return left("invalid_space_id")
215+
if (!isNonEmptyString(object.spaceId) || !isValidUUID(object.spaceId)) {
216+
return left("invalid_space_id")
217+
}
216218
return right({type: "space", spaceId: object.spaceId})
217219
} else if (object.type === "group") {
218220
if (!hasOwnProperty(object, "groupId")) return left("missing_group_id")
219-
if (!isNonEmptyString(object.groupId)) return left("invalid_group_id")
221+
if (!isNonEmptyString(object.groupId) || !isValidUUID(object.groupId)) {
222+
return left("invalid_group_id")
223+
}
220224
return right({type: "group", groupId: object.groupId})
221225
} else if (object.type === "workflow_template") {
222226
if (!hasOwnProperty(object, "workflowTemplateId")) return left("missing_workflow_template_id")
223-
if (!isNonEmptyString(object.workflowTemplateId)) return left("invalid_workflow_template_id")
227+
if (!isNonEmptyString(object.workflowTemplateId) || !isValidUUID(object.workflowTemplateId)) {
228+
return left("invalid_workflow_template_id")
229+
}
224230
return right({type: "workflow_template", workflowTemplateId: object.workflowTemplateId})
225231
}
226232

@@ -252,6 +258,7 @@ export function validateRoleAssignmentRequest(
252258

253259
if (!hasOwnProperty(object, "roles")) return left("missing_roles")
254260
if (!isArray(object.roles)) return left("invalid_roles")
261+
if (object.roles.length === 0) return left("invalid_roles")
255262

256263
const roles: RoleOperationItem[] = []
257264
for (const role of object.roles) {

src/validators/workflow-templates.validators.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Either, left, right, isLeft} from "fp-ts/Either"
2-
import {hasOwnProperty, isNonEmptyString, isStringBigInt} from "../utils/validation.utils"
2+
import {hasOwnProperty, isNonEmptyString} from "../utils/validation.utils"
33
import {getStringAsEnum} from "../utils/enum"
44
import {
55
WorkflowTemplate,
@@ -9,7 +9,6 @@ import {
99
WorkflowTemplateScope,
1010
WorkflowTemplateSummary,
1111
WorkflowTemplateStatus,
12-
ConcurrencyControl,
1312
ListWorkflowTemplates200Response,
1413
ListWorkflowTemplatesParams,
1514
ApprovalRule,
@@ -22,6 +21,7 @@ import {
2221
} from "../../generated/openapi/model/models"
2322
import {ListParamsValidationError, validatePagination, validateSharedListParams} from "./common.validators"
2423
import {prefixLeft, PrefixUnion} from "../utils/types"
24+
import {validateConcurrencyControl} from "./concurrency-control"
2525

2626
type EmailActionValidationError =
2727
| "malformed_object"
@@ -661,9 +661,3 @@ export function validateListWorkflowTemplatesParams(
661661

662662
return right(result)
663663
}
664-
665-
export function validateConcurrencyControl(object: unknown): Either<"invalid_concurrency_control", ConcurrencyControl> {
666-
if (typeof object !== "object" || object === null) return left("invalid_concurrency_control")
667-
if (!hasOwnProperty(object, "version") || !isStringBigInt(object.version)) return left("invalid_concurrency_control")
668-
return right({version: object.version})
669-
}

test/validators/common.validators.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ describe("common validators", () => {
6060
id: "00000000-0000-4000-8000-000000000000",
6161
roles: [],
6262
orgRole: "admin",
63-
groups: [{groupId: "00000000-0000-4000-8000-000000000000", groupName: "Group 1"}]
63+
groups: [{groupId: "00000000-0000-4000-8000-000000000000", groupName: "Group 1"}],
64+
concurrencyControl: {version: "1"}
6465
}
6566
const result = validateGetEntityInfo200Response(validReq)
6667
expect(result).toBeRightOf(validReq)

test/validators/users.validators.test.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,18 @@ describe("user validators", () => {
394394

395395
it("should return left('invalid_roles') when roles is not an array", () => {
396396
// Given
397-
const input = {roles: "admin"}
397+
const input = {roles: "admin", concurrencyControl: {version: "1"}}
398+
399+
// When
400+
const result = validateRoleAssignmentRequest(input)
401+
402+
// Expect
403+
expect(result).toBeLeftOf("invalid_roles")
404+
})
405+
406+
it("should return left('invalid_roles') when roles is empty array", () => {
407+
// Given
408+
const input = {roles: [], concurrencyControl: {version: "1"}}
398409

399410
// When
400411
const result = validateRoleAssignmentRequest(input)
@@ -405,7 +416,7 @@ describe("user validators", () => {
405416

406417
it("should return left('invalid_roles') when roles contains invalid item", () => {
407418
// Given
408-
const input = {roles: [{roleName: "admin"}]}
419+
const input = {roles: [{roleName: "admin"}], concurrencyControl: {version: "1"}}
409420

410421
// When
411422
const result = validateRoleAssignmentRequest(input)
@@ -414,6 +425,44 @@ describe("user validators", () => {
414425
expect(result).toBeLeftOf("invalid_roles")
415426
})
416427

428+
it("should return left('invalid_roles') when spaceId is not a valid UUID", () => {
429+
// Given
430+
const input = {
431+
roles: [
432+
{
433+
roleName: "admin",
434+
scope: {type: "space", spaceId: "not-a-uuid"}
435+
}
436+
],
437+
concurrencyControl: {version: "1"}
438+
}
439+
440+
// When
441+
const result = validateRoleAssignmentRequest(input)
442+
443+
// Expect
444+
expect(result).toBeLeftOf("invalid_roles")
445+
})
446+
447+
it("should return right when spaceId is a valid UUID", () => {
448+
// Given
449+
const input = {
450+
roles: [
451+
{
452+
roleName: "admin",
453+
scope: {type: "space", spaceId: "018f1c8f-2878-7c8a-9f4a-9b5a1a1f3c3a"}
454+
}
455+
],
456+
concurrencyControl: {version: "1"}
457+
}
458+
459+
// When
460+
const result = validateRoleAssignmentRequest(input)
461+
462+
// Expect
463+
expect(result).toBeRightOf(input)
464+
})
465+
417466
it("should return left('invalid_concurrency_control') when concurrencyControl is missing", () => {
418467
// Given
419468
// eslint-disable-next-line @typescript-eslint/no-unused-vars

0 commit comments

Comments
 (0)