From c9bae764f79a909f1a4345226d10f5f3640686b1 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Tue, 9 Sep 2025 23:01:06 +0900 Subject: [PATCH 1/2] =?UTF-8?q?#101=20=E3=83=9E=E3=82=A4=E3=82=B0=E3=83=AC?= =?UTF-8?q?=E3=81=A8=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD=E3=83=BC=E3=83=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/controllers/posts_controller.rb | 15 +++++++++------ ...0909075500_add_original_created_at_to_posts.rb | 6 ++++++ backend/db/schema.rb | 4 +++- 3 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 backend/db/migrate/20250909075500_add_original_created_at_to_posts.rb diff --git a/backend/app/controllers/posts_controller.rb b/backend/app/controllers/posts_controller.rb index ab451d5..831a301 100644 --- a/backend/app/controllers/posts_controller.rb +++ b/backend/app/controllers/posts_controller.rb @@ -8,8 +8,11 @@ class PostsController < ApplicationController limit = params[:limit].presence&.to_i cursor = params[:cursor].presence - q = filtered_posts.order(created_at: :desc) - q = q.where('posts.created_at < ?', Time.iso8601(cursor)) if cursor + created_at = ('COALESCE(posts.original_created_before - INTERVAL 1 SECOND,' + + 'posts.original_created_from,' + + 'posts.created_at)') + q = filtered_posts.order(Arel.sql("#{ created_at } DESC")) + q = q.where("#{ created_at } < ?", Time.iso8601(cursor)) if cursor posts = limit ? q.limit(limit + 1) : q @@ -20,14 +23,14 @@ class PostsController < ApplicationController end render json: { posts: posts.map { |post| - post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }).tap { |json| + post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }).tap do |json| json['thumbnail'] = if post.thumbnail.attached? rails_storage_proxy_url(post.thumbnail, only_path: false) else nil end - } + end }, next_cursor: } end @@ -39,7 +42,7 @@ class PostsController < ApplicationController render json: (post .as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }) - .merge(viewed: viewed)) + .merge(viewed:)) end # GET /posts/1 @@ -60,7 +63,7 @@ class PostsController < ApplicationController return head :forbidden unless current_user.member? # TODO: URL が正規のものがチェック,不正ならエラー - # TODO: title、URL は必須にする. + # TODO: URL は必須にする(タイトルは省略可). # TODO: サイトに応じて thumbnail_base 設定 title = params[:title] url = params[:url] diff --git a/backend/db/migrate/20250909075500_add_original_created_at_to_posts.rb b/backend/db/migrate/20250909075500_add_original_created_at_to_posts.rb new file mode 100644 index 0000000..161892c --- /dev/null +++ b/backend/db/migrate/20250909075500_add_original_created_at_to_posts.rb @@ -0,0 +1,6 @@ +class AddOriginalCreatedAtToPosts < ActiveRecord::Migration[8.0] + def change + add_column :posts, :original_created_from, :datetime, after: :created_at + add_column :posts, :original_created_before, :datetime, after: :original_created_from + end +end diff --git a/backend/db/schema.rb b/backend/db/schema.rb index f339414..4d5cfd6 100644 --- a/backend/db/schema.rb +++ b/backend/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_07_29_210600) do +ActiveRecord::Schema[8.0].define(version: 2025_09_09_075500) do create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false @@ -83,6 +83,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_29_210600) do t.bigint "parent_id" t.bigint "uploaded_user_id" t.datetime "created_at", null: false + t.datetime "original_created_from" + t.datetime "original_created_before" t.datetime "updated_at", null: false t.index ["parent_id"], name: "index_posts_on_parent_id" t.index ["uploaded_user_id"], name: "index_posts_on_uploaded_user_id" -- 2.34.1 From cafd55bf225331cac49f253c35ba8e5ce64e3428 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Mon, 15 Sep 2025 05:20:25 +0900 Subject: [PATCH 2/2] =?UTF-8?q?#101=20=E3=82=AA=E3=83=AA=E3=82=B8=E3=83=8A?= =?UTF-8?q?=E3=83=AB=E3=81=AE=E4=BD=9C=E6=88=90=E6=97=A5=E6=99=82=EF=BC=8C?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0=E3=81=AB=E3=83=95=E3=82=A3?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=83=89=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/controllers/posts_controller.rb | 9 +++- backend/app/models/post.rb | 18 ++++++++ frontend/src/components/PostEditForm.tsx | 38 +++++++++++++--- .../src/components/common/DateTimeField.tsx | 43 +++++++++++++++++++ frontend/src/pages/posts/PostNewPage.tsx | 36 +++++++++++++--- frontend/src/types.ts | 19 ++++---- 6 files changed, 142 insertions(+), 21 deletions(-) create mode 100644 frontend/src/components/common/DateTimeField.tsx diff --git a/backend/app/controllers/posts_controller.rb b/backend/app/controllers/posts_controller.rb index 831a301..6877c70 100644 --- a/backend/app/controllers/posts_controller.rb +++ b/backend/app/controllers/posts_controller.rb @@ -69,8 +69,11 @@ class PostsController < ApplicationController url = params[:url] thumbnail = params[:thumbnail] tag_names = params[:tags].to_s.split(' ') + original_created_from = params[:original_created_from] + original_created_before = params[:original_created_before] - post = Post.new(title:, url:, thumbnail_base: '', uploaded_user: current_user) + post = Post.new(title:, url:, thumbnail_base: '', uploaded_user: current_user, + original_created_from:, original_created_before:) post.thumbnail.attach(thumbnail) if post.save post.resized_thumbnail! @@ -103,10 +106,12 @@ class PostsController < ApplicationController title = params[:title] tag_names = params[:tags].to_s.split(' ') + original_created_from = params[:original_created_from] + original_created_before = params[:original_created_before] post = Post.find(params[:id].to_i) tags = post.tags.where(category: 'nico').to_a + Tag.normalise_tags(tag_names) - if post.update(title:, tags:) + if post.update(title:, tags:, original_created_from:, original_created_before:) render json: post.as_json(include: { tags: { only: [:id, :name, :category, :post_count] } }), status: :ok else diff --git a/backend/app/models/post.rb b/backend/app/models/post.rb index bdc136a..f32f753 100644 --- a/backend/app/models/post.rb +++ b/backend/app/models/post.rb @@ -15,6 +15,8 @@ class Post < ApplicationRecord foreign_key: :target_post_id has_one_attached :thumbnail + validate :validate_original_created_range + def as_json options = { } super(options).merge({ thumbnail: thumbnail.attached? ? Rails.application.routes.url_helpers.rails_blob_url( @@ -49,4 +51,20 @@ class Post < ApplicationRecord filename: 'resized_thumbnail.jpg', content_type: 'image/jpeg') end + + private + + def validate_original_created_range + f = original_created_from + b = original_created_before + return if f.blank? || b.blank? + + f = Time.zone.parse(f) if String === f + b = Time.zone.parse(b) if String === b + return if !(f) || !(b) + + if f >= b + errors.add :original_created_before, 'オリジナルの作成日時の順番がをかしぃです.' + end + end end diff --git a/frontend/src/components/PostEditForm.tsx b/frontend/src/components/PostEditForm.tsx index a254ce1..ad7ba82 100644 --- a/frontend/src/components/PostEditForm.tsx +++ b/frontend/src/components/PostEditForm.tsx @@ -3,6 +3,7 @@ import toCamel from 'camelcase-keys' import { useState } from 'react' import PostFormTagsArea from '@/components/PostFormTagsArea' +import DateTimeField from '@/components/common/DateTimeField' import Label from '@/components/common/Label' import { Button } from '@/components/ui/button' import { API_BASE_URL } from '@/config' @@ -16,6 +17,10 @@ type Props = { post: Post export default (({ post, onSave }: Props) => { + const [originalCreatedBefore, setOriginalCreatedBefore] = + useState (post.originalCreatedBefore) + const [originalCreatedFrom, setOriginalCreatedFrom] = + useState (post.originalCreatedFrom) const [title, setTitle] = useState (post.title) const [tags, setTags] = useState (post.tags .filter (t => t.category !== 'nico') @@ -23,13 +28,19 @@ export default (({ post, onSave }: Props) => { .join (' ')) const handleSubmit = async () => { - const res = await axios.put (`${ API_BASE_URL }/posts/${ post.id }`, { title, tags }, + const res = await axios.put ( + `${ API_BASE_URL }/posts/${ post.id }`, + { title, tags, + original_created_from: originalCreatedFrom, + original_created_before: originalCreatedBefore }, { headers: { 'Content-Type': 'multipart/form-data', - 'X-Transfer-Code': localStorage.getItem ('user_code') ?? '' } } ) + 'X-Transfer-Code': localStorage.getItem ('user_code') ?? '' } }) const data = toCamel (res.data as any, { deep: true }) as Post onSave ({ ...post, - title: data.title, - tags: data.tags } as Post) + title: data.title, + tags: data.tags, + originalCreatedFrom: data.originalCreatedFrom, + originalCreatedBefore: data.originalCreatedBefore } as Post) } return ( @@ -40,12 +51,29 @@ export default (({ post, onSave }: Props) => { setTitle (e.target.value)}/> + onChange={ev => setTitle (ev.target.value)}/> {/* タグ */} + {/* オリジナルの作成日時 */} +
+ +
+ + 以降 +
+
+ + より前 +
+
+ {/* 送信 */}