Skip to content

Commit cb81aa2

Browse files
authored
Merge pull request #96 from 100-hours-a-week/feature/chat
feat: 이력서 내 인적 사항 추가 및 이력서 수정 구현
2 parents 2cda148 + 330c402 commit cb81aa2

File tree

9 files changed

+730
-239
lines changed

9 files changed

+730
-239
lines changed

package-lock.json

Lines changed: 5 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/api/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export const API_CONFIG = {
2424
RESUME_RENAME: (id) => `/resumes/${id}/name`,
2525
RESUME_VERSION: (id, versionNo) => `/resumes/${id}/versions/${versionNo}`,
2626
RESUME_AI_CHAT: (id) => `/resumes/${id}/chat`,
27+
RESUME_EDIT: (id) => `/resumes/${id}`,
28+
RESUME_SSE_STREAM: (id) => `/resumes/${id}/stream`,
2729

2830
// Interviews
2931
INTERVIEWS: '/interviews',

src/app/api/endpoints/resumes.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,17 @@ export const streamResumeChat = async (id, message, options) => {
9191
options
9292
);
9393
};
94+
95+
/**
96+
* Edit resume with AI assistant (PATCH request)
97+
* @param {number} resumeId - Resume ID
98+
* @param {string} message - User edit request message
99+
* @returns {Promise<{resumeId, versionNo, name, taskId, updatedAt}>}
100+
*/
101+
export const editResume = async (resumeId, message) => {
102+
const response = await mutatingClient.patch(
103+
API_CONFIG.ENDPOINTS.RESUME_EDIT(resumeId),
104+
{ message }
105+
);
106+
return response.data.data;
107+
};

src/app/api/sseClient.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* SSE Client using EventSource API
3+
* Handles Server-Sent Events with HttpOnly cookie authentication
4+
*/
5+
export class SSEClient {
6+
constructor(url, options = {}) {
7+
this.url = url;
8+
this.options = options;
9+
this.eventSource = null;
10+
this.eventListeners = new Map(); // event name -> callback
11+
this.onOpen = options.onOpen;
12+
this.onError = options.onError;
13+
this.isConnected = false;
14+
}
15+
16+
connect() {
17+
this.eventSource = new EventSource(this.url, { withCredentials: true });
18+
19+
this.eventSource.onopen = () => {
20+
this.isConnected = true;
21+
this.onOpen?.();
22+
};
23+
24+
this.eventSource.onerror = (error) => {
25+
this.isConnected = false;
26+
this.onError?.(error);
27+
};
28+
29+
this.eventSource.onmessage = () => {
30+
// Generic message handler (not used)
31+
};
32+
33+
// Register all pre-configured event listeners
34+
this.eventListeners.forEach((callback, eventName) => {
35+
this.eventSource.addEventListener(eventName, callback);
36+
});
37+
}
38+
39+
addEventListener(eventName, callback) {
40+
const wrappedCallback = (event) => {
41+
try {
42+
const data = JSON.parse(event.data);
43+
callback(data);
44+
} catch (error) {
45+
console.error('[SSEClient] Event parse error:', error);
46+
}
47+
};
48+
49+
this.eventListeners.set(eventName, wrappedCallback);
50+
51+
if (this.eventSource) {
52+
this.eventSource.addEventListener(eventName, wrappedCallback);
53+
}
54+
}
55+
56+
close() {
57+
if (this.eventSource) {
58+
this.eventSource.close();
59+
this.eventSource = null;
60+
this.isConnected = false;
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)