Skip to content

Commit af95637

Browse files
authored
Merge pull request #59 from MoDeep11/develop
feat: 전체적인 기능 추가
2 parents 10e301a + 947c4d2 commit af95637

21 files changed

Lines changed: 1607 additions & 547 deletions

src/App.jsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,49 @@ import Mypage from "./pages/MyPage.jsx";
99
import Statics from "./pages/Statics.jsx";
1010
import Home from "./pages/Home.jsx";
1111
import EditDiary from "./pages/EditDiary.jsx";
12+
import { useEffect } from "react";
13+
import { useNavigate } from "react-router-dom";
1214

1315
function App() {
16+
17+
const navigate = useNavigate();
18+
useEffect(() => {
19+
const checkTokenExpiry = () => {
20+
const token = localStorage.getItem("accessToken");
21+
const expiry = localStorage.getItem("tokenExpiry");
22+
23+
if (!token) {
24+
return;
25+
}
26+
27+
const expiryMs = Number(expiry);
28+
const now = Date.now();
29+
30+
if (!expiry || !Number.isFinite(expiryMs) || now > expiryMs) {
31+
alert("세션이 만료되었습니다. 다시 로그인해주세요.");
32+
localStorage.clear();
33+
navigate("/");
34+
}
35+
};
36+
37+
checkTokenExpiry();
38+
const interval = setInterval(checkTokenExpiry, 60000);
39+
40+
return () => clearInterval(interval);
41+
}, [navigate]);
42+
1443
return (
1544
<Routes>
1645
<Route path="/" element={<Main />} />
1746
<Route path="/login" element={<Login />} />
1847
<Route path="/signup" element={<Signup />} />
1948
<Route path="/ai/chats" element={<AiChat />} />
20-
<Route path="/diary" element={<Diary />} />
49+
<Route path="/diary/:id" element={<Diary />} />
2150
<Route path="/mypage" element={<Mypage />} />
2251
<Route path="/statics" element={<Statics />} />
2352
<Route path="/home" element={<Home />} />
2453
<Route path="/photobook" element={<Photo_book />} />
25-
<Route path="/editDiary" element={<EditDiary />} />
54+
<Route path="/editDiary/:id" element={<EditDiary />} />
2655
</Routes>
2756
);
2857
}

src/apis/auth.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const login = async (userData) => {
2020
return response.data;
2121
};
2222

23-
// apis/auth.js
2423

