コミットを比較
12 コミット
| 作成者 | SHA1 | 日付 | |
|---|---|---|---|
| 409498729a | |||
| 9e3cbd2469 | |||
| f7af5efaf9 | |||
| 4235903b49 | |||
| 17ff0e8114 | |||
| 16e9b8ca49 | |||
| 6b1079fc1a | |||
| 7885f6dfb9 | |||
| a66a73e004 | |||
| 98330b00bb | |||
| 95ea097636 | |||
| e0e7a22c38 |
@@ -0,0 +1,47 @@
|
|||||||
|
class DeerjikistsController < ApplicationController
|
||||||
|
def show
|
||||||
|
platform = params[:platform].to_s.strip
|
||||||
|
code = params[:code].to_s.strip
|
||||||
|
return head :bad_request if platform.blank? || code.blank?
|
||||||
|
|
||||||
|
deerjikist = Deerjikist
|
||||||
|
.joins(:tag)
|
||||||
|
.includes(tag: :tag_name)
|
||||||
|
.find_by(platform:, code:)
|
||||||
|
if deerjikist
|
||||||
|
render json: DeerjikistRepr.base(deerjikist)
|
||||||
|
else
|
||||||
|
head :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
return head :unauthorized unless current_user
|
||||||
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
|
platform = params[:platform].to_s.strip
|
||||||
|
code = params[:code].to_s.strip
|
||||||
|
tag_id = params[:tag_id].to_i
|
||||||
|
return head :bad_request if platform.blank? || code.blank? || tag_id <= 0
|
||||||
|
|
||||||
|
deerjikist = Deerjikist.find_or_initialize_by(platform:, code:).tap do |d|
|
||||||
|
d.tag_id = tag_id
|
||||||
|
d.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: DeerjikistRepr.base(deerjikist)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
return head :unauthorized unless current_user
|
||||||
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
|
platform = params[:platform].to_s.strip
|
||||||
|
code = params[:code].to_s.strip
|
||||||
|
return head :bad_request if platform.blank? || code.blank?
|
||||||
|
|
||||||
|
Deerjikist.find([platform, code]).destroy!
|
||||||
|
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -25,7 +25,7 @@ class NicoTagsController < ApplicationController
|
|||||||
|
|
||||||
def update
|
def update
|
||||||
return head :unauthorized unless current_user
|
return head :unauthorized unless current_user
|
||||||
return head :forbidden unless current_user.member?
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
id = params[:id].to_i
|
id = params[:id].to_i
|
||||||
|
|
||||||
|
|||||||
@@ -65,15 +65,20 @@ class PostsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
sort_sql =
|
sort_sql =
|
||||||
if order[0] == 'original_created_at'
|
case order[0]
|
||||||
|
when 'original_created_at'
|
||||||
'COALESCE(posts.original_created_before - INTERVAL 1 MINUTE,' +
|
'COALESCE(posts.original_created_before - INTERVAL 1 MINUTE,' +
|
||||||
'posts.original_created_from,' +
|
'posts.original_created_from,' +
|
||||||
'posts.created_at) ' +
|
'posts.created_at) '
|
||||||
order[1]
|
when 'updated_at'
|
||||||
|
updated_at_all_sql
|
||||||
else
|
else
|
||||||
"posts.#{ order[0] } #{ order[1] }"
|
"posts.#{ order[0] }"
|
||||||
end
|
end
|
||||||
posts = q.order(Arel.sql("#{ sort_sql }")).limit(limit).offset(offset).to_a
|
posts = q.order(Arel.sql("#{ sort_sql } #{ order[1] }, id #{ order[1] }"))
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.to_a
|
||||||
|
|
||||||
q = q.except(:select, :order)
|
q = q.except(:select, :order)
|
||||||
|
|
||||||
@@ -116,7 +121,7 @@ class PostsController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
return head :unauthorized unless current_user
|
return head :unauthorized unless current_user
|
||||||
return head :forbidden unless current_user.member?
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
# TODO: サイトに応じて thumbnail_base 設定
|
# TODO: サイトに応じて thumbnail_base 設定
|
||||||
title = params[:title].presence
|
title = params[:title].presence
|
||||||
@@ -160,7 +165,7 @@ class PostsController < ApplicationController
|
|||||||
|
|
||||||
def update
|
def update
|
||||||
return head :unauthorized unless current_user
|
return head :unauthorized unless current_user
|
||||||
return head :forbidden unless current_user.member?
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
title = params[:title].presence
|
title = params[:title].presence
|
||||||
tag_names = params[:tags].to_s.split
|
tag_names = params[:tags].to_s.split
|
||||||
|
|||||||
@@ -86,9 +86,30 @@ class TagsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deerjikists
|
||||||
|
tag = Tag.joins(:tag_name)
|
||||||
|
.includes(:tag_name, tag_name: :wiki_page)
|
||||||
|
.find_by(id: params[:id])
|
||||||
|
return head :not_found unless tag
|
||||||
|
|
||||||
|
render json: DeerjikistRepr.many(tag.deerjikists)
|
||||||
|
end
|
||||||
|
|
||||||
|
def deerjikists_by_name
|
||||||
|
name = params[:name].to_s.strip
|
||||||
|
return head :bad_request if name.blank?
|
||||||
|
|
||||||
|
tag = Tag.joins(:tag_name)
|
||||||
|
.includes(:tag_name, tag_name: :wiki_page)
|
||||||
|
.find_by(tag_names: { name: })
|
||||||
|
return head :not_found unless tag
|
||||||
|
|
||||||
|
render json: DeerjikistRepr.many(tag.deerjikists)
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
return head :unauthorized unless current_user
|
return head :unauthorized unless current_user
|
||||||
return head :forbidden unless current_user.member?
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
name = params[:name].presence
|
name = params[:name].presence
|
||||||
category = params[:category].presence
|
category = params[:category].presence
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class WikiPagesController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
return head :unauthorized unless current_user
|
return head :unauthorized unless current_user
|
||||||
return head :forbidden unless current_user.member?
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
name = params[:title]&.strip
|
name = params[:title]&.strip
|
||||||
body = params[:body].to_s
|
body = params[:body].to_s
|
||||||
@@ -105,7 +105,7 @@ class WikiPagesController < ApplicationController
|
|||||||
|
|
||||||
def update
|
def update
|
||||||
return head :unauthorized unless current_user
|
return head :unauthorized unless current_user
|
||||||
return head :forbidden unless current_user.member?
|
return head :forbidden unless current_user.gte_member?
|
||||||
|
|
||||||
title = params[:title]&.strip
|
title = params[:title]&.strip
|
||||||
body = params[:body].to_s
|
body = params[:body].to_s
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
class Deerjikist < ApplicationRecord
|
||||||
|
self.primary_key = :platform, :code
|
||||||
|
|
||||||
|
belongs_to :tag
|
||||||
|
|
||||||
|
validates :platform, presence: true
|
||||||
|
validates :code, presence: true
|
||||||
|
validates :tag_id, presence: true
|
||||||
|
|
||||||
|
validate :tag_must_be_deerjikist
|
||||||
|
|
||||||
|
enum :platform, nico: 'nico', youtube: 'youtube'
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def tag_must_be_deerjikist
|
||||||
|
if tag && !(tag.deerjikist?)
|
||||||
|
errors.add :tag, 'タグはニジラー・カテゴリである必要があります.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
+53
-9
@@ -23,6 +23,10 @@ class Tag < ApplicationRecord
|
|||||||
has_many :parents, through: :reversed_tag_implications, source: :parent_tag
|
has_many :parents, through: :reversed_tag_implications, source: :parent_tag
|
||||||
|
|
||||||
has_many :tag_similarities, dependent: :delete_all
|
has_many :tag_similarities, dependent: :delete_all
|
||||||
|
has_many :tag_similarities_as_target,
|
||||||
|
class_name: 'TagSimilarity', foreign_key: :target_tag_id, dependent: :delete_all
|
||||||
|
|
||||||
|
has_many :deerjikists, dependent: :delete_all
|
||||||
|
|
||||||
belongs_to :tag_name
|
belongs_to :tag_name
|
||||||
delegate :wiki_page, to: :tag_name
|
delegate :wiki_page, to: :tag_name
|
||||||
@@ -42,8 +46,9 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
validate :nico_tag_name_must_start_with_nico
|
validate :nico_tag_name_must_start_with_nico
|
||||||
validate :tag_name_must_be_canonical
|
validate :tag_name_must_be_canonical
|
||||||
|
validate :category_must_be_deerjikist_with_deerjikists
|
||||||
|
|
||||||
scope :nico_tags, -> { where(category: :nico) }
|
scope :nico_tags, -> { nico }
|
||||||
|
|
||||||
CATEGORY_PREFIXES = {
|
CATEGORY_PREFIXES = {
|
||||||
'general:' => :general,
|
'general:' => :general,
|
||||||
@@ -61,9 +66,7 @@ class Tag < ApplicationRecord
|
|||||||
(self.tag_name ||= build_tag_name).name = val
|
(self.tag_name ||= build_tag_name).name = val
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_wiki
|
def has_wiki = wiki_page.present?
|
||||||
wiki_page.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.tagme
|
def self.tagme
|
||||||
@tagme ||= find_or_create_by_tag_name!('タグ希望', category: :meta)
|
@tagme ||= find_or_create_by_tag_name!('タグ希望', category: :meta)
|
||||||
@@ -73,6 +76,10 @@ class Tag < ApplicationRecord
|
|||||||
@bot ||= find_or_create_by_tag_name!('bot操作', category: :meta)
|
@bot ||= find_or_create_by_tag_name!('bot操作', category: :meta)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.no_deerjikist
|
||||||
|
@no_deerjikist ||= find_or_create_by_tag_name!('ニジラー情報不詳', category: :meta)
|
||||||
|
end
|
||||||
|
|
||||||
def self.video
|
def self.video
|
||||||
@video ||= find_or_create_by_tag_name!('動画', category: :meta)
|
@video ||= find_or_create_by_tag_name!('動画', category: :meta)
|
||||||
end
|
end
|
||||||
@@ -90,13 +97,12 @@ class Tag < ApplicationRecord
|
|||||||
pf, cat = CATEGORY_PREFIXES.find { |p, _| name.downcase.start_with?(p) } || ['', nil]
|
pf, cat = CATEGORY_PREFIXES.find { |p, _| name.downcase.start_with?(p) } || ['', nil]
|
||||||
name = TagName.canonicalise(name.sub(/\A#{ pf }/i, '')).first
|
name = TagName.canonicalise(name.sub(/\A#{ pf }/i, '')).first
|
||||||
find_or_create_by_tag_name!(name, category: (cat || :general)).tap do |tag|
|
find_or_create_by_tag_name!(name, category: (cat || :general)).tap do |tag|
|
||||||
if cat && tag.category != cat
|
tag.update!(category: cat) if cat && tag.category != cat
|
||||||
tag.update!(category: cat)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
tags << Tag.tagme if with_tagme && tags.size < 10 && tags.none?(Tag.tagme)
|
tags << Tag.tagme if with_tagme && tags.size < 10 && tags.none?(Tag.tagme)
|
||||||
|
tags << Tag.no_deerjikist if tags.all? { |t| !(t.deerjikist?) }
|
||||||
tags.uniq(&:id)
|
tags.uniq(&:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -134,12 +140,44 @@ class Tag < ApplicationRecord
|
|||||||
retry
|
retry
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.merge_tags! target_tag, source_tags
|
||||||
|
target_tag => Tag
|
||||||
|
|
||||||
|
Tag.transaction do
|
||||||
|
Array(source_tags).compact.uniq.each do |st|
|
||||||
|
st => Tag
|
||||||
|
|
||||||
|
next if st == target_tag
|
||||||
|
|
||||||
|
st.post_tags.find_each do |pt|
|
||||||
|
if PostTag.kept.exists?(post_id: pt.post_id, tag_id: target_tag.id)
|
||||||
|
pt.discard_by!(nil)
|
||||||
|
# discard 後の update! は禁止なので DB を直に更新
|
||||||
|
pt.update_columns(tag_id: target_tag.id, updated_at: Time.current)
|
||||||
|
else
|
||||||
|
pt.update!(tag: target_tag)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tag_name = st.tag_name
|
||||||
|
st.destroy!
|
||||||
|
tag_name.reload
|
||||||
|
tag_name.update!(canonical: target_tag.tag_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# 投稿件数を再集計
|
||||||
|
target_tag.update_columns(post_count: PostTag.kept.where(tag: target_tag).count)
|
||||||
|
end
|
||||||
|
|
||||||
|
target_tag.reload
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def nico_tag_name_must_start_with_nico
|
def nico_tag_name_must_start_with_nico
|
||||||
n = name.to_s
|
n = name.to_s
|
||||||
if ((category == 'nico' && !(n.downcase.start_with?('nico:'))) ||
|
if ((nico? && !(n.downcase.start_with?('nico:'))) ||
|
||||||
(category != 'nico' && n.downcase.start_with?('nico:')))
|
(!(nico?) && n.downcase.start_with?('nico:')))
|
||||||
errors.add :name, 'ニコニコ・タグの命名規則に反してゐます.'
|
errors.add :name, 'ニコニコ・タグの命名規則に反してゐます.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -149,4 +187,10 @@ class Tag < ApplicationRecord
|
|||||||
errors.add :tag_name, 'tag_names へは実体を示す必要があります.'
|
errors.add :tag_name, 'tag_names へは実体を示す必要があります.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def category_must_be_deerjikist_with_deerjikists
|
||||||
|
if !(deerjikist?) && deerjikists.exists?
|
||||||
|
errors.add :category, 'ニジラーと紐づいてゐるタグはニジラー・カテゴリである必要があります.'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,29 +1,23 @@
|
|||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
enum :role, { guest: 'guest', member: 'member', admin: 'admin' }
|
enum :role, guest: 'guest', member: 'member', admin: 'admin'
|
||||||
|
|
||||||
validates :name, length: { maximum: 255 }
|
validates :name, length: { maximum: 255 }
|
||||||
validates :inheritance_code, presence: true, length: { maximum: 64 }
|
validates :inheritance_code, presence: true, length: { maximum: 64 }
|
||||||
validates :role, presence: true, inclusion: { in: roles.keys }
|
validates :role, presence: true, inclusion: { in: roles.keys }
|
||||||
validates :banned, inclusion: { in: [true, false] }
|
validates :banned, inclusion: { in: [true, false] }
|
||||||
|
|
||||||
has_many :posts
|
has_many :created_posts,
|
||||||
|
class_name: 'Post', foreign_key: :uploaded_user_id, dependent: :nullify
|
||||||
has_many :settings
|
has_many :settings
|
||||||
has_many :user_ips, dependent: :destroy
|
has_many :user_ips, dependent: :destroy
|
||||||
has_many :ip_addresses, through: :user_ips
|
has_many :ip_addresses, through: :user_ips
|
||||||
has_many :user_post_views, dependent: :destroy
|
has_many :user_post_views, dependent: :destroy
|
||||||
has_many :viewed_posts, through: :user_post_views, source: :post
|
has_many :viewed_posts, through: :user_post_views, source: :post
|
||||||
has_many :created_wiki_pages, class_name: 'WikiPage', foreign_key: 'created_user_id', dependent: :nullify
|
has_many :created_wiki_pages,
|
||||||
has_many :updated_wiki_pages, class_name: 'WikiPage', foreign_key: 'updated_user_id', dependent: :nullify
|
class_name: 'WikiPage', foreign_key: :created_user_id, dependent: :nullify
|
||||||
|
has_many :updated_wiki_pages,
|
||||||
|
class_name: 'WikiPage', foreign_key: :updated_user_id, dependent: :nullify
|
||||||
|
|
||||||
def viewed? post
|
def viewed?(post) = user_post_views.exists?(post_id: post.id)
|
||||||
user_post_views.exists? post_id: post.id
|
def gte_member? = member? || admin?
|
||||||
end
|
|
||||||
|
|
||||||
def member?
|
|
||||||
['member', 'admin'].include?(role)
|
|
||||||
end
|
|
||||||
|
|
||||||
def admin?
|
|
||||||
role == 'admin'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class WikiLine < ApplicationRecord
|
|||||||
sha = Digest::SHA256.hexdigest(body)
|
sha = Digest::SHA256.hexdigest(body)
|
||||||
now = Time.current
|
now = Time.current
|
||||||
|
|
||||||
upsert({ sha256: sha, body:, created_at: now, updated_at: now })
|
upsert(sha256: sha, body:, created_at: now, updated_at: now)
|
||||||
|
|
||||||
find_by!(sha256: sha)
|
find_by!(sha256: sha)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,17 +14,13 @@ class WikiPage < ApplicationRecord
|
|||||||
belongs_to :tag_name
|
belongs_to :tag_name
|
||||||
validates :tag_name, presence: true
|
validates :tag_name, presence: true
|
||||||
|
|
||||||
def title
|
def title = tag_name.name
|
||||||
tag_name.name
|
|
||||||
end
|
|
||||||
|
|
||||||
def title= val
|
def title= val
|
||||||
(self.tag_name ||= build_tag_name).name = val
|
(self.tag_name ||= build_tag_name).name = val
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_revision
|
def current_revision = wiki_revisions.order(id: :desc).first
|
||||||
wiki_revisions.order(id: :desc).first
|
|
||||||
end
|
|
||||||
|
|
||||||
def body
|
def body
|
||||||
rev = current_revision
|
rev = current_revision
|
||||||
@@ -49,11 +45,8 @@ class WikiPage < ApplicationRecord
|
|||||||
page
|
page
|
||||||
end
|
end
|
||||||
|
|
||||||
def pred_revision_id revision_id
|
def pred_revision_id(revision_id) =
|
||||||
wiki_revisions.where('id < ?', revision_id).order(id: :desc).limit(1).pick(:id)
|
wiki_revisions.where('id < ?', revision_id).order(id: :desc).limit(1).pick(:id)
|
||||||
end
|
def succ_revision_id(revision_id) =
|
||||||
|
|
||||||
def succ_revision_id revision_id
|
|
||||||
wiki_revisions.where('id > ?', revision_id).order(id: :asc).limit(1).pick(:id)
|
wiki_revisions.where('id > ?', revision_id).order(id: :asc).limit(1).pick(:id)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class WikiRevision < ApplicationRecord
|
|||||||
has_many :wiki_revision_lines, dependent: :delete_all
|
has_many :wiki_revision_lines, dependent: :delete_all
|
||||||
has_many :wiki_lines, through: :wiki_revision_lines
|
has_many :wiki_lines, through: :wiki_revision_lines
|
||||||
|
|
||||||
enum :kind, { content: 0, redirect: 1 }
|
enum :kind, content: 0, redirect: 1
|
||||||
|
|
||||||
validates :kind, presence: true
|
validates :kind, presence: true
|
||||||
validates :lines_count, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
validates :lines_count, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
|
||||||
|
module DeerjikistRepr
|
||||||
|
BASE = { only: [:platform, :code], include: { tag: TagRepr::BASE } }.freeze
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def base deerjikist
|
||||||
|
deerjikist.as_json(BASE)
|
||||||
|
end
|
||||||
|
|
||||||
|
def many deerjikists
|
||||||
|
deerjikists.map { |d| base(d) }
|
||||||
|
end
|
||||||
|
end
|
||||||
+19
-1
@@ -9,7 +9,15 @@ Rails.application.routes.draw do
|
|||||||
resources :tags, only: [:index, :show, :update] do
|
resources :tags, only: [:index, :show, :update] do
|
||||||
collection do
|
collection do
|
||||||
get :autocomplete
|
get :autocomplete
|
||||||
get 'name/:name', action: :show_by_name
|
|
||||||
|
scope :name do
|
||||||
|
get ':name/deerjikists', action: :deerjikists_by_name
|
||||||
|
get ':name', action: :show_by_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
member do
|
||||||
|
get :deerjikists
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -54,4 +62,14 @@ Rails.application.routes.draw do
|
|||||||
post 'code/renew', action: :renew
|
post 'code/renew', action: :renew
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :deerjikists, only: [] do
|
||||||
|
collection do
|
||||||
|
scope ':platform/:code' do
|
||||||
|
get '', action: :show
|
||||||
|
put '', action: :update
|
||||||
|
delete '', action: :destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
class CreateDeerjikists < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
create_table :deerjikists, primary_key: [:platform, :code] do |t|
|
||||||
|
t.string :platform, null: false, limit: 16
|
||||||
|
t.string :code, null: false
|
||||||
|
t.references :tag, null: false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
生成ファイル
+10
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2026_01_27_005300) do
|
ActiveRecord::Schema[8.0].define(version: 2026_03_03_122700) do
|
||||||
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
@@ -39,6 +39,15 @@ ActiveRecord::Schema[8.0].define(version: 2026_01_27_005300) do
|
|||||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "deerjikists", primary_key: ["platform", "code"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
|
t.string "platform", limit: 16, null: false
|
||||||
|
t.string "code", null: false
|
||||||
|
t.bigint "tag_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["tag_id"], name: "index_deerjikists_on_tag_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "ip_addresses", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "ip_addresses", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.binary "ip_address", limit: 16, null: false
|
t.binary "ip_address", limit: 16, null: false
|
||||||
t.boolean "banned", default: false, null: false
|
t.boolean "banned", default: false, null: false
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ namespace :nico do
|
|||||||
desc 'ニコニコ DB 同期'
|
desc 'ニコニコ DB 同期'
|
||||||
task sync: :environment do
|
task sync: :environment do
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'open3'
|
|
||||||
require 'open-uri'
|
|
||||||
require 'nokogiri'
|
require 'nokogiri'
|
||||||
|
require 'open-uri'
|
||||||
|
require 'open3'
|
||||||
require 'set'
|
require 'set'
|
||||||
require 'time'
|
require 'time'
|
||||||
|
|
||||||
@@ -15,12 +15,12 @@ namespace :nico do
|
|||||||
doc.at('meta[name="thumbnail"]')&.[]('content').presence
|
doc.at('meta[name="thumbnail"]')&.[]('content').presence
|
||||||
end
|
end
|
||||||
|
|
||||||
def sync_post_tags! post, desired_tag_ids, current_ids: nil
|
def sync_post_tags! post, desired_tag_ids, current_tag_ids: nil
|
||||||
current_ids ||= PostTag.kept.where(post_id: post.id).pluck(:tag_id).to_set
|
current_tag_ids ||= PostTag.kept.where(post_id: post.id).pluck(:tag_id).to_set
|
||||||
desired_ids = desired_tag_ids.compact.to_set
|
desired_tag_ids = desired_tag_ids.compact.to_set
|
||||||
|
|
||||||
to_add = desired_ids - current_ids
|
to_add = desired_tag_ids - current_tag_ids
|
||||||
to_remove = current_ids - desired_ids
|
to_remove = current_tag_ids - desired_tag_ids
|
||||||
|
|
||||||
Tag.where(id: to_add.to_a).find_each do |tag|
|
Tag.where(id: to_add.to_a).find_each do |tag|
|
||||||
begin
|
begin
|
||||||
@@ -42,12 +42,14 @@ namespace :nico do
|
|||||||
{ 'MYSQL_USER' => mysql_user, 'MYSQL_PASS' => mysql_pass },
|
{ 'MYSQL_USER' => mysql_user, 'MYSQL_PASS' => mysql_pass },
|
||||||
'python3', "#{ nizika_nico_path }/get_videos.py")
|
'python3', "#{ nizika_nico_path }/get_videos.py")
|
||||||
|
|
||||||
abort unless status.success?
|
unless status.success?
|
||||||
|
warn stderr
|
||||||
|
abort
|
||||||
|
end
|
||||||
|
|
||||||
data = JSON.parse(stdout)
|
data = JSON.parse(stdout)
|
||||||
data.each do |datum|
|
data.each do |datum|
|
||||||
code = datum['code']
|
code = datum['code']
|
||||||
|
|
||||||
post =
|
post =
|
||||||
Post
|
Post
|
||||||
.where('url REGEXP ?', "nicovideo\\.jp/watch/#{ Regexp.escape(code) }([^0-9]|$)")
|
.where('url REGEXP ?', "nicovideo\\.jp/watch/#{ Regexp.escape(code) }([^0-9]|$)")
|
||||||
@@ -94,32 +96,50 @@ namespace :nico do
|
|||||||
sync_post_tags!(post, [Tag.tagme.id, Tag.bot.id, Tag.niconico.id, Tag.video.id])
|
sync_post_tags!(post, [Tag.tagme.id, Tag.bot.id, Tag.niconico.id, Tag.video.id])
|
||||||
end
|
end
|
||||||
|
|
||||||
kept_ids = PostTag.kept.where(post_id: post.id).pluck(:tag_id).to_set
|
tags = post.tags
|
||||||
kept_non_nico_ids = post.tags.where.not(category: 'nico').pluck(:id).to_set
|
# 既存のタグ Id. 集合
|
||||||
|
kept_tag_ids = tags.pluck(:id).to_set
|
||||||
|
# うち内部タグ Id. 集合
|
||||||
|
kept_non_nico_tag_ids = tags.not_nico.pluck(:id).to_set
|
||||||
|
|
||||||
|
# 記載すべき外部タグ Id. および連携される内部タグ Id. のリスト
|
||||||
|
desired_nico_tag_based_ids = []
|
||||||
|
# 記載すべき内部タグ Id. のリスト
|
||||||
|
desired_non_nico_tag_ids = []
|
||||||
|
|
||||||
desired_nico_ids = []
|
|
||||||
desired_non_nico_ids = []
|
|
||||||
datum['tags'].each do |raw|
|
datum['tags'].each do |raw|
|
||||||
name = "nico:#{ raw }"
|
name = "nico:#{ raw }"
|
||||||
tag = Tag.find_or_create_by_tag_name!(name, category: 'nico')
|
tag = Tag.find_or_create_by_tag_name!(name, category: :nico)
|
||||||
desired_nico_ids << tag.id
|
desired_nico_tag_based_ids << tag.id
|
||||||
unless tag.id.in?(kept_ids)
|
|
||||||
|
# 新たに記載される外部タグと連携される内部タグを記載
|
||||||
|
unless tag.id.in?(kept_tag_ids)
|
||||||
linked_ids = tag.linked_tags.pluck(:id)
|
linked_ids = tag.linked_tags.pluck(:id)
|
||||||
desired_non_nico_ids.concat(linked_ids)
|
desired_non_nico_tag_ids.concat(linked_ids)
|
||||||
desired_nico_ids.concat(linked_ids)
|
desired_nico_tag_based_ids.concat(linked_ids)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
desired_nico_ids.uniq!
|
|
||||||
|
|
||||||
desired_all_ids = kept_non_nico_ids.to_a + desired_nico_ids
|
deerjikist = Deerjikist.find_by(platform: :nico, code: datum['user'])
|
||||||
desired_non_nico_ids.concat(kept_non_nico_ids.to_a)
|
if deerjikist
|
||||||
desired_non_nico_ids.uniq!
|
desired_non_nico_tag_ids << deerjikist.tag_id
|
||||||
if kept_non_nico_ids.to_set != desired_non_nico_ids.to_set
|
desired_nico_tag_based_ids << deerjikist.tag_id
|
||||||
desired_all_ids << Tag.bot.id
|
elsif !(Tag.where(id: kept_non_nico_tag_ids).where(category: :deerjikist).exists?)
|
||||||
|
desired_non_nico_tag_ids << Tag.no_deerjikist.id
|
||||||
|
desired_nico_tag_based_ids << Tag.no_deerjikist.id
|
||||||
end
|
end
|
||||||
desired_all_ids.uniq!
|
|
||||||
|
|
||||||
sync_post_tags!(post, desired_all_ids, current_ids: kept_ids)
|
desired_nico_tag_based_ids.uniq!
|
||||||
|
|
||||||
|
desired_all_tag_ids = kept_non_nico_tag_ids.to_a + desired_nico_tag_based_ids
|
||||||
|
desired_non_nico_tag_ids.concat(kept_non_nico_tag_ids.to_a)
|
||||||
|
desired_non_nico_tag_ids.uniq!
|
||||||
|
if kept_non_nico_tag_ids != desired_non_nico_tag_ids.to_set
|
||||||
|
desired_all_tag_ids << Tag.bot.id
|
||||||
|
end
|
||||||
|
desired_all_tag_ids.uniq!
|
||||||
|
|
||||||
|
sync_post_tags!(post, desired_all_tag_ids, current_tag_ids: kept_tag_ids)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :tag do
|
factory :tag do
|
||||||
category { 'general' }
|
category { :general }
|
||||||
post_count { 0 }
|
post_count { 0 }
|
||||||
association :tag_name
|
association :tag_name
|
||||||
|
|
||||||
trait :nico do
|
trait :nico do
|
||||||
category { 'nico' }
|
category { :nico }
|
||||||
tag_name { association(:tag_name, name: "nico:#{ SecureRandom.hex(4) }") }
|
tag_name { association(:tag_name, name: "nico:#{ SecureRandom.hex(4) }") }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Tag, type: :model do
|
||||||
|
describe '.merge_tags!' do
|
||||||
|
let!(:target_tag) { create(:tag) }
|
||||||
|
let!(:source_tag) { create(:tag) }
|
||||||
|
|
||||||
|
let!(:post_record) { Post.create!(url: 'https://example.com/posts/1', title: 'test post') }
|
||||||
|
|
||||||
|
context 'when merging a simple source tag' do
|
||||||
|
let!(:source_post_tag) { PostTag.create!(post: post_record, tag: source_tag) }
|
||||||
|
|
||||||
|
it 'moves the post_tag, deletes the source tag, and aliases the source tag_name' do
|
||||||
|
described_class.merge_tags!(target_tag, [source_tag])
|
||||||
|
|
||||||
|
expect(source_post_tag.reload.tag_id).to eq(target_tag.id)
|
||||||
|
expect(Tag.exists?(source_tag.id)).to be(false)
|
||||||
|
expect(source_tag.tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the target already has the same post_tag' do
|
||||||
|
let!(:target_post_tag) { PostTag.create!(post: post_record, tag: target_tag) }
|
||||||
|
let!(:source_post_tag) { PostTag.create!(post: post_record, tag: source_tag) }
|
||||||
|
|
||||||
|
it 'discards the duplicate source post_tag and keeps one active target post_tag' do
|
||||||
|
described_class.merge_tags!(target_tag, [source_tag])
|
||||||
|
|
||||||
|
active = PostTag.kept.where(post_id: post_record.id, tag_id: target_tag.id)
|
||||||
|
discarded_source = PostTag.with_discarded.find(source_post_tag.id)
|
||||||
|
|
||||||
|
expect(active.count).to eq(1)
|
||||||
|
expect(discarded_source.discarded_at).to be_present
|
||||||
|
expect(Tag.exists?(source_tag.id)).to be(false)
|
||||||
|
expect(source_tag.tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when source_tags includes the target itself' do
|
||||||
|
let!(:source_post_tag) { PostTag.create!(post: post_record, tag: source_tag) }
|
||||||
|
|
||||||
|
it 'ignores the target tag in source_tags' do
|
||||||
|
described_class.merge_tags!(target_tag, [source_tag, target_tag])
|
||||||
|
|
||||||
|
expect(Tag.exists?(target_tag.id)).to be(true)
|
||||||
|
expect(Tag.exists?(source_tag.id)).to be(false)
|
||||||
|
expect(source_post_tag.reload.tag_id).to eq(target_tag.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when aliasing the source tag_name is invalid' do
|
||||||
|
let!(:source_post_tag) { PostTag.create!(post: post_record, tag: source_tag) }
|
||||||
|
let!(:wiki_page) do
|
||||||
|
WikiPage.create!(
|
||||||
|
tag_name: source_tag.tag_name,
|
||||||
|
created_user: create_admin_user!,
|
||||||
|
updated_user: create_admin_user!)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rolls back the transaction' do
|
||||||
|
expect {
|
||||||
|
described_class.merge_tags!(target_tag, [source_tag])
|
||||||
|
}.to raise_error(ActiveRecord::RecordInvalid)
|
||||||
|
|
||||||
|
expect(Tag.exists?(source_tag.id)).to be(true)
|
||||||
|
expect(source_post_tag.reload.tag_id).to eq(source_tag.id)
|
||||||
|
expect(source_tag.tag_name.reload.canonical_id).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Deerjikists API', type: :request do
|
||||||
|
let(:platform) { 'nico' }
|
||||||
|
let(:code) { 'deerjika-bot' }
|
||||||
|
|
||||||
|
let!(:tag1) { create(:tag, category: :deerjikist) }
|
||||||
|
let!(:tag2) { create(:tag, category: :deerjikist) }
|
||||||
|
|
||||||
|
let(:member) { create(:user, :member) }
|
||||||
|
let(:guest) { create(:user, role: :guest) }
|
||||||
|
|
||||||
|
describe 'GET /deerjikists/:platform/:code' do
|
||||||
|
subject(:do_request) do
|
||||||
|
get "/deerjikists/#{ platform }/#{ code }"
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when deerjikist exists' do
|
||||||
|
before do
|
||||||
|
Deerjikist.create!(platform:, code:, tag: tag1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 200 and deerjikist json' do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
expect(json).to be_a(Hash)
|
||||||
|
expect(json['platform']).to eq(platform)
|
||||||
|
expect(json['code']).to eq(code)
|
||||||
|
|
||||||
|
expect(json['tag']).to be_a(Hash)
|
||||||
|
expect(json['tag']['id']).to eq(tag1.id)
|
||||||
|
expect(json['tag']['name']).to eq(tag1.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when deerjikist does not exist' do
|
||||||
|
it 'returns 404' do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when platform or code become blank after strip' do
|
||||||
|
it 'returns 400' do
|
||||||
|
get '/deerjikists/%20/%20'
|
||||||
|
expect(response).to have_http_status(:bad_request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'PUT /deerjikists/:platform/:code' do
|
||||||
|
subject(:do_request) do
|
||||||
|
put "/deerjikists/#{ platform }/#{ code }", params: payload
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:payload) { { tag_id: tag1.id } }
|
||||||
|
|
||||||
|
context 'when not legged in' do
|
||||||
|
it 'returns 401' do
|
||||||
|
sign_out
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when logged in but not member' do
|
||||||
|
it 'returns 403' do
|
||||||
|
sign_in_as guest
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when member' do
|
||||||
|
before do
|
||||||
|
sign_in_as member
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when params invalid' do
|
||||||
|
it 'returns 400 when tag_id is missing or invalid' do
|
||||||
|
put "/deerjikists/#{ platform }/#{ code }", params: { tag_id: 0 }
|
||||||
|
expect(response).to have_http_status(:bad_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 400 when platform or code blank after strip' do
|
||||||
|
put '/deerjikists/%20/%20', params: { tag_id: tag1.id }
|
||||||
|
expect(response).to have_http_status(:bad_request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when creating new deerjikist' do
|
||||||
|
it 'creates and returns 200 with json' do
|
||||||
|
expect { do_request }.to change { Deerjikist.count }.by(1)
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
d = Deerjikist.find_by(platform:, code:)
|
||||||
|
expect(d).to be_present
|
||||||
|
expect(d.tag_id).to eq(tag1.id)
|
||||||
|
|
||||||
|
expect(json['platform']).to eq(platform)
|
||||||
|
expect(json['code']).to eq(code)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when updating existing deerjikist' do
|
||||||
|
before do
|
||||||
|
Deerjikist.create!(platform:, code:, tag: tag1)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:payload) { { tag_id: tag2.id } }
|
||||||
|
|
||||||
|
it 'updates tag_id and returns 200' do
|
||||||
|
expect { do_request }.not_to change { Deerjikist.count }
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
d = Deerjikist.find_by(platform:, code:)
|
||||||
|
expect(d.tag_id).to eq(tag2.id)
|
||||||
|
|
||||||
|
expect(json['platform']).to eq(platform)
|
||||||
|
expect(json['code']).to eq(code)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'DELETE /deerjikists/:platform/:code' do
|
||||||
|
subject(:do_request) do
|
||||||
|
delete "/deerjikists/#{ platform }/#{ code }"
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not logged in' do
|
||||||
|
it 'returns 401' do
|
||||||
|
sign_out
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when logged in but not member' do
|
||||||
|
it 'returns 403' do
|
||||||
|
sign_in_as guest
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when member' do
|
||||||
|
before do
|
||||||
|
sign_in_as member
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 400 when platform/code blank after strip' do
|
||||||
|
delete '/deerjikists/%20/%20'
|
||||||
|
expect(response).to have_http_status(:bad_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when deerjikist exists' do
|
||||||
|
before do
|
||||||
|
Deerjikist.create!(platform: platform, code: code, tag: tag1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'destroys and returns 204' do
|
||||||
|
expect {
|
||||||
|
do_request
|
||||||
|
}.to change { Deerjikist.exists?(platform: platform, code: code) }
|
||||||
|
.from(true).to(false)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:no_content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when deerjikist does not exist' do
|
||||||
|
it 'returns 404' do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
# spec/requests/tag_children_spec.rb
|
|
||||||
require "rails_helper"
|
require "rails_helper"
|
||||||
|
|
||||||
RSpec.describe "TagChildren", type: :request do
|
RSpec.describe "TagChildren", type: :request do
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Tags deerjikists API', type: :request do
|
||||||
|
let(:platform1) { 'nico' }
|
||||||
|
let(:code1) { 'deerjika-bot' }
|
||||||
|
let(:platform2) { 'youtube' }
|
||||||
|
let(:code2) { 'deerjika-bot.bsky.social' }
|
||||||
|
|
||||||
|
let!(:tag) { create(:tag, category: :deerjikist) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
# show_by_name / deerjikists_by_name 用に名前を固定
|
||||||
|
tag.tag_name.update!(name: 'deerjika')
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET /tags/:id/deerjikists' do
|
||||||
|
subject(:do_request) do
|
||||||
|
get "/tags/#{ tag_id }/deerjikists"
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:tag_id) { tag.id }
|
||||||
|
|
||||||
|
context 'when tag exists and has no deerjikists' do
|
||||||
|
it 'returns 200 and empty array' do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(json).to eq([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when tag exists and has deerjikists' do
|
||||||
|
before do
|
||||||
|
Deerjikist.create!(platform: platform1, code: code1, tag: tag)
|
||||||
|
Deerjikist.create!(platform: platform2, code: code2, tag: tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 200 and deerjikists array' do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
expect(json).to be_a(Array)
|
||||||
|
expect(json.size).to eq(2)
|
||||||
|
|
||||||
|
expect(json.map { |h| [h['platform'], h['code']] }).to contain_exactly(
|
||||||
|
[platform1, code1],
|
||||||
|
[platform2, code2],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when tag does not exist' do
|
||||||
|
let(:tag_id) { 9_999_999 }
|
||||||
|
|
||||||
|
it 'returns 404' do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET /tags/name/:name/deerjikists' do
|
||||||
|
subject(:do_request) do
|
||||||
|
get "/tags/name/#{ name }/deerjikists"
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:name) { 'deerjika' }
|
||||||
|
|
||||||
|
context 'when name is blank after strip' do
|
||||||
|
let(:name) { '%20' }
|
||||||
|
|
||||||
|
it 'returns 400' do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:bad_request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when tag does not exist for name' do
|
||||||
|
let(:name) { 'no-such-tag' }
|
||||||
|
|
||||||
|
it 'returns 404' do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when tag exists and has deerjikists' do
|
||||||
|
before do
|
||||||
|
Deerjikist.create!(platform: platform1, code: code1, tag: tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 200 and deerjikists array' do
|
||||||
|
do_request
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
expect(json).to be_a(Array)
|
||||||
|
expect(json.size).to eq(1)
|
||||||
|
expect(json[0]['platform']).to eq(platform1)
|
||||||
|
expect(json[0]['code']).to eq(code1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -106,8 +106,8 @@ RSpec.describe 'Tags API', type: :request do
|
|||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(member_user).to receive(:member?).and_return(true)
|
allow(member_user).to receive(:gte_member?).and_return(true)
|
||||||
allow(non_member_user).to receive(:member?).and_return(false)
|
allow(non_member_user).to receive(:gte_member?).and_return(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "PATCH /tags/:id" do
|
describe "PATCH /tags/:id" do
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする