ぼざクリタグ広場 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.
 
 
 
 
 
 

195 lines
5.5 KiB

  1. require 'open-uri'
  2. require 'nokogiri'
  3. class PostsController < ApplicationController
  4. # GET /posts
  5. def index
  6. limit = params[:limit].presence&.to_i
  7. cursor = params[:cursor].presence
  8. created_at = ('COALESCE(posts.original_created_before - INTERVAL 1 SECOND,' +
  9. 'posts.original_created_from,' +
  10. 'posts.created_at)')
  11. q = filtered_posts.order(Arel.sql("#{ created_at } DESC"))
  12. q = q.where("#{ created_at } < ?", Time.iso8601(cursor)) if cursor
  13. posts = limit ? q.limit(limit + 1) : q
  14. next_cursor = nil
  15. if limit && posts.size > limit
  16. next_cursor = posts.last.created_at.iso8601(6)
  17. posts = posts.first(limit)
  18. end
  19. render json: { posts: posts.map { |post|
  20. post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }).tap do |json|
  21. json['thumbnail'] =
  22. if post.thumbnail.attached?
  23. rails_storage_proxy_url(post.thumbnail, only_path: false)
  24. else
  25. nil
  26. end
  27. end
  28. }, next_cursor: }
  29. end
  30. def random
  31. post = filtered_posts.order('RAND()').first
  32. return head :not_found unless post
  33. viewed = current_user&.viewed?(post) || false
  34. render json: (post
  35. .as_json(include: { tags: { only: [:id, :name, :category, :post_count] } })
  36. .merge(viewed:))
  37. end
  38. # GET /posts/1
  39. def show
  40. post = Post.includes(:tags).find(params[:id])
  41. return head :not_found unless post
  42. viewed = current_user&.viewed?(post) || false
  43. json = post.as_json
  44. json['tags'] = build_tag_tree_for(post.tags)
  45. json['related'] = post.related(limit: 20)
  46. json['viewed'] = viewed
  47. render json:
  48. end
  49. # POST /posts
  50. def create
  51. return head :unauthorized unless current_user
  52. return head :forbidden unless current_user.member?
  53. # TODO: URL が正規のものがチェック,不正ならエラー
  54. # TODO: URL は必須にする(タイトルは省略可).
  55. # TODO: サイトに応じて thumbnail_base 設定
  56. title = params[:title]
  57. url = params[:url]
  58. thumbnail = params[:thumbnail]
  59. tag_names = params[:tags].to_s.split(' ')
  60. original_created_from = params[:original_created_from]
  61. original_created_before = params[:original_created_before]
  62. post = Post.new(title:, url:, thumbnail_base: '', uploaded_user: current_user,
  63. original_created_from:, original_created_before:)
  64. post.thumbnail.attach(thumbnail)
  65. if post.save
  66. post.resized_thumbnail!
  67. post.tags = Tag.normalise_tags(tag_names)
  68. post.tags = Tag.expand_parent_tags(post.tags)
  69. render json: post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }),
  70. status: :created
  71. else
  72. render json: { errors: post.errors.full_messages }, status: :unprocessable_entity
  73. end
  74. end
  75. def viewed
  76. return head :unauthorized unless current_user
  77. current_user.viewed_posts << Post.find(params[:id])
  78. head :no_content
  79. end
  80. def unviewed
  81. return head :unauthorized unless current_user
  82. current_user.viewed_posts.delete(Post.find(params[:id]))
  83. head :no_content
  84. end
  85. # PATCH/PUT /posts/1
  86. def update
  87. return head :unauthorized unless current_user
  88. return head :forbidden unless current_user.member?
  89. title = params[:title]
  90. tag_names = params[:tags].to_s.split(' ')
  91. original_created_from = params[:original_created_from]
  92. original_created_before = params[:original_created_before]
  93. post = Post.find(params[:id].to_i)
  94. tags = post.tags.where(category: 'nico').to_a +
  95. Tag.normalise_tags(tag_names, with_tagme: false)
  96. tags = Tag.expand_parent_tags(tags)
  97. if post.update(title:, tags:, original_created_from:, original_created_before:)
  98. json = post.as_json
  99. json['tags'] = build_tag_tree_for(post.tags)
  100. render json:, status: :ok
  101. else
  102. render json: post.errors, status: :unprocessable_entity
  103. end
  104. end
  105. # DELETE /posts/1
  106. def destroy
  107. end
  108. private
  109. def filtered_posts
  110. tag_names = params[:tags]&.split(' ')
  111. match_type = params[:match]
  112. tag_names.present? ? filter_posts_by_tags(tag_names, match_type) : Post.all
  113. end
  114. def filter_posts_by_tags tag_names, match_type
  115. posts = Post.joins(:tags)
  116. if match_type == 'any'
  117. posts = posts.where(tags: { name: tag_names }).distinct
  118. else
  119. tag_names.each do |tag|
  120. posts = posts.where(id: Post.joins(:tags).where(tags: { name: tag }))
  121. end
  122. end
  123. posts.distinct
  124. end
  125. def build_tag_tree_for tags
  126. tags = tags.to_a
  127. tag_ids = tags.map(&:id)
  128. implications = TagImplication.where(parent_tag_id: tag_ids, tag_id: tag_ids)
  129. children_ids_by_parent = Hash.new { |h, k| h[k] = [] }
  130. implications.each do |imp|
  131. children_ids_by_parent[imp.parent_tag_id] << imp.tag_id
  132. end
  133. child_ids = children_ids_by_parent.values.flatten.uniq
  134. root_ids = tag_ids - child_ids
  135. tags_by_id = tags.index_by(&:id)
  136. memo = { }
  137. build_node = -> tag_id, path do
  138. tag = tags_by_id[tag_id]
  139. return nil unless tag
  140. if path.include?(tag_id)
  141. return tag.as_json(only: [:id, :name, :category, :post_count]).merge(children: [])
  142. end
  143. if memo.key?(tag_id)
  144. return memo[tag_id]
  145. end
  146. new_path = path + [tag_id]
  147. child_ids = children_ids_by_parent[tag_id] || []
  148. children = child_ids.filter_map { |cid| build_node.(cid, new_path) }
  149. memo[tag_id] = tag.as_json(only: [:id, :name, :category, :post_count]).merge(children:)
  150. end
  151. root_ids.filter_map { |id| build_node.call(id, []) }
  152. end
  153. end