From 7a57edd59dbc574a6286b7164c6de479005a44e9 Mon Sep 17 00:00:00 2001 From: Juliano Quatrin Nunes Date: Mon, 20 Jan 2025 14:02:04 -0300 Subject: [PATCH] feat: add mutation collect special badgE --- .../certified_delegate.rb | 9 +++++ .../app/graphql/mutations/collect_badge.rb | 39 +++++++++++++++++++ .../app/graphql/types/mutation_type.rb | 3 ++ apps/govquests-api/rails_app/schema.graphql | 36 +++++++++++++++++ apps/govquests-frontend/src/graphql-env.d.ts | 4 +- 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 apps/govquests-api/govquests/gamification/lib/gamification/special_badge_strategies/certified_delegate.rb create mode 100644 apps/govquests-api/rails_app/app/graphql/mutations/collect_badge.rb diff --git a/apps/govquests-api/govquests/gamification/lib/gamification/special_badge_strategies/certified_delegate.rb b/apps/govquests-api/govquests/gamification/lib/gamification/special_badge_strategies/certified_delegate.rb new file mode 100644 index 00000000..a6861de9 --- /dev/null +++ b/apps/govquests-api/govquests/gamification/lib/gamification/special_badge_strategies/certified_delegate.rb @@ -0,0 +1,9 @@ +module Gamification + module Strategies + class CertifiedDelegate < Base + def verify_completion? + true + end + end + end +end \ No newline at end of file diff --git a/apps/govquests-api/rails_app/app/graphql/mutations/collect_badge.rb b/apps/govquests-api/rails_app/app/graphql/mutations/collect_badge.rb new file mode 100644 index 00000000..e030786d --- /dev/null +++ b/apps/govquests-api/rails_app/app/graphql/mutations/collect_badge.rb @@ -0,0 +1,39 @@ +module Mutations + class CollectBadge < BaseMutation + argument :badge_id, ID, required: true + argument :badge_type, ID, required: true, description: "Type of the badge" + + field :badge_earned, Boolean, null: true + field :errors, [String], null: false + + def resolve(badge_id:, badge_type:) + badge = Gamification::BadgeReadModel.find_by(badge_id: badge_id) + + unless badge + return {badge_earned: false, errors: ["Badge not found"]} + end + + command_bus.call( + Gamification::CollectSpecialBadge.new( + badge_id: badge_id, + user_id: context[:current_user].id + ) + ) + + { + badge_earned: true, + errors: [] + } + rescue Gamification::SpecialBadge::VerificationFailedError + { + badge_earned: false, + errors: ["Badge requirements not met"] + } + rescue => e + { + badge_earned: false, + errors: [e.message] + } + end + end +end diff --git a/apps/govquests-api/rails_app/app/graphql/types/mutation_type.rb b/apps/govquests-api/rails_app/app/graphql/types/mutation_type.rb index 56960f7c..6c9e0faa 100644 --- a/apps/govquests-api/rails_app/app/graphql/types/mutation_type.rb +++ b/apps/govquests-api/rails_app/app/graphql/types/mutation_type.rb @@ -14,5 +14,8 @@ class MutationType < Types::BaseObject # Notifications field :mark_notification_as_read, mutation: Mutations::MarkNotificationAsRead, preauthorize: {with: AuthenticatedGraphqlPolicy} field :mark_all_notifications_as_read, mutation: Mutations::MarkAllNotificationsAsRead, preauthorize: {with: AuthenticatedGraphqlPolicy} + + # Special Badges + field :collect_badge, mutation: Mutations::CollectBadge, preauthorize: {with: AuthenticatedGraphqlPolicy} end end diff --git a/apps/govquests-api/rails_app/schema.graphql b/apps/govquests-api/rails_app/schema.graphql index dec745b6..3010662e 100644 --- a/apps/govquests-api/rails_app/schema.graphql +++ b/apps/govquests-api/rails_app/schema.graphql @@ -71,6 +71,36 @@ type BadgeDisplayData { union BadgeableUnion = Quest | Track +""" +Autogenerated input type of CollectBadge +""" +input CollectBadgeInput { + badgeId: ID! + + """ + Type of the badge + """ + badgeType: ID! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String +} + +""" +Autogenerated return type of CollectBadge. +""" +type CollectBadgePayload { + badgeEarned: Boolean + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + errors: [String!]! +} + """ Autogenerated input type of CompleteActionExecution """ @@ -337,6 +367,12 @@ type MarkNotificationAsReadPayload { } type Mutation { + collectBadge( + """ + Parameters for CollectBadge + """ + input: CollectBadgeInput! + ): CollectBadgePayload completeActionExecution( """ Parameters for CompleteActionExecution diff --git a/apps/govquests-frontend/src/graphql-env.d.ts b/apps/govquests-frontend/src/graphql-env.d.ts index b3e23a08..4b4db92d 100644 --- a/apps/govquests-frontend/src/graphql-env.d.ts +++ b/apps/govquests-frontend/src/graphql-env.d.ts @@ -11,6 +11,8 @@ export type introspection_types = { 'BadgeDisplayData': { kind: 'OBJECT'; name: 'BadgeDisplayData'; fields: { 'description': { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'imageUrl': { name: 'imageUrl'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'sequenceNumber': { name: 'sequenceNumber'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'title': { name: 'title'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; 'BadgeableUnion': { kind: 'UNION'; name: 'BadgeableUnion'; fields: {}; possibleTypes: 'Quest' | 'Track'; }; 'Boolean': unknown; + 'CollectBadgeInput': { kind: 'INPUT_OBJECT'; name: 'CollectBadgeInput'; isOneOf: false; inputFields: [{ name: 'badgeId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'badgeType'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; + 'CollectBadgePayload': { kind: 'OBJECT'; name: 'CollectBadgePayload'; fields: { 'badgeEarned': { name: 'badgeEarned'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'clientMutationId': { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'errors': { name: 'errors'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; }; }; 'CompleteActionExecutionInput': { kind: 'INPUT_OBJECT'; name: 'CompleteActionExecutionInput'; isOneOf: false; inputFields: [{ name: 'actionType'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'discourseVerificationCompletionData'; type: { kind: 'INPUT_OBJECT'; name: 'DiscourseVerificationCompletionDataInput'; ofType: null; }; defaultValue: null }, { name: 'executionId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }, { name: 'gitcoinScoreCompletionData'; type: { kind: 'INPUT_OBJECT'; name: 'GitcoinScoreCompletionDataInput'; ofType: null; }; defaultValue: null }, { name: 'nonce'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }]; }; 'CompleteActionExecutionPayload': { kind: 'OBJECT'; name: 'CompleteActionExecutionPayload'; fields: { 'actionExecution': { name: 'actionExecution'; type: { kind: 'OBJECT'; name: 'ActionExecution'; ofType: null; } }; 'clientMutationId': { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'errors': { name: 'errors'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; }; }; 'CompletionDataInterface': { kind: 'INTERFACE'; name: 'CompletionDataInterface'; fields: { 'actionType': { name: 'actionType'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; possibleTypes: 'DiscourseVerificationCompletionData' | 'EmptyActionCompletionData' | 'GitcoinScoreCompletionData'; }; @@ -37,7 +39,7 @@ export type introspection_types = { 'MarkAllNotificationsAsReadPayload': { kind: 'OBJECT'; name: 'MarkAllNotificationsAsReadPayload'; fields: { 'clientMutationId': { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'errors': { name: 'errors'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; 'success': { name: 'success'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; }; 'MarkNotificationAsReadInput': { kind: 'INPUT_OBJECT'; name: 'MarkNotificationAsReadInput'; isOneOf: false; inputFields: [{ name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'deliveryMethod'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: "\"in_app\"" }, { name: 'notificationId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; }; defaultValue: null }]; }; 'MarkNotificationAsReadPayload': { kind: 'OBJECT'; name: 'MarkNotificationAsReadPayload'; fields: { 'clientMutationId': { name: 'clientMutationId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'errors': { name: 'errors'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; 'notificationDelivery': { name: 'notificationDelivery'; type: { kind: 'OBJECT'; name: 'NotificationDelivery'; ofType: null; } }; }; }; - 'Mutation': { kind: 'OBJECT'; name: 'Mutation'; fields: { 'completeActionExecution': { name: 'completeActionExecution'; type: { kind: 'OBJECT'; name: 'CompleteActionExecutionPayload'; ofType: null; } }; 'generateSiweMessage': { name: 'generateSiweMessage'; type: { kind: 'OBJECT'; name: 'GenerateSiweMessagePayload'; ofType: null; } }; 'markAllNotificationsAsRead': { name: 'markAllNotificationsAsRead'; type: { kind: 'OBJECT'; name: 'MarkAllNotificationsAsReadPayload'; ofType: null; } }; 'markNotificationAsRead': { name: 'markNotificationAsRead'; type: { kind: 'OBJECT'; name: 'MarkNotificationAsReadPayload'; ofType: null; } }; 'signInWithEthereum': { name: 'signInWithEthereum'; type: { kind: 'OBJECT'; name: 'SignInWithEthereumPayload'; ofType: null; } }; 'signOut': { name: 'signOut'; type: { kind: 'OBJECT'; name: 'SignOutPayload'; ofType: null; } }; 'startActionExecution': { name: 'startActionExecution'; type: { kind: 'OBJECT'; name: 'StartActionExecutionPayload'; ofType: null; } }; }; }; + 'Mutation': { kind: 'OBJECT'; name: 'Mutation'; fields: { 'collectBadge': { name: 'collectBadge'; type: { kind: 'OBJECT'; name: 'CollectBadgePayload'; ofType: null; } }; 'completeActionExecution': { name: 'completeActionExecution'; type: { kind: 'OBJECT'; name: 'CompleteActionExecutionPayload'; ofType: null; } }; 'generateSiweMessage': { name: 'generateSiweMessage'; type: { kind: 'OBJECT'; name: 'GenerateSiweMessagePayload'; ofType: null; } }; 'markAllNotificationsAsRead': { name: 'markAllNotificationsAsRead'; type: { kind: 'OBJECT'; name: 'MarkAllNotificationsAsReadPayload'; ofType: null; } }; 'markNotificationAsRead': { name: 'markNotificationAsRead'; type: { kind: 'OBJECT'; name: 'MarkNotificationAsReadPayload'; ofType: null; } }; 'signInWithEthereum': { name: 'signInWithEthereum'; type: { kind: 'OBJECT'; name: 'SignInWithEthereumPayload'; ofType: null; } }; 'signOut': { name: 'signOut'; type: { kind: 'OBJECT'; name: 'SignOutPayload'; ofType: null; } }; 'startActionExecution': { name: 'startActionExecution'; type: { kind: 'OBJECT'; name: 'StartActionExecutionPayload'; ofType: null; } }; }; }; 'Node': { kind: 'INTERFACE'; name: 'Node'; fields: { 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; }; possibleTypes: 'Notification' | 'NotificationDelivery'; }; 'Notification': { kind: 'OBJECT'; name: 'Notification'; fields: { 'content': { name: 'content'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ISO8601DateTime'; ofType: null; }; } }; 'deliveries': { name: 'deliveries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'NotificationDeliveryConnection'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'notificationType': { name: 'notificationType'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; 'NotificationConnection': { kind: 'OBJECT'; name: 'NotificationConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'NotificationEdge'; ofType: null; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Notification'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; }; };