From 82e5542cc155a3c440292e5f10d8981f49cd6cb6 Mon Sep 17 00:00:00 2001
From: Myxogastria0808
Date: Sun, 11 Jan 2026 02:02:07 +0900
Subject: [PATCH] wip
---
products/backend/src/db/pay-crew2-schema.ts | 6 +-
.../backend/src/presentation/routes/group.ts | 80 ++++++-------------
.../backend/src/presentation/routes/info.ts | 1 -
products/frontend/openapi.json | 67 +++++++---------
products/frontend/src/api/openapi.d.ts | 22 +++--
.../src/routes/GenerateGroup/index.tsx | 2 +-
.../frontend/src/routes/GroupDetail/index.tsx | 70 +++++++++-------
products/frontend/src/routes/Root/index.tsx | 4 +-
products/validator/src/index.ts | 1 -
products/validator/src/request/group.ts | 2 +-
products/validator/src/response/group.ts | 15 ++--
11 files changed, 113 insertions(+), 157 deletions(-)
diff --git a/products/backend/src/db/pay-crew2-schema.ts b/products/backend/src/db/pay-crew2-schema.ts
index 01f7ae0..7c09e0a 100644
--- a/products/backend/src/db/pay-crew2-schema.ts
+++ b/products/backend/src/db/pay-crew2-schema.ts
@@ -1,5 +1,5 @@
// drizzle
-import { pgTable, text, timestamp, index, uuid, uniqueIndex, integer } from 'drizzle-orm/pg-core';
+import { pgTable, text, timestamp, index, uuid, uniqueIndex, integer, date } from 'drizzle-orm/pg-core';
//auth-schema
import { user } from './auth-schema';
@@ -8,7 +8,7 @@ export const group = pgTable(
{
id: uuid('id').primaryKey(),
name: text('name').notNull(),
- invite_id: text('invite_id').notNull().unique(),
+ inviteId: text('invite_id').notNull().unique(),
createdBy: text('created_by')
.references(() => user.id, { onDelete: 'set null' })
.notNull(),
@@ -58,7 +58,7 @@ export const debt = pgTable(
.references(() => user.id, { onDelete: 'cascade' }),
amount: integer('amount').notNull(),
description: text('description'),
- occurredAt: timestamp('occurred_at').defaultNow().notNull(),
+ occurredAt: date('occurred_at').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at')
.defaultNow()
diff --git a/products/backend/src/presentation/routes/group.ts b/products/backend/src/presentation/routes/group.ts
index 99f4209..fd4aa45 100644
--- a/products/backend/src/presentation/routes/group.ts
+++ b/products/backend/src/presentation/routes/group.ts
@@ -18,8 +18,6 @@ import {
type GetGroupDebtHistoryResponseSchemaType,
GetGroupInfoResponseMemberElementSchemaType,
registerGroupDebtRequestSchema,
- registerGroupDebtResponseSchema,
- type RegisterGroupDebtResponseSchemaType,
deleteGroupDebtRequestSchema,
} from 'validator';
// error schema
@@ -67,9 +65,6 @@ hono.openapi(createGroupSchema, async (c) => {
const loginUser = c.get('user');
const body = c.req.valid('json');
- // 現在時刻の取得
- const now = new Date();
-
// データベース接続
const db = drizzle({ connection: c.env.HYPERDRIVE });
@@ -79,14 +74,12 @@ hono.openapi(createGroupSchema, async (c) => {
.values({
id: crypto.randomUUID(),
name: body.group_name,
- invite_id: `${crypto.randomUUID()}-${crypto.randomUUID()}`,
+ inviteId: `${crypto.randomUUID()}-${crypto.randomUUID()}`,
createdBy: loginUser.id,
- createdAt: now,
- updatedAt: now,
})
.returning({
id: group.id,
- invite_id: group.invite_id,
+ inviteId: group.inviteId,
});
//* グループ作成者をgroupMembershipに挿入 *//
@@ -94,14 +87,13 @@ hono.openapi(createGroupSchema, async (c) => {
id: crypto.randomUUID(),
groupId: result[0].id,
userId: loginUser.id,
- joinedAt: now,
});
// レスポンス
return c.json(
{
group_id: result[0].id,
- invite_id: result[0].invite_id,
+ invite_id: result[0].inviteId,
} satisfies CreateGroupResponseSchemaType,
201
);
@@ -142,9 +134,6 @@ hono.openapi(joinGroupSchema, async (c) => {
const body = c.req.valid('json');
const user = c.get('user');
- // 現在時刻の取得
- const now = new Date();
-
// データベース接続
const db = drizzle({ connection: c.env.HYPERDRIVE });
@@ -154,7 +143,7 @@ hono.openapi(joinGroupSchema, async (c) => {
id: group.id,
})
.from(group)
- .where(eq(group.invite_id, body.invite_id))
+ .where(eq(group.inviteId, body.invite_id))
.limit(1);
// invite_id が不正な場合はエラー
@@ -188,7 +177,6 @@ hono.openapi(joinGroupSchema, async (c) => {
id: crypto.randomUUID(),
groupId: groupData[0].id,
userId: user.id,
- joinedAt: now,
})
.returning({
groupId: groupMembership.groupId,
@@ -261,6 +249,7 @@ hono.openapi(getGroupInfoSchema, async (c) => {
const groupData = await db
.select({
name: group.name,
+ inviteId: group.inviteId,
createdBy: group.createdBy,
})
.from(group)
@@ -320,7 +309,9 @@ hono.openapi(getGroupInfoSchema, async (c) => {
return c.json(
{
group_name: groupData[0].name,
- created_by:
+ invite_id: groupData[0].inviteId,
+ created_by_id: groupData[0].createdBy,
+ created_by_name:
createdByUserNameInfo[0].displayName !== null && createdByUserNameInfo[0].displayName.length > 0
? createdByUserNameInfo[0].displayName
: createdByUserNameInfo[0].name,
@@ -395,6 +386,8 @@ hono.openapi(getGroupDebtHistorySchema, async (c) => {
debtorId: debt.debtorId,
creditorId: debt.creditorId,
amount: debt.amount,
+ description: debt.description,
+ occurredAt: debt.occurredAt,
})
.from(debt)
.where(and(eq(debt.groupId, body.group_id), isNull(debt.deletedAt)));
@@ -438,6 +431,8 @@ hono.openapi(getGroupDebtHistorySchema, async (c) => {
? CreditorNameInfo[0].displayName
: CreditorNameInfo[0].name,
amount: debtEntry.amount,
+ description: debtEntry.description === null ? '' : debtEntry.description,
+ occurred_at: debtEntry.occurredAt,
});
}
@@ -468,13 +463,8 @@ const registerGroupDebtSchema = route.createSchema(
},
},
responses: {
- 201: {
- description: 'Created',
- content: {
- 'application/json': {
- schema: registerGroupDebtResponseSchema,
- },
- },
+ 204: {
+ description: 'No Content',
},
},
},
@@ -485,9 +475,6 @@ hono.openapi(registerGroupDebtSchema, async (c) => {
const loginUser = c.get('user');
const body = c.req.valid('json');
- // 現在時刻の取得
- const now = new Date();
-
// データベース接続
const db = drizzle({ connection: c.env.HYPERDRIVE });
@@ -508,35 +495,17 @@ hono.openapi(registerGroupDebtSchema, async (c) => {
// NOTE: --- 共通化終了 ---
// body.group_id に貸し借り履歴を追加 (debt table)
- const result = await db
- .insert(debt)
- .values({
- id: crypto.randomUUID(),
- groupId: body.group_id,
- creditorId: body.creditor_id,
- debtorId: body.debtor_id,
- amount: body.amount,
- description: typeof body.description === 'undefined' ? null : body.description,
- occurredAt: body.occurred_at ? new Date(body.occurred_at) : now,
- createdAt: now,
- updatedAt: now,
- })
- .returning({
- creditorId: debt.creditorId,
- debtorId: debt.debtorId,
- amount: debt.amount,
- occurredAt: debt.occurredAt,
- });
+ await db.insert(debt).values({
+ id: crypto.randomUUID(),
+ groupId: body.group_id,
+ creditorId: body.creditor_id,
+ debtorId: body.debtor_id,
+ amount: body.amount,
+ description: typeof body.description === 'undefined' ? null : body.description,
+ occurredAt: body.occurred_at,
+ });
- return c.json(
- {
- creditorId: result[0].creditorId,
- debtorId: result[0].debtorId,
- amount: result[0].amount,
- occurredAt: result[0].occurredAt,
- } satisfies RegisterGroupDebtResponseSchemaType,
- 201
- );
+ return c.body(null, 204);
});
// TODO: 貸し借りの履歴の削除エンドポイントの登録
@@ -597,7 +566,6 @@ hono.openapi(deleteGroupDebtSchema, async (c) => {
.set({
deletedBy: loginUser.id,
deletedAt: now,
- updatedAt: now,
})
.where(and(eq(debt.id, body.debt_id), eq(debt.groupId, body.group_id), isNull(debt.deletedAt)));
diff --git a/products/backend/src/presentation/routes/info.ts b/products/backend/src/presentation/routes/info.ts
index 6a7452c..0417912 100644
--- a/products/backend/src/presentation/routes/info.ts
+++ b/products/backend/src/presentation/routes/info.ts
@@ -298,7 +298,6 @@ hono.openapi(infoUserRepaymentSchema, async (c) => {
.set({
deletedBy: loginUser.id,
deletedAt: now,
- updatedAt: now,
})
.where(
or(
diff --git a/products/frontend/openapi.json b/products/frontend/openapi.json
index f8b95d0..f4883c1 100644
--- a/products/frontend/openapi.json
+++ b/products/frontend/openapi.json
@@ -4591,7 +4591,15 @@
"type": "string",
"format": "uuid"
},
- "created_by": {
+ "invite_id": {
+ "type": "string",
+ "minLength": 1
+ },
+ "created_by_id": {
+ "type": "string",
+ "minLength": 1
+ },
+ "created_by_name": {
"type": "string",
"minLength": 1
},
@@ -4618,7 +4626,9 @@
},
"required": [
"group_name",
- "created_by",
+ "invite_id",
+ "created_by_id",
+ "created_by_name",
"members"
]
}
@@ -5855,6 +5865,14 @@
"amount": {
"type": "number",
"minimum": 0
+ },
+ "description": {
+ "type": "string",
+ "minLength": 0
+ },
+ "occurred_at": {
+ "type": "string",
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
}
},
"required": [
@@ -5863,7 +5881,9 @@
"debtor_name",
"creditor_id",
"creditor_name",
- "amount"
+ "amount",
+ "description",
+ "occurred_at"
]
}
}
@@ -7077,53 +7097,24 @@
"type": "string"
},
"occurred_at": {
- "type": "string"
+ "type": "string",
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
}
},
"required": [
"group_id",
"creditor_id",
"debtor_id",
- "amount"
+ "amount",
+ "occurred_at"
]
}
}
}
},
"responses": {
- "201": {
- "description": "Created",
- "content": {
- "application/json": {
- "schema": {
- "type": "object",
- "properties": {
- "creditorId": {
- "type": "string",
- "minLength": 1
- },
- "debtorId": {
- "type": "string",
- "minLength": 1
- },
- "amount": {
- "type": "number",
- "minimum": 0
- },
- "occurredAt": {
- "type": "string",
- "format": "date"
- }
- },
- "required": [
- "creditorId",
- "debtorId",
- "amount",
- "occurredAt"
- ]
- }
- }
- }
+ "204": {
+ "description": "No Content"
},
"400": {
"description": "Bad Request",
diff --git a/products/frontend/src/api/openapi.d.ts b/products/frontend/src/api/openapi.d.ts
index 8993b57..50ae9b4 100644
--- a/products/frontend/src/api/openapi.d.ts
+++ b/products/frontend/src/api/openapi.d.ts
@@ -463,7 +463,9 @@ export interface paths {
"application/json": {
/** Format: uuid */
group_name: string;
- created_by: string;
+ invite_id: string;
+ created_by_id: string;
+ created_by_name: string;
members: {
user_id: string;
user_name: string;
@@ -578,6 +580,8 @@ export interface paths {
creditor_id: string;
creditor_name: string;
amount: number;
+ description: string;
+ occurred_at: string;
}[];
};
};
@@ -675,25 +679,17 @@ export interface paths {
debtor_id: string;
amount: number;
description?: string;
- occurred_at?: string;
+ occurred_at: string;
};
};
};
responses: {
- /** @description Created */
- 201: {
+ /** @description No Content */
+ 204: {
headers: {
[name: string]: unknown;
};
- content: {
- "application/json": {
- creditorId: string;
- debtorId: string;
- amount: number;
- /** Format: date */
- occurredAt: string;
- };
- };
+ content?: never;
};
/** @description Bad Request */
400: {
diff --git a/products/frontend/src/routes/GenerateGroup/index.tsx b/products/frontend/src/routes/GenerateGroup/index.tsx
index 8da7cc9..d3af381 100644
--- a/products/frontend/src/routes/GenerateGroup/index.tsx
+++ b/products/frontend/src/routes/GenerateGroup/index.tsx
@@ -80,7 +80,7 @@ const GenerateGroup: FC = () => {
: isError
? `グループの作成に失敗しました: ${error.message}`
: isSuccess
- ? 'グループの作成しました'
+ ? 'グループの作成に成功しました'
: null}
diff --git a/products/frontend/src/routes/GroupDetail/index.tsx b/products/frontend/src/routes/GroupDetail/index.tsx
index d329ea7..b735348 100644
--- a/products/frontend/src/routes/GroupDetail/index.tsx
+++ b/products/frontend/src/routes/GroupDetail/index.tsx
@@ -16,16 +16,32 @@ import { useForm, type SubmitHandler } from 'react-hook-form';
const GroupDetail: FC = () => {
// URLパラメータからgroupIdを取得
const { groupId } = useParams<{ groupId: string }>();
-
// groupIdが存在しない場合の処理
if (!groupId) return Group ID is not provided.
;
+ // コピー状態管理
+ const [copyStatus, setCopyStatus] = useState<'idle' | 'copying' | 'success' | 'error'>('idle');
+ const [inviteUrl, setInviteUrl] = useState(null);
+ // 招待URLハンドラ
+ const inviteUrlHandler = async (url: string) => {
+ try {
+ setCopyStatus('copying');
+ await navigator.clipboard.writeText(url);
+ setCopyStatus('success');
+ } catch (e) {
+ console.error(e);
+ setCopyStatus('error');
+ }
+ };
+
// グループ情報の取得
const [groupInfoResult, setGroupInfoResult] = useState(null);
const groupInfoMutation = $api.useMutation('post', `/api/group/info`, {
onSuccess: (data) => {
// グループ情報をセット
setGroupInfoResult(data);
+ // 招待URLをセット
+ setInviteUrl(`${import.meta.env.VITE_CLIENT_URL}/invite/${data.invite_id}`);
},
});
@@ -51,18 +67,12 @@ const GroupDetail: FC = () => {
},
});
- //NOTE: こいつは、どうにかする予定
- const toDatetimeLocal = (d: Date) => {
+ // react-hook-formの設定
+ const toDateInputValue = (d: Date) => {
const pad = (n: number) => String(n).padStart(2, '0');
- const yyyy = d.getFullYear();
- const mm = pad(d.getMonth() + 1);
- const dd = pad(d.getDate());
- const hh = pad(d.getHours());
- const mi = pad(d.getMinutes());
- return `${yyyy}-${mm}-${dd}T${hh}:${mi}`; // "YYYY-MM-DDTHH:mm"
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
};
- // react-hook-formの設定
const {
register,
handleSubmit,
@@ -76,7 +86,7 @@ const GroupDetail: FC = () => {
debtor_id: '',
amount: 0,
description: '',
- occurred_at: toDatetimeLocal(new Date()),
+ occurred_at: toDateInputValue(new Date()),
},
});
@@ -101,12 +111,6 @@ const GroupDetail: FC = () => {
// 貸し借り登録ハンドラ
const onSubmit: SubmitHandler = (formData) => {
- // occurred_atをISO文字列に変換(未入力の場合はundefined)
- const occurred_at =
- typeof formData.occurred_at === 'string' && formData.occurred_at !== ''
- ? new Date(formData.occurred_at).toISOString()
- : undefined;
-
debtRegisterMutation.mutate({
body: {
group_id: groupId,
@@ -114,7 +118,7 @@ const GroupDetail: FC = () => {
creditor_id: formData.creditor_id,
amount: formData.amount,
description: formData.description,
- occurred_at: occurred_at,
+ occurred_at: formData.occurred_at,
},
credentials: 'include',
});
@@ -126,7 +130,6 @@ const GroupDetail: FC = () => {
debtHistoryMutation.mutate({ body: { group_id: groupId }, credentials: 'include' });
},
});
-
// 貸し借り削除ハンドラ
const deleteGroupDebtHandler = (debtId: string) => {
deleteGroupDebtMutation.mutate({ body: { group_id: groupId, debt_id: debtId }, credentials: 'include' });
@@ -140,15 +143,21 @@ const GroupDetail: FC = () => {
{groupInfoMutation.isSuccess && groupInfoResult && (
<>
グループ名: {groupInfoResult.group_name}
- 作成者: {groupInfoResult.created_by}
+ 作成者: {groupInfoResult.created_by_name}
+ {inviteUrl && (
+
+
+
+ {copyStatus === 'success' &&
コピーしました
}
+ {copyStatus === 'error' &&
コピーに失敗しました
}
+
+ )}
メンバー一覧
{groupInfoResult.members.map((member) =>
- member ? (
- -
- {member.user_name} (ID: {member.user_id})
-
- ) : null
+ member ? - {member.user_name}
: null
)}
貸し借りの登録
@@ -199,7 +208,7 @@ const GroupDetail: FC = () => {
-
+
@@ -208,11 +217,11 @@ const GroupDetail: FC = () => {
{debtRegisterMutation.isPending
- ? 'グループの作成中...'
+ ? '貸し借りの登録中...'
: debtRegisterMutation.isError
- ? `グループの作成に失敗しました: ${debtRegisterMutation.error.message}`
+ ? `貸し借りの登録に失敗しました: ${debtRegisterMutation.error.message}`
: debtRegisterMutation.isSuccess
- ? 'グループの作成しました'
+ ? '貸し借りの登録に成功しました'
: null}
@@ -225,7 +234,8 @@ const GroupDetail: FC = () => {
{debtHistoryResult.debts.map((debt, index) => (
-
- {debt.debtor_name} さんが {debt.creditor_name} さんに {debt.amount} 円を借りています。
+ {debt.debtor_name} さんが {debt.creditor_name} さんに {debt.amount} 円を借りています。 (詳細:{' '}
+ {debt.description}、発生日時: {debt.occurred_at}
diff --git a/products/frontend/src/routes/Root/index.tsx b/products/frontend/src/routes/Root/index.tsx
index 1c5d1fc..387fedd 100644
--- a/products/frontend/src/routes/Root/index.tsx
+++ b/products/frontend/src/routes/Root/index.tsx
@@ -75,9 +75,7 @@ const Root: FC = () => {
{group.members.map((member) => (
- -
- {member.user_name} (ID: {member.user_id})
-
+ - {member.user_name}
))}
diff --git a/products/validator/src/index.ts b/products/validator/src/index.ts
index 17ee75f..46a82e1 100644
--- a/products/validator/src/index.ts
+++ b/products/validator/src/index.ts
@@ -52,7 +52,6 @@ export {
type GetGroupDebtHistoryResponseElementSchemaType,
} from './response/group';
export { getGroupDebtHistoryResponseSchema, type GetGroupDebtHistoryResponseSchemaType } from './response/group';
-export { registerGroupDebtResponseSchema, type RegisterGroupDebtResponseSchemaType } from './response/group';
// error response schema
export { errorResponseSchema, type ErrorResponseSchemaType } from './response/error';
diff --git a/products/validator/src/request/group.ts b/products/validator/src/request/group.ts
index abd9236..f9c7c41 100644
--- a/products/validator/src/request/group.ts
+++ b/products/validator/src/request/group.ts
@@ -30,7 +30,7 @@ export const registerGroupDebtRequestSchema = z.object({
debtor_id: z.string().min(1),
amount: z.number().min(0),
description: z.string().optional(),
- occurred_at: z.string().optional(),
+ occurred_at: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
});
export type RegisterGroupDebtRequestSchemaType = z.infer;
diff --git a/products/validator/src/response/group.ts b/products/validator/src/response/group.ts
index 11fe17d..c747e7d 100644
--- a/products/validator/src/response/group.ts
+++ b/products/validator/src/response/group.ts
@@ -20,7 +20,9 @@ export const getGroupInfoResponseMemberElementSchema = z.object({
export const getGroupInfoResponseSchema = z.object({
group_name: z.uuid(),
- created_by: z.string().min(1),
+ invite_id: z.string().min(1),
+ created_by_id: z.string().min(1),
+ created_by_name: z.string().min(1),
members: z.array(getGroupInfoResponseMemberElementSchema),
});
@@ -34,6 +36,8 @@ export const getGroupDebtHistoryResponseElementSchema = z.object({
creditor_id: z.string().min(1),
creditor_name: z.string().min(1),
amount: z.number().min(0),
+ description: z.string().min(0),
+ occurred_at: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
});
export const getGroupDebtHistoryResponseSchema = z.object({
@@ -42,12 +46,3 @@ export const getGroupDebtHistoryResponseSchema = z.object({
export type GetGroupDebtHistoryResponseElementSchemaType = z.infer;
export type GetGroupDebtHistoryResponseSchemaType = z.infer;
-
-export const registerGroupDebtResponseSchema = z.object({
- creditorId: z.string().min(1),
- debtorId: z.string().min(1),
- amount: z.number().min(0),
- occurredAt: z.date(),
-});
-
-export type RegisterGroupDebtResponseSchemaType = z.infer;