Skip to content

chore: refactoring backend#84

Merged
Myxogastria0808 merged 16 commits intomainfrom
dev
Feb 9, 2026
Merged

chore: refactoring backend#84
Myxogastria0808 merged 16 commits intomainfrom
dev

Conversation

@Myxogastria0808
Copy link
Copy Markdown
Member

No description provided.

Myxogastria0808 and others added 16 commits February 10, 2026 00:28
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
chore: fix info section layer
Copilot AI review requested due to automatic review settings February 9, 2026 18:24
@Myxogastria0808 Myxogastria0808 merged commit 5317dfd into main Feb 9, 2026
12 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors backend route handlers to move business logic into a dedicated application layer (use cases + shared helpers), while updating validator exports for the /api/info/* responses and adding 404 to the shared OpenAPI error schema.

Changes:

  • Migrate DB/query logic out of presentation routes into products/backend/src/application/* use cases and shared utilities.
  • Add a new protected session route module and replace the old check route wiring.
  • Rename info response schemas/types in the validator package (and adjust an amount constraint).

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
products/validator/src/response/info.ts Renames info response schemas/types and loosens amount validation.
products/validator/src/index.ts Re-exports renamed info response schemas/types.
products/backend/src/presentation/utils/types.ts Removes presentation-layer TransactionType (moved to application layer).
products/backend/src/presentation/share/error.ts Adds 404 to shared OpenAPI error responses.
products/backend/src/presentation/routes/userProfile.ts Delegates profile read/update to application use cases; adds 404 to schema for GET.
products/backend/src/presentation/routes/session.ts New session-check route delegating to application use case.
products/backend/src/presentation/routes/info.ts Delegates info endpoints to application use cases.
products/backend/src/presentation/routes/group.ts Delegates group endpoints to application use cases.
products/backend/src/presentation/routes/check.ts Removes old session-check route implementation.
products/backend/src/presentation/api.protected.ts Switches routing from check to new session route module.
products/backend/src/application/utils/user.ts Adds shared user name formatting + user lookup helpers.
products/backend/src/application/utils/types.ts Adds shared application-layer types (DB, transaction aggregation, user info).
products/backend/src/application/utils/group.ts Adds group membership validation + group member fetch helper.
products/backend/src/application/utils/db.ts Centralizes Drizzle DB connection creation.
products/backend/src/application/userProfile.ts Adds profile use cases for get/update.
products/backend/src/application/session.ts Adds session-check use case.
products/backend/src/application/info.ts Adds info endpoints’ use cases (groups list, transactions aggregation, repayment delete).
products/backend/src/application/group.ts Adds group endpoints’ use cases (create/join/info/debt history/register/delete/cancel).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +24 to +26
// ユーザが存在しない場合はエラー
if (userNameInfo.length === 0) {
throw new HTTPException(500, { message: 'Internal Server Error' });
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getUserInfo throws an HTTP 500 when the user row is missing. This same refactor introduces a 404 path for missing users in getUserProfileUseCase, so this behavior is inconsistent and can surface as an unexpected 500 (e.g., if the session references a deleted user). Consider returning a 404 (or 401) here and updating any route schemas that rely on this helper accordingly.

Suggested change
// ユーザが存在しない場合はエラー
if (userNameInfo.length === 0) {
throw new HTTPException(500, { message: 'Internal Server Error' });
// ユーザが存在しない場合は 404 を返す
if (userNameInfo.length === 0) {
throw new HTTPException(404, { message: 'User not found' });

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +73
// createdByのユーザ名の取得
const uniqueCreatedByIds = Array.from(new Set(groupData.map((group) => group.createdBy)));
const createdByNameMap = await getUserNameMap(db, uniqueCreatedByIds);

// グループメンバーのユーザ名の取得
const uniqueGroupIds = Array.from(new Set(groupData.map((group) => group.id)));
const groupMembersEntries = await Promise.all(
uniqueGroupIds.map(async (groupId) => {
const members = await getGroupMembers(db, groupId);
const formattedMembers = members.map((member) => {
return {
user_id: member.id,
user_name: member.name,
} as GetInfoAboutGroupsTheUserBelongsToResponseMemberElementSchemaType;
});
return [groupId, formattedMembers] as const;
})
);
const groupMembersMap: Map<string, GetInfoAboutGroupsTheUserBelongsToResponseMemberElementSchemaType[]> = new Map(
groupMembersEntries
);

// グループ情報の整形
const groupInfo: GetInfoAboutGroupsTheUserBelongsToResponseGroupElementSchemaType[] = await Promise.all(
groupData.map(async (groupData) => {
// createdByのユーザ名取得
const createdByName = createdByNameMap.get(groupData.createdBy);
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getInfoAboutGroupsTheUserBelongsToUseCase calls getGroupMembers once per group ID. Since getGroupMembers performs multiple DB queries, total round-trips scale with the number of groups (N+1 style at the group level). Consider batching memberships for all groups in one query and resolving user names in one getUserNameMap call to keep query count bounded.

Suggested change
// createdByのユーザ名の取得
const uniqueCreatedByIds = Array.from(new Set(groupData.map((group) => group.createdBy)));
const createdByNameMap = await getUserNameMap(db, uniqueCreatedByIds);
// グループメンバーのユーザ名の取得
const uniqueGroupIds = Array.from(new Set(groupData.map((group) => group.id)));
const groupMembersEntries = await Promise.all(
uniqueGroupIds.map(async (groupId) => {
const members = await getGroupMembers(db, groupId);
const formattedMembers = members.map((member) => {
return {
user_id: member.id,
user_name: member.name,
} as GetInfoAboutGroupsTheUserBelongsToResponseMemberElementSchemaType;
});
return [groupId, formattedMembers] as const;
})
);
const groupMembersMap: Map<string, GetInfoAboutGroupsTheUserBelongsToResponseMemberElementSchemaType[]> = new Map(
groupMembersEntries
);
// グループ情報の整形
const groupInfo: GetInfoAboutGroupsTheUserBelongsToResponseGroupElementSchemaType[] = await Promise.all(
groupData.map(async (groupData) => {
// createdByのユーザ名取得
const createdByName = createdByNameMap.get(groupData.createdBy);
// グループメンバー情報の取得(N+1 クエリを避けるため一括で取得)
const uniqueGroupIds = Array.from(new Set(groupData.map((group) => group.id)));
const groupMemberships = await db
.select({
groupId: groupMembership.groupId,
userId: groupMembership.userId,
})
.from(groupMembership)
.where(inArray(groupMembership.groupId, uniqueGroupIds));
// createdBy と メンバー両方のユーザ名を一括で取得
const uniqueCreatedByIds = Array.from(new Set(groupData.map((group) => group.createdBy)));
const memberUserIds = Array.from(new Set(groupMemberships.map((membership) => membership.userId)));
const allUserIds = Array.from(new Set([...uniqueCreatedByIds, ...memberUserIds]));
const userNameMap = await getUserNameMap(db, allUserIds);
// グループごとのメンバー一覧を構築
const groupMembersMap: Map<
string,
GetInfoAboutGroupsTheUserBelongsToResponseMemberElementSchemaType[]
> = new Map();
for (const membership of groupMemberships) {
const userName = userNameMap.get(membership.userId);
if (userName === undefined) {
throw new HTTPException(500, { message: 'Internal Server Error' });
}
const memberList =
groupMembersMap.get(membership.groupId) ?? [];
const member: GetInfoAboutGroupsTheUserBelongsToResponseMemberElementSchemaType = {
user_id: membership.userId,
user_name: userName,
};
groupMembersMap.set(membership.groupId, [...memberList, member]);
}
// グループ情報の整形
const groupInfo: GetInfoAboutGroupsTheUserBelongsToResponseGroupElementSchemaType[] = await Promise.all(
groupData.map(async (groupData) => {
// createdByのユーザ名取得
const createdByName = userNameMap.get(groupData.createdBy);

Copilot uses AI. Check for mistakes.
Comment on lines +145 to +149
return {
group_name: groupData[0].name,
invite_id: groupData[0].inviteId,
created_by_id: groupData[0].createdBy,
created_by_name: createdByUserInfo.name,
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response sets group_name to the group's display name (groupData[0].name). In the validator package, getGroupInfoResponseSchema currently defines group_name as a UUID, so the emitted OpenAPI/types will not match the actual runtime response. Align the validator schema (or the response field) so group_name is validated/typed as a non-empty string.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants