From 7072612d9087bf3ab7fd0d1a2f93c126e2159eb0 Mon Sep 17 00:00:00 2001 From: p31729568 Date: Tue, 23 Jul 2019 15:38:32 +0800 Subject: [PATCH] library feature api --- app/controllers/libraries_controller.rb | 87 +++++++++++++++++++ app/decorators/library_decorator.rb | 5 ++ app/forms/libraries/save_form.rb | 12 +++ app/libs/util/uuid.rb | 9 ++ app/models/library.rb | 43 +++++++++ app/models/library_apply.rb | 19 ++++ app/models/library_library_tag.rb | 4 + app/models/library_tag.rb | 6 ++ app/services/libraries/agree_apply_service.rb | 32 +++++++ .../libraries/refuse_apply_service.rb | 39 +++++++++ app/services/libraries/save_service.rb | 69 +++++++++++++++ app/services/libraries/submit_service.rb | 32 +++++++ app/views/libraries/index.json.jbuilder | 16 ++++ app/views/libraries/show.json.jbuilder | 46 ++++++++++ .../locales/forms/save_library_form.zh-CN.yml | 12 +++ config/routes.rb | 2 + 16 files changed, 433 insertions(+) create mode 100644 app/controllers/libraries_controller.rb create mode 100644 app/decorators/library_decorator.rb create mode 100644 app/forms/libraries/save_form.rb create mode 100644 app/libs/util/uuid.rb create mode 100644 app/models/library.rb create mode 100644 app/models/library_apply.rb create mode 100644 app/models/library_library_tag.rb create mode 100644 app/models/library_tag.rb create mode 100644 app/services/libraries/agree_apply_service.rb create mode 100644 app/services/libraries/refuse_apply_service.rb create mode 100644 app/services/libraries/save_service.rb create mode 100644 app/services/libraries/submit_service.rb create mode 100644 app/views/libraries/index.json.jbuilder create mode 100644 app/views/libraries/show.json.jbuilder create mode 100644 config/locales/forms/save_library_form.zh-CN.yml diff --git a/app/controllers/libraries_controller.rb b/app/controllers/libraries_controller.rb new file mode 100644 index 000000000..55894aaa6 --- /dev/null +++ b/app/controllers/libraries_controller.rb @@ -0,0 +1,87 @@ +class LibrariesController < ApplicationController + include PaginateHelper + + before_action :require_login, :check_auth, except: %i[index show] + + helper_method :current_library, :library_manageable? + + def index + libraries = Library.all + + libraries = + if User.current&.logged? && params[:type] == 'mine' + libraries.where(user_id: current_user.id).order(created_at: :desc) + else + libraries.where(status: :published).order(visited_count: :desc) + end + + keyword = params[:keyword].to_s.strip + if keyword.present? + libraries = libraries.where('title LIKE :keyword OR author_name LIKE :keyword OR author_school_name LIKE :keyword', + keyword: "%#{keyword}%") + end + + @count = libraries.count + @libraries = paginate libraries.includes(:library_tags, :praise_tread_cache, user: :user_extension) + + ids = @libraries.map(&:id) + @download_count_map = Attachment.where(container_type: 'Library', container_id: ids) + .group(:container_id).sum(:downloads) + end + + def show + unless current_library.published? || library_manageable?(current_library) + return render_forbidden + end + end + + def create + library = current_user.libraries.new + Libraries::SaveService.call(library, current_user, save_params) + render_ok + rescue Libraries::SaveService::Error => ex + render_error(ex.message) + end + + def update + return render_forbidden unless library_manageable?(current_library) + + Libraries::SaveService.call(current_library, current_user, save_params) + render_ok + rescue Libraries::SaveService::Error => ex + render_error(ex.message) + end + + def destroy + if admin_or_business? + current_library.destroy! + elsif current_library.user_id == current_user&.id + unless current_library.pending? + render_error('只有草稿才能删除') + return + end + + current_library.destroy! + else + render_forbidden + return + end + + render_ok + end + + private + + def current_library + @_current_library ||= Library.find(params[:id]) + end + + def library_manageable?(library) + current_user&.id == library.user_id || admin_or_business? + end + + def save_params + params.permit(:title, :content, :author_name, :author_school_name, + :cover_id, :publish, attachment_ids: [], tag_ids: []) + end +end \ No newline at end of file diff --git a/app/decorators/library_decorator.rb b/app/decorators/library_decorator.rb new file mode 100644 index 000000000..2768ad036 --- /dev/null +++ b/app/decorators/library_decorator.rb @@ -0,0 +1,5 @@ +module LibraryDecorator + extend ApplicationDecorator + + display_time_method :published_at, :created_at, :updated_at +end \ No newline at end of file diff --git a/app/forms/libraries/save_form.rb b/app/forms/libraries/save_form.rb new file mode 100644 index 000000000..89e00cb20 --- /dev/null +++ b/app/forms/libraries/save_form.rb @@ -0,0 +1,12 @@ +class Libraries::SaveForm + include ActiveModel::Model + + attr_accessor :title, :content, :author_name, :author_school_name, :cover_id, + :publish, :attachment_ids, :tag_ids + + validates :title, presence: true, length: { maximum: 255 } + validates :content, presence: true + validates :author_name, presence: true, length: { maximum: 10 } + validates :author_school_name, presence: true, length: { maximum: 50 } + validates :attachment_ids, presence: true +end \ No newline at end of file diff --git a/app/libs/util/uuid.rb b/app/libs/util/uuid.rb new file mode 100644 index 000000000..2f6757139 --- /dev/null +++ b/app/libs/util/uuid.rb @@ -0,0 +1,9 @@ +module Util + module UUID + module_function + + def time_uuid(format: '%Y%m%d%H%M%S', suffix: 8) + "#{Time.zone.now.strftime(format)}#{Random.rand(10**suffix).to_i}" + end + end +end diff --git a/app/models/library.rb b/app/models/library.rb new file mode 100644 index 000000000..894dcdac0 --- /dev/null +++ b/app/models/library.rb @@ -0,0 +1,43 @@ +class Library < ApplicationRecord + include AASM + + belongs_to :user + belongs_to :cover, class_name: 'Attachment', foreign_key: :cover_id, optional: true + + has_many :library_applies, dependent: :delete_all + has_many :library_library_tags, dependent: :delete_all + has_many :library_tags, through: :library_library_tags + + has_many :attachments, as: :container + has_one :praise_tread_cache, foreign_key: :object_id + + validates :uuid, presence: true, uniqueness: true + + aasm(:status) do + state :pending, initiali: true + state :processing + state :refused + state :published + + event :submit do + transitions from: [:pending, :refused], to: :processing + end + + event :refuse do + transitions from: :processing, to: :refused + end + + event :publish do + transitions from: :processing, to: :published + end + end + + def generate_uuid + uuid = Util::UUID.time_uuid + while Library.exists?(uuid: uuid) + uuid = Util::UUID.time_uuid + end + + self.uuid = uuid + end +end \ No newline at end of file diff --git a/app/models/library_apply.rb b/app/models/library_apply.rb new file mode 100644 index 000000000..13e195f34 --- /dev/null +++ b/app/models/library_apply.rb @@ -0,0 +1,19 @@ +class LibraryApply < ApplicationRecord + include AASM + + belongs_to :library + + aasm(:status) do + state :pending, initiali: true + state :refused + state :agreed + + event :refuse do + transitions from: :pending, to: :refused + end + + event :agree do + transitions from: :pending, to: :agreed + end + end +end \ No newline at end of file diff --git a/app/models/library_library_tag.rb b/app/models/library_library_tag.rb new file mode 100644 index 000000000..9527289b0 --- /dev/null +++ b/app/models/library_library_tag.rb @@ -0,0 +1,4 @@ +class LibraryLibraryTag < ApplicationRecord + belongs_to :library + belongs_to :library_tag +end diff --git a/app/models/library_tag.rb b/app/models/library_tag.rb new file mode 100644 index 000000000..79ff4672f --- /dev/null +++ b/app/models/library_tag.rb @@ -0,0 +1,6 @@ +class LibraryTag < ApplicationRecord + has_many :library_library_tags, dependent: :delete_all + has_many :libraries, through: :library_library_tags + + validates :name, presence: true, uniqueness: true +end diff --git a/app/services/libraries/agree_apply_service.rb b/app/services/libraries/agree_apply_service.rb new file mode 100644 index 000000000..d452083b3 --- /dev/null +++ b/app/services/libraries/agree_apply_service.rb @@ -0,0 +1,32 @@ +class Libraries::AgreeApplyService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :library_apply, :library, :user + + def initialize(library_apply, user) + @library_apply = library_apply + @library = library_apply.library + @user = user + end + + def call + raise Error, '该状态下不能进行此操作' unless library_apply.may_agree? + + ActiveRecord::Base.transaction do + library_apply.agree! + library_apply.library.publish! + + # 将消息改为已处理 + Tiding.where(container_id: library.id, container_type: 'Library', tiding_type: 'Apply', status: 0).update_all(status: 1) + notify_library_author! + end + end + + private + + def notify_library_author! + Tiding.create!(user_id: library.user_id, trigger_user_id: 1, + container_id: library.id, container_type: 'Library', + tiding_type: 'System', status: 1) + end +end \ No newline at end of file diff --git a/app/services/libraries/refuse_apply_service.rb b/app/services/libraries/refuse_apply_service.rb new file mode 100644 index 000000000..9b249b6fa --- /dev/null +++ b/app/services/libraries/refuse_apply_service.rb @@ -0,0 +1,39 @@ +class Libraries::RefuseApplyService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :library_apply, :library, :user, :params + + def initialize(library_apply, user, params) + @library_apply = library_apply + @library = library_apply.library + @user = user + @params = params + end + + def call + reason = params[:reason].to_s.strip + raise Error, '原因不能为空' if reason.blank? + raise Error, '该状态下不能进行此操作' unless library_apply.may_refuse? + + ActiveRecord::Base.transaction do + library_apply.reason = reason + library_apply.refused_at = Time.current + library_apply.refuse + library_apply.save! + + library.refuse! + + # 将消息改为已处理 + Tiding.where(container_id: library.id, container_type: 'Library', tiding_type: 'Apply', status: 0).update_all(status: 1) + notify_library_author! + end + end + + private + + def notify_library_author! + Tiding.create!(user_id: library.user_id, trigger_user_id: 1, + container_id: library.id, container_type: 'Library', + tiding_type: 'System', status: 2, extra: library_apply.reason) + end +end \ No newline at end of file diff --git a/app/services/libraries/save_service.rb b/app/services/libraries/save_service.rb new file mode 100644 index 000000000..45cd033dd --- /dev/null +++ b/app/services/libraries/save_service.rb @@ -0,0 +1,69 @@ +class Libraries::SaveService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :library, :user, :params + + def initialize(library, user, params) + @library = library + @user = user + @params = params + end + + def call + Libraries::SaveForm.new(params).validate! + + if library.new_record? + library.user_id = user.id + library.generate_uuid + end + + ActiveRecord::Base.transaction do + library.assign_attributes(library_params) + library.save! + + deal_library_tag! + deal_attachments! + + Libraries::SubmitService.call(library) if with_publish? + end + + library + rescue Libraries::SubmitService::Error => ex + raise Error, ex.message + end + + private + + def deal_library_tag! + new_tag_ids = LibraryTag.where(id: Array.wrap(params[:tag_ids]).compact).pluck(:id) + old_tag_ids = library.library_library_tags.pluck(:library_tag_id) + + # 删除标签 + destroy_ids = old_tag_ids - new_tag_ids + library.library_library_tags.where(library_tag_id: destroy_ids).delete_all + + # 创建标签 + created_ids = new_tag_ids - old_tag_ids + created_ids.each do |id| + library.library_library_tags.create!(library_tag_id: id) + end + end + + def deal_attachments! + attachment_ids = Array.wrap(params[:attachment_ids]).compact.map(&:to_i) + old_attachment_id = library.attachments.pluck(:id) + + destroy_ids = old_attachment_id - attachment_ids + library.attachments.where(id: destroy_ids).delete_all + + Attachment.where(id: attachment_ids, author_id: user.id).update_all(container: library) + end + + def library_params + params.slice(*%i[title content author_name author_school_name cover_id]) + end + + def with_publish? + params[:publish].to_s == 'true' + end +end diff --git a/app/services/libraries/submit_service.rb b/app/services/libraries/submit_service.rb new file mode 100644 index 000000000..ab7af2910 --- /dev/null +++ b/app/services/libraries/submit_service.rb @@ -0,0 +1,32 @@ +class Libraries::SubmitService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :library + + def initialize(library) + @library = library + end + + def call + return if library.processing? || library.published? + + raise Error, '该状态下不能提交审核' unless library.may_submit? + + ActiveRecord::Base.transaction do + library.published_at = Time.current + library.submit + library.save! + + library.library_applies.create! + send_library_apply_notify! + end + end + + private + + def send_library_apply_notify! + Tiding.create!(user_id: 1, trigger_user_id: library.user_id, + container_id: library.id, container_type: 'Library', + tiding_type: 'Apply', status: 0) + end +end \ No newline at end of file diff --git a/app/views/libraries/index.json.jbuilder b/app/views/libraries/index.json.jbuilder new file mode 100644 index 000000000..c58d390b0 --- /dev/null +++ b/app/views/libraries/index.json.jbuilder @@ -0,0 +1,16 @@ +json.count @count +json.libraries do + json.array! @libraries.each do |library| + json.extract! library, :id, :title, :content, :author_name, :author_school_name, :status, :visited_count + + json.cover_url library.cover_id.present? ? download_url(library.cover) : nil + + json.praise_count library.praise_tread_cache&.praise_num || 0 + json.download_count @download_count_map.fetch(library.id, 0) + + json.published_at library.display_published_at + json.created_at library.display_created_at + + json.tags library.library_tags.map(&:name) + end +end \ No newline at end of file diff --git a/app/views/libraries/show.json.jbuilder b/app/views/libraries/show.json.jbuilder new file mode 100644 index 000000000..0f4b6ea17 --- /dev/null +++ b/app/views/libraries/show.json.jbuilder @@ -0,0 +1,46 @@ +library = current_library + +json.extract! library, :id, :uuid, :title, :content, :author_name, :author_school_name, :status, :visited_count + +json.praise_count library.praise_tread_cache&.praise_num || 0 + +json.published_at library.display_published_at +json.created_at library.display_created_at + +# 创建者 +json.creator do + json.partial! 'users/user_simple', user: library.user +end + +# 封面 +if library.cover_id.present? + json.cover do + json.partial! 'attachments/attachment_simple', attachment: library.cover + end +else + json.cover nil +end + +json.attachments library.attachments, partial: 'attachments/attachment_small', as: :attachment + +# 标签 +json.tags do + json.array! library.library_tags.each do |tag| + json.extract! tag, :id, :name + end +end + +# 操作权限 +json.operation do + if current_user&.logged? + manageable = library_manageable?(library) + + json.can_deletable manageable + json.can_editable manageable + json.user_praised PraiseTread.exists?(user_id: current_user&.id) + else + json.can_deletable false + json.can_editable false + json.user_praised false + end +end diff --git a/config/locales/forms/save_library_form.zh-CN.yml b/config/locales/forms/save_library_form.zh-CN.yml new file mode 100644 index 000000000..e28fa2a55 --- /dev/null +++ b/config/locales/forms/save_library_form.zh-CN.yml @@ -0,0 +1,12 @@ +'zh-CN': + activemodel: + attributes: + libraries/save_form: + title: 标题 + content: 描述 + author_name: 作者名称 + author_school_name: 作者单位 + cover_id: 封面 + publish: '' + attachment_ids: 附件 + tag_ids: 标签 diff --git a/config/routes.rb b/config/routes.rb index 9da4d4a7e..16140e99c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -690,6 +690,8 @@ Rails.application.routes.draw do post :win, on: :collection end end + + resources :libraries, only: [:index, :show, :create, :update, :destroy] end #git 认证回调