Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Commit 4c56de8

Browse files
API 문서 및 로그 표시 개선
1 parent 3a961a5 commit 4c56de8

File tree

3 files changed

+164
-79
lines changed

3 files changed

+164
-79
lines changed

src/app/components/api-docs/api-docs.component.ts

Lines changed: 125 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ import { AdminService, ApiDoc } from '../../services/admin.service';
3535
<div class="flex flex-col md:flex-row gap-4 mb-6">
3636
<div class="flex-1 max-w-md">
3737
<div class="relative">
38-
<input
39-
type="text"
40-
[(ngModel)]="searchTerm"
41-
(input)="filterApis()"
38+
<input
39+
type="text"
40+
[(ngModel)]="searchTerm"
41+
(input)="filterApis()"
4242
placeholder="경로나 설명으로 검색..."
4343
class="w-full p-3 pr-12 bg-md-sys-color-surface-container-highest text-md-sys-color-on-surface rounded-xl border border-md-sys-color-outline focus:border-md-sys-color-primary focus:outline-none md-typescale-body-large">
4444
<mat-icon class="w-5 h-5 absolute right-3 top-1/2 transform -translate-y-1/2 text-md-sys-color-on-surface-variant">search</mat-icon>
@@ -84,18 +84,18 @@ import { AdminService, ApiDoc } from '../../services/admin.service';
8484
<div *ngFor="let doc of filteredDocs; trackBy: trackByPath" class="md-card bg-md-sys-color-surface-container text-md-sys-color-on-surface">
8585
<div class="flex items-center justify-between mb-4">
8686
<div class="flex items-center gap-3">
87-
<span class="px-3 py-1 rounded-full text-sm font-medium min-w-[60px] text-center"
88-
[class]="getMethodColor(doc.method) === 'primary' ? 'bg-md-sys-color-primary text-md-sys-color-on-primary' :
89-
getMethodColor(doc.method) === 'accent' ? 'bg-md-sys-color-secondary text-md-sys-color-on-secondary' :
90-
getMethodColor(doc.method) === 'warn' ? 'bg-md-sys-color-error text-md-sys-color-on-error' :
87+
<span class="px-3 py-1 rounded-full text-sm font-medium min-w-[60px] text-center"
88+
[class]="getMethodColor(doc.method) === 'primary' ? 'bg-md-sys-color-primary text-md-sys-color-on-primary' :
89+
getMethodColor(doc.method) === 'accent' ? 'bg-md-sys-color-secondary text-md-sys-color-on-secondary' :
90+
getMethodColor(doc.method) === 'warn' ? 'bg-md-sys-color-error text-md-sys-color-on-error' :
9191
'bg-md-sys-color-surface-container-high text-md-sys-color-on-surface'">
9292
{{ doc.method }}
9393
</span>
9494
<span class="font-mono md-typescale-title-medium font-medium text-md-sys-color-on-surface">{{ doc.path }}</span>
9595
</div>
9696
</div>
97-
98-
<p class="md-typescale-body-large text-md-sys-color-on-surface-variant mb-6">{{ doc.brief }}</p>
97+
98+
<p class="md-typescale-body-large text-md-sys-color-on-surface-variant mb-6">{{ doc.name }}</p>
9999
100100
<div class="space-y-6">
101101
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 p-4 bg-md-sys-color-surface-container-high rounded-xl">
@@ -105,10 +105,10 @@ import { AdminService, ApiDoc } from '../../services/admin.service';
105105
</div>
106106
<div class="flex items-center gap-2">
107107
<span class="md-typescale-body-medium font-medium text-md-sys-color-on-surface-variant">메소드:</span>
108-
<span class="px-2 py-1 rounded-full text-xs"
109-
[class]="getMethodColor(doc.method) === 'primary' ? 'bg-md-sys-color-primary-container text-md-sys-color-on-primary-container' :
110-
getMethodColor(doc.method) === 'accent' ? 'bg-md-sys-color-secondary-container text-md-sys-color-on-secondary-container' :
111-
getMethodColor(doc.method) === 'warn' ? 'bg-md-sys-color-error-container text-md-sys-color-on-error-container' :
108+
<span class="px-2 py-1 rounded-full text-xs"
109+
[class]="getMethodColor(doc.method) === 'primary' ? 'bg-md-sys-color-primary-container text-md-sys-color-on-primary-container' :
110+
getMethodColor(doc.method) === 'accent' ? 'bg-md-sys-color-secondary-container text-md-sys-color-on-secondary-container' :
111+
getMethodColor(doc.method) === 'warn' ? 'bg-md-sys-color-error-container text-md-sys-color-on-error-container' :
112112
'bg-md-sys-color-surface-container text-md-sys-color-on-surface'">
113113
{{ doc.method }}
114114
</span>
@@ -119,12 +119,43 @@ import { AdminService, ApiDoc } from '../../services/admin.service';
119119
</div>
120120
</div>
121121
122-
<div *ngIf="doc.details" class="space-y-2">
122+
<div *ngIf="doc.routeParams && doc.routeParams.length > 0" class="space-y-2">
123123
<h4 class="md-typescale-title-small text-md-sys-color-on-surface flex items-center gap-2">
124-
<mat-icon class="w-5 h-5 text-md-sys-color-primary">description</mat-icon>
125-
상세 설명
124+
<mat-icon class="w-5 h-5 text-md-sys-color-primary">route</mat-icon>
125+
경로 파라미터
126126
</h4>
127-
<p class="md-typescale-body-medium text-md-sys-color-on-surface leading-relaxed">{{ doc.details }}</p>
127+
<div class="space-y-3">
128+
<div *ngFor="let routeParam of doc.routeParams" class="p-3 bg-md-sys-color-primary-container rounded-lg">
129+
<div class="flex items-start justify-between mb-2">
130+
<div class="flex items-center gap-2">
131+
<code class="px-2 py-1 bg-md-sys-color-surface-container rounded text-sm font-mono text-md-sys-color-on-surface">:{{ routeParam.name }}</code>
132+
<span class="px-2 py-1 rounded-full text-xs bg-md-sys-color-error-container text-md-sys-color-on-error-container">필수</span>
133+
</div>
134+
<span class="text-sm font-mono text-md-sys-color-on-primary-container">{{ routeParam.type }}</span>
135+
</div>
136+
<p class="md-typescale-body-small text-md-sys-color-on-primary-container">{{ routeParam.description }}</p>
137+
</div>
138+
</div>
139+
</div>
140+
141+
<div *ngIf="doc.params && doc.params.length > 0" class="space-y-2">
142+
<h4 class="md-typescale-title-small text-md-sys-color-on-surface flex items-center gap-2">
143+
<mat-icon class="w-5 h-5 text-md-sys-color-primary">settings</mat-icon>
144+
{{ doc.method.toUpperCase() === 'GET' ? '쿼리 파라미터' : '요청 본문' }}
145+
</h4>
146+
<div class="space-y-3">
147+
<div *ngFor="let param of doc.params" class="p-3 bg-md-sys-color-surface-container-high rounded-lg">
148+
<div class="flex items-start justify-between mb-2">
149+
<div class="flex items-center gap-2">
150+
<code class="px-2 py-1 bg-md-sys-color-surface-container rounded text-sm font-mono text-md-sys-color-on-surface">{{ param.name }}</code>
151+
<span class="px-2 py-1 rounded-full text-xs"
152+
[class]="param.required ? 'bg-md-sys-color-error-container text-md-sys-color-on-error-container' : 'bg-md-sys-color-surface-container text-md-sys-color-on-surface'">{{ param.required ? '필수' : '선택' }}</span>
153+
</div>
154+
<span class="text-sm font-mono text-md-sys-color-on-surface-variant">{{ param.type }}</span>
155+
</div>
156+
<p class="md-typescale-body-small text-md-sys-color-on-surface-variant">{{ param.description }}</p>
157+
</div>
158+
</div>
128159
</div>
129160
130161
<div *ngIf="doc.returns" class="space-y-2">
@@ -142,8 +173,8 @@ import { AdminService, ApiDoc } from '../../services/admin.service';
142173
</h4>
143174
<div class="relative bg-gray-900 text-gray-100 rounded-xl overflow-hidden">
144175
<pre class="p-4 overflow-x-auto font-mono text-sm leading-relaxed"><code>{{ generateCurlExample(doc) }}</code></pre>
145-
<button class="absolute top-3 right-3 p-2 bg-gray-800 hover:bg-gray-700 rounded-lg transition-colors"
146-
(click)="copyCurl(doc)"
176+
<button class="absolute top-3 right-3 p-2 bg-gray-800 hover:bg-gray-700 rounded-lg transition-colors"
177+
(click)="copyCurl(doc)"
147178
title="클립보드에 복사">
148179
<mat-icon class="w-5 h-5 text-gray-300">content_copy</mat-icon>
149180
</button>
@@ -161,11 +192,11 @@ import { AdminService, ApiDoc } from '../../services/admin.service';
161192
border-radius: 16px;
162193
transition: all 0.3s ease;
163194
}
164-
195+
165196
.md-card:hover {
166197
transform: translateY(-2px);
167198
}
168-
199+
169200
.md-button {
170201
border: none;
171202
cursor: pointer;
@@ -175,17 +206,17 @@ import { AdminService, ApiDoc } from '../../services/admin.service';
175206
align-items: center;
176207
justify-content: center;
177208
}
178-
209+
179210
.md-button:hover {
180211
transform: translateY(-1px);
181212
}
182-
213+
183214
.md-button:disabled {
184215
opacity: 0.5;
185216
cursor: not-allowed;
186217
transform: none;
187218
}
188-
219+
189220
input:focus {
190221
box-shadow: 0 0 0 2px var(--md-sys-color-primary);
191222
}
@@ -203,6 +234,7 @@ export class ApiDocsComponent implements OnInit {
203234
this.loadApiDocs();
204235
}
205236

