@bloomneo/appkit 1.2.9
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/LICENSE +21 -0
- package/README.md +902 -0
- package/bin/appkit.js +71 -0
- package/bin/commands/generate.js +1050 -0
- package/bin/templates/backend/README.md.template +39 -0
- package/bin/templates/backend/api.http.template +0 -0
- package/bin/templates/backend/docs/APPKIT_CLI.md +507 -0
- package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +61 -0
- package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +2539 -0
- package/bin/templates/backend/package.json.template +34 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.http.template +29 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.route.ts.template +36 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.service.ts.template +88 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.types.ts.template +18 -0
- package/bin/templates/backend/src/api/lib/api-router.ts.template +84 -0
- package/bin/templates/backend/src/api/server.ts.template +188 -0
- package/bin/templates/backend/tsconfig.api.json.template +24 -0
- package/bin/templates/backend/tsconfig.json.template +40 -0
- package/bin/templates/feature/feature.http.template +63 -0
- package/bin/templates/feature/feature.route.ts.template +36 -0
- package/bin/templates/feature/feature.service.ts.template +81 -0
- package/bin/templates/feature/feature.types.ts.template +23 -0
- package/bin/templates/feature-db/feature.http.template +63 -0
- package/bin/templates/feature-db/feature.model.ts.template +74 -0
- package/bin/templates/feature-db/feature.route.ts.template +58 -0
- package/bin/templates/feature-db/feature.service.ts.template +231 -0
- package/bin/templates/feature-db/feature.types.ts.template +25 -0
- package/bin/templates/feature-db/schema-addition.prisma.template +9 -0
- package/bin/templates/feature-db/seeding/README.md.template +57 -0
- package/bin/templates/feature-db/seeding/feature.seed.js.template +67 -0
- package/bin/templates/feature-user/schema-addition.prisma.template +19 -0
- package/bin/templates/feature-user/user.http.template +157 -0
- package/bin/templates/feature-user/user.model.ts.template +244 -0
- package/bin/templates/feature-user/user.route.ts.template +379 -0
- package/bin/templates/feature-user/user.seed.js.template +182 -0
- package/bin/templates/feature-user/user.service.ts.template +426 -0
- package/bin/templates/feature-user/user.types.ts.template +127 -0
- package/dist/auth/auth.d.ts +182 -0
- package/dist/auth/auth.d.ts.map +1 -0
- package/dist/auth/auth.js +477 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/defaults.d.ts +104 -0
- package/dist/auth/defaults.d.ts.map +1 -0
- package/dist/auth/defaults.js +374 -0
- package/dist/auth/defaults.js.map +1 -0
- package/dist/auth/index.d.ts +70 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +94 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cache/cache.d.ts +118 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +249 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/cache/defaults.d.ts +63 -0
- package/dist/cache/defaults.d.ts.map +1 -0
- package/dist/cache/defaults.js +193 -0
- package/dist/cache/defaults.js.map +1 -0
- package/dist/cache/index.d.ts +101 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +203 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/strategies/memory.d.ts +138 -0
- package/dist/cache/strategies/memory.d.ts.map +1 -0
- package/dist/cache/strategies/memory.js +348 -0
- package/dist/cache/strategies/memory.js.map +1 -0
- package/dist/cache/strategies/redis.d.ts +105 -0
- package/dist/cache/strategies/redis.d.ts.map +1 -0
- package/dist/cache/strategies/redis.js +318 -0
- package/dist/cache/strategies/redis.js.map +1 -0
- package/dist/config/config.d.ts +62 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +107 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/defaults.d.ts +44 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +217 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +105 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +163 -0
- package/dist/config/index.js.map +1 -0
- package/dist/database/adapters/mongoose.d.ts +106 -0
- package/dist/database/adapters/mongoose.d.ts.map +1 -0
- package/dist/database/adapters/mongoose.js +480 -0
- package/dist/database/adapters/mongoose.js.map +1 -0
- package/dist/database/adapters/prisma.d.ts +106 -0
- package/dist/database/adapters/prisma.d.ts.map +1 -0
- package/dist/database/adapters/prisma.js +494 -0
- package/dist/database/adapters/prisma.js.map +1 -0
- package/dist/database/defaults.d.ts +87 -0
- package/dist/database/defaults.d.ts.map +1 -0
- package/dist/database/defaults.js +271 -0
- package/dist/database/defaults.js.map +1 -0
- package/dist/database/index.d.ts +137 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +490 -0
- package/dist/database/index.js.map +1 -0
- package/dist/email/defaults.d.ts +100 -0
- package/dist/email/defaults.d.ts.map +1 -0
- package/dist/email/defaults.js +400 -0
- package/dist/email/defaults.js.map +1 -0
- package/dist/email/email.d.ts +139 -0
- package/dist/email/email.d.ts.map +1 -0
- package/dist/email/email.js +316 -0
- package/dist/email/email.js.map +1 -0
- package/dist/email/index.d.ts +176 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +251 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/strategies/console.d.ts +90 -0
- package/dist/email/strategies/console.d.ts.map +1 -0
- package/dist/email/strategies/console.js +268 -0
- package/dist/email/strategies/console.js.map +1 -0
- package/dist/email/strategies/resend.d.ts +84 -0
- package/dist/email/strategies/resend.d.ts.map +1 -0
- package/dist/email/strategies/resend.js +266 -0
- package/dist/email/strategies/resend.js.map +1 -0
- package/dist/email/strategies/smtp.d.ts +77 -0
- package/dist/email/strategies/smtp.d.ts.map +1 -0
- package/dist/email/strategies/smtp.js +286 -0
- package/dist/email/strategies/smtp.js.map +1 -0
- package/dist/error/defaults.d.ts +40 -0
- package/dist/error/defaults.d.ts.map +1 -0
- package/dist/error/defaults.js +75 -0
- package/dist/error/defaults.js.map +1 -0
- package/dist/error/error.d.ts +140 -0
- package/dist/error/error.d.ts.map +1 -0
- package/dist/error/error.js +200 -0
- package/dist/error/error.js.map +1 -0
- package/dist/error/index.d.ts +145 -0
- package/dist/error/index.d.ts.map +1 -0
- package/dist/error/index.js +145 -0
- package/dist/error/index.js.map +1 -0
- package/dist/event/defaults.d.ts +111 -0
- package/dist/event/defaults.d.ts.map +1 -0
- package/dist/event/defaults.js +378 -0
- package/dist/event/defaults.js.map +1 -0
- package/dist/event/event.d.ts +171 -0
- package/dist/event/event.d.ts.map +1 -0
- package/dist/event/event.js +391 -0
- package/dist/event/event.js.map +1 -0
- package/dist/event/index.d.ts +173 -0
- package/dist/event/index.d.ts.map +1 -0
- package/dist/event/index.js +302 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/strategies/memory.d.ts +122 -0
- package/dist/event/strategies/memory.d.ts.map +1 -0
- package/dist/event/strategies/memory.js +331 -0
- package/dist/event/strategies/memory.js.map +1 -0
- package/dist/event/strategies/redis.d.ts +115 -0
- package/dist/event/strategies/redis.d.ts.map +1 -0
- package/dist/event/strategies/redis.js +434 -0
- package/dist/event/strategies/redis.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/defaults.d.ts +67 -0
- package/dist/logger/defaults.d.ts.map +1 -0
- package/dist/logger/defaults.js +213 -0
- package/dist/logger/defaults.js.map +1 -0
- package/dist/logger/index.d.ts +84 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +101 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.d.ts +165 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +843 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/logger/transports/console.d.ts +102 -0
- package/dist/logger/transports/console.d.ts.map +1 -0
- package/dist/logger/transports/console.js +276 -0
- package/dist/logger/transports/console.js.map +1 -0
- package/dist/logger/transports/database.d.ts +153 -0
- package/dist/logger/transports/database.d.ts.map +1 -0
- package/dist/logger/transports/database.js +539 -0
- package/dist/logger/transports/database.js.map +1 -0
- package/dist/logger/transports/file.d.ts +146 -0
- package/dist/logger/transports/file.d.ts.map +1 -0
- package/dist/logger/transports/file.js +464 -0
- package/dist/logger/transports/file.js.map +1 -0
- package/dist/logger/transports/http.d.ts +128 -0
- package/dist/logger/transports/http.d.ts.map +1 -0
- package/dist/logger/transports/http.js +401 -0
- package/dist/logger/transports/http.js.map +1 -0
- package/dist/logger/transports/webhook.d.ts +152 -0
- package/dist/logger/transports/webhook.d.ts.map +1 -0
- package/dist/logger/transports/webhook.js +485 -0
- package/dist/logger/transports/webhook.js.map +1 -0
- package/dist/queue/defaults.d.ts +66 -0
- package/dist/queue/defaults.d.ts.map +1 -0
- package/dist/queue/defaults.js +205 -0
- package/dist/queue/defaults.js.map +1 -0
- package/dist/queue/index.d.ts +124 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +116 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/queue.d.ts +156 -0
- package/dist/queue/queue.d.ts.map +1 -0
- package/dist/queue/queue.js +387 -0
- package/dist/queue/queue.js.map +1 -0
- package/dist/queue/transports/database.d.ts +165 -0
- package/dist/queue/transports/database.d.ts.map +1 -0
- package/dist/queue/transports/database.js +595 -0
- package/dist/queue/transports/database.js.map +1 -0
- package/dist/queue/transports/memory.d.ts +143 -0
- package/dist/queue/transports/memory.d.ts.map +1 -0
- package/dist/queue/transports/memory.js +415 -0
- package/dist/queue/transports/memory.js.map +1 -0
- package/dist/queue/transports/redis.d.ts +203 -0
- package/dist/queue/transports/redis.d.ts.map +1 -0
- package/dist/queue/transports/redis.js +744 -0
- package/dist/queue/transports/redis.js.map +1 -0
- package/dist/security/defaults.d.ts +64 -0
- package/dist/security/defaults.d.ts.map +1 -0
- package/dist/security/defaults.js +159 -0
- package/dist/security/defaults.js.map +1 -0
- package/dist/security/index.d.ts +110 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +160 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security.d.ts +138 -0
- package/dist/security/security.d.ts.map +1 -0
- package/dist/security/security.js +419 -0
- package/dist/security/security.js.map +1 -0
- package/dist/storage/defaults.d.ts +79 -0
- package/dist/storage/defaults.d.ts.map +1 -0
- package/dist/storage/defaults.js +358 -0
- package/dist/storage/defaults.js.map +1 -0
- package/dist/storage/index.d.ts +153 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +242 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/storage.d.ts +151 -0
- package/dist/storage/storage.d.ts.map +1 -0
- package/dist/storage/storage.js +439 -0
- package/dist/storage/storage.js.map +1 -0
- package/dist/storage/strategies/local.d.ts +117 -0
- package/dist/storage/strategies/local.d.ts.map +1 -0
- package/dist/storage/strategies/local.js +368 -0
- package/dist/storage/strategies/local.js.map +1 -0
- package/dist/storage/strategies/r2.d.ts +130 -0
- package/dist/storage/strategies/r2.d.ts.map +1 -0
- package/dist/storage/strategies/r2.js +470 -0
- package/dist/storage/strategies/r2.js.map +1 -0
- package/dist/storage/strategies/s3.d.ts +121 -0
- package/dist/storage/strategies/s3.d.ts.map +1 -0
- package/dist/storage/strategies/s3.js +461 -0
- package/dist/storage/strategies/s3.js.map +1 -0
- package/dist/util/defaults.d.ts +77 -0
- package/dist/util/defaults.d.ts.map +1 -0
- package/dist/util/defaults.js +193 -0
- package/dist/util/defaults.js.map +1 -0
- package/dist/util/index.d.ts +97 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +165 -0
- package/dist/util/index.js.map +1 -0
- package/dist/util/util.d.ts +145 -0
- package/dist/util/util.d.ts.map +1 -0
- package/dist/util/util.js +481 -0
- package/dist/util/util.js.map +1 -0
- package/package.json +234 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Backend API with FBCA pattern",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/api/server.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "nodemon --exec tsx src/api/server.ts",
|
|
9
|
+
"start": "tsx src/api/server.ts",
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start:prod": "node dist/api/server.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"api",
|
|
15
|
+
"backend",
|
|
16
|
+
"fbca"
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@bloomneo/appkit": "^1.0.0",
|
|
22
|
+
"cors": "^2.8.5",
|
|
23
|
+
"dotenv": "^16.4.5",
|
|
24
|
+
"express": "^4.18.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/cors": "^2.8.19",
|
|
28
|
+
"@types/express": "^5.0.3",
|
|
29
|
+
"@types/node": "^24.4.0",
|
|
30
|
+
"nodemon": "^3.0.1",
|
|
31
|
+
"tsx": "^4.20.5",
|
|
32
|
+
"typescript": "^5.9.2"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
### Welcome API Testing with Frontend Key
|
|
2
|
+
### Base URL: http://localhost:3000
|
|
3
|
+
### Note: In development, X-Frontend-Key is not required but shown for testing
|
|
4
|
+
|
|
5
|
+
### 1. Basic hello (without key - should work in dev)
|
|
6
|
+
GET http://localhost:3000/api/welcome
|
|
7
|
+
|
|
8
|
+
###
|
|
9
|
+
|
|
10
|
+
### 2. Basic hello (with frontend key)
|
|
11
|
+
GET http://localhost:3000/api/welcome
|
|
12
|
+
X-Frontend-Key: {{frontendKey}}
|
|
13
|
+
|
|
14
|
+
###
|
|
15
|
+
|
|
16
|
+
### 3. Test with wrong key (should fail in production)
|
|
17
|
+
GET http://localhost:3000/api/welcome
|
|
18
|
+
X-Frontend-Key: wrong_key_123
|
|
19
|
+
|
|
20
|
+
###
|
|
21
|
+
|
|
22
|
+
### Health Check (not protected)
|
|
23
|
+
GET http://localhost:3000/health
|
|
24
|
+
|
|
25
|
+
###
|
|
26
|
+
|
|
27
|
+
### API Info (protected)
|
|
28
|
+
GET http://localhost:3000/api
|
|
29
|
+
X-Frontend-Key: {{frontendKey}}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Welcome Feature Routes - Express endpoints with AppKit integration
|
|
3
|
+
* @module {{projectName}}/welcome-routes
|
|
4
|
+
* @file {{projectName}}/src/api/features/{featureName}/{featureName}.route.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need HTTP endpoints for welcome feature with error handling
|
|
7
|
+
* @llm-rule AVOID: Adding routes without asyncRoute wrapper - breaks error handling
|
|
8
|
+
* @llm-rule NOTE: Auto-discovered by api-router.ts using FBCA pattern, exports default Express router
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import express from 'express';
|
|
12
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
13
|
+
import { loggerClass } from '@bloomneo/appkit/logger';
|
|
14
|
+
import { welcomeService } from './welcome.service.js';
|
|
15
|
+
|
|
16
|
+
const router = express.Router();
|
|
17
|
+
const error = errorClass.get();
|
|
18
|
+
const logger = loggerClass.get('welcome-routes');
|
|
19
|
+
|
|
20
|
+
// GET /api/{featureName} - replies "hello"
|
|
21
|
+
router.get('/', error.asyncRoute(async (_req, res) => {
|
|
22
|
+
logger.info('GET /api/{featureName} request received');
|
|
23
|
+
const result = await welcomeService.getHello();
|
|
24
|
+
res.json(result);
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// GET /api/{featureName}/:name - replies "hello :name"
|
|
28
|
+
router.get('/:name', error.asyncRoute(async (req, res) => {
|
|
29
|
+
const name = req.params.name;
|
|
30
|
+
logger.info('GET /api/{featureName}/:name request received', { name });
|
|
31
|
+
|
|
32
|
+
const result = await welcomeService.getHelloWithName(name);
|
|
33
|
+
res.json(result);
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
export default router;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Welcome Feature Service - Business logic with AppKit integration
|
|
3
|
+
* @module {{projectName}}/welcome-service
|
|
4
|
+
* @file {{projectName}}/src/api/features/{featureName}/{featureName}.service.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need business logic layer with validation, logging, and config
|
|
7
|
+
* @llm-rule AVOID: Direct database calls from routes - always use service layer
|
|
8
|
+
* @llm-rule NOTE: Demonstrates AppKit logger, config, and error patterns for FBCA
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { loggerClass } from '@bloomneo/appkit/logger';
|
|
12
|
+
import { configClass } from '@bloomneo/appkit/config';
|
|
13
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
14
|
+
import type { WelcomeResponse, PersonalizedWelcomeResponse } from './welcome.types.js';
|
|
15
|
+
|
|
16
|
+
// Initialize AppKit modules following the pattern
|
|
17
|
+
const logger = loggerClass.get('welcome');
|
|
18
|
+
const config = configClass.get();
|
|
19
|
+
const error = errorClass.get();
|
|
20
|
+
|
|
21
|
+
export const welcomeService = {
|
|
22
|
+
/**
|
|
23
|
+
* Get basic hello message
|
|
24
|
+
*/
|
|
25
|
+
async getHello(): Promise<WelcomeResponse> {
|
|
26
|
+
try {
|
|
27
|
+
logger.info('Processing basic hello request');
|
|
28
|
+
|
|
29
|
+
// Get default welcome from config (with fallback)
|
|
30
|
+
const defaultWelcome = config.get('welcome.default', 'hello') as string;
|
|
31
|
+
|
|
32
|
+
const result: WelcomeResponse = {
|
|
33
|
+
message: defaultWelcome,
|
|
34
|
+
timestamp: new Date().toISOString()
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
logger.info('Basic hello request completed', { result });
|
|
38
|
+
return result;
|
|
39
|
+
|
|
40
|
+
} catch (err) {
|
|
41
|
+
logger.error('Failed to process basic hello request', { error: err });
|
|
42
|
+
throw error.serverError('Failed to generate welcome message');
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get hello message with name
|
|
48
|
+
*/
|
|
49
|
+
async getHelloWithName(name: string): Promise<PersonalizedWelcomeResponse> {
|
|
50
|
+
try {
|
|
51
|
+
logger.info('Processing personalized hello request', { name });
|
|
52
|
+
|
|
53
|
+
// Validate input
|
|
54
|
+
if (!name || typeof name !== 'string') {
|
|
55
|
+
logger.warn('Invalid name provided', { name });
|
|
56
|
+
throw error.badRequest('Name must be a valid string');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (name.length > 100) {
|
|
60
|
+
logger.warn('Name too long', { name, length: name.length });
|
|
61
|
+
throw error.badRequest('Name must be less than 100 characters');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Get welcome format from config
|
|
65
|
+
const welcomeFormat = config.get('welcome.format', 'hello {name}') as string;
|
|
66
|
+
const message = welcomeFormat.replace('{name}', name);
|
|
67
|
+
|
|
68
|
+
const result = {
|
|
69
|
+
message,
|
|
70
|
+
name,
|
|
71
|
+
timestamp: new Date().toISOString()
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
logger.info('Personalized hello request completed', { name, result });
|
|
75
|
+
return result;
|
|
76
|
+
|
|
77
|
+
} catch (err: any) {
|
|
78
|
+
// Re-throw AppKit errors as-is
|
|
79
|
+
if (err.statusCode) {
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Convert other errors to server errors
|
|
84
|
+
logger.error('Failed to process personalized hello request', { name, error: err });
|
|
85
|
+
throw error.serverError('Failed to generate personalized welcome message');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Welcome Feature Type Definitions - Shared interfaces and types
|
|
3
|
+
* @module {{projectName}}/welcome-types
|
|
4
|
+
* @file {{projectName}}/src/api/features/{featureName}/{featureName}.types.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need type safety for welcome feature data structures
|
|
7
|
+
* @llm-rule AVOID: Defining types inline - reduces reusability and consistency
|
|
8
|
+
* @llm-rule NOTE: Shared across service and route layers for type consistency
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface WelcomeResponse {
|
|
12
|
+
message: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PersonalizedWelcomeResponse extends WelcomeResponse {
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FBCA Auto-Discovery Router
|
|
3
|
+
* @module {{projectName}}/api-router
|
|
4
|
+
* @file {{projectName}}/src/api/lib/api-router.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need automatic API route discovery based on feature directories
|
|
7
|
+
* @llm-rule AVOID: Manual route registration - defeats FBCA auto-discovery purpose
|
|
8
|
+
* @llm-rule NOTE: Follows {featureName}/{featureName}.route.ts naming convention for TypeScript or .js for JavaScript
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import express from 'express';
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
13
|
+
import { dirname, join } from 'path';
|
|
14
|
+
import { readdir } from 'fs/promises';
|
|
15
|
+
import { loggerClass } from '@bloomneo/appkit/logger';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
|
|
20
|
+
export async function createApiRouter() {
|
|
21
|
+
const router = express.Router();
|
|
22
|
+
const discoveredRoutes: string[] = [];
|
|
23
|
+
const logger = loggerClass.get('api-router');
|
|
24
|
+
|
|
25
|
+
// API root route - list available endpoints
|
|
26
|
+
router.get('/', (_req, res) => {
|
|
27
|
+
res.json({
|
|
28
|
+
message: 'API Server - Feature-Based Component Architecture',
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
endpoints: {
|
|
31
|
+
health: '/health',
|
|
32
|
+
api: '/api',
|
|
33
|
+
features: discoveredRoutes.map(route => `/api${route}`)
|
|
34
|
+
},
|
|
35
|
+
timestamp: new Date().toISOString()
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Auto-discover feature routes
|
|
41
|
+
const featuresPath = join(__dirname, '../features');
|
|
42
|
+
const features = await readdir(featuresPath, { withFileTypes: true });
|
|
43
|
+
|
|
44
|
+
logger.info('🔍 Discovering API routes:');
|
|
45
|
+
|
|
46
|
+
for (const feature of features) {
|
|
47
|
+
if (feature.isDirectory()) {
|
|
48
|
+
const featureName = feature.name;
|
|
49
|
+
// Try .ts first, then .js
|
|
50
|
+
const routeFiles = [
|
|
51
|
+
join(featuresPath, featureName, `${featureName}.route.ts`),
|
|
52
|
+
join(featuresPath, featureName, `${featureName}.route.js`)
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
let loaded = false;
|
|
56
|
+
for (const routeFile of routeFiles) {
|
|
57
|
+
try {
|
|
58
|
+
const fileUrl = pathToFileURL(routeFile).href;
|
|
59
|
+
const { default: featureRouter } = await import(fileUrl);
|
|
60
|
+
router.use(`/${featureName}`, featureRouter);
|
|
61
|
+
discoveredRoutes.push(`/${featureName}`);
|
|
62
|
+
const fileType = routeFile.endsWith('.ts') ? '.ts' : '.js';
|
|
63
|
+
logger.info(` /api/${featureName} -> ${featureName}.route${fileType}`);
|
|
64
|
+
loaded = true;
|
|
65
|
+
break;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Continue to next file type
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!loaded) {
|
|
72
|
+
logger.warn(`⚠️ Could not load route for feature "${featureName}"`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
logger.info('✅ API routes discovered');
|
|
78
|
+
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.warn('⚠️ No features directory found');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return router;
|
|
84
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FBCA Backend API Server with AppKit integration
|
|
3
|
+
* @module {{projectName}}/server
|
|
4
|
+
* @file {{projectName}}/src/api/server.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Creating backend APIs with Feature-Based Component Architecture
|
|
7
|
+
* @llm-rule AVOID: Using without AppKit modules - breaks structured logging and error handling
|
|
8
|
+
* @llm-rule NOTE: Auto-discovers features in features/ directory using naming convention
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import 'dotenv/config';
|
|
12
|
+
import express from 'express';
|
|
13
|
+
import cors from 'cors';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { loggerClass } from '@bloomneo/appkit/logger';
|
|
18
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
19
|
+
import { configClass } from '@bloomneo/appkit/config';
|
|
20
|
+
import { createApiRouter } from './lib/api-router.js';
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = path.dirname(__filename);
|
|
24
|
+
|
|
25
|
+
// Initialize AppKit modules following the pattern
|
|
26
|
+
const logger = loggerClass.get('server');
|
|
27
|
+
const error = errorClass.get();
|
|
28
|
+
const config = configClass.get();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if frontend build exists
|
|
32
|
+
*/
|
|
33
|
+
function checkFrontendExists(distPath: string): boolean {
|
|
34
|
+
try {
|
|
35
|
+
const indexPath = path.join(distPath, 'index.html');
|
|
36
|
+
return fs.existsSync(indexPath);
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const app = express();
|
|
43
|
+
const PORT = config.get('server.port', process.env.PORT || 3000);
|
|
44
|
+
|
|
45
|
+
// Middleware (following AppKit recommended order)
|
|
46
|
+
app.use(cors());
|
|
47
|
+
app.use(express.json());
|
|
48
|
+
app.use(express.urlencoded({ extended: true }));
|
|
49
|
+
|
|
50
|
+
// Request logging middleware
|
|
51
|
+
app.use((req, _res, next) => {
|
|
52
|
+
logger.info('Incoming request', {
|
|
53
|
+
method: req.method,
|
|
54
|
+
url: req.url,
|
|
55
|
+
userAgent: req.get('User-Agent'),
|
|
56
|
+
ip: req.ip
|
|
57
|
+
});
|
|
58
|
+
next();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Frontend key protection for API routes (excluding /api root and /health)
|
|
62
|
+
app.use('/api', (req, res, next) => {
|
|
63
|
+
// Skip for specific endpoints
|
|
64
|
+
if (req.path === '/' || req.path === '') {
|
|
65
|
+
logger.info('🔓 Frontend key check skipped for /api root');
|
|
66
|
+
return next();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Skip in development environment
|
|
70
|
+
if (process.env.NODE_ENV === 'development') {
|
|
71
|
+
logger.info('🔓 Frontend key check skipped (development mode)');
|
|
72
|
+
return next();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const frontendKey = req.headers['x-frontend-key'] as string;
|
|
76
|
+
const expectedKey = process.env.VOILA_FRONTEND_KEY;
|
|
77
|
+
|
|
78
|
+
// Warn if no key is configured in production
|
|
79
|
+
if (!expectedKey) {
|
|
80
|
+
logger.warn('⚠️ VOILA_FRONTEND_KEY not configured in production environment');
|
|
81
|
+
return next(); // Fail gracefully
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check if frontend key is provided and matches
|
|
85
|
+
if (!frontendKey) {
|
|
86
|
+
logger.warn('❌ Frontend key missing in request headers', { url: req.url });
|
|
87
|
+
return res.status(403).json({
|
|
88
|
+
error: 'Frontend access key required',
|
|
89
|
+
message: 'API access requires valid frontend key in X-Frontend-Key header'
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (frontendKey !== expectedKey) {
|
|
94
|
+
logger.warn('❌ Invalid frontend key provided', {
|
|
95
|
+
url: req.url,
|
|
96
|
+
providedKey: frontendKey.substring(0, 8) + '...'
|
|
97
|
+
});
|
|
98
|
+
return res.status(403).json({
|
|
99
|
+
error: 'Invalid frontend access key',
|
|
100
|
+
message: 'The provided frontend key is not valid'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
logger.info('✅ Frontend key validated successfully', { url: req.url });
|
|
105
|
+
next();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Health check with AppKit integration
|
|
109
|
+
app.get('/health', error.asyncRoute(async (_req, res) => {
|
|
110
|
+
logger.info('Health check requested');
|
|
111
|
+
|
|
112
|
+
const healthData = {
|
|
113
|
+
status: 'ok',
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
uptime: process.uptime(),
|
|
116
|
+
environment: config.get('app.environment', 'development')
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
res.json(healthData);
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
// Initialize server with async setup
|
|
123
|
+
async function startServer() {
|
|
124
|
+
try {
|
|
125
|
+
logger.info('Initializing server...');
|
|
126
|
+
|
|
127
|
+
// API routes with auto-discovery
|
|
128
|
+
app.use('/api', await createApiRouter());
|
|
129
|
+
|
|
130
|
+
// Check if frontend build exists and we're not in API-only dev mode
|
|
131
|
+
const distPath = path.join(__dirname, '../../dist');
|
|
132
|
+
const frontendExists = checkFrontendExists(distPath);
|
|
133
|
+
const isApiOnlyMode = process.env.NODE_ENV !== 'production' && process.env.API_ONLY === 'true';
|
|
134
|
+
|
|
135
|
+
logger.info('Frontend check:', { distPath, frontendExists, isApiOnlyMode });
|
|
136
|
+
|
|
137
|
+
if (frontendExists && !isApiOnlyMode) {
|
|
138
|
+
// Serve static files from dist directory (production frontend)
|
|
139
|
+
logger.info('🌐 Serving frontend from dist directory');
|
|
140
|
+
app.use(express.static(distPath));
|
|
141
|
+
|
|
142
|
+
// SPA fallback - serve index.html for all non-API routes
|
|
143
|
+
app.get('*', (req, res) => {
|
|
144
|
+
const indexPath = path.join(distPath, 'index.html');
|
|
145
|
+
logger.info('Serving SPA route', { path: req.path });
|
|
146
|
+
res.sendFile(indexPath, (err) => {
|
|
147
|
+
if (err) {
|
|
148
|
+
logger.error('Failed to serve index.html', { error: err });
|
|
149
|
+
res.status(404).json({
|
|
150
|
+
error: 'Frontend not found',
|
|
151
|
+
message: 'Frontend files exist but failed to serve'
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
// Fallback to /api route when no frontend
|
|
158
|
+
logger.info('🔧 No frontend found, redirecting root to /api');
|
|
159
|
+
app.get('/', (_req, res) => {
|
|
160
|
+
logger.info('Root route requested, redirecting to /api');
|
|
161
|
+
res.redirect('/api');
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// AppKit error handling middleware (ALWAYS LAST)
|
|
166
|
+
app.use(error.handleErrors());
|
|
167
|
+
|
|
168
|
+
app.listen(PORT, () => {
|
|
169
|
+
if (frontendExists) {
|
|
170
|
+
logger.info(`🚀 Full-stack server running on http://localhost:${PORT}`);
|
|
171
|
+
logger.info(`🌐 Frontend: http://localhost:${PORT}`);
|
|
172
|
+
} else {
|
|
173
|
+
logger.info(`🔧 API-only server running on http://localhost:${PORT}`);
|
|
174
|
+
logger.info(`🌐 Root: http://localhost:${PORT} → redirects to /api`);
|
|
175
|
+
}
|
|
176
|
+
logger.info(`📚 API routes: http://localhost:${PORT}/api`);
|
|
177
|
+
logger.info(`💊 Health check: http://localhost:${PORT}/health`);
|
|
178
|
+
logger.info('Server initialization completed successfully');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
} catch (err: any) {
|
|
182
|
+
logger.error('Failed to start server', { error: err });
|
|
183
|
+
logger.error('❌ Server startup failed:', { error: err.message });
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
startServer();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2023"],
|
|
5
|
+
"module": "Node16",
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"moduleResolution": "node16",
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"strict": false,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src/api/**/*"
|
|
18
|
+
],
|
|
19
|
+
"exclude": [
|
|
20
|
+
"node_modules",
|
|
21
|
+
"dist",
|
|
22
|
+
"src/web"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"checkJs": false,
|
|
10
|
+
"declaration": false,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"rootDir": "./src",
|
|
13
|
+
"removeComments": false,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noImplicitAny": true,
|
|
16
|
+
"strictNullChecks": true,
|
|
17
|
+
"strictFunctionTypes": true,
|
|
18
|
+
"noImplicitReturns": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"noImplicitOverride": true,
|
|
21
|
+
"exactOptionalPropertyTypes": false,
|
|
22
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
23
|
+
"forceConsistentCasingInFileNames": true,
|
|
24
|
+
"skipLibCheck": true,
|
|
25
|
+
"resolveJsonModule": true,
|
|
26
|
+
"types": ["node"]
|
|
27
|
+
},
|
|
28
|
+
"include": [
|
|
29
|
+
"src/**/*"
|
|
30
|
+
],
|
|
31
|
+
"exclude": [
|
|
32
|
+
"node_modules",
|
|
33
|
+
"dist",
|
|
34
|
+
"**/*.test.ts",
|
|
35
|
+
"**/*.spec.ts"
|
|
36
|
+
],
|
|
37
|
+
"ts-node": {
|
|
38
|
+
"esm": true
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
### {{FeatureName}} API Testing with Frontend Key
|
|
2
|
+
### Base URL: http://localhost:3000
|
|
3
|
+
### Note: In development, X-Frontend-Key is not required but shown for testing
|
|
4
|
+
|
|
5
|
+
### 1. Get all {{featureName}} (without key - should work in dev)
|
|
6
|
+
GET http://localhost:3000/api/{{featureName}}
|
|
7
|
+
|
|
8
|
+
###
|
|
9
|
+
|
|
10
|
+
### 2. Get all {{featureName}} (with frontend key)
|
|
11
|
+
GET http://localhost:3000/api/{{featureName}}
|
|
12
|
+
X-Frontend-Key: {{frontendKey}}
|
|
13
|
+
|
|
14
|
+
###
|
|
15
|
+
|
|
16
|
+
### 3. Create a new {{featureName}} (with frontend key)
|
|
17
|
+
POST http://localhost:3000/api/{{featureName}}
|
|
18
|
+
Content-Type: application/json
|
|
19
|
+
X-Frontend-Key: {{frontendKey}}
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
"name": "Sample {{FeatureName}}"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
###
|
|
26
|
+
|
|
27
|
+
### 4. Get {{featureName}} by ID (with frontend key)
|
|
28
|
+
GET http://localhost:3000/api/{{featureName}}/1
|
|
29
|
+
X-Frontend-Key: {{frontendKey}}
|
|
30
|
+
|
|
31
|
+
###
|
|
32
|
+
|
|
33
|
+
### 5. Update {{featureName}} by ID (with frontend key)
|
|
34
|
+
PUT http://localhost:3000/api/{{featureName}}/1
|
|
35
|
+
Content-Type: application/json
|
|
36
|
+
X-Frontend-Key: {{frontendKey}}
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
"name": "Updated {{FeatureName}}"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
###
|
|
43
|
+
|
|
44
|
+
### 6. Delete {{featureName}} by ID (with frontend key)
|
|
45
|
+
DELETE http://localhost:3000/api/{{featureName}}/1
|
|
46
|
+
X-Frontend-Key: {{frontendKey}}
|
|
47
|
+
|
|
48
|
+
###
|
|
49
|
+
|
|
50
|
+
### 7. Test with wrong key (should fail in production)
|
|
51
|
+
GET http://localhost:3000/api/{{featureName}}
|
|
52
|
+
X-Frontend-Key: wrong_key_123
|
|
53
|
+
|
|
54
|
+
###
|
|
55
|
+
|
|
56
|
+
### Health Check (not protected)
|
|
57
|
+
GET http://localhost:3000/health
|
|
58
|
+
|
|
59
|
+
###
|
|
60
|
+
|
|
61
|
+
### API Info (protected)
|
|
62
|
+
GET http://localhost:3000/api
|
|
63
|
+
X-Frontend-Key: {{frontendKey}}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{featureName}} Feature Routes - Express endpoints with AppKit integration
|
|
3
|
+
* @module {{projectName}}/{{featureName}}-routes
|
|
4
|
+
* @file src/api/features/{{featureName}}/{{featureName}}.route.ts
|
|
5
|
+
*
|
|
6
|
+
* @llm-rule WHEN: Need HTTP endpoints for {{featureName}} feature with error handling
|
|
7
|
+
* @llm-rule AVOID: Adding routes without asyncRoute wrapper - breaks error handling
|
|
8
|
+
* @llm-rule NOTE: Auto-discovered by router.ts, exports default Express router
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import express from 'express';
|
|
12
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
13
|
+
import { loggerClass } from '@bloomneo/appkit/logger';
|
|
14
|
+
import { {{featureName}}Service } from './{{featureName}}.service.js';
|
|
15
|
+
|
|
16
|
+
const router = express.Router();
|
|
17
|
+
const error = errorClass.get();
|
|
18
|
+
const logger = loggerClass.get('{{featureName}}-routes');
|
|
19
|
+
|
|
20
|
+
// GET /api/{{featureName}} - Basic {{featureName}} endpoint
|
|
21
|
+
router.get('/', error.asyncRoute(async (_req, res) => {
|
|
22
|
+
logger.info('GET /api/{{featureName}} request received');
|
|
23
|
+
const result = await {{featureName}}Service.getAll();
|
|
24
|
+
res.json(result);
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// GET /api/{{featureName}}/:id - Get specific {{featureName}} by ID
|
|
28
|
+
router.get('/:id', error.asyncRoute(async (req, res) => {
|
|
29
|
+
const id = req.params.id;
|
|
30
|
+
logger.info('GET /api/{{featureName}}/:id request received', { id });
|
|
31
|
+
|
|
32
|
+
const result = await {{featureName}}Service.getById(id);
|
|
33
|
+
res.json(result);
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
export default router;
|