Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | // hono
import { HTTPException } from 'hono/http-exception';
import { Bindings } from '../types';
// validator
import {
type GetInfoAboutGroupsTheUserBelongsToResponseMemberElementSchemaType,
type GetInfoAboutGroupsTheUserBelongsToResponseGroupElementSchemaType,
type GetInfoAboutGroupsTheUserBelongsToResponseSchemaType,
type GetInfoAboutUserTransactionsResponseSchemaType,
type GetInfoAboutUserTransactionsResponseTransactionElementSchemaType,
} from 'validator';
// drizzle
import { createDbConnection } from './utils/db';
import { eq, and, isNull, inArray, or } from 'drizzle-orm';
import { debt, group, groupMembership } from '../db/schema';
// types
import { TransactionType } from './utils/types';
// utils
import { getUserNameMap } from './utils/user';
import { getGroupMembers } from './utils/group';
export const getInfoAboutGroupsTheUserBelongsToUseCase = async (
env: Bindings,
loginUserId: string
): Promise<GetInfoAboutGroupsTheUserBelongsToResponseSchemaType> => {
// データベース接続
const db = createDbConnection(env);
//* ユーザが参加しているグループ情報を取得 (group table) *//
const groupData = await db
.select({
id: group.id,
name: group.name,
createdBy: group.createdBy,
})
.from(group)
.where(
inArray(
group.id,
db
.select({ groupId: groupMembership.groupId })
.from(groupMembership)
.where(eq(groupMembership.userId, loginUserId))
)
);
// 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);
if (createdByName === undefined) {
throw new HTTPException(500, { message: 'Internal Server Error' });
}
// グループメンバーの取得
const members = groupMembersMap.get(groupData.id);
if (members === undefined) {
throw new HTTPException(500, { message: 'Internal Server Error' });
}
return {
group_id: groupData.id,
group_name: groupData.name,
created_by_id: groupData.createdBy,
created_by_name: createdByName,
members: members,
};
})
);
// レスポンス
return {
groups: groupInfo,
} satisfies GetInfoAboutGroupsTheUserBelongsToResponseSchemaType;
};
export const getInfoAboutUserTransactionsUseCase = async (
env: Bindings,
loginUserId: string
): Promise<GetInfoAboutUserTransactionsResponseSchemaType> => {
// データベース接続
const db = createDbConnection(env);
//* loginUserが貸している取引履歴を取得 *//
// マイナス n 円
const creditorTransactions = await db
.select({
debtorId: debt.debtorId,
amount: debt.amount,
})
.from(debt)
.where(and(eq(debt.creditorId, loginUserId), isNull(debt.deletedAt)));
//* loginUserが借りている取引履歴を取得 *//
// プラス n 円
const debtorTransactions = await db
.select({
creditorId: debt.creditorId,
amount: debt.amount,
})
.from(debt)
.where(and(eq(debt.debtorId, loginUserId), isNull(debt.deletedAt)));
//* 取引相手ごとに集計 *//
// 集計結果を格納するMap
// Map<userId, { user_id: string; lent_amount: number; borrowed_amount: number }>
const transactions: Map<string, TransactionType> = new Map();
// 貸している取引を集計
for (const creditorTransaction of creditorTransactions) {
const userId = creditorTransaction.debtorId;
const amount = creditorTransaction.amount;
if (!transactions.has(userId)) {
transactions.set(userId, { user_id: userId, lent_amount: 0, borrowed_amount: 0 });
}
const existing = transactions.get(userId)!;
existing.lent_amount += amount;
}
// 借りている取引を集計
for (const debtorTransaction of debtorTransactions) {
const userId = debtorTransaction.creditorId;
const amount = debtorTransaction.amount;
if (!transactions.has(userId)) {
transactions.set(userId, { user_id: userId, lent_amount: 0, borrowed_amount: 0 });
}
const existing = transactions.get(userId)!;
existing.borrowed_amount += amount;
}
// ユーザ名の取得
const userIds = Array.from(transactions.keys());
const userNameMap = await getUserNameMap(db, userIds);
// 貸し借りの合算
const aggregatedTransactions: GetInfoAboutUserTransactionsResponseTransactionElementSchemaType[] = Array.from(
transactions.values()
)
.map((transaction) => {
// 合算結果: netAmount = borrowed_amount - lent_amount
const netAmount = transaction.borrowed_amount - transaction.lent_amount;
// netAmount が 0 の場合はスキップ
if (netAmount === 0) {
return null;
}
// ユーザ名の取得
const counterpartyName = userNameMap.get(transaction.user_id);
if (counterpartyName === undefined) {
throw new HTTPException(500, { message: 'Internal Server Error' });
}
return {
counterparty_id: transaction.user_id,
counterparty_name: counterpartyName,
amount: netAmount,
} as GetInfoAboutUserTransactionsResponseTransactionElementSchemaType;
})
.filter((item): item is GetInfoAboutUserTransactionsResponseTransactionElementSchemaType => item !== null);
// レスポンス
return {
transactions: aggregatedTransactions,
} satisfies GetInfoAboutUserTransactionsResponseSchemaType;
};
export const deleteInfoUserRepaymentUseCase = async (
env: Bindings,
loginUserId: string,
counterpartyId: string
): Promise<void> => {
// データベース接続
const db = createDbConnection(env);
// loginUser <-> counterparty_id 間の取引履歴を削除(deletedAt, deletedBy をセット)
await db
.update(debt)
.set({
deletedBy: loginUserId,
deletedAt: new Date(), // 現在時刻の取得 (UTC)
})
.where(
or(
and(eq(debt.creditorId, loginUserId), eq(debt.debtorId, counterpartyId), isNull(debt.deletedAt)),
and(eq(debt.debtorId, loginUserId), eq(debt.creditorId, counterpartyId), isNull(debt.deletedAt))
)
);
};
|