Skip to content

Commit 68655e8

Browse files
.env 파일 추가 및 ESLint 설정을 개선하여 TypeScript 관련 플러그인과 규칙을 통합하였으며, 패키지 의존성을 업데이트하고 Vitest를 추가하여 테스트 환경을 강화함. 또한, 여러 파일에서 스키마 및 API 메서드를 개선하여 코드 품질을 향상시킴.
1 parent 6fa2d2e commit 68655e8

16 files changed

Lines changed: 1221 additions & 339 deletions

File tree

.cursor/rules/tdd-rules.mdc

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: true
5+
---
6+
# ROLE AND EXPERTISE
7+
8+
You are a senior software engineer who follows Kent Beck's Test-Driven Development (TDD) and Tidy First principles. Your purpose is to guide development following these methodologies precisely.
9+
10+
# CORE DEVELOPMENT PRINCIPLES
11+
12+
- Always follow the TDD cycle: Red → Green → Refactor
13+
- Write the simplest failing test first
14+
- Implement the minimum code needed to make tests pass
15+
- Refactor only after tests are passing
16+
- Follow Beck's "Tidy First" approach by separating structural changes from behavioral changes
17+
- Maintain high code quality throughout development
18+
19+
# TDD METHODOLOGY GUIDANCE
20+
21+
- Start by writing a failing test that defines a small increment of functionality
22+
- Use meaningful test names that describe behavior (e.g., "shouldSumTwoPositiveNumbers")
23+
- Make test failures clear and informative
24+
- Write just enough code to make the test pass - no more
25+
- Once tests pass, consider if refactoring is needed
26+
- Repeat the cycle for new functionality
27+
28+
# TIDY FIRST APPROACH
29+
30+
- Separate all changes into two distinct types:
31+
1. STRUCTURAL CHANGES: Rearranging code without changing behavior (renaming, extracting methods, moving code)
32+
2. BEHAVIORAL CHANGES: Adding or modifying actual functionality
33+
- Never mix structural and behavioral changes in the same commit
34+
- Always make structural changes first when both are needed
35+
- Validate structural changes do not alter behavior by running tests before and after
36+
37+
# COMMIT DISCIPLINE
38+
39+
- Only commit when:
40+
1. ALL tests are passing
41+
2. ALL compiler/linter warnings have been resolved
42+
3. The change represents a single logical unit of work
43+
4. Commit messages clearly state whether the commit contains structural or behavioral changes
44+
- Use small, frequent commits rather than large, infrequent ones
45+
46+
# CODE QUALITY STANDARDS
47+
48+
- Eliminate duplication ruthlessly
49+
- Express intent clearly through naming and structure
50+
- Make dependencies explicit
51+
- Keep methods small and focused on a single responsibility
52+
- Minimize state and side effects
53+
- Use the simplest solution that could possibly work
54+
55+
# REFACTORING GUIDELINES
56+
57+
- Refactor only when tests are passing (in the "Green" phase)
58+
- Use established refactoring patterns with their proper names
59+
- Make one refactoring change at a time
60+
- Run tests after each refactoring step
61+
- Prioritize refactorings that remove duplication or improve clarity
62+
63+
# EXAMPLE WORKFLOW
64+
65+
When approaching a new feature:
66+
1. Write a simple failing test for a small part of the feature
67+
2. Implement the bare minimum to make it pass
68+
3. Run tests to confirm they pass (Green)
69+
4. Make any necessary structural changes (Tidy First), running tests after each change
70+
5. Commit structural changes separately
71+
6. Add another test for the next small increment of functionality
72+
7. Repeat until the feature is complete, committing behavioral changes separately from structural ones
73+
74+
Follow this process precisely, always prioritizing clean, well-tested code over quick implementation.
75+
76+
Always write one test at a time, make it run, then improve structure. Always run all the tests (except long-running tests) each time.
77+
78+
# TypeScript-specific
79+
80+
1. Prefer functional programming style over imperative style in Effect-ts(library). Use Schema library's feature instead of pattern matching with if let or match when possible.
81+
2. Don't use `any` type.
82+
3. Check and fix wrong import path(alias) when you write code.
83+
4. lint first, fix after write down code.
84+

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@ Thumbs.db
3232
Desktop.ini
3333
$RECYCLE.BIN/
3434

35+
# env
36+
.env
37+
3538
# Claude
3639
CLAUDE.md

eslint.config.mjs

Lines changed: 7 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,10 @@
1-
import typescriptEslintEslintPlugin from '@typescript-eslint/eslint-plugin';
2-
import tsdoc from 'eslint-plugin-tsdoc';
3-
import unusedImports from 'eslint-plugin-unused-imports';
4-
import globals from 'globals';
5-
import tsParser from '@typescript-eslint/parser';
6-
import path from 'node:path';
7-
import {fileURLToPath} from 'node:url';
8-
import js from '@eslint/js';
9-
import {FlatCompat} from '@eslint/eslintrc';
1+
import eslint from '@eslint/js';
2+
import tseslint from 'typescript-eslint';
103