237+
206238
loadApiDocs() {
207239
this.loading = true;
208240
this.adminService.getApiDocs().subscribe({
@@ -234,10 +266,17 @@ export class ApiDocsComponent implements OnInit {
234266
const term = this.searchTerm.toLowerCase();
235267
this.filteredDocs = this.apiDocs.filter(doc =>
236268
doc.path.toLowerCase().includes(term) ||
237-
doc.brief.toLowerCase().includes(term) ||
238-
doc.details.toLowerCase().includes(term) ||
269+
doc.name.toLowerCase().includes(term) ||
239270
doc.method.toLowerCase().includes(term) ||
240-
doc.file.toLowerCase().includes(term)
271+
doc.file.toLowerCase().includes(term) ||
272+
(doc.params && doc.params.some(param =>
273+
param.name.toLowerCase().includes(term) ||
274+
param.description.toLowerCase().includes(term)
275+
)) ||
276+
(doc.routeParams && doc.routeParams.some(routeParam =>
277+
routeParam.name.toLowerCase().includes(term) ||
278+
routeParam.description.toLowerCase().includes(term)
279+
))
241280
);
242281
}
243282

@@ -277,31 +316,65 @@ export class ApiDocsComponent implements OnInit {
277316

278317
generateCurlExample(doc: ApiDoc): string {
279318
const baseUrl = 'https://api-dev.dimiplan.com';
280-
const url = `${baseUrl}${doc.path}`;
319+
let url = `${baseUrl}${doc.path}`;
320+
321+
// Replace route parameters with example values
322+
if (doc.routeParams && doc.routeParams.length > 0) {
323+
doc.routeParams.forEach(routeParam => {
324+
const exampleValue = routeParam.type === 'string' ? 'example' : routeParam.type === 'number' ? '123' : 'value';
325+
url = url.replace(`:${routeParam.name}`, exampleValue);
326+
});
327+
}
281328

282329
switch (doc.method.toUpperCase()) {
283330
case 'GET':
331+
if (doc.params && doc.params.length > 0) {
332+
const queryParams = doc.params.map(param => `${param.name}=${param.type === 'string' ? 'value' : param.type === 'number' ? '1' : 'true'}`).join('&');
333+
url += `?${queryParams}`;
334+
}
284335
return `curl -X GET "${url}" \\
285336
-H "Content-Type: application/json" \\
286337
-H "Authorization: Bearer YOUR_TOKEN"`;
287338

288339
case 'POST':
340+
let postBody = '{\n';
341+
if (doc.params && doc.params.length > 0) {
342+
const bodyParams = doc.params.map(param => {
343+
const value = param.type === 'string' ? '"value"' : param.type === 'number' ? '1' : 'true';
344+
return ` "${param.name}": ${value}`;
345+
}).join(',\n');
346+
postBody += bodyParams + '\n';
347+
} else {
348+
postBody += ' "key": "value"\n';
349+
}
350+
postBody += ' }';
289351
return `curl -X POST "${url}" \\
290352
-H "Content-Type: application/json" \\
291353
-H "Authorization: Bearer YOUR_TOKEN" \\
292-
-d '{
293-
"key": "value"
294-
}'`;
354+
-d '${postBody}'`;
295355

296356
case 'PUT':
357+
let putBody = '{\n';
358+
if (doc.params && doc.params.length > 0) {
359+
const bodyParams = doc.params.map(param => {
360+
const value = param.type === 'string' ? '"value"' : param.type === 'number' ? '1' : 'true';
361+
return ` "${param.name}": ${value}`;
362+
}).join(',\n');
363+
putBody += bodyParams + '\n';
364+
} else {
365+
putBody += ' "key": "value"\n';
366+
}
367+
putBody += ' }';
297368
return `curl -X PUT "${url}" \\
298369
-H "Content-Type: application/json" \\
299370
-H "Authorization: Bearer YOUR_TOKEN" \\
300-
-d '{
301-
"key": "value"
302-
}'`;
371+
-d '${putBody}'`;
303372

304373
case 'DELETE':
374+
if (doc.params && doc.params.length > 0) {
375+
const queryParams = doc.params.map(param => `${param.name}=${param.type === 'string' ? 'value' : param.type === 'number' ? '1' : 'true'}`).join('&');
376+
url += `?${queryParams}`;
377+
}
305378
return `curl -X DELETE "${url}" \\
306379
-H "Content-Type: application/json" \\
307380
-H "Authorization: Bearer YOUR_TOKEN"`;
@@ -351,14 +424,25 @@ export class ApiDocsComponent implements OnInit {
351424

352425
for (const doc of docs) {
353426
markdown += `### ${doc.method} ${doc.path}\n\n`;
354-
if (doc.brief) {
355-
markdown += `**요약:** ${doc.brief}\n\n`;
427+
if (doc.name) {
428+
markdown += `**요약:** ${doc.name}\n\n`;
429+
}
430+
if (doc.routeParams && doc.routeParams.length > 0) {
431+
markdown += `**경로 파라미터:**\n\n`;
432+
for (const routeParam of doc.routeParams) {
433+
markdown += `- \`:${routeParam.name}\` (${routeParam.type}) **필수**: ${routeParam.description}\n`;
434+
}
435+
markdown += '\n';
356436
}
357-
if (doc.details) {
358-
markdown += `**설명:** ${doc.details}\n\n`;
437+
if (doc.params && doc.params.length > 0) {
438+
markdown += `**${doc.method.toUpperCase() === 'GET' ? '쿼리 파라미터' : '요청 본문'}:**\n\n`;
439+
for (const param of doc.params) {
440+
markdown += `- \`${param.name}\` (${param.type}) ${param.required ? '**필수**' : '*선택*'}: ${param.description}\n`;
441+
}
442+
markdown += '\n';
359443
}
360444
if (doc.returns) {
361-
markdown += `**반환값:** ${doc.returns}\n\n`;
445+
markdown += `**반환값:** ${doc.returns.type} - ${doc.returns.description}\n\n`;
362446
}
363447
markdown += '**사용 예시:**\n\n';
364448
markdown += '```bash\n';
@@ -371,7 +455,7 @@ export class ApiDocsComponent implements OnInit {
371455
return markdown;
372456
}
373457

374-
trackByPath(index: number, doc: ApiDoc): string {
458+
trackByPath(_: number, doc: ApiDoc): string {
375459
return `${doc.method}-${doc.path}`;
376460
}
377461
}

0 commit comments

Comments
 (0)