opensoma 0.5.1 → 0.7.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 +8 -5
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts +34 -7
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +224 -52
- 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 +3 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +3 -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/commands/toz.d.ts +16 -0
- package/dist/src/commands/toz.d.ts.map +1 -0
- package/dist/src/commands/toz.js +488 -0
- package/dist/src/commands/toz.js.map +1 -0
- 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 +15 -0
- package/dist/src/credential-manager.d.ts.map +1 -1
- package/dist/src/credential-manager.js +46 -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 +8 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/session-recovery.js +2 -0
- package/dist/src/session-recovery.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 -39
- 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/toz-client.d.ts +89 -0
- package/dist/src/toz-client.d.ts.map +1 -0
- package/dist/src/toz-client.js +204 -0
- package/dist/src/toz-client.js.map +1 -0
- package/dist/src/toz-pending-store.d.ts +30 -0
- package/dist/src/toz-pending-store.d.ts.map +1 -0
- package/dist/src/toz-pending-store.js +36 -0
- package/dist/src/toz-pending-store.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 +10 -5
- package/src/client.test.ts +673 -30
- package/src/client.ts +287 -67
- package/src/commands/agent-browser.ts +33 -0
- package/src/commands/auth.test.ts +77 -26
- 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 +76 -25
- package/src/commands/helpers.ts +3 -3
- package/src/commands/index.ts +3 -1
- package/src/commands/mentoring.ts +60 -29
- package/src/commands/notice.ts +2 -1
- 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/commands/toz.test.ts +51 -0
- package/src/commands/toz.ts +607 -0
- package/src/constants.ts +20 -8
- package/src/credential-manager.test.ts +98 -0
- package/src/credential-manager.ts +50 -0
- package/src/formatters.test.ts +528 -33
- package/src/formatters.ts +309 -55
- package/src/http.test.ts +71 -2
- package/src/http.ts +41 -2
- package/src/index.ts +23 -1
- package/src/session-recovery.ts +2 -0
- package/src/shared/utils/swmaestro.test.ts +245 -9
- package/src/shared/utils/swmaestro.ts +150 -47
- 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/toz-client.test.ts +243 -0
- package/src/toz-client.ts +311 -0
- package/src/toz-pending-store.test.ts +91 -0
- package/src/toz-pending-store.ts +62 -0
- package/src/types.test.ts +26 -13
- 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,21 @@ 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'
|
|
25
|
+
import { TozClient } from './toz-client'
|
|
23
26
|
import type {
|
|
24
27
|
ApplicationHistoryItem,
|
|
25
28
|
ApprovalListItem,
|
|
26
29
|
Dashboard,
|
|
27
|
-
EventListItem,
|
|
28
30
|
MemberInfo,
|
|
29
31
|
MentoringDetail,
|
|
30
32
|
MentoringListItem,
|
|
@@ -37,6 +39,11 @@ import type {
|
|
|
37
39
|
ReportListItem,
|
|
38
40
|
ReportUpdateOptions,
|
|
39
41
|
RoomCard,
|
|
42
|
+
RoomReservationDetail,
|
|
43
|
+
RoomReservationListItem,
|
|
44
|
+
RoomReservationStatus,
|
|
45
|
+
RoomUpdateOptions,
|
|
46
|
+
ScheduleListItem,
|
|
40
47
|
TeamInfo,
|
|
41
48
|
} from './types'
|
|
42
49
|
|
|
@@ -46,6 +53,8 @@ export interface SomaClientOptions {
|
|
|
46
53
|
username?: string
|
|
47
54
|
password?: string
|
|
48
55
|
verbose?: boolean
|
|
56
|
+
tozName?: string
|
|
57
|
+
tozPhone?: string
|
|
49
58
|
/** @internal */
|
|
50
59
|
http?: SomaHttp
|
|
51
60
|
}
|
|
@@ -54,6 +63,8 @@ export class SomaClient {
|
|
|
54
63
|
private readonly http: SomaHttp
|
|
55
64
|
private readonly options: SomaClientOptions
|
|
56
65
|
private loginCredentials: { username: string; password: string } | null
|
|
66
|
+
// Single-flight guard: SWMaestro kills a session if it sees parallel logins for it.
|
|
67
|
+
private reloginInFlight: Promise<void> | null = null
|
|
57
68
|
|
|
58
69
|
readonly mentoring: {
|
|
59
70
|
list(options?: {
|
|
@@ -71,8 +82,11 @@ export class SomaClient {
|
|
|
71
82
|
endTime: string
|
|
72
83
|
venue: string
|
|
73
84
|
maxAttendees?: number
|
|
85
|
+
receiptType?: 'UNTIL_LECTURE' | 'DIRECT'
|
|
74
86
|
regStart?: string
|
|
87
|
+
regStartTime?: string
|
|
75
88
|
regEnd?: string
|
|
89
|
+
regEndTime?: string
|
|
76
90
|
content?: string
|
|
77
91
|
}): Promise<void>
|
|
78
92
|
update(id: number, params: MentoringUpdateOptions): Promise<void>
|
|
@@ -93,6 +107,15 @@ export class SomaClient {
|
|
|
93
107
|
attendees?: number
|
|
94
108
|
notes?: string
|
|
95
109
|
}): Promise<void>
|
|
110
|
+
get(rentId: number): Promise<RoomReservationDetail>
|
|
111
|
+
update(rentId: number, params?: RoomUpdateOptions): Promise<void>
|
|
112
|
+
cancel(rentId: number): Promise<void>
|
|
113
|
+
reservations(options?: {
|
|
114
|
+
status?: Exclude<RoomReservationStatus, 'unknown'> | 'all'
|
|
115
|
+
startDate?: string
|
|
116
|
+
endDate?: string
|
|
117
|
+
page?: number
|
|
118
|
+
}): Promise<{ items: RoomReservationListItem[]; pagination: Pagination }>
|
|
96
119
|
}
|
|
97
120
|
|
|
98
121
|
readonly dashboard: {
|
|
@@ -126,19 +149,21 @@ export class SomaClient {
|
|
|
126
149
|
}
|
|
127
150
|
|
|
128
151
|
readonly team: {
|
|
129
|
-
|
|
152
|
+
list(options?: { search?: TeamSearchQuery }): Promise<TeamInfo>
|
|
153
|
+
join(teamId: string): Promise<void>
|
|
154
|
+
leave(teamId: string): Promise<void>
|
|
130
155
|
}
|
|
131
156
|
|
|
132
157
|
readonly member: {
|
|
133
158
|
show(): Promise<MemberInfo>
|
|
134
159
|
}
|
|
135
160
|
|
|
136
|
-
readonly
|
|
137
|
-
list(options?: { page?: number }): Promise<{ items:
|
|
138
|
-
get(id: number): Promise<unknown>
|
|
139
|
-
apply(id: number): Promise<void>
|
|
161
|
+
readonly schedule: {
|
|
162
|
+
list(options?: { page?: number }): Promise<{ items: ScheduleListItem[]; pagination: Pagination }>
|
|
140
163
|
}
|
|
141
164
|
|
|
165
|
+
readonly toz: TozClient
|
|
166
|
+
|
|
142
167
|
constructor(options: SomaClientOptions = {}) {
|
|
143
168
|
this.options = options
|
|
144
169
|
this.loginCredentials =
|
|
@@ -165,9 +190,10 @@ export class SomaClient {
|
|
|
165
190
|
user,
|
|
166
191
|
}),
|
|
167
192
|
)
|
|
193
|
+
const items = formatters.parseMentoringList(html)
|
|
168
194
|
return {
|
|
169
|
-
items
|
|
170
|
-
pagination: formatters.parsePagination(html),
|
|
195
|
+
items,
|
|
196
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
171
197
|
}
|
|
172
198
|
},
|
|
173
199
|
get: async (id) => {
|
|
@@ -189,18 +215,27 @@ export class SomaClient {
|
|
|
189
215
|
},
|
|
190
216
|
update: async (id, params) => {
|
|
191
217
|
await this.requireAuth()
|
|
192
|
-
const
|
|
218
|
+
const [editHtml, viewHtml] = await Promise.all([
|
|
219
|
+
this.http.get('/mypage/mentoLec/forUpdate.do', { menuNo: MENU_NO.MENTORING, qustnrSn: String(id) }),
|
|
220
|
+
this.http.get('/mypage/mentoLec/view.do', { menuNo: MENU_NO.MENTORING, qustnrSn: String(id) }),
|
|
221
|
+
])
|
|
222
|
+
const existing = formatters.parseMentoringEditForm(editHtml, id)
|
|
223
|
+
const existingContent = formatters.parseMentoringDetail(viewHtml, id).content
|
|
224
|
+
|
|
193
225
|
const merged = buildUpdateMentoringPayload(id, {
|
|
194
226
|
title: params.title ?? existing.title,
|
|
195
|
-
type: params.type ??
|
|
196
|
-
date: params.date ?? existing.
|
|
197
|
-
startTime: params.startTime ?? existing.
|
|
198
|
-
endTime: params.endTime ?? existing.
|
|
199
|
-
venue: params.venue ?? existing.
|
|
200
|
-
maxAttendees: params.maxAttendees ?? existing.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
227
|
+
type: params.type ?? (existing.reportCd === 'MRC020' ? 'lecture' : 'public'),
|
|
228
|
+
date: params.date ?? existing.eventDt,
|
|
229
|
+
startTime: params.startTime ?? existing.eventStime,
|
|
230
|
+
endTime: params.endTime ?? existing.eventEtime,
|
|
231
|
+
venue: params.venue ?? existing.place,
|
|
232
|
+
maxAttendees: params.maxAttendees ?? existing.applyCnt,
|
|
233
|
+
receiptType: params.receiptType ?? existing.receiptType,
|
|
234
|
+
regStart: params.regStart ?? existing.bgndeDate,
|
|
235
|
+
regStartTime: params.regStartTime ?? existing.bgndeTime,
|
|
236
|
+
regEnd: params.regEnd ?? existing.enddeDate,
|
|
237
|
+
regEndTime: params.regEndTime ?? existing.enddeTime,
|
|
238
|
+
content: params.content ?? existingContent,
|
|
204
239
|
})
|
|
205
240
|
const html = await this.http.postForm('/mypage/mentoLec/update.do', merged)
|
|
206
241
|
if (this.containsErrorIndicator(html)) {
|
|
@@ -225,9 +260,10 @@ export class SomaClient {
|
|
|
225
260
|
menuNo: MENU_NO.APPLICATION_HISTORY,
|
|
226
261
|
...(options?.page ? { pageIndex: String(options.page) } : {}),
|
|
227
262
|
})
|
|
263
|
+
const items = formatters.parseApplicationHistory(html)
|
|
228
264
|
return {
|
|
229
|
-
items
|
|
230
|
-
pagination: formatters.parsePagination(html),
|
|
265
|
+
items,
|
|
266
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
231
267
|
}
|
|
232
268
|
},
|
|
233
269
|
}
|
|
@@ -279,24 +315,98 @@ export class SomaClient {
|
|
|
279
315
|
await this.requireAuth()
|
|
280
316
|
await this.http.post('/mypage/itemRent/insert.do', buildRoomReservationPayload(params))
|
|
281
317
|
},
|
|
318
|
+
get: async (rentId) => {
|
|
319
|
+
await this.requireAuth()
|
|
320
|
+
return formatters.parseRoomReservationDetail(
|
|
321
|
+
await this.http.get('/mypage/itemRent/view.do', {
|
|
322
|
+
menuNo: MENU_NO.ROOM,
|
|
323
|
+
rentId: String(rentId),
|
|
324
|
+
}),
|
|
325
|
+
)
|
|
326
|
+
},
|
|
327
|
+
update: async (rentId, params = {}) => {
|
|
328
|
+
await this.requireAuth()
|
|
329
|
+
const existing = await this.room.get(rentId)
|
|
330
|
+
await this.postRoomUpdate(buildRoomUpdatePayload(existing, params))
|
|
331
|
+
},
|
|
332
|
+
cancel: async (rentId) => {
|
|
333
|
+
await this.requireAuth()
|
|
334
|
+
const existing = await this.room.get(rentId)
|
|
335
|
+
await this.postRoomUpdate(buildRoomCancelPayload(existing))
|
|
336
|
+
},
|
|
337
|
+
reservations: async (options) => {
|
|
338
|
+
await this.requireAuth()
|
|
339
|
+
const params: Record<string, string> = {
|
|
340
|
+
menuNo: MENU_NO.ROOM,
|
|
341
|
+
pageIndex: String(options?.page ?? 1),
|
|
342
|
+
}
|
|
343
|
+
if (options?.startDate) params.sdate = options.startDate
|
|
344
|
+
if (options?.endDate) params.edate = options.endDate
|
|
345
|
+
const status = options?.status ?? 'confirmed'
|
|
346
|
+
if (status !== 'all') {
|
|
347
|
+
params.searchStat = status === 'cancelled' ? 'RS002' : 'RS001'
|
|
348
|
+
}
|
|
349
|
+
const html = await this.http.get('/mypage/itemRent/list.do', params)
|
|
350
|
+
const items = formatters.parseRoomReservationList(html)
|
|
351
|
+
return {
|
|
352
|
+
items,
|
|
353
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
354
|
+
}
|
|
355
|
+
},
|
|
282
356
|
}
|
|
283
357
|
|
|
284
358
|
this.dashboard = {
|
|
285
359
|
get: async () => {
|
|
286
360
|
await this.requireAuth()
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
361
|
+
const dashboard = formatters.parseDashboard(
|
|
362
|
+
await this.http.get('/mypage/myMain/dashboard.do', { menuNo: MENU_NO.DASHBOARD }),
|
|
363
|
+
)
|
|
364
|
+
const trainee = isTraineeRole(dashboard.role)
|
|
365
|
+
const teamSearchField = trainee ? ('member' as const) : ('mentor' as const)
|
|
366
|
+
if (trainee) {
|
|
367
|
+
const [firstPage, teams] = await Promise.all([
|
|
368
|
+
this.mentoring.history(),
|
|
369
|
+
this.team.list({ search: { field: teamSearchField, value: '@me', me: true } }),
|
|
370
|
+
])
|
|
371
|
+
const remainingPages = await Promise.all(
|
|
372
|
+
Array.from({ length: Math.max(0, firstPage.pagination.totalPages - 1) }, (_, i) =>
|
|
373
|
+
this.mentoring.history({ page: i + 2 }),
|
|
374
|
+
),
|
|
375
|
+
)
|
|
376
|
+
const historyItems = [firstPage, ...remainingPages].flatMap((p) => p.items)
|
|
377
|
+
dashboard.mentoringSessions = sortDashboardMentoringItems(
|
|
378
|
+
historyItems
|
|
379
|
+
.map(applicationHistoryToDashboardItem)
|
|
380
|
+
.filter((item): item is Dashboard['mentoringSessions'][number] => item !== null),
|
|
381
|
+
)
|
|
382
|
+
dashboard.teams = teams.teams
|
|
383
|
+
return dashboard
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const search = { field: 'author' as const, value: '@me', me: true }
|
|
387
|
+
const [firstPage, teams] = await Promise.all([
|
|
388
|
+
this.mentoring.list({ search }),
|
|
389
|
+
this.team.list({ search: { field: teamSearchField, value: '@me', me: true } }),
|
|
290
390
|
])
|
|
291
|
-
dashboard
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
391
|
+
// Exhaust pagination: dashboard time totals must span the whole month, not just page 1.
|
|
392
|
+
const remainingPages = await Promise.all(
|
|
393
|
+
Array.from({ length: Math.max(0, firstPage.pagination.totalPages - 1) }, (_, i) =>
|
|
394
|
+
this.mentoring.list({ search, page: i + 2 }),
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
const myMentoring = [firstPage, ...remainingPages].flatMap((p) => p.items)
|
|
398
|
+
dashboard.mentoringSessions = sortDashboardMentoringItems(
|
|
399
|
+
myMentoring.map((item) => ({
|
|
400
|
+
title: item.title,
|
|
401
|
+
url: `/mypage/mentoLec/view.do?qustnrSn=${item.id}`,
|
|
402
|
+
status: item.status,
|
|
403
|
+
date: item.sessionDate,
|
|
404
|
+
time: item.sessionTime.start,
|
|
405
|
+
timeEnd: item.sessionTime.end,
|
|
406
|
+
type: item.type,
|
|
407
|
+
})),
|
|
408
|
+
)
|
|
409
|
+
dashboard.teams = teams.teams
|
|
300
410
|
return dashboard
|
|
301
411
|
},
|
|
302
412
|
}
|
|
@@ -308,9 +418,10 @@ export class SomaClient {
|
|
|
308
418
|
menuNo: MENU_NO.NOTICE,
|
|
309
419
|
...(options?.page ? { pageIndex: String(options.page) } : {}),
|
|
310
420
|
})
|
|
421
|
+
const items = formatters.parseNoticeList(html)
|
|
311
422
|
return {
|
|
312
|
-
items
|
|
313
|
-
pagination: formatters.parsePagination(html),
|
|
423
|
+
items,
|
|
424
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
314
425
|
}
|
|
315
426
|
},
|
|
316
427
|
get: async (id) => {
|
|
@@ -335,9 +446,10 @@ export class SomaClient {
|
|
|
335
446
|
if (options?.searchField !== undefined) params.searchCnd = options.searchField
|
|
336
447
|
if (options?.searchKeyword) params.searchWrd = options.searchKeyword
|
|
337
448
|
const html = await this.http.get('/mypage/mentoringReport/list.do', params)
|
|
449
|
+
const items = formatters.parseReportList(html)
|
|
338
450
|
return {
|
|
339
|
-
items
|
|
340
|
-
pagination: formatters.parsePagination(html),
|
|
451
|
+
items,
|
|
452
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
341
453
|
}
|
|
342
454
|
},
|
|
343
455
|
get: async (id) => {
|
|
@@ -429,17 +541,27 @@ export class SomaClient {
|
|
|
429
541
|
if (options?.month) params.searchMonth = options.month
|
|
430
542
|
if (options?.reportType !== undefined) params.searchReport = options.reportType
|
|
431
543
|
const html = await this.http.get('/mypage/mentoringReport/resultList.do', params)
|
|
544
|
+
const items = formatters.parseApprovalList(html)
|
|
432
545
|
return {
|
|
433
|
-
items
|
|
434
|
-
pagination: formatters.parsePagination(html),
|
|
546
|
+
items,
|
|
547
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
435
548
|
}
|
|
436
549
|
},
|
|
437
550
|
}
|
|
438
551
|
|
|
439
552
|
this.team = {
|
|
440
|
-
|
|
553
|
+
list: async (options) => {
|
|
441
554
|
await this.requireAuth()
|
|
442
|
-
|
|
555
|
+
const user = options?.search?.me ? await this.resolveUser() : undefined
|
|
556
|
+
return formatters.parseTeamInfo(
|
|
557
|
+
await this.http.get('/mypage/myTeam/team.do', buildTeamListParams({ search: options?.search, user })),
|
|
558
|
+
)
|
|
559
|
+
},
|
|
560
|
+
join: async (teamId) => {
|
|
561
|
+
await this.postTeamAction('/mypage/myTeam/updateUserTeamIn.json', teamId, '팀 참여에 실패했습니다.')
|
|
562
|
+
},
|
|
563
|
+
leave: async (teamId) => {
|
|
564
|
+
await this.postTeamAction('/mypage/myTeam/updateUserTeamOut.json', teamId, '팀 탈퇴에 실패했습니다.')
|
|
443
565
|
},
|
|
444
566
|
}
|
|
445
567
|
|
|
@@ -452,32 +574,21 @@ export class SomaClient {
|
|
|
452
574
|
},
|
|
453
575
|
}
|
|
454
576
|
|
|
455
|
-
this.
|
|
577
|
+
this.schedule = {
|
|
456
578
|
list: async (options) => {
|
|
457
579
|
await this.requireAuth()
|
|
458
|
-
const html = await this.http.get('/mypage/
|
|
459
|
-
menuNo: MENU_NO.
|
|
580
|
+
const html = await this.http.get('/mypage/schedule/list.do', {
|
|
581
|
+
menuNo: MENU_NO.SCHEDULE,
|
|
460
582
|
...(options?.page ? { pageIndex: String(options.page) } : {}),
|
|
461
583
|
})
|
|
462
|
-
return
|
|
463
|
-
items: formatters.parseEventList(html),
|
|
464
|
-
pagination: formatters.parsePagination(html),
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
get: async (id) => {
|
|
468
|
-
await this.requireAuth()
|
|
469
|
-
return parseEventDetail(
|
|
470
|
-
await this.http.get('/mypage/applicants/view.do', {
|
|
471
|
-
menuNo: MENU_NO.EVENT,
|
|
472
|
-
bbsId: String(id),
|
|
473
|
-
}),
|
|
474
|
-
)
|
|
475
|
-
},
|
|
476
|
-
apply: async (id) => {
|
|
477
|
-
await this.requireAuth()
|
|
478
|
-
await this.http.post('/application/application/application.do', buildApplicationPayload(id))
|
|
584
|
+
return formatters.parseScheduleList(html)
|
|
479
585
|
},
|
|
480
586
|
}
|
|
587
|
+
|
|
588
|
+
this.toz = new TozClient({
|
|
589
|
+
name: options.tozName,
|
|
590
|
+
phone: options.tozPhone,
|
|
591
|
+
})
|
|
481
592
|
}
|
|
482
593
|
|
|
483
594
|
getSessionData(): { sessionCookie: string | undefined; csrfToken: string | null } {
|
|
@@ -490,7 +601,7 @@ export class SomaClient {
|
|
|
490
601
|
private async requireAuth(): Promise<void> {
|
|
491
602
|
let identity = await this.http.checkLogin()
|
|
492
603
|
if (!identity && this.loginCredentials) {
|
|
493
|
-
await this.
|
|
604
|
+
await this.relogin()
|
|
494
605
|
identity = await this.http.checkLogin()
|
|
495
606
|
}
|
|
496
607
|
|
|
@@ -499,6 +610,19 @@ export class SomaClient {
|
|
|
499
610
|
}
|
|
500
611
|
}
|
|
501
612
|
|
|
613
|
+
private async relogin(): Promise<void> {
|
|
614
|
+
if (!this.loginCredentials) {
|
|
615
|
+
throw new AuthenticationError()
|
|
616
|
+
}
|
|
617
|
+
if (!this.reloginInFlight) {
|
|
618
|
+
const { username, password } = this.loginCredentials
|
|
619
|
+
this.reloginInFlight = this.http.login(username, password).finally(() => {
|
|
620
|
+
this.reloginInFlight = null
|
|
621
|
+
})
|
|
622
|
+
}
|
|
623
|
+
await this.reloginInFlight
|
|
624
|
+
}
|
|
625
|
+
|
|
502
626
|
private async resolveUser(): Promise<UserIdentity | undefined> {
|
|
503
627
|
const identity = await this.http.checkLogin()
|
|
504
628
|
return identity ?? undefined
|
|
@@ -523,6 +647,10 @@ export class SomaClient {
|
|
|
523
647
|
return Boolean(await this.http.checkLogin())
|
|
524
648
|
}
|
|
525
649
|
|
|
650
|
+
async whoami(): Promise<UserIdentity | null> {
|
|
651
|
+
return this.http.checkLogin()
|
|
652
|
+
}
|
|
653
|
+
|
|
526
654
|
async logout(): Promise<void> {
|
|
527
655
|
await this.http.logout()
|
|
528
656
|
}
|
|
@@ -544,6 +672,30 @@ export class SomaClient {
|
|
|
544
672
|
})
|
|
545
673
|
}
|
|
546
674
|
|
|
675
|
+
private async postTeamAction(path: string, teamId: string, fallbackMessage: string): Promise<void> {
|
|
676
|
+
await this.requireAuth()
|
|
677
|
+
const user = await this.resolveUser()
|
|
678
|
+
if (!user) throw new AuthenticationError()
|
|
679
|
+
if (!user.userNo) {
|
|
680
|
+
throw new Error('현재 사용자의 userNo를 확인할 수 없습니다.')
|
|
681
|
+
}
|
|
682
|
+
const response = await this.http.postJson<{ resultCode?: string }>(path, buildTeamActionPayload(teamId, user))
|
|
683
|
+
if (response.resultCode !== 'success') {
|
|
684
|
+
throw new Error(fallbackMessage)
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private async postRoomUpdate(payload: Record<string, string>): Promise<void> {
|
|
689
|
+
try {
|
|
690
|
+
await this.http.post('/mypage/itemRent/update.do', payload)
|
|
691
|
+
} catch (error) {
|
|
692
|
+
if (error instanceof Error && isSuccessAlertMessage(error.message)) {
|
|
693
|
+
return
|
|
694
|
+
}
|
|
695
|
+
throw error
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
547
699
|
private containsErrorIndicator(html: string): boolean {
|
|
548
700
|
const errorPatterns = [
|
|
549
701
|
'class="error"',
|
|
@@ -554,14 +706,20 @@ export class SomaClient {
|
|
|
554
706
|
'실패하였습니다',
|
|
555
707
|
'잘못된 접근',
|
|
556
708
|
'권한이 없습니다',
|
|
557
|
-
'<script>alert(',
|
|
558
709
|
]
|
|
559
|
-
|
|
710
|
+
if (errorPatterns.some((pattern) => html.includes(pattern))) {
|
|
711
|
+
return true
|
|
712
|
+
}
|
|
713
|
+
const alertMatch = html.match(/<script[^>]*>\s*alert\(['"](.+?)['"]\)/)
|
|
714
|
+
if (alertMatch && !isSuccessAlertMessage(alertMatch[1])) {
|
|
715
|
+
return true
|
|
716
|
+
}
|
|
717
|
+
return false
|
|
560
718
|
}
|
|
561
719
|
|
|
562
720
|
private extractErrorMessage(html: string): string | null {
|
|
563
|
-
const alertMatch = html.match(/<script
|
|
564
|
-
if (alertMatch) {
|
|
721
|
+
const alertMatch = html.match(/<script[^>]*>\s*alert\(['"](.+?)['"]\)/)
|
|
722
|
+
if (alertMatch && !isSuccessAlertMessage(alertMatch[1])) {
|
|
565
723
|
return alertMatch[1]
|
|
566
724
|
}
|
|
567
725
|
const errorDivMatch = html.match(/class="error[^"]*"[^>]*>\s*([^<]+)/)
|
|
@@ -571,3 +729,65 @@ export class SomaClient {
|
|
|
571
729
|
return null
|
|
572
730
|
}
|
|
573
731
|
}
|
|
732
|
+
|
|
733
|
+
function isTraineeRole(role: string): boolean {
|
|
734
|
+
return role.includes('연수생')
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function applicationHistoryToDashboardItem(
|
|
738
|
+
item: ApplicationHistoryItem,
|
|
739
|
+
): Dashboard['mentoringSessions'][number] | null {
|
|
740
|
+
if (item.applicationStatus.includes('취소')) return null
|
|
741
|
+
|
|
742
|
+
const type = applicationCategoryToMentoringType(item.category)
|
|
743
|
+
if (!type) return null
|
|
744
|
+
|
|
745
|
+
const { date, time, timeEnd } = parseApplicationSessionDate(item.sessionDate)
|
|
746
|
+
if (date && date < new Date().toISOString().slice(0, 10)) return null
|
|
747
|
+
|
|
748
|
+
return {
|
|
749
|
+
title: item.title,
|
|
750
|
+
url: item.url ?? '/mentoring/history',
|
|
751
|
+
status: item.applicationStatus,
|
|
752
|
+
...(date ? { date } : {}),
|
|
753
|
+
...(time ? { time } : {}),
|
|
754
|
+
...(timeEnd ? { timeEnd } : {}),
|
|
755
|
+
type,
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function sortDashboardMentoringItems(items: Dashboard['mentoringSessions']): Dashboard['mentoringSessions'] {
|
|
760
|
+
return [...items].sort((a, b) => dashboardMentoringSortKey(a).localeCompare(dashboardMentoringSortKey(b)))
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function dashboardMentoringSortKey(item: Dashboard['mentoringSessions'][number]): string {
|
|
764
|
+
return `${item.date || '9999-12-31'} ${item.time || '99:99'}`
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function applicationCategoryToMentoringType(category: string): '자유 멘토링' | '멘토 특강' | null {
|
|
768
|
+
const compact = category.replace(/\s+/g, '')
|
|
769
|
+
if (compact.includes('특강')) return '멘토 특강'
|
|
770
|
+
if (compact.includes('멘토링')) return '자유 멘토링'
|
|
771
|
+
return null
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function parseApplicationSessionDate(value: string): { date?: string; time?: string; timeEnd?: string } {
|
|
775
|
+
const date = value.match(/\d{4}-\d{2}-\d{2}/)?.[0]
|
|
776
|
+
const times = value.match(/\d{1,2}:\d{2}(?::\d{2})?/g)?.map(normalizeDashboardTime) ?? []
|
|
777
|
+
return {
|
|
778
|
+
...(date ? { date } : {}),
|
|
779
|
+
...(times[0] ? { time: times[0] } : {}),
|
|
780
|
+
...(times[1] ? { timeEnd: times[1] } : {}),
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function normalizeDashboardTime(value: string): string {
|
|
785
|
+
const [hours = '', minutes = ''] = value.split(':')
|
|
786
|
+
return `${hours.padStart(2, '0')}:${minutes}`
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function isSuccessAlertMessage(message: string): boolean {
|
|
790
|
+
return /정상적으로|등록\s?하였습니다|등록\s?되었습니다|수정\s?하였습니다|수정\s?되었습니다|저장\s?되었습니다|완료\s?되었습니다|삭제\s?되었습니다|취소\s?되었습니다/.test(
|
|
791
|
+
message,
|
|
792
|
+
)
|
|
793
|
+
}
|
|
@@ -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
|
+
)
|