From 6486be77c283052f173b9020006e1ddc67a95a0f Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Wed, 7 Jan 2026 23:13:50 +0900 Subject: [PATCH 01/23] refactor: remove updated_at field from post schema and related definitions --- packages/backend_app/src/apps/posts/dto.ts | 6 ------ packages/backend_app/src/db/field.ts | 1 - packages/backend_app/src/db/schema.ts | 11 +++++++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/backend_app/src/apps/posts/dto.ts b/packages/backend_app/src/apps/posts/dto.ts index 095d674a..648908e6 100644 --- a/packages/backend_app/src/apps/posts/dto.ts +++ b/packages/backend_app/src/apps/posts/dto.ts @@ -25,12 +25,6 @@ const postSelectSchema = createSelectSchema(postsTable, { example: "2025-01-01T00:00:00Z", format: "date-time", }), - updated_at: (schema) => - schema.openapi({ - description: "The date and time the post was updated", - example: "2025-01-01T00:00:00Z", - format: "date-time", - }), }) .pick(postPublicFields) .strict() diff --git a/packages/backend_app/src/db/field.ts b/packages/backend_app/src/db/field.ts index 73495d1e..b17f9e59 100644 --- a/packages/backend_app/src/db/field.ts +++ b/packages/backend_app/src/db/field.ts @@ -9,7 +9,6 @@ export const postPublicFieldDefs = [ "public_id", "content", "created_at", - "updated_at", ] satisfies (keyof typeof postsTable.$inferSelect)[]; const createFieldsFromDefs = ( diff --git a/packages/backend_app/src/db/schema.ts b/packages/backend_app/src/db/schema.ts index 414b10e9..2a578fcc 100644 --- a/packages/backend_app/src/db/schema.ts +++ b/packages/backend_app/src/db/schema.ts @@ -8,10 +8,6 @@ const primaryKeys = () => ({ const timestamps = { created_at: timestamp().defaultNow().notNull(), - updated_at: timestamp() - .defaultNow() - .notNull() - .$onUpdate(() => new Date()), }; export const usersTable = pgTable("users", { @@ -36,3 +32,10 @@ export const postsRelations = relations(postsTable, ({ one }) => ({ references: [usersTable.id], }), })); + +export const postLogsTable = pgTable("post_logs", { + ...primaryKeys(), + user_id: integer().notNull(), + content: text().notNull(), + ...timestamps, +}); From 5bf2beb09b4b49384b929273cb538106b8560caa Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Wed, 7 Jan 2026 23:30:16 +0900 Subject: [PATCH 02/23] feat: enhance post creation and logging functionality - Added a new post_logs table to track post creation details. - Updated post creation logic to include a public_id and log the post details upon creation. - Modified post update logic to ensure proper handling of public_id for querying and deletion. --- packages/backend_app/src/apps/posts/index.ts | 49 ++++++++++++++------ packages/backend_app/src/db/schema.ts | 5 +- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/packages/backend_app/src/apps/posts/index.ts b/packages/backend_app/src/apps/posts/index.ts index 0ad81408..e175a765 100644 --- a/packages/backend_app/src/apps/posts/index.ts +++ b/packages/backend_app/src/apps/posts/index.ts @@ -2,7 +2,7 @@ import { desc, eq } from "drizzle-orm"; import { HTTPException } from "hono/http-exception"; import { db } from "../../db"; import { postWithUserQuery } from "../../db/query"; -import { postsTable } from "../../db/schema"; +import { postLogsTable, postsTable } from "../../db/schema"; import { userMiddleware } from "../../middlewares/user"; import { createApp } from "../factory"; import { @@ -35,19 +35,33 @@ const routes = postApp .openapi(postPostRoute, async (c) => { const { content } = c.req.valid("json"); const user = c.get("user"); - const result = await db - .insert(postsTable) - .values({ user_id: user.id, content }) - .returning(); - const post = result[0]; - if (!post) - throw new HTTPException(500, { - message: "Failed to create post", + const result = await db.transaction(async (tx) => { + const public_id = crypto.randomUUID(); + const post = ( + await tx + .insert(postsTable) + .values({ public_id, user_id: user.id, content }) + .returning() + )[0]; + if (!post) + throw new HTTPException(500, { + message: "Failed to create post", + }); + + await tx.insert(postLogsTable).values({ + public_id: post.public_id, + user_id: post.user_id, + content: post.content, + created_at: post.created_at, }); + + return post; + }); + const response = { post: await db.query.postsTable.findFirst({ ...postWithUserQuery, - where: eq(postsTable.id, post.id), + where: eq(postsTable.id, result.id), }), }; return c.json(postPostResponseSchema.parse(response), 200); @@ -58,7 +72,7 @@ const routes = postApp const { content } = c.req.valid("json"); const user = c.get("user"); - const post = await db.transaction(async (tx) => { + await db.transaction(async (tx) => { const target = ( await tx .select() @@ -78,10 +92,15 @@ const routes = postApp }); } + await tx.delete(postsTable).where(eq(postsTable.public_id, public_id)); + const results = await tx - .update(postsTable) - .set({ content }) - .where(eq(postsTable.public_id, public_id)) + .insert(postsTable) + .values({ + public_id: target.public_id, + user_id: user.id, + content, + }) .returning(); const result = results[0]; @@ -95,7 +114,7 @@ const routes = postApp const response = { post: await db.query.postsTable.findFirst({ ...postWithUserQuery, - where: eq(postsTable.id, post.id), + where: eq(postsTable.public_id, public_id), }), }; diff --git a/packages/backend_app/src/db/schema.ts b/packages/backend_app/src/db/schema.ts index 2a578fcc..8f4d20a4 100644 --- a/packages/backend_app/src/db/schema.ts +++ b/packages/backend_app/src/db/schema.ts @@ -3,7 +3,7 @@ import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; const primaryKeys = () => ({ id: integer().primaryKey().generatedAlwaysAsIdentity(), // TODO: uuid v7 を検討 - public_id: uuid().unique().notNull().defaultRandom(), // uuid v4 + public_id: uuid().unique().notNull(), // uuid v4 }); const timestamps = { @@ -34,7 +34,8 @@ export const postsRelations = relations(postsTable, ({ one }) => ({ })); export const postLogsTable = pgTable("post_logs", { - ...primaryKeys(), + id: integer().primaryKey(), + public_id: uuid().notNull(), user_id: integer().notNull(), content: text().notNull(), ...timestamps, From ca3606dbcfb04558f716f9983c355e7dbc0a4860 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Wed, 7 Jan 2026 23:31:46 +0900 Subject: [PATCH 03/23] feat: include post ID in logging during post creation - Added the post ID to the values inserted into the post_logs table to enhance tracking of post creation details. --- packages/backend_app/src/apps/posts/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend_app/src/apps/posts/index.ts b/packages/backend_app/src/apps/posts/index.ts index e175a765..f40acdee 100644 --- a/packages/backend_app/src/apps/posts/index.ts +++ b/packages/backend_app/src/apps/posts/index.ts @@ -49,6 +49,7 @@ const routes = postApp }); await tx.insert(postLogsTable).values({ + id: post.id, public_id: post.public_id, user_id: post.user_id, content: post.content, From a9ae53fa1c97b4943182858ee812732cb24b6dfe Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 00:04:27 +0900 Subject: [PATCH 04/23] feat: add public_id to user creation and tests - Introduced public_id generation during user creation in the userApp. - Updated related tests to include public_id in user insertion for consistency. --- packages/backend_app/src/apps/user/index.test.ts | 2 ++ packages/backend_app/src/apps/user/index.ts | 3 ++- packages/backend_app/src/middlewares/user/index.test.ts | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/backend_app/src/apps/user/index.test.ts b/packages/backend_app/src/apps/user/index.test.ts index 49be2303..03a0ae9f 100644 --- a/packages/backend_app/src/apps/user/index.test.ts +++ b/packages/backend_app/src/apps/user/index.test.ts @@ -49,6 +49,7 @@ describe("userApp", () => { describe("when supabase_uid is already registered", () => { beforeEach(async () => { await db.insert(usersTable).values({ + public_id: crypto.randomUUID(), // FIXME supabase_uid: supabaseUid, display_name: faker.person.fullName(), }); @@ -85,6 +86,7 @@ describe("userApp", () => { await db .insert(usersTable) .values({ + public_id: crypto.randomUUID(), // FIXME supabase_uid: supabaseUid, display_name: faker.person.fullName(), }) diff --git a/packages/backend_app/src/apps/user/index.ts b/packages/backend_app/src/apps/user/index.ts index f2ff610d..d32b0271 100644 --- a/packages/backend_app/src/apps/user/index.ts +++ b/packages/backend_app/src/apps/user/index.ts @@ -17,9 +17,10 @@ const routes = userApp .openapi(signupRoute, async (c) => { const { sub: supabase_uid } = c.get("jwtClaims"); const { display_name } = await c.req.valid("json"); + const public_id = crypto.randomUUID(); await db .insert(usersTable) - .values({ supabase_uid, display_name }) + .values({ public_id, supabase_uid, display_name }) .onConflictDoNothing({ target: usersTable.supabase_uid }); return c.json(null, 200); }) diff --git a/packages/backend_app/src/middlewares/user/index.test.ts b/packages/backend_app/src/middlewares/user/index.test.ts index d7567cb2..277931e0 100644 --- a/packages/backend_app/src/middlewares/user/index.test.ts +++ b/packages/backend_app/src/middlewares/user/index.test.ts @@ -33,6 +33,7 @@ describe("userMiddleware", () => { describe("when user is found", () => { beforeEach(async () => { await db.insert(usersTable).values({ + public_id: crypto.randomUUID(), // FIXME supabase_uid: supabaseUid, display_name: faker.person.fullName(), }); From 95ee2af72cf853f3903b7ca1840cf839ca8abfd5 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 00:12:30 +0900 Subject: [PATCH 05/23] feat: update post schema and tests for user_id handling - Modified the post update schema to require the content field. - Removed the updated_at field assertions from tests to reflect schema changes. - Updated the user_id assignment in post creation logic for consistency with the new structure. --- packages/backend_app/src/apps/posts/dto.ts | 8 +++++--- packages/backend_app/src/apps/posts/index.test.ts | 6 ------ packages/backend_app/src/apps/posts/index.ts | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/backend_app/src/apps/posts/dto.ts b/packages/backend_app/src/apps/posts/dto.ts index 648908e6..c5077a13 100644 --- a/packages/backend_app/src/apps/posts/dto.ts +++ b/packages/backend_app/src/apps/posts/dto.ts @@ -62,9 +62,11 @@ export const updatePostRequestSchema = createUpdateSchema(postsTable, { description: "The content of the post", example: "test", }), -}).pick({ - content: true, -}); +}) + .pick({ + content: true, + }) + .required(); export const updatePostResponseSchema = postPostResponseSchema; diff --git a/packages/backend_app/src/apps/posts/index.test.ts b/packages/backend_app/src/apps/posts/index.test.ts index 3f986ca0..17e9663c 100644 --- a/packages/backend_app/src/apps/posts/index.test.ts +++ b/packages/backend_app/src/apps/posts/index.test.ts @@ -77,7 +77,6 @@ describe("postsApp", () => { public_id: expect.any(String), content: "test2", created_at: expect.any(String), - updated_at: expect.any(String), user: { public_id: user.public_id, display_name: user.display_name, @@ -87,7 +86,6 @@ describe("postsApp", () => { public_id: expect.any(String), content: "test", created_at: expect.any(String), - updated_at: expect.any(String), user: { public_id: user.public_id, display_name: user.display_name, @@ -133,7 +131,6 @@ describe("postsApp", () => { const post = posts[0]; if (!post) throw new Error("post is not found"); expect(post.content).toBe(content); - expect(post.created_at).toEqual(post.updated_at); }); }); @@ -209,9 +206,6 @@ describe("postsApp", () => { const post = posts[0]; if (!post) throw new Error("post is not found"); expect(post.content).toBe(content); - expect(post.created_at.getTime()).toBeLessThan( - post.updated_at.getTime(), - ); }); }); diff --git a/packages/backend_app/src/apps/posts/index.ts b/packages/backend_app/src/apps/posts/index.ts index f40acdee..6c0c07fd 100644 --- a/packages/backend_app/src/apps/posts/index.ts +++ b/packages/backend_app/src/apps/posts/index.ts @@ -99,7 +99,7 @@ const routes = postApp .insert(postsTable) .values({ public_id: target.public_id, - user_id: user.id, + user_id: target.user_id, content, }) .returning(); From 03c240b3634135b492b9794c38567aa85a6fb9ab Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 00:19:23 +0900 Subject: [PATCH 06/23] fix: remove FIXME comments for public_id generation in user tests and logic - Cleaned up the user creation logic by directly generating public_id during insertion. - Updated related test files to remove FIXME comments and ensure consistency in public_id handling. --- packages/backend_app/src/apps/user/index.test.ts | 4 ++-- packages/backend_app/src/apps/user/index.ts | 3 +-- packages/backend_app/src/middlewares/user/index.test.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/backend_app/src/apps/user/index.test.ts b/packages/backend_app/src/apps/user/index.test.ts index 03a0ae9f..86a81821 100644 --- a/packages/backend_app/src/apps/user/index.test.ts +++ b/packages/backend_app/src/apps/user/index.test.ts @@ -49,7 +49,7 @@ describe("userApp", () => { describe("when supabase_uid is already registered", () => { beforeEach(async () => { await db.insert(usersTable).values({ - public_id: crypto.randomUUID(), // FIXME + public_id: crypto.randomUUID(), supabase_uid: supabaseUid, display_name: faker.person.fullName(), }); @@ -86,7 +86,7 @@ describe("userApp", () => { await db .insert(usersTable) .values({ - public_id: crypto.randomUUID(), // FIXME + public_id: crypto.randomUUID(), supabase_uid: supabaseUid, display_name: faker.person.fullName(), }) diff --git a/packages/backend_app/src/apps/user/index.ts b/packages/backend_app/src/apps/user/index.ts index d32b0271..570a3de6 100644 --- a/packages/backend_app/src/apps/user/index.ts +++ b/packages/backend_app/src/apps/user/index.ts @@ -17,10 +17,9 @@ const routes = userApp .openapi(signupRoute, async (c) => { const { sub: supabase_uid } = c.get("jwtClaims"); const { display_name } = await c.req.valid("json"); - const public_id = crypto.randomUUID(); await db .insert(usersTable) - .values({ public_id, supabase_uid, display_name }) + .values({ public_id: crypto.randomUUID(), supabase_uid, display_name }) .onConflictDoNothing({ target: usersTable.supabase_uid }); return c.json(null, 200); }) diff --git a/packages/backend_app/src/middlewares/user/index.test.ts b/packages/backend_app/src/middlewares/user/index.test.ts index 277931e0..454fdef5 100644 --- a/packages/backend_app/src/middlewares/user/index.test.ts +++ b/packages/backend_app/src/middlewares/user/index.test.ts @@ -33,7 +33,7 @@ describe("userMiddleware", () => { describe("when user is found", () => { beforeEach(async () => { await db.insert(usersTable).values({ - public_id: crypto.randomUUID(), // FIXME + public_id: crypto.randomUUID(), supabase_uid: supabaseUid, display_name: faker.person.fullName(), }); From 4207aa1fc0333594172af62ee644352fff9da5d9 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 00:21:53 +0900 Subject: [PATCH 07/23] refactor: standardize public_id generation in user and post tests - Replaced crypto.randomUUID() with faker.string.uuid() for public_id generation in user and post test files. - Ensured consistency in public_id handling across user and post creation logic in tests. --- packages/backend_app/src/apps/posts/index.test.ts | 6 ++++-- packages/backend_app/src/apps/user/index.test.ts | 4 ++-- packages/backend_app/src/middlewares/user/index.test.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/backend_app/src/apps/posts/index.test.ts b/packages/backend_app/src/apps/posts/index.test.ts index 17e9663c..ebb9dafa 100644 --- a/packages/backend_app/src/apps/posts/index.test.ts +++ b/packages/backend_app/src/apps/posts/index.test.ts @@ -21,6 +21,7 @@ describe("postsApp", () => { await db .insert(usersTable) .values({ + public_id: faker.string.uuid(), supabase_uid: supabaseUid, display_name: faker.person.fullName(), }) @@ -33,6 +34,7 @@ describe("postsApp", () => { await db .insert(usersTable) .values({ + public_id: crypto.randomUUID(), supabase_uid: faker.string.uuid(), display_name: faker.person.fullName(), }) @@ -60,10 +62,10 @@ describe("postsApp", () => { beforeEach(async () => { await db .insert(postsTable) - .values({ user_id: user.id, content: "test" }); + .values({ public_id: faker.string.uuid(), user_id: user.id, content: "test" }); await db .insert(postsTable) - .values({ user_id: user.id, content: "test2" }); + .values({ public_id: faker.string.uuid(), user_id: user.id, content: "test2" }); }); it("should return 200 Response", async () => { diff --git a/packages/backend_app/src/apps/user/index.test.ts b/packages/backend_app/src/apps/user/index.test.ts index 86a81821..acb3c8fd 100644 --- a/packages/backend_app/src/apps/user/index.test.ts +++ b/packages/backend_app/src/apps/user/index.test.ts @@ -49,7 +49,7 @@ describe("userApp", () => { describe("when supabase_uid is already registered", () => { beforeEach(async () => { await db.insert(usersTable).values({ - public_id: crypto.randomUUID(), + public_id: faker.string.uuid(), supabase_uid: supabaseUid, display_name: faker.person.fullName(), }); @@ -86,7 +86,7 @@ describe("userApp", () => { await db .insert(usersTable) .values({ - public_id: crypto.randomUUID(), + public_id: faker.string.uuid(), supabase_uid: supabaseUid, display_name: faker.person.fullName(), }) diff --git a/packages/backend_app/src/middlewares/user/index.test.ts b/packages/backend_app/src/middlewares/user/index.test.ts index 454fdef5..3b09bace 100644 --- a/packages/backend_app/src/middlewares/user/index.test.ts +++ b/packages/backend_app/src/middlewares/user/index.test.ts @@ -33,7 +33,7 @@ describe("userMiddleware", () => { describe("when user is found", () => { beforeEach(async () => { await db.insert(usersTable).values({ - public_id: crypto.randomUUID(), + public_id: faker.string.uuid(), supabase_uid: supabaseUid, display_name: faker.person.fullName(), }); From 55d631abac17cf951cba9e106082adaa10ae4a78 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 00:25:54 +0900 Subject: [PATCH 08/23] refactor: update post schema to remove updated_at field and adjust test cases - Removed the updated_at field from the post schema and updated the required fields accordingly. - Modified test cases to reflect the changes in the post schema, ensuring consistency in content field requirements. --- packages/backend_app/openapi.json | 11 +++-------- packages/backend_app/src/apps/posts/index.test.ts | 12 ++++++++++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/backend_app/openapi.json b/packages/backend_app/openapi.json index 907a542c..9f1d76da 100644 --- a/packages/backend_app/openapi.json +++ b/packages/backend_app/openapi.json @@ -56,15 +56,9 @@ "format": "date-time", "description": "The date and time the post was created", "example": "2025-01-01T00:00:00Z" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "The date and time the post was updated", - "example": "2025-01-01T00:00:00Z" } }, - "required": ["public_id", "content", "created_at", "updated_at"], + "required": ["public_id", "content", "created_at"], "additionalProperties": false } }, @@ -752,7 +746,8 @@ "description": "The content of the post", "example": "test" } - } + }, + "required": ["content"] } } } diff --git a/packages/backend_app/src/apps/posts/index.test.ts b/packages/backend_app/src/apps/posts/index.test.ts index ebb9dafa..fa543d42 100644 --- a/packages/backend_app/src/apps/posts/index.test.ts +++ b/packages/backend_app/src/apps/posts/index.test.ts @@ -62,10 +62,18 @@ describe("postsApp", () => { beforeEach(async () => { await db .insert(postsTable) - .values({ public_id: faker.string.uuid(), user_id: user.id, content: "test" }); + .values({ + public_id: faker.string.uuid(), + user_id: user.id, + content: "test", + }); await db .insert(postsTable) - .values({ public_id: faker.string.uuid(), user_id: user.id, content: "test2" }); + .values({ + public_id: faker.string.uuid(), + user_id: user.id, + content: "test2", + }); }); it("should return 200 Response", async () => { From 943db312b80f58f196e6edce4c30aae55cfb3844 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 00:27:16 +0900 Subject: [PATCH 09/23] refactor: standardize public_id generation in post tests - Replaced crypto.randomUUID() with faker.string.uuid() for public_id generation in post test files. - Ensured consistency in public_id handling across post creation logic in tests. --- packages/backend_app/src/apps/posts/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend_app/src/apps/posts/index.test.ts b/packages/backend_app/src/apps/posts/index.test.ts index fa543d42..336172ce 100644 --- a/packages/backend_app/src/apps/posts/index.test.ts +++ b/packages/backend_app/src/apps/posts/index.test.ts @@ -34,7 +34,7 @@ describe("postsApp", () => { await db .insert(usersTable) .values({ - public_id: crypto.randomUUID(), + public_id: faker.string.uuid(), supabase_uid: faker.string.uuid(), display_name: faker.person.fullName(), }) From f6655c15f17bc8f21f45e328e9b4853088b8f2f0 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 00:32:06 +0900 Subject: [PATCH 10/23] feat: log post details upon update in post_logs table - Enhanced the post update logic to insert relevant post details into the post_logs table after a successful update. - This addition improves tracking and auditing of post updates by capturing essential information such as public_id and content. --- packages/backend_app/src/apps/posts/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/backend_app/src/apps/posts/index.ts b/packages/backend_app/src/apps/posts/index.ts index 6c0c07fd..e234b7cc 100644 --- a/packages/backend_app/src/apps/posts/index.ts +++ b/packages/backend_app/src/apps/posts/index.ts @@ -109,7 +109,14 @@ const routes = postApp throw new HTTPException(500, { message: "Failed to update post", }); - return result; + + await tx.insert(postLogsTable).values({ + id: result.id, + public_id: result.public_id, + user_id: result.user_id, + content: result.content, + created_at: result.created_at, + }); }); const response = { From 044e5c8f261cbc0812f16a276c73766b7aa36af1 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 00:35:45 +0900 Subject: [PATCH 11/23] test: integrate post_logs verification in post tests - Updated post test cases to include assertions for the post_logs table, ensuring that post details are logged correctly upon post creation and updates. - Enhanced the test logic to verify the presence and correctness of logged post information, including public_id, user_id, and content. --- .../backend_app/src/apps/posts/index.test.ts | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/packages/backend_app/src/apps/posts/index.test.ts b/packages/backend_app/src/apps/posts/index.test.ts index 336172ce..ba571f2c 100644 --- a/packages/backend_app/src/apps/posts/index.test.ts +++ b/packages/backend_app/src/apps/posts/index.test.ts @@ -4,7 +4,7 @@ import type { ClientRequestOptions } from "hono/client"; import { testClient } from "hono/testing"; import { app } from "../../apps"; import { db } from "../../db"; -import { postsTable, usersTable } from "../../db/schema"; +import { postLogsTable, postsTable, usersTable } from "../../db/schema"; import { supabaseUid } from "../../test/supabase"; describe("postsApp", () => { @@ -60,20 +60,16 @@ describe("postsApp", () => { describe("when there are some posts", () => { beforeEach(async () => { - await db - .insert(postsTable) - .values({ - public_id: faker.string.uuid(), - user_id: user.id, - content: "test", - }); - await db - .insert(postsTable) - .values({ - public_id: faker.string.uuid(), - user_id: user.id, - content: "test2", - }); + await db.insert(postsTable).values({ + public_id: faker.string.uuid(), + user_id: user.id, + content: "test", + }); + await db.insert(postsTable).values({ + public_id: faker.string.uuid(), + user_id: user.id, + content: "test2", + }); }); it("should return 200 Response", async () => { @@ -141,6 +137,14 @@ describe("postsApp", () => { const post = posts[0]; if (!post) throw new Error("post is not found"); expect(post.content).toBe(content); + + const postLogs = await db.select().from(postLogsTable); + expect(postLogs).toHaveLength(1); + const postLog = postLogs[0]; + if (!postLog) throw new Error("postLog is not found"); + expect(postLog.public_id).toBe(post.public_id); + expect(postLog.user_id).toBe(post.user_id); + expect(postLog.content).toBe(content); }); }); @@ -216,6 +220,16 @@ describe("postsApp", () => { const post = posts[0]; if (!post) throw new Error("post is not found"); expect(post.content).toBe(content); + expect(post.public_id).toBe(public_id); + + const postLogs = await db.select().from(postLogsTable); + expect(postLogs).toHaveLength(1); + const postLog = postLogs[0]; + if (!postLog) throw new Error("postLog is not found"); + expect(postLog.id).toBe(post.id); + expect(postLog.public_id).toBe(post.public_id); + expect(postLog.user_id).toBe(post.user_id); + expect(postLog.content).toBe(content); }); }); From d7f7d2eb408c725f8cddbe4c78aea89863135bcb Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 16:19:54 +0900 Subject: [PATCH 12/23] refactor: streamline public_id generation in post creation logic - Updated the post creation logic to generate public_id directly within the values insertion, ensuring consistency and clarity in the code. - This change simplifies the transaction process by removing the separate declaration of public_id. --- packages/backend_app/src/apps/posts/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/backend_app/src/apps/posts/index.ts b/packages/backend_app/src/apps/posts/index.ts index e234b7cc..c84be981 100644 --- a/packages/backend_app/src/apps/posts/index.ts +++ b/packages/backend_app/src/apps/posts/index.ts @@ -36,11 +36,10 @@ const routes = postApp const { content } = c.req.valid("json"); const user = c.get("user"); const result = await db.transaction(async (tx) => { - const public_id = crypto.randomUUID(); const post = ( await tx .insert(postsTable) - .values({ public_id, user_id: user.id, content }) + .values({ public_id: crypto.randomUUID(), user_id: user.id, content }) .returning() )[0]; if (!post) From 2a055a938ff84580854f6a9fbc55064ff5689b65 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 16:58:08 +0900 Subject: [PATCH 13/23] feat: add updated_at field to post schema and tests - Introduced the updated_at field in the post schema to track the last modification timestamp. - Updated relevant test cases to include assertions for the updated_at field, ensuring accurate verification of post details. - Adjusted post creation logic to set the updated_at field upon post creation. --- packages/backend_app/src/apps/posts/dto.ts | 6 ++++++ packages/backend_app/src/apps/posts/index.test.ts | 2 ++ packages/backend_app/src/apps/posts/index.ts | 1 + packages/backend_app/src/db/field.ts | 1 + packages/backend_app/src/db/schema.ts | 1 + 5 files changed, 11 insertions(+) diff --git a/packages/backend_app/src/apps/posts/dto.ts b/packages/backend_app/src/apps/posts/dto.ts index c5077a13..419054a4 100644 --- a/packages/backend_app/src/apps/posts/dto.ts +++ b/packages/backend_app/src/apps/posts/dto.ts @@ -25,6 +25,12 @@ const postSelectSchema = createSelectSchema(postsTable, { example: "2025-01-01T00:00:00Z", format: "date-time", }), + updated_at: (schema) => + schema.openapi({ + description: "The date and time the post was updated", + example: "2025-01-01T00:00:00Z", + format: "date-time", + }), }) .pick(postPublicFields) .strict() diff --git a/packages/backend_app/src/apps/posts/index.test.ts b/packages/backend_app/src/apps/posts/index.test.ts index ba571f2c..d986ef09 100644 --- a/packages/backend_app/src/apps/posts/index.test.ts +++ b/packages/backend_app/src/apps/posts/index.test.ts @@ -83,6 +83,7 @@ describe("postsApp", () => { public_id: expect.any(String), content: "test2", created_at: expect.any(String), + updated_at: expect.any(String), user: { public_id: user.public_id, display_name: user.display_name, @@ -92,6 +93,7 @@ describe("postsApp", () => { public_id: expect.any(String), content: "test", created_at: expect.any(String), + updated_at: expect.any(String), user: { public_id: user.public_id, display_name: user.display_name, diff --git a/packages/backend_app/src/apps/posts/index.ts b/packages/backend_app/src/apps/posts/index.ts index c84be981..e497a343 100644 --- a/packages/backend_app/src/apps/posts/index.ts +++ b/packages/backend_app/src/apps/posts/index.ts @@ -100,6 +100,7 @@ const routes = postApp public_id: target.public_id, user_id: target.user_id, content, + updated_at: new Date(), }) .returning(); diff --git a/packages/backend_app/src/db/field.ts b/packages/backend_app/src/db/field.ts index b17f9e59..73495d1e 100644 --- a/packages/backend_app/src/db/field.ts +++ b/packages/backend_app/src/db/field.ts @@ -9,6 +9,7 @@ export const postPublicFieldDefs = [ "public_id", "content", "created_at", + "updated_at", ] satisfies (keyof typeof postsTable.$inferSelect)[]; const createFieldsFromDefs = ( diff --git a/packages/backend_app/src/db/schema.ts b/packages/backend_app/src/db/schema.ts index 8f4d20a4..1a9c3c98 100644 --- a/packages/backend_app/src/db/schema.ts +++ b/packages/backend_app/src/db/schema.ts @@ -8,6 +8,7 @@ const primaryKeys = () => ({ const timestamps = { created_at: timestamp().defaultNow().notNull(), + updated_at: timestamp(), }; export const usersTable = pgTable("users", { From 4d41a98e7e1f0d8cd6ac591881e4e4f03fc61ca1 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 17:01:41 +0900 Subject: [PATCH 14/23] feat: add updated_at field to post schema and update required fields - Introduced the updated_at field in the post schema to track the last modification timestamp. - Updated the required fields to include updated_at, ensuring comprehensive post detail tracking. --- packages/backend_app/openapi.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/backend_app/openapi.json b/packages/backend_app/openapi.json index 9f1d76da..f9aa3d08 100644 --- a/packages/backend_app/openapi.json +++ b/packages/backend_app/openapi.json @@ -56,9 +56,16 @@ "format": "date-time", "description": "The date and time the post was created", "example": "2025-01-01T00:00:00Z" + }, + "updated_at": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "The date and time the post was updated", + "example": "2025-01-01T00:00:00Z" } }, - "required": ["public_id", "content", "created_at"], + "required": ["public_id", "content", "created_at", "updated_at"], "additionalProperties": false } }, From 7370668a1ade9677068cc1cadad76855a6f68f7b Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 17:06:03 +0900 Subject: [PATCH 15/23] fix: include updated_at field in post response object - Added the updated_at field to the post response object to ensure that the last modification timestamp is included when retrieving post details. --- packages/backend_app/src/apps/posts/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend_app/src/apps/posts/index.ts b/packages/backend_app/src/apps/posts/index.ts index e497a343..897bb92e 100644 --- a/packages/backend_app/src/apps/posts/index.ts +++ b/packages/backend_app/src/apps/posts/index.ts @@ -116,6 +116,7 @@ const routes = postApp user_id: result.user_id, content: result.content, created_at: result.created_at, + updated_at: result.updated_at, }); }); From 6da46b7aa4ae124b4b16ac2d87052b08a8a0ff16 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 17:08:40 +0900 Subject: [PATCH 16/23] fix: update updated_at field in post tests to null - Modified test cases to set the updated_at field to null instead of expecting a string, ensuring alignment with the current post response structure. --- packages/backend_app/src/apps/posts/index.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend_app/src/apps/posts/index.test.ts b/packages/backend_app/src/apps/posts/index.test.ts index d986ef09..3146df1d 100644 --- a/packages/backend_app/src/apps/posts/index.test.ts +++ b/packages/backend_app/src/apps/posts/index.test.ts @@ -83,7 +83,7 @@ describe("postsApp", () => { public_id: expect.any(String), content: "test2", created_at: expect.any(String), - updated_at: expect.any(String), + updated_at: null, user: { public_id: user.public_id, display_name: user.display_name, @@ -93,7 +93,7 @@ describe("postsApp", () => { public_id: expect.any(String), content: "test", created_at: expect.any(String), - updated_at: expect.any(String), + updated_at: null, user: { public_id: user.public_id, display_name: user.display_name, From 63e04b625f26c43134543b9cc5c86f4da4cc78f2 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 17:12:34 +0900 Subject: [PATCH 17/23] fix: update updated_at field to allow null values in post schema and components - Modified the updated_at field in the Post interface to accept null values, reflecting the possibility of no updates. - Adjusted related components and mock response functions to accommodate the updated field type, ensuring consistent handling of post update timestamps. --- packages/frontend_app/src/client/index.msw.ts | 15 ++++++++++++--- packages/frontend_app/src/client/index.schemas.ts | 9 ++++++--- .../pages/post/components/PostCard/index.tsx | 6 +++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/frontend_app/src/client/index.msw.ts b/packages/frontend_app/src/client/index.msw.ts index db3459e6..160cd69e 100644 --- a/packages/frontend_app/src/client/index.msw.ts +++ b/packages/frontend_app/src/client/index.msw.ts @@ -34,7 +34,10 @@ export const getGetPostsResponseMock = ( public_id: faker.string.uuid(), content: faker.string.alpha({ length: { min: 10, max: 20 } }), created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - updated_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + updated_at: faker.helpers.arrayElement([ + `${faker.date.past().toISOString().split(".")[0]}Z`, + null, + ]), }, ...{ user: { @@ -54,7 +57,10 @@ export const getPostPostsResponseMock = ( public_id: faker.string.uuid(), content: faker.string.alpha({ length: { min: 10, max: 20 } }), created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - updated_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + updated_at: faker.helpers.arrayElement([ + `${faker.date.past().toISOString().split(".")[0]}Z`, + null, + ]), }, ...{ user: { @@ -74,7 +80,10 @@ export const getPutPostsPublicIdResponseMock = ( public_id: faker.string.uuid(), content: faker.string.alpha({ length: { min: 10, max: 20 } }), created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - updated_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + updated_at: faker.helpers.arrayElement([ + `${faker.date.past().toISOString().split(".")[0]}Z`, + null, + ]), }, ...{ user: { diff --git a/packages/frontend_app/src/client/index.schemas.ts b/packages/frontend_app/src/client/index.schemas.ts index be7d7ddf..6b2a855e 100644 --- a/packages/frontend_app/src/client/index.schemas.ts +++ b/packages/frontend_app/src/client/index.schemas.ts @@ -34,8 +34,11 @@ export interface Post { content: string; /** The date and time the post was created */ created_at: string; - /** The date and time the post was updated */ - updated_at: string; + /** + * The date and time the post was updated + * @nullable + */ + updated_at: string | null; } export type PostUserSignupBody = { @@ -399,7 +402,7 @@ export type PutPostsPublicIdBody = { * The content of the post * @minLength 1 */ - content?: string; + content: string; }; export type PutPostsPublicId200PostAllOf = { diff --git a/packages/frontend_app/src/components/pages/post/components/PostCard/index.tsx b/packages/frontend_app/src/components/pages/post/components/PostCard/index.tsx index f43292b7..9ba409dc 100644 --- a/packages/frontend_app/src/components/pages/post/components/PostCard/index.tsx +++ b/packages/frontend_app/src/components/pages/post/components/PostCard/index.tsx @@ -24,7 +24,7 @@ type Post = { public_id: string; content: string; created_at: string; - updated_at: string; + updated_at: string | null; user: { public_id: string; display_name: string; @@ -42,7 +42,7 @@ export const PostCard = ({ post }: PostCardProps) => { const [isEditing, setIsEditing] = useState(false); const [editContent, setEditContent] = useState(post.content); - const isUpdated = post.updated_at !== post.created_at; + const isUpdated = post.updated_at !== null; const updatePostMutation = usePutPostsPublicId(); const deletePostMutation = useDeletePostsPublicId(); @@ -183,7 +183,7 @@ export const PostCard = ({ post }: PostCardProps) => { Created: {formatDate(post.created_at)} - {isUpdated && ( + {post.updated_at !== null && ( Updated: {formatDate(post.updated_at)} From f65031bb0acbd8d401187eff4df716f80ac7c1d5 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Thu, 8 Jan 2026 17:15:46 +0900 Subject: [PATCH 18/23] fix: set updated_at field to null in PostCard mock data - Updated the mock post creation function in PostCard stories to set the updated_at field to null, aligning with the recent schema changes that allow null values for this field. --- .../components/pages/post/components/PostCard/index.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend_app/src/components/pages/post/components/PostCard/index.stories.tsx b/packages/frontend_app/src/components/pages/post/components/PostCard/index.stories.tsx index b90c0857..3b67e743 100644 --- a/packages/frontend_app/src/components/pages/post/components/PostCard/index.stories.tsx +++ b/packages/frontend_app/src/components/pages/post/components/PostCard/index.stories.tsx @@ -48,7 +48,7 @@ const createMockPost = ( public_id: faker.string.uuid(), content: postContent, created_at: "2025-01-01T00:00:00.000Z", - updated_at: "2025-01-01T00:00:00.000Z", + updated_at: null, user, ...overrides, }); From b4c2109c7fd6c2c567e8d412e6fa5009b879c504 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Sat, 17 Jan 2026 23:21:15 +0900 Subject: [PATCH 19/23] feat: introduce first_created_at field in post schema and update related logic - Added first_created_at field to the post schema to track the original creation timestamp. - Updated post creation and response logic to handle first_created_at, transforming it to created_at for consistency. - Adjusted tests to ensure first_created_at is correctly set and maintained during post operations. --- packages/backend_app/openapi.json | 169 +++++++++++------- packages/backend_app/src/apps/posts/dto.ts | 67 ++++++- .../backend_app/src/apps/posts/index.test.ts | 60 +++++-- packages/backend_app/src/apps/posts/index.ts | 11 +- packages/backend_app/src/db/field.ts | 2 +- packages/backend_app/src/db/schema.ts | 2 +- 6 files changed, 218 insertions(+), 93 deletions(-) diff --git a/packages/backend_app/openapi.json b/packages/backend_app/openapi.json index f9aa3d08..ecf4a9cd 100644 --- a/packages/backend_app/openapi.json +++ b/packages/backend_app/openapi.json @@ -36,37 +36,6 @@ }, "required": ["public_id", "display_name"], "additionalProperties": false - }, - "post": { - "type": "object", - "properties": { - "public_id": { - "type": "string", - "format": "uuid", - "description": "Public ID", - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "content": { - "type": "string", - "description": "The content of the post", - "example": "test" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "The date and time the post was created", - "example": "2025-01-01T00:00:00Z" - }, - "updated_at": { - "type": "string", - "nullable": true, - "format": "date-time", - "description": "The date and time the post was updated", - "example": "2025-01-01T00:00:00Z" - } - }, - "required": ["public_id", "content", "created_at", "updated_at"], - "additionalProperties": false } }, "parameters": {} @@ -393,17 +362,41 @@ "posts": { "type": "array", "items": { - "allOf": [ - { "$ref": "#/components/schemas/post" }, - { - "type": "object", - "properties": { - "user": { "$ref": "#/components/schemas/user" } - }, - "required": ["user"], - "additionalProperties": false - } - ] + "type": "object", + "properties": { + "public_id": { + "type": "string", + "format": "uuid", + "description": "Public ID", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "content": { + "type": "string", + "description": "The content of the post", + "example": "test" + }, + "first_created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time the post was originally created", + "example": "2025-01-01T00:00:00Z" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time the post was created (or last updated in case of delete&insert)", + "example": "2025-01-01T00:00:00Z" + }, + "user": { "$ref": "#/components/schemas/user" } + }, + "required": [ + "public_id", + "content", + "first_created_at", + "created_at", + "user" + ], + "additionalProperties": false } } }, @@ -576,17 +569,41 @@ "type": "object", "properties": { "post": { - "allOf": [ - { "$ref": "#/components/schemas/post" }, - { - "type": "object", - "properties": { - "user": { "$ref": "#/components/schemas/user" } - }, - "required": ["user"], - "additionalProperties": false - } - ] + "type": "object", + "properties": { + "public_id": { + "type": "string", + "format": "uuid", + "description": "Public ID", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "content": { + "type": "string", + "description": "The content of the post", + "example": "test" + }, + "first_created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time the post was originally created", + "example": "2025-01-01T00:00:00Z" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time the post was created (or last updated in case of delete&insert)", + "example": "2025-01-01T00:00:00Z" + }, + "user": { "$ref": "#/components/schemas/user" } + }, + "required": [ + "public_id", + "content", + "first_created_at", + "created_at", + "user" + ], + "additionalProperties": false } }, "required": ["post"] @@ -768,17 +785,41 @@ "type": "object", "properties": { "post": { - "allOf": [ - { "$ref": "#/components/schemas/post" }, - { - "type": "object", - "properties": { - "user": { "$ref": "#/components/schemas/user" } - }, - "required": ["user"], - "additionalProperties": false - } - ] + "type": "object", + "properties": { + "public_id": { + "type": "string", + "format": "uuid", + "description": "Public ID", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "content": { + "type": "string", + "description": "The content of the post", + "example": "test" + }, + "first_created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time the post was originally created", + "example": "2025-01-01T00:00:00Z" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time the post was created (or last updated in case of delete&insert)", + "example": "2025-01-01T00:00:00Z" + }, + "user": { "$ref": "#/components/schemas/user" } + }, + "required": [ + "public_id", + "content", + "first_created_at", + "created_at", + "user" + ], + "additionalProperties": false } }, "required": ["post"] diff --git a/packages/backend_app/src/apps/posts/dto.ts b/packages/backend_app/src/apps/posts/dto.ts index 419054a4..ba65ec00 100644 --- a/packages/backend_app/src/apps/posts/dto.ts +++ b/packages/backend_app/src/apps/posts/dto.ts @@ -8,7 +8,8 @@ import { } from "../factory"; import { userSelectSchema } from "../user/dto"; -const postSelectSchema = createSelectSchema(postsTable, { +// 内部スキーマ(DBの構造を反映) +const postSelectSchemaInternal = createSelectSchema(postsTable, { public_id: (schema) => schema.openapi({ description: "Public ID", @@ -19,27 +20,75 @@ const postSelectSchema = createSelectSchema(postsTable, { description: "The content of the post", example: "test", }), - created_at: (schema) => + first_created_at: (schema) => schema.openapi({ - description: "The date and time the post was created", + description: "The date and time the post was originally created", example: "2025-01-01T00:00:00Z", format: "date-time", }), - updated_at: (schema) => + created_at: (schema) => schema.openapi({ - description: "The date and time the post was updated", + description: + "The date and time the post was created (or last updated in case of delete&insert)", example: "2025-01-01T00:00:00Z", format: "date-time", }), }) .pick(postPublicFields) - .strict() - .openapi("post"); + .strict(); -export const postWithUserSelectSchema = postSelectSchema.extend({ - user: userSelectSchema, +// レスポンス用スキーマ(フィールド名を変換) +const postSelectSchemaBase = z.object({ + public_id: z.string().uuid().openapi({ + description: "Public ID", + example: "123e4567-e89b-12d3-a456-426614174000", + }), + content: z.string().openapi({ + description: "The content of the post", + example: "test", + }), + created_at: z.string().datetime().openapi({ + description: "The date and time the post was originally created", + example: "2025-01-01T00:00:00Z", + format: "date-time", + }), + updated_at: z.string().datetime().openapi({ + description: + "The date and time the post was created (or last updated in case of delete&insert)", + example: "2025-01-01T00:00:00Z", + format: "date-time", + }), }); +// Post単体のスキーマ(変換を含む、OpenAPI定義用) +export const postSelectSchema = postSelectSchemaInternal + .transform((data) => ({ + public_id: data.public_id, + content: data.content, + created_at: data.first_created_at.toISOString(), // first_created_at → created_at + updated_at: data.created_at.toISOString(), // created_at → updated_at + })) + .pipe(postSelectSchemaBase) + .openapi("post"); + +// Post with Userのスキーマ(変換を含む) +export const postWithUserSelectSchema = postSelectSchemaInternal + .extend({ + user: userSelectSchema, + }) + .transform((data) => ({ + public_id: data.public_id, + content: data.content, + created_at: data.first_created_at.toISOString(), // first_created_at → created_at + updated_at: data.created_at.toISOString(), // created_at → updated_at + user: data.user, + })) + .pipe( + postSelectSchemaBase.extend({ + user: userSelectSchema, + }), + ); + export const getPostsResponseSchema = z.object({ posts: postWithUserSelectSchema.array(), }); diff --git a/packages/backend_app/src/apps/posts/index.test.ts b/packages/backend_app/src/apps/posts/index.test.ts index 3146df1d..02e1506b 100644 --- a/packages/backend_app/src/apps/posts/index.test.ts +++ b/packages/backend_app/src/apps/posts/index.test.ts @@ -60,15 +60,18 @@ describe("postsApp", () => { describe("when there are some posts", () => { beforeEach(async () => { + const now = new Date(); await db.insert(postsTable).values({ public_id: faker.string.uuid(), user_id: user.id, content: "test", + first_created_at: now, }); await db.insert(postsTable).values({ public_id: faker.string.uuid(), user_id: user.id, content: "test2", + first_created_at: now, }); }); @@ -82,8 +85,8 @@ describe("postsApp", () => { expect(json.posts[0]).toEqual({ public_id: expect.any(String), content: "test2", - created_at: expect.any(String), - updated_at: null, + created_at: expect.any(String), // first_created_at → created_at に変換 + updated_at: expect.any(String), // created_at → updated_at に変換 user: { public_id: user.public_id, display_name: user.display_name, @@ -92,8 +95,8 @@ describe("postsApp", () => { expect(json.posts[1]).toEqual({ public_id: expect.any(String), content: "test", - created_at: expect.any(String), - updated_at: null, + created_at: expect.any(String), // first_created_at → created_at に変換 + updated_at: expect.any(String), // created_at → updated_at に変換 user: { public_id: user.public_id, display_name: user.display_name, @@ -147,6 +150,10 @@ describe("postsApp", () => { expect(postLog.public_id).toBe(post.public_id); expect(postLog.user_id).toBe(post.user_id); expect(postLog.content).toBe(content); + + // first_created_atが正しく設定されていることを確認 + expect(post.first_created_at).toBeDefined(); + expect(post.first_created_at).toBeInstanceOf(Date); }); }); @@ -202,10 +209,21 @@ describe("postsApp", () => { }); describe("when post is found", () => { + let firstPost: typeof postsTable.$inferSelect; + beforeEach(async () => { - await db + const result = await db .insert(postsTable) - .values({ public_id, user_id: user.id, content: "test" }); + .values({ + public_id, + user_id: user.id, + content: "test", + first_created_at: new Date(), + }) + .returning(); + const post = result[0]; + if (!post) throw new Error("post is not found"); + firstPost = post; }); it("should return 200 Response", async () => { @@ -224,6 +242,9 @@ describe("postsApp", () => { expect(post.content).toBe(content); expect(post.public_id).toBe(public_id); + // first_created_atが維持されていることを確認 + expect(post.first_created_at).toEqual(firstPost.first_created_at); + const postLogs = await db.select().from(postLogsTable); expect(postLogs).toHaveLength(1); const postLog = postLogs[0]; @@ -237,9 +258,12 @@ describe("postsApp", () => { describe("when post user is not the same as the current user", () => { beforeEach(async () => { - await db - .insert(postsTable) - .values({ public_id, user_id: anotherUser.id, content: "test" }); + await db.insert(postsTable).values({ + public_id, + user_id: anotherUser.id, + content: "test", + first_created_at: new Date(), + }); }); it("should return 403 Response", async () => { @@ -309,9 +333,12 @@ describe("postsApp", () => { describe("when post is found", () => { beforeEach(async () => { - await db - .insert(postsTable) - .values({ public_id, user_id: user.id, content: "test" }); + await db.insert(postsTable).values({ + public_id, + user_id: user.id, + content: "test", + first_created_at: new Date(), + }); }); it("should return 200 Response", async () => { @@ -325,9 +352,12 @@ describe("postsApp", () => { describe("when post user is not the same as the current user", () => { beforeEach(async () => { - await db - .insert(postsTable) - .values({ public_id, user_id: anotherUser.id, content: "test" }); + await db.insert(postsTable).values({ + public_id, + user_id: anotherUser.id, + content: "test", + first_created_at: new Date(), + }); }); it("should return 403 Response", async () => { diff --git a/packages/backend_app/src/apps/posts/index.ts b/packages/backend_app/src/apps/posts/index.ts index 897bb92e..aae97077 100644 --- a/packages/backend_app/src/apps/posts/index.ts +++ b/packages/backend_app/src/apps/posts/index.ts @@ -36,10 +36,16 @@ const routes = postApp const { content } = c.req.valid("json"); const user = c.get("user"); const result = await db.transaction(async (tx) => { + const now = new Date(); const post = ( await tx .insert(postsTable) - .values({ public_id: crypto.randomUUID(), user_id: user.id, content }) + .values({ + public_id: crypto.randomUUID(), + user_id: user.id, + content, + first_created_at: now, // 最初の作成日時を設定 + }) .returning() )[0]; if (!post) @@ -100,7 +106,7 @@ const routes = postApp public_id: target.public_id, user_id: target.user_id, content, - updated_at: new Date(), + first_created_at: target.first_created_at, // 最初の作成日時を維持 }) .returning(); @@ -116,7 +122,6 @@ const routes = postApp user_id: result.user_id, content: result.content, created_at: result.created_at, - updated_at: result.updated_at, }); }); diff --git a/packages/backend_app/src/db/field.ts b/packages/backend_app/src/db/field.ts index 73495d1e..49b66cb1 100644 --- a/packages/backend_app/src/db/field.ts +++ b/packages/backend_app/src/db/field.ts @@ -8,8 +8,8 @@ export const userPublicFieldDefs = [ export const postPublicFieldDefs = [ "public_id", "content", + "first_created_at", "created_at", - "updated_at", ] satisfies (keyof typeof postsTable.$inferSelect)[]; const createFieldsFromDefs = ( diff --git a/packages/backend_app/src/db/schema.ts b/packages/backend_app/src/db/schema.ts index 1a9c3c98..1e262def 100644 --- a/packages/backend_app/src/db/schema.ts +++ b/packages/backend_app/src/db/schema.ts @@ -8,7 +8,6 @@ const primaryKeys = () => ({ const timestamps = { created_at: timestamp().defaultNow().notNull(), - updated_at: timestamp(), }; export const usersTable = pgTable("users", { @@ -24,6 +23,7 @@ export const postsTable = pgTable("posts", { .notNull() .references(() => usersTable.id), content: text().notNull(), + first_created_at: timestamp().notNull(), // 最初の作成日時を保持(delete&insert方式のため) ...timestamps, }); From e8bb6cf77ce7b654b0c306ed435277ee036fc831 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Sat, 17 Jan 2026 23:23:00 +0900 Subject: [PATCH 20/23] refactor: update post schema and mock data to include first_created_at and remove updated_at - Refactored the post schema to include first_created_at and removed the updated_at field, aligning with recent changes. - Updated mock data generation in response functions to reflect the new schema, ensuring consistency in post creation and retrieval. - Adjusted related types to streamline the post structure and improve clarity in the codebase. --- packages/frontend_app/src/client/index.msw.ts | 57 ++++++------------- .../frontend_app/src/client/index.schemas.ts | 50 ++++++++-------- 2 files changed, 45 insertions(+), 62 deletions(-) diff --git a/packages/frontend_app/src/client/index.msw.ts b/packages/frontend_app/src/client/index.msw.ts index 160cd69e..0e6fe593 100644 --- a/packages/frontend_app/src/client/index.msw.ts +++ b/packages/frontend_app/src/client/index.msw.ts @@ -30,20 +30,13 @@ export const getGetPostsResponseMock = ( { length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1, ).map(() => ({ - ...{ + public_id: faker.string.uuid(), + content: faker.string.alpha({ length: { min: 10, max: 20 } }), + first_created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + user: { public_id: faker.string.uuid(), - content: faker.string.alpha({ length: { min: 10, max: 20 } }), - created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - updated_at: faker.helpers.arrayElement([ - `${faker.date.past().toISOString().split(".")[0]}Z`, - null, - ]), - }, - ...{ - user: { - public_id: faker.string.uuid(), - display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), - }, + display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), }, })), ...overrideResponse, @@ -53,20 +46,13 @@ export const getPostPostsResponseMock = ( overrideResponse: Partial = {}, ): PostPosts200 => ({ post: { - ...{ + public_id: faker.string.uuid(), + content: faker.string.alpha({ length: { min: 10, max: 20 } }), + first_created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + user: { public_id: faker.string.uuid(), - content: faker.string.alpha({ length: { min: 10, max: 20 } }), - created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - updated_at: faker.helpers.arrayElement([ - `${faker.date.past().toISOString().split(".")[0]}Z`, - null, - ]), - }, - ...{ - user: { - public_id: faker.string.uuid(), - display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), - }, + display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), }, }, ...overrideResponse, @@ -76,20 +62,13 @@ export const getPutPostsPublicIdResponseMock = ( overrideResponse: Partial = {}, ): PutPostsPublicId200 => ({ post: { - ...{ + public_id: faker.string.uuid(), + content: faker.string.alpha({ length: { min: 10, max: 20 } }), + first_created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + user: { public_id: faker.string.uuid(), - content: faker.string.alpha({ length: { min: 10, max: 20 } }), - created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - updated_at: faker.helpers.arrayElement([ - `${faker.date.past().toISOString().split(".")[0]}Z`, - null, - ]), - }, - ...{ - user: { - public_id: faker.string.uuid(), - display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), - }, + display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), }, }, ...overrideResponse, diff --git a/packages/frontend_app/src/client/index.schemas.ts b/packages/frontend_app/src/client/index.schemas.ts index 6b2a855e..c72ba986 100644 --- a/packages/frontend_app/src/client/index.schemas.ts +++ b/packages/frontend_app/src/client/index.schemas.ts @@ -27,20 +27,6 @@ export interface User { display_name: string; } -export interface Post { - /** Public ID */ - public_id: string; - /** The content of the post */ - content: string; - /** The date and time the post was created */ - created_at: string; - /** - * The date and time the post was updated - * @nullable - */ - updated_at: string | null; -} - export type PostUserSignupBody = { /** * The display name of the user @@ -209,12 +195,18 @@ export type GetUserLogin500AllOf = { export type GetUserLogin500 = ErrorResponse & GetUserLogin500AllOf; -export type GetPosts200PostsItemAllOf = { +export type GetPosts200PostsItem = { + /** Public ID */ + public_id: string; + /** The content of the post */ + content: string; + /** The date and time the post was originally created */ + first_created_at: string; + /** The date and time the post was created (or last updated in case of delete&insert) */ + created_at: string; user: User; }; -export type GetPosts200PostsItem = Post & GetPosts200PostsItemAllOf; - export type GetPosts200 = { posts: GetPosts200PostsItem[]; }; @@ -307,12 +299,18 @@ export type PostPostsBody = { content: string; }; -export type PostPosts200PostAllOf = { +export type PostPosts200Post = { + /** Public ID */ + public_id: string; + /** The content of the post */ + content: string; + /** The date and time the post was originally created */ + first_created_at: string; + /** The date and time the post was created (or last updated in case of delete&insert) */ + created_at: string; user: User; }; -export type PostPosts200Post = Post & PostPosts200PostAllOf; - export type PostPosts200 = { post: PostPosts200Post; }; @@ -405,12 +403,18 @@ export type PutPostsPublicIdBody = { content: string; }; -export type PutPostsPublicId200PostAllOf = { +export type PutPostsPublicId200Post = { + /** Public ID */ + public_id: string; + /** The content of the post */ + content: string; + /** The date and time the post was originally created */ + first_created_at: string; + /** The date and time the post was created (or last updated in case of delete&insert) */ + created_at: string; user: User; }; -export type PutPostsPublicId200Post = Post & PutPostsPublicId200PostAllOf; - export type PutPostsPublicId200 = { post: PutPostsPublicId200Post; }; From 1c7e9bb5c3008eaa0abfa9054c396648d1801df9 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Sat, 17 Jan 2026 23:57:06 +0900 Subject: [PATCH 21/23] refactor: enhance post schema by consolidating properties and improving structure - Introduced a new "post" schema definition to encapsulate post properties, including public_id, content, first_created_at, and created_at. - Updated the post response structure to utilize the new schema, ensuring a more organized and maintainable codebase. - Removed redundant property definitions in favor of schema references, streamlining the overall schema design. --- packages/backend_app/openapi.json | 145 ++++++--------------- packages/backend_app/src/apps/posts/dto.ts | 57 ++------ 2 files changed, 49 insertions(+), 153 deletions(-) diff --git a/packages/backend_app/openapi.json b/packages/backend_app/openapi.json index ecf4a9cd..c9c33523 100644 --- a/packages/backend_app/openapi.json +++ b/packages/backend_app/openapi.json @@ -36,6 +36,16 @@ }, "required": ["public_id", "display_name"], "additionalProperties": false + }, + "post": { + "type": "object", + "properties": { + "public_id": { "type": "string", "format": "uuid" }, + "content": { "type": "string" }, + "first_created_at": { "type": "string", "format": "date" }, + "created_at": { "type": "string", "format": "date" } + }, + "required": ["public_id", "content", "first_created_at", "created_at"] } }, "parameters": {} @@ -362,41 +372,16 @@ "posts": { "type": "array", "items": { - "type": "object", - "properties": { - "public_id": { - "type": "string", - "format": "uuid", - "description": "Public ID", - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "content": { - "type": "string", - "description": "The content of the post", - "example": "test" - }, - "first_created_at": { - "type": "string", - "format": "date-time", - "description": "The date and time the post was originally created", - "example": "2025-01-01T00:00:00Z" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "The date and time the post was created (or last updated in case of delete&insert)", - "example": "2025-01-01T00:00:00Z" - }, - "user": { "$ref": "#/components/schemas/user" } - }, - "required": [ - "public_id", - "content", - "first_created_at", - "created_at", - "user" - ], - "additionalProperties": false + "allOf": [ + { "$ref": "#/components/schemas/post" }, + { + "type": "object", + "properties": { + "user": { "$ref": "#/components/schemas/user" } + }, + "required": ["user"] + } + ] } } }, @@ -569,41 +554,16 @@ "type": "object", "properties": { "post": { - "type": "object", - "properties": { - "public_id": { - "type": "string", - "format": "uuid", - "description": "Public ID", - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "content": { - "type": "string", - "description": "The content of the post", - "example": "test" - }, - "first_created_at": { - "type": "string", - "format": "date-time", - "description": "The date and time the post was originally created", - "example": "2025-01-01T00:00:00Z" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "The date and time the post was created (or last updated in case of delete&insert)", - "example": "2025-01-01T00:00:00Z" - }, - "user": { "$ref": "#/components/schemas/user" } - }, - "required": [ - "public_id", - "content", - "first_created_at", - "created_at", - "user" - ], - "additionalProperties": false + "allOf": [ + { "$ref": "#/components/schemas/post" }, + { + "type": "object", + "properties": { + "user": { "$ref": "#/components/schemas/user" } + }, + "required": ["user"] + } + ] } }, "required": ["post"] @@ -785,41 +745,16 @@ "type": "object", "properties": { "post": { - "type": "object", - "properties": { - "public_id": { - "type": "string", - "format": "uuid", - "description": "Public ID", - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "content": { - "type": "string", - "description": "The content of the post", - "example": "test" - }, - "first_created_at": { - "type": "string", - "format": "date-time", - "description": "The date and time the post was originally created", - "example": "2025-01-01T00:00:00Z" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "The date and time the post was created (or last updated in case of delete&insert)", - "example": "2025-01-01T00:00:00Z" - }, - "user": { "$ref": "#/components/schemas/user" } - }, - "required": [ - "public_id", - "content", - "first_created_at", - "created_at", - "user" - ], - "additionalProperties": false + "allOf": [ + { "$ref": "#/components/schemas/post" }, + { + "type": "object", + "properties": { + "user": { "$ref": "#/components/schemas/user" } + }, + "required": ["user"] + } + ] } }, "required": ["post"] diff --git a/packages/backend_app/src/apps/posts/dto.ts b/packages/backend_app/src/apps/posts/dto.ts index ba65ec00..11defd0d 100644 --- a/packages/backend_app/src/apps/posts/dto.ts +++ b/packages/backend_app/src/apps/posts/dto.ts @@ -8,38 +8,9 @@ import { } from "../factory"; import { userSelectSchema } from "../user/dto"; -// 内部スキーマ(DBの構造を反映) -const postSelectSchemaInternal = createSelectSchema(postsTable, { - public_id: (schema) => - schema.openapi({ - description: "Public ID", - example: "123e4567-e89b-12d3-a456-426614174000", - }), - content: (schema) => - schema.openapi({ - description: "The content of the post", - example: "test", - }), - first_created_at: (schema) => - schema.openapi({ - description: "The date and time the post was originally created", - example: "2025-01-01T00:00:00Z", - format: "date-time", - }), - created_at: (schema) => - schema.openapi({ - description: - "The date and time the post was created (or last updated in case of delete&insert)", - example: "2025-01-01T00:00:00Z", - format: "date-time", - }), -}) - .pick(postPublicFields) - .strict(); - // レスポンス用スキーマ(フィールド名を変換) const postSelectSchemaBase = z.object({ - public_id: z.string().uuid().openapi({ + public_id: z.uuid().openapi({ description: "Public ID", example: "123e4567-e89b-12d3-a456-426614174000", }), @@ -47,12 +18,12 @@ const postSelectSchemaBase = z.object({ description: "The content of the post", example: "test", }), - created_at: z.string().datetime().openapi({ + created_at: z.iso.datetime().openapi({ description: "The date and time the post was originally created", example: "2025-01-01T00:00:00Z", format: "date-time", }), - updated_at: z.string().datetime().openapi({ + updated_at: z.iso.datetime().openapi({ description: "The date and time the post was created (or last updated in case of delete&insert)", example: "2025-01-01T00:00:00Z", @@ -61,7 +32,8 @@ const postSelectSchemaBase = z.object({ }); // Post単体のスキーマ(変換を含む、OpenAPI定義用) -export const postSelectSchema = postSelectSchemaInternal +export const postSelectSchema = createSelectSchema(postsTable) + .pick(postPublicFields) .transform((data) => ({ public_id: data.public_id, content: data.content, @@ -72,22 +44,11 @@ export const postSelectSchema = postSelectSchemaInternal .openapi("post"); // Post with Userのスキーマ(変換を含む) -export const postWithUserSelectSchema = postSelectSchemaInternal - .extend({ +export const postWithUserSelectSchema = postSelectSchema.and( + z.object({ user: userSelectSchema, - }) - .transform((data) => ({ - public_id: data.public_id, - content: data.content, - created_at: data.first_created_at.toISOString(), // first_created_at → created_at - updated_at: data.created_at.toISOString(), // created_at → updated_at - user: data.user, - })) - .pipe( - postSelectSchemaBase.extend({ - user: userSelectSchema, - }), - ); + }), +); export const getPostsResponseSchema = z.object({ posts: postWithUserSelectSchema.array(), From 01327414a16a161e7a029d474b4a75fdb287b298 Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Sun, 18 Jan 2026 09:03:50 +0900 Subject: [PATCH 22/23] refactor: update post schema and response handling to include updated_at field - Enhanced the post schema to include the updated_at field, ensuring accurate tracking of post modifications. - Adjusted response structures and mock data to reflect the new schema, maintaining consistency across the application. - Removed the first_created_at field from the schema and updated required fields accordingly, streamlining the post data model. --- packages/backend_app/openapi.json | 29 ++++++++-- packages/backend_app/src/apps/posts/dto.ts | 58 +++++++++---------- packages/frontend_app/src/client/index.msw.ts | 48 +++++++++------ .../frontend_app/src/client/index.schemas.ts | 47 +++++++-------- 4 files changed, 102 insertions(+), 80 deletions(-) diff --git a/packages/backend_app/openapi.json b/packages/backend_app/openapi.json index c9c33523..8e0f3d52 100644 --- a/packages/backend_app/openapi.json +++ b/packages/backend_app/openapi.json @@ -40,12 +40,31 @@ "post": { "type": "object", "properties": { - "public_id": { "type": "string", "format": "uuid" }, - "content": { "type": "string" }, - "first_created_at": { "type": "string", "format": "date" }, - "created_at": { "type": "string", "format": "date" } + "public_id": { + "type": "string", + "format": "uuid", + "description": "Public ID", + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "content": { + "type": "string", + "description": "The content of the post", + "example": "test" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time the post was originally created", + "example": "2025-01-01T00:00:00Z" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The date and time the post was created (or last updated in case of delete&insert)", + "example": "2025-01-01T00:00:00Z" + } }, - "required": ["public_id", "content", "first_created_at", "created_at"] + "required": ["public_id", "content", "created_at", "updated_at"] } }, "parameters": {} diff --git a/packages/backend_app/src/apps/posts/dto.ts b/packages/backend_app/src/apps/posts/dto.ts index 11defd0d..17ef6734 100644 --- a/packages/backend_app/src/apps/posts/dto.ts +++ b/packages/backend_app/src/apps/posts/dto.ts @@ -8,31 +8,31 @@ import { } from "../factory"; import { userSelectSchema } from "../user/dto"; -// レスポンス用スキーマ(フィールド名を変換) -const postSelectSchemaBase = z.object({ - public_id: z.uuid().openapi({ - description: "Public ID", - example: "123e4567-e89b-12d3-a456-426614174000", - }), - content: z.string().openapi({ - description: "The content of the post", - example: "test", - }), - created_at: z.iso.datetime().openapi({ - description: "The date and time the post was originally created", - example: "2025-01-01T00:00:00Z", - format: "date-time", - }), - updated_at: z.iso.datetime().openapi({ - description: - "The date and time the post was created (or last updated in case of delete&insert)", - example: "2025-01-01T00:00:00Z", - format: "date-time", - }), -}); +const postSchema = z + .object({ + public_id: z.uuid().openapi({ + description: "Public ID", + example: "123e4567-e89b-12d3-a456-426614174000", + }), + content: z.string().openapi({ + description: "The content of the post", + example: "test", + }), + created_at: z.iso.datetime().openapi({ + description: "The date and time the post was originally created", + example: "2025-01-01T00:00:00Z", + format: "date-time", + }), + updated_at: z.iso.datetime().openapi({ + description: + "The date and time the post was created (or last updated in case of delete&insert)", + example: "2025-01-01T00:00:00Z", + format: "date-time", + }), + }) + .openapi("post"); -// Post単体のスキーマ(変換を含む、OpenAPI定義用) -export const postSelectSchema = createSelectSchema(postsTable) +export const postResponseSchema = createSelectSchema(postsTable) .pick(postPublicFields) .transform((data) => ({ public_id: data.public_id, @@ -40,18 +40,16 @@ export const postSelectSchema = createSelectSchema(postsTable) created_at: data.first_created_at.toISOString(), // first_created_at → created_at updated_at: data.created_at.toISOString(), // created_at → updated_at })) - .pipe(postSelectSchemaBase) - .openapi("post"); + .pipe(postSchema); -// Post with Userのスキーマ(変換を含む) -export const postWithUserSelectSchema = postSelectSchema.and( +export const postWithUserResponseSchema = postSchema.and( z.object({ user: userSelectSchema, }), ); export const getPostsResponseSchema = z.object({ - posts: postWithUserSelectSchema.array(), + posts: postWithUserResponseSchema.array(), }); export const postPostRequestSchema = createInsertSchema(postsTable, { @@ -65,7 +63,7 @@ export const postPostRequestSchema = createInsertSchema(postsTable, { }); export const postPostResponseSchema = z.object({ - post: postWithUserSelectSchema, + post: postWithUserResponseSchema, }); export const updatePostParamsSchema = z.object({ diff --git a/packages/frontend_app/src/client/index.msw.ts b/packages/frontend_app/src/client/index.msw.ts index 0e6fe593..db3459e6 100644 --- a/packages/frontend_app/src/client/index.msw.ts +++ b/packages/frontend_app/src/client/index.msw.ts @@ -30,13 +30,17 @@ export const getGetPostsResponseMock = ( { length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1, ).map(() => ({ - public_id: faker.string.uuid(), - content: faker.string.alpha({ length: { min: 10, max: 20 } }), - first_created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - user: { + ...{ public_id: faker.string.uuid(), - display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), + content: faker.string.alpha({ length: { min: 10, max: 20 } }), + created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + updated_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + }, + ...{ + user: { + public_id: faker.string.uuid(), + display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), + }, }, })), ...overrideResponse, @@ -46,13 +50,17 @@ export const getPostPostsResponseMock = ( overrideResponse: Partial = {}, ): PostPosts200 => ({ post: { - public_id: faker.string.uuid(), - content: faker.string.alpha({ length: { min: 10, max: 20 } }), - first_created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - user: { + ...{ public_id: faker.string.uuid(), - display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), + content: faker.string.alpha({ length: { min: 10, max: 20 } }), + created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + updated_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + }, + ...{ + user: { + public_id: faker.string.uuid(), + display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), + }, }, }, ...overrideResponse, @@ -62,13 +70,17 @@ export const getPutPostsPublicIdResponseMock = ( overrideResponse: Partial = {}, ): PutPostsPublicId200 => ({ post: { - public_id: faker.string.uuid(), - content: faker.string.alpha({ length: { min: 10, max: 20 } }), - first_created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, - user: { + ...{ public_id: faker.string.uuid(), - display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), + content: faker.string.alpha({ length: { min: 10, max: 20 } }), + created_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + updated_at: `${faker.date.past().toISOString().split(".")[0]}Z`, + }, + ...{ + user: { + public_id: faker.string.uuid(), + display_name: faker.string.alpha({ length: { min: 10, max: 20 } }), + }, }, }, ...overrideResponse, diff --git a/packages/frontend_app/src/client/index.schemas.ts b/packages/frontend_app/src/client/index.schemas.ts index c72ba986..2e1df4f9 100644 --- a/packages/frontend_app/src/client/index.schemas.ts +++ b/packages/frontend_app/src/client/index.schemas.ts @@ -27,6 +27,17 @@ export interface User { display_name: string; } +export interface Post { + /** Public ID */ + public_id: string; + /** The content of the post */ + content: string; + /** The date and time the post was originally created */ + created_at: string; + /** The date and time the post was created (or last updated in case of delete&insert) */ + updated_at: string; +} + export type PostUserSignupBody = { /** * The display name of the user @@ -195,18 +206,12 @@ export type GetUserLogin500AllOf = { export type GetUserLogin500 = ErrorResponse & GetUserLogin500AllOf; -export type GetPosts200PostsItem = { - /** Public ID */ - public_id: string; - /** The content of the post */ - content: string; - /** The date and time the post was originally created */ - first_created_at: string; - /** The date and time the post was created (or last updated in case of delete&insert) */ - created_at: string; +export type GetPosts200PostsItemAllOf = { user: User; }; +export type GetPosts200PostsItem = Post & GetPosts200PostsItemAllOf; + export type GetPosts200 = { posts: GetPosts200PostsItem[]; }; @@ -299,18 +304,12 @@ export type PostPostsBody = { content: string; }; -export type PostPosts200Post = { - /** Public ID */ - public_id: string; - /** The content of the post */ - content: string; - /** The date and time the post was originally created */ - first_created_at: string; - /** The date and time the post was created (or last updated in case of delete&insert) */ - created_at: string; +export type PostPosts200PostAllOf = { user: User; }; +export type PostPosts200Post = Post & PostPosts200PostAllOf; + export type PostPosts200 = { post: PostPosts200Post; }; @@ -403,18 +402,12 @@ export type PutPostsPublicIdBody = { content: string; }; -export type PutPostsPublicId200Post = { - /** Public ID */ - public_id: string; - /** The content of the post */ - content: string; - /** The date and time the post was originally created */ - first_created_at: string; - /** The date and time the post was created (or last updated in case of delete&insert) */ - created_at: string; +export type PutPostsPublicId200PostAllOf = { user: User; }; +export type PutPostsPublicId200Post = Post & PutPostsPublicId200PostAllOf; + export type PutPostsPublicId200 = { post: PutPostsPublicId200Post; }; From 61afb2e191b04be15d107930e520a88f43ed75db Mon Sep 17 00:00:00 2001 From: mikan3rd Date: Sun, 18 Jan 2026 14:01:00 +0900 Subject: [PATCH 23/23] refactor: streamline post response handling with transformation functions - Removed the previous response schema definitions and replaced them with transformation functions to convert database post data into the API response format. - Introduced `transformPost` and `transformPostWithUser` functions to enhance clarity and maintainability in response handling. - Updated routes to utilize the new transformation functions, ensuring consistent response structures across post operations. --- packages/backend_app/src/apps/posts/dto.ts | 46 +++++++++++++------- packages/backend_app/src/apps/posts/index.ts | 44 +++++++++---------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/packages/backend_app/src/apps/posts/dto.ts b/packages/backend_app/src/apps/posts/dto.ts index 17ef6734..2b420753 100644 --- a/packages/backend_app/src/apps/posts/dto.ts +++ b/packages/backend_app/src/apps/posts/dto.ts @@ -1,14 +1,9 @@ import { z } from "@hono/zod-openapi"; -import { postPublicFields } from "../../db/field"; import { postsTable } from "../../db/schema"; -import { - createInsertSchema, - createSelectSchema, - createUpdateSchema, -} from "../factory"; +import { createInsertSchema, createUpdateSchema } from "../factory"; import { userSelectSchema } from "../user/dto"; -const postSchema = z +export const postSchema = z .object({ public_id: z.uuid().openapi({ description: "Public ID", @@ -32,15 +27,34 @@ const postSchema = z }) .openapi("post"); -export const postResponseSchema = createSelectSchema(postsTable) - .pick(postPublicFields) - .transform((data) => ({ - public_id: data.public_id, - content: data.content, - created_at: data.first_created_at.toISOString(), // first_created_at → created_at - updated_at: data.created_at.toISOString(), // created_at → updated_at - })) - .pipe(postSchema); +// DB データから API レスポンス形式への変換関数 +export const transformPost = (post: { + public_id: string; + content: string; + first_created_at: Date; + created_at: Date; +}): z.infer => ({ + public_id: post.public_id, + content: post.content, + created_at: post.first_created_at.toISOString(), // first_created_at → created_at + updated_at: post.created_at.toISOString(), // created_at → updated_at +}); + +// user 付きの変換関数 +export const transformPostWithUser = < + T extends { + public_id: string; + content: string; + first_created_at: Date; + created_at: Date; + user: z.infer; + }, +>( + post: T, +): z.infer => ({ + ...transformPost(post), + user: post.user, +}); export const postWithUserResponseSchema = postSchema.and( z.object({ diff --git a/packages/backend_app/src/apps/posts/index.ts b/packages/backend_app/src/apps/posts/index.ts index aae97077..ab8f2fe4 100644 --- a/packages/backend_app/src/apps/posts/index.ts +++ b/packages/backend_app/src/apps/posts/index.ts @@ -5,11 +5,7 @@ import { postWithUserQuery } from "../../db/query"; import { postLogsTable, postsTable } from "../../db/schema"; import { userMiddleware } from "../../middlewares/user"; import { createApp } from "../factory"; -import { - getPostsResponseSchema, - postPostResponseSchema, - updatePostResponseSchema, -} from "./dto"; +import { transformPostWithUser } from "./dto"; import { deletePostRoute, getPostsRoute, @@ -28,8 +24,7 @@ const routes = postApp ...postWithUserQuery, orderBy: desc(postsTable.id), }); - const response = { posts }; - return c.json(getPostsResponseSchema.parse(response), 200); + return c.json({ posts: posts.map(transformPostWithUser) }, 200); }) .openapi(postPostRoute, async (c) => { @@ -64,13 +59,16 @@ const routes = postApp return post; }); - const response = { - post: await db.query.postsTable.findFirst({ - ...postWithUserQuery, - where: eq(postsTable.id, result.id), - }), - }; - return c.json(postPostResponseSchema.parse(response), 200); + const post = await db.query.postsTable.findFirst({ + ...postWithUserQuery, + where: eq(postsTable.id, result.id), + }); + if (!post) { + throw new HTTPException(500, { + message: "Failed to fetch created post", + }); + } + return c.json({ post: transformPostWithUser(post) }, 200); }) .openapi(updatePostRoute, async (c) => { @@ -125,14 +123,16 @@ const routes = postApp }); }); - const response = { - post: await db.query.postsTable.findFirst({ - ...postWithUserQuery, - where: eq(postsTable.public_id, public_id), - }), - }; - - return c.json(updatePostResponseSchema.parse(response), 200); + const post = await db.query.postsTable.findFirst({ + ...postWithUserQuery, + where: eq(postsTable.public_id, public_id), + }); + if (!post) { + throw new HTTPException(500, { + message: "Failed to fetch updated post", + }); + } + return c.json({ post: transformPostWithUser(post) }, 200); }) .openapi(deletePostRoute, async (c) => {