Skip to content

Commit

Permalink
feat: create user track aggregate, handle start and progress tracks
Browse files Browse the repository at this point in the history
  • Loading branch information
juliano-quatrin-nunes committed Jan 20, 2025
1 parent 41d4f1c commit e35f9c3
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 51 deletions.
8 changes: 4 additions & 4 deletions apps/govquests-api/govquests/processes/lib/processes.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require "infra"

require_relative "processes/start_quest_on_action_execution_started"
require_relative "processes/start_track_and_quest_on_action_execution_started"
require_relative "processes/update_profile_on_reward_issued"
require_relative "processes/update_quest_progress_on_action_execution_completed"
require_relative "processes/distribute_rewards_on_quest_completed"
Expand All @@ -11,19 +11,19 @@
require_relative "processes/notify_on_tier_achieved"
require_relative "processes/create_badge_on_track_or_quest_created"
require_relative "processes/reward_badge_on_quest_or_track_completed"
require_relative "processes/complete_track_on_quest_completed"
require_relative "processes/update_track_on_quest_completed"

require_relative "processes/deliver_notification_on_created"
module Processes
class Configuration
def call(event_store, command_bus)
StartQuestOnActionExecutionStarted.new(event_store, command_bus).subscribe
StartTrackAndQuestOnActionExecutionStarted.new(event_store, command_bus).subscribe
UpdateQuestProgressOnActionExecutionCompleted.new(event_store, command_bus).subscribe
UpdateProfileOnRewardIssued.new(event_store, command_bus).subscribe
DistributeRewardsOnQuestCompleted.new(event_store, command_bus).subscribe
CreateBadgeOnTrackOrQuestCreated.new(event_store, command_bus).subscribe
RewardBadgeOnQuestOrTrackCompleted.new(event_store, command_bus).subscribe
CompleteTrackOnQuestCompleted.new(event_store, command_bus).subscribe
UpdateTrackOnQuestCompleted.new(event_store, command_bus).subscribe

NotifyOnQuestCompleted.new(event_store, command_bus).subscribe
NotifyOnRewardIssued.new(event_store, command_bus).subscribe
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
module Processes
class StartQuestOnActionExecutionStarted
class StartTrackAndQuestOnActionExecutionStarted
QuestNotFound = Class.new(StandardError)
TrackNotFound = Class.new(StandardError)

def initialize(event_store, command_bus)
@event_store = event_store
@command_bus = command_bus
Expand All @@ -22,6 +25,21 @@ def call(event)

return if events.any? { |event| event.is_a?(Questing::QuestStarted) }

quest = Questing::QuestReadModel.find_by(quest_id: quest_id)

raise QuestNotFound, "Quest not found" unless quest
raise TrackNotFound, "Track not found for quest" unless quest.track

user_track_id = Questing.generate_user_track_id(quest.track.track_id, user_id)

@command_bus.call(
::Questing::StartUserTrack.new(
user_track_id: ,
user_id: user_id,
track_id: quest.track.track_id
)
)

command = ::Questing::StartUserQuest.new(
user_quest_id: user_quest_id,
quest_id: quest_id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Processes
class CompleteTrackOnQuestCompleted
class UpdateTrackOnQuestCompleted
def initialize(event_store, command_bus)
@event_store = event_store
@command_bus = command_bus
Expand All @@ -16,23 +16,14 @@ def call(event)
track = quest.track
user_id = event.data[:user_id]

completed_quests_count = track.quests
.joins(:user_quests)
.where(
user_quests: {
user_id: user_id,
status: "completed"
}
).count
user_track_id = Questing.generate_user_track_id(track.track_id, user_id)

if completed_quests_count == track.quests.count
@command_bus.call(
Questing::CompleteTrack.new(
user_id: user_id,
track_id: track.track_id
)
@command_bus.call(
Questing::UpdateUserTrackProgress.new(
user_track_id: ,
quest_id: event.data[:quest_id]
)
end
)
end
end
end
21 changes: 19 additions & 2 deletions apps/govquests-api/govquests/questing/lib/questing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require_relative "questing/quest"
require_relative "questing/user_quest"
require_relative "questing/track"
require_relative "questing/user_track"

QUESTING_NAMESPACE_UUID = "14f9d670-d4f7-4fea-bc48-1438f0f9f11c".freeze

Expand All @@ -15,6 +16,12 @@ def generate_user_quest_id(quest_id, user_id)
namespace_uuid = QUESTING_NAMESPACE_UUID
Digest::UUID.uuid_v5(namespace_uuid, name)
end

def generate_user_track_id(track_id, user_id)
name = "Track$#{track_id}-User$#{user_id}"
namespace_uuid = QUESTING_NAMESPACE_UUID
Digest::UUID.uuid_v5(namespace_uuid, name)
end
end

class Configuration
Expand Down Expand Up @@ -60,8 +67,18 @@ class CommandHandler < Infra::CommandHandlerRegistry
)
end

handle "Questing::CompleteTrack", aggregate: Track do |track, cmd|
track.complete(user_id: cmd.user_id)
handle "Questing::StartUserTrack", aggregate: UserTrack do |user_track, cmd, repository|
repository.with_aggregate(Track, cmd.track_id) do |track|
user_track.start(cmd.track_id, cmd.user_id, track.quests)
end
end

handle "Questing::UpdateUserTrackProgress", aggregate: UserTrack do |user_track, cmd|
user_track.add_progress(cmd.quest_id)
end

handle "Questing::CompleteUserTrack", aggregate: UserTrack do |user_track, cmd|
user_track.complete
end
end
end
21 changes: 18 additions & 3 deletions apps/govquests-api/govquests/questing/lib/questing/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,25 @@ class CreateTrack < Infra::Command
alias_method :aggregate_id, :track_id
end

class CompleteTrack < Infra::Command
attribute :user_id, Infra::Types::UUID
class StartUserTrack < Infra::Command
attribute :user_track_id, Infra::Types::UUID
attribute :track_id, Infra::Types::UUID
attribute :user_id, Infra::Types::UUID

alias_method :aggregate_id, :track_id
alias_method :aggregate_id, :user_track_id
end

class UpdateUserTrackProgress < Infra::Command
attribute :user_track_id, Infra::Types::UUID
attribute :quest_id, Infra::Types::UUID

alias_method :aggregate_id, :user_track_id
end


class CompleteUserTrack < Infra::Command
attribute :user_track_id, Infra::Types::UUID

alias_method :aggregate_id, :user_track_id
end
end
14 changes: 13 additions & 1 deletion apps/govquests-api/govquests/questing/lib/questing/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,20 @@ class TrackCreated < Infra::Event
attribute :badge_display_data, Infra::Types::Hash
end

class TrackCompleted < Infra::Event
class TrackStarted < Infra::Event
attribute :user_track_id, Infra::Types::UUID
attribute :track_id, Infra::Types::UUID
attribute :user_id, Infra::Types::UUID
end

class TrackProgressUpdated < Infra::Event
attribute :user_track_id, Infra::Types::UUID
attribute :quest_id, Infra::Types::UUID
end

class TrackCompleted < Infra::Event
attribute :user_track_id, Infra::Types::UUID
attribute :track_id, Infra::Types::UUID
attribute :user_id, Infra::Types::UUID
end
end
16 changes: 3 additions & 13 deletions apps/govquests-api/govquests/questing/lib/questing/track.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ class Track
AlreadyExists = Class.new(StandardError)
QuestAlreadyAssociated = Class.new(StandardError)

attr_reader :quests

def initialize(id)
@id = id
@quests = []
Expand All @@ -25,22 +27,10 @@ def create(display_data:, quest_ids:, badge_display_data:)
)
end

def complete(user_id:)
apply(TrackCompleted.new(data: {
user_id:,
track_id: @id
}))
end

on TrackCreated do |event|
@display_data = event.data[:display_data]
@quest_ids = event.data[:quest_ids]
@quests = event.data[:quest_ids]
@badge_display_data = event.data[:badge_display_data]
end

