diff --git a/.rubocop.yml b/.rubocop.yml index d63f0418d..dafd9a620 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -184,3 +184,4 @@ AllCops: - db/migrate/20200927135222_add_last_activity_at_to_conversation.rb - db/migrate/20210306170117_add_last_activity_at_to_contacts.rb - db/migrate/20220809104508_revert_cascading_indexes.rb + diff --git a/Gemfile b/Gemfile index ba83ce964..9c3590017 100644 --- a/Gemfile +++ b/Gemfile @@ -158,6 +158,10 @@ group :test do gem 'webmock' end +group :development, :test, :staging do + gem 'faker' +end + group :development, :test do gem 'active_record_query_trace' ##--- gems for debugging and error reporting ---## @@ -167,7 +171,6 @@ group :development, :test do gem 'byebug', platform: :mri gem 'climate_control' gem 'factory_bot_rails' - gem 'faker' gem 'listen' gem 'mock_redis' gem 'pry-rails' diff --git a/app/controllers/super_admin/accounts_controller.rb b/app/controllers/super_admin/accounts_controller.rb index 28cae96b0..3d038281c 100644 --- a/app/controllers/super_admin/accounts_controller.rb +++ b/app/controllers/super_admin/accounts_controller.rb @@ -41,4 +41,9 @@ class SuperAdmin::AccountsController < SuperAdmin::ApplicationController # See https://administrate-prototype.herokuapp.com/customizing_controller_actions # for more information + + def seed + Seeders::AccountSeeder.new(account: requested_resource).perform! + redirect_back(fallback_location: [namespace, requested_resource], notice: 'Account seeding triggered') + end end diff --git a/app/fields/avatar_field.rb b/app/fields/avatar_field.rb index a9674eb94..c443b7a21 100644 --- a/app/fields/avatar_field.rb +++ b/app/fields/avatar_field.rb @@ -2,6 +2,8 @@ require 'administrate/field/base' class AvatarField < Administrate::Field::Base def avatar_url - data.presence&.gsub('?d=404', '?d=mp') + return data.presence if data.presence + + resource.is_a?(User) ? '/assets/administrate/user/avatar.png' : '/assets/administrate/bot/avatar.png' end end diff --git a/app/views/super_admin/accounts/_seed_data.html.erb b/app/views/super_admin/accounts/_seed_data.html.erb new file mode 100644 index 000000000..9f8358228 --- /dev/null +++ b/app/views/super_admin/accounts/_seed_data.html.erb @@ -0,0 +1,14 @@ +<% if !Rails.env.production? %> +
+
+ <%= form_for([:seed, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %> + +
+

Click the button to generate seed data into this account for demos.

+

Note: This will clear all the existing data in this account.

+
+ <%= f.submit 'Generate Seed Data' %> +
+ <% end %> +
+<% end %> diff --git a/app/views/super_admin/accounts/show.html.erb b/app/views/super_admin/accounts/show.html.erb index 93cd37945..1b37e4402 100644 --- a/app/views/super_admin/accounts/show.html.erb +++ b/app/views/super_admin/accounts/show.html.erb @@ -85,3 +85,5 @@ as well as a link to its edit page. <% end %> + +<%= render partial: "seed_data", locals: {page: page} %> diff --git a/config/routes.rb b/config/routes.rb index 44b5ce7ea..2b218bf76 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -340,7 +340,9 @@ Rails.application.routes.draw do resource :app_config, only: [:show, :create] # order of resources affect the order of sidebar navigation in super admin - resources :accounts + resources :accounts, only: [:index, :new, :create, :show, :edit, :update] do + post :seed, on: :member + end resources :users, only: [:index, :new, :create, :show, :edit, :update] resources :access_tokens, only: [:index, :show] resources :installation_configs, only: [:index, :new, :create, :show, :edit, :update] diff --git a/db/seeds.rb b/db/seeds.rb index 47ce3f6fb..c4c7eda71 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -11,6 +11,12 @@ end ## Seeds for Local Development unless Rails.env.production? + # Enables creating additional accounts from dashboard + installation_config = InstallationConfig.find_by(name: 'CREATE_NEW_ACCOUNT_FROM_DASHBOARD') + installation_config.value = true + installation_config.save! + GlobalConfig.clear_cache + account = Account.create!( name: 'Acme Inc' ) @@ -35,12 +41,6 @@ unless Rails.env.production? role: :administrator ) - # Enables creating additional accounts from dashboard - installation_config = InstallationConfig.find_by(name: 'CREATE_NEW_ACCOUNT_FROM_DASHBOARD') - installation_config.value = true - installation_config.save! - GlobalConfig.clear_cache - web_widget = Channel::WebWidget.create!(account: account, website_url: 'https://acme.inc') inbox = Inbox.create!(channel: web_widget, account: account, name: 'Acme Support') diff --git a/lib/seeders/account_seeder.rb b/lib/seeders/account_seeder.rb index 162bc4cd9..5302daefe 100644 --- a/lib/seeders/account_seeder.rb +++ b/lib/seeders/account_seeder.rb @@ -1,89 +1,105 @@ -## Class to generate sample data for a chatwoot test Account. +## Class to generate sample data for a chatwoot test @Account. ############################################################ ### Usage ##### # # # Seed an account with all data types in this class -# Seeders::AccountSeeder.new(account: account).seed! +# Seeders::AccountSeeder.new(account: @Account.find(1)).perform! # -# # When you want to seed only a specific type of data -# Seeders::AccountSeeder.new(account: account).seed_canned_responses -# # Seed specific number of objects -# Seeders::AccountSeeder.new(account: account).seed_canned_responses(count: 10) # ############################################################ class Seeders::AccountSeeder - pattr_initialize [:account!] + def initialize(account:) + raise 'Account Seeding is not allowed in production.' if Rails.env.production? - def seed! + @account_data = HashWithIndifferentAccess.new(YAML.safe_load(File.read(Rails.root.join('lib/seeders/seed_data.yml')))) + @account = account + end + + def perform! + set_up_account + seed_teams + set_up_users + seed_labels seed_canned_responses seed_inboxes + seed_contacts + end + + def set_up_account + @account.teams.destroy_all + @account.conversations.destroy_all + @account.labels.destroy_all + @account.inboxes.destroy_all + @account.contacts.destroy_all + end + + def seed_teams + @account_data['teams'].each do |team_name| + @account.teams.create!(name: team_name) + end + end + + def seed_labels + @account_data['labels'].each do |label| + @account.labels.create!(label) + end + end + + def set_up_users + @account_data['users'].each do |user| + user_record = User.create_with(name: user['name'], password: 'Password1!.').find_or_create_by!(email: (user['email']).to_s) + user_record.skip_confirmation! + user_record.save! + Avatar::AvatarFromUrlJob.perform_later(user_record, "https://xsgames.co/randomusers/avatar.php?g=#{user['gender']}") + AccountUser.create_with(role: (user['role'] || 'agent')).find_or_create_by!(account_id: @account.id, user_id: user_record.id) + next if user['team'].blank? + + add_user_to_teams(user: user_record, teams: user['team']) + end + end + + def add_user_to_teams(user:, teams:) + teams.each do |team| + team_record = @account.teams.where('name LIKE ?', "%#{team.downcase}%").first if team.present? + TeamMember.find_or_create_by!(team_id: team_record.id, user_id: user.id) unless team_record.nil? + end end def seed_canned_responses(count: 50) count.times do - account.canned_responses.create(content: Faker::Quote.fortune_cookie, short_code: Faker::Alphanumeric.alpha(number: 10)) + @account.canned_responses.create(content: Faker::Quote.fortune_cookie, short_code: Faker::Alphanumeric.alpha(number: 10)) + end + end + + def seed_contacts + @account_data['contacts'].each do |contact_data| + contact = @account.contacts.create!(contact_data.slice('name', 'email')) + Avatar::AvatarFromUrlJob.perform_later(contact, "https://xsgames.co/randomusers/avatar.php?g=#{contact_data['gender']}") + contact_data['conversations'].each do |conversation_data| + inbox = @account.inboxes.find_by(channel_type: conversation_data['channel']) + contact_inbox = inbox.contact_inboxes.create!(contact: contact, source_id: (conversation_data['source_id'] || SecureRandom.hex)) + create_conversation(contact_inbox: contact_inbox, conversation_data: conversation_data) + end + end + end + + def create_conversation(contact_inbox:, conversation_data:) + assignee = User.find_by(email: conversation_data['assignee']) if conversation_data['assignee'].present? + conversation = contact_inbox.conversations.create!(account: contact_inbox.inbox.account, contact: contact_inbox.contact, + inbox: contact_inbox.inbox, assignee: assignee) + create_messages(conversation: conversation, messages: conversation_data['messages']) + end + + def create_messages(conversation:, messages:) + messages.each do |message_data| + sender = User.find_by(email: message_data['sender']) if message_data['sender'].present? + conversation.messages.create!(message_data.slice('content', 'message_type').merge(account: conversation.inbox.account, sender: sender, + inbox: conversation.inbox)) end end def seed_inboxes - seed_website_inbox - seed_facebook_inbox - seed_twitter_inbox - seed_whatsapp_inbox - seed_sms_inbox - seed_email_inbox - seed_api_inbox - seed_telegram_inbox - seed_line_inbox - end - - def seed_website_inbox - channel = Channel::WebWidget.create!(account: account, website_url: 'https://acme.inc') - Inbox.create!(channel: channel, account: account, name: 'Acme Website') - end - - def seed_facebook_inbox - channel = Channel::FacebookPage.create!(account: account, user_access_token: 'test', page_access_token: 'test', page_id: 'test') - Inbox.create!(channel: channel, account: account, name: 'Acme Facebook') - end - - def seed_twitter_inbox - channel = Channel::TwitterProfile.create!(account: account, twitter_access_token: 'test', twitter_access_token_secret: 'test', profile_id: '123') - Inbox.create!(channel: channel, account: account, name: 'Acme Twitter') - end - - def seed_whatsapp_inbox - channel = Channel::Whatsapp.create!(account: account, phone_number: '+123456789') - Inbox.create!(channel: channel, account: account, name: 'Acme Whatsapp') - end - - def seed_sms_inbox - channel = Channel::Sms.create!(account: account, phone_number: '+123456789') - Inbox.create!(channel: channel, account: account, name: 'Acme SMS') - end - - def seed_email_inbox - channel = Channel::Email.create!(account: account, email: 'test@acme.inc', forward_to_email: 'test_fwd@acme.inc') - Inbox.create!(channel: channel, account: account, name: 'Acme Email') - end - - def seed_api_inbox - channel = Channel::Api.create!(account: account) - Inbox.create!(channel: channel, account: account, name: 'Acme API') - end - - def seed_telegram_inbox - # rubocop:disable Rails/SkipsModelValidations - Channel::Telegram.insert({ account_id: account.id, bot_name: 'Acme', bot_token: 'test', created_at: Time.now.utc, updated_at: Time.now.utc }, - returning: %w[id]) - channel = Channel::Telegram.find_by(bot_token: 'test') - Inbox.create!(channel: channel, account: account, name: 'Acme Telegram') - # rubocop:enable Rails/SkipsModelValidations - end - - def seed_line_inbox - channel = Channel::Line.create!(account: account, line_channel_id: 'test', line_channel_secret: 'test', line_channel_token: 'test') - Inbox.create!(channel: channel, account: account, name: 'Acme Line') + Seeders::InboxSeeder.new(account: @account, company_data: @account_data[:company]).perform! end end diff --git a/lib/seeders/inbox_seeder.rb b/lib/seeders/inbox_seeder.rb new file mode 100644 index 000000000..0c4bca669 --- /dev/null +++ b/lib/seeders/inbox_seeder.rb @@ -0,0 +1,105 @@ +## Class to generate sample inboxes for a chatwoot test @Account. +############################################################ +### Usage ##### +# +# # Seed an account with all data types in this class +# Seeders::InboxSeeder.new(account: @Account.find(1), company_data: {name: 'PaperLayer', doamin: 'paperlayer.test'}).perform! +# +# +############################################################ + +class Seeders::InboxSeeder + def initialize(account:, company_data:) + raise 'Inbox Seeding is not allowed in production.' if Rails.env.production? + + @account = account + @company_data = company_data + end + + def perform! + seed_website_inbox + seed_facebook_inbox + seed_twitter_inbox + seed_whatsapp_inbox + seed_sms_inbox + seed_email_inbox + seed_api_inbox + seed_telegram_inbox + seed_line_inbox + end + + def seed_website_inbox + channel = Channel::WebWidget.create!(account: @account, website_url: "https://#{@company_data['domain']}") + Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Website") + end + + def seed_facebook_inbox + channel = Channel::FacebookPage.create!(account: @account, user_access_token: SecureRandom.hex, page_access_token: SecureRandom.hex, + page_id: SecureRandom.hex) + Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Facebook") + end + + def seed_twitter_inbox + channel = Channel::TwitterProfile.create!(account: @account, twitter_access_token: SecureRandom.hex, + twitter_access_token_secret: SecureRandom.hex, profile_id: '123') + Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Twitter") + end + + def seed_whatsapp_inbox + # rubocop:disable Rails/SkipsModelValidations + Channel::Whatsapp.insert( + { + account_id: @account.id, + phone_number: Faker::PhoneNumber.cell_phone_in_e164, + created_at: Time.now.utc, + updated_at: Time.now.utc + }, + returning: %w[id] + ) + # rubocop:enable Rails/SkipsModelValidations + + channel = Channel::Whatsapp.find_by(account_id: @account.id) + + Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Whatsapp") + end + + def seed_sms_inbox + channel = Channel::Sms.create!(account: @account, phone_number: Faker::PhoneNumber.cell_phone_in_e164) + Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Mobile") + end + + def seed_email_inbox + channel = Channel::Email.create!(account: @account, email: "test#{SecureRandom.hex}@#{@company_data['domain']}", + forward_to_email: "test_fwd#{SecureRandom.hex}@#{@company_data['domain']}") + Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Email") + end + + def seed_api_inbox + channel = Channel::Api.create!(account: @account) + Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} API") + end + + def seed_telegram_inbox + # rubocop:disable Rails/SkipsModelValidations + bot_token = SecureRandom.hex + Channel::Telegram.insert( + { + account_id: @account.id, + bot_name: (@company_data['name']).to_s, + bot_token: bot_token, + created_at: Time.now.utc, + updated_at: Time.now.utc + }, + returning: %w[id] + ) + channel = Channel::Telegram.find_by(bot_token: bot_token) + Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Telegram") + # rubocop:enable Rails/SkipsModelValidations + end + + def seed_line_inbox + channel = Channel::Line.create!(account: @account, line_channel_id: SecureRandom.hex, line_channel_secret: SecureRandom.hex, + line_channel_token: SecureRandom.hex) + Inbox.create!(channel: channel, account: @account, name: "#{@company_data['name']} Line") + end +end diff --git a/lib/seeders/seed_data.yml b/lib/seeders/seed_data.yml new file mode 100644 index 000000000..847ba5a8c --- /dev/null +++ b/lib/seeders/seed_data.yml @@ -0,0 +1,367 @@ +company: + name: 'PaperLayer' + domain: 'paperlayer.test' +users: + - name: 'Michael Scott' + gender: male + email: 'michale@paperlayer.test' + team: + - 'sales' + - 'management' + - 'administration' + - 'warehouse' + role: 'administrator' + - name: 'David Wallace' + gender: male + email: 'david@paperlayer.test' + team: + - 'Management' + - name: 'Deangelo Vickers' + gender: male + email: 'deangelo@paperlayer.test' + team: + - 'Management' + - name: 'Jo Bennett' + gender: female + email: 'jo@paperlayer.test' + team: + - 'Management' + - name: 'Josh Porter' + gender: male + email: 'josh@paperlayer.test' + team: + - 'Management' + - name: 'Charles Miner' + gender: male + email: 'charles@paperlayer.test' + team: + - 'Management' + - name: 'Ed Truck' + gender: male + email: 'ed@paperlayer.test' + team: + - 'Management' + - name: 'Dan Gore' + gender: male + email: 'dan@paperlayer.test' + team: + - 'Management' + - name: 'Craig D' + gender: male + email: 'craig@paperlayer.test' + team: + - 'Management' + - name: 'Troy Underbridge' + gender: male + email: 'troy@paperlayer.test' + team: + - 'Management' + - name: 'Karen Filippelli' + gender: female + email: 'karn@paperlayer.test' + team: + - 'Sales' + - name: 'Danny Cordray' + gender: female + email: 'danny@paperlayer.test' + team: + - 'Sales' + - name: 'Ben Nugent' + gender: male + email: 'ben@paperlayer.test' + team: + - 'Sales' + - name: 'Todd Packer' + gender: male + email: 'todd@paperlayer.test' + team: + - 'Sales' + - name: 'Cathy Simms' + gender: female + email: 'cathy@paperlayer.test' + team: + - 'Administration' + - name: 'Hunter Jo' + gender: male + email: 'hunter@paperlayer.test' + team: + - 'Administration' + - name: 'Rolando Silva' + gender: male + email: 'rolando@paperlayer.test' + team: + - 'Administration' + - name: 'Stephanie Wilson' + gender: female + email: 'stephanie@paperlayer.test' + team: + - 'Administration' + - name: 'Jordan Garfield' + gender: male + email: 'jorodan@paperlayer.test' + team: + - 'Administration' + - name: 'Ronni Carlo' + gender: male + email: 'ronni@paperlayer.test' + team: + - 'Administration' + - name: 'Lonny Collins' + gender: female + email: 'lonny@paperlayer.test' + team: + - 'Warehouse' + - name: 'Madge Madsen' + gender: female + email: 'madge@paperlayer.test' + team: + - 'Warehouse' + - name: 'Glenn Max' + gender: female + email: 'glenn@paperlayer.test' + team: + - 'Warehouse' + - name: 'Jerry DiCanio' + gender: male + email: 'jerry@paperlayer.test' + team: + - 'Warehouse' + - name: 'Phillip Martin' + gender: male + email: 'phillip@paperlayer.test' + team: + - 'Warehouse' + - name: 'Michael Josh' + gender: male + email: 'michale_josh@paperlayer.test' + team: + - 'Warehouse' + - name: 'Matt Hudson' + gender: male + email: 'matt@paperlayer.test' + team: + - 'Warehouse' + - name: 'Gideon' + gender: male + email: 'gideon@paperlayer.test' + team: + - 'Warehouse' + - name: 'Bruce' + gender: male + email: 'bruce@paperlayer.test' + team: + - 'Warehouse' + - name: 'Frank' + gender: male + email: 'frank@paperlayer.test' + team: + - 'Warehouse' + - name: 'Louanne Kelley' + gender: female + email: 'louanne@paperlayer.test' + - name: 'Devon White' + gender: male + email: 'devon@paperlayer.test' + - name: 'Kendall' + gender: male + email: 'kendall@paperlayer.test' + - email: 'sadiq@paperlayer.test' + name: 'Sadiq' + gender: male +teams: + - '💰 Sales' + - '💼 Management' + - '👩‍💼 Administration' + - '🚛 Warehouse' +labels: + - title: 'billing' + color: '#28AD21' + show_on_sidebar: true + - title: 'software' + color: '#8F6EF2' + show_on_sidebar: true + - title: 'delivery' + color: '#A2FDD5' + show_on_sidebar: true + - title: 'ops-handover' + color: '#A53326' + show_on_sidebar: true + - title: 'premium-customer' + color: '#6FD4EF' + show_on_sidebar: true + - title: 'lead' + color: '#F161C8' + show_on_sidebar: true +contacts: + - name: "Lorrie Trosdall" + email: "ltrosdall0@bravesites.test" + gender: 'female' + conversations: + - channel: Channel::WebWidget + messages: + - message_type: incoming + content: hello world + - name: "Tiffanie Cloughton" + email: "tcloughton1@newyorker.test" + gender: 'female' + conversations: + - channel: Channel::FacebookPage + messages: + - message_type: incoming + content: hello world + - name: "Melonie Keatch" + email: "mkeatch2@reuters.test" + gender: 'female' + conversations: + - channel: Channel::TwitterProfile + messages: + - message_type: incoming + content: hello world + - name: "Olin Canniffe" + email: "ocanniffe3@feedburner.test" + gender: 'male' + conversations: + - channel: Channel::Whatsapp + messages: + - message_type: incoming + content: hello world + - name: "Viviene Corp" + email: "vcorp4@instagram.test" + gender: 'female' + conversations: + - channel: Channel::Sms + source_id: "+1234567" + messages: + - message_type: incoming + content: hello world + - name: "Drake Pittway" + email: "dpittway5@chron.test" + gender: 'male' + conversations: + - channel: Channel::Line + messages: + - message_type: incoming + content: hello world + - name: "Klaus Crawley" + email: "kcrawley6@narod.ru" + gender: 'male' + conversations: + - channel: Channel::WebWidget + messages: + - message_type: incoming + content: hello world + - name: "Bing Cusworth" + email: "bcusworth7@arstechnica.test" + gender: 'male' + conversations: + - channel: Channel::TwitterProfile + messages: + - message_type: incoming + content: hello world + - name: "Claus Jira" + email: "cjira8@comcast.net" + gender: 'male' + conversations: + - channel: Channel::Whatsapp + messages: + - message_type: incoming + content: hello world + - name: "Quent Dalliston" + email: "qdalliston9@zimbio.test" + gender: 'male' + conversations: + - channel: Channel::Whatsapp + messages: + - message_type: incoming + content: hello world + - name: "Coreen Mewett" + email: "cmewetta@home.pl" + gender: 'female' + conversations: + - channel: Channel::FacebookPage + messages: + - message_type: incoming + content: hello world + - name: "Benyamin Janeway" + email: "bjanewayb@ustream.tv" + gender: 'male' + conversations: + - channel: Channel::Line + messages: + - message_type: incoming + content: hello world + - name: "Cordell Dalinder" + email: "cdalinderc@msn.test" + gender: 'male' + conversations: + - channel: Channel::Email + source_id: "cdalinderc@msn.test" + messages: + - message_type: incoming + content: hello world + - name: "Merrile Petruk" + email: "mpetrukd@wunderground.test" + gender: 'female' + conversations: + - channel: Channel::Email + source_id: "mpetrukd@wunderground.test" + messages: + - message_type: incoming + content: hello world + - name: "Nathaniel Vannuchi" + email: "nvannuchie@photobucket.test" + gender: 'male' + conversations: + - channel: Channel::FacebookPage + messages: + - message_type: incoming + content: "Hey there,I need some help with billing, my card is not working on the website." + - name: "Olia Olenchenko" + email: "oolenchenkof@bluehost.test" + gender: 'female' + conversations: + - channel: Channel::WebWidget + assignee: michael_scott@paperlayer.test + messages: + - message_type: incoming + content: "Billing section is not working, it throws some error." + - name: "Elisabeth Derington" + email: "ederingtong@printfriendly.test" + gender: 'female' + conversations: + - channel: Channel::Whatsapp + messages: + - message_type: incoming + content: "Hey \n I didn't get the product delivered, but it shows it is delivered to my address. Please check" + - name: "Willy Castelot" + email: "wcasteloth@exblog.jp" + gender: 'male' + conversations: + - channel: Channel::WebWidget + messages: + - message_type: incoming + content: "Hey there, \n I need some help with the product, my button is not working on the website." + - name: "Ophelia Folkard" + email: "ofolkardi@taobao.test" + gender: 'female' + conversations: + - channel: Channel::WebWidget + assignee: michael_scott@paperlayer.test + messages: + - message_type: incoming + content: "Hey, \n My card is not working on your website. Please help" + - name: "Candice Matherson" + email: "cmathersonj@va.gov" + gender: 'female' + conversations: + - channel: Channel::Email + source_id: "cmathersonj@va.gov" + assignee: michael_scott@paperlayer.test + messages: + - message_type: incoming + content: "Hey, \n I'm looking for some help to figure out if it is the right product for me." + - message_type: outgoing + content: Welcome to PaperLayer. Our Team will be getting back you shortly. + - message_type: outgoing + content: How may i help you ? + sender: michael_scott@paperlayer.test diff --git a/public/assets/administrate/bot/avatar.png b/public/assets/administrate/bot/avatar.png new file mode 100644 index 000000000..4b5a2d686 Binary files /dev/null and b/public/assets/administrate/bot/avatar.png differ diff --git a/public/assets/administrate/user/avatar.png b/public/assets/administrate/user/avatar.png new file mode 100644 index 000000000..269eab83a Binary files /dev/null and b/public/assets/administrate/user/avatar.png differ