import axios from 'axios';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.REACT_APP_SUPABASE_URL,
process.env.REACT_APP_SUPABASE_ANON_KEY
);
const API_BASE_URL = 'http://localhost:8001';
// API 요청을 위한 axios 인스턴스 생성
const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
// 요청 인터셉터: 모든 요청에 Supabase JWT를 자동으로 추가합니다.
api.interceptors.request.use(async (config) => {
const { data: { session } } = await supabase.auth.getSession();
if (session?.access_token) {
config.headers.Authorization = `Bearer ${session.access_token}`;
}
return config;
});
// --- 지갑 관련 API ---
/**
* Supabase에서 로그인 유저의 wallet_id를 조회합니다.
* @param {string} userId - Supabase auth.users.id
* @returns {Promise<string>} 사용자의 지갑 주소(wallet_id)
*/
export const getUserWalletAddress = async (userId) => {
const { data, error } = await supabase
.from('profiles')
.select('wallet_id')
.eq('id', userId)
.single();
if (error) throw new Error('지갑 주소 조회 실패: ' + error.message);
if (!data?.wallet_id) throw new Error('지갑 주소가 등록되지 않았습니다.');
return data.wallet_id;
};
/**
* 사용자의 지갑을 생성합니다. (서버 경유 -> 체인코드 호출)
* @param {string} userId - 지갑을 생성할 사용자의 UUID
* @returns {Promise<object>} 서버 응답 객체
*/
export const createWallet = async (userId) => {
try {
const response = await api.post('/wallet/create', { userId });
return response.data;
} catch (error) {
console.error('api.js Sending wallet create request', userId);
throw error;
}
};
/**
* 특정 지갑 주소의 잔액을 조회합니다.
* @param {string} address - 잔액을 조회할 지갑 주소
* @returns {Promise<object>} 체인코드에서 반환된 잔액 정보
*/
export const getWalletBalance = async (address) => {
try {
const response = await api.get(`/getWalletBalance?address=${address}`);
return response.data;
} catch (error) {
console.error('잔액 조회 실패:', error);
throw error;
}
};
// --- P2P 대출 관련 API ---
/**
* 새로운 P2P 대출을 생성하고 계약서를 저장합니다.
* @param {object} loanData - 대출 데이터 객체
* @param {string} loanData.id - 대출 고유 ID
* @param {string} loanData.lender - 채권자 지갑 주소
* @param {string} loanData.borrower - 채무자 지갑 주소
* @param {number} loanData.amount - 대출 원금
* @param {number} loanData.durationDays - 대출 기간(일)
* @param {number} loanData.interestRate - 이자율
* @param {string} loanData.contractImage - Base64로 인코딩된 계약서 이미지
* @returns {Promise<object>} 서버 응답 객체
*/
export const createLoan = async (loanData) => {
try {
if (!loanData.contractImage) {
throw new Error('계약서 이미지가 필요합니다.');
}
const response = await api.post('/createLoan', {
id: loanData.id,
lender: loanData.lender,
borrower: loanData.borrower,
amount: loanData.amount,
durationDays: loanData.durationDays,
interestRate: loanData.interestRate
});
await downloadAndSaveContract(loanData.id, loanData.contractImage);
return {
...response.data,
message: '대출이 생성되었고 계약서가 다운로드되었습니다.'
};
} catch (error) {
console.error('대출 생성 실패:', error);
throw error;
}
};
/**
* P2P 대출을 승인합니다.
* @param {string} loanId - 승인할 대출의 ID
* @returns {Promise<object>} 서버 응답 객체
*/
export const approveLoan = async (loanId) => {
try {
const response = await api.get(`/approveLoan?id=${loanId}`);
return response.data;
} catch (error) {
console.error('대출 승인 실패:', error);
throw error;
}
};
/**
* P2P 대출을 거절합니다.
* @param {string} loanId - 거절할 대출의 ID
* @returns {Promise<object>} 서버 응답 객체
*/
export const denyLoan = async (loanId) => {
try {
const response = await api.get(`/denyLoan?id=${loanId}`);
return response.data;
} catch (error) {
console.error('대출 거절 실패:', error);
throw error;
}
};
/**
* P2P 대출금을 상환합니다.
* @param {string} loanId - 상환할 대출의 ID
* @returns {Promise<object>} 서버 응답 객체
*/
export const repayLoan = async (loanId) => {
try {
const response = await api.post('/loan/repay', { loanId });
return response.data;
} catch (error) {
console.error('상환 요청 실패:', error);
throw error;
}
};
/**
* 특정 P2P 대출 정보를 조회합니다.
* @param {string} id - 조회할 대출의 ID
* @returns {Promise<object>} 대출 상세 정보
*/
export const queryLoan = async (id) => {
try {
const response = await api.get(`/queryLoan?id=${id}`);
return response.data;
} catch (error) {
throw error;
}
};
/**
* 체인코드에 기록된 모든 P2P 대출 목록을 조회합니다.
* @returns {Promise<Array<object>>} 전체 대출 목록
*/
export const queryAllLoans = async () => {
try {
const response = await api.get('/queryAllLoans');
return response.data;
} catch (error) {
console.error('대출 조회 실패:', error);
throw error;
}
};
/**
* 특정 지갑 주소와 관련된 모든 P2P 대출 목록을 조회합니다.
* @param {string} walletAddress - 사용자의 지갑 주소
* @returns {Promise<Array<object>>} 나의 대출 목록
*/
export const queryMyLoans = async (walletAddress) => {
try {
const response = await api.get(`/myLoans?wallet=${walletAddress}`);
return response.data;
} catch (error) {
console.error('내 대출 목록 조회 실패:', error);
throw error;
}
};
// --- 대출 풀 관련 API ---
/**
* 새로운 대출 풀을 생성합니다.
* @param {object} poolData - 생성할 대출 풀의 데이터
* @returns {Promise<object>} 서버 응답 객체
*/
export const createPool = async (poolData) => {
try {
const response = await api.post('/createPool', poolData);
return response.data;
} catch (error) {
console.error('대출풀 생성 실패:', error);
throw error;
}
};
/**
* 특정 대출 풀 정보를 조회합니다.
* @param {string} id - 조회할 풀의 ID
* @returns {Promise<object>} 풀 상세 정보
*/
export const queryPool = async (id) => {
try {
const response = await api.get(`/queryPool?id=${id}`);
return response.data;
} catch (error) {
console.error('단일 풀 조회 실패:', error);
throw error;
}
};
/**
* 모든 대출 풀 목록을 조회합니다.
* @returns {Promise<Array<object>>} 전체 풀 목록
*/
export const queryAllPools = async () => {
try {
const response = await api.get('/queryAllPools');
const fixedData = (Array.isArray(response.data) ? response.data : response.data.result).map(pool => ({
...pool,
participants: Array.isArray(pool.participants) ? pool.participants : [],
weights: typeof pool.weights === 'object' && pool.weights !== null ? pool.weights : {},
}));
return fixedData;
} catch (error) {
console.error('전체 풀 조회 실패:', error);
throw error;
}
};
/**
* 대출 풀에 참여(자금 예치)합니다.
* @param {object} data - 참여 정보
* @param {string} data.poolID - 참여할 풀의 ID
* @param {string} data.userAddress - 참여자 지갑 주소
* @param {number} data.deposit - 예치할 금액
* @returns {Promise<object>} 서버 응답 객체
*/
export const joinPool = async ({ poolID, userAddress, deposit }) => {
try {
const response = await api.post('/joinPool', { poolID, userAddress, deposit });
return response.data;
} catch (error) {
console.error('풀 참여 실패:', error);
throw error;
}
};
// --- 사용자 및 프로필 API (Supabase 직접 호출) ---
/**
* 현재 로그인된 유저의 Supabase auth 정보를 가져옵니다.
* @returns {Promise<object>} Supabase 사용자 객체
*/
export async function getCurrentUser() {
const { data: { user }, error } = await supabase.auth.getUser();
if (error) throw error;
return user;
}
/**
* 특정 사용자의 상세 프로필 정보를 Supabase에서 직접 가져옵니다.
* @param {string} userId - 조회할 사용자의 UUID
* @returns {Promise<object>} 사용자의 프로필 객체
*/
export async function getUserProfile(userId) {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.single();
if (error) throw error;
return data;
}
/**
* 현재 사용자와 친구 관계이며 지갑이 있는 모든 친구 목록을 조회합니다.
* @param {string} userId - 현재 사용자의 UUID
* @returns {Promise<Array<object>>} 친구들의 프로필 객체 배열
*/
export const fetchAcceptedFriendsWithWallets = async (userId) => {
if (!userId) return [];
const { data: sent, error: sentError } = await supabase
.from('friends')
.select('friend_user_id')
.eq('user_id', userId)
.eq('status', 'accepted');
const { data: received, error: receivedError } = await supabase
.from('friends')
.select('user_id')
.eq('friend_user_id', userId)
.eq('status', 'accepted');
if (sentError || receivedError) {
throw new Error('친구 목록 조회 실패');
}
const friendIds = [
...sent.map(f => f.friend_user_id),
...received.map(f => f.user_id),
];
if (friendIds.length === 0) return [];
const { data: profiles, error: profileError } = await supabase
.from('profiles')
.select('*')
.in('id', friendIds);
if (profileError) {
throw new Error('친구 프로필 조회 실패');
}
return profiles.filter(profile => profile.wallet_id);
};
// --- 친구 관계 API ---
/**
* 다른 사용자에게 친구 추가를 요청합니다.
* @param {string} userId - 요청을 보내는 사용자의 UUID
* @param {string} friendEmail - 친구 요청을 받을 사용자의 이메일
* @returns {Promise<object>} 서버 응답 객체
*/
export async function sendFriendRequest(userId, friendEmail) {
try {
const response = await api.post('/api/friends/add', {
userId,
friendEmail,
});
return response.data;
} catch (error) {
console.error('[app.js] sendFriendRequest 실패:', error.response?.data || error.message);
throw error;
}
}
/**
* 현재 사용자의 친구 목록을 조회합니다.
* @returns {Promise<Array<object>>} 친구 목록
*/
export async function getFriendList() {
try {
const response = await api.get('/api/friends');
return response.data.friends || [];
} catch (error) {
console.error('[app.js] getFriendList 실패:', error);
throw error;
}
}
/**
* 받은 친구 요청 목록을 조회합니다.
* @returns {Promise<Array<object>>} 받은 친구 요청 목록
*/
export async function getReceivedRequests() {
try {
const response = await api.get('/api/friends/received');
return response.data.requests || [];
} catch (error) {
console.error('[app.js] getReceivedRequests 실패:', error);
throw error;
}
}
/**
* 친구 요청을 수락 또는 거절합니다.
* @param {string} requestId - 처리할 친구 요청의 ID (friends 테이블의 PK)
* @param {boolean} [accept=true] - 수락 여부 (기본값: true)
* @returns {Promise<object>} 서버 응답 객체
*/
export async function handleFriendRequest(requestId, accept = true) {
try {
const response = await api.patch('/api/friends/request', {
requestId,
status: accept ? 'accepted' : 'rejected',
});
return response.data;
} catch (error) {
console.error('[app.js] handleFriendRequest 실패:', error);
throw error;
}
}
// --- 계약서 관련 API ---
/**
* 계약서 이미지의 유효성을 검증합니다.
* @param {string} loanId - 대출 ID
* @param {string} contractImage - 검증할 계약서의 Base64 데이터
* @returns {Promise<object>} 검증 결과 객체
*/
export const verifyContract = async (loanId, contractImage) => {
try {
const response = await api.get('/api/contract/verify', {
params: { loanId },
data: { contractImage }
});
return response.data;
} catch (error) {
console.error('계약서 검증 중 오류:', error);
throw error;
}
};
/**
* 계약서 이미지의 해시를 서버에 저장합니다.
* @param {string} loanId - 대출 ID
* @param {string} contractImage - 해시를 생성할 계약서의 Base64 데이터
* @returns {Promise<object>} 저장 결과 객체
*/
export const saveContract = async (loanId, contractImage) => {
try {
const response = await api.post('/api/contract/save', {
loanId,
contractImage
});
return response.data;
} catch (error) {
console.error('계약서 저장 중 오류:', error);
throw error;
}
};
/**
* 계약서 해시를 서버에 저장하고, 사용자에게 파일을 다운로드해주는 필수 절차 함수입니다.
* @param {string} loanId - 대출 ID
* @param {string} contractImage - 계약서의 Base64 데이터
* @returns {Promise<object>} 성공 여부 및 해시값
*/
export const downloadAndSaveContract = async (loanId, contractImage) => {
try {
if (!contractImage) {
throw new Error('계약서 이미지가 필요합니다.');
}
const saveResult = await saveContract(loanId, contractImage);
if (!saveResult.success) {
throw new Error('계약서 해시 저장에 실패했습니다.');
}
function dataURLToBlob(dataUrl) {
const [header, base64Data] = dataUrl.split(',');
const binaryString = atob(base64Data);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new Blob([bytes], { type: 'image/png' });
}
const blob = dataURLToBlob(contractImage);
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `contract_${loanId}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
return {
success: true,
contractHash: saveResult.contractHash,
message: '계약서가 다운로드되었습니다.'
};
} catch (error) {
console.error('계약서 다운로드 및 저장 실패:', error);
throw error;
}
};
// --- 기타 헬퍼 API ---
/**
* 사용자의 모든 대출 관련 거래(송금, 수신) 기록을 조회합니다.
* @param {string} userId - 조회할 사용자의 UUID
* @returns {Promise<Array<object>>} 거래 기록 배열
*/
export const getMyLoanTransactions = async (userId) => {
try {
const response = await api.post('/myLoanTransactions', { userId });
return response.data.data;
} catch (error) {
console.error('내 대출 거래 기록 조회 실패:', error);
throw error;
}
};
/**
* 사용자 ID로 이름을 조회합니다.
* @param {string} userId - 조회할 사용자의 UUID
* @returns {Promise<string>} 사용자 이름
*/
export const getNameById = async (userId) => {
try {
const res = await api.post('/getName', { userId });
return res.data;
} catch (err) {
console.error('[❌ getNameById 실패]', err.response?.data || err.message);
throw err;
}
};
/**
* 대출 ID로 이자율과 기간을 조회합니다.
* @param {string} loanId - 조회할 대출의 ID
* @returns {Promise<object>} 이자율과 기간 정보 객체 { interest_rate, duration_days }
*/
export const getLoanMeta = async (loanId) => {
try {
const res = await api.post('/getLoanMeta', { loanId });
return res.data;
} catch (error) {
console.error('[❌ getLoanMeta 실패]', error.response?.data || error.message);
throw error;
}
};