From b284a37ee234a8c0a327401583e66332cf54b7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Wed, 4 Aug 2021 19:53:32 -0300 Subject: [PATCH 1/6] add pundit --- Gemfile | 1 + Gemfile.lock | 3 ++ app/controllers/application_controller.rb | 1 + app/policies/application_policy.rb | 50 +++++++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 app/policies/application_policy.rb diff --git a/Gemfile b/Gemfile index e824bb2..6af48e3 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,7 @@ gem "administrate" gem "graphql" gem "tailwindcss-rails" gem "httparty" +gem "pundit" group :development, :test do gem "dotenv-rails" diff --git a/Gemfile.lock b/Gemfile.lock index 2fd4fc7..d77b31e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -174,6 +174,8 @@ GEM public_suffix (4.0.6) puma (5.4.0) nio4r (~> 2.0) + pundit (2.1.0) + activesupport (>= 3.0.0) racc (1.5.2) rack (2.2.3) rack-proxy (0.7.0) @@ -322,6 +324,7 @@ DEPENDENCIES pg (~> 1.1) pry-byebug puma (~> 5.0) + pundit rails (~> 6.1.4) rspec-rails rubocop-rails diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e049134..396f23e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,4 @@ # frozen_string_literal: true class ApplicationController < ActionController::Base + include Pundit end diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb new file mode 100644 index 0000000..f527406 --- /dev/null +++ b/app/policies/application_policy.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true +class ApplicationPolicy + attr_reader :user, :record + + def initialize(user, record) + @user = user + @record = record + end + + def index? + false + end + + def show? + false + end + + def create? + false + end + + def new? + create? + end + + def update? + false + end + + def edit? + update? + end + + def destroy? + false + end + + class Scope + attr_reader :user, :scope + + def initialize(user, scope) + @user = user + @scope = scope + end + + def resolve + scope.all + end + end +end From a90855c5af52b21a68fb27070f79baa8d8d17cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Wed, 4 Aug 2021 19:59:21 -0300 Subject: [PATCH 2/6] add annotate gem --- Gemfile | 1 + Gemfile.lock | 4 ++ app/models/admin_user.rb | 19 +++++++++ db/schema.rb | 6 --- lib/tasks/auto_annotate_models.rake | 60 +++++++++++++++++++++++++++++ spec/models/admin_user_spec.rb | 19 +++++++++ 6 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 lib/tasks/auto_annotate_models.rake diff --git a/Gemfile b/Gemfile index 6af48e3..91a3d70 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,7 @@ group :development, :test do end group :development do + gem "annotate" gem "graphql_playground-rails" gem "web-console", ">= 4.1.0" diff --git a/Gemfile.lock b/Gemfile.lock index d77b31e..f894b7f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,6 +72,9 @@ GEM momentjs-rails (~> 2.8) sassc-rails (~> 2.1) selectize-rails (~> 0.6) + annotate (3.1.1) + activerecord (>= 3.2, < 7.0) + rake (>= 10.4, < 14.0) ast (2.4.2) bcrypt (3.1.16) bindex (0.8.1) @@ -310,6 +313,7 @@ PLATFORMS DEPENDENCIES administrate + annotate bootsnap (>= 1.4.4) capybara devise diff --git a/app/models/admin_user.rb b/app/models/admin_user.rb index 9380387..3d62e8d 100644 --- a/app/models/admin_user.rb +++ b/app/models/admin_user.rb @@ -1,4 +1,23 @@ # frozen_string_literal: true + +# == Schema Information +# +# Table name: admin_users +# +# id :bigint not null, primary key +# email :string default(""), not null +# encrypted_password :string default(""), not null +# remember_created_at :datetime +# reset_password_sent_at :datetime +# reset_password_token :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_admin_users_on_email (email) UNIQUE +# index_admin_users_on_reset_password_token (reset_password_token) UNIQUE +# class AdminUser < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable diff --git a/db/schema.rb b/db/schema.rb index 70fae2b..4abad79 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -27,10 +27,4 @@ ActiveRecord::Schema.define(version: 2021_08_03_222524) do t.index ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true end - create_table "customers", force: :cascade do |t| - t.string "name" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - end - end diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake new file mode 100644 index 0000000..9e736fc --- /dev/null +++ b/lib/tasks/auto_annotate_models.rake @@ -0,0 +1,60 @@ +# frozen_string_literal: true +# NOTE: only doing this in development as some production environments (Heroku) +# NOTE: are sensitive to local FS writes, and besides -- it's just not proper +# NOTE: to have a dev-mode tool do its thing in production. +if Rails.env.development? + require "annotate" + task set_annotation_options: :environment do + # You can override any of these by setting an environment variable of the + # same name. + Annotate.set_defaults( + "active_admin" => "false", + "additional_file_patterns" => [], + "routes" => "false", + "models" => "true", + "position_in_routes" => "before", + "position_in_class" => "before", + "position_in_test" => "before", + "position_in_fixture" => "before", + "position_in_factory" => "before", + "position_in_serializer" => "before", + "show_foreign_keys" => "true", + "show_complete_foreign_keys" => "false", + "show_indexes" => "true", + "simple_indexes" => "false", + "model_dir" => "app/models", + "root_dir" => "", + "include_version" => "false", + "require" => "", + "exclude_tests" => "false", + "exclude_fixtures" => "false", + "exclude_factories" => "false", + "exclude_serializers" => "false", + "exclude_scaffolds" => "true", + "exclude_controllers" => "true", + "exclude_helpers" => "true", + "exclude_sti_subclasses" => "false", + "ignore_model_sub_dir" => "false", + "ignore_columns" => nil, + "ignore_routes" => nil, + "ignore_unknown_models" => "false", + "hide_limit_column_types" => "integer,bigint,boolean", + "hide_default_column_types" => "json,jsonb,hstore", + "skip_on_db_migrate" => "false", + "format_bare" => "true", + "format_rdoc" => "false", + "format_yard" => "false", + "format_markdown" => "false", + "sort" => "false", + "force" => "false", + "frozen" => "false", + "classified_sort" => "true", + "trace" => "false", + "wrapper_open" => nil, + "wrapper_close" => nil, + "with_comment" => "true" + ) + end + + Annotate.load_tasks +end diff --git a/spec/models/admin_user_spec.rb b/spec/models/admin_user_spec.rb index cea59fd..a6218ff 100644 --- a/spec/models/admin_user_spec.rb +++ b/spec/models/admin_user_spec.rb @@ -1,4 +1,23 @@ # frozen_string_literal: true + +# == Schema Information +# +# Table name: admin_users +# +# id :bigint not null, primary key +# email :string default(""), not null +# encrypted_password :string default(""), not null +# remember_created_at :datetime +# reset_password_sent_at :datetime +# reset_password_token :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_admin_users_on_email (email) UNIQUE +# index_admin_users_on_reset_password_token (reset_password_token) UNIQUE +# require "rails_helper" RSpec.describe(AdminUser, type: :model) do From 41e311995363bc60c73fc1f8a94c6e2c76ab918e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Wed, 4 Aug 2021 20:32:58 -0300 Subject: [PATCH 3/6] add user model --- Gemfile | 10 +++-- Gemfile.lock | 6 +-- app/controllers/admin/users_controller.rb | 8 ++++ app/dashboards/user_dashboard.rb | 55 +++++++++++++++++++++++ app/models/user.rb | 24 ++++++++++ config/routes.rb | 3 +- db/migrate/20210804230323_create_users.rb | 14 ++++++ db/schema.rb | 11 ++++- spec/models/user_spec.rb | 26 +++++++++++ spec/rails_helper.rb | 7 +++ 10 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 app/controllers/admin/users_controller.rb create mode 100644 app/dashboards/user_dashboard.rb create mode 100644 app/models/user.rb create mode 100644 db/migrate/20210804230323_create_users.rb create mode 100644 spec/models/user_spec.rb diff --git a/Gemfile b/Gemfile index 91a3d70..8c466a3 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,6 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "2.7.4" -gem "jbuilder", "~> 2.7" gem "pg", "~> 1.1" gem "puma", "~> 5.0" gem "rails", "~> 6.1.4" @@ -15,11 +14,12 @@ gem "webpacker", "~> 5.0" gem "bootsnap", ">= 1.4.4", require: false gem "devise" -gem "devise-bootstrap-views" gem "devise-i18n" +gem "devise-bootstrap-views" + +gem "tailwindcss-rails" gem "administrate" gem "graphql" -gem "tailwindcss-rails" gem "httparty" gem "pundit" @@ -42,3 +42,7 @@ group :development do gem "listen", "~> 3.3" gem "spring" end + +group :test do + gem "shoulda-matchers", "~> 5.0" +end diff --git a/Gemfile.lock b/Gemfile.lock index f894b7f..abdc3b0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -121,8 +121,6 @@ GEM multi_xml (>= 0.5.2) i18n (1.8.10) concurrent-ruby (~> 1.0) - jbuilder (2.11.2) - activesupport (>= 5.0.0) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -271,6 +269,8 @@ GEM tilt selectize-rails (0.12.6) semantic_range (3.0.0) + shoulda-matchers (5.0.0) + activesupport (>= 5.2.0) spring (2.1.1) sprockets (4.0.2) concurrent-ruby (~> 1.0) @@ -323,7 +323,6 @@ DEPENDENCIES graphql graphql_playground-rails httparty - jbuilder (~> 2.7) listen (~> 3.3) pg (~> 1.1) pry-byebug @@ -335,6 +334,7 @@ DEPENDENCIES rubocop-rspec rubocop-shopify sass-rails (>= 6) + shoulda-matchers (~> 5.0) spring tailwindcss-rails turbolinks (~> 5) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb new file mode 100644 index 0000000..b8cac1c --- /dev/null +++ b/app/controllers/admin/users_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Admin + class UsersController < Admin::ApplicationController + def valid_action?(name, resource = resource_class) + ["new", "destroy"].exclude?(name.to_s) && super + end + end +end diff --git a/app/dashboards/user_dashboard.rb b/app/dashboards/user_dashboard.rb new file mode 100644 index 0000000..41e33f7 --- /dev/null +++ b/app/dashboards/user_dashboard.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +require "administrate/base_dashboard" + +class UserDashboard < Administrate::BaseDashboard + # ATTRIBUTE_TYPES + # a hash that describes the type of each of the model's fields. + # + # Each different type represents an Administrate::Field object, + # which determines how the attribute is displayed + # on pages throughout the dashboard. + ATTRIBUTE_TYPES = { + id: Field::Number, + full_name: Field::String, + first_name: Field::String, + last_name: Field::String, + email: Field::String, + created_at: Field::DateTime, + updated_at: Field::DateTime, + }.freeze + + # COLLECTION_ATTRIBUTES + # an array of attributes that will be displayed on the model's index page. + # + # By default, it's limited to four items to reduce clutter on index pages. + # Feel free to add, remove, or rearrange items. + COLLECTION_ATTRIBUTES = [:full_name, :email].freeze + + # SHOW_PAGE_ATTRIBUTES + # an array of attributes that will be displayed on the model's show page. + SHOW_PAGE_ATTRIBUTES = [:id, :first_name, :last_name, :email, :created_at, :updated_at].freeze + + # FORM_ATTRIBUTES + # an array of attributes that will be displayed + # on the model's form (`new` and `edit`) pages. + FORM_ATTRIBUTES = [:first_name, :last_name].freeze + + # COLLECTION_FILTERS + # a hash that defines filters that can be used while searching via the search + # field of the dashboard. + # + # For example to add an option to search for open resources by typing "open:" + # in the search field: + # + # COLLECTION_FILTERS = { + # open: ->(resources) { resources.where(open: true) } + # }.freeze + COLLECTION_FILTERS = {}.freeze + + # Overwrite this method to customize how users are displayed + # across all pages of the admin dashboard. + # + # def display_resource(user) + # "User ##{user.id}" + # end +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..76a2265 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: users +# +# id :bigint not null, primary key +# email :string not null +# first_name :string not null +# last_name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_email (email) UNIQUE +# +class User < ApplicationRecord + validates :first_name, :last_name, :email, presence: true + + def full_name + "#{first_name} #{last_name}" + end +end diff --git a/config/routes.rb b/config/routes.rb index b85e8d8..60f56ab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,9 +3,10 @@ Rails.application.routes.draw do devise_for :admin_users namespace :admin do + resources :users resources :admin_users - root to: "admin_users#index" + root to: "users#index" end root to: "home#index" diff --git a/db/migrate/20210804230323_create_users.rb b/db/migrate/20210804230323_create_users.rb new file mode 100644 index 0000000..55ca6e0 --- /dev/null +++ b/db/migrate/20210804230323_create_users.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +class CreateUsers < ActiveRecord::Migration[6.1] + def change + create_table(:users) do |t| + t.string(:first_name, null: false) + t.string(:last_name, null: false) + t.string(:email, null: false) + + t.timestamps + end + + add_index(:users, :email, unique: true) + end +end diff --git a/db/schema.rb b/db/schema.rb index 4abad79..6577b7a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_08_03_222524) do +ActiveRecord::Schema.define(version: 2021_08_04_230323) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -27,4 +27,13 @@ ActiveRecord::Schema.define(version: 2021_08_03_222524) do t.index ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true end + create_table "users", force: :cascade do |t| + t.string "first_name", null: false + t.string "last_name", null: false + t.string "email", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["email"], name: "index_users_on_email", unique: true + end + end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000..94b73f9 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: users +# +# id :bigint not null, primary key +# email :string not null +# first_name :string not null +# last_name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_email (email) UNIQUE +# +require "rails_helper" + +RSpec.describe(User, type: :model) do + describe "validations" do + it { is_expected.to(validate_presence_of(:first_name)) } + it { is_expected.to(validate_presence_of(:last_name)) } + it { is_expected.to(validate_presence_of(:email)) } + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 262e28b..990e786 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -67,3 +67,10 @@ RSpec.configure do |config| # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") end + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework(:rspec) + with.library(:rails) + end +end From 95079e2ae741ea4c24bc04f0209d3302b9a83531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Wed, 4 Aug 2021 20:35:26 -0300 Subject: [PATCH 4/6] add active storage --- Gemfile | 1 + Gemfile.lock | 7 +++ ...te_active_storage_tables.active_storage.rb | 43 +++++++++++++++++++ db/schema.rb | 32 +++++++++++++- 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210804233306_create_active_storage_tables.active_storage.rb diff --git a/Gemfile b/Gemfile index 8c466a3..2e53c9d 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,7 @@ gem "sass-rails", ">= 6" gem "turbolinks", "~> 5" gem "webpacker", "~> 5.0" gem "bootsnap", ">= 1.4.4", require: false +gem "image_processing", "~> 1.12" gem "devise" gem "devise-i18n" diff --git a/Gemfile.lock b/Gemfile.lock index abdc3b0..4728078 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -121,6 +121,9 @@ GEM multi_xml (>= 0.5.2) i18n (1.8.10) concurrent-ruby (~> 1.0) + image_processing (1.12.1) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -150,6 +153,7 @@ GEM mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2021.0704) + mini_magick (4.11.0) mini_mime (1.1.0) mini_portile2 (2.6.1) minitest (5.14.4) @@ -257,6 +261,8 @@ GEM rubocop-shopify (2.2.0) rubocop (~> 1.18) ruby-progressbar (1.11.0) + ruby-vips (2.1.2) + ffi (~> 1.12) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -323,6 +329,7 @@ DEPENDENCIES graphql graphql_playground-rails httparty + image_processing (~> 1.12) listen (~> 3.3) pg (~> 1.1) pry-byebug diff --git a/db/migrate/20210804233306_create_active_storage_tables.active_storage.rb b/db/migrate/20210804233306_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..08c3ae3 --- /dev/null +++ b/db/migrate/20210804233306_create_active_storage_tables.active_storage.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table(:active_storage_blobs) do |t| + t.string(:key, null: false) + t.string(:filename, null: false) + t.string(:content_type) + t.text(:metadata) + t.string(:service_name, null: false) + t.bigint(:byte_size, null: false) + t.string(:checksum, null: false) + t.datetime(:created_at, null: false) + + t.index([:key], unique: true) + end + + create_table(:active_storage_attachments) do |t| + t.string(:name, null: false) + t.references(:record, null: false, polymorphic: true, index: false) + t.references(:blob, null: false) + + t.datetime(:created_at, null: false) + + t.index( + [:record_type, :record_id, :name, :blob_id], + name: "index_active_storage_attachments_uniqueness", + unique: true + ) + t.foreign_key(:active_storage_blobs, column: :blob_id) + end + + # rubocop:disable Rails/CreateTableWithTimestamps + create_table(:active_storage_variant_records) do |t| + t.belongs_to(:blob, null: false, index: false) + t.string(:variation_digest, null: false) + + t.index([:blob_id, :variation_digest], name: "index_active_storage_variant_records_uniqueness", unique: true) + t.foreign_key(:active_storage_blobs, column: :blob_id) + end + # rubocop:enable Rails/CreateTableWithTimestamps + end +end diff --git a/db/schema.rb b/db/schema.rb index 6577b7a..3c1943b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,39 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_08_04_230323) do +ActiveRecord::Schema.define(version: 2021_08_04_233306) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name", null: false + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + create_table "admin_users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false @@ -36,4 +64,6 @@ ActiveRecord::Schema.define(version: 2021_08_04_230323) do t.index ["email"], name: "index_users_on_email", unique: true end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" end From 30b290514f69d1950c0d374ba361a257ecdb8858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Wed, 4 Aug 2021 21:53:58 -0300 Subject: [PATCH 5/6] add user document model --- Gemfile | 4 +- Gemfile.lock | 7 +++ .../admin/user_documents_controller.rb | 8 +++ app/dashboards/user_document_dashboard.rb | 54 +++++++++++++++++++ app/models/user.rb | 2 + app/models/user_document.rb | 30 +++++++++++ config/routes.rb | 3 +- .../20210805000225_create_user_documents.rb | 11 ++++ db/schema.rb | 11 +++- spec/models/user_document_spec.rb | 27 ++++++++++ spec/models/user_spec.rb | 4 ++ 11 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 app/controllers/admin/user_documents_controller.rb create mode 100644 app/dashboards/user_document_dashboard.rb create mode 100644 app/models/user_document.rb create mode 100644 db/migrate/20210805000225_create_user_documents.rb create mode 100644 spec/models/user_document_spec.rb diff --git a/Gemfile b/Gemfile index 2e53c9d..aade363 100644 --- a/Gemfile +++ b/Gemfile @@ -18,10 +18,12 @@ gem "devise" gem "devise-i18n" gem "devise-bootstrap-views" +gem "administrate-field-active_storage" gem "tailwindcss-rails" gem "administrate" -gem "graphql" +gem "enumerize" gem "httparty" +gem "graphql" gem "pundit" group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 4728078..083982b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,6 +72,9 @@ GEM momentjs-rails (~> 2.8) sassc-rails (~> 2.1) selectize-rails (~> 0.6) + administrate-field-active_storage (0.3.7) + administrate (>= 0.2.2) + rails (>= 6.0) annotate (3.1.1) activerecord (>= 3.2, < 7.0) rake (>= 10.4, < 14.0) @@ -109,6 +112,8 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) + enumerize (2.4.0) + activesupport (>= 3.2) erubi (1.10.0) ffi (1.15.3) globalid (0.5.2) @@ -319,6 +324,7 @@ PLATFORMS DEPENDENCIES administrate + administrate-field-active_storage annotate bootsnap (>= 1.4.4) capybara @@ -326,6 +332,7 @@ DEPENDENCIES devise-bootstrap-views devise-i18n dotenv-rails + enumerize graphql graphql_playground-rails httparty diff --git a/app/controllers/admin/user_documents_controller.rb b/app/controllers/admin/user_documents_controller.rb new file mode 100644 index 0000000..6fc8f77 --- /dev/null +++ b/app/controllers/admin/user_documents_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Admin + class UserDocumentsController < Admin::ApplicationController + def valid_action?(name, resource = resource_class) + ["destroy"].exclude?(name.to_s) && super + end + end +end diff --git a/app/dashboards/user_document_dashboard.rb b/app/dashboards/user_document_dashboard.rb new file mode 100644 index 0000000..29d7a1d --- /dev/null +++ b/app/dashboards/user_document_dashboard.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +require "administrate/base_dashboard" + +class UserDocumentDashboard < Administrate::BaseDashboard + # ATTRIBUTE_TYPES + # a hash that describes the type of each of the model's fields. + # + # Each different type represents an Administrate::Field object, + # which determines how the attribute is displayed + # on pages throughout the dashboard. + ATTRIBUTE_TYPES = { + id: Field::Number, + user: Field::BelongsTo, + image: Field::ActiveStorage, + status: Field::Select.with_options(collection: UserDocument.status.values), + created_at: Field::DateTime, + updated_at: Field::DateTime, + }.freeze + + # COLLECTION_ATTRIBUTES + # an array of attributes that will be displayed on the model's index page. + # + # By default, it's limited to four items to reduce clutter on index pages. + # Feel free to add, remove, or rearrange items. + COLLECTION_ATTRIBUTES = [:user, :status].freeze + + # SHOW_PAGE_ATTRIBUTES + # an array of attributes that will be displayed on the model's show page. + SHOW_PAGE_ATTRIBUTES = [:id, :user, :status, :image, :created_at, :updated_at].freeze + + # FORM_ATTRIBUTES + # an array of attributes that will be displayed + # on the model's form (`new` and `edit`) pages. + FORM_ATTRIBUTES = [:status].freeze + + # COLLECTION_FILTERS + # a hash that defines filters that can be used while searching via the search + # field of the dashboard. + # + # For example to add an option to search for open resources by typing "open:" + # in the search field: + # + # COLLECTION_FILTERS = { + # open: ->(resources) { resources.where(open: true) } + # }.freeze + COLLECTION_FILTERS = {}.freeze + + # Overwrite this method to customize how user documents are displayed + # across all pages of the admin dashboard. + # + # def display_resource(user_document) + # "UserDocument ##{user_document.id}" + # end +end diff --git a/app/models/user.rb b/app/models/user.rb index 76a2265..7073a2d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,6 +16,8 @@ # index_users_on_email (email) UNIQUE # class User < ApplicationRecord + has_many :documents, class_name: "UserDocument", dependent: :destroy + validates :first_name, :last_name, :email, presence: true def full_name diff --git a/app/models/user_document.rb b/app/models/user_document.rb new file mode 100644 index 0000000..df5578c --- /dev/null +++ b/app/models/user_document.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: user_documents +# +# id :bigint not null, primary key +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# user_id :bigint not null +# +# Indexes +# +# index_user_documents_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +class UserDocument < ApplicationRecord + extend Enumerize + + belongs_to :user + has_one_attached :image + + enumerize :status, + in: [:pending_review, :approved, :refused], + default: :pending_review +end diff --git a/config/routes.rb b/config/routes.rb index 60f56ab..ccf16ca 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,7 @@ Rails.application.routes.draw do namespace :admin do resources :users + resources :user_documents resources :admin_users root to: "users#index" @@ -11,7 +12,7 @@ Rails.application.routes.draw do root to: "home#index" get "*all" => "home#index", constraints: lambda { |req| - Rails.env.development? ? req.path.exclude?("playground") : req + req.path.exclude?("playground") && req.path.exclude?("rails") } post "/graphql", to: "graphql#execute" diff --git a/db/migrate/20210805000225_create_user_documents.rb b/db/migrate/20210805000225_create_user_documents.rb new file mode 100644 index 0000000..8d663a3 --- /dev/null +++ b/db/migrate/20210805000225_create_user_documents.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +class CreateUserDocuments < ActiveRecord::Migration[6.1] + def change + create_table(:user_documents) do |t| + t.string(:status, null: false) + t.references(:user, null: false, foreign_key: true) + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 3c1943b..edd001a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_08_04_233306) do +ActiveRecord::Schema.define(version: 2021_08_05_000225) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -55,6 +55,14 @@ ActiveRecord::Schema.define(version: 2021_08_04_233306) do t.index ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true end + create_table "user_documents", force: :cascade do |t| + t.string "status", null: false + t.bigint "user_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["user_id"], name: "index_user_documents_on_user_id" + end + create_table "users", force: :cascade do |t| t.string "first_name", null: false t.string "last_name", null: false @@ -66,4 +74,5 @@ ActiveRecord::Schema.define(version: 2021_08_04_233306) do add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "user_documents", "users" end diff --git a/spec/models/user_document_spec.rb b/spec/models/user_document_spec.rb new file mode 100644 index 0000000..d13bd1a --- /dev/null +++ b/spec/models/user_document_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: user_documents +# +# id :bigint not null, primary key +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# user_id :bigint not null +# +# Indexes +# +# index_user_documents_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +require "rails_helper" + +RSpec.describe(UserDocument, type: :model) do + describe "associations" do + it { is_expected.to(belong_to(:user)) } + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 94b73f9..d1af9d9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -23,4 +23,8 @@ RSpec.describe(User, type: :model) do it { is_expected.to(validate_presence_of(:last_name)) } it { is_expected.to(validate_presence_of(:email)) } end + + describe "associations" do + it { is_expected.to(have_many(:documents)) } + end end From 964ae85d46f063a57532056e42b5e198f411f089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Wed, 4 Aug 2021 23:06:28 -0300 Subject: [PATCH 6/6] add createUser mutation --- .rubocop.yml | 6 ++ Gemfile | 1 + Gemfile.lock | 3 + app/controllers/graphql_controller.rb | 2 +- app/graphql/inputs/user_attributes_input.rb | 9 +++ app/graphql/mutations/base_mutation.rb | 4 ++ app/graphql/mutations/create_user.rb | 16 +++++ app/graphql/types/mutation_type.rb | 7 +- app/models/user.rb | 1 + app/services/auth/authenticate.rb | 2 + .../inputs/user_attributes_input_spec.rb | 11 ++++ spec/graphql/mutations/create_user_spec.rb | 66 +++++++++++++++++++ 12 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 app/graphql/inputs/user_attributes_input.rb create mode 100644 app/graphql/mutations/create_user.rb create mode 100644 spec/graphql/inputs/user_attributes_input_spec.rb create mode 100644 spec/graphql/mutations/create_user_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 81319d1..c96a2be 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -10,3 +10,9 @@ AllCops: Exclude: - db/schema.rb - bin/**/* + +RSpec/ExampleLength: + Enabled: false + +RSpec/MultipleExpectations: + Enabled: false diff --git a/Gemfile b/Gemfile index aade363..5a8db7b 100644 --- a/Gemfile +++ b/Gemfile @@ -48,4 +48,5 @@ end group :test do gem "shoulda-matchers", "~> 5.0" + gem "rspec-graphql_matchers", "~> 1.3" end diff --git a/Gemfile.lock b/Gemfile.lock index 083982b..bc77765 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -233,6 +233,8 @@ GEM rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) + rspec-graphql_matchers (1.3.0) + graphql (>= 1.8, < 2.0) rspec-mocks (3.10.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) @@ -343,6 +345,7 @@ DEPENDENCIES puma (~> 5.0) pundit rails (~> 6.1.4) + rspec-graphql_matchers (~> 1.3) rspec-rails rubocop-rails rubocop-rspec diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 8f9e23c..b729732 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -9,8 +9,8 @@ class GraphqlController < ApplicationController query = params[:query] operation_name = params[:operationName] context = { - current_user: current_admin_user, # || current_auth.current_user, current_auth: current_auth, + current_user: current_admin_user, # || current_auth.current_user, } result = XStakeSchema.execute(query, variables: variables, context: context, operation_name: operation_name) render(json: result) diff --git a/app/graphql/inputs/user_attributes_input.rb b/app/graphql/inputs/user_attributes_input.rb new file mode 100644 index 0000000..61dc56a --- /dev/null +++ b/app/graphql/inputs/user_attributes_input.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true +module Inputs + class UserAttributesInput < Types::BaseInputObject + graphql_name "UserAttributesInput" + + argument :first_name, String, required: true + argument :last_name, String, required: true + end +end diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index a6576af..e589946 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -5,5 +5,9 @@ module Mutations field_class Types::BaseField input_object_class Types::BaseInputObject object_class Types::BaseObject + + field :errors, [String], + null: true, + description: "Errors encountered during execution of the mutation." end end diff --git a/app/graphql/mutations/create_user.rb b/app/graphql/mutations/create_user.rb new file mode 100644 index 0000000..c314e14 --- /dev/null +++ b/app/graphql/mutations/create_user.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +module Mutations + class CreateUser < BaseMutation + field :success, Boolean, null: false + + argument :user, Inputs::UserAttributesInput, required: true + + def resolve(user:) + User.create!({ **user, email: context[:current_auth].email }) + + { success: true } + rescue ActiveRecord::RecordInvalid => e + { success: false, errors: [e.message] } + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 1c6dc93..7712f72 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -1,11 +1,6 @@ # frozen_string_literal: true module Types class MutationType < Types::BaseObject - # TODO: remove me - field :test_field, String, null: false, - description: "An example field added by the generator" - def test_field - "Hello World" - end + field :create_user, mutation: Mutations::CreateUser end end diff --git a/app/models/user.rb b/app/models/user.rb index 7073a2d..42f639c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,6 +19,7 @@ class User < ApplicationRecord has_many :documents, class_name: "UserDocument", dependent: :destroy validates :first_name, :last_name, :email, presence: true + validates :email, uniqueness: true def full_name "#{first_name} #{last_name}" diff --git a/app/services/auth/authenticate.rb b/app/services/auth/authenticate.rb index dce1225..b6d8838 100644 --- a/app/services/auth/authenticate.rb +++ b/app/services/auth/authenticate.rb @@ -8,6 +8,8 @@ module Auth end def profile + return nil if jwt_token.blank? + Auth0Client.find_profile(jwt_token) end end diff --git a/spec/graphql/inputs/user_attributes_input_spec.rb b/spec/graphql/inputs/user_attributes_input_spec.rb new file mode 100644 index 0000000..d3fe858 --- /dev/null +++ b/spec/graphql/inputs/user_attributes_input_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +require "rails_helper" + +RSpec.describe(Inputs::UserAttributesInput) do + subject { described_class } + + describe "arguments" do + it { is_expected.to(accept_argument(:first_name).of_type("String!")) } + it { is_expected.to(accept_argument(:last_name).of_type("String!")) } + end +end diff --git a/spec/graphql/mutations/create_user_spec.rb b/spec/graphql/mutations/create_user_spec.rb new file mode 100644 index 0000000..ef8f711 --- /dev/null +++ b/spec/graphql/mutations/create_user_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true +require "rails_helper" + +RSpec.describe(Mutations::CreateUser) do + describe "#resolve" do + let(:mutation_string) do + <<~GQL + mutation($input: CreateUserInput!) { + createUser(input: $input) { + success + errors + } + } + GQL + end + + let(:context) do + { + current_auth: Auth::Profile.new({ + id: "_", + email: "user@example.com", + }), + } + end + + let(:variables) do + { + input: { user: { + firstName: "First Name", + lastName: "Last Name", + } }, + } + end + + context "when current_auth is not being used by any user" do + it "create a user to auth" do + result = XStakeSchema.execute( + mutation_string, + variables: variables, + context: context + ).to_h + + expect(result["data"]["createUser"]["success"]).to(eq(true)) + end + end + + context "when auth is being used by no users" do + it "returns error" do + User.create( + first_name: "First Name", + last_name: "Last Name", + email: "user@example.com" + ) + + result = XStakeSchema.execute( + mutation_string, + variables: variables, + context: context + ).to_h + + expect(result["data"]["createUser"]["success"]).to(eq(false)) + expect(result["data"]["createUser"]["errors"]).to(eq(["Validation failed: Email has already been taken"])) + end + end + end +end