@digitaldefiance/express-suite-starter 2.4.6 → 2.4.7
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/scaffolding/api-lib/src/lib/application.ts +62 -13
- package/scaffolding/api-lib/src/lib/routers/api.ts +145 -5
- package/scaffolding/api-lib/src/lib/routers/app.ts +4 -3
- package/scaffolding/api-lib/src/lib/routers/index.ts +0 -1
- package/scaffolding/api-lib/src/lib/schemas/schema.ts +2 -2
- package/scaffolding/api-lib/src/lib/shared-types.ts +1 -1
- package/templates/api-lib/README.md.mustache +74 -6
package/package.json
CHANGED
|
@@ -1,35 +1,84 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Application,
|
|
3
|
+
BaseApplication,
|
|
4
|
+
IFailableResult,
|
|
5
|
+
IServerInitResult,
|
|
6
|
+
DummyEmailService,
|
|
7
|
+
emailServiceRegistry,
|
|
8
|
+
BaseRouter,
|
|
9
|
+
IApplication,
|
|
10
|
+
ApiRouter as LibraryApiRouter,
|
|
11
|
+
} from '@digitaldefiance/node-express-suite';
|
|
2
12
|
import { Environment } from './environment';
|
|
3
13
|
import { IConstants } from './interfaces/constants';
|
|
4
14
|
import { Constants } from './constants';
|
|
5
|
-
import { ApiRouter } from './routers/api';
|
|
6
15
|
import { AppRouter } from './routers/app';
|
|
7
16
|
import { getSchemaMap } from './schemas/schema';
|
|
8
17
|
import { initMiddleware } from './middlewares';
|
|
9
18
|
import { EmailService } from './services/email';
|
|
19
|
+
import { Types } from '@digitaldefiance/mongoose-types';
|
|
20
|
+
import { ModelDocMap } from './shared-types';
|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Main application class.
|
|
24
|
+
*
|
|
25
|
+
* This class sets up the Express application with:
|
|
26
|
+
* - Library's ApiRouter which provides user authentication routes
|
|
27
|
+
* - App router for serving React frontend
|
|
28
|
+
* - Database initialization
|
|
29
|
+
* - Email service registration
|
|
30
|
+
*
|
|
31
|
+
* Note: The library's ApiRouter includes UserController which provides:
|
|
32
|
+
* - /api/user/verify - Token verification
|
|
33
|
+
* - /api/user/request-direct-login - Direct login request
|
|
34
|
+
* - /api/user/login - User login
|
|
35
|
+
* - And other user management endpoints
|
|
36
|
+
*
|
|
37
|
+
* For custom API routes, create additional decorator-based controllers
|
|
38
|
+
* and mount them on the router.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const env = new Environment(join(App.distDir, 'my-api', '.env'));
|
|
43
|
+
* const app = new App(
|
|
44
|
+
* env,
|
|
45
|
+
* DatabaseInitializationService.initUserDb.bind(DatabaseInitializationService),
|
|
46
|
+
* DatabaseInitializationService.serverInitResultHash.bind(DatabaseInitializationService),
|
|
47
|
+
* Constants,
|
|
48
|
+
* );
|
|
49
|
+
* await app.start();
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export class App<
|
|
53
|
+
TInitResults extends IServerInitResult<Types.ObjectId> = IServerInitResult<Types.ObjectId>,
|
|
54
|
+
TConstants extends IConstants = IConstants,
|
|
55
|
+
> extends Application<TInitResults, ModelDocMap, Types.ObjectId, Environment<Types.ObjectId>, TConstants, AppRouter> {
|
|
12
56
|
constructor(
|
|
13
|
-
environment: Environment
|
|
14
|
-
databaseInitFunction: (
|
|
57
|
+
environment: Environment<Types.ObjectId>,
|
|
58
|
+
databaseInitFunction: (
|
|
59
|
+
application: BaseApplication<Types.ObjectId, ModelDocMap, TInitResults>,
|
|
60
|
+
) => Promise<IFailableResult<TInitResults>>,
|
|
15
61
|
initResultHashFunction: (initResults: TInitResults) => string,
|
|
16
62
|
constants: TConstants = Constants as TConstants,
|
|
17
63
|
) {
|
|
18
64
|
super(
|
|
19
|
-
environment,
|
|
20
|
-
|
|
65
|
+
environment,
|
|
66
|
+
// Use the library's ApiRouter which includes UserController for auth routes
|
|
67
|
+
(app: IApplication<Types.ObjectId>): BaseRouter<Types.ObjectId> => {
|
|
68
|
+
return new LibraryApiRouter(app);
|
|
69
|
+
},
|
|
21
70
|
getSchemaMap,
|
|
22
|
-
databaseInitFunction,
|
|
23
|
-
initResultHashFunction,
|
|
71
|
+
databaseInitFunction,
|
|
72
|
+
initResultHashFunction,
|
|
24
73
|
undefined, // Default CSP config
|
|
25
74
|
constants,
|
|
26
|
-
(apiRouter: BaseRouter<
|
|
75
|
+
(apiRouter: BaseRouter<Types.ObjectId>): AppRouter => new AppRouter(apiRouter),
|
|
27
76
|
initMiddleware,
|
|
28
77
|
);
|
|
29
|
-
|
|
78
|
+
|
|
30
79
|
// Register the DummyEmailService - users should replace this with their own email service
|
|
31
|
-
const emailService = new DummyEmailService(this);
|
|
32
|
-
//const emailService = new EmailService(this);
|
|
80
|
+
const emailService = new DummyEmailService<Types.ObjectId>(this);
|
|
81
|
+
// const emailService = new EmailService(this);
|
|
33
82
|
emailServiceRegistry.setService(emailService);
|
|
34
83
|
}
|
|
35
84
|
}
|
|
@@ -1,11 +1,77 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DecoratorBaseController,
|
|
3
|
+
ApiController,
|
|
4
|
+
Get,
|
|
5
|
+
RequireAuth,
|
|
6
|
+
Public,
|
|
7
|
+
ApiTags,
|
|
8
|
+
ApiSummary,
|
|
9
|
+
ApiDescription,
|
|
10
|
+
Returns,
|
|
11
|
+
Param,
|
|
12
|
+
Query,
|
|
13
|
+
Paginated,
|
|
14
|
+
IApplication,
|
|
15
|
+
} from '@digitaldefiance/node-express-suite';
|
|
2
16
|
import { ITokenRole, ITokenUser, IUserBase } from '@digitaldefiance/suite-core-lib';
|
|
3
17
|
import { Types } from '@digitaldefiance/mongoose-types';
|
|
4
18
|
import { Environment } from '../environment';
|
|
5
19
|
import { IConstants } from '../interfaces/constants';
|
|
6
20
|
import { CoreLanguageCode } from '@digitaldefiance/i18n-lib';
|
|
7
21
|
|
|
8
|
-
|
|
22
|
+
/**
|
|
23
|
+
* CustomApiController - Example decorator-based API controller.
|
|
24
|
+
*
|
|
25
|
+
* This controller demonstrates the recommended decorator-based approach for
|
|
26
|
+
* building custom Express API endpoints with @digitaldefiance/node-express-suite.
|
|
27
|
+
*
|
|
28
|
+
* Note: The main user authentication routes (/api/user/*) are provided by the
|
|
29
|
+
* library's ApiRouter which is configured in application.ts. This controller
|
|
30
|
+
* is for adding your own custom API endpoints.
|
|
31
|
+
*
|
|
32
|
+
* Features demonstrated:
|
|
33
|
+
* - @ApiController for controller registration and OpenAPI metadata
|
|
34
|
+
* - @Get, @Post, @Put, @Delete for HTTP method routing
|
|
35
|
+
* - @RequireAuth and @Public for authentication control
|
|
36
|
+
* - @ApiTags, @ApiSummary, @ApiDescription for OpenAPI documentation
|
|
37
|
+
* - @Returns for response documentation
|
|
38
|
+
* - @Param, @Query for parameter injection
|
|
39
|
+
* - @Paginated for pagination support
|
|
40
|
+
*
|
|
41
|
+
* To use this controller, mount it on your application's router in application.ts:
|
|
42
|
+
* ```typescript
|
|
43
|
+
* // In your Application class constructor, after creating the LibraryApiRouter:
|
|
44
|
+
* const customController = new CustomApiController(app);
|
|
45
|
+
* apiRouter.router.use('/custom', customController.router);
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* // Create your own controller by extending DecoratorBaseController
|
|
51
|
+
* @ApiTags('Users')
|
|
52
|
+
* @ApiController('/api/users', { description: 'User management endpoints' })
|
|
53
|
+
* class UserController extends DecoratorBaseController {
|
|
54
|
+
* constructor(app: IApplication) {
|
|
55
|
+
* super(app);
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* @Public()
|
|
59
|
+
* @ApiSummary('Get user by ID')
|
|
60
|
+
* @Returns(200, 'User', { description: 'User found' })
|
|
61
|
+
* @Returns(404, 'ErrorResponse', { description: 'User not found' })
|
|
62
|
+
* @Get('/:id')
|
|
63
|
+
* async getUser(@Param('id') id: string) {
|
|
64
|
+
* // Your implementation here
|
|
65
|
+
* }
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
@ApiTags('Custom')
|
|
70
|
+
@ApiController('/custom', {
|
|
71
|
+
description: 'Custom API endpoints - add your own routes here',
|
|
72
|
+
})
|
|
73
|
+
@RequireAuth()
|
|
74
|
+
export class CustomApiController<
|
|
9
75
|
TID extends Types.ObjectId | string = Types.ObjectId,
|
|
10
76
|
TDate extends Date = Date,
|
|
11
77
|
TLanguage extends CoreLanguageCode = CoreLanguageCode,
|
|
@@ -15,10 +81,84 @@ export class ApiRouter<
|
|
|
15
81
|
TTokenUser extends ITokenUser = ITokenUser,
|
|
16
82
|
TEnvironment extends Environment<TID> = Environment<TID>,
|
|
17
83
|
TConstants extends IConstants = IConstants,
|
|
18
|
-
TBaseDocument extends IBaseDocument<any, TID> = IBaseDocument<any, TID>,
|
|
19
84
|
TApplication extends IApplication<TID> = IApplication<TID>,
|
|
20
|
-
> extends
|
|
85
|
+
> extends DecoratorBaseController<TLanguage, TID> {
|
|
21
86
|
constructor(app: TApplication) {
|
|
22
87
|
super(app);
|
|
23
88
|
}
|
|
24
|
-
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Health check endpoint.
|
|
92
|
+
* Returns the current server status.
|
|
93
|
+
*/
|
|
94
|
+
@Public()
|
|
95
|
+
@ApiSummary('Health check')
|
|
96
|
+
@ApiDescription('Returns the current server health status')
|
|
97
|
+
@Returns(200, 'HealthResponse', { description: 'Server is healthy' })
|
|
98
|
+
@Get('/health')
|
|
99
|
+
async healthCheck() {
|
|
100
|
+
return {
|
|
101
|
+
status: 'ok',
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Example paginated list endpoint.
|
|
108
|
+
* Demonstrates pagination with query parameter injection.
|
|
109
|
+
*/
|
|
110
|
+
@Public()
|
|
111
|
+
@Paginated({ defaultPageSize: 20, maxPageSize: 100 })
|
|
112
|
+
@ApiSummary('List items')
|
|
113
|
+
@ApiDescription('Returns a paginated list of items')
|
|
114
|
+
@Returns(200, 'ItemList', { description: 'List of items' })
|
|
115
|
+
@Get('/items')
|
|
116
|
+
async listItems(
|
|
117
|
+
@Query('page') page: number = 1,
|
|
118
|
+
@Query('limit') limit: number = 20,
|
|
119
|
+
) {
|
|
120
|
+
// Example implementation - replace with your actual logic
|
|
121
|
+
return {
|
|
122
|
+
items: [],
|
|
123
|
+
total: 0,
|
|
124
|
+
page,
|
|
125
|
+
pageSize: limit,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Example get by ID endpoint.
|
|
131
|
+
* Demonstrates path parameter injection.
|
|
132
|
+
*/
|
|
133
|
+
@Public()
|
|
134
|
+
@ApiSummary('Get item by ID')
|
|
135
|
+
@ApiDescription('Returns a single item by its ID')
|
|
136
|
+
@Returns(200, 'Item', { description: 'Item found' })
|
|
137
|
+
@Returns(404, 'ErrorResponse', { description: 'Item not found' })
|
|
138
|
+
@Get('/items/:id')
|
|
139
|
+
async getItem(@Param('id') id: string) {
|
|
140
|
+
// Example implementation - replace with your actual logic
|
|
141
|
+
return {
|
|
142
|
+
id,
|
|
143
|
+
name: 'Example Item',
|
|
144
|
+
createdAt: new Date().toISOString(),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Example authenticated endpoint.
|
|
150
|
+
* Requires JWT authentication (inherited from class-level @RequireAuth).
|
|
151
|
+
*/
|
|
152
|
+
@ApiSummary('Get current user profile')
|
|
153
|
+
@ApiDescription('Returns the authenticated user profile')
|
|
154
|
+
@Returns(200, 'UserProfile', { description: 'User profile' })
|
|
155
|
+
@Returns(401, 'ErrorResponse', { description: 'Unauthorized' })
|
|
156
|
+
@Get('/profile')
|
|
157
|
+
async getProfile() {
|
|
158
|
+
// Access authenticated user via this.req.user
|
|
159
|
+
return {
|
|
160
|
+
message: 'Authenticated endpoint',
|
|
161
|
+
// user: this.req.user,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -3,9 +3,10 @@ import { Request, Response, NextFunction } from 'express';
|
|
|
3
3
|
import { Environment } from '../environment';
|
|
4
4
|
import { IConstants } from '../interfaces/constants';
|
|
5
5
|
import { Constants } from '../constants';
|
|
6
|
+
import { Types } from '@digitaldefiance/mongoose-types';
|
|
6
7
|
|
|
7
|
-
export class AppRouter extends BaseAppRouter<IApplication
|
|
8
|
-
constructor(apiRouter: BaseRouter<IApplication
|
|
8
|
+
export class AppRouter extends BaseAppRouter<Types.ObjectId, IApplication<Types.ObjectId>> {
|
|
9
|
+
constructor(apiRouter: BaseRouter<Types.ObjectId, IApplication<Types.ObjectId>>) {
|
|
9
10
|
super(apiRouter);
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -17,7 +18,7 @@ export class AppRouter extends BaseAppRouter<IApplication> {
|
|
|
17
18
|
const jsFile = this.getAssetFilename(this.assetsDir, /^index-.*\.js$/);
|
|
18
19
|
const cssFile = this.getAssetFilename(this.assetsDir, /^index-.*\.css$/);
|
|
19
20
|
const constants = this.application.constants as IConstants;
|
|
20
|
-
const environment = this.application.environment as Environment
|
|
21
|
+
const environment = this.application.environment as Environment<Types.ObjectId>;
|
|
21
22
|
const SiteName = constants.Site;
|
|
22
23
|
const locals = {
|
|
23
24
|
...this.getBaseViewLocals(req, res),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Connection } from '@digitaldefiance/mongoose-types';
|
|
1
|
+
import { Connection, Types } from '@digitaldefiance/mongoose-types';
|
|
2
2
|
import { BaseModelName, createEmailTokenSchema, createMnemonicSchema, createRoleSchema, createUsedDirectLoginTokenSchema, createUserRoleSchema, MnemonicSchema, RoleSchema, SchemaCollection, SchemaMap, UsedDirectLoginTokenSchema, UserRoleSchema } from '@digitaldefiance/node-express-suite';
|
|
3
3
|
import EmailTokenModel from '../models/email-token';
|
|
4
4
|
import MnemonicModel from '../models/mnemonic';
|
|
@@ -11,7 +11,7 @@ import { ModelDocMap } from '../shared-types';
|
|
|
11
11
|
import { IConstants } from '../interfaces/constants';
|
|
12
12
|
import { Constants } from '../constants';
|
|
13
13
|
|
|
14
|
-
export function getSchemaMap(connection: Connection, constants: IConstants = Constants): SchemaMap<ModelDocMap> {
|
|
14
|
+
export function getSchemaMap(connection: Connection, constants: IConstants = Constants): SchemaMap<Types.ObjectId, ModelDocMap> {
|
|
15
15
|
return {
|
|
16
16
|
EmailToken: {
|
|
17
17
|
collection: SchemaCollection.EmailToken,
|
|
@@ -48,7 +48,7 @@ export type SchemaMap = {
|
|
|
48
48
|
/**
|
|
49
49
|
* For each model name, contains the corresponding schema and model
|
|
50
50
|
*/
|
|
51
|
-
[K in keyof ModelDocMap]: ISchema<ModelDocMap[K]>;
|
|
51
|
+
[K in keyof ModelDocMap]: ISchema<Types.ObjectId, ModelDocMap[K]>;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
export type ApiRequestHandler<T extends ApiResponse> = (
|
|
@@ -4,20 +4,88 @@ API business logic library for {{WORKSPACE_NAME}}.
|
|
|
4
4
|
|
|
5
5
|
## Purpose
|
|
6
6
|
|
|
7
|
-
Business logic, services, and
|
|
7
|
+
Business logic, services, models, and decorator-based API controllers for the API layer.
|
|
8
8
|
|
|
9
9
|
## Structure
|
|
10
10
|
|
|
11
11
|
```
|
|
12
12
|
src/
|
|
13
|
-
├──
|
|
14
|
-
├──
|
|
15
|
-
├──
|
|
16
|
-
|
|
13
|
+
├── lib/
|
|
14
|
+
│ ├── routers/ # Decorator-based API controllers
|
|
15
|
+
│ ├── services/ # Business logic services
|
|
16
|
+
│ ├── models/ # Mongoose data models
|
|
17
|
+
│ ├── documents/ # TypeScript document interfaces
|
|
18
|
+
│ ├── schemas/ # Mongoose schemas
|
|
19
|
+
│ ├── interfaces/ # TypeScript interfaces
|
|
20
|
+
│ ├── application.ts # Main application class
|
|
21
|
+
│ ├── environment.ts # Environment configuration
|
|
22
|
+
│ └── constants.ts # Application constants
|
|
23
|
+
└── index.ts # Public exports
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Decorator-Based Controllers
|
|
27
|
+
|
|
28
|
+
This library uses the decorator-based approach from `@digitaldefiance/node-express-suite` for defining API routes. This provides:
|
|
29
|
+
|
|
30
|
+
- **Type-safe routing** with `@Get`, `@Post`, `@Put`, `@Delete`, `@Patch` decorators
|
|
31
|
+
- **Automatic OpenAPI documentation** with `@ApiTags`, `@ApiSummary`, `@Returns` decorators
|
|
32
|
+
- **Built-in authentication** with `@RequireAuth`, `@Public` decorators
|
|
33
|
+
- **Request validation** with `@ValidateBody`, `@ValidateParams`, `@ValidateQuery` decorators
|
|
34
|
+
- **Parameter injection** with `@Param`, `@Body`, `@Query`, `@Header` decorators
|
|
35
|
+
|
|
36
|
+
### Example Controller
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import {
|
|
40
|
+
DecoratorBaseController,
|
|
41
|
+
ApiController,
|
|
42
|
+
Get,
|
|
43
|
+
Post,
|
|
44
|
+
RequireAuth,
|
|
45
|
+
Public,
|
|
46
|
+
ApiTags,
|
|
47
|
+
ApiSummary,
|
|
48
|
+
Returns,
|
|
49
|
+
Param,
|
|
50
|
+
Body,
|
|
51
|
+
ValidateBody,
|
|
52
|
+
} from '@digitaldefiance/node-express-suite';
|
|
53
|
+
import { z } from 'zod';
|
|
54
|
+
|
|
55
|
+
const CreateItemSchema = z.object({
|
|
56
|
+
name: z.string().min(1).max(100),
|
|
57
|
+
description: z.string().optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
@ApiTags('Items')
|
|
61
|
+
@ApiController('/api/items', { description: 'Item management endpoints' })
|
|
62
|
+
@RequireAuth()
|
|
63
|
+
export class ItemController extends DecoratorBaseController {
|
|
64
|
+
constructor(app: IApplication) {
|
|
65
|
+
super(app);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Public()
|
|
69
|
+
@ApiSummary('List all items')
|
|
70
|
+
@Returns(200, 'ItemList')
|
|
71
|
+
@Get('/')
|
|
72
|
+
async listItems() {
|
|
73
|
+
return { items: [] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@ValidateBody(CreateItemSchema)
|
|
77
|
+
@ApiSummary('Create a new item')
|
|
78
|
+
@Returns(201, 'Item')
|
|
79
|
+
@Returns(400, 'ValidationError')
|
|
80
|
+
@Post('/')
|
|
81
|
+
async createItem(@Body() data: z.infer<typeof CreateItemSchema>) {
|
|
82
|
+
return { id: 'new-id', ...data };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
17
85
|
```
|
|
18
86
|
|
|
19
87
|
## Usage
|
|
20
88
|
|
|
21
89
|
```typescript
|
|
22
|
-
import {
|
|
90
|
+
import { App, Environment, Constants } from '{{NAMESPACE_ROOT}}/api-lib';
|
|
23
91
|
```
|