on TrackCompleted do |event|
@completed_by ||= []
@completed_by << event.data[:user_id]
end
end
end
81 changes: 81 additions & 0 deletions apps/govquests-api/govquests/questing/lib/questing/user_track.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module Questing
class UserTrack
include AggregateRoot

TrackNotStartedError = Class.new(StandardError)
TrackAlreadyStartedError = Class.new(StandardError)
TrackAlreadyCompletedError = Class.new(StandardError)
QuestsNotCompletedError = Class.new(StandardError)
InvalidQuestError = Class.new(StandardError)

def initialize(id)
@id = id
@state = :not_started
@track_id = nil
@user_id = nil
@quests = []
@progress = {}
end

def start(track_id, user_id, quests)
raise TrackAlreadyStartedError if @state == :in_progress
raise TrackAlreadyCompletedError if @state == :completed

apply TrackStarted.new(data: {
user_track_id: @id,
track_id: track_id,
user_id: user_id,
quests: quests
})
end

def add_progress(quest_id)
raise TrackNotStartedError unless @state == :in_progress

unless @quests.include?(quest_id)
raise InvalidQuestError, "Quest #{quest_id} is not part of this track."
end

apply TrackProgressUpdated.new(data: {
user_track_id: @id,
quest_id: quest_id,
})

complete if all_quests_completed?
end

def complete
raise TrackNotStartedError unless @state == :in_progress
raise TrackAlreadyCompletedError if @state == :completed
raise QuestsNotCompletedError unless all_quests_completed?

apply TrackCompleted.new(data: {
user_track_id: @id,
track_id: @track_id,
user_id: @user_id
})
end

private

def all_quests_completed?
(@quests - @progress.keys).empty?
end

on TrackStarted do |event|
@state = :in_progress
@track_id = event.data[:track_id]
@user_id = event.data[:user_id]
@quests = event.data[:quests]
@progress = {}
end

on TrackProgressUpdated do |event|
@progress[event.data[:quest_id]] = true
end

on TrackCompleted do |event|
@state = :completed
end
end
end
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
module Questing
class OnTrackCompleted
def call(event)
UserTrackReadModel.create_or_find_by!(
user_id: event.data[:user_id],
track_id: event.data[:track_id]
UserTrackReadModel.find_by!(
user_track_id: event.data[:user_track_id]
).update!(
status: "completed",
completed_at: Time.current
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Questing
class OnTrackStarted
def call(event)
user_track_id = event.data[:user_track_id]
track_id = event.data[:track_id]
user_id = event.data[:user_id]

UserTrackReadModel.find_or_create_by(user_track_id:, track_id:, user_id:).update(
status: "in_progress"
)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def call(event_store)
event_store.subscribe(OnQuestStarted, to: [Questing::QuestStarted])
event_store.subscribe(OnQuestCompleted, to: [Questing::QuestCompleted])
event_store.subscribe(OnTrackCreated, to: [Questing::TrackCreated])
event_store.subscribe(OnTrackStarted, to: [Questing::TrackStarted])
event_store.subscribe(OnTrackCompleted, to: [Questing::TrackCompleted])
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,19 @@ def completed?
#
# Table name: user_tracks
#
# id :bigint not null, primary key
# completed_at :datetime
# status :string default("in_progress"), not null
# created_at :datetime not null
# updated_at :datetime not null
# track_id :string not null
# user_id :string not null
# id :bigint not null, primary key
# completed_at :datetime
# status :string default("in_progress"), not null
# created_at :datetime not null
# updated_at :datetime not null
# track_id :string not null
# user_id :string not null
# user_track_id :string
#
# Indexes
#
# index_user_tracks_on_status (status)
# index_user_tracks_on_track_id (track_id)
# index_user_tracks_on_user_id_and_track_id (user_id,track_id) UNIQUE
# index_user_tracks_on_user_track_id (user_track_id) UNIQUE
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddUserTrackIdToUserTracks < ActiveRecord::Migration[8.1]
def change
add_column :user_tracks, :user_track_id, :string

add_index :user_tracks, :user_track_id, unique: true
end
end
4 changes: 3 additions & 1 deletion apps/govquests-api/rails_app/db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e35f9c3

Please sign in to comment.