Skip to content

Commit

Permalink
feat: enable user to collect special badge
Browse files Browse the repository at this point in the history
  • Loading branch information
juliano-quatrin-nunes committed Jan 20, 2025
1 parent 7a57edd commit dd68161
Show file tree
Hide file tree
Showing 15 changed files with 102 additions and 17 deletions.
13 changes: 13 additions & 0 deletions apps/govquests-api/govquests/gamification/lib/gamification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
require_relative "gamification/user_badge"
require_relative "gamification/special_badge"

require_relative "gamification/strategies/base"
require_relative "gamification/strategies/special_badge_strategy_factory"

Dir[File.join(__dir__, "gamification/strategies/*.rb")].each do |f|
next if f.end_with?("base.rb")
next if f.end_with?("special_badge_strategy_factory.rb")
require_relative f
end

puts "Available constants in Gamification::Strategies: #{Gamification::Strategies.constants}"

ACTION_BADGE_NAMESPACE_UUID = "5FA78373-03E0-4D0B-91D1-3F2C6CA3F088"

module Gamification
Expand All @@ -23,6 +34,8 @@ def generate_badge_id(entity_name, entity_id)
class Configuration
def call(event_store, command_bus)
CommandHandler.register_commands(event_store, command_bus)

Gamification.command_bus = command_bus
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ def verify_completion?(user_id:)
strategy.verify_completion?
end

def collect_badge(user_id:)
def collect_badge(user_id)
raise VerificationFailedError unless verify_completion?(user_id: user_id)

apply EarnBadge.new(data: {
user_id:,
badge_id: @id,
badge_type: "Gamification::SpecialBadgeReadModel",
})
Gamification.command_bus.call(
EarnBadge.new(
user_id: user_id,
badge_id: @id,
badge_type: "Gamification::SpecialBadgeReadModel"
)
)
end

private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ def strategies

def for(badge_type, **dependencies)
strategy_name = badge_type.to_s.camelize
strategy_class = "Gamification::SpecialBadgeStrategies::#{strategy_name}".constantize
strategy_class = "Gamification::Strategies::#{strategy_name}".constantize

puts "Looking for class: Gamification::Strategies::#{strategy_name}" # Debug

strategy_class.new(**dependencies)
rescue NameError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def call(event)
@command_bus.call(
::Gamification::EarnBadge.new(
user_id:,
badge_id: badge.id.to_s,
badge_id: badge.badge_id,
badge_type: badge.class.name,
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
module Mutations
class CollectBadge < BaseMutation
argument :badge_id, ID, required: true
argument :badge_type, ID, required: true, description: "Type of the badge"
argument :badge_type, String, 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)
badge = Gamification::SpecialBadgeReadModel.find_by(badge_id: badge_id)

unless badge
return {badge_earned: false, errors: ["Badge not found"]}
end

command_bus.call(
Rails.configuration.command_bus.call(
Gamification::CollectSpecialBadge.new(
badge_id: badge_id,
user_id: context[:current_user].id
user_id: context[:current_user].user_id
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ def call(event)
badge_type = event.data[:badge_type]
earned_at = event.data[:earned_at]

badge = badge_type.constantize.find_by(badge_id: badge_id)

UserBadgeReadModel.create!(
user_id:,
badge_id: badge_id,
badge_id: badge.id,
badge_type: badge_type,
earned_at: earned_at
)
Expand Down
2 changes: 1 addition & 1 deletion apps/govquests-api/rails_app/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ input CollectBadgeInput {
"""
Type of the badge
"""
badgeType: ID!
badgeType: String!

"""
A unique identifier for the client performing the mutation.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
import { Button } from "@/components/ui/Button";
import { useState } from "react";
import { useFetchSpecialBadge } from "../hooks/useFetchSpecialBadge";
import { useCollectBadge } from "../hooks/useCollectBadge";

export const SpecialBadgeContent = ({ badgeId }: { badgeId: string }) => {
const { data } = useFetchSpecialBadge(badgeId);

const isCompleted = data.specialBadge.earnedByCurrentUser;

const { mutate } = useCollectBadge();

const [error, setError] = useState(null);

const handleCollectBadge = () => {
mutate(
{
badgeId,
badgeType: data.specialBadge.badgeType,
},
{
onSuccess: (result) => {
if (result.collectBadge.errors) {
setError(result.collectBadge.errors);
} else {
setError(null);
}
},
onError: (error) => {
setError(error.message);
},
},
);
};

return (
data && (
<div className="flex flex-col gap-4">
Expand All @@ -27,7 +51,11 @@ export const SpecialBadgeContent = ({ badgeId }: { badgeId: string }) => {
now.
</span>
<div className="flex flex-col gap-2 justify-center items-center">
<Button className="px-5 w-fit self-center" disabled={isCompleted}>
<Button
className="px-5 w-fit self-center"
disabled={isCompleted}
onClick={handleCollectBadge}
>
Collect Badge
</Button>
{error && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { graphql } from "gql.tada";

export const COLLECT_BADGE = graphql(`
mutation CollectBadge($badgeId: ID!, $badgeType: String!) {
collectBadge(input: { badgeId: $badgeId, badgeType: $badgeType }) {
badgeEarned
errors
}
}
`);
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { collectBadge } from "../services/collectBadgeService";
import { CollectBadgeResult, CollectBadgeVariables } from "../types/badgeTypes";

export const useCollectBadge = () => {
const queryClient = useQueryClient();
return useMutation<CollectBadgeResult, Error, CollectBadgeVariables>({
mutationFn: collectBadge,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["specialBadge"] });
queryClient.invalidateQueries({ queryKey: ["specialBadges"] });
},
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { API_URL } from "@/lib/utils";
import request from "graphql-request";
import { COLLECT_BADGE } from "../graphql/collectBadge";
import { CollectBadgeResult, CollectBadgeVariables } from "../types/badgeTypes";

export const collectBadge = async (
variables: CollectBadgeVariables,
): Promise<CollectBadgeResult> => {
return await request(API_URL, COLLECT_BADGE, variables);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ResultOf } from "gql.tada";
import { ResultOf, VariablesOf } from "gql.tada";
import { BadgeQuery } from "../graphql/badgeQuery";
import { COLLECT_BADGE } from "../graphql/collectBadge";

export type Badge = ResultOf<typeof BadgeQuery>["badge"];

export type CollectBadgeVariables = VariablesOf<typeof COLLECT_BADGE>;
export type CollectBadgeResult = ResultOf<typeof COLLECT_BADGE>;
2 changes: 1 addition & 1 deletion apps/govquests-frontend/src/graphql-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ 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 }]; };
'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: 'String'; 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; }; }; }; } }; }; };
Expand Down

0 comments on commit dd68161

Please sign in to comment.