aiwf 0.3.17 → 0.3.18
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/package.json +1 -1
- package/src/config/file-lists.js +5 -5
- package/templates/README.md +0 -78
- package/templates/api-server/config.json +0 -64
- package/templates/api-server/template/.aiwf/config.json +0 -56
- package/templates/api-server/template/.aiwf/feature-ledger.json +0 -42
- package/templates/api-server/template/.aiwf/personas/backend-engineer.json +0 -40
- package/templates/api-server/template/.aiwf/scripts/cli.js +0 -111
- package/templates/api-server/template/.env.example +0 -29
- package/templates/api-server/template/.eslintrc.json +0 -23
- package/templates/api-server/template/README.md +0 -171
- package/templates/api-server/template/jest.config.js +0 -26
- package/templates/api-server/template/nodemon.json +0 -9
- package/templates/api-server/template/package.json +0 -61
- package/templates/api-server/template/src/app.ts +0 -60
- package/templates/api-server/template/src/config/swagger.ts +0 -38
- package/templates/api-server/template/src/controllers/aiwfController.ts +0 -54
- package/templates/api-server/template/src/controllers/authController.ts +0 -128
- package/templates/api-server/template/src/controllers/statusController.ts +0 -28
- package/templates/api-server/template/src/index.ts +0 -28
- package/templates/api-server/template/src/middleware/aiwfMiddleware.ts +0 -50
- package/templates/api-server/template/src/middleware/authMiddleware.ts +0 -41
- package/templates/api-server/template/src/middleware/errorHandler.ts +0 -42
- package/templates/api-server/template/src/middleware/notFoundHandler.ts +0 -14
- package/templates/api-server/template/src/middleware/requestLogger.ts +0 -20
- package/templates/api-server/template/src/routes/aiwf.ts +0 -31
- package/templates/api-server/template/src/routes/index.ts +0 -13
- package/templates/api-server/template/src/routes/v1/index.ts +0 -67
- package/templates/api-server/template/src/utils/logger.ts +0 -37
- package/templates/api-server/template/tests/app.test.ts +0 -40
- package/templates/api-server/template/tsconfig.json +0 -43
- package/templates/npm-library/config.json +0 -61
- package/templates/npm-library/template/LICENSE +0 -21
- package/templates/npm-library/template/README.md +0 -201
- package/templates/npm-library/template/package.json +0 -78
- package/templates/web-app/config.json +0 -53
- package/templates/web-app/template/.aiwf/config.json +0 -54
- package/templates/web-app/template/.aiwf/feature-ledger.json +0 -37
- package/templates/web-app/template/.aiwf/personas/fullstack-developer.json +0 -40
- package/templates/web-app/template/.aiwf/scripts/cli.js +0 -110
- package/templates/web-app/template/.eslintrc.cjs +0 -20
- package/templates/web-app/template/README.md +0 -151
- package/templates/web-app/template/index.html +0 -14
- package/templates/web-app/template/package.json +0 -44
- package/templates/web-app/template/postcss.config.js +0 -6
- package/templates/web-app/template/public/vite.svg +0 -1
- package/templates/web-app/template/src/App.tsx +0 -21
- package/templates/web-app/template/src/components/Layout.tsx +0 -57
- package/templates/web-app/template/src/components/aiwf/ContextStatus.tsx +0 -107
- package/templates/web-app/template/src/components/aiwf/TokenUsage.tsx +0 -71
- package/templates/web-app/template/src/index.css +0 -60
- package/templates/web-app/template/src/main.tsx +0 -10
- package/templates/web-app/template/src/pages/AiwfDashboard.tsx +0 -57
- package/templates/web-app/template/src/pages/HomePage.tsx +0 -89
- package/templates/web-app/template/src/pages/NotFound.tsx +0 -25
- package/templates/web-app/template/src/stores/aiwfStore.ts +0 -126
- package/templates/web-app/template/src/types/global.d.ts +0 -9
- package/templates/web-app/template/src/vite-env.d.ts +0 -1
- package/templates/web-app/template/tailwind.config.js +0 -30
- package/templates/web-app/template/tsconfig.json +0 -37
- package/templates/web-app/template/tsconfig.node.json +0 -10
- package/templates/web-app/template/vite.config.ts +0 -23
- /package/rules/global/{code-style-guide.md → aiwf-code-style-guide.md} +0 -0
- /package/rules/global/{coding-principles.md → aiwf-coding-principles.md} +0 -0
- /package/rules/global/{development-process.md → aiwf-development-process.md} +0 -0
- /package/rules/global/{global-rules.md → aiwf-global-rules.md} +0 -0
- /package/rules/manual/{generate-plan-docs.md → aiwf-generate-plan-docs.md} +0 -0
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{projectName}}",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"description": "AIWF 통합 Express API 서버",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "nodemon",
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"start": "node dist/index.js",
|
|
10
|
-
"test": "jest",
|
|
11
|
-
"test:watch": "jest --watch",
|
|
12
|
-
"lint": "eslint . --ext .ts",
|
|
13
|
-
"aiwf": "node .aiwf/scripts/cli.js",
|
|
14
|
-
"aiwf:status": "npm run aiwf status",
|
|
15
|
-
"aiwf:feature": "npm run aiwf feature",
|
|
16
|
-
"aiwf:api": "npm run aiwf api"
|
|
17
|
-
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"api",
|
|
20
|
-
"express",
|
|
21
|
-
"typescript",
|
|
22
|
-
"aiwf"
|
|
23
|
-
],
|
|
24
|
-
"author": "",
|
|
25
|
-
"license": "MIT",
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"express": "^4.18.2",
|
|
28
|
-
"cors": "^2.8.5",
|
|
29
|
-
"helmet": "^7.0.0",
|
|
30
|
-
"compression": "^1.7.4",
|
|
31
|
-
"dotenv": "^16.3.1",
|
|
32
|
-
"jsonwebtoken": "^9.0.1",
|
|
33
|
-
"bcryptjs": "^2.4.3",
|
|
34
|
-
"express-validator": "^7.0.1",
|
|
35
|
-
"swagger-jsdoc": "^6.2.8",
|
|
36
|
-
"swagger-ui-express": "^5.0.0",
|
|
37
|
-
"winston": "^3.9.0",
|
|
38
|
-
"express-rate-limit": "^6.8.0"
|
|
39
|
-
},
|
|
40
|
-
"devDependencies": {
|
|
41
|
-
"@types/express": "^4.17.17",
|
|
42
|
-
"@types/node": "^20.0.0",
|
|
43
|
-
"@types/cors": "^2.8.13",
|
|
44
|
-
"@types/compression": "^1.7.2",
|
|
45
|
-
"@types/jsonwebtoken": "^9.0.2",
|
|
46
|
-
"@types/bcryptjs": "^2.4.2",
|
|
47
|
-
"@types/swagger-jsdoc": "^6.0.1",
|
|
48
|
-
"@types/swagger-ui-express": "^4.1.3",
|
|
49
|
-
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
|
50
|
-
"@typescript-eslint/parser": "^5.60.0",
|
|
51
|
-
"typescript": "^5.1.0",
|
|
52
|
-
"ts-node": "^10.9.0",
|
|
53
|
-
"nodemon": "^3.0.0",
|
|
54
|
-
"eslint": "^8.43.0",
|
|
55
|
-
"jest": "^29.5.0",
|
|
56
|
-
"@types/jest": "^29.5.0",
|
|
57
|
-
"ts-jest": "^29.1.0",
|
|
58
|
-
"supertest": "^6.3.0",
|
|
59
|
-
"@types/supertest": "^2.0.12"
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import express, { Application } from 'express';
|
|
2
|
-
import cors from 'cors';
|
|
3
|
-
import helmet from 'helmet';
|
|
4
|
-
import compression from 'compression';
|
|
5
|
-
import rateLimit from 'express-rate-limit';
|
|
6
|
-
import swaggerUi from 'swagger-ui-express';
|
|
7
|
-
import { swaggerSpec } from '@config/swagger';
|
|
8
|
-
import { errorHandler } from '@middleware/errorHandler';
|
|
9
|
-
import { notFoundHandler } from '@middleware/notFoundHandler';
|
|
10
|
-
import { requestLogger } from '@middleware/requestLogger';
|
|
11
|
-
import { aiwfMiddleware } from '@middleware/aiwfMiddleware';
|
|
12
|
-
import routes from '@routes/index';
|
|
13
|
-
|
|
14
|
-
const app: Application = express();
|
|
15
|
-
|
|
16
|
-
// Security middleware
|
|
17
|
-
app.use(helmet());
|
|
18
|
-
app.use(cors());
|
|
19
|
-
|
|
20
|
-
// Rate limiting
|
|
21
|
-
const limiter = rateLimit({
|
|
22
|
-
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000'),
|
|
23
|
-
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100'),
|
|
24
|
-
message: 'Too many requests from this IP, please try again later.',
|
|
25
|
-
});
|
|
26
|
-
app.use('/api', limiter);
|
|
27
|
-
|
|
28
|
-
// Body parsing middleware
|
|
29
|
-
app.use(express.json({ limit: '10mb' }));
|
|
30
|
-
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
31
|
-
|
|
32
|
-
// Compression middleware
|
|
33
|
-
app.use(compression());
|
|
34
|
-
|
|
35
|
-
// Request logging
|
|
36
|
-
app.use(requestLogger);
|
|
37
|
-
|
|
38
|
-
// AIWF integration
|
|
39
|
-
app.use(aiwfMiddleware);
|
|
40
|
-
|
|
41
|
-
// API Documentation
|
|
42
|
-
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
|
43
|
-
|
|
44
|
-
// Health check
|
|
45
|
-
app.get('/health', (req, res) => {
|
|
46
|
-
res.status(200).json({
|
|
47
|
-
status: 'healthy',
|
|
48
|
-
timestamp: new Date().toISOString(),
|
|
49
|
-
uptime: process.uptime(),
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// API routes
|
|
54
|
-
app.use('/api', routes);
|
|
55
|
-
|
|
56
|
-
// Error handlers
|
|
57
|
-
app.use(notFoundHandler);
|
|
58
|
-
app.use(errorHandler);
|
|
59
|
-
|
|
60
|
-
export default app;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import swaggerJsdoc from 'swagger-jsdoc';
|
|
2
|
-
import { version } from '../../package.json';
|
|
3
|
-
|
|
4
|
-
const options: swaggerJsdoc.Options = {
|
|
5
|
-
definition: {
|
|
6
|
-
openapi: '3.0.0',
|
|
7
|
-
info: {
|
|
8
|
-
title: '{{projectName}} API',
|
|
9
|
-
version,
|
|
10
|
-
description: 'AIWF 통합 Express API 서버',
|
|
11
|
-
contact: {
|
|
12
|
-
name: 'API Support',
|
|
13
|
-
email: 'support@example.com',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
servers: [
|
|
17
|
-
{
|
|
18
|
-
url: `http://localhost:${process.env.PORT || 3000}`,
|
|
19
|
-
description: 'Development server',
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
components: {
|
|
23
|
-
securitySchemes: {
|
|
24
|
-
bearerAuth: {
|
|
25
|
-
type: 'http',
|
|
26
|
-
scheme: 'bearer',
|
|
27
|
-
bearerFormat: 'JWT',
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
security: [{
|
|
32
|
-
bearerAuth: [],
|
|
33
|
-
}],
|
|
34
|
-
},
|
|
35
|
-
apis: ['./src/routes/**/*.ts', './src/models/**/*.ts'],
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export const swaggerSpec = swaggerJsdoc(options);
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
export const aiwfController = {
|
|
6
|
-
|
|
7
|
-
getTokenUsage: async (req: Request, res: Response, next: NextFunction) => {
|
|
8
|
-
try {
|
|
9
|
-
// 실제 구현에서는 토큰 사용량을 추적하는 로직 필요
|
|
10
|
-
const mockData = {
|
|
11
|
-
total: parseInt(process.env.AIWF_TOKEN_LIMIT || '100000'),
|
|
12
|
-
used: 45000,
|
|
13
|
-
remaining: 55000,
|
|
14
|
-
percentage: 45,
|
|
15
|
-
history: [
|
|
16
|
-
{
|
|
17
|
-
date: new Date().toISOString(),
|
|
18
|
-
tokens: 1500,
|
|
19
|
-
action: 'API endpoint creation',
|
|
20
|
-
endpoint: 'POST /api/v1/users',
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
res.json({
|
|
26
|
-
success: true,
|
|
27
|
-
data: mockData,
|
|
28
|
-
});
|
|
29
|
-
} catch (error) {
|
|
30
|
-
next(error);
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
getContextStatus: async (req: Request, res: Response, next: NextFunction) => {
|
|
35
|
-
try {
|
|
36
|
-
const configPath = path.join(process.cwd(), '.aiwf', 'config.json');
|
|
37
|
-
const configData = await fs.readFile(configPath, 'utf-8');
|
|
38
|
-
const config = JSON.parse(configData);
|
|
39
|
-
|
|
40
|
-
res.json({
|
|
41
|
-
success: true,
|
|
42
|
-
data: {
|
|
43
|
-
compressionLevel: config.features.context_compression.level,
|
|
44
|
-
cacheEnabled: config.features.context_compression.cache_enabled,
|
|
45
|
-
tokenLimit: config.features.context_compression.token_limit,
|
|
46
|
-
activePersona: config.features.ai_persona.default,
|
|
47
|
-
availablePersonas: config.features.ai_persona.available,
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
} catch (error) {
|
|
51
|
-
next(error);
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
};
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import { validationResult } from 'express-validator';
|
|
3
|
-
import bcrypt from 'bcryptjs';
|
|
4
|
-
import jwt from 'jsonwebtoken';
|
|
5
|
-
|
|
6
|
-
interface User {
|
|
7
|
-
id: string;
|
|
8
|
-
email: string;
|
|
9
|
-
password: string;
|
|
10
|
-
name: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// 임시 메모리 저장소 (실제로는 데이터베이스 사용)
|
|
14
|
-
const users: User[] = [];
|
|
15
|
-
|
|
16
|
-
export const authController = {
|
|
17
|
-
register: async (req: Request, res: Response, next: NextFunction) => {
|
|
18
|
-
try {
|
|
19
|
-
const errors = validationResult(req);
|
|
20
|
-
if (!errors.isEmpty()) {
|
|
21
|
-
return res.status(400).json({
|
|
22
|
-
success: false,
|
|
23
|
-
errors: errors.array()
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const { email, password, name } = req.body;
|
|
28
|
-
|
|
29
|
-
// 중복 체크
|
|
30
|
-
const existingUser = users.find(u => u.email === email);
|
|
31
|
-
if (existingUser) {
|
|
32
|
-
return res.status(409).json({
|
|
33
|
-
success: false,
|
|
34
|
-
error: { message: 'Email already exists' }
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 비밀번호 해싱
|
|
39
|
-
const hashedPassword = await bcrypt.hash(
|
|
40
|
-
password,
|
|
41
|
-
parseInt(process.env.BCRYPT_ROUNDS || '10')
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
// 사용자 생성
|
|
45
|
-
const newUser: User = {
|
|
46
|
-
id: Date.now().toString(),
|
|
47
|
-
email,
|
|
48
|
-
password: hashedPassword,
|
|
49
|
-
name,
|
|
50
|
-
};
|
|
51
|
-
users.push(newUser);
|
|
52
|
-
|
|
53
|
-
// JWT 토큰 생성
|
|
54
|
-
const token = jwt.sign(
|
|
55
|
-
{ id: newUser.id, email: newUser.email },
|
|
56
|
-
process.env.JWT_SECRET || 'secret',
|
|
57
|
-
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
res.status(201).json({
|
|
61
|
-
success: true,
|
|
62
|
-
data: {
|
|
63
|
-
user: {
|
|
64
|
-
id: newUser.id,
|
|
65
|
-
email: newUser.email,
|
|
66
|
-
name: newUser.name,
|
|
67
|
-
},
|
|
68
|
-
token,
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
} catch (error) {
|
|
72
|
-
next(error);
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
login: async (req: Request, res: Response, next: NextFunction) => {
|
|
77
|
-
try {
|
|
78
|
-
const errors = validationResult(req);
|
|
79
|
-
if (!errors.isEmpty()) {
|
|
80
|
-
return res.status(400).json({
|
|
81
|
-
success: false,
|
|
82
|
-
errors: errors.array()
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const { email, password } = req.body;
|
|
87
|
-
|
|
88
|
-
// 사용자 찾기
|
|
89
|
-
const user = users.find(u => u.email === email);
|
|
90
|
-
if (!user) {
|
|
91
|
-
return res.status(401).json({
|
|
92
|
-
success: false,
|
|
93
|
-
error: { message: 'Invalid credentials' }
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// 비밀번호 확인
|
|
98
|
-
const isPasswordValid = await bcrypt.compare(password, user.password);
|
|
99
|
-
if (!isPasswordValid) {
|
|
100
|
-
return res.status(401).json({
|
|
101
|
-
success: false,
|
|
102
|
-
error: { message: 'Invalid credentials' }
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// JWT 토큰 생성
|
|
107
|
-
const token = jwt.sign(
|
|
108
|
-
{ id: user.id, email: user.email },
|
|
109
|
-
process.env.JWT_SECRET || 'secret',
|
|
110
|
-
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
res.json({
|
|
114
|
-
success: true,
|
|
115
|
-
data: {
|
|
116
|
-
user: {
|
|
117
|
-
id: user.id,
|
|
118
|
-
email: user.email,
|
|
119
|
-
name: user.name,
|
|
120
|
-
},
|
|
121
|
-
token,
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
} catch (error) {
|
|
125
|
-
next(error);
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Request, Response } from 'express';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
|
|
4
|
-
export const statusController = {
|
|
5
|
-
getStatus: (req: Request, res: Response) => {
|
|
6
|
-
res.json({
|
|
7
|
-
success: true,
|
|
8
|
-
data: {
|
|
9
|
-
service: '{{projectName}}',
|
|
10
|
-
version: process.env.npm_package_version || '0.0.1',
|
|
11
|
-
environment: process.env.NODE_ENV || 'development',
|
|
12
|
-
uptime: process.uptime(),
|
|
13
|
-
timestamp: new Date().toISOString(),
|
|
14
|
-
system: {
|
|
15
|
-
platform: os.platform(),
|
|
16
|
-
release: os.release(),
|
|
17
|
-
totalMemory: os.totalmem(),
|
|
18
|
-
freeMemory: os.freemem(),
|
|
19
|
-
cpus: os.cpus().length,
|
|
20
|
-
},
|
|
21
|
-
aiwf: {
|
|
22
|
-
enabled: process.env.AIWF_ENABLED === 'true',
|
|
23
|
-
compressionLevel: process.env.AIWF_COMPRESSION_LEVEL || 'balanced',
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
},
|
|
28
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
2
|
-
import app from './app';
|
|
3
|
-
import logger from '@utils/logger';
|
|
4
|
-
|
|
5
|
-
const PORT = process.env.PORT || 3000;
|
|
6
|
-
const HOST = process.env.HOST || 'localhost';
|
|
7
|
-
|
|
8
|
-
const server = app.listen(PORT, () => {
|
|
9
|
-
logger.info(`🚀 Server is running on http://${HOST}:${PORT}`);
|
|
10
|
-
logger.info(`📚 API Documentation available at http://${HOST}:${PORT}/docs`);
|
|
11
|
-
logger.info(`🤖 AIWF Integration enabled`);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// Graceful shutdown
|
|
15
|
-
process.on('SIGTERM', () => {
|
|
16
|
-
logger.info('SIGTERM signal received: closing HTTP server');
|
|
17
|
-
server.close(() => {
|
|
18
|
-
logger.info('HTTP server closed');
|
|
19
|
-
process.exit(0);
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
24
|
-
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
25
|
-
// Application specific logging, throwing an error, or other logic here
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
export default server;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
interface AiwfRequest extends Request {
|
|
6
|
-
aiwf?: {
|
|
7
|
-
featureId?: string;
|
|
8
|
-
persona?: string;
|
|
9
|
-
tokenUsage?: number;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const aiwfMiddleware = async (
|
|
14
|
-
req: AiwfRequest,
|
|
15
|
-
res: Response,
|
|
16
|
-
next: NextFunction
|
|
17
|
-
) => {
|
|
18
|
-
// AIWF 헤더 체크
|
|
19
|
-
const featureId = req.headers['x-aiwf-feature-id'] as string;
|
|
20
|
-
const persona = req.headers['x-aiwf-persona'] as string;
|
|
21
|
-
|
|
22
|
-
req.aiwf = {
|
|
23
|
-
featureId,
|
|
24
|
-
persona: persona || 'backend-engineer',
|
|
25
|
-
tokenUsage: 0,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Feature 추적
|
|
29
|
-
if (featureId && process.env.AIWF_ENABLED === 'true') {
|
|
30
|
-
try {
|
|
31
|
-
const ledgerPath = path.join(process.cwd(), '.aiwf', 'feature-ledger.json');
|
|
32
|
-
const ledgerData = await fs.readFile(ledgerPath, 'utf-8');
|
|
33
|
-
const ledger = JSON.parse(ledgerData);
|
|
34
|
-
|
|
35
|
-
// API 엔드포인트 추적 로직
|
|
36
|
-
const endpoint = `${req.method} ${req.route?.path || req.path}`;
|
|
37
|
-
const feature = ledger.features.find((f: any) => f.id === featureId);
|
|
38
|
-
|
|
39
|
-
if (feature && !feature.endpoints?.includes(endpoint)) {
|
|
40
|
-
feature.endpoints = feature.endpoints || [];
|
|
41
|
-
feature.endpoints.push(endpoint);
|
|
42
|
-
await fs.writeFile(ledgerPath, JSON.stringify(ledger, null, 2));
|
|
43
|
-
}
|
|
44
|
-
} catch (error) {
|
|
45
|
-
// 에러 무시 (AIWF 기능이 없어도 API는 동작해야 함)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
next();
|
|
50
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import jwt from 'jsonwebtoken';
|
|
3
|
-
|
|
4
|
-
interface AuthRequest extends Request {
|
|
5
|
-
user?: {
|
|
6
|
-
id: string;
|
|
7
|
-
email: string;
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const authMiddleware = (
|
|
12
|
-
req: AuthRequest,
|
|
13
|
-
res: Response,
|
|
14
|
-
next: NextFunction
|
|
15
|
-
) => {
|
|
16
|
-
const authHeader = req.headers.authorization;
|
|
17
|
-
|
|
18
|
-
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
19
|
-
return res.status(401).json({
|
|
20
|
-
success: false,
|
|
21
|
-
error: { message: 'No token provided' }
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const token = authHeader.substring(7);
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const decoded = jwt.verify(
|
|
29
|
-
token,
|
|
30
|
-
process.env.JWT_SECRET || 'secret'
|
|
31
|
-
) as { id: string; email: string };
|
|
32
|
-
|
|
33
|
-
req.user = decoded;
|
|
34
|
-
next();
|
|
35
|
-
} catch (error) {
|
|
36
|
-
return res.status(401).json({
|
|
37
|
-
success: false,
|
|
38
|
-
error: { message: 'Invalid token' }
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import logger from '@utils/logger';
|
|
3
|
-
|
|
4
|
-
interface CustomError extends Error {
|
|
5
|
-
statusCode?: number;
|
|
6
|
-
status?: string;
|
|
7
|
-
isOperational?: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const errorHandler = (
|
|
11
|
-
err: CustomError,
|
|
12
|
-
req: Request,
|
|
13
|
-
res: Response,
|
|
14
|
-
next: NextFunction
|
|
15
|
-
) => {
|
|
16
|
-
let { statusCode = 500, message } = err;
|
|
17
|
-
|
|
18
|
-
logger.error({
|
|
19
|
-
error: err,
|
|
20
|
-
request: {
|
|
21
|
-
method: req.method,
|
|
22
|
-
url: req.url,
|
|
23
|
-
headers: req.headers,
|
|
24
|
-
body: req.body,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Don't leak error details in production
|
|
29
|
-
if (process.env.NODE_ENV === 'production' && statusCode === 500) {
|
|
30
|
-
message = 'Internal Server Error';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
res.status(statusCode).json({
|
|
34
|
-
success: false,
|
|
35
|
-
error: {
|
|
36
|
-
statusCode,
|
|
37
|
-
message,
|
|
38
|
-
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
|
|
39
|
-
},
|
|
40
|
-
timestamp: new Date().toISOString(),
|
|
41
|
-
});
|
|
42
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Request, Response } from 'express';
|
|
2
|
-
|
|
3
|
-
export const notFoundHandler = (req: Request, res: Response) => {
|
|
4
|
-
res.status(404).json({
|
|
5
|
-
success: false,
|
|
6
|
-
error: {
|
|
7
|
-
statusCode: 404,
|
|
8
|
-
message: 'Resource not found',
|
|
9
|
-
path: req.url,
|
|
10
|
-
method: req.method,
|
|
11
|
-
},
|
|
12
|
-
timestamp: new Date().toISOString(),
|
|
13
|
-
});
|
|
14
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import logger from '@utils/logger';
|
|
3
|
-
|
|
4
|
-
export const requestLogger = (req: Request, res: Response, next: NextFunction) => {
|
|
5
|
-
const start = Date.now();
|
|
6
|
-
|
|
7
|
-
res.on('finish', () => {
|
|
8
|
-
const duration = Date.now() - start;
|
|
9
|
-
logger.info({
|
|
10
|
-
method: req.method,
|
|
11
|
-
url: req.url,
|
|
12
|
-
status: res.statusCode,
|
|
13
|
-
duration: `${duration}ms`,
|
|
14
|
-
ip: req.ip,
|
|
15
|
-
userAgent: req.get('user-agent'),
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
next();
|
|
20
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { aiwfController } from '@controllers/aiwfController';
|
|
3
|
-
|
|
4
|
-
const router = Router();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @swagger
|
|
9
|
-
* /api/aiwf/token-usage:
|
|
10
|
-
* get:
|
|
11
|
-
* summary: 토큰 사용량 조회
|
|
12
|
-
* tags: [AIWF]
|
|
13
|
-
* responses:
|
|
14
|
-
* 200:
|
|
15
|
-
* description: 토큰 사용 현황
|
|
16
|
-
*/
|
|
17
|
-
router.get('/token-usage', aiwfController.getTokenUsage);
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @swagger
|
|
21
|
-
* /api/aiwf/context:
|
|
22
|
-
* get:
|
|
23
|
-
* summary: Context 상태 조회
|
|
24
|
-
* tags: [AIWF]
|
|
25
|
-
* responses:
|
|
26
|
-
* 200:
|
|
27
|
-
* description: Context 압축 상태
|
|
28
|
-
*/
|
|
29
|
-
router.get('/context', aiwfController.getContextStatus);
|
|
30
|
-
|
|
31
|
-
export default router;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import v1Routes from './v1';
|
|
3
|
-
import aiwfRoutes from './aiwf';
|
|
4
|
-
|
|
5
|
-
const router = Router();
|
|
6
|
-
|
|
7
|
-
// API 버전 라우팅
|
|
8
|
-
router.use('/v1', v1Routes);
|
|
9
|
-
|
|
10
|
-
// AIWF 전용 라우트
|
|
11
|
-
router.use('/aiwf', aiwfRoutes);
|
|
12
|
-
|
|
13
|
-
export default router;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { statusController } from '@controllers/statusController';
|
|
3
|
-
import { authController } from '@controllers/authController';
|
|
4
|
-
import { authMiddleware } from '@middleware/authMiddleware';
|
|
5
|
-
|
|
6
|
-
const router = Router();
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @swagger
|
|
10
|
-
* /api/v1/status:
|
|
11
|
-
* get:
|
|
12
|
-
* summary: API 상태 확인
|
|
13
|
-
* tags: [Status]
|
|
14
|
-
* responses:
|
|
15
|
-
* 200:
|
|
16
|
-
* description: API 상태 정보
|
|
17
|
-
*/
|
|
18
|
-
router.get('/status', statusController.getStatus);
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @swagger
|
|
22
|
-
* /api/v1/auth/register:
|
|
23
|
-
* post:
|
|
24
|
-
* summary: 사용자 등록
|
|
25
|
-
* tags: [Auth]
|
|
26
|
-
* requestBody:
|
|
27
|
-
* required: true
|
|
28
|
-
* content:
|
|
29
|
-
* application/json:
|
|
30
|
-
* schema:
|
|
31
|
-
* type: object
|
|
32
|
-
* properties:
|
|
33
|
-
* email:
|
|
34
|
-
* type: string
|
|
35
|
-
* password:
|
|
36
|
-
* type: string
|
|
37
|
-
* name:
|
|
38
|
-
* type: string
|
|
39
|
-
*/
|
|
40
|
-
router.post('/auth/register', authController.register);
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @swagger
|
|
44
|
-
* /api/v1/auth/login:
|
|
45
|
-
* post:
|
|
46
|
-
* summary: 사용자 로그인
|
|
47
|
-
* tags: [Auth]
|
|
48
|
-
* requestBody:
|
|
49
|
-
* required: true
|
|
50
|
-
* content:
|
|
51
|
-
* application/json:
|
|
52
|
-
* schema:
|
|
53
|
-
* type: object
|
|
54
|
-
* properties:
|
|
55
|
-
* email:
|
|
56
|
-
* type: string
|
|
57
|
-
* password:
|
|
58
|
-
* type: string
|
|
59
|
-
*/
|
|
60
|
-
router.post('/auth/login', authController.login);
|
|
61
|
-
|
|
62
|
-
// Protected routes example
|
|
63
|
-
router.get('/protected', authMiddleware, (req, res) => {
|
|
64
|
-
res.json({ message: 'This is a protected route' });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
export default router;
|