-
Notifications
You must be signed in to change notification settings - Fork 144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
User Stats #77
User Stats #77
Conversation
self.stats_data[slug] = 0 | ||
end | ||
|
||
self.stats_data[slug] += 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant self detected. (https://github.com/bbatsov/ruby-style-guide#no-self-unless-required)
|
||
def increment_data(slug) | ||
unless stats_data[slug] | ||
self.stats_data[slug] = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant self detected. (https://github.com/bbatsov/ruby-style-guide#no-self-unless-required)
end | ||
|
||
def increment_data(slug) | ||
unless stats_data[slug] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Favor modifier unless usage when having a single-line body. Another good alternative is the usage of control flow &&/||. (https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier)
|
||
# update data with + 1 | ||
def library_entry_update(library_entry) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra empty line detected at method body beginning.
end | ||
|
||
# update data with + 1 | ||
def library_entry_update(library_entry) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put empty method definitions on a single line. (https://github.com/bbatsov/ruby-style-guide#no-single-line-methods)
app/models/library_entry.rb
Outdated
@@ -228,4 +228,15 @@ def myanimelist_linked_account | |||
type: 'LinkedAccount::MyAnimeList' | |||
) | |||
end | |||
|
|||
after_commit ->() { create_stat }, on: :create |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I already know this name sucks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's... actually not a bad name 🤔 That said, it might be better to just toss them in a block like this, depending on how much logic you'll have in there (and how many of these you need):
after_commit(on: :create) do
Stat::AnimeGenreBreakdown.increment_genres(user, media.genres)
end
app/models/library_entry.rb
Outdated
@@ -228,4 +228,15 @@ def myanimelist_linked_account | |||
type: 'LinkedAccount::MyAnimeList' | |||
) | |||
end | |||
|
|||
after_commit ->() { create_stat }, on: :create | |||
after_destroy ->() { delete_stat } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and this one
|
||
def library_entries | ||
user.library_entries.where(media_type: 'Anime') | ||
p record |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will remove
end | ||
end | ||
|
||
def self.increment_genres(user, genres) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of feel like that these methods can just be set to increment
and decrement
and then just used across all the other Stats (so they are all consistent, because we already know it is genres due to type
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good thinking!
app/models/library_entry.rb
Outdated
@@ -228,4 +228,15 @@ def myanimelist_linked_account | |||
type: 'LinkedAccount::MyAnimeList' | |||
) | |||
end | |||
|
|||
after_commit ->() { create_stat }, on: :create |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's... actually not a bad name 🤔 That said, it might be better to just toss them in a block like this, depending on how much logic you'll have in there (and how many of these you need):
after_commit(on: :create) do
Stat::AnimeGenreBreakdown.increment_genres(user, media.genres)
end
app/models/stat.rb
Outdated
class Stat < ApplicationRecord | ||
belongs_to :user, required: true | ||
# expose for jsonapi | ||
alias_attribute :kind, :type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is gonna need adjustment once #86 is merged
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, looked at what you did and it seems good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this with the STIResource
app/models/stat.rb
Outdated
# expose for jsonapi | ||
alias_attribute :kind, :type | ||
|
||
validates_presence_of :type, :stats_data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally I prefer validates :type, :stats_data, presence: true
save | ||
else | ||
update_attribute(:stats_data, stats_data) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't save
work for both of these cases?
Also we might want save!
which'll throw an error if the save fails (otherwise this might break silently)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, now that I think about it I don't know why I actually split it.
|
||
record.stats_data[genre.slug] -= 1 | ||
record.stats_data['total'] -= 1 | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might be able to combine increment
and decrement
into an update_genres(genres, -1)
method (where the second parameter is either -1
or +1
depending on whether you wanna decrement or increment. Just something to think about
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally had it with an update_genres and was thinking about going with the (+1 -1, we talked about it) but decided that splitting them up would be best for consistency across all the Stats.
@@ -0,0 +1 @@ | |||
class Stat::AnimeGenreBreakdownPolicy < StatPolicy; end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use nested module/class definitions instead of compact style.
end | ||
|
||
def progress_to_time(le, progress) | ||
return 0 if le.anime.episode_length.nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just assumed we are skipping if there is no episode_length
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems reasonable tbh
@@ -0,0 +1,13 @@ | |||
class StatResource < BaseResource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be immutable
, now that I think about it recalculate!
is run with a rake task and both increment
+ decrement
are just inside the LibraryEntry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, yes, that's a good point!
class RegenerateStatService | ||
class << self | ||
def anime_genre_breakdown | ||
User.where(id: LibraryEntry.select(:user_id).where(media_type: 'Anime')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the .where(media-type: 'Anime')
because no point running it if they only have manga library entries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense
|
||
say_with_time 'Filling time_spent column' do | ||
LibraryEntry.where(media_type: 'Anime').joins(:anime) | ||
.update_in_batches('time_spent = anime.episode_length * progress') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens if there is no anime.episode_length
because there is no default (or null constraint) so wouldn't it be nil * progress
which will cause an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure it'll just replace it with 0 (since this is Postgres math, not Ruby math), that's what it did last night when I was pulling numbers for josh 🤔 But it's possible that order matters, I did progress * episode_length
If you wanna be explicit you could use coalesce(anime.episode_length, 0)
spec/factories/stats.rb
Outdated
factory :stat do | ||
association :user, factory: :user, strategy: :build | ||
type 'Stat::AnimeGenreBreakdown' | ||
stats_data { { total: 0 } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am getting this error if stats_data
is just a {}
:
Failure/Error: FactoryGirl::Linter.new([factory], :factory).lint!
FactoryGirl::InvalidFactoryError:
The following factories are invalid:
* stat - Validation failed: Stats data can't be blank (ActiveRecord::RecordInvalid)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that if you do stats_data {}
or is it with stats_data { {} }
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first one is uhh... wrong? I have tried with the second one, and there is a default set already to {}
at a database level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First one is really more "ambiguous" than wrong, it could be either a block or a hash (and both are valid for FactoryGirl).
Also the problem is simple:
{}.blank? #=> true
You can probably drop the validation since the database provides a default
subject { described_class.new } | ||
|
||
it { should belong_to(:user) } | ||
it { should validate_presence_of(:type) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add validation for stats_data
also
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
app/models/library_entry.rb
Outdated
after_commit(on: :create) do | ||
sync_entry(:create) # mal exporter | ||
# Stat STI | ||
Stat::AnimeGenreBreakdown.increment(user, media.genres) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These probably shouldn't be in after_commit
, that'll make them run in a separate transaction which could result in them being out-of-sync with this model. Should be in after_create
and after_destroy
hooks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well the after_create
is creating some janky timing errors with my tests now....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really? That suggests your tests are wrong, since after_commit
is just never run during tests (the commit
never actually happens, instead it just runs a rollback
after each test)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heh, well I know what is wrong but my easy solution of just doing a before do subject.recalculate!
fails it seems...
app/models/library_entry.rb
Outdated
after_destroy do | ||
sync_entry(:delete) # mal exporter | ||
# Stat STI | ||
Stat::AnimeGenreBreakdown.decrement(user, media.genres) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above
app/models/stat.rb
Outdated
class Stat < ApplicationRecord | ||
belongs_to :user, required: true | ||
# expose for jsonapi | ||
alias_attribute :kind, :type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this with the STIResource
|
||
def self.decrement(user, library_entry) | ||
record = user.stats.find_by(type: 'Stat::AnimeAmountWatched') | ||
# TODO: do we want to raise or return? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just do nothing, raising an exception might break library updates
end | ||
|
||
def progress_to_time(le, progress) | ||
return 0 if le.anime.episode_length.nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems reasonable tbh
@@ -0,0 +1,13 @@ | |||
class StatResource < BaseResource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, yes, that's a good point!
class RegenerateStatService | ||
class << self | ||
def anime_genre_breakdown | ||
User.where(id: LibraryEntry.select(:user_id).where(media_type: 'Anime')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense
|
||
say_with_time 'Filling time_spent column' do | ||
LibraryEntry.where(media_type: 'Anime').joins(:anime) | ||
.update_in_batches('time_spent = anime.episode_length * progress') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure it'll just replace it with 0 (since this is Postgres math, not Ruby math), that's what it did last night when I was pulling numbers for josh 🤔 But it's possible that order matters, I did progress * episode_length
If you wanna be explicit you could use coalesce(anime.episode_length, 0)
spec/factories/stats.rb
Outdated
factory :stat do | ||
association :user, factory: :user, strategy: :build | ||
type 'Stat::AnimeGenreBreakdown' | ||
stats_data { { total: 0 } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that if you do stats_data {}
or is it with stats_data { {} }
?
subject { described_class.new } | ||
|
||
it { should belong_to(:user) } | ||
it { should validate_presence_of(:type) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
app/models/library_entry.rb
Outdated
|
||
after_create do | ||
# Stat STI | ||
case media_type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer if you used kind
for this (returns a symbol :anime
, :manga
, etc.), since media_type
is deprecated and slated for eventual removal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, there are quite a few other places that I use media_type
so should I change them to kind
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, anywhere you're using media_type
from LibraryEntry, change to kind
, so we can gradually wean off the polymorphic column
|
||
def default_stats | ||
# needs to be a string and NOT a symbol | ||
# due to how it is being saved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure Rails will handle all the conversions itself?
Also it's probably better for default_stats
to just be a DEFAULT_STATS
constant that you self.stats_data = DEFAULT_STATS if stats_data.blank?
, instead of a method which modifies the instance and returns the self.
When a method has a name like default_stats
, you expect that it'll return the default stats. The fact that this method doesn't is a bit of a betrayal of expectations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay! I actually wasn't entirely sure how to approach the default_stats
because I felt like a method wasn't right. Thinking about it now, having a constant (frozen) is prob the best method.
save! | ||
end | ||
|
||
def default_stats |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above, re: the other default_stats
method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This, again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops... I only fixed 1 of them...
add_column :library_entries, :time_spent, :integer | ||
|
||
say_with_time 'Filling time_spent column' do | ||
LibraryEntry.where(media_type: 'Anime').joins(:anime) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer if you used by_kind(:anime)
for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so instead of joins(:anime)
, use by_kind(:anime)
? Can I have a tl;dr for what the difference is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also for the media_type: 'Anime'
should I change it to .where(kind: :anime)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nonono, replace where(media_type: 'Anime')
with by_kind(:anime)
As the name implies, by_kind
gets Library Entries by kind 😉
lib/tasks/user_stats.rake
Outdated
@@ -0,0 +1,10 @@ | |||
task user_stats: 'kitsu:user_stats_setup' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a task named user_stats_setup
, cause I ain't seeing one 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I failed at copying the stats.rake
:D. it should be user_stats_setup
(like 2 lines down)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uhhhh why are you copying stats.rake
? That's literally just a thing to configure how we count lines of code with rake stats
(try running it, it'll tell you the lines of code in Kitsu!)
It shouldn't be named user_stats_setup
unless it's doing some from of configuration setup (which it isn't)
lib/tasks/user_stats.rake
Outdated
task user_stats: 'kitsu:user_stats_setup' | ||
|
||
namespace :kitsu do | ||
task :user_stats do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably want to use the environment
dependency so that all of Rails will load for this (otherwise it probably won't be able to access any Rails models)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can I get an English translation of this please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TL;DR go look at kitsu.rake
lib/tasks/user_stats.rake
Outdated
@@ -0,0 +1,8 @@ | |||
namespace :kitsu do | |||
task :user_stats => :environment do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the new Ruby 1.9 hash syntax. (https://github.com/bbatsov/ruby-style-guide#hash-literals)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assuming it just wants user_stats: :environment
?
class Stat | ||
class AnimeAmountWatched < Stat | ||
DEFAULT_STATS = { | ||
'total_anime' => 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use 2 spaces for indentation in a hash, relative to the start of the line where the left curly brace is.
@@ -0,0 +1,66 @@ | |||
class Stat | |||
class AnimeAmountWatched < Stat | |||
DEFAULT_STATS = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Freeze mutable objects assigned to constants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tis ain't possible houndy :(.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't that possible? It looks possible to me :v
app/models/library_entry.rb
Outdated
when :anime | ||
Stat::AnimeGenreBreakdown.decrement(user, media.genres) | ||
Stat::AnimeAmountWatched.decrement(user, self) | ||
when :manga |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid when branches without a body.
app/models/library_entry.rb
Outdated
when :anime | ||
Stat::AnimeGenreBreakdown.increment(user, media.genres) | ||
Stat::AnimeAmountWatched.increment(user, self) | ||
when :manga |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid when branches without a body.
app/resources/user_resource.rb
Outdated
@@ -2,7 +2,7 @@ class UserResource < BaseResource | |||
PRIVATE_FIELDS = %i[email password confirmed previous_email language time_zone | |||
country share_to_global title_language_preference | |||
sfw_filter rating_system].freeze | |||
|
|||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected. (https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace)
app/resources/user_resource.rb
Outdated
@@ -2,7 +2,7 @@ class UserResource < BaseResource | |||
PRIVATE_FIELDS = %i[email password confirmed previous_email language time_zone | |||
country share_to_global title_language_preference | |||
sfw_filter rating_system].freeze | |||
|
|||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected. (https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great progress, I like where this is going!
@@ -0,0 +1,66 @@ | |||
class Stat | |||
class AnimeAmountWatched < Stat | |||
DEFAULT_STATS = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't that possible? It looks possible to me :v
save! | ||
end | ||
|
||
def default_stats |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This, again.
db/schema.rb
Outdated
|
||
ActiveRecord::Schema.define(version: 20170326222205) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this might've gotten messed up in your merge, but I think the blank line is supposed to be after the ActiveRecord::Schema.define
line 🤔
(not a big deal tho, it'll get fixed next time rails dumps the schema)
record = user.stats.find_or_initialize_by( | ||
type: 'Stat::AnimeAmountWatched' | ||
) | ||
record.stats_data['all_time'] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Favor a normal if-statement over a modifier clause in a multiline statement. (https://github.com/bbatsov/ruby-style-guide#no-multiline-if-modifiers)
https://www.notion.so/User-Stats-78ece66c495e41c1929856524309957d