diff --git a/app/models/badge.rb b/app/models/badge.rb new file mode 100644 index 000000000..21b4160ce --- /dev/null +++ b/app/models/badge.rb @@ -0,0 +1,17 @@ +class Badge < ApplicationRecord + mount_uploader :badge_image, BadgeUploader + + has_many :badge_achievements + has_many :users, through: :badge_achievements + + validates :title, presence: true, uniqueness: true + validates :description, presence: true + + before_validation :generate_slug + + private + + def generate_slug + self.slug = title.to_s.downcase.tr(" ", "-").gsub(/[^\w-]/, "").tr("_", "") + end +end diff --git a/app/models/badge_achievement.rb b/app/models/badge_achievement.rb new file mode 100644 index 000000000..aa64d1938 --- /dev/null +++ b/app/models/badge_achievement.rb @@ -0,0 +1,5 @@ +class BadgeAchievement < ApplicationRecord + belongs_to :user + belongs_to :badge + belongs_to :rewarder, class_name: "User" +end diff --git a/app/models/user.rb b/app/models/user.rb index 90c34cd70..32109951d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -13,6 +13,8 @@ class User < ApplicationRecord belongs_to :organization, optional: true has_many :articles + has_many :badge_achievements + has_many :badges, through: :badge_achievements has_many :collections has_many :comments has_many :email_messages, class_name: "Ahoy::Message" diff --git a/app/uploaders/badge_uploader.rb b/app/uploaders/badge_uploader.rb new file mode 100644 index 000000000..6f603c49d --- /dev/null +++ b/app/uploaders/badge_uploader.rb @@ -0,0 +1,11 @@ +class BadgeUploader < CarrierWave::Uploader::Base + include CarrierWave::BombShelter + + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + + def extension_whitelist + %w(jpg jpeg gif png) + end +end diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb index 94c41f092..903b8d199 100644 --- a/config/initializers/carrierwave.rb +++ b/config/initializers/carrierwave.rb @@ -1,20 +1,19 @@ -require 'carrierwave/storage/abstract' -require 'carrierwave/storage/file' -require 'carrierwave/storage/fog' +require "carrierwave/storage/abstract" +require "carrierwave/storage/file" +require "carrierwave/storage/fog" CarrierWave.configure do |config| - if Rails.env.development? || Rails.env.test? config.storage = :file else # config.fog_provider = 'fog-aws' config.storage = :fog config.fog_credentials = { - :provider => 'AWS', - :aws_access_key_id => ENV['AWS_ID'], - :aws_secret_access_key => ENV['AWS_SECRET'], - :region => 'us-east-1' + provider: "AWS", + aws_access_key_id: ENV["AWS_ID"], + aws_secret_access_key: ENV["AWS_SECRET"], + region: "us-east-1", } - config.fog_directory = ENV['AWS_BUCKET_NAME'] + config.fog_directory = ENV["AWS_BUCKET_NAME"] end end diff --git a/db/migrate/20180502152520_create_badges.rb b/db/migrate/20180502152520_create_badges.rb new file mode 100644 index 000000000..1134c54a7 --- /dev/null +++ b/db/migrate/20180502152520_create_badges.rb @@ -0,0 +1,14 @@ +class CreateBadges < ActiveRecord::Migration[5.1] + def change + create_table :badges do |t| + t.string :title, null: false + t.string :slug, null: false + t.string :description, null: false + t.string :badge_image + + t.timestamps + end + + add_index :badges, :title, unique: true + end +end diff --git a/db/migrate/20180502152621_create_badge_achievements.rb b/db/migrate/20180502152621_create_badge_achievements.rb new file mode 100644 index 000000000..af938ba67 --- /dev/null +++ b/db/migrate/20180502152621_create_badge_achievements.rb @@ -0,0 +1,13 @@ +class CreateBadgeAchievements < ActiveRecord::Migration[5.1] + def change + create_table :badge_achievements do |t| + t.references :user, foreign_key: true, null: false + t.integer :rewarder_id + t.references :badge, foreign_key: true, null: false + + t.timestamps + end + + add_index :badge_achievements, [:user_id, :badge_id] + end +end diff --git a/db/schema.rb b/db/schema.rb index 385a63b01..6f48d5b00 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -116,6 +116,27 @@ ActiveRecord::Schema.define(version: 20180502174301) do t.index ["user_id"], name: "index_articles_on_user_id" end + create_table "badge_achievements", force: :cascade do |t| + t.bigint "badge_id", null: false + t.datetime "created_at", null: false + t.integer "rewarder_id" + t.datetime "updated_at", null: false + t.bigint "user_id", null: false + t.index ["badge_id"], name: "index_badge_achievements_on_badge_id" + t.index ["user_id", "badge_id"], name: "index_badge_achievements_on_user_id_and_badge_id" + t.index ["user_id"], name: "index_badge_achievements_on_user_id" + end + + create_table "badges", force: :cascade do |t| + t.string "badge_image" + t.datetime "created_at", null: false + t.string "description", null: false + t.string "slug", null: false + t.string "title", null: false + t.datetime "updated_at", null: false + t.index ["title"], name: "index_badges_on_title", unique: true + end + create_table "blocks", id: :serial, force: :cascade do |t| t.text "body_html" t.text "body_markdown" @@ -652,6 +673,8 @@ ActiveRecord::Schema.define(version: 20180502174301) do t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id" end + add_foreign_key "badge_achievements", "badges" + add_foreign_key "badge_achievements", "users" add_foreign_key "messages", "chat_channels" add_foreign_key "messages", "users" end diff --git a/spec/factories/badges.rb b/spec/factories/badges.rb new file mode 100644 index 000000000..dfa395ae7 --- /dev/null +++ b/spec/factories/badges.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :badge do + title { Faker::Overwatch.quote } + description { Faker::Lorem.sentence } + end +end diff --git a/spec/models/badge_achievement_spec.rb b/spec/models/badge_achievement_spec.rb new file mode 100644 index 000000000..16cc2c1e9 --- /dev/null +++ b/spec/models/badge_achievement_spec.rb @@ -0,0 +1,21 @@ +require "rails_helper" + +RSpec.describe BadgeAchievement, type: :model do + let(:user) { create(:user) } + let(:badge) { create(:badge) } + + describe "validations" do + subject { BadgeAchievement.create(user_id: user.id, badge_id: badge.id) } + + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:badge) } + it { is_expected.to belong_to(:rewarder).class_name("User") } + end + + it "allow duplicate badges" do + BadgeAchievement.create(user_id: user.id, badge_id: badge.id, rewarder_id: create(:user).id) + expect do + BadgeAchievement.create!(user_id: user.id, badge_id: badge.id, rewarder_id: create(:user).id) + end.not_to raise_error + end +end diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb new file mode 100644 index 000000000..51f988ea6 --- /dev/null +++ b/spec/models/badge_spec.rb @@ -0,0 +1,13 @@ +require "rails_helper" + +RSpec.describe Badge, type: :model do + describe "validations" do + subject { Badge.create(title: Faker::Overwatch.quote, description: Faker::Lorem.sentence) } + + it { is_expected.to have_many(:users).through(:badge_achievements) } + it { is_expected.to have_many(:badge_achievements) } + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_uniqueness_of(:title) } + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 75a8c36a0..2c718b6ee 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -9,15 +9,17 @@ RSpec.describe User, type: :model do let(:org) { create(:organization) } it { is_expected.to have_many(:articles) } - it { is_expected.to have_many(:reactions) } - it { is_expected.to have_many(:comments) } - it { is_expected.to have_many(:identities) } + it { is_expected.to have_many(:badge_achievements) } + it { is_expected.to have_many(:badges).through(:badge_achievements) } it { is_expected.to have_many(:collections) } - it { is_expected.to have_many(:tweets) } - it { is_expected.to have_many(:notifications) } - it { is_expected.to have_many(:mentions) } + it { is_expected.to have_many(:comments) } it { is_expected.to have_many(:email_messages).class_name("Ahoy::Message") } + it { is_expected.to have_many(:identities) } + it { is_expected.to have_many(:mentions) } it { is_expected.to have_many(:notes) } + it { is_expected.to have_many(:notifications) } + it { is_expected.to have_many(:reactions) } + it { is_expected.to have_many(:tweets) } it { is_expected.to validate_uniqueness_of(:username).case_insensitive } it { is_expected.to validate_uniqueness_of(:github_username).allow_blank } it { is_expected.to validate_uniqueness_of(:twitter_username).allow_blank }