diff --git a/Gemfile b/Gemfile index 562c5cc..d7610af 100644 --- a/Gemfile +++ b/Gemfile @@ -26,14 +26,15 @@ gem "boolean_timestamp", "~> 1.1" gem "jsonb_accessor", "~> 1.3" gem "oj_serializers", "~> 2.0" gem "types_from_serializers", "~> 2.1" +gem "public_activity", "~> 3.0" # Helpers gem "factory_bot", ">= 6.2" gem "js-routes", "~> 2.2" gem "foreman", "~> 0.87.2" gem "amazing_print", "~> 1.4" -# gem "ruby-osc", "~> 1.0" gem "eventmachine", "~> 1.2" +# gem "ruby-osc", "~> 1.0" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data" diff --git a/Gemfile.lock b/Gemfile.lock index 5c68b16..fedd14e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -240,6 +240,11 @@ GEM pry (>= 0.10.4) psych (5.1.2) stringio + public_activity (3.0.1) + actionpack (>= 6.1.0) + activerecord (>= 6.1) + i18n (>= 0.5.0) + railties (>= 6.1.0) public_suffix (5.0.4) puma (6.4.2) nio4r (~> 2.0) @@ -444,6 +449,7 @@ DEPENDENCIES pg (~> 1.1) pg_search (~> 2.3) pry-rails (~> 0.3.9) + public_activity (~> 3.0) puma (~> 6.0) pundit (~> 2.3) pundit-matchers (~> 3.1) diff --git a/app/controllers/api/protocols_controller.rb b/app/controllers/api/protocols_controller.rb index 8412545..d128e2a 100644 --- a/app/controllers/api/protocols_controller.rb +++ b/app/controllers/api/protocols_controller.rb @@ -3,7 +3,10 @@ class Api::ProtocolsController < ApplicationController # @route PUT /api/protocol/:id/execute (api_execute_protocol) def execute - render json: protocol, status: :accepted + protocol.create_activity key: :slug, owner: current_user + SendOscCommandsJob.perform_later(protocol) + + render json: protocol, status: :accepted end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f298ec8..a0112c8 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,6 @@ class ApplicationController < ActionController::Base include Pundit::Authorization + include PublicActivity::StoreController protect_from_forgery with: :exception @@ -22,8 +23,7 @@ class ApplicationController < ActionController::Base } if current_user - share_object[:menu] = { - } + share_object[:menu] = {} end share_object diff --git a/app/frontend/Components/Table/Table.tsx b/app/frontend/Components/Table/Table.tsx index 8f863e9..61e4817 100644 --- a/app/frontend/Components/Table/Table.tsx +++ b/app/frontend/Components/Table/Table.tsx @@ -47,7 +47,7 @@ const TableComponent: TableComponent & TableObjects = ({ className, wrapper = true, fixed = false, - rowSpacing = true, + rowSpacing = false, striped = true, highlightOnHover = true, ...props diff --git a/app/frontend/Pages/Servers/Index/index.tsx b/app/frontend/Pages/Servers/Index/index.tsx index 62fe92e..752de79 100644 --- a/app/frontend/Pages/Servers/Index/index.tsx +++ b/app/frontend/Pages/Servers/Index/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Routes } from '@/lib' -import { IndexPageTemplate } from '@/Layouts/AppLayout/Components' +import { IndexPageTemplate } from '@/Features' import { NewIcon } from '@/Components/Icons' import ServersTable from '../Table' diff --git a/app/frontend/types/routes.d.ts b/app/frontend/types/routes.d.ts index 848b8e9..3be452c 100644 --- a/app/frontend/types/routes.d.ts +++ b/app/frontend/types/routes.d.ts @@ -106,6 +106,18 @@ export const apiSpotlights: (( options?: {format?: OptionalRouteParameter} & RouteOptions ) => string) & RouteHelperExtras; +/** + * Generates rails route to + * /api/users/:id/update_table_preferences(.:format) + * @param {any} id + * @param {object | undefined} options + * @returns {string} route path + */ +export const apiUpdateTablePreferences: (( + id: RequiredRouteParameter, + options?: {format?: OptionalRouteParameter} & RouteOptions +) => string) & RouteHelperExtras; + /** * Generates rails route to * /api/users/:id/update_user_preferences(.:format) diff --git a/app/frontend/types/routes.js b/app/frontend/types/routes.js index 9eea773..026ae04 100644 --- a/app/frontend/types/routes.js +++ b/app/frontend/types/routes.js @@ -550,6 +550,15 @@ export const apiExecuteProtocol = /*#__PURE__*/ __jsr.r({"id":{"r":true},"format */ export const apiSpotlights = /*#__PURE__*/ __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"api"],[2,[7,"/"],[2,[6,"spotlights"],[1,[2,[8,"."],[3,"format"]]]]]]]); +/** + * Generates rails route to + * /api/users/:id/update_table_preferences(.:format) + * @param {any} id + * @param {object | undefined} options + * @returns {string} route path + */ +export const apiUpdateTablePreferences = /*#__PURE__*/ __jsr.r({"id":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"api"],[2,[7,"/"],[2,[6,"users"],[2,[7,"/"],[2,[3,"id"],[2,[7,"/"],[2,[6,"update_table_preferences"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]); + /** * Generates rails route to * /api/users/:id/update_user_preferences(.:format) diff --git a/app/jobs/send_osc_commands_job.rb b/app/jobs/send_osc_commands_job.rb index 150e0e1..d9111f0 100644 --- a/app/jobs/send_osc_commands_job.rb +++ b/app/jobs/send_osc_commands_job.rb @@ -11,5 +11,7 @@ def perform(protocol) client.send(OscService::Message.new(command.message)) end end + rescue Errno::ECONNREFUSED + server.create_activity key: :hostname end end diff --git a/app/models/command.rb b/app/models/command.rb index e06f4bf..6e3e2da 100644 --- a/app/models/command.rb +++ b/app/models/command.rb @@ -27,6 +27,7 @@ # class Command < ApplicationRecord include PgSearch::Model + include PublicActivity::Model pg_search_scope( :search, @@ -37,6 +38,7 @@ class Command < ApplicationRecord }, ) + tracked owner: proc { |controller| controller&.current_user } resourcify enum :payload_type, { integer: 0, float: 1, string: 2, blob: 3, time: 4, symbol: 5, character: 6, boolean: 7 } diff --git a/app/models/protocol.rb b/app/models/protocol.rb index 6f9785a..0519da0 100644 --- a/app/models/protocol.rb +++ b/app/models/protocol.rb @@ -15,6 +15,7 @@ # class Protocol < ApplicationRecord include PgSearch::Model + include PublicActivity::Model pg_search_scope( :search, @@ -25,6 +26,7 @@ class Protocol < ApplicationRecord }, ) + tracked owner: proc { |controller| controller&.current_user } resourcify slug :title diff --git a/app/models/server.rb b/app/models/server.rb index 5c74876..05a1406 100644 --- a/app/models/server.rb +++ b/app/models/server.rb @@ -12,6 +12,7 @@ # class Server < ApplicationRecord include PgSearch::Model + include PublicActivity::Model pg_search_scope( :search, @@ -22,6 +23,7 @@ class Server < ApplicationRecord }, ) + tracked owner: proc { |controller| controller&.current_user } resourcify has_many :commands, dependent: :nullify diff --git a/app/models/user.rb b/app/models/user.rb index 43f4d9d..b551a71 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,6 +26,7 @@ # reset_password_sent_at :datetime # reset_password_token :string # sign_in_count :integer default(0), not null +# table_preferences :jsonb # unconfirmed_email :string # unlock_token :string # user_preferences :jsonb @@ -41,10 +42,14 @@ # index_users_on_invited_by (invited_by_type,invited_by_id) # index_users_on_invited_by_id (invited_by_id) # index_users_on_reset_password_token (reset_password_token) UNIQUE +# index_users_on_table_preferences (table_preferences) USING gin # index_users_on_unlock_token (unlock_token) UNIQUE # index_users_on_user_preferences (user_preferences) USING gin # class User < ApplicationRecord + include PublicActivity::Model + + tracked owner: proc { |controller| controller&.current_user } resourcify rolify diff --git a/app/policies/server_policy.rb b/app/policies/server_policy.rb new file mode 100644 index 0000000..1b6e7a1 --- /dev/null +++ b/app/policies/server_policy.rb @@ -0,0 +1,8 @@ +class ServerPolicy < ApplicationPolicy + class Scope < Scope + # NOTE: Be explicit about which records you allow access to! + # def resolve + # scope.all + # end + end +end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index af247c3..b2b94c0 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -26,6 +26,7 @@ # reset_password_sent_at :datetime # reset_password_token :string # sign_in_count :integer default(0), not null +# table_preferences :jsonb # unconfirmed_email :string # unlock_token :string # user_preferences :jsonb @@ -41,6 +42,7 @@ # index_users_on_invited_by (invited_by_type,invited_by_id) # index_users_on_invited_by_id (invited_by_id) # index_users_on_reset_password_token (reset_password_token) UNIQUE +# index_users_on_table_preferences (table_preferences) USING gin # index_users_on_unlock_token (unlock_token) UNIQUE # index_users_on_user_preferences (user_preferences) USING gin # diff --git a/config/routes/api.rb b/config/routes/api.rb index 76bd1eb..599addf 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -1,6 +1,7 @@ namespace :api do resources :users, only: [:create, :update] patch "users/:id/update_user_preferences" => "users#update_user_preferences", as: :update_user_preferences + patch "users/:id/update_table_preferences" => "users#update_table_preferences", as: :update_table_preferences resources :spotlights, only: [:index] diff --git a/db/migrate/20230521142961_add_fields_to_users.rb b/db/migrate/20230521142961_add_fields_to_users.rb index 2e11057..e532c99 100644 --- a/db/migrate/20230521142961_add_fields_to_users.rb +++ b/db/migrate/20230521142961_add_fields_to_users.rb @@ -3,6 +3,9 @@ def change change_table :users, bulk: true do |t| t.boolean :active, default: true, null: false + t.jsonb :table_preferences, default: {} + t.index :table_preferences, using: :gin + t.jsonb :user_preferences, default: {} t.index :user_preferences, using: :gin end diff --git a/db/migrate/20240331175622_create_activities.rb b/db/migrate/20240331175622_create_activities.rb new file mode 100644 index 0000000..eb12906 --- /dev/null +++ b/db/migrate/20240331175622_create_activities.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Migration responsible for creating a table with activities +class CreateActivities < ActiveRecord::Migration[6.1] + def self.up + create_table :activities do |t| + t.belongs_to :trackable, polymorphic: true + t.belongs_to :owner, polymorphic: true + t.string :key + t.text :parameters + t.belongs_to :recipient, polymorphic: true + + t.timestamps + end + + add_index :activities, %i[trackable_id trackable_type] + add_index :activities, %i[owner_id owner_type] + add_index :activities, %i[recipient_id recipient_type] + end + + # Drop table + def self.down + drop_table :activities + end +end diff --git a/db/schema.rb b/db/schema.rb index 57fa2d5..b3b72c8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,29 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_02_29_095137) do +ActiveRecord::Schema[7.1].define(version: 2024_03_31_175622) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "activities", force: :cascade do |t| + t.string "trackable_type" + t.bigint "trackable_id" + t.string "owner_type" + t.bigint "owner_id" + t.string "key" + t.text "parameters" + t.string "recipient_type" + t.bigint "recipient_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["owner_id", "owner_type"], name: "index_activities_on_owner_id_and_owner_type" + t.index ["owner_type", "owner_id"], name: "index_activities_on_owner" + t.index ["recipient_id", "recipient_type"], name: "index_activities_on_recipient_id_and_recipient_type" + t.index ["recipient_type", "recipient_id"], name: "index_activities_on_recipient" + t.index ["trackable_id", "trackable_type"], name: "index_activities_on_trackable_id_and_trackable_type" + t.index ["trackable_type", "trackable_id"], name: "index_activities_on_trackable" + end + create_table "commands", force: :cascade do |t| t.string "title", null: false t.text "description" @@ -122,6 +141,7 @@ t.bigint "invited_by_id" t.integer "invitations_count", default: 0 t.boolean "active", default: true, null: false + t.jsonb "table_preferences", default: {} t.jsonb "user_preferences", default: {} t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true @@ -129,6 +149,7 @@ t.index ["invited_by_id"], name: "index_users_on_invited_by_id" t.index ["invited_by_type", "invited_by_id"], name: "index_users_on_invited_by" t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + t.index ["table_preferences"], name: "index_users_on_table_preferences", using: :gin t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true t.index ["user_preferences"], name: "index_users_on_user_preferences", using: :gin end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 4dd909b..bcf578c 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -26,6 +26,7 @@ # reset_password_sent_at :datetime # reset_password_token :string # sign_in_count :integer default(0), not null +# table_preferences :jsonb # unconfirmed_email :string # unlock_token :string # user_preferences :jsonb @@ -41,6 +42,7 @@ # index_users_on_invited_by (invited_by_type,invited_by_id) # index_users_on_invited_by_id (invited_by_id) # index_users_on_reset_password_token (reset_password_token) UNIQUE +# index_users_on_table_preferences (table_preferences) USING gin # index_users_on_unlock_token (unlock_token) UNIQUE # index_users_on_user_preferences (user_preferences) USING gin # diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 97e7ab1..88325d4 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -26,6 +26,7 @@ # reset_password_sent_at :datetime # reset_password_token :string # sign_in_count :integer default(0), not null +# table_preferences :jsonb # unconfirmed_email :string # unlock_token :string # user_preferences :jsonb @@ -41,6 +42,7 @@ # index_users_on_invited_by (invited_by_type,invited_by_id) # index_users_on_invited_by_id (invited_by_id) # index_users_on_reset_password_token (reset_password_token) UNIQUE +# index_users_on_table_preferences (table_preferences) USING gin # index_users_on_unlock_token (unlock_token) UNIQUE # index_users_on_user_preferences (user_preferences) USING gin # diff --git a/spec/policies/server_policy_spec.rb b/spec/policies/server_policy_spec.rb new file mode 100644 index 0000000..9e76217 --- /dev/null +++ b/spec/policies/server_policy_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +RSpec.describe ServerPolicy, type: :policy do + let(:user) { User.new } + + subject { described_class } + + permissions ".scope" do + pending "add some examples to (or delete) #{__FILE__}" + end + + permissions :show? do + pending "add some examples to (or delete) #{__FILE__}" + end + + permissions :create? do + pending "add some examples to (or delete) #{__FILE__}" + end + + permissions :update? do + pending "add some examples to (or delete) #{__FILE__}" + end + + permissions :destroy? do + pending "add some examples to (or delete) #{__FILE__}" + end +end diff --git a/spec/requests/protocols_spec.rb b/spec/requests/protocols_spec.rb index c340c63..79bd656 100644 --- a/spec/requests/protocols_spec.rb +++ b/spec/requests/protocols_spec.rb @@ -13,7 +13,7 @@ # sticking to rails and rspec-rails APIs to keep things simple and stable. RSpec.describe "/protocols", type: :request do - + # This should return the minimal set of attributes required to create a valid # Protocol. As you add validations to Protocol, be sure to # adjust the attributes here as well. @@ -74,15 +74,14 @@ it "does not create a new Protocol" do expect { post protocols_url, params: { protocol: invalid_attributes } - }.to change(Protocol, :count).by(0) + }.not_to change(Protocol, :count) end - it "renders a response with 422 status (i.e. to display the 'new' template)" do post protocols_url, params: { protocol: invalid_attributes } expect(response).to have_http_status(:unprocessable_entity) end - + end end @@ -108,13 +107,13 @@ end context "with invalid parameters" do - + it "renders a response with 422 status (i.e. to display the 'edit' template)" do protocol = Protocol.create! valid_attributes patch protocol_url(protocol), params: { protocol: invalid_attributes } expect(response).to have_http_status(:unprocessable_entity) end - + end end diff --git a/spec/requests/screens_spec.rb b/spec/requests/screens_spec.rb index 409788d..aff32ee 100644 --- a/spec/requests/screens_spec.rb +++ b/spec/requests/screens_spec.rb @@ -13,7 +13,7 @@ # sticking to rails and rspec-rails APIs to keep things simple and stable. RSpec.describe "/screens", type: :request do - + # This should return the minimal set of attributes required to create a valid # Screen. As you add validations to Screen, be sure to # adjust the attributes here as well. @@ -41,13 +41,6 @@ end end - describe "GET /new" do - it "renders a successful response" do - get new_screen_url - expect(response).to be_successful - end - end - describe "GET /edit" do it "renders a successful response" do screen = Screen.create! valid_attributes @@ -74,15 +67,14 @@ it "does not create a new Screen" do expect { post screens_url, params: { screen: invalid_attributes } - }.to change(Screen, :count).by(0) + }.not_to change(Screen, :count) end - it "renders a response with 422 status (i.e. to display the 'new' template)" do post screens_url, params: { screen: invalid_attributes } expect(response).to have_http_status(:unprocessable_entity) end - + end end @@ -108,13 +100,13 @@ end context "with invalid parameters" do - + it "renders a response with 422 status (i.e. to display the 'edit' template)" do screen = Screen.create! valid_attributes patch screen_url(screen), params: { screen: invalid_attributes } expect(response).to have_http_status(:unprocessable_entity) end - + end end