opensoma 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +5 -1
- package/dist/src/agent-browser-launcher.d.ts +43 -0
- package/dist/src/agent-browser-launcher.d.ts.map +1 -0
- package/dist/src/agent-browser-launcher.js +97 -0
- package/dist/src/agent-browser-launcher.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts +36 -7
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +231 -63
- package/dist/src/client.js.map +1 -1
- package/dist/src/commands/agent-browser.d.ts +3 -0
- package/dist/src/commands/agent-browser.d.ts.map +1 -0
- package/dist/src/commands/agent-browser.js +27 -0
- package/dist/src/commands/agent-browser.js.map +1 -0
- package/dist/src/commands/auth.d.ts +1 -1
- package/dist/src/commands/auth.d.ts.map +1 -1
- package/dist/src/commands/auth.js +4 -2
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/dashboard.d.ts +13 -0
- package/dist/src/commands/dashboard.d.ts.map +1 -1
- package/dist/src/commands/dashboard.js +10 -18
- package/dist/src/commands/dashboard.js.map +1 -1
- package/dist/src/commands/helpers.d.ts +1 -1
- package/dist/src/commands/helpers.d.ts.map +1 -1
- package/dist/src/commands/helpers.js +2 -2
- package/dist/src/commands/helpers.js.map +1 -1
- package/dist/src/commands/index.d.ts +2 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +2 -1
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/mentoring.d.ts.map +1 -1
- package/dist/src/commands/mentoring.js +54 -29
- package/dist/src/commands/mentoring.js.map +1 -1
- package/dist/src/commands/notice.d.ts.map +1 -1
- package/dist/src/commands/notice.js +2 -1
- package/dist/src/commands/notice.js.map +1 -1
- package/dist/src/commands/report.d.ts.map +1 -1
- package/dist/src/commands/report.js +4 -2
- package/dist/src/commands/report.js.map +1 -1
- package/dist/src/commands/room.d.ts.map +1 -1
- package/dist/src/commands/room.js +125 -2
- package/dist/src/commands/room.js.map +1 -1
- package/dist/src/commands/schedule.d.ts +3 -0
- package/dist/src/commands/schedule.d.ts.map +1 -0
- package/dist/src/commands/schedule.js +27 -0
- package/dist/src/commands/schedule.js.map +1 -0
- package/dist/src/commands/team.d.ts.map +1 -1
- package/dist/src/commands/team.js +55 -4
- package/dist/src/commands/team.js.map +1 -1
- package/dist/src/constants.d.ts +5 -5
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +20 -8
- package/dist/src/constants.js.map +1 -1
- package/dist/src/credential-manager.d.ts +9 -0
- package/dist/src/credential-manager.d.ts.map +1 -1
- package/dist/src/credential-manager.js +24 -0
- package/dist/src/credential-manager.js.map +1 -1
- package/dist/src/formatters.d.ts +11 -3
- package/dist/src/formatters.d.ts.map +1 -1
- package/dist/src/formatters.js +281 -52
- package/dist/src/formatters.js.map +1 -1
- package/dist/src/http.d.ts +8 -0
- package/dist/src/http.d.ts.map +1 -1
- package/dist/src/http.js +29 -1
- package/dist/src/http.js.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/shared/utils/swmaestro.d.ts +34 -1
- package/dist/src/shared/utils/swmaestro.d.ts.map +1 -1
- package/dist/src/shared/utils/swmaestro.js +102 -43
- package/dist/src/shared/utils/swmaestro.js.map +1 -1
- package/dist/src/shared/utils/team-action-params.d.ts +3 -0
- package/dist/src/shared/utils/team-action-params.d.ts.map +1 -0
- package/dist/src/shared/utils/team-action-params.js +10 -0
- package/dist/src/shared/utils/team-action-params.js.map +1 -0
- package/dist/src/shared/utils/team-params.d.ts +12 -0
- package/dist/src/shared/utils/team-params.d.ts.map +1 -0
- package/dist/src/shared/utils/team-params.js +38 -0
- package/dist/src/shared/utils/team-params.js.map +1 -0
- package/dist/src/types.d.ts +147 -10
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +74 -6
- package/dist/src/types.js.map +1 -1
- package/package.json +5 -1
- package/src/agent-browser-launcher.test.ts +263 -0
- package/src/agent-browser-launcher.ts +159 -0
- package/src/cli.ts +4 -2
- package/src/client.test.ts +801 -140
- package/src/client.ts +293 -79
- package/src/commands/agent-browser.ts +33 -0
- package/src/commands/auth.test.ts +83 -32
- package/src/commands/auth.ts +5 -3
- package/src/commands/dashboard.test.ts +57 -0
- package/src/commands/dashboard.ts +22 -19
- package/src/commands/helpers.test.ts +79 -32
- package/src/commands/helpers.ts +3 -3
- package/src/commands/index.ts +2 -1
- package/src/commands/mentoring.ts +60 -29
- package/src/commands/notice.ts +2 -1
- package/src/commands/report.test.ts +7 -7
- package/src/commands/report.ts +4 -2
- package/src/commands/room.ts +160 -1
- package/src/commands/schedule.ts +32 -0
- package/src/commands/team.ts +73 -5
- package/src/constants.ts +20 -8
- package/src/credential-manager.test.ts +49 -5
- package/src/credential-manager.ts +27 -0
- package/src/formatters.test.ts +548 -53
- package/src/formatters.ts +309 -55
- package/src/http.test.ts +108 -39
- package/src/http.ts +41 -2
- package/src/index.ts +10 -1
- package/src/shared/utils/mentoring-params.test.ts +16 -16
- package/src/shared/utils/swmaestro.test.ts +326 -11
- package/src/shared/utils/swmaestro.ts +150 -52
- package/src/shared/utils/team-action-params.test.ts +32 -0
- package/src/shared/utils/team-action-params.ts +10 -0
- package/src/shared/utils/team-params.test.ts +141 -0
- package/src/shared/utils/team-params.ts +53 -0
- package/src/shared/utils/toz.test.ts +12 -7
- package/src/token-extractor.test.ts +12 -12
- package/src/toz-http.test.ts +11 -11
- package/src/types.test.ts +235 -206
- package/src/types.ts +87 -7
- package/dist/src/commands/event.d.ts +0 -3
- package/dist/src/commands/event.d.ts.map +0 -1
- package/dist/src/commands/event.js +0 -58
- package/dist/src/commands/event.js.map +0 -1
- package/src/commands/event.ts +0 -73
package/src/client.ts
CHANGED
|
@@ -12,19 +12,20 @@ import {
|
|
|
12
12
|
buildDeleteMentoringPayload,
|
|
13
13
|
buildMentoringPayload,
|
|
14
14
|
buildReportPayload,
|
|
15
|
+
buildRoomCancelPayload,
|
|
15
16
|
buildRoomReservationPayload,
|
|
17
|
+
buildRoomUpdatePayload,
|
|
16
18
|
buildUpdateMentoringPayload,
|
|
17
|
-
parseEventDetail,
|
|
18
19
|
resolveRoomId,
|
|
19
|
-
toMentoringType,
|
|
20
20
|
toRegionCode,
|
|
21
21
|
toReportTypeCd,
|
|
22
22
|
} from './shared/utils/swmaestro'
|
|
23
|
+
import { buildTeamActionPayload } from './shared/utils/team-action-params'
|
|
24
|
+
import { buildTeamListParams, type TeamSearchQuery } from './shared/utils/team-params'
|
|
23
25
|
import type {
|
|
24
26
|
ApplicationHistoryItem,
|
|
25
27
|
ApprovalListItem,
|
|
26
28
|
Dashboard,
|
|
27
|
-
EventListItem,
|
|
28
29
|
MemberInfo,
|
|
29
30
|
MentoringDetail,
|
|
30
31
|
MentoringListItem,
|
|
@@ -37,6 +38,11 @@ import type {
|
|
|
37
38
|
ReportListItem,
|
|
38
39
|
ReportUpdateOptions,
|
|
39
40
|
RoomCard,
|
|
41
|
+
RoomReservationDetail,
|
|
42
|
+
RoomReservationListItem,
|
|
43
|
+
RoomReservationStatus,
|
|
44
|
+
RoomUpdateOptions,
|
|
45
|
+
ScheduleListItem,
|
|
40
46
|
TeamInfo,
|
|
41
47
|
} from './types'
|
|
42
48
|
|
|
@@ -46,12 +52,16 @@ export interface SomaClientOptions {
|
|
|
46
52
|
username?: string
|
|
47
53
|
password?: string
|
|
48
54
|
verbose?: boolean
|
|
55
|
+
/** @internal */
|
|
56
|
+
http?: SomaHttp
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
export class SomaClient {
|
|
52
60
|
private readonly http: SomaHttp
|
|
53
61
|
private readonly options: SomaClientOptions
|
|
54
62
|
private loginCredentials: { username: string; password: string } | null
|
|
63
|
+
// Single-flight guard: SWMaestro kills a session if it sees parallel logins for it.
|
|
64
|
+
private reloginInFlight: Promise<void> | null = null
|
|
55
65
|
|
|
56
66
|
readonly mentoring: {
|
|
57
67
|
list(options?: {
|
|
@@ -69,8 +79,11 @@ export class SomaClient {
|
|
|
69
79
|
endTime: string
|
|
70
80
|
venue: string
|
|
71
81
|
maxAttendees?: number
|
|
82
|
+
receiptType?: 'UNTIL_LECTURE' | 'DIRECT'
|
|
72
83
|
regStart?: string
|
|
84
|
+
regStartTime?: string
|
|
73
85
|
regEnd?: string
|
|
86
|
+
regEndTime?: string
|
|
74
87
|
content?: string
|
|
75
88
|
}): Promise<void>
|
|
76
89
|
update(id: number, params: MentoringUpdateOptions): Promise<void>
|
|
@@ -91,6 +104,15 @@ export class SomaClient {
|
|
|
91
104
|
attendees?: number
|
|
92
105
|
notes?: string
|
|
93
106
|
}): Promise<void>
|
|
107
|
+
get(rentId: number): Promise<RoomReservationDetail>
|
|
108
|
+
update(rentId: number, params?: RoomUpdateOptions): Promise<void>
|
|
109
|
+
cancel(rentId: number): Promise<void>
|
|
110
|
+
reservations(options?: {
|
|
111
|
+
status?: Exclude<RoomReservationStatus, 'unknown'> | 'all'
|
|
112
|
+
startDate?: string
|
|
113
|
+
endDate?: string
|
|
114
|
+
page?: number
|
|
115
|
+
}): Promise<{ items: RoomReservationListItem[]; pagination: Pagination }>
|
|
94
116
|
}
|
|
95
117
|
|
|
96
118
|
readonly dashboard: {
|
|
@@ -109,7 +131,7 @@ export class SomaClient {
|
|
|
109
131
|
searchKeyword?: string
|
|
110
132
|
}): Promise<{ items: ReportListItem[]; pagination: Pagination }>
|
|
111
133
|
get(id: number): Promise<ReportDetail>
|
|
112
|
-
create(options: ReportCreateOptions,
|
|
134
|
+
create(options: ReportCreateOptions, files: Array<{ buffer: Buffer; name: string }>): Promise<void>
|
|
113
135
|
update(
|
|
114
136
|
id: number,
|
|
115
137
|
options: Omit<ReportUpdateOptions, 'id'>,
|
|
@@ -124,28 +146,30 @@ export class SomaClient {
|
|
|
124
146
|
}
|
|
125
147
|
|
|
126
148
|
readonly team: {
|
|
127
|
-
|
|
149
|
+
list(options?: { search?: TeamSearchQuery }): Promise<TeamInfo>
|
|
150
|
+
join(teamId: string): Promise<void>
|
|
151
|
+
leave(teamId: string): Promise<void>
|
|
128
152
|
}
|
|
129
153
|
|
|
130
154
|
readonly member: {
|
|
131
155
|
show(): Promise<MemberInfo>
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
readonly
|
|
135
|
-
list(options?: { page?: number }): Promise<{ items:
|
|
136
|
-
get(id: number): Promise<unknown>
|
|
137
|
-
apply(id: number): Promise<void>
|
|
158
|
+
readonly schedule: {
|
|
159
|
+
list(options?: { page?: number }): Promise<{ items: ScheduleListItem[]; pagination: Pagination }>
|
|
138
160
|
}
|
|
139
161
|
|
|
140
162
|
constructor(options: SomaClientOptions = {}) {
|
|
141
163
|
this.options = options
|
|
142
164
|
this.loginCredentials =
|
|
143
165
|
options.username && options.password ? { username: options.username, password: options.password } : null
|
|
144
|
-
this.http =
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
166
|
+
this.http =
|
|
167
|
+
options.http ??
|
|
168
|
+
new SomaHttp({
|
|
169
|
+
sessionCookie: options.sessionCookie,
|
|
170
|
+
csrfToken: options.csrfToken,
|
|
171
|
+
verbose: options.verbose,
|
|
172
|
+
})
|
|
149
173
|
|
|
150
174
|
this.mentoring = {
|
|
151
175
|
list: async (options) => {
|
|
@@ -161,9 +185,10 @@ export class SomaClient {
|
|
|
161
185
|
user,
|
|
162
186
|
}),
|
|
163
187
|
)
|
|
188
|
+
const items = formatters.parseMentoringList(html)
|
|
164
189
|
return {
|
|
165
|
-
items
|
|
166
|
-
pagination: formatters.parsePagination(html),
|
|
190
|
+
items,
|
|
191
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
167
192
|
}
|
|
168
193
|
},
|
|
169
194
|
get: async (id) => {
|
|
@@ -185,18 +210,27 @@ export class SomaClient {
|
|
|
185
210
|
},
|
|
186
211
|
update: async (id, params) => {
|
|
187
212
|
await this.requireAuth()
|
|
188
|
-
const
|
|
213
|
+
const [editHtml, viewHtml] = await Promise.all([
|
|
214
|
+
this.http.get('/mypage/mentoLec/forUpdate.do', { menuNo: MENU_NO.MENTORING, qustnrSn: String(id) }),
|
|
215
|
+
this.http.get('/mypage/mentoLec/view.do', { menuNo: MENU_NO.MENTORING, qustnrSn: String(id) }),
|
|
216
|
+
])
|
|
217
|
+
const existing = formatters.parseMentoringEditForm(editHtml, id)
|
|
218
|
+
const existingContent = formatters.parseMentoringDetail(viewHtml, id).content
|
|
219
|
+
|
|
189
220
|
const merged = buildUpdateMentoringPayload(id, {
|
|
190
221
|
title: params.title ?? existing.title,
|
|
191
|
-
type: params.type ??
|
|
192
|
-
date: params.date ?? existing.
|
|
193
|
-
startTime: params.startTime ?? existing.
|
|
194
|
-
endTime: params.endTime ?? existing.
|
|
195
|
-
venue: params.venue ?? existing.
|
|
196
|
-
maxAttendees: params.maxAttendees ?? existing.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
222
|
+
type: params.type ?? (existing.reportCd === 'MRC020' ? 'lecture' : 'public'),
|
|
223
|
+
date: params.date ?? existing.eventDt,
|
|
224
|
+
startTime: params.startTime ?? existing.eventStime,
|
|
225
|
+
endTime: params.endTime ?? existing.eventEtime,
|
|
226
|
+
venue: params.venue ?? existing.place,
|
|
227
|
+
maxAttendees: params.maxAttendees ?? existing.applyCnt,
|
|
228
|
+
receiptType: params.receiptType ?? existing.receiptType,
|
|
229
|
+
regStart: params.regStart ?? existing.bgndeDate,
|
|
230
|
+
regStartTime: params.regStartTime ?? existing.bgndeTime,
|
|
231
|
+
regEnd: params.regEnd ?? existing.enddeDate,
|
|
232
|
+
regEndTime: params.regEndTime ?? existing.enddeTime,
|
|
233
|
+
content: params.content ?? existingContent,
|
|
200
234
|
})
|
|
201
235
|
const html = await this.http.postForm('/mypage/mentoLec/update.do', merged)
|
|
202
236
|
if (this.containsErrorIndicator(html)) {
|
|
@@ -221,9 +255,10 @@ export class SomaClient {
|
|
|
221
255
|
menuNo: MENU_NO.APPLICATION_HISTORY,
|
|
222
256
|
...(options?.page ? { pageIndex: String(options.page) } : {}),
|
|
223
257
|
})
|
|
258
|
+
const items = formatters.parseApplicationHistory(html)
|
|
224
259
|
return {
|
|
225
|
-
items
|
|
226
|
-
pagination: formatters.parsePagination(html),
|
|
260
|
+
items,
|
|
261
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
227
262
|
}
|
|
228
263
|
},
|
|
229
264
|
}
|
|
@@ -275,24 +310,98 @@ export class SomaClient {
|
|
|
275
310
|
await this.requireAuth()
|
|
276
311
|
await this.http.post('/mypage/itemRent/insert.do', buildRoomReservationPayload(params))
|
|
277
312
|
},
|
|
313
|
+
get: async (rentId) => {
|
|
314
|
+
await this.requireAuth()
|
|
315
|
+
return formatters.parseRoomReservationDetail(
|
|
316
|
+
await this.http.get('/mypage/itemRent/view.do', {
|
|
317
|
+
menuNo: MENU_NO.ROOM,
|
|
318
|
+
rentId: String(rentId),
|
|
319
|
+
}),
|
|
320
|
+
)
|
|
321
|
+
},
|
|
322
|
+
update: async (rentId, params = {}) => {
|
|
323
|
+
await this.requireAuth()
|
|
324
|
+
const existing = await this.room.get(rentId)
|
|
325
|
+
await this.postRoomUpdate(buildRoomUpdatePayload(existing, params))
|
|
326
|
+
},
|
|
327
|
+
cancel: async (rentId) => {
|
|
328
|
+
await this.requireAuth()
|
|
329
|
+
const existing = await this.room.get(rentId)
|
|
330
|
+
await this.postRoomUpdate(buildRoomCancelPayload(existing))
|
|
331
|
+
},
|
|
332
|
+
reservations: async (options) => {
|
|
333
|
+
await this.requireAuth()
|
|
334
|
+
const params: Record<string, string> = {
|
|
335
|
+
menuNo: MENU_NO.ROOM,
|
|
336
|
+
pageIndex: String(options?.page ?? 1),
|
|
337
|
+
}
|
|
338
|
+
if (options?.startDate) params.sdate = options.startDate
|
|
339
|
+
if (options?.endDate) params.edate = options.endDate
|
|
340
|
+
const status = options?.status ?? 'confirmed'
|
|
341
|
+
if (status !== 'all') {
|
|
342
|
+
params.searchStat = status === 'cancelled' ? 'RS002' : 'RS001'
|
|
343
|
+
}
|
|
344
|
+
const html = await this.http.get('/mypage/itemRent/list.do', params)
|
|
345
|
+
const items = formatters.parseRoomReservationList(html)
|
|
346
|
+
return {
|
|
347
|
+
items,
|
|
348
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
349
|
+
}
|
|
350
|
+
},
|
|
278
351
|
}
|
|
279
352
|
|
|
280
353
|
this.dashboard = {
|
|
281
354
|
get: async () => {
|
|
282
355
|
await this.requireAuth()
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
356
|
+
const dashboard = formatters.parseDashboard(
|
|
357
|
+
await this.http.get('/mypage/myMain/dashboard.do', { menuNo: MENU_NO.DASHBOARD }),
|
|
358
|
+
)
|
|
359
|
+
const trainee = isTraineeRole(dashboard.role)
|
|
360
|
+
const teamSearchField = trainee ? ('member' as const) : ('mentor' as const)
|
|
361
|
+
if (trainee) {
|
|
362
|
+
const [firstPage, teams] = await Promise.all([
|
|
363
|
+
this.mentoring.history(),
|
|
364
|
+
this.team.list({ search: { field: teamSearchField, value: '@me', me: true } }),
|
|
365
|
+
])
|
|
366
|
+
const remainingPages = await Promise.all(
|
|
367
|
+
Array.from({ length: Math.max(0, firstPage.pagination.totalPages - 1) }, (_, i) =>
|
|
368
|
+
this.mentoring.history({ page: i + 2 }),
|
|
369
|
+
),
|
|
370
|
+
)
|
|
371
|
+
const historyItems = [firstPage, ...remainingPages].flatMap((p) => p.items)
|
|
372
|
+
dashboard.mentoringSessions = sortDashboardMentoringItems(
|
|
373
|
+
historyItems
|
|
374
|
+
.map(applicationHistoryToDashboardItem)
|
|
375
|
+
.filter((item): item is Dashboard['mentoringSessions'][number] => item !== null),
|
|
376
|
+
)
|
|
377
|
+
dashboard.teams = teams.teams
|
|
378
|
+
return dashboard
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const search = { field: 'author' as const, value: '@me', me: true }
|
|
382
|
+
const [firstPage, teams] = await Promise.all([
|
|
383
|
+
this.mentoring.list({ search }),
|
|
384
|
+
this.team.list({ search: { field: teamSearchField, value: '@me', me: true } }),
|
|
286
385
|
])
|
|
287
|
-
dashboard
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
386
|
+
// Exhaust pagination: dashboard time totals must span the whole month, not just page 1.
|
|
387
|
+
const remainingPages = await Promise.all(
|
|
388
|
+
Array.from({ length: Math.max(0, firstPage.pagination.totalPages - 1) }, (_, i) =>
|
|
389
|
+
this.mentoring.list({ search, page: i + 2 }),
|
|
390
|
+
),
|
|
391
|
+
)
|
|
392
|
+
const myMentoring = [firstPage, ...remainingPages].flatMap((p) => p.items)
|
|
393
|
+
dashboard.mentoringSessions = sortDashboardMentoringItems(
|
|
394
|
+
myMentoring.map((item) => ({
|
|
395
|
+
title: item.title,
|
|
396
|
+
url: `/mypage/mentoLec/view.do?qustnrSn=${item.id}`,
|
|
397
|
+
status: item.status,
|
|
398
|
+
date: item.sessionDate,
|
|
399
|
+
time: item.sessionTime.start,
|
|
400
|
+
timeEnd: item.sessionTime.end,
|
|
401
|
+
type: item.type,
|
|
402
|
+
})),
|
|
403
|
+
)
|
|
404
|
+
dashboard.teams = teams.teams
|
|
296
405
|
return dashboard
|
|
297
406
|
},
|
|
298
407
|
}
|
|
@@ -304,9 +413,10 @@ export class SomaClient {
|
|
|
304
413
|
menuNo: MENU_NO.NOTICE,
|
|
305
414
|
...(options?.page ? { pageIndex: String(options.page) } : {}),
|
|
306
415
|
})
|
|
416
|
+
const items = formatters.parseNoticeList(html)
|
|
307
417
|
return {
|
|
308
|
-
items
|
|
309
|
-
pagination: formatters.parsePagination(html),
|
|
418
|
+
items,
|
|
419
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
310
420
|
}
|
|
311
421
|
},
|
|
312
422
|
get: async (id) => {
|
|
@@ -331,9 +441,10 @@ export class SomaClient {
|
|
|
331
441
|
if (options?.searchField !== undefined) params.searchCnd = options.searchField
|
|
332
442
|
if (options?.searchKeyword) params.searchWrd = options.searchKeyword
|
|
333
443
|
const html = await this.http.get('/mypage/mentoringReport/list.do', params)
|
|
444
|
+
const items = formatters.parseReportList(html)
|
|
334
445
|
return {
|
|
335
|
-
items
|
|
336
|
-
pagination: formatters.parsePagination(html),
|
|
446
|
+
items,
|
|
447
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
337
448
|
}
|
|
338
449
|
},
|
|
339
450
|
get: async (id) => {
|
|
@@ -344,7 +455,7 @@ export class SomaClient {
|
|
|
344
455
|
})
|
|
345
456
|
return formatters.parseReportDetail(html, id)
|
|
346
457
|
},
|
|
347
|
-
create: async (options,
|
|
458
|
+
create: async (options, files) => {
|
|
348
459
|
await this.requireAuth()
|
|
349
460
|
const payload = buildReportPayload({
|
|
350
461
|
menteeRegion: options.menteeRegion,
|
|
@@ -369,11 +480,11 @@ export class SomaClient {
|
|
|
369
480
|
for (const [key, value] of Object.entries(payload)) {
|
|
370
481
|
formData.append(key, value)
|
|
371
482
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
483
|
+
for (let i = 0; i < files.length; i++) {
|
|
484
|
+
const { buffer, name } = files[i]
|
|
485
|
+
const uint8Array = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
|
|
486
|
+
formData.append(`file_1_${i + 1}`, new Blob([uint8Array as unknown as ArrayBuffer]), name)
|
|
487
|
+
}
|
|
377
488
|
formData.append('fileFieldNm_1', 'file_1')
|
|
378
489
|
formData.append('atchFileId', '')
|
|
379
490
|
await this.http.postMultipart('/mypage/mentoringReport/insert.do', formData)
|
|
@@ -425,17 +536,27 @@ export class SomaClient {
|
|
|
425
536
|
if (options?.month) params.searchMonth = options.month
|
|
426
537
|
if (options?.reportType !== undefined) params.searchReport = options.reportType
|
|
427
538
|
const html = await this.http.get('/mypage/mentoringReport/resultList.do', params)
|
|
539
|
+
const items = formatters.parseApprovalList(html)
|
|
428
540
|
return {
|
|
429
|
-
items
|
|
430
|
-
pagination: formatters.parsePagination(html),
|
|
541
|
+
items,
|
|
542
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
431
543
|
}
|
|
432
544
|
},
|
|
433
545
|
}
|
|
434
546
|
|
|
435
547
|
this.team = {
|
|
436
|
-
|
|
548
|
+
list: async (options) => {
|
|
437
549
|
await this.requireAuth()
|
|
438
|
-
|
|
550
|
+
const user = options?.search?.me ? await this.resolveUser() : undefined
|
|
551
|
+
return formatters.parseTeamInfo(
|
|
552
|
+
await this.http.get('/mypage/myTeam/team.do', buildTeamListParams({ search: options?.search, user })),
|
|
553
|
+
)
|
|
554
|
+
},
|
|
555
|
+
join: async (teamId) => {
|
|
556
|
+
await this.postTeamAction('/mypage/myTeam/updateUserTeamIn.json', teamId, '팀 참여에 실패했습니다.')
|
|
557
|
+
},
|
|
558
|
+
leave: async (teamId) => {
|
|
559
|
+
await this.postTeamAction('/mypage/myTeam/updateUserTeamOut.json', teamId, '팀 탈퇴에 실패했습니다.')
|
|
439
560
|
},
|
|
440
561
|
}
|
|
441
562
|
|
|
@@ -448,30 +569,14 @@ export class SomaClient {
|
|
|
448
569
|
},
|
|
449
570
|
}
|
|
450
571
|
|
|
451
|
-
this.
|
|
572
|
+
this.schedule = {
|
|
452
573
|
list: async (options) => {
|
|
453
574
|
await this.requireAuth()
|
|
454
|
-
const html = await this.http.get('/mypage/
|
|
455
|
-
menuNo: MENU_NO.
|
|
575
|
+
const html = await this.http.get('/mypage/schedule/list.do', {
|
|
576
|
+
menuNo: MENU_NO.SCHEDULE,
|
|
456
577
|
...(options?.page ? { pageIndex: String(options.page) } : {}),
|
|
457
578
|
})
|
|
458
|
-
return
|
|
459
|
-
items: formatters.parseEventList(html),
|
|
460
|
-
pagination: formatters.parsePagination(html),
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
get: async (id) => {
|
|
464
|
-
await this.requireAuth()
|
|
465
|
-
return parseEventDetail(
|
|
466
|
-
await this.http.get('/mypage/applicants/view.do', {
|
|
467
|
-
menuNo: MENU_NO.EVENT,
|
|
468
|
-
bbsId: String(id),
|
|
469
|
-
}),
|
|
470
|
-
)
|
|
471
|
-
},
|
|
472
|
-
apply: async (id) => {
|
|
473
|
-
await this.requireAuth()
|
|
474
|
-
await this.http.post('/application/application/application.do', buildApplicationPayload(id))
|
|
579
|
+
return formatters.parseScheduleList(html)
|
|
475
580
|
},
|
|
476
581
|
}
|
|
477
582
|
}
|
|
@@ -486,7 +591,7 @@ export class SomaClient {
|
|
|
486
591
|
private async requireAuth(): Promise<void> {
|
|
487
592
|
let identity = await this.http.checkLogin()
|
|
488
593
|
if (!identity && this.loginCredentials) {
|
|
489
|
-
await this.
|
|
594
|
+
await this.relogin()
|
|
490
595
|
identity = await this.http.checkLogin()
|
|
491
596
|
}
|
|
492
597
|
|
|
@@ -495,6 +600,19 @@ export class SomaClient {
|
|
|
495
600
|
}
|
|
496
601
|
}
|
|
497
602
|
|
|
603
|
+
private async relogin(): Promise<void> {
|
|
604
|
+
if (!this.loginCredentials) {
|
|
605
|
+
throw new AuthenticationError()
|
|
606
|
+
}
|
|
607
|
+
if (!this.reloginInFlight) {
|
|
608
|
+
const { username, password } = this.loginCredentials
|
|
609
|
+
this.reloginInFlight = this.http.login(username, password).finally(() => {
|
|
610
|
+
this.reloginInFlight = null
|
|
611
|
+
})
|
|
612
|
+
}
|
|
613
|
+
await this.reloginInFlight
|
|
614
|
+
}
|
|
615
|
+
|
|
498
616
|
private async resolveUser(): Promise<UserIdentity | undefined> {
|
|
499
617
|
const identity = await this.http.checkLogin()
|
|
500
618
|
return identity ?? undefined
|
|
@@ -519,6 +637,10 @@ export class SomaClient {
|
|
|
519
637
|
return Boolean(await this.http.checkLogin())
|
|
520
638
|
}
|
|
521
639
|
|
|
640
|
+
async whoami(): Promise<UserIdentity | null> {
|
|
641
|
+
return this.http.checkLogin()
|
|
642
|
+
}
|
|
643
|
+
|
|
522
644
|
async logout(): Promise<void> {
|
|
523
645
|
await this.http.logout()
|
|
524
646
|
}
|
|
@@ -540,6 +662,30 @@ export class SomaClient {
|
|
|
540
662
|
})
|
|
541
663
|
}
|
|
542
664
|
|
|
665
|
+
private async postTeamAction(path: string, teamId: string, fallbackMessage: string): Promise<void> {
|
|
666
|
+
await this.requireAuth()
|
|
667
|
+
const user = await this.resolveUser()
|
|
668
|
+
if (!user) throw new AuthenticationError()
|
|
669
|
+
if (!user.userNo) {
|
|
670
|
+
throw new Error('현재 사용자의 userNo를 확인할 수 없습니다.')
|
|
671
|
+
}
|
|
672
|
+
const response = await this.http.postJson<{ resultCode?: string }>(path, buildTeamActionPayload(teamId, user))
|
|
673
|
+
if (response.resultCode !== 'success') {
|
|
674
|
+
throw new Error(fallbackMessage)
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private async postRoomUpdate(payload: Record<string, string>): Promise<void> {
|
|
679
|
+
try {
|
|
680
|
+
await this.http.post('/mypage/itemRent/update.do', payload)
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if (error instanceof Error && isSuccessAlertMessage(error.message)) {
|
|
683
|
+
return
|
|
684
|
+
}
|
|
685
|
+
throw error
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
543
689
|
private containsErrorIndicator(html: string): boolean {
|
|
544
690
|
const errorPatterns = [
|
|
545
691
|
'class="error"',
|
|
@@ -550,14 +696,20 @@ export class SomaClient {
|
|
|
550
696
|
'실패하였습니다',
|
|
551
697
|
'잘못된 접근',
|
|
552
698
|
'권한이 없습니다',
|
|
553
|
-
'<script>alert(',
|
|
554
699
|
]
|
|
555
|
-
|
|
700
|
+
if (errorPatterns.some((pattern) => html.includes(pattern))) {
|
|
701
|
+
return true
|
|
702
|
+
}
|
|
703
|
+
const alertMatch = html.match(/<script[^>]*>\s*alert\(['"](.+?)['"]\)/)
|
|
704
|
+
if (alertMatch && !isSuccessAlertMessage(alertMatch[1])) {
|
|
705
|
+
return true
|
|
706
|
+
}
|
|
707
|
+
return false
|
|
556
708
|
}
|
|
557
709
|
|
|
558
710
|
private extractErrorMessage(html: string): string | null {
|
|
559
|
-
const alertMatch = html.match(/<script
|
|
560
|
-
if (alertMatch) {
|
|
711
|
+
const alertMatch = html.match(/<script[^>]*>\s*alert\(['"](.+?)['"]\)/)
|
|
712
|
+
if (alertMatch && !isSuccessAlertMessage(alertMatch[1])) {
|
|
561
713
|
return alertMatch[1]
|
|
562
714
|
}
|
|
563
715
|
const errorDivMatch = html.match(/class="error[^"]*"[^>]*>\s*([^<]+)/)
|
|
@@ -567,3 +719,65 @@ export class SomaClient {
|
|
|
567
719
|
return null
|
|
568
720
|
}
|
|
569
721
|
}
|
|
722
|
+
|
|
723
|
+
function isTraineeRole(role: string): boolean {
|
|
724
|
+
return role.includes('연수생')
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function applicationHistoryToDashboardItem(
|
|
728
|
+
item: ApplicationHistoryItem,
|
|
729
|
+
): Dashboard['mentoringSessions'][number] | null {
|
|
730
|
+
if (item.applicationStatus.includes('취소')) return null
|
|
731
|
+
|
|
732
|
+
const type = applicationCategoryToMentoringType(item.category)
|
|
733
|
+
if (!type) return null
|
|
734
|
+
|
|
735
|
+
const { date, time, timeEnd } = parseApplicationSessionDate(item.sessionDate)
|
|
736
|
+
if (date && date < new Date().toISOString().slice(0, 10)) return null
|
|
737
|
+
|
|
738
|
+
return {
|
|
739
|
+
title: item.title,
|
|
740
|
+
url: item.url ?? '/mentoring/history',
|
|
741
|
+
status: item.applicationStatus,
|
|
742
|
+
...(date ? { date } : {}),
|
|
743
|
+
...(time ? { time } : {}),
|
|
744
|
+
...(timeEnd ? { timeEnd } : {}),
|
|
745
|
+
type,
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function sortDashboardMentoringItems(items: Dashboard['mentoringSessions']): Dashboard['mentoringSessions'] {
|
|
750
|
+
return [...items].sort((a, b) => dashboardMentoringSortKey(a).localeCompare(dashboardMentoringSortKey(b)))
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function dashboardMentoringSortKey(item: Dashboard['mentoringSessions'][number]): string {
|
|
754
|
+
return `${item.date || '9999-12-31'} ${item.time || '99:99'}`
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function applicationCategoryToMentoringType(category: string): '자유 멘토링' | '멘토 특강' | null {
|
|
758
|
+
const compact = category.replace(/\s+/g, '')
|
|
759
|
+
if (compact.includes('특강')) return '멘토 특강'
|
|
760
|
+
if (compact.includes('멘토링')) return '자유 멘토링'
|
|
761
|
+
return null
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function parseApplicationSessionDate(value: string): { date?: string; time?: string; timeEnd?: string } {
|
|
765
|
+
const date = value.match(/\d{4}-\d{2}-\d{2}/)?.[0]
|
|
766
|
+
const times = value.match(/\d{1,2}:\d{2}(?::\d{2})?/g)?.map(normalizeDashboardTime) ?? []
|
|
767
|
+
return {
|
|
768
|
+
...(date ? { date } : {}),
|
|
769
|
+
...(times[0] ? { time: times[0] } : {}),
|
|
770
|
+
...(times[1] ? { timeEnd: times[1] } : {}),
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function normalizeDashboardTime(value: string): string {
|
|
775
|
+
const [hours = '', minutes = ''] = value.split(':')
|
|
776
|
+
return `${hours.padStart(2, '0')}:${minutes}`
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function isSuccessAlertMessage(message: string): boolean {
|
|
780
|
+
return /정상적으로|등록\s?하였습니다|등록\s?되었습니다|수정\s?하였습니다|수정\s?되었습니다|저장\s?되었습니다|완료\s?되었습니다|삭제\s?되었습니다|취소\s?되었습니다/.test(
|
|
781
|
+
message,
|
|
782
|
+
)
|
|
783
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { AgentBrowserLauncher } from '../agent-browser-launcher'
|
|
4
|
+
import { handleError } from '../shared/utils/error-handler'
|
|
5
|
+
import { createAuthenticatedHttp } from './helpers'
|
|
6
|
+
|
|
7
|
+
type LaunchOptions = { binary?: string }
|
|
8
|
+
|
|
9
|
+
async function launchAction(url: string, options: LaunchOptions): Promise<void> {
|
|
10
|
+
try {
|
|
11
|
+
const http = await createAuthenticatedHttp()
|
|
12
|
+
const sessionCookie = http.getSessionCookie()
|
|
13
|
+
if (!sessionCookie) {
|
|
14
|
+
throw new Error('Authenticated session is missing a session cookie. Run: opensoma auth login')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const launcher = new AgentBrowserLauncher({ binary: options.binary })
|
|
18
|
+
const { exitCode } = await launcher.launch({ url, sessionCookie })
|
|
19
|
+
process.exit(exitCode)
|
|
20
|
+
} catch (error) {
|
|
21
|
+
handleError(error)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const agentBrowserCommand = new Command('agent-browser')
|
|
26
|
+
.description('Launch agent-browser pre-authenticated to swmaestro.ai')
|
|
27
|
+
.addCommand(
|
|
28
|
+
new Command('launch')
|
|
29
|
+
.description('Open a swmaestro.ai URL in agent-browser with the current opensoma session injected')
|
|
30
|
+
.argument('<url>', 'swmaestro.ai URL to open')
|
|
31
|
+
.option('--binary <path>', 'Path to the agent-browser executable (default: agent-browser on PATH)')
|
|
32
|
+
.action(launchAction),
|
|
33
|
+
)
|