グカネータ / 質問パターン見直し (#41) #365
@@ -126,12 +126,16 @@ npm run preview
|
|||||||
- In TypeScript and TSX only, replace every leading run of 8 spaces with a tab.
|
- In TypeScript and TSX only, replace every leading run of 8 spaces with a tab.
|
||||||
- Tabs are only for leading indentation, never for spaces after non-space text.
|
- Tabs are only for leading indentation, never for spaces after non-space text.
|
||||||
- Do not add production dependencies without explicit approval.
|
- Do not add production dependencies without explicit approval.
|
||||||
|
- Do not create, modify, or run tests unless the user explicitly asks for
|
||||||
|
test work. When the user asks for tests, keep working and rerun them until
|
||||||
|
they pass or the remaining failure is clearly blocked.
|
||||||
|
|
||||||
## Backend rules
|
## Backend rules
|
||||||
|
|
||||||
- Inspect existing routes, controllers, models, services, and specs before
|
- Inspect existing routes, controllers, models, services, and specs before
|
||||||
editing backend behavior.
|
editing backend behavior.
|
||||||
- For API behavior changes, add or update request specs under `backend/spec/requests`.
|
- For API behavior changes, add or update request specs under
|
||||||
|
`backend/spec/requests` only when the user explicitly asks for tests.
|
||||||
- Prefer RSpec for new backend tests; existing minitest files under
|
- Prefer RSpec for new backend tests; existing minitest files under
|
||||||
`backend/test` do not make minitest the default for new coverage.
|
`backend/test` do not make minitest the default for new coverage.
|
||||||
- Do not weaken authentication, BAN user checks, or IP BAN checks.
|
- Do not weaken authentication, BAN user checks, or IP BAN checks.
|
||||||
@@ -211,10 +215,11 @@ function PostFormTagsArea ({ tags, setTags }: Props) {
|
|||||||
`node_modules`, `dist`, `tmp`, `log`, and `storage` unless explicitly needed.
|
`node_modules`, `dist`, `tmp`, `log`, and `storage` unless explicitly needed.
|
||||||
- Before touching wiki, tag, versioning, BAN, IP BAN, or authentication
|
- Before touching wiki, tag, versioning, BAN, IP BAN, or authentication
|
||||||
behavior, inspect the related request specs and service objects.
|
behavior, inspect the related request specs and service objects.
|
||||||
- If frontend code changes, run the existing frontend verification commands
|
- If frontend code changes, run only non-test verification commands that
|
||||||
that apply: `npm run build`, `npm run lint`, and `npm run test:run`.
|
apply, such as `npm run build` and `npm run lint`. Run `npm run test:run`
|
||||||
- If backend code changes, run the relevant RSpec command; for broad backend
|
only when the user explicitly asks for tests.
|
||||||
changes, run `bundle exec rspec`.
|
- If backend code changes, do not run RSpec unless the user explicitly asks
|
||||||
|
for tests.
|
||||||
- If a verification command cannot be run or fails, report the exact command and failure.
|
- If a verification command cannot be run or fails, report the exact command and failure.
|
||||||
|
|
||||||
## Completion criteria
|
## Completion criteria
|
||||||
@@ -222,7 +227,8 @@ function PostFormTagsArea ({ tags, setTags }: Props) {
|
|||||||
A task is complete only when:
|
A task is complete only when:
|
||||||
|
|
||||||
- implementation is complete,
|
- implementation is complete,
|
||||||
- relevant verification commands pass, or failures are clearly explained,
|
- relevant non-test verification commands pass, or failures are clearly
|
||||||
|
explained,
|
||||||
- unrelated files are not changed,
|
- unrelated files are not changed,
|
||||||
- migrations and schema are consistent when schema changes are made,
|
- migrations and schema are consistent when schema changes are made,
|
||||||
- user-facing behavior is documented when needed.
|
- user-facing behavior is documented when needed.
|
||||||
|
|||||||
+11
-4
@@ -47,6 +47,10 @@ bundle exec rspec
|
|||||||
|
|
||||||
If a command cannot be run or fails, report the exact command and failure.
|
If a command cannot be run or fails, report the exact command and failure.
|
||||||
|
|
||||||
|
Do not create, modify, or run tests unless the user explicitly asks for test
|
||||||
|
work. When the user asks for tests, keep working and rerun them until they
|
||||||
|
pass or the remaining failure is clearly blocked.
|
||||||
|
|
||||||
## Rails structure
|
## Rails structure
|
||||||
|
|
||||||
- `app/controllers`: API controllers.
|
- `app/controllers`: API controllers.
|
||||||
@@ -116,7 +120,8 @@ service, representation, and spec.
|
|||||||
- `User#banned?` and `IpAddress#banned?` check `banned_at.present?`.
|
- `User#banned?` and `IpAddress#banned?` check `banned_at.present?`.
|
||||||
- Do not weaken BAN or IP BAN behavior.
|
- Do not weaken BAN or IP BAN behavior.
|
||||||
- If changing request authentication or controller before actions, add or
|
- If changing request authentication or controller before actions, add or
|
||||||
update request specs covering banned users and banned IP addresses.
|
update request specs covering banned users and banned IP addresses only when
|
||||||
|
the user explicitly asks for tests.
|
||||||
|
|
||||||
## RSpec
|
## RSpec
|
||||||
|
|
||||||
@@ -130,8 +135,9 @@ service, representation, and spec.
|
|||||||
- `AuthHelper#sign_in_as(user)` stubs
|
- `AuthHelper#sign_in_as(user)` stubs
|
||||||
`ApplicationController#current_user`; use it when matching existing
|
`ApplicationController#current_user`; use it when matching existing
|
||||||
request spec style.
|
request spec style.
|
||||||
- Add or update request specs for API behavior changes, especially status
|
- Add or update request specs for API behavior changes only when the user
|
||||||
codes, permissions, response shape, and version conflict behavior.
|
explicitly asks for tests, especially status codes, permissions, response
|
||||||
|
shape, and version conflict behavior.
|
||||||
|
|
||||||
## Migrations
|
## Migrations
|
||||||
|
|
||||||
@@ -164,7 +170,8 @@ service, representation, and spec.
|
|||||||
the record `version_no`.
|
the record `version_no`.
|
||||||
- Do not update versioned records without considering whether a version snapshot must be created.
|
- Do not update versioned records without considering whether a version snapshot must be created.
|
||||||
- For optimistic concurrency paths, preserve `base_version_no`, `force`, and
|
- For optimistic concurrency paths, preserve `base_version_no`, `force`, and
|
||||||
`merge` semantics and cover conflicts in request specs.
|
`merge` semantics. Cover conflicts in request specs only when the user
|
||||||
|
explicitly asks for tests.
|
||||||
|
|
||||||
## Domain cautions
|
## Domain cautions
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class GekanatorGamesController < ApplicationController
|
class GekanatorGamesController < ApplicationController
|
||||||
def create
|
def create
|
||||||
return head :unauthorized unless current_user
|
return head :not_found unless current_user&.admin?
|
||||||
|
|
||||||
guessed_post_id = params.require(:guessed_post_id)
|
guessed_post_id = params.require(:guessed_post_id)
|
||||||
correct_post_id = params[:correct_post_id].presence
|
correct_post_id = params[:correct_post_id].presence
|
||||||
@@ -11,7 +11,7 @@ class GekanatorGamesController < ApplicationController
|
|||||||
guessed_post_id:,
|
guessed_post_id:,
|
||||||
correct_post_id:,
|
correct_post_id:,
|
||||||
won: correct_post_id.present? && guessed_post_id.to_i == correct_post_id.to_i,
|
won: correct_post_id.present? && guessed_post_id.to_i == correct_post_id.to_i,
|
||||||
question_count: params.require(:question_count),
|
question_count: answers.length,
|
||||||
answers:)
|
answers:)
|
||||||
|
|
||||||
if game.save
|
if game.save
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
class CreateGekanatorGames < ActiveRecord::Migration[8.0]
|
class CreateGekanatorGames < ActiveRecord::Migration[8.0]
|
||||||
def change
|
def change
|
||||||
create_table :gekanator_games do |t|
|
create_table :gekanator_games do |t|
|
||||||
t.references :user, foreign_key: true
|
t.references :user, null: false, foreign_key: true
|
||||||
t.references :guessed_post, null: false, foreign_key: { to_table: :posts }
|
t.references :guessed_post, null: false, foreign_key: { to_table: :posts }
|
||||||
t.references :correct_post, foreign_key: { to_table: :posts }
|
t.references :correct_post, null: false, foreign_key: { to_table: :posts }
|
||||||
t.boolean :won, null: false
|
t.boolean :won, null: false
|
||||||
t.integer :question_count, null: false
|
t.integer :question_count, null: false
|
||||||
t.json :answers, null: false
|
t.json :answers, null: false
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
class RequireGekanatorGameUserAndCorrectPost < ActiveRecord::Migration[8.0]
|
|
||||||
def up
|
|
||||||
execute <<~SQL.squish
|
|
||||||
UPDATE gekanator_games
|
|
||||||
SET correct_post_id = guessed_post_id
|
|
||||||
WHERE correct_post_id IS NULL
|
|
||||||
SQL
|
|
||||||
|
|
||||||
change_column_null :gekanator_games, :user_id, false
|
|
||||||
change_column_null :gekanator_games, :correct_post_id, false
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
change_column_null :gekanator_games, :correct_post_id, true
|
|
||||||
change_column_null :gekanator_games, :user_id, true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
生成ファイル
+1
-20
@@ -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_06_08_000000) do
|
ActiveRecord::Schema[8.0].define(version: 2026_06_07_000000) 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
|
||||||
@@ -152,19 +152,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_06_08_000000) do
|
|||||||
t.index ["target_post_id"], name: "index_post_similarities_on_target_post_id"
|
t.index ["target_post_id"], name: "index_post_similarities_on_target_post_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "post_tag_sections", primary_key: ["post_id", "tag_id", "begin_ms"], charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
|
||||||
t.bigint "post_id", null: false
|
|
||||||
t.bigint "tag_id", null: false
|
|
||||||
t.integer "begin_ms", null: false
|
|
||||||
t.integer "end_ms", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.index ["post_id", "begin_ms"], name: "idx_post_tag_sections_post_id_begin_ms"
|
|
||||||
t.index ["tag_id"], name: "fk_rails_8be3847903"
|
|
||||||
t.check_constraint "`begin_ms` < `end_ms`", name: "chk_post_tag_sections_end_ms_after_begin_ms"
|
|
||||||
t.check_constraint "`begin_ms` >= 0", name: "chk_post_tag_sections_begin_ms_natural"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "post_tags", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
create_table "post_tags", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||||
t.bigint "post_id", null: false
|
t.bigint "post_id", null: false
|
||||||
t.bigint "tag_id", null: false
|
t.bigint "tag_id", null: false
|
||||||
@@ -215,11 +202,8 @@ ActiveRecord::Schema[8.0].define(version: 2026_06_08_000000) do
|
|||||||
t.datetime "original_created_before"
|
t.datetime "original_created_before"
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.integer "version_no", null: false
|
t.integer "version_no", null: false
|
||||||
t.integer "video_ms"
|
|
||||||
t.index ["uploaded_user_id"], name: "index_posts_on_uploaded_user_id"
|
t.index ["uploaded_user_id"], name: "index_posts_on_uploaded_user_id"
|
||||||
t.index ["url"], name: "index_posts_on_url", unique: true
|
t.index ["url"], name: "index_posts_on_url", unique: true
|
||||||
t.index ["video_ms", "id"], name: "idx_posts_video_ms_id"
|
|
||||||
t.check_constraint "(`video_ms` is null) or (`video_ms` > 0)", name: "chk_posts_video_ms_positive"
|
|
||||||
t.check_constraint "`version_no` > 0", name: "chk_posts_version_no_positive"
|
t.check_constraint "`version_no` > 0", name: "chk_posts_version_no_positive"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -370,7 +354,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_06_08_000000) do
|
|||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["expires_at"], name: "index_theatre_watching_users_on_expires_at"
|
t.index ["expires_at"], name: "index_theatre_watching_users_on_expires_at"
|
||||||
t.index ["theatre_id", "expires_at"], name: "idx_on_theatre_id_skip_expires_at_4c8de1dd42"
|
|
||||||
t.index ["theatre_id", "expires_at"], name: "index_theatre_watching_users_on_theatre_id_and_expires_at"
|
t.index ["theatre_id", "expires_at"], name: "index_theatre_watching_users_on_theatre_id_and_expires_at"
|
||||||
t.index ["theatre_id"], name: "index_theatre_watching_users_on_theatre_id"
|
t.index ["theatre_id"], name: "index_theatre_watching_users_on_theatre_id"
|
||||||
t.index ["user_id"], name: "index_theatre_watching_users_on_user_id"
|
t.index ["user_id"], name: "index_theatre_watching_users_on_user_id"
|
||||||
@@ -530,8 +513,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_06_08_000000) do
|
|||||||
add_foreign_key "post_implications", "posts", column: "parent_post_id"
|
add_foreign_key "post_implications", "posts", column: "parent_post_id"
|
||||||
add_foreign_key "post_similarities", "posts"
|
add_foreign_key "post_similarities", "posts"
|
||||||
add_foreign_key "post_similarities", "posts", column: "target_post_id"
|
add_foreign_key "post_similarities", "posts", column: "target_post_id"
|
||||||
add_foreign_key "post_tag_sections", "posts"
|
|
||||||
add_foreign_key "post_tag_sections", "tags"
|
|
||||||
add_foreign_key "post_tags", "posts"
|
add_foreign_key "post_tags", "posts"
|
||||||
add_foreign_key "post_tags", "tags"
|
add_foreign_key "post_tags", "tags"
|
||||||
add_foreign_key "post_tags", "users", column: "created_user_id"
|
add_foreign_key "post_tags", "users", column: "created_user_id"
|
||||||
|
|||||||
@@ -1,31 +1,32 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe 'Gekanator games API', type: :request do
|
RSpec.describe 'Gekanator games API', type: :request do
|
||||||
|
let!(:admin) { create_admin_user! }
|
||||||
let!(:user) { create_member_user! }
|
let!(:user) { create_member_user! }
|
||||||
let!(:guessed_post) { Post.create!(title: 'guess', url: 'https://example.com/guess') }
|
let!(:guessed_post) { Post.create!(title: 'guess', url: 'https://example.com/guess') }
|
||||||
let!(:correct_post) { Post.create!(title: 'correct', url: 'https://example.com/correct') }
|
let!(:correct_post) { Post.create!(title: 'correct', url: 'https://example.com/correct') }
|
||||||
|
|
||||||
describe 'POST /gekanator/games' do
|
describe 'POST /gekanator/games' do
|
||||||
it 'stores a won game' do
|
it 'stores a won game' do
|
||||||
sign_in_as user
|
sign_in_as admin
|
||||||
|
|
||||||
post '/gekanator/games', params: {
|
post '/gekanator/games', params: {
|
||||||
guessed_post_id: guessed_post.id,
|
guessed_post_id: guessed_post.id,
|
||||||
correct_post_id: guessed_post.id,
|
correct_post_id: guessed_post.id,
|
||||||
question_count: 3,
|
|
||||||
answers: [{ question_id: 'tag:1', answer: 'yes' }] }
|
answers: [{ question_id: 'tag:1', answer: 'yes' }] }
|
||||||
|
|
||||||
expect(response).to have_http_status(:created)
|
expect(response).to have_http_status(:created)
|
||||||
game = GekanatorGame.find(json['id'])
|
game = GekanatorGame.find(json['id'])
|
||||||
expect(game.user).to eq(user)
|
expect(game.user).to eq(admin)
|
||||||
expect(game.guessed_post).to eq(guessed_post)
|
expect(game.guessed_post).to eq(guessed_post)
|
||||||
expect(game.correct_post).to eq(guessed_post)
|
expect(game.correct_post).to eq(guessed_post)
|
||||||
expect(game.won).to eq(true)
|
expect(game.won).to eq(true)
|
||||||
|
expect(game.question_count).to eq(1)
|
||||||
expect(game.answers).to eq([{ 'question_id' => 'tag:1', 'answer' => 'yes' }])
|
expect(game.answers).to eq([{ 'question_id' => 'tag:1', 'answer' => 'yes' }])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'stores a lost game with the correct post' do
|
it 'stores a lost game with the correct post' do
|
||||||
sign_in_as user
|
sign_in_as admin
|
||||||
|
|
||||||
post '/gekanator/games', params: {
|
post '/gekanator/games', params: {
|
||||||
guessed_post_id: guessed_post.id,
|
guessed_post_id: guessed_post.id,
|
||||||
@@ -37,10 +38,11 @@ RSpec.describe 'Gekanator games API', type: :request do
|
|||||||
game = GekanatorGame.find(json['id'])
|
game = GekanatorGame.find(json['id'])
|
||||||
expect(game.correct_post).to eq(correct_post)
|
expect(game.correct_post).to eq(correct_post)
|
||||||
expect(game.won).to eq(false)
|
expect(game.won).to eq(false)
|
||||||
|
expect(game.question_count).to eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects a game without the correct post' do
|
it 'rejects a game without the correct post' do
|
||||||
sign_in_as user
|
sign_in_as admin
|
||||||
|
|
||||||
post '/gekanator/games', params: {
|
post '/gekanator/games', params: {
|
||||||
guessed_post_id: guessed_post.id,
|
guessed_post_id: guessed_post.id,
|
||||||
@@ -50,14 +52,24 @@ RSpec.describe 'Gekanator games API', type: :request do
|
|||||||
expect(response).to have_http_status(:unprocessable_entity)
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'requires a user' do
|
it 'returns not found without an admin user' do
|
||||||
post '/gekanator/games', params: {
|
post '/gekanator/games', params: {
|
||||||
guessed_post_id: guessed_post.id,
|
guessed_post_id: guessed_post.id,
|
||||||
correct_post_id: guessed_post.id,
|
correct_post_id: guessed_post.id,
|
||||||
question_count: 1,
|
|
||||||
answers: [] }
|
answers: [] }
|
||||||
|
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns not found for a non-admin user' do
|
||||||
|
sign_in_as user
|
||||||
|
|
||||||
|
post '/gekanator/games', params: {
|
||||||
|
guessed_post_id: guessed_post.id,
|
||||||
|
correct_post_id: guessed_post.id,
|
||||||
|
answers: [{ question_id: 'tag:1', answer: 'yes' }] }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ npm run lint
|
|||||||
|
|
||||||
If either command cannot be run or fails, report the exact command and failure.
|
If either command cannot be run or fails, report the exact command and failure.
|
||||||
|
|
||||||
|
Do not create, modify, or run tests unless the user explicitly asks for test
|
||||||
|
work. When the user asks for tests, keep working and rerun them until they
|
||||||
|
pass or the remaining failure is clearly blocked.
|
||||||
|
|
||||||
## TypeScript
|
## TypeScript
|
||||||
|
|
||||||
- TypeScript is strict. `tsconfig.app.json` enables `strict`,
|
- TypeScript is strict. `tsconfig.app.json` enables `strict`,
|
||||||
|
|||||||
+15
-2
@@ -40,7 +40,7 @@ import WikiHistoryPage from '@/pages/wiki/WikiHistoryPage'
|
|||||||
import WikiNewPage from '@/pages/wiki/WikiNewPage'
|
import WikiNewPage from '@/pages/wiki/WikiNewPage'
|
||||||
import WikiSearchPage from '@/pages/wiki/WikiSearchPage'
|
import WikiSearchPage from '@/pages/wiki/WikiSearchPage'
|
||||||
|
|
||||||
import type { Dispatch, FC, SetStateAction } from 'react'
|
import type { Dispatch, FC, ReactNode, SetStateAction } from 'react'
|
||||||
|
|
||||||
import type { User } from '@/types'
|
import type { User } from '@/types'
|
||||||
|
|
||||||
@@ -81,7 +81,10 @@ const RouteTransitionWrapper = ({ user, setUser }: {
|
|||||||
<Route path="/users/settings" element={<SettingPage user={user} setUser={setUser}/>}/>
|
<Route path="/users/settings" element={<SettingPage user={user} setUser={setUser}/>}/>
|
||||||
<Route path="/settings" element={<Navigate to="/users/settings" replace/>}/>
|
<Route path="/settings" element={<Navigate to="/users/settings" replace/>}/>
|
||||||
<Route path="/tos" element={<TOSPage/>}/>
|
<Route path="/tos" element={<TOSPage/>}/>
|
||||||
<Route path="/gekanator" element={<GekanatorPage/>}/>
|
<Route path="/gekanator" element={
|
||||||
|
<AdminOnly user={user}>
|
||||||
|
<GekanatorPage/>
|
||||||
|
</AdminOnly>}/>
|
||||||
<Route path="/more" element={<MorePage/>}/>
|
<Route path="/more" element={<MorePage/>}/>
|
||||||
<Route path="*" element={<NotFound/>}/>
|
<Route path="*" element={<NotFound/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
@@ -89,6 +92,16 @@ const RouteTransitionWrapper = ({ user, setUser }: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const AdminOnly = ({ user, children }: {
|
||||||
|
user: User | null
|
||||||
|
children: ReactNode }) => {
|
||||||
|
if (user?.role !== 'admin')
|
||||||
|
return <NotFound/>
|
||||||
|
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const PostDetailRoute = ({ user }: { user: User | null }) => {
|
const PostDetailRoute = ({ user }: { user: User | null }) => {
|
||||||
const location = useLocation ()
|
const location = useLocation ()
|
||||||
const key = location.pathname
|
const key = location.pathname
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ export const menuOutline = ({ tag, wikiId, user, pathName }: {
|
|||||||
{ name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
|
{ name: '履歴', to: `/wiki/changes?id=${ wikiId }`, visible: wikiPageFlg },
|
||||||
{ name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`, visible: wikiPageFlg }] },
|
{ name: '編輯', to: `/wiki/${ wikiId || wikiTitle }/edit`, visible: wikiPageFlg }] },
|
||||||
{ name: 'おたのしみ', visible: false, subMenu: [
|
{ name: 'おたのしみ', visible: false, subMenu: [
|
||||||
{ name: 'グカネータ', to: '/gekanator' },
|
|
||||||
{ name: '上映会 (β)', to: '/theatres/1' }] },
|
{ name: '上映会 (β)', to: '/theatres/1' }] },
|
||||||
{ name: 'ユーザ', to: '/users/settings', visible: false, subMenu: [
|
{ name: 'ユーザ', to: '/users/settings', visible: false, subMenu: [
|
||||||
{ name: '一覧', to: '/users', visible: false },
|
{ name: '一覧', to: '/users', visible: false },
|
||||||
|
|||||||
@@ -208,7 +208,6 @@ export const saveGekanatorGame = async ({
|
|||||||
await apiPost ('/gekanator/games', {
|
await apiPost ('/gekanator/games', {
|
||||||
guessed_post_id: guessedPostId,
|
guessed_post_id: guessedPostId,
|
||||||
correct_post_id: correctPostId,
|
correct_post_id: correctPostId,
|
||||||
question_count: answers.length,
|
|
||||||
answers: answers.map (answer => ({
|
answers: answers.map (answer => ({
|
||||||
question_id: answer.questionId,
|
question_id: answer.questionId,
|
||||||
question_text: answer.questionText,
|
question_text: answer.questionText,
|
||||||
|
|||||||
@@ -335,6 +335,8 @@ const chooseQuestion = ({
|
|||||||
candidates: { post: Post; score: number }[],
|
candidates: { post: Post; score: number }[],
|
||||||
) => {
|
) => {
|
||||||
const redundant = redundantSignatures (candidates)
|
const redundant = redundantSignatures (candidates)
|
||||||
|
const nonTagCount =
|
||||||
|
questions.filter (question => askedIds.has (question.id) && question.kind !== 'tag').length
|
||||||
|
|
||||||
return questionsToRank
|
return questionsToRank
|
||||||
.map (question => {
|
.map (question => {
|
||||||
@@ -348,7 +350,7 @@ const chooseQuestion = ({
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
const splitScore = Math.abs (candidates.length / 2 - yes)
|
const splitScore = Math.abs (candidates.length / 2 - yes)
|
||||||
const tagPenalty = question.kind === 'tag' ? 0 : 20
|
const tagPenalty = question.kind === 'tag' && nonTagCount < 4 ? 12 : 0
|
||||||
const minSide = candidates.length < 10 ? 1 : Math.max (3, candidates.length * .08)
|
const minSide = candidates.length < 10 ? 1 : Math.max (3, candidates.length * .08)
|
||||||
const narrowPenalty = yes < minSide || no < minSide ? candidates.length : 0
|
const narrowPenalty = yes < minSide || no < minSide ? candidates.length : 0
|
||||||
|
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする