| @@ -5,24 +5,18 @@ require 'nokogiri' | |||||
| class PostsController < ApplicationController | class PostsController < ApplicationController | ||||
| # GET /posts | # GET /posts | ||||
| def index | def index | ||||
| if params[:tags].present? | |||||
| tag_names = params[:tags].split(',') | |||||
| match_type = params[:match] | |||||
| if match_type == 'any' | |||||
| posts = Post.joins(:tags).where(tags: { name: tag_names }).distinct | |||||
| else | |||||
| posts = Post.joins(:tags) | |||||
| tag_names.each do |tag| | |||||
| posts = posts.where(id: Post.joins(:tags).where(tags: { name: tag })) | |||||
| end | |||||
| posts = posts.distinct | |||||
| end | |||||
| else | |||||
| posts = Post.all | |||||
| end | |||||
| posts = filtered_posts | |||||
| render json: posts.as_json(include: { tags: { only: [:id, :name, :category] } }) | render json: posts.as_json(include: { tags: { only: [:id, :name, :category] } }) | ||||
| end | end | ||||
| def random | |||||
| post = filtered_posts.order('RAND()').first | |||||
| viewed = current_user&.viewed?(post) | |||||
| render json: (post | |||||
| .as_json(include: { tags: { only: [:id, :name, :category] } }) | |||||
| .merge(viewed: viewed)) | |||||
| end | |||||
| # GET /posts/1 | # GET /posts/1 | ||||
| def show | def show | ||||
| post = Post.includes(:tags).find(params[:id]) | post = Post.includes(:tags).find(params[:id]) | ||||
| @@ -86,4 +80,24 @@ class PostsController < ApplicationController | |||||
| # DELETE /posts/1 | # DELETE /posts/1 | ||||
| def destroy | def destroy | ||||
| end | end | ||||
| private | |||||
| def filtered_posts | |||||
| tag_names = params[:tags]&.split(',') | |||||
| match_type = params[:match] | |||||
| tag_names.present? ? filter_posts_by_tags(tag_names, match_type) : Post.all | |||||
| end | |||||
| def filter_posts_by_tags tag_names, match_type | |||||
| posts = Post.joins(:tags) | |||||
| if match_type == 'any' | |||||
| posts = posts.where(tags: { name: tag_names }).distinct | |||||
| else | |||||
| tag_names.each do |tag| | |||||
| posts = posts.where(id: Post.joins(:tags).where(tags: { name: tag })) | |||||
| end | |||||
| end | |||||
| posts.distinct | |||||
| end | |||||
| end | end | ||||
| @@ -1,5 +1,6 @@ | |||||
| Rails.application.routes.draw do | Rails.application.routes.draw do | ||||
| get 'tags/autocomplete', to: 'tags#autocomplete' | get 'tags/autocomplete', to: 'tags#autocomplete' | ||||
| get 'posts/random', to: 'posts#random' | |||||
| post 'posts/:id/viewed', to: 'posts#viewed' | post 'posts/:id/viewed', to: 'posts#viewed' | ||||
| delete 'posts/:id/viewed', to: 'posts#unviewed' | delete 'posts/:id/viewed', to: 'posts#unviewed' | ||||
| get 'preview/title', to: 'preview#title' | get 'preview/title', to: 'preview#title' | ||||
| @@ -1,10 +1,11 @@ | |||||
| import React, { useEffect, useState } from 'react' | |||||
| import axios from 'axios' | import axios from 'axios' | ||||
| import { Link, useParams } from 'react-router-dom' | |||||
| import { API_BASE_URL } from '@/config' | |||||
| import React, { useEffect, useState } from 'react' | |||||
| import { Link, useNavigate, useParams } from 'react-router-dom' | |||||
| import TagSearch from '@/components/TagSearch' | import TagSearch from '@/components/TagSearch' | ||||
| import SidebarComponent from '@/components/layout/SidebarComponent' | |||||
| import SectionTitle from '@/components/common/SectionTitle' | import SectionTitle from '@/components/common/SectionTitle' | ||||
| import SidebarComponent from '@/components/layout/SidebarComponent' | |||||
| import { API_BASE_URL } from '@/config' | |||||
| import type { Post, Tag } from '@/types' | import type { Post, Tag } from '@/types' | ||||
| @@ -14,6 +15,8 @@ type Props = { posts: Post[] } | |||||
| export default ({ posts }: Props) => { | export default ({ posts }: Props) => { | ||||
| const navigate = useNavigate () | |||||
| const [tags, setTags] = useState<TagByCategory> ({ }) | const [tags, setTags] = useState<TagByCategory> ({ }) | ||||
| const [tagsCounts, setTagsCounts] = useState<{ [key: number]: number }> ({ }) | const [tagsCounts, setTagsCounts] = useState<{ [key: number]: number }> ({ }) | ||||
| @@ -56,6 +59,13 @@ export default ({ posts }: Props) => { | |||||
| </>))} | </>))} | ||||
| </ul> | </ul> | ||||
| <SectionTitle>関聯</SectionTitle> | <SectionTitle>関聯</SectionTitle> | ||||
| <Link>ランダム</Link> | |||||
| <a href="#" | |||||
| onClick={ev => { | |||||
| ev.preventDefault () | |||||
| void (axios.get (`${ API_BASE_URL }/posts/random`) | |||||
| .then (res => navigate (`/posts/${ res.data.id }`))) | |||||
| }}> | |||||
| ランダム | |||||
| </a> | |||||
| </SidebarComponent>) | </SidebarComponent>) | ||||
| } | } | ||||
| @@ -2,7 +2,7 @@ import axios from 'axios' | |||||
| import toCamel from 'camelcase-keys' | import toCamel from 'camelcase-keys' | ||||
| import { useEffect, useState } from 'react' | import { useEffect, useState } from 'react' | ||||
| import { Helmet } from 'react-helmet' | import { Helmet } from 'react-helmet' | ||||
| import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' | |||||
| import { Link, useLocation, useNavigate, useParams } from 'react-router-dom' | |||||
| import WikiBody from '@/components/WikiBody' | import WikiBody from '@/components/WikiBody' | ||||
| import PageTitle from '@/components/common/PageTitle' | import PageTitle from '@/components/common/PageTitle' | ||||