Merge pull request #7694 from rekonder/aj_SCI_10465

Add local storage backend [SCI-10465]
This commit is contained in:
ajugo 2024-07-11 10:03:48 +02:00 committed by GitHub
commit 97d2513a04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 377 additions and 4 deletions

View file

@ -0,0 +1,89 @@
# frozen_string_literal: true
class StorageLocationRepositoryRowsController < ApplicationController
before_action :load_storage_location_repository_row, only: %i(update destroy)
before_action :load_storage_location
before_action :load_repository_row
before_action :check_read_permissions, only: :index
before_action :check_manage_permissions, except: :index
def index
storage_location_repository_row = Lists::StorageLocationRepositoryRowsService.new(
current_team, storage_location_repository_row_params
).call
render json: storage_location_repository_row,
each_serializer: Lists::StorageLocationRepositoryRowSerializer,
include: %i(repository_row)
end
def update
@storage_location_repository_row.update(storage_location_repository_row_params)
if @storage_location_repository_row.save
render json: @storage_location_repository_row,
serializer: Lists::StorageLocationRepositoryRowSerializer,
include: :repository_row
else
render json: @storage_location_repository_row.errors, status: :unprocessable_entity
end
end
def create
@storage_location_repository_row = StorageLocationRepositoryRow.new(
repository_row: @repository_row,
storage_location: @storage_location,
metadata: storage_location_repository_row_params[:metadata] || {},
created_by: current_user
)
if @storage_location_repository_row.save
render json: @storage_location_repository_row,
serializer: Lists::StorageLocationRepositoryRowSerializer,
include: :repository_row
else
render json: @storage_location_repository_row.errors, status: :unprocessable_entity
end
end
def destroy
if @storage_location_repository_row.discard
render json: {}
else
render json: { errors: @storage_location_repository_row.errors.full_messages }, status: :unprocessable_entity
end
end
private
def load_storage_location_repository_row
@storage_location_repository_row = StorageLocationRepositoryRow.find(
storage_location_repository_row_params[:id]
)
render_404 unless @storage_location_repository_row
end
def load_storage_location
@storage_location = StorageLocation.where(team: current_team).find(
storage_location_repository_row_params[:storage_location_id]
)
render_404 unless @storage_location
end
def load_repository_row
@repository_row = RepositoryRow.find(storage_location_repository_row_params[:repository_row_id])
render_404 unless @repository_row
end
def storage_location_repository_row_params
params.permit(:id, :storage_location_id, :repository_row_id,
metadata: { position: [] })
end
def check_read_permissions
render_403 unless true
end
def check_manage_permissions
render_403 unless true
end
end

View file

@ -0,0 +1,65 @@
# frozen_string_literal: true
class StorageLocationsController < ApplicationController
before_action :load_storage_location, only: %i(update destroy)
before_action :check_read_permissions, only: :index
before_action :check_manage_permissions, except: :index
def index
storage_locations = Lists::StorageLocationsService.new(current_team, storage_location_params).call
render json: storage_locations, each_serializer: Lists::StorageLocationSerializer
end
def update
@storage_location.image.attach(storage_location_params[:signed_blob_id]) if storage_location_params[:signed_blob_id]
@storage_location.update(storage_location_params)
if @storage_location.save
render json: @storage_location, serializer: Lists::StorageLocationSerializer
else
render json: @storage_location.errors, status: :unprocessable_entity
end
end
def create
@storage_location = StorageLocation.new(
storage_location_params.merge({ team: current_team, created_by: current_user })
)
@storage_location.image.attach(storage_location_params[:signed_blob_id]) if storage_location_params[:signed_blob_id]
if @storage_location.save
render json: @storage_location, serializer: Lists::StorageLocationSerializer
else
render json: @storage_location.errors, status: :unprocessable_entity
end
end
def destroy
if @storage_location.discard
render json: {}
else
render json: { errors: @storage_location.errors.full_messages }, status: :unprocessable_entity
end
end
private
def storage_location_params
params.permit(:id, :parent_id, :name, :container, :signed_blob_id, :description,
metadata: { dimensions: [], parent_coordinations: [], display_type: :string })
end
def load_storage_location
@storage_location = StorageLocation.where(team: current_team).find(storage_location_params[:id])
render_404 unless @storage_location
end
def check_read_permissions
render_403 unless true
end
def check_manage_permissions
render_403 unless true
end
end

View file

@ -98,6 +98,8 @@ class RepositoryRow < ApplicationRecord
class_name: 'RepositoryRow',
source: :parent,
dependent: :destroy
has_many :storage_location_repository_rows, inverse_of: :repository_row, dependent: :destroy
has_many :storage_locations, through: :storage_location_repository_rows
auto_strip_attributes :name, nullify: false
validates :name,

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
class StorageLocation < ApplicationRecord
include Discard::Model
ID_PREFIX = 'SL'
include PrefixedIdModel
default_scope -> { kept }
has_one_attached :image
belongs_to :team
belongs_to :parent, class_name: 'StorageLocation', optional: true
belongs_to :created_by, class_name: 'User'
has_many :storage_location_repository_rows, inverse_of: :storage_location
has_many :repository_rows, through: :storage_location_repository_row
validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }
after_discard do
StorageLocation.where(parent_id: id).find_each(&:discard)
storage_location_repository_rows.each(&:discard)
end
end

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
class StorageLocationRepositoryRow < ApplicationRecord
include Discard::Model
default_scope -> { kept }
belongs_to :storage_location, inverse_of: :storage_location_repository_rows
belongs_to :repository_row, inverse_of: :storage_location_repository_rows
belongs_to :created_by, class_name: 'User'
with_options if: -> { storage_location.container && storage_location.metadata['type'] == 'grid' } do
validate :position_must_be_present
validate :ensure_uniq_position
end
def position_must_be_present
if metadata['position'].blank?
errors.add(:base, I18n.t('activerecord.errors.models.storage_location.missing_position'))
end
end
def ensure_uniq_position
if StorageLocationRepositoryRow.where(storage_location: storage_location)
.where('metadata @> ?', { position: metadata['position'] }.to_json)
.where.not(id: id).exists?
errors.add(:base, I18n.t('activerecord.errors.models.storage_location.not_uniq_position'))
end
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Lists
class StorageLocationRepositoryRowSerializer < ActiveModel::Serializer
attributes :created_by, :created_on, :position
belongs_to :repository_row, serializer: RepositoryRowSerializer
def created_by
object.created_by.full_name
end
def created_on
I18n.l(object.created_at, format: :full)
end
def position
object.metadata['position']
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Lists
class StorageLocationSerializer < ActiveModel::Serializer
attributes :id, :code, :name, :container, :description, :owned_by, :created_by, :created_on
def owned_by
object.team.name
end
def created_by
object.created_by.full_name
end
def created_on
I18n.l(object.created_at, format: :full)
end
end
end

View file

@ -29,7 +29,7 @@ module Lists
end
def paginate_records
@records = @records.page(@params[:page]).per(@params[:per_page])
@records = @records.page(@params[:page]).per(@params[:per_page]) if @params[:page].present?
end
def sort_direction(order_params)

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Lists
class StorageLocationRepositoryRowsService < BaseService
def initialize(team, params)
@team = team
@storage_location_id = params[:storage_location_id]
@params = params
end
def fetch_records
@records = StorageLocationRepositoryRow.includes(:repository_row).where(storage_location_id: @storage_location_id)
end
def filter_records; end
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Lists
class StorageLocationsService < BaseService
def initialize(team, params)
@team = team
@parent_id = params[:parent_id]
@params = params
end
def fetch_records
@records = StorageLocation.where(team: @team, parent_id: @parent_id)
end
def filter_records; end
end
end

View file

@ -259,6 +259,9 @@ en:
attributes:
text: Text is too long
position: "Position has already been taken by another item in the checklist"
storage_location:
missing_position: 'Missing position metadata'
not_uniq_position: 'Position already taken'
storage:
limit_reached: "Storage limit has been reached."
helpers:

View file

