From 0ea0188c3fe43a18cf4eeae52813cb3023ddf185 Mon Sep 17 00:00:00 2001 From: AnySiPlusplus Date: Tue, 4 Jul 2023 14:39:35 +0300 Subject: [PATCH 1/3] added friend func --- app/controllers/api/v1/friends_controller.rb | 20 +++ app/interactors/api/v1/friends/add_friend.rb | 48 +++++++ app/interactors/api/v1/friends/index.rb | 13 ++ .../api/v1/friends/remove_friend.rb | 25 ++++ app/models/friend.rb | 6 + app/models/user.rb | 2 + app/models/user_friend.rb | 4 + app/policies/api/v1/friend_policy.rb | 9 ++ app/serializers/friend_serializer.rb | 3 + config/routes.rb | 1 + db/migrate/20230630084051_create_friends.rb | 9 ++ .../20230704074345_create_user_friends.rb | 10 ++ db/schema.rb | 21 ++- spec/factories/friends.rb | 5 + spec/factories/user_friends.rb | 6 + .../api/v1/friends/add_friend_spec.rb | 28 ++++ spec/models/friend_spec.rb | 5 + spec/models/user_friend_spec.rb | 6 + spec/requests/api/v1/friend_spec.rb | 135 ++++++++++++++++++ spec/support/api/schemas/friend_added.json | 36 +++++ spec/support/api/schemas/friend_index.json | 39 +++++ 21 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 app/controllers/api/v1/friends_controller.rb create mode 100644 app/interactors/api/v1/friends/add_friend.rb create mode 100644 app/interactors/api/v1/friends/index.rb create mode 100644 app/interactors/api/v1/friends/remove_friend.rb create mode 100644 app/models/friend.rb create mode 100644 app/models/user_friend.rb create mode 100644 app/policies/api/v1/friend_policy.rb create mode 100644 app/serializers/friend_serializer.rb create mode 100644 db/migrate/20230630084051_create_friends.rb create mode 100644 db/migrate/20230704074345_create_user_friends.rb create mode 100644 spec/factories/friends.rb create mode 100644 spec/factories/user_friends.rb create mode 100644 spec/interactors/api/v1/friends/add_friend_spec.rb create mode 100644 spec/models/friend_spec.rb create mode 100644 spec/models/user_friend_spec.rb create mode 100644 spec/requests/api/v1/friend_spec.rb create mode 100644 spec/support/api/schemas/friend_added.json create mode 100644 spec/support/api/schemas/friend_index.json diff --git a/app/controllers/api/v1/friends_controller.rb b/app/controllers/api/v1/friends_controller.rb new file mode 100644 index 0000000..eb5f4a7 --- /dev/null +++ b/app/controllers/api/v1/friends_controller.rb @@ -0,0 +1,20 @@ +module Api + module V1 + class FriendsController < AuthentificateController + def index + result = Api::V1::Friends::Index.call(current_user:, serializer: FriendSerializer) + default_handler(result) + end + + def create + result = Api::V1::Friends::AddFriend.call(params:, current_user:, serializer: FriendSerializer) + default_handler(result) + end + + def destroy + result = Api::V1::Friends::RemoveFriend.call(params:, current_user:) + default_handler(result) + end + end + end +end diff --git a/app/interactors/api/v1/friends/add_friend.rb b/app/interactors/api/v1/friends/add_friend.rb new file mode 100644 index 0000000..1492c74 --- /dev/null +++ b/app/interactors/api/v1/friends/add_friend.rb @@ -0,0 +1,48 @@ +module Api + module V1 + module Friends + class AddFriend < BaseInteractor + def call + return not_found unless current_friend + return good_outcome if friend_excist? + + current_form.save + result + end + + private + + def good_outcome + context.status = :ok + context.success_message = { friend: :already_added } + end + + def friend_excist? + context.current_user.friends.each do |friend| + return true if friend.user == current_friend + end + + false + end + + def current_form + @current_form ||= UserFriend.new(user_id: context.current_user.id, friend_id: friend_form.id) + end + + def current_friend + @current_friend ||= User.find_by(id: context.params[:user_id]) + end + + def friend_form + @friend_form ||= Friend.create(user_id: current_friend.id) + end + + def result + context.form = current_form + context.status = :created + context.success_message = { friend: :added } + end + end + end + end +end diff --git a/app/interactors/api/v1/friends/index.rb b/app/interactors/api/v1/friends/index.rb new file mode 100644 index 0000000..0c4bda5 --- /dev/null +++ b/app/interactors/api/v1/friends/index.rb @@ -0,0 +1,13 @@ +module Api + module V1 + module Friends + class Index < BaseInteractor + def call + context.form = context.current_user.friends + context.status = :ok + context.success_message = { operation: :success } + end + end + end + end +end diff --git a/app/interactors/api/v1/friends/remove_friend.rb b/app/interactors/api/v1/friends/remove_friend.rb new file mode 100644 index 0000000..50e517d --- /dev/null +++ b/app/interactors/api/v1/friends/remove_friend.rb @@ -0,0 +1,25 @@ +module Api + module V1 + module Friends + class RemoveFriend < BaseInteractor + def call + return not_found unless current_friend + return access_denied unless belong_to_user?(Api::V1::FriendPolicy, current_friend) + + result + end + + private + + def current_friend + @current_friend ||= Friend.find_by(id: context.params[:id]) + end + + def result + context.status = :no_content + current_friend.destroy + end + end + end + end +end diff --git a/app/models/friend.rb b/app/models/friend.rb new file mode 100644 index 0000000..3c8aa38 --- /dev/null +++ b/app/models/friend.rb @@ -0,0 +1,6 @@ +class Friend < ApplicationRecord + belongs_to :user + + has_many :user_friends, dependent: :destroy + has_many :users, through: :user_friends +end diff --git a/app/models/user.rb b/app/models/user.rb index c92827a..6f4aa46 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,4 +3,6 @@ class User < ApplicationRecord has_many :messages, dependent: :destroy has_many :news, dependent: :destroy + has_many :user_friends, dependent: :destroy + has_many :friends, through: :user_friends end diff --git a/app/models/user_friend.rb b/app/models/user_friend.rb new file mode 100644 index 0000000..442ee2c --- /dev/null +++ b/app/models/user_friend.rb @@ -0,0 +1,4 @@ +class UserFriend < ApplicationRecord + belongs_to :user + belongs_to :friend +end diff --git a/app/policies/api/v1/friend_policy.rb b/app/policies/api/v1/friend_policy.rb new file mode 100644 index 0000000..288b604 --- /dev/null +++ b/app/policies/api/v1/friend_policy.rb @@ -0,0 +1,9 @@ +module Api + module V1 + class FriendPolicy < ApplicationPolicy + def belong_to_user? + @subject.friends.include?(@model) + end + end + end +end diff --git a/app/serializers/friend_serializer.rb b/app/serializers/friend_serializer.rb new file mode 100644 index 0000000..2addaac --- /dev/null +++ b/app/serializers/friend_serializer.rb @@ -0,0 +1,3 @@ +class FriendSerializer < ApplicationSerializer + attributes :user_id, :created_at, :updated_at +end diff --git a/config/routes.rb b/config/routes.rb index d9de39a..3c58565 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,7 @@ resources :messages resources :news resource :newsline, only: :show + resources :friends, except: %i[update show] end end end diff --git a/db/migrate/20230630084051_create_friends.rb b/db/migrate/20230630084051_create_friends.rb new file mode 100644 index 0000000..2ab60b1 --- /dev/null +++ b/db/migrate/20230630084051_create_friends.rb @@ -0,0 +1,9 @@ +class CreateFriends < ActiveRecord::Migration[7.0] + def change + create_table :friends do |t| + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20230704074345_create_user_friends.rb b/db/migrate/20230704074345_create_user_friends.rb new file mode 100644 index 0000000..e020e63 --- /dev/null +++ b/db/migrate/20230704074345_create_user_friends.rb @@ -0,0 +1,10 @@ +class CreateUserFriends < ActiveRecord::Migration[7.0] + def change + create_table :user_friends do |t| + t.references :user, null: false, foreign_key: true + t.references :friend, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 6847d7b..19e3cf9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,17 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_06_26_101303) do +ActiveRecord::Schema[7.0].define(version: 2023_07_04_074345) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "friends", force: :cascade do |t| + t.bigint "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id"], name: "index_friends_on_user_id" + end + create_table "messages", force: :cascade do |t| t.bigint "user_id", null: false t.text "text" @@ -33,6 +40,15 @@ t.index ["user_id"], name: "index_news_on_user_id" end + create_table "user_friends", force: :cascade do |t| + t.bigint "user_id", null: false + t.bigint "friend_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["friend_id"], name: "index_user_friends_on_friend_id" + t.index ["user_id"], name: "index_user_friends_on_user_id" + end + create_table "users", force: :cascade do |t| t.string "login", null: false t.string "password_digest", null: false @@ -40,6 +56,9 @@ t.datetime "updated_at", null: false end + add_foreign_key "friends", "users" add_foreign_key "messages", "users" add_foreign_key "news", "users" + add_foreign_key "user_friends", "friends" + add_foreign_key "user_friends", "users" end diff --git a/spec/factories/friends.rb b/spec/factories/friends.rb new file mode 100644 index 0000000..1a7eca0 --- /dev/null +++ b/spec/factories/friends.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :friend do + user { create(:user) } + end +end diff --git a/spec/factories/user_friends.rb b/spec/factories/user_friends.rb new file mode 100644 index 0000000..e3d745f --- /dev/null +++ b/spec/factories/user_friends.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :user_friend do + user { create(:user) } + friend { create(:friend) } + end +end diff --git a/spec/interactors/api/v1/friends/add_friend_spec.rb b/spec/interactors/api/v1/friends/add_friend_spec.rb new file mode 100644 index 0000000..5c566fa --- /dev/null +++ b/spec/interactors/api/v1/friends/add_friend_spec.rb @@ -0,0 +1,28 @@ +RSpec.describe Api::V1::Friends::AddFriend do + subject(:result) { described_class.call(params:, current_user: user) } + + let!(:user) { create(:user) } + + describe 'Success' do + let!(:friend_user) { create(:user) } + let(:params) { { user_id: friend_user.id } } + + it 'is create message' do + expect { result }.to change(Friend, :count).from(0).to(1) + end + + it 'is result success' do + expect(result).to be_success + end + end + + describe 'Wrong' do + context 'when not found user' do + let(:params) { { user_id: 0 } } + + it 'is result not success' do + expect(result).not_to be_success + end + end + end +end diff --git a/spec/models/friend_spec.rb b/spec/models/friend_spec.rb new file mode 100644 index 0000000..46990e9 --- /dev/null +++ b/spec/models/friend_spec.rb @@ -0,0 +1,5 @@ +RSpec.describe Friend, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:user) } + end +end diff --git a/spec/models/user_friend_spec.rb b/spec/models/user_friend_spec.rb new file mode 100644 index 0000000..374f9d1 --- /dev/null +++ b/spec/models/user_friend_spec.rb @@ -0,0 +1,6 @@ +RSpec.describe UserFriend, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:friend) } + end +end diff --git a/spec/requests/api/v1/friend_spec.rb b/spec/requests/api/v1/friend_spec.rb new file mode 100644 index 0000000..e428b40 --- /dev/null +++ b/spec/requests/api/v1/friend_spec.rb @@ -0,0 +1,135 @@ +require 'swagger_helper' + +RSpec.describe 'Friends', type: :request do + describe 'Friends' do + let!(:user) { create(:user) } + let(:Authorization) { "Bearer #{generate_token(user)}" } + + path '/api/v1/friends' do + post 'Add friend' do + tags 'Friend' + produces 'application/json' + consumes 'application/json' + parameter name: :params, in: :body, schema: { + properties: { + user_id: { type: :string } + }, + + required: %w[user_id] + } + + parameter({ + in: :header, + type: :string, + name: :Authorization, + required: true, + description: 'Client token' + }) + + response '201', 'Friend added' do + let(:params) { { user_id: create(:user).id } } + + run_test! do + expect(response).to match_json_schema('friend_added') + end + end + + response '404', 'not found' do + let(:params) { { user_id: 0 } } + + run_test! do + expect(response).to match_json_schema('not_found') + end + end + + response '401', 'not aurhorized' do + let(:Authorization) { 'no token' } + let(:params) { 'params' } + + run_test! do + expect(response).to match_json_schema('session_not_authorized') + end + end + end + + get 'index friends' do + tags 'Friend' + produces 'application/json' + consumes 'application/json' + + parameter({ + in: :header, + type: :string, + name: :Authorization, + required: true, + description: 'Client token' + }) + + response '200', 'index friends' do + let(:friend) { create(:user_friend, user_id: user.id) } + + run_test! do + expect(response).to match_json_schema('friend_index') + end + end + + response '401', 'not aurhorized' do + let(:Authorization) { 'no token' } + + run_test! do + expect(response).to match_json_schema('session_not_authorized') + end + end + end + end + + path '/api/v1/friends/{id}' do + parameter({ + in: :header, + type: :string, + name: :Authorization, + required: true, + description: 'Client token' + }) + + delete 'remove friend' do + tags 'Friend' + consumes 'application/json' + produces 'application/json' + parameter name: :id, in: :path, type: :string + + response '204', 'remove friend' do + let(:user_friend) { create(:user_friend, user_id: user.id) } + let(:id) { user_friend.friend.id } + + run_test! + end + + response '401', 'un authorized' do + let(:Authorization) { 'no token' } + let(:id) { 0 } + + run_test! do + expect(response).to match_json_schema('session_not_authorized') + end + end + + response '403', 'forbidden' do + let(:id) { create(:user_friend, user_id: create(:user).id).friend.id } + + run_test! do + expect(response).to match_json_schema('user_forbidden') + end + end + + response '404', 'not found' do + let(:id) { 0 } + + run_test! do + expect(response).to match_json_schema('not_found') + end + end + end + end + end +end diff --git a/spec/support/api/schemas/friend_added.json b/spec/support/api/schemas/friend_added.json new file mode 100644 index 0000000..e6f4812 --- /dev/null +++ b/spec/support/api/schemas/friend_added.json @@ -0,0 +1,36 @@ +{ + "type": "array", + "items": { + "type": "object", + "required": [], + "properties": { + "data": { + "type": "object", + "required": [], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "type": "object", + "required": [], + "properties": { + "title": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + } + } + } + } + } +} diff --git a/spec/support/api/schemas/friend_index.json b/spec/support/api/schemas/friend_index.json new file mode 100644 index 0000000..8e82408 --- /dev/null +++ b/spec/support/api/schemas/friend_index.json @@ -0,0 +1,39 @@ +{ + "type": "array", + "items": { + "type": "object", + "required": [], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "type": "object", + "required": [], + "properties": { + "title": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + } + } + } + } + } + } +} From 5fcb41a70837f8333c4c531992f6b3949bc4a7e7 Mon Sep 17 00:00:00 2001 From: AnySiPlusplus Date: Mon, 10 Jul 2023 11:07:12 +0300 Subject: [PATCH 2/3] readme.md --- README.md | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 7db80e4..56fb14d 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,7 @@ -# README +##### Prerequisites -This README would normally document whatever steps are necessary to get the -application up and running. +The setups steps expect following tools installed on the system. -Things you may want to cover: - -* Ruby version - -* System dependencies - -* Configuration - -* Database creation - -* Database initialization - -* How to run the test suite - -* Services (job queues, cache servers, search engines, etc.) - -* Deployment instructions - -* ... +- Github +- Ruby [3.2.2](https://github.com/organization/project-name/blob/master/.ruby-version#L1) +- Rails [7.0.5](https://github.com/organization/project-name/blob/master/Gemfile#L12) From 971b84264ac257e169dca962c2ac374ecaa4bab0 Mon Sep 17 00:00:00 2001 From: Denys Date: Mon, 10 Jul 2023 11:08:22 +0300 Subject: [PATCH 3/3] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 56fb14d..b37b508 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,9 @@ The setups steps expect following tools installed on the system. - Github - Ruby [3.2.2](https://github.com/organization/project-name/blob/master/.ruby-version#L1) - Rails [7.0.5](https://github.com/organization/project-name/blob/master/Gemfile#L12) + +##### 1. Check out the repository + +```bash +git clone git@github.com:organization/project-name.git +```