@digitaldefiance/express-suite-starter 3.0.3 → 4.20.0
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/README.md +185 -0
- package/config/presets/brightstack.json +90 -0
- package/dist/src/core/interfaces/devcontainer-config.interface.d.ts +1 -0
- package/dist/src/core/interfaces/devcontainer-config.interface.d.ts.map +1 -1
- package/dist/src/core/interfaces/generator-config.interface.d.ts +1 -0
- package/dist/src/core/interfaces/generator-config.interface.d.ts.map +1 -1
- package/dist/src/core/interfaces/validation-issue.interface.d.ts +1 -1
- package/dist/src/core/interfaces/validation-issue.interface.d.ts.map +1 -1
- package/dist/src/core/programmatic-generator.d.ts +4 -0
- package/dist/src/core/programmatic-generator.d.ts.map +1 -1
- package/dist/src/core/programmatic-generator.js +45 -12
- package/dist/src/core/programmatic-generator.js.map +1 -1
- package/dist/src/core/validators/post-generation-validator.d.ts +22 -1
- package/dist/src/core/validators/post-generation-validator.d.ts.map +1 -1
- package/dist/src/core/validators/post-generation-validator.js +127 -0
- package/dist/src/core/validators/post-generation-validator.js.map +1 -1
- package/dist/src/generate-monorepo.d.ts.map +1 -1
- package/dist/src/generate-monorepo.js +129 -45
- package/dist/src/generate-monorepo.js.map +1 -1
- package/dist/src/i18n/starter-string-key.d.ts +13 -1
- package/dist/src/i18n/starter-string-key.d.ts.map +1 -1
- package/dist/src/i18n/starter-string-key.js +17 -0
- package/dist/src/i18n/starter-string-key.js.map +1 -1
- package/dist/src/i18n/strings/de.d.ts.map +1 -1
- package/dist/src/i18n/strings/de.js +17 -0
- package/dist/src/i18n/strings/de.js.map +1 -1
- package/dist/src/i18n/strings/en-gb.d.ts +12 -0
- package/dist/src/i18n/strings/en-gb.d.ts.map +1 -1
- package/dist/src/i18n/strings/en-us.d.ts.map +1 -1
- package/dist/src/i18n/strings/en-us.js +17 -0
- package/dist/src/i18n/strings/en-us.js.map +1 -1
- package/dist/src/i18n/strings/es.d.ts.map +1 -1
- package/dist/src/i18n/strings/es.js +17 -0
- package/dist/src/i18n/strings/es.js.map +1 -1
- package/dist/src/i18n/strings/fr.d.ts.map +1 -1
- package/dist/src/i18n/strings/fr.js +17 -0
- package/dist/src/i18n/strings/fr.js.map +1 -1
- package/dist/src/i18n/strings/ja.d.ts.map +1 -1
- package/dist/src/i18n/strings/ja.js +17 -0
- package/dist/src/i18n/strings/ja.js.map +1 -1
- package/dist/src/i18n/strings/uk.d.ts.map +1 -1
- package/dist/src/i18n/strings/uk.js +17 -0
- package/dist/src/i18n/strings/uk.js.map +1 -1
- package/dist/src/i18n/strings/zh-cn.d.ts.map +1 -1
- package/dist/src/i18n/strings/zh-cn.js +17 -0
- package/dist/src/i18n/strings/zh-cn.js.map +1 -1
- package/dist/src/i18n/translations-all.d.ts +12 -0
- package/dist/src/i18n/translations-all.d.ts.map +1 -1
- package/package.json +2 -1
- package/scaffolding/api-brightstack/.env.example.mustache +47 -0
- package/scaffolding/api-brightstack/src/main.ts.mustache +20 -0
- package/scaffolding/api-lib-brightstack/src/index.ts +9 -0
- package/scaffolding/api-lib-brightstack/src/lib/application.ts +85 -0
- package/scaffolding/api-lib-brightstack/src/lib/collections/index.ts +13 -0
- package/scaffolding/api-lib-brightstack/src/lib/documents/user.ts +22 -0
- package/scaffolding/api-lib-brightstack/src/lib/environment.ts +55 -0
- package/scaffolding/api-lib-brightstack/src/lib/routers/api.ts +164 -0
- package/scaffolding/api-lib-brightstack/src/lib/routers/app.ts +32 -0
- package/scaffolding/api-lib-brightstack/src/lib/routers/base.ts +22 -0
- package/scaffolding/api-lib-brightstack/src/lib/routers/index.ts +2 -0
- package/scaffolding/api-lib-brightstack/src/lib/schemas/index.ts +2 -0
- package/scaffolding/api-lib-brightstack/src/lib/schemas/schema.ts +28 -0
- package/scaffolding/api-lib-brightstack/src/lib/schemas/user.ts +26 -0
- package/scaffolding/api-lib-brightstack/src/lib/services/email.ts +108 -0
- package/scaffolding/api-lib-brightstack/src/lib/shared-types.ts +149 -0
- package/scaffolding/api-lib-mern/src/lib/constants.ts.mustache +10 -0
- package/scaffolding/api-lib-mern/src/lib/documents/index.ts +1 -0
- package/scaffolding/api-lib-mern/src/lib/interfaces/constants.ts +9 -0
- package/scaffolding/api-lib-mern/src/lib/interfaces/environment-aws.ts +7 -0
- package/scaffolding/api-lib-mern/src/lib/interfaces/environment.ts +10 -0
- package/scaffolding/api-lib-mern/src/lib/middlewares.ts.mustache +113 -0
- package/scaffolding/api-lib-mern/src/lib/services/index.ts +1 -0
- package/scaffolding/api-mern/src/assets/.gitkeep +0 -0
- package/scaffolding/api-mern/src/views/index.ejs +34 -0
- package/scaffolding/inituserdb-brightstack/.env.example.mustache +13 -0
- package/scaffolding/inituserdb-brightstack/src/main.ts.mustache +195 -0
- /package/scaffolding/{api → api-brightstack}/src/assets/.gitkeep +0 -0
- /package/scaffolding/{api → api-brightstack}/src/views/index.ejs +0 -0
- /package/scaffolding/{api-lib → api-lib-brightstack}/src/lib/constants.ts.mustache +0 -0
- /package/scaffolding/{api-lib → api-lib-brightstack}/src/lib/documents/index.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-brightstack}/src/lib/interfaces/constants.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-brightstack}/src/lib/interfaces/environment-aws.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-brightstack}/src/lib/interfaces/environment.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-brightstack}/src/lib/middlewares.ts.mustache +0 -0
- /package/scaffolding/{api-lib → api-lib-brightstack}/src/lib/services/index.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/index.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/application.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/documents/user.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/environment.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/models/email-token.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/models/mnemonic.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/models/role.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/models/used-direct-login-token.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/models/user-role.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/models/user.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/routers/api.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/routers/app.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/routers/index.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/schemas/index.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/schemas/schema.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/schemas/user.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/services/email.ts +0 -0
- /package/scaffolding/{api-lib → api-lib-mern}/src/lib/shared-types.ts +0 -0
- /package/scaffolding/{api → api-mern}/.env.example.mustache +0 -0
- /package/scaffolding/{api → api-mern}/src/main.ts.mustache +0 -0
- /package/scaffolding/{inituserdb → inituserdb-mern}/.env.example.mustache +0 -0
- /package/scaffolding/{inituserdb → inituserdb-mern}/src/main.ts.mustache +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
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';
|
|
16
|
+
import { ITokenRole, ITokenUser, IUserBase } from '@digitaldefiance/suite-core-lib';
|
|
17
|
+
import { PlatformID } from '@digitaldefiance/node-ecies-lib';
|
|
18
|
+
import { Environment } from '../environment';
|
|
19
|
+
import { IConstants } from '../interfaces/constants';
|
|
20
|
+
import { CoreLanguageCode } from '@digitaldefiance/i18n-lib';
|
|
21
|
+
|
|
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<
|
|
75
|
+
TID extends PlatformID = PlatformID,
|
|
76
|
+
TDate extends Date = Date,
|
|
77
|
+
TLanguage extends CoreLanguageCode = CoreLanguageCode,
|
|
78
|
+
TAccountStatus extends string = string,
|
|
79
|
+
TUser extends IUserBase<TID, TDate, TLanguage, TAccountStatus> = IUserBase<TID, TDate, TLanguage, TAccountStatus>,
|
|
80
|
+
TTokenRole extends ITokenRole<TID, TDate> = ITokenRole<TID, TDate>,
|
|
81
|
+
TTokenUser extends ITokenUser = ITokenUser,
|
|
82
|
+
TEnvironment extends Environment<TID> = Environment<TID>,
|
|
83
|
+
TConstants extends IConstants = IConstants,
|
|
84
|
+
TApplication extends IApplication<TID> = IApplication<TID>,
|
|
85
|
+
> extends DecoratorBaseController<TLanguage, TID> {
|
|
86
|
+
constructor(app: TApplication) {
|
|
87
|
+
super(app);
|
|
88
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AppRouter as BaseAppRouter, BaseRouter, IApplication } from '@digitaldefiance/node-express-suite';
|
|
2
|
+
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
import { Environment } from '../environment';
|
|
4
|
+
import { IConstants } from '../interfaces/constants';
|
|
5
|
+
import { Constants } from '../constants';
|
|
6
|
+
import { PlatformID } from '@digitaldefiance/node-ecies-lib';
|
|
7
|
+
|
|
8
|
+
export class AppRouter extends BaseAppRouter<PlatformID, IApplication<PlatformID>> {
|
|
9
|
+
constructor(apiRouter: BaseRouter<PlatformID, IApplication<PlatformID>>) {
|
|
10
|
+
super(apiRouter);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public override renderIndex(req: Request, res: Response, next: NextFunction): void {
|
|
14
|
+
if (req.url.endsWith('.js')) {
|
|
15
|
+
res.type('application/javascript');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const jsFile = this.getAssetFilename(this.assetsDir, /^index-.*\.js$/);
|
|
19
|
+
const cssFile = this.getAssetFilename(this.assetsDir, /^index-.*\.css$/);
|
|
20
|
+
const constants = this.application.constants as IConstants;
|
|
21
|
+
const environment = this.application.environment as Environment<PlatformID>;
|
|
22
|
+
const SiteName = constants.Site;
|
|
23
|
+
const locals = {
|
|
24
|
+
...this.getBaseViewLocals(req, res),
|
|
25
|
+
title: SiteName,
|
|
26
|
+
jsFile: jsFile ? `assets/${jsFile}` : undefined,
|
|
27
|
+
cssFile: cssFile ? `assets/${cssFile}` : undefined,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
this.renderTemplate(req, res, next, 'index', locals);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseRouter,
|
|
3
|
+
IApplication,
|
|
4
|
+
} from '@digitaldefiance/node-express-suite';
|
|
5
|
+
import { PlatformID } from '@digitaldefiance/node-ecies-lib';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Concrete API router for BrightStack applications.
|
|
9
|
+
*
|
|
10
|
+
* Extends the abstract BaseRouter from @digitaldefiance/node-express-suite
|
|
11
|
+
* to provide a concrete router that can be instantiated without Mongoose
|
|
12
|
+
* dependencies. Mount decorator-based controllers on this router for
|
|
13
|
+
* custom API endpoints.
|
|
14
|
+
*/
|
|
15
|
+
export class ApiRouter<
|
|
16
|
+
TID extends PlatformID = PlatformID,
|
|
17
|
+
TApplication extends IApplication<TID> = IApplication<TID>,
|
|
18
|
+
> extends BaseRouter<TID, TApplication> {
|
|
19
|
+
constructor(application: TApplication) {
|
|
20
|
+
super(application);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IBlockStorageSchemaEntry,
|
|
3
|
+
SchemaMap,
|
|
4
|
+
} from '@brightchain/node-express-suite';
|
|
5
|
+
import { UserSchema } from './user';
|
|
6
|
+
import { IConstants } from '../interfaces/constants';
|
|
7
|
+
import { Constants } from '../constants';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns the BrightDB schema map for all collections.
|
|
11
|
+
*
|
|
12
|
+
* Unlike the MERN variant which creates Mongoose models via connection.model(),
|
|
13
|
+
* BrightDB uses declarative schema entries that describe the collection structure.
|
|
14
|
+
* The BrightDbDatabasePlugin uses this map to initialize collections.
|
|
15
|
+
*/
|
|
16
|
+
export function getSchemaMap(constants: IConstants = Constants): SchemaMap {
|
|
17
|
+
return {
|
|
18
|
+
User: {
|
|
19
|
+
collection: 'users',
|
|
20
|
+
model: {
|
|
21
|
+
modelName: 'User',
|
|
22
|
+
schema: UserSchema,
|
|
23
|
+
},
|
|
24
|
+
modelName: 'User',
|
|
25
|
+
schema: UserSchema,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IBlockStorageSchema } from '@brightchain/node-express-suite';
|
|
2
|
+
import { IUserDocument } from '../documents';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* BrightDB schema definition for User documents.
|
|
6
|
+
*
|
|
7
|
+
* Unlike the MERN variant which uses Mongoose Schema with validators and hooks,
|
|
8
|
+
* BrightDB schemas are declarative field definitions used by the block storage layer.
|
|
9
|
+
*/
|
|
10
|
+
export const UserSchema: IBlockStorageSchema<IUserDocument> = {
|
|
11
|
+
name: 'User',
|
|
12
|
+
fields: {
|
|
13
|
+
name: { type: 'string', required: true },
|
|
14
|
+
email: { type: 'string', required: true },
|
|
15
|
+
passwordHash: { type: 'string', required: true },
|
|
16
|
+
language: { type: 'string', required: true },
|
|
17
|
+
accountStatus: { type: 'string', required: true },
|
|
18
|
+
roles: { type: 'array', required: false },
|
|
19
|
+
createdAt: { type: 'date', required: false },
|
|
20
|
+
updatedAt: { type: 'date', required: false },
|
|
21
|
+
},
|
|
22
|
+
indexes: [
|
|
23
|
+
{ fields: { email: 1 }, options: { unique: true } },
|
|
24
|
+
{ fields: { name: 1 } },
|
|
25
|
+
],
|
|
26
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
2
|
+
import { IApplication } from '@digitaldefiance/node-express-suite';
|
|
3
|
+
|
|
4
|
+
// Simple debugLog implementation
|
|
5
|
+
function debugLog(debug: boolean, type: 'log' | 'warn' | 'error', ...args: any[]): void {
|
|
6
|
+
if (debug) {
|
|
7
|
+
console[type](...args);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
import { Environment } from '../environment';
|
|
11
|
+
import { PlatformID } from '@digitaldefiance/node-ecies-lib';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A generic service for sending emails using Amazon SES.
|
|
15
|
+
*/
|
|
16
|
+
export class EmailService<TID extends PlatformID> {
|
|
17
|
+
private readonly sesClient: SESClient;
|
|
18
|
+
private readonly emailSender: string;
|
|
19
|
+
private readonly disableEmailSend: boolean;
|
|
20
|
+
private readonly debug: boolean;
|
|
21
|
+
private readonly application: IApplication<TID>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Constructs an instance of EmailService.
|
|
25
|
+
* @param config Configuration object containing AWS credentials, region, sender email, and debug/disable flags.
|
|
26
|
+
*/
|
|
27
|
+
constructor(application: IApplication<TID>) {
|
|
28
|
+
this.application = application;
|
|
29
|
+
const environment = application.environment as Environment<TID>;
|
|
30
|
+
this.emailSender = environment.emailSender;
|
|
31
|
+
this.disableEmailSend = environment.disableEmailSend;
|
|
32
|
+
this.debug = environment.debug;
|
|
33
|
+
|
|
34
|
+
// Initialize the SES client with provided AWS credentials and region.
|
|
35
|
+
this.sesClient = new SESClient({
|
|
36
|
+
region: environment.aws.region,
|
|
37
|
+
credentials: {
|
|
38
|
+
accessKeyId: environment.aws.accessKeyId.value ?? '',
|
|
39
|
+
secretAccessKey:
|
|
40
|
+
environment.aws.secretAccessKey.value ?? '',
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sends an email using Amazon SES.
|
|
47
|
+
* @param to The recipient's email address.
|
|
48
|
+
* @param subject The subject line of the email.
|
|
49
|
+
* @param text The plain text body of the email.
|
|
50
|
+
* @param html The HTML body of the email.
|
|
51
|
+
* @returns A Promise that resolves when the email is sent successfully, or rejects on failure.
|
|
52
|
+
* @throws Error if email sending is disabled or if SES encounters an error.
|
|
53
|
+
*/
|
|
54
|
+
public async sendEmail(
|
|
55
|
+
to: string,
|
|
56
|
+
subject: string,
|
|
57
|
+
text: string,
|
|
58
|
+
html: string,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
if (this.disableEmailSend) {
|
|
61
|
+
debugLog(
|
|
62
|
+
this.debug,
|
|
63
|
+
'log',
|
|
64
|
+
`Email sending disabled for: ${to} - Subject: ${subject}`,
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const sendCommand = new SendEmailCommand({
|
|
70
|
+
Destination: {
|
|
71
|
+
ToAddresses: [to], // Recipient email address
|
|
72
|
+
},
|
|
73
|
+
Message: {
|
|
74
|
+
Body: {
|
|
75
|
+
Html: {
|
|
76
|
+
Charset: 'UTF-8',
|
|
77
|
+
Data: html, // HTML content of the email
|
|
78
|
+
},
|
|
79
|
+
Text: {
|
|
80
|
+
Charset: 'UTF-8',
|
|
81
|
+
Data: text, // Plain text content of the email
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
Subject: {
|
|
85
|
+
Charset: 'UTF-8',
|
|
86
|
+
Data: subject, // Subject of the email
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
Source: this.emailSender, // Sender email address (must be verified in SES)
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await this.sesClient.send(sendCommand);
|
|
94
|
+
debugLog(
|
|
95
|
+
this.debug,
|
|
96
|
+
'log',
|
|
97
|
+
`Email sent successfully to ${to} with subject: ${subject}`,
|
|
98
|
+
);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(`Failed to send email to ${to}:`, error);
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Failed to send email: ${
|
|
103
|
+
error instanceof Error ? error.message : String(error)
|
|
104
|
+
}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { NextFunction, Request, RequestHandler, Response } from 'express';
|
|
2
|
+
import { ValidationChain } from 'express-validator';
|
|
3
|
+
import { Brand } from 'ts-brand';
|
|
4
|
+
import type { IUserDocument } from './documents/user';
|
|
5
|
+
import { CoreLanguageCode } from '@digitaldefiance/i18n-lib';
|
|
6
|
+
import { IKeyPairBufferWithUnEncryptedPrivateKey, ISigningKeyPrivateKeyInfo, ISimplePublicKeyOnly, ISimpleKeyPairBuffer, ISimplePublicKeyOnlyBuffer, PlatformID } from '@digitaldefiance/node-ecies-lib';
|
|
7
|
+
import { IApiErrorResponse, IApiMessageResponse, IStatusCodeResponse, IApiExpressValidationErrorResponse } from '@digitaldefiance/node-express-suite';
|
|
8
|
+
import {
|
|
9
|
+
DefaultBackendIdType,
|
|
10
|
+
IBlockStorageSchema,
|
|
11
|
+
IBlockStorageModel,
|
|
12
|
+
IBlockStorageSchemaEntry,
|
|
13
|
+
SchemaMap as BrightDbSchemaMap,
|
|
14
|
+
BrightDbCollection,
|
|
15
|
+
} from '@brightchain/node-express-suite';
|
|
16
|
+
|
|
17
|
+
export type { DefaultBackendIdType } from '@brightchain/node-express-suite';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validated body for express-validator
|
|
21
|
+
*/
|
|
22
|
+
export type ValidatedBody<T extends string> = {
|
|
23
|
+
[K in T]: unknown;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Schema map type for BrightDB collections.
|
|
28
|
+
* Maps model names to their BrightDB schema entries.
|
|
29
|
+
*/
|
|
30
|
+
export type SchemaMap = BrightDbSchemaMap;
|
|
31
|
+
|
|
32
|
+
export type ApiRequestHandler<T extends ApiResponse> = (
|
|
33
|
+
req: Request,
|
|
34
|
+
res: Response,
|
|
35
|
+
next: NextFunction,
|
|
36
|
+
) => Promise<IStatusCodeResponse<T>>;
|
|
37
|
+
|
|
38
|
+
export type TypedHandlers = {
|
|
39
|
+
[key: string]: ApiRequestHandler<ApiResponse>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export interface IRouteDefinition<T extends TypedHandlers> {
|
|
43
|
+
method: 'get' | 'post' | 'put' | 'delete' | 'patch';
|
|
44
|
+
path: string;
|
|
45
|
+
options: {
|
|
46
|
+
handlerKey: keyof T;
|
|
47
|
+
validation?: (validationLanguage: CoreLanguageCode) => ValidationChain[];
|
|
48
|
+
useAuthentication: boolean;
|
|
49
|
+
useCryptoAuthentication: boolean;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type RouteHandlers = Record<string, ApiRequestHandler<ApiResponse>>;
|
|
54
|
+
|
|
55
|
+
export type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
|
|
56
|
+
|
|
57
|
+
export interface RouteConfig<H extends object> {
|
|
58
|
+
method: HttpMethod;
|
|
59
|
+
path: string;
|
|
60
|
+
handlerKey: keyof H;
|
|
61
|
+
handlerArgs?: Array<unknown>;
|
|
62
|
+
useAuthentication: boolean;
|
|
63
|
+
useCryptoAuthentication: boolean;
|
|
64
|
+
middleware?: RequestHandler[];
|
|
65
|
+
validation?: FlexibleValidationChain;
|
|
66
|
+
rawJsonHandler?: boolean;
|
|
67
|
+
authFailureStatusCode?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function routeConfig<T extends object>(
|
|
71
|
+
method: 'get' | 'post' | 'put' | 'delete' | 'patch',
|
|
72
|
+
path: string,
|
|
73
|
+
options: {
|
|
74
|
+
handlerKey: keyof T;
|
|
75
|
+
validation?: (validationLanguage: CoreLanguageCode) => ValidationChain[];
|
|
76
|
+
useAuthentication: boolean;
|
|
77
|
+
useCryptoAuthentication: boolean;
|
|
78
|
+
},
|
|
79
|
+
): RouteConfig<T> {
|
|
80
|
+
return {
|
|
81
|
+
method,
|
|
82
|
+
path,
|
|
83
|
+
handlerKey: options.handlerKey,
|
|
84
|
+
validation: options.validation,
|
|
85
|
+
useAuthentication: options.useAuthentication,
|
|
86
|
+
useCryptoAuthentication: options.useCryptoAuthentication,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type THandlerArgs<T extends Array<unknown>> = T;
|
|
91
|
+
|
|
92
|
+
export type FlexibleValidationChain =
|
|
93
|
+
| ValidationChain[]
|
|
94
|
+
| ((lang: CoreLanguageCode) => ValidationChain[]);
|
|
95
|
+
|
|
96
|
+
export type JsonPrimitive = string | number | boolean | null | undefined;
|
|
97
|
+
|
|
98
|
+
export type JsonResponse =
|
|
99
|
+
| JsonPrimitive
|
|
100
|
+
| { [key: string]: JsonResponse }
|
|
101
|
+
| JsonResponse[];
|
|
102
|
+
|
|
103
|
+
export type ApiErrorResponse =
|
|
104
|
+
| IApiErrorResponse
|
|
105
|
+
| IApiExpressValidationErrorResponse;
|
|
106
|
+
|
|
107
|
+
export type ApiResponse = IApiMessageResponse | ApiErrorResponse | JsonResponse;
|
|
108
|
+
|
|
109
|
+
export type SendFunction<T extends ApiResponse> = (
|
|
110
|
+
statusCode: number,
|
|
111
|
+
data: T,
|
|
112
|
+
res: Response<T>,
|
|
113
|
+
) => void;
|
|
114
|
+
|
|
115
|
+
export type KeyPairBufferWithUnEncryptedPrivateKey = Brand<
|
|
116
|
+
IKeyPairBufferWithUnEncryptedPrivateKey,
|
|
117
|
+
'KeyPairBufferWithUnEncryptedPrivateKey'
|
|
118
|
+
>;
|
|
119
|
+
export type SigningKeyPrivateKeyInfo = Brand<
|
|
120
|
+
ISigningKeyPrivateKeyInfo,
|
|
121
|
+
'SigningKeyPrivateKeyInfo'
|
|
122
|
+
>;
|
|
123
|
+
export type SimpleKeyPair = Brand<SimplePublicKeyOnly, 'SimpleKeyPair'>;
|
|
124
|
+
export type SimplePublicKeyOnly = Brand<
|
|
125
|
+
ISimplePublicKeyOnly,
|
|
126
|
+
'SimplePublicKeyOnly'
|
|
127
|
+
>;
|
|
128
|
+
export type SimpleKeyPairBuffer = Brand<
|
|
129
|
+
ISimpleKeyPairBuffer,
|
|
130
|
+
'SimpleKeyPairBuffer'
|
|
131
|
+
>;
|
|
132
|
+
export type SimplePublicKeyOnlyBuffer = Brand<
|
|
133
|
+
ISimplePublicKeyOnlyBuffer,
|
|
134
|
+
'SimplePublicKeyOnlyBuffer'
|
|
135
|
+
>;
|
|
136
|
+
export type HexString = Brand<string, 'HexString'>;
|
|
137
|
+
export type SignatureString = Brand<HexString, 'SignatureString'>;
|
|
138
|
+
export type SignatureBuffer = Buffer & Brand<Buffer, 'SignatureBuffer'>;
|
|
139
|
+
export type ChecksumBuffer = Buffer &
|
|
140
|
+
Brand<Buffer, 'Sha3Checksum', 'ChecksumBuffer'>;
|
|
141
|
+
export type ChecksumString = Brand<HexString, 'Sha3Checksum', 'ChecksumString'>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Extended Buffer type for data
|
|
145
|
+
*/
|
|
146
|
+
export type DataBuffer = Buffer & {
|
|
147
|
+
toBuffer(): Buffer;
|
|
148
|
+
toHex(): string;
|
|
149
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IConstants } from './interfaces/constants';
|
|
2
|
+
import { createExpressConstants } from '@digitaldefiance/node-express-suite';
|
|
3
|
+
|
|
4
|
+
export const Constants: IConstants = {
|
|
5
|
+
...createExpressConstants('{{hostname}}', '{{hostname}}'),
|
|
6
|
+
Site: '{{siteTitle}}' as const,
|
|
7
|
+
SiteTagline: '{{siteTagline}}' as const,
|
|
8
|
+
SiteDescription: '{{siteDescription}}' as const,
|
|
9
|
+
PasswordRegex: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?])[A-Za-z\d!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]{8,}$/,
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { IUserDocument } from './user';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IConstants as IBaseConstants } from '@digitaldefiance/node-express-suite';
|
|
2
|
+
|
|
3
|
+
export interface IConstants extends IBaseConstants {
|
|
4
|
+
readonly Site: string;
|
|
5
|
+
readonly SiteTagline: string;
|
|
6
|
+
readonly SiteDescription: string;
|
|
7
|
+
readonly SiteHostname: string;
|
|
8
|
+
readonly PasswordRegex: RegExp;
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IEnvironment as IBaseEnvironment } from '@digitaldefiance/node-express-suite';
|
|
2
|
+
import { IEnvironmentAws } from './environment-aws';
|
|
3
|
+
import { PlatformID } from '@digitaldefiance/node-ecies-lib';
|
|
4
|
+
|
|
5
|
+
export interface IEnvironment<TID extends PlatformID> extends IBaseEnvironment<TID> {
|
|
6
|
+
/**
|
|
7
|
+
* AWS configuration
|
|
8
|
+
*/
|
|
9
|
+
aws: IEnvironmentAws;
|
|
10
|
+
}
|