ぼざクリタグ広場 https://hub.nizika.monster
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

288 lines
8.3 KiB

  1. class PostsController < ApplicationController
  2. Event = Struct.new(:post, :tag, :user, :change_type, :timestamp, keyword_init: true)
  3. def index
  4. url = params[:url].presence
  5. title = params[:title].presence
  6. original_created_from = params[:original_created_from].presence
  7. original_created_to = params[:original_created_to].presence
  8. created_between = params[:created_from].presence, params[:created_to].presence
  9. updated_between = params[:updated_from].presence, params[:updated_to].presence
  10. page = (params[:page].presence || 1).to_i
  11. limit = (params[:limit].presence || 20).to_i
  12. page = 1 if page < 1
  13. limit = 1 if limit < 1
  14. offset = (page - 1) * limit
  15. q =
  16. filtered_posts
  17. .preload(tags: { tag_name: :wiki_page })
  18. .with_attached_thumbnail
  19. q = q.where('posts.url LIKE ?', "%#{ url }%") if url
  20. q = q.where('posts.title LIKE ?', "%#{ title }%") if title
  21. if original_created_from
  22. q = q.where('posts.original_created_before > ?', original_created_from)
  23. end
  24. if original_created_to
  25. q = q.where('posts.original_created_from <= ?', original_created_to)
  26. end
  27. q = q.where('posts.created_at >= ?', created_between[0]) if created_between[0]
  28. q = q.where('posts.created_at <= ?', created_between[1]) if created_between[1]
  29. q = q.where('posts.updated_at >= ?', updated_between[0]) if updated_between[0]
  30. q = q.where('posts.updated_at <= ?', updated_between[1]) if updated_between[1]
  31. sort_sql =
  32. 'COALESCE(posts.original_created_before - INTERVAL 1 MINUTE,' +
  33. 'posts.original_created_from,' +
  34. 'posts.created_at)'
  35. posts = q.select("posts.*, #{ sort_sql } AS sort_ts")
  36. .order(Arel.sql("#{ sort_sql } DESC"))
  37. .limit(limit).offset(offset).to_a
  38. render json: { posts: posts.map { |post|
  39. PostRepr.base(post).tap do |json|
  40. json['thumbnail'] =
  41. if post.thumbnail.attached?
  42. rails_storage_proxy_url(post.thumbnail, only_path: false)
  43. else
  44. nil
  45. end
  46. end
  47. }, count: q.group_values.present? ? q.count.size : q.count }
  48. end
  49. def random
  50. post = filtered_posts.preload(tags: { tag_name: :wiki_page })
  51. .order('RAND()')
  52. .first
  53. return head :not_found unless post
  54. viewed = current_user&.viewed?(post) || false
  55. render json: PostRepr.base(post).merge(viewed:)
  56. end
  57. def show
  58. post = Post.includes(tags: { tag_name: :wiki_page }).find_by(id: params[:id])
  59. return head :not_found unless post
  60. viewed = current_user&.viewed?(post) || false
  61. json = post.as_json
  62. json['tags'] = build_tag_tree_for(post.tags)
  63. json['related'] = post.related(limit: 20)
  64. json['viewed'] = viewed
  65. render json:
  66. end
  67. def create
  68. return head :unauthorized unless current_user
  69. return head :forbidden unless current_user.member?
  70. # TODO: サイトに応じて thumbnail_base 設定
  71. title = params[:title].presence
  72. url = params[:url]
  73. thumbnail = params[:thumbnail]
  74. tag_names = params[:tags].to_s.split
  75. original_created_from = params[:original_created_from]
  76. original_created_before = params[:original_created_before]
  77. post = Post.new(title:, url:, thumbnail_base: nil, uploaded_user: current_user,
  78. original_created_from:, original_created_before:)
  79. post.thumbnail.attach(thumbnail)
  80. if post.save
  81. post.resized_thumbnail!
  82. tags = Tag.normalise_tags(tag_names)
  83. tags = Tag.expand_parent_tags(tags)
  84. sync_post_tags!(post, tags)
  85. post.reload
  86. render json: PostRepr.base(post), status: :created
  87. else
  88. render json: { errors: post.errors.full_messages }, status: :unprocessable_entity
  89. end
  90. rescue Tag::NicoTagNormalisationError
  91. head :bad_request
  92. end
  93. def viewed
  94. return head :unauthorized unless current_user
  95. current_user.viewed_posts << Post.find(params[:id])
  96. head :no_content
  97. end
  98. def unviewed
  99. return head :unauthorized unless current_user
  100. current_user.viewed_posts.delete(Post.find(params[:id]))
  101. head :no_content
  102. end
  103. def update
  104. return head :unauthorized unless current_user
  105. return head :forbidden unless current_user.member?
  106. title = params[:title].presence
  107. tag_names = params[:tags].to_s.split
  108. original_created_from = params[:original_created_from]
  109. original_created_before = params[:original_created_before]
  110. post = Post.find(params[:id].to_i)
  111. if post.update(title:, original_created_from:, original_created_before:)
  112. tags = post.tags.where(category: 'nico').to_a +
  113. Tag.normalise_tags(tag_names, with_tagme: false)
  114. tags = Tag.expand_parent_tags(tags)
  115. sync_post_tags!(post, tags)
  116. post.reload
  117. json = post.as_json
  118. json['tags'] = build_tag_tree_for(post.tags)
  119. render json:, status: :ok
  120. else
  121. render json: post.errors, status: :unprocessable_entity
  122. end
  123. rescue Tag::NicoTagNormalisationError
  124. head :bad_request
  125. end
  126. def changes
  127. id = params[:id].presence
  128. page = (params[:page].presence || 1).to_i
  129. limit = (params[:limit].presence || 20).to_i
  130. page = 1 if page < 1
  131. limit = 1 if limit < 1
  132. offset = (page - 1) * limit
  133. pts = PostTag.with_discarded
  134. pts = pts.where(post_id: id) if id.present?
  135. pts = pts.includes(:post, :created_user, :deleted_user,
  136. tag: { tag_name: :wiki_page })
  137. events = []
  138. pts.each do |pt|
  139. tag = TagRepr.base(pt.tag)
  140. post = pt.post
  141. events << Event.new(
  142. post:,
  143. tag:,
  144. user: pt.created_user && { id: pt.created_user.id, name: pt.created_user.name },
  145. change_type: 'add',
  146. timestamp: pt.created_at)
  147. if pt.discarded_at
  148. events << Event.new(
  149. post:,
  150. tag:,
  151. user: pt.deleted_user && { id: pt.deleted_user.id, name: pt.deleted_user.name },
  152. change_type: 'remove',
  153. timestamp: pt.discarded_at)
  154. end
  155. end
  156. events.sort_by!(&:timestamp)
  157. events.reverse!
  158. render json: { changes: (events.slice(offset, limit) || []).as_json, count: events.size }
  159. end
  160. private
  161. def filtered_posts
  162. tag_names = params[:tags].to_s.split
  163. match_type = params[:match]
  164. if tag_names.present?
  165. filter_posts_by_tags(tag_names, match_type)
  166. else
  167. Post.all
  168. end
  169. end
  170. def filter_posts_by_tags tag_names, match_type
  171. tag_names = TagName.canonicalise(tag_names)
  172. posts = Post.joins(tags: :tag_name)
  173. if match_type == 'any'
  174. posts.where(tag_names: { name: tag_names }).distinct
  175. else
  176. posts.where(tag_names: { name: tag_names })
  177. .group('posts.id')
  178. .having('COUNT(DISTINCT tag_names.id) = ?', tag_names.uniq.size)
  179. end
  180. end
  181. def sync_post_tags! post, desired_tags
  182. desired_tags.each do |t|
  183. t.save! if t.new_record?
  184. end
  185. desired_ids = desired_tags.map(&:id).to_set
  186. current_ids = post.tags.pluck(:id).to_set
  187. to_add = desired_ids - current_ids
  188. to_remove = current_ids - desired_ids
  189. Tag.where(id: to_add).find_each do |tag|
  190. begin
  191. PostTag.create!(post:, tag:, created_user: current_user)
  192. rescue ActiveRecord::RecordNotUnique
  193. ;
  194. end
  195. end
  196. PostTag.where(post_id: post.id, tag_id: to_remove.to_a).kept.find_each do |pt|
  197. pt.discard_by!(current_user)
  198. end
  199. end
  200. def build_tag_tree_for tags
  201. tags = tags.to_a
  202. tag_ids = tags.map(&:id)
  203. implications = TagImplication.where(parent_tag_id: tag_ids, tag_id: tag_ids)
  204. children_ids_by_parent = Hash.new { |h, k| h[k] = [] }
  205. implications.each do |imp|
  206. children_ids_by_parent[imp.parent_tag_id] << imp.tag_id
  207. end
  208. child_ids = children_ids_by_parent.values.flatten.uniq
  209. root_ids = tag_ids - child_ids
  210. tags_by_id = tags.index_by(&:id)
  211. memo = { }
  212. build_node = -> tag_id, path do
  213. tag = tags_by_id[tag_id]
  214. return nil unless tag
  215. if path.include?(tag_id)
  216. return TagRepr.base(tag).merge(children: [])
  217. end
  218. if memo.key?(tag_id)
  219. return memo[tag_id]
  220. end
  221. new_path = path + [tag_id]
  222. child_ids = children_ids_by_parent[tag_id] || []
  223. children = child_ids.filter_map { |cid| build_node.(cid, new_path) }
  224. memo[tag_id] = TagRepr.base(tag).merge(children:)
  225. end
  226. root_ids.filter_map { |id| build_node.call(id, []) }
  227. end
  228. end