11-
const __filename = fileURLToPath(import.meta.url);
12-
const __dirname = path.dirname(__filename);
13-
const compat = new FlatCompat({
14-
baseDirectory: __dirname,
15-
recommendedConfig: js.configs.recommended,
16-
allConfig: js.configs.all,
17-
});
18-
19-
export default [
20-
{
21-
ignores: ['dist/**/*', 'examples/**/*', 'debug/**/*', 'docs/**/*'],
22-
},
23-
...compat.extends('plugin:@typescript-eslint/recommended'),
4+
export default tseslint.config(
5+
eslint.configs.recommended,
6+
...tseslint.configs.recommended,
247
{
25-
plugins: {
26-
'@typescript-eslint': typescriptEslintEslintPlugin,
27-
tsdoc,
28-
'unused-imports': unusedImports,
29-
},
30-
31-
languageOptions: {
32-
globals: {
33-
...globals.node,
34-
},
35-
parser: tsParser,
36-
ecmaVersion: 2018,
37-
sourceType: 'module',
38-
},
39-
40-
rules: {
41-
quotes: ['error', 'single'],
42-
semi: [2, 'always'],
43-
'@typescript-eslint/explicit-function-return-type': 'off',
44-
'@typescript-eslint/no-explicit-any': 1,
45-
46-
'@typescript-eslint/no-inferrable-types': [
47-
'warn',
48-
{
49-
ignoreParameters: true,
50-
},
51-
],
52-
53-
'@typescript-eslint/no-unused-vars': 'warn',
54-
'unused-imports/no-unused-imports': 'warn',
55-
},
8+
ignores: ['jest.config.js', 'dist', 'coverage', 'docs', 'examples'],
569
},
57-
];
10+
);

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,22 @@
3737
"docs": "typedoc --entryPointStrategy expand ./src",
3838
"format": "prettier ./**/*.ts ./**/*.js ./**/*.mjs",
3939
"lint": "eslint . --plugin file-progress --rule \"file-progress/activate: 1\"",
40-
"dev": "tsup --watch"
40+
"dev": "tsup --watch",
41+
"test": "vitest run",
42+
"test:watch": "vitest"
4143
},
4244
"dependencies": {
4345
"date-fns": "^4.1.0",
4446
"effect": "^3.16.7"
4547
},
4648
"devDependencies": {
49+
"@effect/vitest": "^0.23.10",
4750
"@eslint/eslintrc": "^3",
4851
"@eslint/js": "^9",
4952
"@types/node": "^18",
5053
"@typescript-eslint/eslint-plugin": "^8.34.0",
5154
"@typescript-eslint/parser": "^8.34.0",
55+
"dotenv": "^16.4.5",
5256
"eslint": "^9",
5357
"eslint-plugin-file-progress": "^3.0.2",
5458
"eslint-plugin-tsdoc": "^0.4.0",
@@ -57,7 +61,8 @@
5761
"prettier": "^3.5.3",
5862
"tsup": "^8.5.0",
5963
"typedoc": "^0.28.5",
60-
"typescript": "^5.8.3"
64+
"typescript": "^5.8.3",
65+
"vitest": "^1.6.0"
6166
},
6267
"packageManager": "yarn@4.9.2",
6368
"engines": {

src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ export class SolapiMessageService {
4949
*/
5050
readonly getBlockGroups: typeof IamService.prototype.getBlockGroups;
5151

52+
/**
53+
* 수신 거부 번호 조회
54+
* @param data 수신 거부 번호 상세 조회용 request 데이터
55+
* @returns GetBlockNumbersResponse
56+
*/
57+
readonly getBlockNumbers: typeof IamService.prototype.getBlockNumbers;
58+
5259
// KakaoChannelService 위임
5360
/**
5461
* 카카오 채널 카테고리 조회

src/models/base/kakao/kakaoOption.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import {Schema} from 'effect';
22
import {kakaoOptionRequest} from '../../requests/kakao/kakaoOptionRequest';
33
import {KakaoButton, kakaoButtonSchema} from './kakaoButton';
44

5+
const kakaoOptionBmsSchema = Schema.Struct({
6+
targeting: Schema.optional(
7+
Schema.Enums({
8+
M: 'M',
9+
N: 'N',
10+
I: 'I',
11+
}),
12+
),
13+
});
14+
515
export const baseKakaoOptionSchema = Schema.Struct({
616
pfId: Schema.String,
717
templateId: Schema.optional(Schema.String),
@@ -28,6 +38,7 @@ export const baseKakaoOptionSchema = Schema.Struct({
2838
adFlag: Schema.optional(Schema.Boolean),
2939
imageId: Schema.optional(Schema.String),
3040
buttons: Schema.optional(Schema.Array(kakaoButtonSchema)),
41+
bms: Schema.optional(kakaoOptionBmsSchema),
3142
});
3243

3344
export class KakaoOption {

src/models/base/messages/message.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { Schema } from 'effect';
2-
import { FileIds } from '@models/requests/messages/groupMessageRequest';
3-
import { KakaoOption, baseKakaoOptionSchema } from '@models/base/kakao/kakaoOption';
4-
import { naverOptionSchema } from '@models/base/naver/naverOption';
5-
import { RcsOption, rcsOptionSchema } from '@models/base/rcs/rcsOption';
1+
import {
2+
KakaoOption,
3+
baseKakaoOptionSchema,
4+
} from '@models/base/kakao/kakaoOption';
5+
import {naverOptionSchema} from '@models/base/naver/naverOption';
6+
import {RcsOption, rcsOptionSchema} from '@models/base/rcs/rcsOption';
7+
import {FileIds} from '@models/requests/messages/groupMessageRequest';
8+
import {Schema} from 'effect';
69

710
/**
811
* @name MessageType 메시지 유형(단문 문자, 장문 문자, 알림톡 등)
@@ -70,10 +73,10 @@ export const messageSchema = Schema.Struct({
7073
country: Schema.optional(Schema.String),
7174
replacements: Schema.optional(Schema.Array(Schema.Struct({}))),
7275
customFields: Schema.optional(
73-
Schema.Record({ key: Schema.String, value: Schema.String }),
76+
Schema.Record({key: Schema.String, value: Schema.String}),
7477
),
7578
faxOptions: Schema.optional(
76-
Schema.Struct({ fileIds: Schema.Array(Schema.String) }),
79+
Schema.Struct({fileIds: Schema.Array(Schema.String)}),
7780
),
7881
naverOptions: Schema.optional(naverOptionSchema),
7982
});

src/models/requests/messages/groupMessageRequest.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import {Message} from '../../base/messages/message';
1+
import {Schema} from 'effect';
2+
import {messageSchema} from '../../base/messages/message';
23
import type {DefaultAgentType} from './requestConfig';
34

45
/**
56
* 그룹 메시지 추가 요청
67
*/
7-
export class GroupMessageAddRequest {
8-
messages: ReadonlyArray<Message>;
9-
10-
constructor(messages: Array<Message>) {
11-
this.messages = messages;
12-
}
13-
}
8+
export const groupMessageAddRequestSchema = Schema.Struct({
9+
messages: Schema.Array(messageSchema),
10+
});
11+
export type GroupMessageAddRequest = Schema.Schema.Type<
12+
typeof groupMessageAddRequestSchema
13+
>;
1414

1515
/**
1616
* 그룹 예약 발송 설정 요청

src/models/requests/messages/requestConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import {formatWithTransfer} from '@lib/stringDateTrasnfer';
22
import {Schema} from 'effect';
33

44
// SDK 및 OS 정보
5-
const osPlatform = `${process.platform} | ${process.version}`;
5+
export const osPlatform = `${process.platform} | ${process.version}`;
66
// NOTE: 라이브러리 배포 시 반드시 업데이트해야 합니다.
7-
const sdkVersion = 'nodejs/5.5.0';
7+
export const sdkVersion = 'nodejs/5.5.0';
88

99
// Agent 정보 타입
1010
export type DefaultAgentType = {

src/services/iam/iamService.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ import {
77
GetBlockGroupsFinalizeRequest,
88
GetBlockGroupsRequest,
99
} from '@models/requests/iam/getBlockGroupsRequest';
10+
import {
11+
GetBlockNumbersFinalizeRequest,
12+
GetBlockNumbersRequest,
13+
} from '@models/requests/iam/getBlockNumbersRequest';
1014
import {GetBlacksResponse} from '@models/responses/iam/getBlacksResponse';
1115
import {GetBlockGroupsResponse} from '@models/responses/iam/getBlockGroupsResponse';
16+
import {GetBlockNumbersResponse} from '@models/responses/iam/getBlockNumbersResponse';
1217
import DefaultService from '../defaultService';
1318

1419
export default class IamService extends DefaultService {
@@ -57,4 +62,26 @@ export default class IamService extends DefaultService {
5762
url: `iam/v1/block/groups${parameter}`,
5863
});
5964
}
65+
66+
/**
67+
* 수신 거부 번호 조회
68+
* @param data 수신 거부 번호 상세 조회용 request 데이터
69+
* @returns GetBlockNumbersResponse
70+
*/
71+
async getBlockNumbers(
72+
data?: GetBlockNumbersRequest,
73+
): Promise<GetBlockNumbersResponse> {
74+
let payload: GetBlockNumbersFinalizeRequest = {};
75+
if (data) {
76+
payload = new GetBlockNumbersFinalizeRequest(data);
77+
}
78+
const parameter = stringifyQuery(payload, {
79+
indices: false,
80+
addQueryPrefix: true,
81+
});
82+
return this.request<never, GetBlockNumbersResponse>({
83+
httpMethod: 'GET',
84+
url: `iam/v1/block/numbers${parameter}`,
85+
});
86+
}
6087
}

0 commit comments

Comments
 (0)