opensoma 0.5.1 → 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 +30 -7
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +218 -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 +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 -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/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 +673 -30
- package/src/client.ts +277 -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 +72 -25
- 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.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 +44 -0
- package/src/credential-manager.ts +27 -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 +10 -1
- 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/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,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
|
|
|
@@ -54,6 +60,8 @@ export class SomaClient {
|
|
|
54
60
|
private readonly http: SomaHttp
|
|
55
61
|
private readonly options: SomaClientOptions
|
|
56
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
|
|
57
65
|
|
|
58
66
|
readonly mentoring: {
|
|
59
67
|
list(options?: {
|
|
@@ -71,8 +79,11 @@ export class SomaClient {
|
|
|
71
79
|
endTime: string
|
|
72
80
|
venue: string
|
|
73
81
|
maxAttendees?: number
|
|
82
|
+
receiptType?: 'UNTIL_LECTURE' | 'DIRECT'
|
|
74
83
|
regStart?: string
|
|
84
|
+
regStartTime?: string
|
|
75
85
|
regEnd?: string
|
|
86
|
+
regEndTime?: string
|
|
76
87
|
content?: string
|
|
77
88
|
}): Promise<void>
|
|
78
89
|
update(id: number, params: MentoringUpdateOptions): Promise<void>
|
|
@@ -93,6 +104,15 @@ export class SomaClient {
|
|
|
93
104
|
attendees?: number
|
|
94
105
|
notes?: string
|
|
95
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 }>
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
readonly dashboard: {
|
|
@@ -126,17 +146,17 @@ export class SomaClient {
|
|
|
126
146
|
}
|
|
127
147
|
|
|
128
148
|
readonly team: {
|
|
129
|
-
|
|
149
|
+
list(options?: { search?: TeamSearchQuery }): Promise<TeamInfo>
|
|
150
|
+
join(teamId: string): Promise<void>
|
|
151
|
+
leave(teamId: string): Promise<void>
|
|
130
152
|
}
|
|
131
153
|
|
|
132
154
|
readonly member: {
|
|
133
155
|
show(): Promise<MemberInfo>
|
|
134
156
|
}
|
|
135
157
|
|
|
136
|
-
readonly
|
|
137
|
-
list(options?: { page?: number }): Promise<{ items:
|
|
138
|
-
get(id: number): Promise<unknown>
|
|
139
|
-
apply(id: number): Promise<void>
|
|
158
|
+
readonly schedule: {
|
|
159
|
+
list(options?: { page?: number }): Promise<{ items: ScheduleListItem[]; pagination: Pagination }>
|
|
140
160
|
}
|
|
141
161
|
|
|
142
162
|
constructor(options: SomaClientOptions = {}) {
|
|
@@ -165,9 +185,10 @@ export class SomaClient {
|
|
|
165
185
|
user,
|
|
166
186
|
}),
|
|
167
187
|
)
|
|
188
|
+
const items = formatters.parseMentoringList(html)
|
|
168
189
|
return {
|
|
169
|
-
items
|
|
170
|
-
pagination: formatters.parsePagination(html),
|
|
190
|
+
items,
|
|
191
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
171
192
|
}
|
|
172
193
|
},
|
|
173
194
|
get: async (id) => {
|
|
@@ -189,18 +210,27 @@ export class SomaClient {
|
|
|
189
210
|
},
|
|
190
211
|
update: async (id, params) => {
|
|
191
212
|
await this.requireAuth()
|
|
192
|
-
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
|
+
|
|
193
220
|
const merged = buildUpdateMentoringPayload(id, {
|
|
194
221
|
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
|
-
|
|
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,
|
|
204
234
|
})
|
|
205
235
|
const html = await this.http.postForm('/mypage/mentoLec/update.do', merged)
|
|
206
236
|
if (this.containsErrorIndicator(html)) {
|
|
@@ -225,9 +255,10 @@ export class SomaClient {
|
|
|
225
255
|
menuNo: MENU_NO.APPLICATION_HISTORY,
|
|
226
256
|
...(options?.page ? { pageIndex: String(options.page) } : {}),
|
|
227
257
|
})
|
|
258
|
+
const items = formatters.parseApplicationHistory(html)
|
|
228
259
|
return {
|
|
229
|
-
items
|
|
230
|
-
pagination: formatters.parsePagination(html),
|
|
260
|
+
items,
|
|
261
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
231
262
|
}
|
|
232
263
|
},
|
|
233
264
|
}
|
|
@@ -279,24 +310,98 @@ export class SomaClient {
|
|
|
279
310
|
await this.requireAuth()
|
|
280
311
|
await this.http.post('/mypage/itemRent/insert.do', buildRoomReservationPayload(params))
|
|
281
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
|
+
},
|
|
282
351
|
}
|
|
283
352
|
|
|
284
353
|
this.dashboard = {
|
|
285
354
|
get: async () => {
|
|
286
355
|
await this.requireAuth()
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
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 } }),
|
|
290
385
|
])
|
|
291
|
-
dashboard
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
|
300
405
|
return dashboard
|
|
301
406
|
},
|
|
302
407
|
}
|
|
@@ -308,9 +413,10 @@ export class SomaClient {
|
|
|
308
413
|
menuNo: MENU_NO.NOTICE,
|
|
309
414
|
...(options?.page ? { pageIndex: String(options.page) } : {}),
|
|
310
415
|
})
|
|
416
|
+
const items = formatters.parseNoticeList(html)
|
|
311
417
|
return {
|
|
312
|
-
items
|
|
313
|
-
pagination: formatters.parsePagination(html),
|
|
418
|
+
items,
|
|
419
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
314
420
|
}
|
|
315
421
|
},
|
|
316
422
|
get: async (id) => {
|
|
@@ -335,9 +441,10 @@ export class SomaClient {
|
|
|
335
441
|
if (options?.searchField !== undefined) params.searchCnd = options.searchField
|
|
336
442
|
if (options?.searchKeyword) params.searchWrd = options.searchKeyword
|
|
337
443
|
const html = await this.http.get('/mypage/mentoringReport/list.do', params)
|
|
444
|
+
const items = formatters.parseReportList(html)
|
|
338
445
|
return {
|
|
339
|
-
items
|
|
340
|
-
pagination: formatters.parsePagination(html),
|
|
446
|
+
items,
|
|
447
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
341
448
|
}
|
|
342
449
|
},
|
|
343
450
|
get: async (id) => {
|
|
@@ -429,17 +536,27 @@ export class SomaClient {
|
|
|
429
536
|
if (options?.month) params.searchMonth = options.month
|
|
430
537
|
if (options?.reportType !== undefined) params.searchReport = options.reportType
|
|
431
538
|
const html = await this.http.get('/mypage/mentoringReport/resultList.do', params)
|
|
539
|
+
const items = formatters.parseApprovalList(html)
|
|
432
540
|
return {
|
|
433
|
-
items
|
|
434
|
-
pagination: formatters.parsePagination(html),
|
|
541
|
+
items,
|
|
542
|
+
pagination: formatters.parsePagination(html, { itemCount: items.length }),
|
|
435
543
|
}
|
|
436
544
|
},
|
|
437
545
|
}
|
|
438
546
|
|
|
439
547
|
this.team = {
|
|
440
|
-
|
|
548
|
+
list: async (options) => {
|
|
441
549
|
await this.requireAuth()
|
|
442
|
-
|
|
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, '팀 탈퇴에 실패했습니다.')
|
|
443
560
|
},
|
|
444
561
|
}
|
|
445
562
|
|
|
@@ -452,30 +569,14 @@ export class SomaClient {
|
|
|
452
569
|
},
|
|
453
570
|
}
|
|
454
571
|
|
|
455
|
-
this.
|
|
572
|
+
this.schedule = {
|
|
456
573
|
list: async (options) => {
|
|
457
574
|
await this.requireAuth()
|
|
458
|
-
const html = await this.http.get('/mypage/
|
|
459
|
-
menuNo: MENU_NO.
|
|
575
|
+
const html = await this.http.get('/mypage/schedule/list.do', {
|
|
576
|
+
menuNo: MENU_NO.SCHEDULE,
|
|
460
577
|
...(options?.page ? { pageIndex: String(options.page) } : {}),
|
|
461
578
|
})
|
|
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))
|
|
579
|
+
return formatters.parseScheduleList(html)
|
|
479
580
|
},
|
|
480
581
|
}
|
|
481
582
|
}
|
|
@@ -490,7 +591,7 @@ export class SomaClient {
|
|
|
490
591
|
private async requireAuth(): Promise<void> {
|
|
491
592
|
let identity = await this.http.checkLogin()
|
|
492
593
|
if (!identity && this.loginCredentials) {
|
|
493
|
-
await this.
|
|
594
|
+
await this.relogin()
|
|
494
595
|
identity = await this.http.checkLogin()
|
|
495
596
|
}
|
|
496
597
|
|
|
@@ -499,6 +600,19 @@ export class SomaClient {
|
|
|
499
600
|
}
|
|
500
601
|
}
|
|
501
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
|
+
|
|
502
616
|
private async resolveUser(): Promise<UserIdentity | undefined> {
|
|
503
617
|
const identity = await this.http.checkLogin()
|
|
504
618
|
return identity ?? undefined
|
|
@@ -523,6 +637,10 @@ export class SomaClient {
|
|
|
523
637
|
return Boolean(await this.http.checkLogin())
|
|
524
638
|
}
|
|
525
639
|
|
|
640
|
+
async whoami(): Promise<UserIdentity | null> {
|
|
641
|
+
return this.http.checkLogin()
|
|
642
|
+
}
|
|
643
|
+
|
|
526
644
|
async logout(): Promise<void> {
|
|
527
645
|
await this.http.logout()
|
|
528
646
|
}
|
|
@@ -544,6 +662,30 @@ export class SomaClient {
|
|
|
544
662
|
})
|
|
545
663
|
}
|
|
546
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
|
+
|
|
547
689
|
private containsErrorIndicator(html: string): boolean {
|
|
548
690
|
const errorPatterns = [
|
|
549
691
|
'class="error"',
|
|
@@ -554,14 +696,20 @@ export class SomaClient {
|
|
|
554
696
|
'실패하였습니다',
|
|
555
697
|
'잘못된 접근',
|
|
556
698
|
'권한이 없습니다',
|
|
557
|
-
'<script>alert(',
|
|
558
699
|
]
|
|
559
|
-
|
|
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
|
|
560
708
|
}
|
|
561
709
|
|
|
562
710
|
private extractErrorMessage(html: string): string | null {
|
|
563
|
-
const alertMatch = html.match(/<script
|
|
564
|
-
if (alertMatch) {
|
|
711
|
+
const alertMatch = html.match(/<script[^>]*>\s*alert\(['"](.+?)['"]\)/)
|
|
712
|
+
if (alertMatch && !isSuccessAlertMessage(alertMatch[1])) {
|
|
565
713
|
return alertMatch[1]
|
|
566
714
|
}
|
|
567
715
|
const errorDivMatch = html.match(/class="error[^"]*"[^>]*>\s*([^<]+)/)
|
|
@@ -571,3 +719,65 @@ export class SomaClient {
|
|
|
571
719
|
return null
|
|
572
720
|
}
|
|
573
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
|
+
)
|