-
Notifications
You must be signed in to change notification settings - Fork 0
chore: refactoring backend #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
a75229b
feat: refactoring backend
Myxogastria0808 ddf82b5
chore: fix copilot suggestion
Myxogastria0808 9ca20f0
wip
Myxogastria0808 bbc90ed
chore: refactoring info usecase layer logic
Myxogastria0808 7b535a0
chore: rename info section
Myxogastria0808 6533062
chore: fix info usecase layer
Myxogastria0808 baf513d
wip
Myxogastria0808 eda66bf
Update products/backend/src/application/info.ts
Myxogastria0808 546abd2
Update products/backend/src/presentation/routes/userProfile.ts
Myxogastria0808 5f76184
Update products/backend/src/presentation/routes/userProfile.ts
Myxogastria0808 941506f
Update products/backend/src/presentation/routes/info.ts
Myxogastria0808 b1f665d
wip
Myxogastria0808 d08ab53
wip
Myxogastria0808 2417dde
Update products/backend/src/presentation/routes/userProfile.ts
Myxogastria0808 327bb2f
Update products/backend/src/presentation/routes/info.ts
Myxogastria0808 5ff3661
Merge pull request #83 from Pay-Crew/sample
Myxogastria0808 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,308 @@ | ||
| // hono | ||
| import { HTTPException } from 'hono/http-exception'; | ||
| import { Bindings } from '../types'; | ||
| // validator | ||
| import { | ||
| type JoinGroupResponseSchemaType, | ||
| type CreateGroupResponseSchemaType, | ||
| type GetGroupInfoResponseSchemaType, | ||
| type GetGroupDebtHistoryResponseSchemaType, | ||
| type GetGroupDebtHistoryResponseElementSchemaType, | ||
| } from 'validator'; | ||
| // drizzle | ||
| import { createDbConnection } from './utils/db'; | ||
| import { eq, and, isNull, isNotNull } from 'drizzle-orm'; | ||
| import { debt, group, groupMembership } from '../db/schema'; | ||
| // utils | ||
| import { validateIsGroupMember, getGroupMembers } from './utils/group'; | ||
| import { getUserInfo, getUserNameMap } from './utils/user'; | ||
|
|
||
| export const createGroupUseCase = async ( | ||
| env: Bindings, | ||
| loginUserId: string, | ||
| groupName: string | ||
| ): Promise<CreateGroupResponseSchemaType> => { | ||
| // データベース接続 | ||
| const db = createDbConnection(env); | ||
|
|
||
| //* グループ情報を挿入 *// | ||
| const result = await db | ||
| .insert(group) | ||
| .values({ | ||
| id: crypto.randomUUID(), | ||
| name: groupName, | ||
| inviteId: `${crypto.randomUUID()}-${crypto.randomUUID()}`, | ||
| createdBy: loginUserId, | ||
| }) | ||
| .returning({ | ||
| id: group.id, | ||
| inviteId: group.inviteId, | ||
| }); | ||
|
|
||
| //* グループ作成者をgroupMembershipに挿入 *// | ||
| await db.insert(groupMembership).values({ | ||
| id: crypto.randomUUID(), | ||
| groupId: result[0].id, | ||
| userId: loginUserId, | ||
| }); | ||
|
|
||
| // レスポンス | ||
| return { | ||
| group_id: result[0].id, | ||
| invite_id: result[0].inviteId, | ||
| } satisfies CreateGroupResponseSchemaType; | ||
| }; | ||
|
|
||
| export const joinGroupUseCase = async ( | ||
| env: Bindings, | ||
| loginUserId: string, | ||
| inviteId: string | ||
| ): Promise<JoinGroupResponseSchemaType> => { | ||
| // データベース接続 | ||
| const db = createDbConnection(env); | ||
|
|
||
| // invite_id から group を取得 | ||
| const groupData = await db | ||
| .select({ | ||
| id: group.id, | ||
| }) | ||
| .from(group) | ||
| .where(eq(group.inviteId, inviteId)) | ||
| .limit(1); | ||
|
|
||
| // invite_id が不正な場合はエラー | ||
| if (groupData.length === 0) { | ||
| throw new HTTPException(400, { message: 'Bad Request' }); | ||
| } | ||
|
|
||
| // すでに参加している場合は即座に返す | ||
| const existingMembership = await db | ||
| .select({ | ||
| groupMembershipId: groupMembership.id, | ||
| }) | ||
| .from(groupMembership) | ||
| .where(and(eq(groupMembership.groupId, groupData[0].id), eq(groupMembership.userId, loginUserId))) | ||
| .limit(1); | ||
|
|
||
| if (existingMembership.length > 0) { | ||
| // レスポンス | ||
| return { | ||
| group_id: groupData[0].id, | ||
| } satisfies JoinGroupResponseSchemaType; | ||
| } | ||
|
|
||
| // loginUser を invite_id の group に参加させる | ||
| const result = await db | ||
| .insert(groupMembership) | ||
| .values({ | ||
| id: crypto.randomUUID(), | ||
| groupId: groupData[0].id, | ||
| userId: loginUserId, | ||
| }) | ||
| .returning({ | ||
| groupId: groupMembership.groupId, | ||
| }); | ||
|
|
||
| // レスポンス | ||
| return { | ||
| group_id: result[0].groupId, | ||
| } satisfies JoinGroupResponseSchemaType; | ||
| }; | ||
|
|
||
| export const getGroupInfoUseCase = async ( | ||
| env: Bindings, | ||
| loginUserId: string, | ||
| groupId: string | ||
| ): Promise<GetGroupInfoResponseSchemaType> => { | ||
| // データベース接続 | ||
| const db = createDbConnection(env); | ||
|
|
||
| // loginUser が グループのメンバーであることを確認 | ||
| await validateIsGroupMember(db, groupId, loginUserId); | ||
|
|
||
| //* body.group_id のグループ情報を取得 *// | ||
| const groupData = await db | ||
| .select({ | ||
| name: group.name, | ||
| inviteId: group.inviteId, | ||
| createdBy: group.createdBy, | ||
| }) | ||
| .from(group) | ||
| .where(eq(group.id, groupId)) | ||
| .limit(1); | ||
|
|
||
| //* body.group_id のグループ作成者情報を取得 *// | ||
| // ユーザ情報を取得 | ||
| const createdByUserInfo = await getUserInfo(db, groupData[0].createdBy); | ||
|
|
||
| // グループのメンバー情報を取得 | ||
| const members = (await getGroupMembers(db, groupId)).map((member) => ({ | ||
| user_id: member.id, | ||
| user_name: member.name, | ||
| })); | ||
|
|
||
| // レスポンス | ||
| return { | ||
| group_name: groupData[0].name, | ||
| invite_id: groupData[0].inviteId, | ||
| created_by_id: groupData[0].createdBy, | ||
| created_by_name: createdByUserInfo.name, | ||
| members, | ||
| } satisfies GetGroupInfoResponseSchemaType; | ||
| }; | ||
|
|
||
| export const getGroupDebtHistoryUseCase = async ( | ||
| env: Bindings, | ||
| loginUserId: string, | ||
| groupId: string | ||
| ): Promise<GetGroupDebtHistoryResponseSchemaType> => { | ||
| // データベース接続 | ||
| const db = createDbConnection(env); | ||
|
|
||
| // loginUser がグループのメンバーであることを確認 | ||
| await validateIsGroupMember(db, groupId, loginUserId); | ||
|
|
||
| //* グループの貸し借り履歴を取得 *// | ||
| // body.group_id の貸し借り履歴を取得 (debt table) | ||
| const rawDebtData = await db | ||
| .select({ | ||
| id: debt.id, | ||
| debtorId: debt.debtorId, | ||
| creditorId: debt.creditorId, | ||
| amount: debt.amount, | ||
| description: debt.description, | ||
| occurredAt: debt.occurredAt, | ||
| deletedAt: debt.deletedAt, | ||
| deletedBy: debt.deletedBy, | ||
| }) | ||
| .from(debt) | ||
| .where(eq(debt.groupId, groupId)); | ||
|
|
||
| const userIds = rawDebtData.flatMap((debtEntry) => | ||
| [debtEntry.debtorId, debtEntry.creditorId, debtEntry.deletedBy].filter((id): id is string => id !== null) | ||
| ); | ||
|
|
||
| // ユーザ名の取得 | ||
| const uniqueUserIds = Array.from(new Set(userIds)); | ||
| const userNameMap = await getUserNameMap(db, uniqueUserIds); | ||
|
|
||
| // 貸し借り履歴データを配列に追加 | ||
| const debtData: GetGroupDebtHistoryResponseElementSchemaType[] = rawDebtData.map((debtEntry) => { | ||
| // debt_name, creditor_name を取得 | ||
| const debtorName = userNameMap.get(debtEntry.debtorId); | ||
| const creditorName = userNameMap.get(debtEntry.creditorId); | ||
| if (debtorName === undefined || creditorName === undefined) { | ||
| throw new HTTPException(500, { message: 'Internal Server Error' }); | ||
| } | ||
|
|
||
| // deleted_by_name を取得 | ||
| let deletedByName = null; | ||
| if (debtEntry.deletedBy !== null) { | ||
| // deletedByが存在する場合のみ名前を取得 | ||
| const name = userNameMap.get(debtEntry.deletedBy); | ||
| if (name === undefined) { | ||
| throw new HTTPException(500, { message: 'Internal Server Error' }); | ||
| } | ||
| deletedByName = name; | ||
| } | ||
|
|
||
| return { | ||
| debt_id: debtEntry.id, | ||
| debtor_id: debtEntry.debtorId, | ||
| debtor_name: debtorName, | ||
| creditor_id: debtEntry.creditorId, | ||
| creditor_name: creditorName, | ||
| amount: debtEntry.amount, | ||
| description: debtEntry.description === null ? '' : debtEntry.description, | ||
| occurred_at: debtEntry.occurredAt, | ||
| deleted_at: | ||
| debtEntry.deletedAt | ||
| ?.toLocaleDateString('ja-JP', { | ||
| timeZone: 'Asia/Tokyo', | ||
| year: 'numeric', | ||
| month: '2-digit', | ||
| day: '2-digit', | ||
| }) | ||
| .replace(/\//g, '-') || null, | ||
| deleted_by_id: debtEntry.deletedBy, | ||
| deleted_by_name: deletedByName, | ||
| }; | ||
| }); | ||
|
|
||
| // レスポンス | ||
| return { | ||
| debts: debtData, | ||
| } satisfies GetGroupDebtHistoryResponseSchemaType; | ||
| }; | ||
|
|
||
| export const registerGroupDebtUseCase = async ( | ||
| env: Bindings, | ||
| loginUserId: string, | ||
| groupId: string, | ||
| creditorId: string, | ||
| debtorId: string, | ||
| amount: number, | ||
| occurredAt: string, | ||
| description: string | undefined | ||
| ): Promise<void> => { | ||
| // データベース接続 | ||
| const db = createDbConnection(env); | ||
|
|
||
| // loginUser が グループのメンバーであることを確認 | ||
| await validateIsGroupMember(db, groupId, loginUserId); | ||
|
|
||
| // body.group_id に貸し借り履歴を追加 (debt table) | ||
| await db.insert(debt).values({ | ||
| id: crypto.randomUUID(), | ||
| groupId, | ||
| creditorId, | ||
| debtorId, | ||
| amount, | ||
| description: typeof description === 'undefined' ? null : description, | ||
| occurredAt, | ||
| }); | ||
| }; | ||
|
|
||
| export const deleteGroupDebtUseCase = async ( | ||
| env: Bindings, | ||
| loginUserId: string, | ||
| groupId: string, | ||
| debtId: string | ||
| ): Promise<void> => { | ||
| // データベース接続 | ||
| const db = createDbConnection(env); | ||
|
|
||
| // loginUser が グループのメンバーであることを確認 | ||
| await validateIsGroupMember(db, groupId, loginUserId); | ||
|
|
||
| //* body.debt_id の貸し借りの履歴の削除 (論理削除) *// | ||
| await db | ||
| .update(debt) | ||
| .set({ | ||
| deletedBy: loginUserId, | ||
| deletedAt: new Date(), // 現在時刻の取得 (UTC) | ||
| }) | ||
| .where(and(eq(debt.id, debtId), eq(debt.groupId, groupId), isNull(debt.deletedAt))); | ||
| }; | ||
|
|
||
| export const cancelGroupDebtUseCase = async ( | ||
| env: Bindings, | ||
| loginUserId: string, | ||
| groupId: string, | ||
| debtId: string | ||
| ): Promise<void> => { | ||
| // データベース接続 | ||
| const db = createDbConnection(env); | ||
|
|
||
| // loginUser が グループのメンバーであることを確認 | ||
| await validateIsGroupMember(db, groupId, loginUserId); | ||
|
|
||
| //* body.debt_id の貸し借りの履歴の削除の取り消し (論理削除の取り消し) *// | ||
| await db | ||
| .update(debt) | ||
| .set({ | ||
| deletedBy: null, | ||
| deletedAt: null, | ||
| }) | ||
| .where(and(eq(debt.id, debtId), eq(debt.groupId, groupId), isNotNull(debt.deletedAt))); | ||
| }; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The response sets
group_nameto the group's display name (groupData[0].name). In the validator package,getGroupInfoResponseSchemacurrently definesgroup_nameas a UUID, so the emitted OpenAPI/types will not match the actual runtime response. Align the validator schema (or the response field) sogroup_nameis validated/typed as a non-empty string.