@ -807,6 +807,10 @@ Rails.application.routes.draw do
resources :connected_devices, controller: 'users/connected_devices', only: %i(destroy)
resources :storage_locations, only: %i(index create destroy update) do
resources :storage_location_repository_rows, only: %i(index create destroy update)
end
get 'search' => 'search#index'
get 'search/new' => 'search#new', as: :new_search
resource :search, only: [], controller: :search do

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
class AddStorageLocations < ActiveRecord::Migration[7.0]
include DatabaseHelper
def up
create_table :storage_locations do |t|
t.string :name
t.string :description
t.references :parent, index: true, foreign_key: { to_table: :storage_locations }
t.references :team, index: true, foreign_key: { to_table: :teams }
t.references :created_by, foreign_key: { to_table: :users }
t.boolean :container, default: false, null: false, index: true
t.jsonb :metadata, null: false, default: {}
t.datetime :discarded_at, index: true
t.timestamps
end
create_table :storage_location_repository_rows do |t|
t.references :repository_row, index: true, foreign_key: { to_table: :repository_rows }
t.references :storage_location, index: true, foreign_key: { to_table: :storage_locations }
t.references :created_by, foreign_key: { to_table: :users }
t.jsonb :metadata, null: false, default: {}
t.datetime :discarded_at, index: true
t.timestamps
end
add_gin_index_without_tags :storage_locations, :name
add_gin_index_without_tags :storage_locations, :description
end
def down
drop_table :storage_location_repository_rows
drop_table :storage_locations
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_06_26_113515) do
ActiveRecord::Schema[7.0].define(version: 2024_07_05_122903) do
# These are extensions that must be enabled in order to support this database
enable_extension "btree_gist"
enable_extension "pg_trgm"
@ -721,8 +721,8 @@ ActiveRecord::Schema[7.0].define(version: 2024_06_26_113515) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "type"
t.datetime "start_time_dup"
t.datetime "end_time_dup"
t.datetime "start_time_dup", precision: nil
t.datetime "end_time_dup", precision: nil
t.index "((end_time)::date)", name: "index_repository_date_time_range_values_on_end_time_as_date", where: "((type)::text = 'RepositoryDateRangeValue'::text)"
t.index "((end_time)::time without time zone)", name: "index_repository_date_time_range_values_on_end_time_as_time", where: "((type)::text = 'RepositoryTimeRangeValue'::text)"
t.index "((start_time)::date)", name: "index_repository_date_time_range_values_on_start_time_as_date", where: "((type)::text = 'RepositoryDateRangeValue'::text)"
@ -1083,6 +1083,40 @@ ActiveRecord::Schema[7.0].define(version: 2024_06_26_113515) do
t.index ["user_id"], name: "index_steps_on_user_id"
end
create_table "storage_location_repository_rows", force: :cascade do |t|
t.bigint "repository_row_id"
t.bigint "storage_location_id"
t.bigint "created_by_id"
t.jsonb "metadata", default: {}, null: false
t.datetime "discarded_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["created_by_id"], name: "index_storage_location_repository_rows_on_created_by_id"
t.index ["discarded_at"], name: "index_storage_location_repository_rows_on_discarded_at"
t.index ["repository_row_id"], name: "index_storage_location_repository_rows_on_repository_row_id"
t.index ["storage_location_id"], name: "index_storage_location_repository_rows_on_storage_location_id"
end
create_table "storage_locations", force: :cascade do |t|
t.string "name"
t.string "description"
t.bigint "parent_id"
t.bigint "team_id"
t.bigint "created_by_id"
t.boolean "container", default: false, null: false
t.jsonb "metadata", default: {}, null: false
t.datetime "discarded_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index "trim_html_tags((description)::text) gin_trgm_ops", name: "index_storage_locations_on_description", using: :gin
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_storage_locations_on_name", using: :gin
t.index ["container"], name: "index_storage_locations_on_container"
t.index ["created_by_id"], name: "index_storage_locations_on_created_by_id"
t.index ["discarded_at"], name: "index_storage_locations_on_discarded_at"
t.index ["parent_id"], name: "index_storage_locations_on_parent_id"
t.index ["team_id"], name: "index_storage_locations_on_team_id"
end
create_table "tables", force: :cascade do |t|
t.binary "contents", null: false
t.datetime "created_at", precision: nil, null: false
@ -1278,6 +1312,9 @@ ActiveRecord::Schema[7.0].define(version: 2024_06_26_113515) do
t.integer "failed_attempts", default: 0, null: false
t.datetime "locked_at", precision: nil
t.string "unlock_token"
t.string "api_key"
t.datetime "api_key_expires_at", precision: nil
t.datetime "api_key_created_at", precision: nil
t.index "trim_html_tags((full_name)::text) gin_trgm_ops", name: "index_users_on_full_name", using: :gin
t.index ["authentication_token"], name: "index_users_on_authentication_token", unique: true
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
@ -1502,6 +1539,12 @@ ActiveRecord::Schema[7.0].define(version: 2024_06_26_113515) do
add_foreign_key "steps", "protocols"
add_foreign_key "steps", "users"
add_foreign_key "steps", "users", column: "last_modified_by_id"
add_foreign_key "storage_location_repository_rows", "repository_rows"
add_foreign_key "storage_location_repository_rows", "storage_locations"
add_foreign_key "storage_location_repository_rows", "users", column: "created_by_id"
add_foreign_key "storage_locations", "storage_locations", column: "parent_id"
add_foreign_key "storage_locations", "teams"
add_foreign_key "storage_locations", "users", column: "created_by_id"
add_foreign_key "tables", "users", column: "created_by_id"
add_foreign_key "tables", "users", column: "last_modified_by_id"
add_foreign_key "tags", "projects"