diff --git a/app-modules/alert/graphql/alert.graphql b/app-modules/alert/graphql/alert.graphql index 60831b22e1..4bd5732f0e 100644 --- a/app-modules/alert/graphql/alert.graphql +++ b/app-modules/alert/graphql/alert.graphql @@ -1,6 +1,6 @@ type Alert @model(class: "AdvisingApp\\Alert\\Models\\Alert") { "Unique primary key." - id: ID! + id: UUID! "The concern of the alert." concern: Educatable! @morphTo @@ -29,7 +29,7 @@ type Alert @model(class: "AdvisingApp\\Alert\\Models\\Alert") { extend type Query { "Get an alert by its primary key." - alert("Search by primary key." id: ID! @whereKey): Alert + alert("Search by primary key." id: UUID! @whereKey): Alert @find @canResolved(ability: "view") @@ -39,7 +39,7 @@ extend type Query { input CreateAlertInput { "The id of the concern of the alert." - concern_id: ID! @rules(apply: ["required"]) + concern_id: UUID! @rules(apply: ["required"]) "The type of concern of the alert." concern_type: String! @rules(apply: ["required", "in:student,prospect"]) @@ -80,13 +80,13 @@ extend type Mutation { "Update an existing alert." updateAlert( "The primary key of the alert." - id: ID! @drop @whereKey + id: UUID! @drop @whereKey input: UpdateAlertInput! @spread ): Alert! @update @canFind(ability: "update", find: "id") "Delete an existing alert." - deleteAlert("The primary key of the alert." id: ID! @whereKey): Alert + deleteAlert("The primary key of the alert." id: UUID! @whereKey): Alert @delete @canFind(ability: "delete", find: "id") } diff --git a/app-modules/care-team/graphql/care-team.graphql b/app-modules/care-team/graphql/care-team.graphql index 635b180d0d..f2253731eb 100644 --- a/app-modules/care-team/graphql/care-team.graphql +++ b/app-modules/care-team/graphql/care-team.graphql @@ -1,6 +1,6 @@ type UserCareTeam @model(class: "AdvisingApp\\CareTeam\\Models\\CareTeam") { "Unique primary key." - id: ID! + id: UUID! "The user related to this care team assignment." user: User! @belongsTo "The educatable in the user's care team." @@ -13,7 +13,7 @@ type UserCareTeam @model(class: "AdvisingApp\\CareTeam\\Models\\CareTeam") { extend type Query { "Get a care team assignment by its primary key." - userCareTeam("Search by primary key." id: ID! @whereKey): UserCareTeam + userCareTeam("Search by primary key." id: UUID! @whereKey): UserCareTeam @find @canResolved(ability: "view") @@ -25,7 +25,7 @@ extend type Mutation { "Create a new care team assignment." createUserCareTeam( "The user to add to the care team of." - user_id: ID! + user_id: UUID! @rules( apply: [ "required" @@ -35,7 +35,7 @@ extend type Mutation { ) "The educatable to add to the care team." - educatable_id: ID! + educatable_id: EducatableId! @rules( apply: [ "required" @@ -51,6 +51,6 @@ extend type Mutation { "Delete an existing care team assignment." deleteUserCareTeam( "The primary key of the care team assignment." - id: ID! @whereKey + id: UUID! @whereKey ): UserCareTeam @delete @canFind(ability: "delete", find: "id") } diff --git a/app-modules/division/graphql/division.graphql b/app-modules/division/graphql/division.graphql index c6f026a79b..c01d7f2f23 100644 --- a/app-modules/division/graphql/division.graphql +++ b/app-modules/division/graphql/division.graphql @@ -1,6 +1,6 @@ type Division @model(class: "AdvisingApp\\Division\\Models\\Division") { "Unique primary key." - id: ID! + id: UUID! "The name of the division." name: String! diff --git a/app-modules/interaction/graphql/interaction-campaign.graphql b/app-modules/interaction/graphql/interaction-campaign.graphql index 7ff2e2a465..cda799661d 100644 --- a/app-modules/interaction/graphql/interaction-campaign.graphql +++ b/app-modules/interaction/graphql/interaction-campaign.graphql @@ -1,7 +1,7 @@ type InteractionCampaign @model(class: "AdvisingApp\\Interaction\\Models\\InteractionCampaign") { "Unique primary key." - id: ID! + id: UUID! "The name of the interaction campaign." name: String! @@ -21,7 +21,7 @@ type InteractionCampaign input InteractionCampaignQuery { "The unique primary key of the interaction campaign." - id: ID + id: UUID "The name of the interaction relation." name: String @@ -41,7 +41,7 @@ input InteractionCampaignQuery { extend type Query { "Get a specific interaction campaign by id." - interactionCampaign(id: ID! @eq): InteractionCampaign + interactionCampaign(id: UUID! @eq): InteractionCampaign @find @softDeletes @canResolved(ability: "view") @@ -86,7 +86,7 @@ extend type Mutation { "Update an existing interaction campaign." updateInteractionCampaign( "The identifier of the interaction campaign you would like to update." - id: ID! @whereKey + id: UUID! @whereKey "The fields you would like to update." input: UpdateInteractionCampaignInput! @spread @@ -95,6 +95,6 @@ extend type Mutation { "Delete an existing interaction campaign." deleteInteractionCampaign( "The identifier of the interaction campaign you would like to delete." - id: ID! @whereKey + id: UUID! @whereKey ): InteractionCampaign @canFind(ability: "delete", find: "id") @delete } diff --git a/app-modules/interaction/graphql/interaction-driver.graphql b/app-modules/interaction/graphql/interaction-driver.graphql index 64807a70ad..a4ca724b8a 100644 --- a/app-modules/interaction/graphql/interaction-driver.graphql +++ b/app-modules/interaction/graphql/interaction-driver.graphql @@ -1,7 +1,7 @@ type InteractionDriver @model(class: "AdvisingApp\\Interaction\\Models\\InteractionDriver") { "Unique primary key." - id: ID! + id: UUID! "The name of the interaction driver." name: String! @@ -21,7 +21,7 @@ type InteractionDriver input InteractionDriverQuery { "The unique primary key of the interaction driver." - id: ID + id: UUID "The name of the interaction driver." name: String @@ -41,7 +41,7 @@ input InteractionDriverQuery { extend type Query { "Get a specific interaction driver by id." - interactionDriver(id: ID! @eq): InteractionDriver + interactionDriver(id: UUID! @eq): InteractionDriver @find @softDeletes @canResolved(ability: "view") @@ -84,7 +84,7 @@ extend type Mutation { "Update an existing interaction driver." updateInteractionDriver( "The identifier of the interaction driver you would like to update." - id: ID! @whereKey + id: UUID! @whereKey "The fields you would like to update." input: UpdateInteractionDriverInput! @spread @@ -93,6 +93,6 @@ extend type Mutation { "Delete an existing interaction driver." deleteInteractionDriver( "The identifier of the interaction driver you would like to delete." - id: ID! @whereKey + id: UUID! @whereKey ): InteractionDriver @canFind(ability: "delete", find: "id") @delete } diff --git a/app-modules/interaction/graphql/interaction-outcome.graphql b/app-modules/interaction/graphql/interaction-outcome.graphql index 33bdcbd0cc..370c0dbaac 100644 --- a/app-modules/interaction/graphql/interaction-outcome.graphql +++ b/app-modules/interaction/graphql/interaction-outcome.graphql @@ -1,7 +1,7 @@ type InteractionOutcome @model(class: "AdvisingApp\\Interaction\\Models\\InteractionOutcome") { "Unique primary key." - id: ID! + id: UUID! "The name of the interaction outcome." name: String! @@ -21,7 +21,7 @@ type InteractionOutcome input InteractionOutcomeQuery { "The unique primary key of the interaction outcome." - id: ID + id: UUID "The name of the interaction outcome." name: String @@ -41,7 +41,7 @@ input InteractionOutcomeQuery { extend type Query { "Get a specific interaction outcome by id." - interactionOutcome(id: ID! @eq): InteractionOutcome + interactionOutcome(id: UUID! @eq): InteractionOutcome @find @softDeletes @canResolved(ability: "view") @@ -84,7 +84,7 @@ extend type Mutation { "Update an existing interaction outcome." updateInteractionOutcome( "The identifier of the interaction outcome you would like to update." - id: ID! @whereKey + id: UUID! @whereKey "The fields you would like to update." input: UpdateInteractionOutcomeInput! @spread @@ -93,6 +93,6 @@ extend type Mutation { "Delete an existing interaction outcome." deleteInteractionOutcome( "The identifier of the interaction outcome you would like to delete." - id: ID! @whereKey + id: UUID! @whereKey ): InteractionOutcome @canFind(ability: "delete", find: "id") @delete } diff --git a/app-modules/interaction/graphql/interaction-relation.graphql b/app-modules/interaction/graphql/interaction-relation.graphql index d93896e71d..d1cb975660 100644 --- a/app-modules/interaction/graphql/interaction-relation.graphql +++ b/app-modules/interaction/graphql/interaction-relation.graphql @@ -1,7 +1,7 @@ type InteractionRelation @model(class: "AdvisingApp\\Interaction\\Models\\InteractionRelation") { "Unique primary key." - id: ID! + id: UUID! "The name of the interaction relation." name: String! @@ -21,7 +21,7 @@ type InteractionRelation input InteractionRelationQuery { "The unique primary key of the interaction relation." - id: ID + id: UUID "The name of the interaction relation." name: String @@ -41,7 +41,7 @@ input InteractionRelationQuery { extend type Query { "Get a specific interaction relation by id." - interactionRelation(id: ID! @eq): InteractionRelation + interactionRelation(id: UUID! @eq): InteractionRelation @find @softDeletes @canResolved(ability: "view") @@ -86,7 +86,7 @@ extend type Mutation { "Update an existing interaction relation." updateInteractionRelation( "The identifier of the interaction relation you would like to update." - id: ID! @whereKey + id: UUID! @whereKey "The fields you would like to update." input: UpdateInteractionRelationInput! @spread @@ -95,6 +95,6 @@ extend type Mutation { "Delete an existing interaction relation." deleteInteractionRelation( "The identifier of the interaction relation you would like to delete." - id: ID! @whereKey + id: UUID! @whereKey ): InteractionRelation @canFind(ability: "delete", find: "id") @delete } diff --git a/app-modules/interaction/graphql/interaction-status.graphql b/app-modules/interaction/graphql/interaction-status.graphql index e2ea0762e8..5a5e4295b3 100644 --- a/app-modules/interaction/graphql/interaction-status.graphql +++ b/app-modules/interaction/graphql/interaction-status.graphql @@ -1,7 +1,7 @@ type InteractionStatus @model(class: "AdvisingApp\\Interaction\\Models\\InteractionStatus") { "Unique primary key." - id: ID! + id: UUID! "The name of the interaction driver." name: String! @@ -24,7 +24,7 @@ type InteractionStatus input InteractionStatusesQuery { "The unique primary key of the interaction status." - id: ID + id: UUID "The name of the interaction status." name: String @@ -47,7 +47,7 @@ input InteractionStatusesQuery { extend type Query { "Get a specific interaction status by id." - interactionStatus(id: ID! @eq): InteractionStatus + interactionStatus(id: UUID! @eq): InteractionStatus @find @softDeletes @canResolved(ability: "view") @@ -90,7 +90,7 @@ extend type Mutation { "Update an existing interaction status." updateInteractionStatus( "The identifier of the interaction status you would like to update." - id: ID! @whereKey + id: UUID! @whereKey "The fields you would like to update." input: UpdateInteractionStatusInput! @spread @@ -99,6 +99,6 @@ extend type Mutation { "Delete an existing interaction status." deleteInteractionStatus( "The identifier of the interaction status you would like to delete." - id: ID! @whereKey + id: UUID! @whereKey ): InteractionStatus @canFind(ability: "delete", find: "id") @delete } diff --git a/app-modules/interaction/graphql/interaction-type.graphql b/app-modules/interaction/graphql/interaction-type.graphql index 7f9d12ca72..05e3c8ea7c 100644 --- a/app-modules/interaction/graphql/interaction-type.graphql +++ b/app-modules/interaction/graphql/interaction-type.graphql @@ -1,7 +1,7 @@ type InteractionType @model(class: "AdvisingApp\\Interaction\\Models\\InteractionType") { "Unique primary key." - id: ID! + id: UUID! "The name of the interaction type." name: String! @@ -21,7 +21,7 @@ type InteractionType input InteractionTypeQuery { "The unique primary key of the interaction type." - id: ID + id: UUID "The name of the interaction relation." name: String @@ -41,7 +41,7 @@ input InteractionTypeQuery { extend type Query { "Get a specific interaction type by id." - interactionType(id: ID! @eq): InteractionType + interactionType(id: UUID! @eq): InteractionType @find @softDeletes @canResolved(ability: "view") @@ -81,7 +81,7 @@ extend type Mutation { "Update an existing interaction type." updateInteractionType( "The identifier of the interaction type you would like to update." - id: ID! @whereKey + id: UUID! @whereKey "The fields you would like to update." input: UpdateInteractionTypeInput! @spread @@ -90,6 +90,6 @@ extend type Mutation { "Delete an existing interaction type." deleteInteractionType( "The identifier of the interaction type you would like to delete." - id: ID! @whereKey + id: UUID! @whereKey ): InteractionType @canFind(ability: "delete", find: "id") @delete } diff --git a/app-modules/interaction/graphql/interaction.graphql b/app-modules/interaction/graphql/interaction.graphql index 90507dd0c1..3b2c95418c 100644 --- a/app-modules/interaction/graphql/interaction.graphql +++ b/app-modules/interaction/graphql/interaction.graphql @@ -1,9 +1,13 @@ union Interactable = Student | Prospect | ServiceRequest +scalar InteractableId + @scalar(class: "App\\GraphQL\\Scalars\\InteractableId") + @searchByOperators(type: "ID") + type Interaction @model(class: "AdvisingApp\\Interaction\\Models\\Interaction") { "Unique primary key." - id: ID! + id: UUID! "The subject of the interaction." subject: String! @@ -78,7 +82,7 @@ extend type Query { "Find a single interaction by an identifying attribute." interaction( "The value of the attribute to match." - id: ID! @whereKey + id: UUID! @whereKey ): Interaction @find @canResolved(ability: "view") "List multiple interactions." @@ -95,10 +99,10 @@ input UpdateInteractionInput { description: String @rules(apply: ["string"]) "The User related to the interaction." - user_id: ID @rules(apply: ["exists:users,id"]) + user_id: UUID @rules(apply: ["exists:users,id"]) "The Interactable related to the interaction." - interactable_id: ID + interactable_id: InteractableId @rules( apply: [ "AdvisingApp\\Interaction\\Rules\\InteractableIdExistsRules" @@ -116,27 +120,29 @@ input UpdateInteractionInput { ) "The type of interaction." - interaction_type_id: ID @rules(apply: ["exists:interaction_types,id"]) + interaction_type_id: UUID @rules(apply: ["exists:interaction_types,id"]) "The relation of the interaction." - interaction_relation_id: ID + interaction_relation_id: UUID @rules(apply: ["exists:interaction_relations,id"]) "The campaign of the interaction." - interaction_campaign_id: ID + interaction_campaign_id: UUID @rules(apply: ["exists:interaction_campaigns,id"]) "The driver of the interaction." - interaction_driver_id: ID @rules(apply: ["exists:interaction_drivers,id"]) + interaction_driver_id: UUID @rules(apply: ["exists:interaction_drivers,id"]) "The status of the interaction." - interaction_status_id: ID @rules(apply: ["exists:interaction_statuses,id"]) + interaction_status_id: UUID + @rules(apply: ["exists:interaction_statuses,id"]) "The outcome of the interaction." - interaction_outcome_id: ID @rules(apply: ["exists:interaction_outcomes,id"]) + interaction_outcome_id: UUID + @rules(apply: ["exists:interaction_outcomes,id"]) "The division of the interaction." - division_id: ID @rules(apply: ["exists:divisions,id"]) + division_id: UUID @rules(apply: ["exists:divisions,id"]) "The start datetime of the interaction." start_datetime: DateTime @rules(apply: ["date_format:Y-m-d H:i:s"]) @@ -154,10 +160,10 @@ input CreateInteractionInput { description: String! @rules(apply: ["required", "string"]) "The User related to the interaction." - user_id: ID! @rules(apply: ["required", "exists:users,id"]) + user_id: UUID! @rules(apply: ["required", "exists:users,id"]) "The Interactable related to the interaction." - interactable_id: ID! + interactable_id: InteractableId! @rules( apply: [ "required" @@ -170,31 +176,31 @@ input CreateInteractionInput { @rules(apply: ["required", "in:student,prospect,service_request"]) "The type of interaction." - interaction_type_id: ID! + interaction_type_id: UUID! @rules(apply: ["required", "exists:interaction_types,id"]) "The relation of the interaction." - interaction_relation_id: ID! + interaction_relation_id: UUID! @rules(apply: ["required", "exists:interaction_relations,id"]) "The campaign of the interaction." - interaction_campaign_id: ID! + interaction_campaign_id: UUID! @rules(apply: ["required", "exists:interaction_campaigns,id"]) "The driver of the interaction." - interaction_driver_id: ID! + interaction_driver_id: UUID! @rules(apply: ["required", "exists:interaction_drivers,id"]) "The status of the interaction." - interaction_status_id: ID! + interaction_status_id: UUID! @rules(apply: ["required", "exists:interaction_statuses,id"]) "The outcome of the interaction." - interaction_outcome_id: ID! + interaction_outcome_id: UUID! @rules(apply: ["required", "exists:interaction_outcomes,id"]) "The division of the interaction." - division_id: ID! @rules(apply: ["required", "exists:divisions,id"]) + division_id: UUID! @rules(apply: ["required", "exists:divisions,id"]) "The start datetime of the interaction." start_datetime: DateTime! @@ -214,7 +220,7 @@ extend type Mutation { "Update a interaction." updateInteraction( "The identifier of the interaction you would like to update." - id: ID! @whereKey + id: UUID! @whereKey input: UpdateInteractionInput! @spread ): Interaction! @canFind(ability: "update", find: "id") @update @@ -222,7 +228,7 @@ extend type Mutation { "Delete a interaction." deleteInteraction( "The identifier of the interaction you would like to delete." - id: ID! @whereKey + id: UUID! @whereKey ): Interaction @canFind(ability: "delete", find: "id") @delete } diff --git a/app-modules/knowledge-base/graphql/knowledge-base-item.graphql b/app-modules/knowledge-base/graphql/knowledge-base-item.graphql index 132f8dc7d2..89444eca8c 100644 --- a/app-modules/knowledge-base/graphql/knowledge-base-item.graphql +++ b/app-modules/knowledge-base/graphql/knowledge-base-item.graphql @@ -1,7 +1,7 @@ type KnowledgeBaseItem @feature(feature: "knowledge-management") @model(class: "AdvisingApp\\KnowledgeBase\\Models\\KnowledgeBaseItem") { - id: ID! + id: UUID! } extend type Query @feature(feature: "knowledge-management") { diff --git a/app-modules/notifications/graphql/subscription.graphql b/app-modules/notifications/graphql/subscription.graphql index 3f2d1a640e..1c0dc60882 100644 --- a/app-modules/notifications/graphql/subscription.graphql +++ b/app-modules/notifications/graphql/subscription.graphql @@ -1,7 +1,7 @@ type UserSubscription @model(class: "AdvisingApp\\Notifications\\Models\\Subscription") { "Unique primary key." - id: ID! + id: UUID! "The user related to this subscription." user: User! @belongsTo "The subscribable the user is subscribed to." @@ -16,7 +16,7 @@ extend type Query { "Get a subscription by its primary key." userSubscription( "Search by primary key." - id: ID! @whereKey + id: UUID! @whereKey ): UserSubscription @find @canResolved(ability: "view") "Get all subscriptions." @@ -29,7 +29,7 @@ extend type Mutation { "Create a new subscription." createUserSubscription( "The user to subscribe." - user_id: ID! + user_id: UUID! @rules( apply: [ "required" @@ -39,7 +39,7 @@ extend type Mutation { ) "The subscribable to subscribe to." - subscribable_id: ID! + subscribable_id: EducatableId! @rules( apply: [ "required" @@ -55,6 +55,6 @@ extend type Mutation { "Delete an existing subscription." deleteUserSubscription( "The primary key of the subscription." - id: ID! @whereKey + id: UUID! @whereKey ): UserSubscription @delete @canFind(ability: "delete", find: "id") } diff --git a/app-modules/prospect/graphql/prospect.graphql b/app-modules/prospect/graphql/prospect.graphql index d9b7b55f5a..e96557a0e7 100644 --- a/app-modules/prospect/graphql/prospect.graphql +++ b/app-modules/prospect/graphql/prospect.graphql @@ -1,10 +1,10 @@ type Prospect { "Unique primary key." - id: ID! + id: UUID! - status_id: ID + status_id: UUID - source_id: ID + source_id: UUID first_name: String @@ -14,7 +14,7 @@ type Prospect { } input ProspectQuery { - id: ID + id: UUID first_name: String diff --git a/app-modules/service-management/graphql/service-management.graphql b/app-modules/service-management/graphql/service-management.graphql index 4d9ddc6d7e..bde65e07fa 100644 --- a/app-modules/service-management/graphql/service-management.graphql +++ b/app-modules/service-management/graphql/service-management.graphql @@ -1,14 +1,14 @@ type ServiceRequest @model(class: "AdvisingApp\\ServiceManagement\\Models\\ServiceRequest") { "Unique primary key." - id: ID! + id: UUID! # TODO: Finish fields } input ServiceRequestQuery { "Unique primary key." - id: ID + id: UUID } # TODO: Query and Mutate diff --git a/app/GraphQL/Scalars/EducatableId.php b/app/GraphQL/Scalars/EducatableId.php new file mode 100644 index 0000000000..a27906bb69 --- /dev/null +++ b/app/GraphQL/Scalars/EducatableId.php @@ -0,0 +1,47 @@ + + + Copyright © 2022-2023, Canyon GBS LLC. All rights reserved. + + Advising App™ is licensed under the Elastic License 2.0. For more details, + see https://github.com/canyongbs/advisingapp/blob/main/LICENSE. + + Notice: + + - You may not provide the software to third parties as a hosted or managed + service, where the service provides users with access to any substantial set of + the features or functionality of the software. + - You may not move, change, disable, or circumvent the license key functionality + in the software, and you may not remove or obscure any functionality in the + software that is protected by the license key. + - You may not alter, remove, or obscure any licensing, copyright, or other notices + of the licensor in the software. Any use of the licensor’s trademarks is subject + to applicable law. + - Canyon GBS LLC respects the intellectual property rights of others and expects the + same in return. Canyon GBS™ and Advising App™ are registered trademarks of + Canyon GBS LLC, and we are committed to enforcing and protecting our trademarks + vigorously. + - The software solution, including services, infrastructure, and code, is offered as a + Software as a Service (SaaS) by Canyon GBS LLC. + - Use of this software implies agreement to the license terms and conditions as stated + in the Elastic License 2.0. + + For more information or inquiries please visit our website at + https://www.canyongbs.com or contact us via email at legal@canyongbs.com. + + +*/ + +declare(strict_types = 1); + +namespace App\GraphQL\Scalars; + +use GraphQL\Type\Definition\StringType; + +/** Read more about scalars here: https://webonyx.github.io/graphql-php/type-definitions/scalars. */ +final class EducatableId extends StringType +{ + public ?string $description = 'The `EducatableId` scalar type represents a unique identifier of an educatable entity. Due to the differences between some of the educatable entities, the type of the identifier may vary. A Prospect has a UUID, a Student has a sisid, which can be an integer, string, or UUID.'; +} diff --git a/app/GraphQL/Scalars/InteractableId.php b/app/GraphQL/Scalars/InteractableId.php new file mode 100644 index 0000000000..42ba1d24f2 --- /dev/null +++ b/app/GraphQL/Scalars/InteractableId.php @@ -0,0 +1,47 @@ + + + Copyright © 2022-2023, Canyon GBS LLC. All rights reserved. + + Advising App™ is licensed under the Elastic License 2.0. For more details, + see https://github.com/canyongbs/advisingapp/blob/main/LICENSE. + + Notice: + + - You may not provide the software to third parties as a hosted or managed + service, where the service provides users with access to any substantial set of + the features or functionality of the software. + - You may not move, change, disable, or circumvent the license key functionality + in the software, and you may not remove or obscure any functionality in the + software that is protected by the license key. + - You may not alter, remove, or obscure any licensing, copyright, or other notices + of the licensor in the software. Any use of the licensor’s trademarks is subject + to applicable law. + - Canyon GBS LLC respects the intellectual property rights of others and expects the + same in return. Canyon GBS™ and Advising App™ are registered trademarks of + Canyon GBS LLC, and we are committed to enforcing and protecting our trademarks + vigorously. + - The software solution, including services, infrastructure, and code, is offered as a + Software as a Service (SaaS) by Canyon GBS LLC. + - Use of this software implies agreement to the license terms and conditions as stated + in the Elastic License 2.0. + + For more information or inquiries please visit our website at + https://www.canyongbs.com or contact us via email at legal@canyongbs.com. + + +*/ + +declare(strict_types = 1); + +namespace App\GraphQL\Scalars; + +use GraphQL\Type\Definition\StringType; + +/** Read more about scalars here: https://webonyx.github.io/graphql-php/type-definitions/scalars. */ +final class InteractableId extends StringType +{ + public ?string $description = 'The `InteractableId` scalar type represents a unique identifier of an interactable entity. Due to the differences between some of the educatable entities, the type of the identifier may vary. A Prospect has a UUID, a Student has a sisid, which can be an integer, string, or UUID, and a Service Request has a UUID.'; +} diff --git a/app/GraphQL/Scalars/UUID.php b/app/GraphQL/Scalars/UUID.php new file mode 100644 index 0000000000..cc534f2bd4 --- /dev/null +++ b/app/GraphQL/Scalars/UUID.php @@ -0,0 +1,128 @@ + + + Copyright © 2022-2023, Canyon GBS LLC. All rights reserved. + + Advising App™ is licensed under the Elastic License 2.0. For more details, + see https://github.com/canyongbs/advisingapp/blob/main/LICENSE. + + Notice: + + - You may not provide the software to third parties as a hosted or managed + service, where the service provides users with access to any substantial set of + the features or functionality of the software. + - You may not move, change, disable, or circumvent the license key functionality + in the software, and you may not remove or obscure any functionality in the + software that is protected by the license key. + - You may not alter, remove, or obscure any licensing, copyright, or other notices + of the licensor in the software. Any use of the licensor’s trademarks is subject + to applicable law. + - Canyon GBS LLC respects the intellectual property rights of others and expects the + same in return. Canyon GBS™ and Advising App™ are registered trademarks of + Canyon GBS LLC, and we are committed to enforcing and protecting our trademarks + vigorously. + - The software solution, including services, infrastructure, and code, is offered as a + Software as a Service (SaaS) by Canyon GBS LLC. + - Use of this software implies agreement to the license terms and conditions as stated + in the Elastic License 2.0. + + For more information or inquiries please visit our website at + https://www.canyongbs.com or contact us via email at legal@canyongbs.com. + + +*/ + +declare(strict_types = 1); + +namespace App\GraphQL\Scalars; + +use GraphQL\Error\Error; +use GraphQL\Utils\Utils; +use GraphQL\Language\AST\Node; +use GraphQL\Type\Definition\Type; +use GraphQL\Language\AST\ValueNode; +use GraphQL\Error\InvariantViolation; +use GraphQL\Type\Definition\ScalarType; +use GraphQL\Language\AST\StringValueNode; +use GraphQL\Language\AST\TypeDefinitionNode; +use LastDragon_ru\LaraASP\GraphQL\Builder\BuilderInfo; +use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource; +use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition; + +/** Read more about scalars here: https://webonyx.github.io/graphql-php/type-definitions/scalars. */ +final class UUID extends ScalarType implements TypeDefinition +{ + public string $name = 'UUID'; + + public ?string $description = 'Represents a uniquely identifying string.'; + + /** Serializes an internal value to include in a response. */ + public function serialize(mixed $value): string + { + if (! $this->validateUUID($value)) { + throw new InvariantViolation('Could not serialize following value as UUID: ' . Utils::printSafe($value)); + } + + // Assuming the internal representation of the value is always correct + return $value; + } + + /** Parses an externally provided value (query variable) to use as an input. */ + public function parseValue(mixed $value): string + { + if (! $this->validateUUID($value)) { + throw new InvariantViolation('Cannot represent following value as UUID: ' . Utils::printSafe($value)); + } + + return $value; + } + + /** + * Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input. + * + * Should throw an exception with a client friendly message on invalid value nodes, @param ValueNode&Node $valueNode + * + * @param array|null $variables + * + * @throws Error + * + * @see \GraphQL\Error\ClientAware. + * + */ + public function parseLiteral(Node $valueNode, ?array $variables = null): string + { + // Throw GraphQL\Error\Error vs \UnexpectedValueException to locate the error in the query + if (! $valueNode instanceof StringValueNode) { + throw new Error('Query error: Can only parse strings got: ' . $valueNode->kind, [$valueNode]); + } + + if (! $this->validateUUID($valueNode->value)) { + throw new Error('Not a valid UUID', [$valueNode]); + } + + return $valueNode->value; + } + + public function getTypeName(Manipulator $manipulator, BuilderInfo $builder, TypeSource $source): string + { + return $this->name(); + } + + public function getTypeDefinition( + Manipulator $manipulator, + string $name, + TypeSource $source, + ): TypeDefinitionNode|Type|null { + return $this; + } + + private function validateUUID($value): false|int + { + $pattern = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i'; + + return preg_match($pattern, $value); + } +} diff --git a/graphql/schema.graphql b/graphql/schema.graphql index ce7fbe4249..31b176add0 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -2,6 +2,14 @@ scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime") +scalar UUID + @scalar(class: "App\\GraphQL\\Scalars\\UUID") + @searchByOperators(type: "ID") + +scalar EducatableId + @scalar(class: "App\\GraphQL\\Scalars\\EducatableId") + @searchByOperators(type: "ID") + type Query type Mutation diff --git a/graphql/user.graphql b/graphql/user.graphql index 94ca3fa7ae..6c26f604ea 100644 --- a/graphql/user.graphql +++ b/graphql/user.graphql @@ -2,7 +2,9 @@ extend type Query { "Find a single user by an identifying attribute." user( "Search by primary key." - id: ID @eq @rules(apply: ["prohibits:email", "required_without:email"]) + id: UUID + @eq + @rules(apply: ["prohibits:email", "required_without:email"]) "Search by email address." email: String @@ -19,7 +21,7 @@ extend type Query { input UserQuery { "Unique primary key." - id: ID! + id: UUID! "Users email address." email: String! @@ -28,7 +30,7 @@ input UserQuery { "Account of a person who utilizes this application." type User @model(class: "user") { "Unique primary key." - id: ID! + id: UUID! "Unique email address." email: String!