diff --git a/src/services/the-camp/requesters/index.ts b/src/services/the-camp/requesters/index.ts index af3a0ac..abb8013 100644 --- a/src/services/the-camp/requesters/index.ts +++ b/src/services/the-camp/requesters/index.ts @@ -1,3 +1,4 @@ -export * from './register-cafe'; export * from './login'; +export * from './register-cafe'; export * from './register-soldier'; +export * from './send-letter'; diff --git a/src/services/the-camp/requesters/register-cafe/register-cafe.requester.ts b/src/services/the-camp/requesters/register-cafe/register-cafe.requester.ts index d224da1..441c6be 100644 --- a/src/services/the-camp/requesters/register-cafe/register-cafe.requester.ts +++ b/src/services/the-camp/requesters/register-cafe/register-cafe.requester.ts @@ -1,4 +1,5 @@ import { TheCampSession } from '@common/types'; +import { Parameter } from '@core/http'; import { 관계, 관계CodeMap, @@ -9,7 +10,6 @@ import { } from '@core/types'; import axios, { AxiosRequestConfig } from 'axios'; -import { Parameter } from '../../../../core/http'; import { assertsResponse } from './asserts-response'; export class RegisterCafeRequester { diff --git a/src/services/the-camp/requesters/send-letter/asserts-response.spec.ts b/src/services/the-camp/requesters/send-letter/asserts-response.spec.ts new file mode 100644 index 0000000..2b67281 --- /dev/null +++ b/src/services/the-camp/requesters/send-letter/asserts-response.spec.ts @@ -0,0 +1,18 @@ +import { assertsResponse } from './asserts-response'; +import 성공 from './test/성공.json'; + +describe('assertsResponse', () => { + it('성공', () => { + expect(() => assertsResponse(성공 as any)).not.toThrow(); + }); + + it('실패', () => { + expect(() => + assertsResponse({ + data: { + resultCd: '-1', + }, + } as any), + ).toThrow('알 수 없는 오류가 발생하였습니다.'); + }); +}); diff --git a/src/services/the-camp/requesters/send-letter/asserts-response.ts b/src/services/the-camp/requesters/send-letter/asserts-response.ts new file mode 100644 index 0000000..9ac1246 --- /dev/null +++ b/src/services/the-camp/requesters/send-letter/asserts-response.ts @@ -0,0 +1,9 @@ +import { AxiosResponse } from 'axios'; + +export function assertsResponse(response: AxiosResponse) { + if (response.data.resultCd !== '0000') { + throw new Error( + response.data.resultMsg || '알 수 없는 오류가 발생하였습니다.', + ); + } +} diff --git a/src/services/the-camp/requesters/send-letter/index.ts b/src/services/the-camp/requesters/send-letter/index.ts new file mode 100644 index 0000000..5e19d3a --- /dev/null +++ b/src/services/the-camp/requesters/send-letter/index.ts @@ -0,0 +1 @@ +export * from './send-letter.requester'; diff --git a/src/services/the-camp/requesters/send-letter/send-letter.requester.e2e.spec.ts b/src/services/the-camp/requesters/send-letter/send-letter.requester.e2e.spec.ts new file mode 100644 index 0000000..bcb6e85 --- /dev/null +++ b/src/services/the-camp/requesters/send-letter/send-letter.requester.e2e.spec.ts @@ -0,0 +1,25 @@ +import { loginRequester } from '../login'; +import { sendLetterRequester } from './send-letter.requester'; + +describe('SendLetterRequester e2e', () => { + it('성공', async () => { + const session = await loginRequester.request({ + id: '', + password: '', + }); + await sendLetterRequester.request( + { + 제목: '내용은 곧 제목', + 작성자: '장지훈', + 내용: `하이 요즘 뭐해 + 바쁘니 + 자니 메롱 + 냠냠`, + 입영부대: '육군훈련소-논산', + 입영부대EduId: '', + 훈련병Id: '', + }, + session, + ); + }); +}); diff --git a/src/services/the-camp/requesters/send-letter/send-letter.requester.ts b/src/services/the-camp/requesters/send-letter/send-letter.requester.ts new file mode 100644 index 0000000..b32f183 --- /dev/null +++ b/src/services/the-camp/requesters/send-letter/send-letter.requester.ts @@ -0,0 +1,99 @@ +import { TheCampSession } from '@common/types'; +import { Parameter } from '@core/http'; +import { 입영부대, 입영부대CodeMap } from '@core/types'; +import axios, { AxiosRequestConfig } from 'axios'; + +import { assertsResponse } from './asserts-response'; + +export class SendLetterRequester { + constructor(private readonly asserts = assertsResponse) {} + + async request(dto: SendLetterDto, session: TheCampSession): Promise { + const response = await axios.post( + 'https://www.thecamp.or.kr/consolLetter/insertConsolLetterA.do', + this.createPayload(dto), + this.createOptions(session), + ); + + this.asserts(response); + } + + private createPayload({ + 제목, + 작성자, + 내용, + 입영부대, + 입영부대EduId, + 훈련병Id, + }: SendLetterDto): string { + return ( + new Parameter({ + traineeMgrSeq: 훈련병Id, + sympathyLetterSubject: '#제목#제목#제목#제목#', + sympathyLetterContent: this.createLetterContent(작성자, 내용), + trainUnitCd: 입영부대CodeMap[입영부대], + trainUnitEduSeq: 입영부대EduId, + boardDiv: 'sympathyLetter', + tempSaveYn: 'N', + sympathyLetterEditorFileGroupSeq: '', + fileGroupMgrSeq: '', + fileMgrSeq: '', + sympathyLetterMgrSeq: '', + }) + .toString() + // 제목은 공백을 +로 치환해야함 일단 하드코딩딩딩 + .replace( + encodeURIComponent('#제목#제목#제목#제목#'), + 제목.trim().split(' ').map(encodeURIComponent).join('+'), + ) + ); + } + + private createLetterContent(작성자: string, 내용: string): string { + return ( + `

작성자: ${작성자}

` + + 내용 + .split('\n') + .map((line) => `

${line.trim() || ' '}

`) + .join('') + ).replaceAll(' ', ' '); + } + + private createOptions(session: TheCampSession): AxiosRequestConfig { + return { + headers: { + Accept: 'application/json, text/javascript, */*; q=0.01', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest', + 'User-Agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.70 Whale/3.13.131.27 Safari/537.36', + Host: 'www.thecamp.or.kr', + Origin: 'https://www.thecamp.or.kr', + Referer: 'https://www.thecamp.or.kr/eduUnitCafe/viewEduUnitCafeMain.do', + 'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Whale";v="3"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'Sec-Fetch-Site': 'same-origin', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Dest': 'empty', + Cookie: session.cookies + .map(({ key, value }) => `${key}=${value}`) + .join('; '), + }, + }; + } +} +export interface SendLetterDto { + 제목: string; + 작성자: string; + 내용: string; + 입영부대: 입영부대; + // trainUnitEduSeq + 입영부대EduId: string; + // traineeMgrSeq + 훈련병Id: string; +} + +export const sendLetterRequester = new SendLetterRequester(); diff --git "a/src/services/the-camp/requesters/send-letter/test/\354\204\261\352\263\265.json" "b/src/services/the-camp/requesters/send-letter/test/\354\204\261\352\263\265.json" new file mode 100644 index 0000000..631217c --- /dev/null +++ "b/src/services/the-camp/requesters/send-letter/test/\354\204\261\352\263\265.json" @@ -0,0 +1,3 @@ +{ + "data": { "resultCd": "0000", "resultMsg": "정상처리되었습니다." } +} diff --git a/src/services/the-camp/the-camp.service.ts b/src/services/the-camp/the-camp.service.ts index 241c0ec..b7bdec1 100644 --- a/src/services/the-camp/the-camp.service.ts +++ b/src/services/the-camp/the-camp.service.ts @@ -7,6 +7,8 @@ import { registerCafeRequester as _registerCafeRequester, RegisterSoldierDto, registerSoldierRequester as _registerSoldierRequester, + SendLetterDto, + sendLetterRequester as _sendLetterRequester, } from './requesters'; export class TheCampService { @@ -14,6 +16,7 @@ export class TheCampService { private readonly loginRequester = _loginRequester, private readonly registerSoldierRequester = _registerSoldierRequester, private readonly registerCafeRequester = _registerCafeRequester, + private readonly sendLetterRequester = _sendLetterRequester, ) {} async login(credential: Credential): Promise { @@ -36,5 +39,9 @@ export class TheCampService { ): Promise { await this.registerCafeRequester.request(dto, session); } + + async sendLetter(dto: SendLetterDto, session: TheCampSession): Promise { + await this.sendLetterRequester.request(dto, session); + } } export const theCampService = new TheCampService();