2524
export const logout = async (refreshToken) => {
2625
const response = await instance.post('/api/v1/auth/logout', {

src/apis/chat.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import instance from "./instance";
2+
3+
const logApiError = (label, error) => {
4+
const status = error?.response?.status;
5+
const message = error?.response?.data?.message ?? error?.message;
6+
console.error(label, { status, message });
7+
};
8+
9+
// 채팅 생성
10+
export const createChat = async (chatData) => {
11+
try {
12+
const res = await instance.post(`/api/v1/chats`, chatData);
13+
return res.data;
14+
} catch (error) {
15+
// 상세 에러 로깅
16+
logApiError("채팅 생성 실패:", error);
17+
throw error;
18+
}
19+
};
20+
21+
// 채팅 음성 생성
22+
export const createChatVoice = async (chatId, voiceData) => {
23+
try {
24+
const res = await instance.post(`/api/v1/chats/${chatId}/voice`, voiceData);
25+
return res.data;
26+
} catch (error) {
27+
logApiError("음성 생성 실패:", error);
28+
throw error;
29+
}
30+
};
31+
32+
// 채팅 메시지 전송
33+
export const createChatMessage = async (chatId, messageData) => {
34+
try {
35+
const res = await instance.post(
36+
`/api/v1/chats/${chatId}/messages`,
37+
messageData,
38+
);
39+
return res.data;
40+
} catch (error) {
41+
logApiError("메시지 전송 실패:", error);
42+
throw error;
43+
}
44+
};
45+
46+
// 채팅 수정
47+
export const updateChat = async (chatId) => {
48+
try {
49+
const res = await instance.patch(`/api/v1/chats/${chatId}`);
50+
return res.data;
51+
} catch (error) {
52+
logApiError("채팅 수정 실패:", error);
53+
throw error;
54+
}
55+
};
56+
57+
// 대화 중 사진 추가
58+
export const createChatImage = async (chatId, imageData) => {
59+
try {
60+
const res = await instance.post(
61+
`/api/v1/chats/${chatId}/images`,
62+
imageData,
63+
);
64+
return res.data;
65+
} catch (error) {
66+
logApiError("이미지 생성 실패:", error);
67+
throw error;
68+
}
69+
};
70+
71+
// AI 이미지 생성 요청
72+
export const createChatImageGeneration = async (chatId, generationData) => {
73+
try {
74+
const res = await instance.post(
75+
`/api/v1/chats/${chatId}/images/generations`,
76+
generationData,
77+
);
78+
return res.data;
79+
} catch (error) {
80+
logApiError("이미지 생성 실패:", error);
81+
throw error;
82+
}
83+
};

src/apis/diaries.js

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1-
import instance from "./axios";
1+
import instance from "./instance";
22

3-
// 일기 조회
3+
// 일기 단건 조회
44
export const getDiaries = async (id) => {
55
try {
66
const res = await instance.get(`/api/v1/diaries/${id}`);
7-
return res.data;
7+
return res.data.data;
88
} catch (error) {
99
console.error("일기 조회 실패:", error);
1010
throw error;
1111
}
1212
};
1313

14+
// 일기 목록 조회
15+
export const getDiariesList = async (params) => {
16+
try {
17+
const res = await instance.get(`/api/v1/diaries`, { params });
18+
return res.data;
19+
} catch (error) {
20+
console.error("일기 목록 조회 실패:", error);
21+
throw error;
22+
}
23+
};
24+
1425
// 일기 내용 수정
1526
export const updateDiaries = async (id, updateData) => {
1627
try {
@@ -26,15 +37,11 @@ export const updateDiaries = async (id, updateData) => {
2637
};
2738

2839
// 일기 이미지 수정
29-
export const updateDiariesImg = async (diary_id, imageFile) => {
40+
export const updateDiariesImg = async (diaryId, imageList) => {
3041
try {
31-
const formData = new FormData();
32-
formData.append("file", imageFile);
33-
3442
const res = await instance.patch(
35-
`/api/v1/diaries/${diary_id}/images`,
36-
formData,
37-
{ headers: { "Content-Type": "multipart/form-data" } },
43+
`/api/v1/diaries/${diaryId}/images`,
44+
imageList,
3845
);
3946
return res.data;
4047
} catch (error) {
@@ -44,13 +51,24 @@ export const updateDiariesImg = async (diary_id, imageFile) => {
4451
};
4552

4653
// 일기 삭제
47-
export const deleteDiaries = async (diary_id) => {
54+
export const deleteDiaries = async (diaryId) => {
4855
try {
49-
const res = await instance.delete(`/api/v1/diaries/${diary_id}`);
56+
const res = await instance.delete(`/api/v1/diaries/${diaryId}`);
5057
return res.data;
5158
} catch (error) {
5259
console.error("일기 삭제 실패:", error);
5360
throw error;
5461
}
5562
};
5663

64+
// 다이어리 추천 조회
65+
export const getDiaryRecommendation = async () => {
66+
try {
67+
const res = await instance.get(`/api/v1/diaries/recommendation`);
68+
return res.data;
69+
} catch (error) {
70+
console.error("상태 코드:", error.response?.status);
71+
console.error("에러 메시지:", error.response?.data);
72+
throw error;
73+
}
74+
};

src/apis/instance.js

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import axios from "axios";
22

33
const instance = axios.create({
44
baseURL: import.meta.env.VITE_BASE_URL,
5-
timeout: 5000,
5+
timeout: 30000,
66
headers: { "Content-Type": "application/json" },
77
});
88

@@ -22,13 +22,13 @@ const processQueue = (error, token = null) => {
2222

2323
instance.interceptors.request.use(
2424
(config) => {
25-
const accessToken = localStorage.getItem("accessToken");
26-
if (accessToken) {
27-
config.headers.Authorization = `Bearer ${accessToken}`;
25+
const token = localStorage.getItem("accessToken");
26+
if (token) {
27+
config.headers.Authorization = `Bearer ${token}`;
2828
}
2929
return config;
3030
},
31-
(error) => Promise.reject(error)
31+
(error) => Promise.reject(error),
3232
);
3333

3434
instance.interceptors.response.use(
@@ -53,43 +53,64 @@ instance.interceptors.response.use(
5353

5454
try {
5555
const refreshToken = localStorage.getItem("refreshToken");
56+
const accessToken = localStorage.getItem("accessToken");
57+
if (!refreshToken) {
58+
throw Object.assign(new Error("Missing refresh token"), {
59+
response: { status: 401 },
60+
});
61+
}
62+
63+
const refreshHeaders = accessToken
64+
? { Authorization: `Bearer ${accessToken}` }
65+
: {};
66+
5667
const res = await axios.post(
5768
`${import.meta.env.VITE_BASE_URL}/api/v1/auth/reissue`,
58-
{ refreshToken: refreshToken }
69+
{ refreshToken },
70+
{
71+
headers: refreshHeaders,
72+
timeout: instance.defaults.timeout,
73+
},
5974
);
6075

6176
if (res.data.status === "success") {
62-
const { accessToken, refreshToken: newRefreshToken } = res.data.data;
77+
const tokenPayload = res.data.data ?? res.data;
78+
const { accessToken: newAccessToken, refreshToken: newRefreshToken } =
79+
tokenPayload ?? {};
80+
if (!newAccessToken) {
81+
throw new Error("Token refresh response missing access token");
82+
}
83+
84+
localStorage.setItem("accessToken", newAccessToken);
85+
if (newRefreshToken) {
86+
localStorage.setItem("refreshToken", newRefreshToken);
87+
}
88+
localStorage.setItem("tokenExpiry", Date.now() + 60 * 60 * 1000);
6389

64-
localStorage.setItem("accessToken", accessToken);
65-
localStorage.setItem("refreshToken", newRefreshToken);
90+
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
91+
processQueue(null, newAccessToken);
6692

67-
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
68-
processQueue(null, accessToken);
69-
7093
return instance(originalRequest);
7194
} else {
72-
const failError = new Error("토큰 리프레시가 실패했어요");
73-
processQueue(failError, null);
74-
return Promise.reject(failError);
95+
throw new Error("Token refresh failed");
7596
}
7697
} catch (refreshError) {
7798
processQueue(refreshError, null);
78-
99+
79100
const status = refreshError.response?.status;
80101
if (status === 401 || status === 403) {
81-
console.warn("세션이 만료되었습니다. 다시 로그인해주세요.");
102+
console.warn("세션이 만료되어 로그아웃됩니다.");
82103
localStorage.clear();
83104
window.location.href = "/login";
84105
}
85-
86106
return Promise.reject(refreshError);
87107
} finally {
88108
isRefreshing = false;
89109
}
90110
}
111+
91112
return Promise.reject(error);
92-
}
113+
},
93114
);
94115

95-
export default instance;
116+
export default instance;

src/apis/mypages.api.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import instance from "./instance";
2+
3+
export const getMyInfo = async () => {
4+
const response = await instance.get("/api/v1/users/me");
5+
return response.data;
6+
};
7+
8+
export const withdrawAccount = async () => {
9+
const response = await instance.delete("/api/v1/auth/me");
10+
return response.data;
11+
};
12+
13+
export const updateProfile = async (formData) => {
14+
const response = await instance.patch('/api/v1/users/me/profile', formData, {
15+
headers: {
16+
"Content-Type": undefined,
17+
},
18+
});
19+
return response.data;
20+
};
21+
22+
export const changePassword = async (passwordData) => {
23+
const response = await instance.patch('/api/v1/users/me/password', passwordData);
24+
return response.data;
25+
};
26+
27+
export const deleteUser = async () => {
28+
const refreshToken = localStorage.getItem("refreshToken");
29+
const response = await instance.delete('/api/v1/users/me', {
30+
data: {
31+
refreshToken: refreshToken
32+
}
33+
});
34+
return response.data;
35+
};
36+
37+
export const logout = async () => {
38+
const refreshToken = localStorage.getItem("refreshToken");
39+
const response = await instance.post('/api/v1/auth/logout', {
40+
refreshToken: refreshToken
41+
});
42+
return response.data;
43+
};
44+
45+
export const getRandomProfile = async () => {
46+
const response = await instance.patch('/api/v1/users/me/profile-images/random');
47+
return response.data;
48+
};

src/apis/photo.api.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import instance from "./instance";
2+
3+
export const getMyGallery = async (yearMonth, tag = "") => {
4+
const response = await instance.get('/api/v1/diaries', {
5+
params: {
6+
imageType: 'MANUAL',
7+
hasPhoto: true,
8+
yearMonth: yearMonth,
9+
limit: 32,
10+
tag: tag || null,
11+
sort: 'createdAt,desc'
12+
}
13+
});
14+
return response.data;
15+
};

0 commit comments

Comments
 (0)