create-forgeon 0.3.15 → 0.3.17
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 +4 -2
- package/src/cli/add-options.test.mjs +5 -2
- package/src/cli/options.test.mjs +1 -0
- package/src/cli/prompt-select.test.mjs +1 -0
- package/src/core/docs.test.mjs +80 -40
- package/src/core/scaffold.test.mjs +100 -0
- package/src/core/validate.test.mjs +1 -0
- package/src/modules/accounts.mjs +416 -0
- package/src/modules/db-prisma.mjs +23 -55
- package/src/modules/dependencies.test.mjs +71 -29
- package/src/modules/executor.mjs +3 -2
- package/src/modules/executor.test.mjs +631 -500
- package/src/modules/files-access.mjs +36 -105
- package/src/modules/files-image.mjs +35 -107
- package/src/modules/files-local.mjs +15 -6
- package/src/modules/files-quotas.mjs +75 -93
- package/src/modules/files-s3.mjs +17 -6
- package/src/modules/files.mjs +56 -125
- package/src/modules/i18n.mjs +17 -121
- package/src/modules/idempotency.test.mjs +180 -0
- package/src/modules/logger.mjs +0 -9
- package/src/modules/probes.test.mjs +204 -0
- package/src/modules/queue.mjs +325 -440
- package/src/modules/rate-limit.mjs +36 -76
- package/src/modules/rbac.mjs +39 -78
- package/src/modules/registry.mjs +22 -35
- package/src/modules/scheduler.mjs +51 -171
- package/src/modules/shared/files-runtime-wiring.mjs +81 -0
- package/src/modules/shared/nest-runtime-wiring.mjs +110 -0
- package/src/modules/shared/patch-utils.mjs +29 -1
- package/src/modules/shared/probes.mjs +235 -0
- package/src/modules/sync-integrations.mjs +109 -396
- package/src/modules/sync-integrations.test.mjs +141 -0
- package/src/run-add-module.test.mjs +154 -0
- package/templates/base/README.md +7 -55
- package/templates/base/apps/web/src/App.tsx +70 -42
- package/templates/base/apps/web/src/probes.ts +61 -0
- package/templates/base/apps/web/src/styles.css +86 -25
- package/templates/base/package.json +21 -15
- package/templates/base/scripts/forgeon-sync-integrations.mjs +65 -281
- package/templates/module-fragments/{jwt-auth → accounts}/00_title.md +2 -1
- package/templates/module-fragments/{jwt-auth → accounts}/10_overview.md +5 -5
- package/templates/module-fragments/accounts/20_scope.md +29 -0
- package/templates/module-fragments/accounts/90_status_implemented.md +8 -0
- package/templates/module-fragments/accounts/90_status_planned.md +7 -0
- package/templates/module-fragments/rbac/30_what_it_adds.md +3 -2
- package/templates/module-fragments/rbac/40_how_it_works.md +2 -1
- package/templates/module-fragments/rbac/50_how_to_use.md +2 -1
- package/templates/module-fragments/swagger/20_scope.md +2 -1
- package/templates/module-presets/accounts/apps/api/prisma/migrations/0002_accounts_core/migration.sql +97 -0
- package/templates/module-presets/accounts/apps/api/src/accounts/forgeon-accounts-db-prisma.module.ts +17 -0
- package/templates/module-presets/accounts/apps/api/src/accounts/prisma-accounts-persistence.store.ts +332 -0
- package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/package.json +5 -5
- package/templates/module-presets/accounts/packages/accounts-api/src/accounts-email.port.ts +13 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/accounts-persistence.port.ts +67 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/accounts-rbac.port.ts +14 -0
- package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/auth-config.loader.ts +7 -7
- package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/auth-config.service.ts +7 -7
- package/templates/module-presets/accounts/packages/accounts-api/src/auth-core.service.ts +318 -0
- package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/auth-env.schema.ts +4 -4
- package/templates/module-presets/accounts/packages/accounts-api/src/auth-jwt.service.ts +58 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/auth-password.service.ts +21 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/auth.controller.ts +93 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/auth.service.ts +48 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/auth.types.ts +17 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/dto/change-password.dto.ts +13 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/dto/confirm-password-reset.dto.ts +12 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/dto/index.ts +10 -0
- package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/dto/login.dto.ts +1 -1
- package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/dto/refresh.dto.ts +1 -1
- package/templates/module-presets/accounts/packages/accounts-api/src/dto/register.dto.ts +23 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/dto/request-password-reset.dto.ts +7 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/dto/update-user-profile.dto.ts +16 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/dto/update-user-settings.dto.ts +16 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/dto/update-user.dto.ts +8 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/dto/verify-email.dto.ts +8 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/forgeon-accounts.module.ts +82 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/index.ts +24 -0
- package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/jwt.strategy.ts +3 -3
- package/templates/module-presets/accounts/packages/accounts-api/src/owner-access.guard.ts +39 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/users-config.ts +13 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/users.controller.ts +65 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/users.service.ts +87 -0
- package/templates/module-presets/accounts/packages/accounts-api/src/users.types.ts +65 -0
- package/templates/module-presets/{jwt-auth/packages/auth-contracts → accounts/packages/accounts-contracts}/package.json +1 -1
- package/templates/module-presets/accounts/packages/accounts-contracts/src/index.ts +119 -0
- package/templates/module-presets/files/apps/api/src/files/forgeon-files-db-prisma.module.ts +17 -0
- package/templates/module-presets/files/apps/api/src/files/prisma-files-persistence.store.ts +164 -0
- package/templates/module-presets/files/packages/files/package.json +1 -2
- package/templates/module-presets/files/packages/files/src/files.ports.ts +107 -0
- package/templates/module-presets/files/packages/files/src/files.service.ts +81 -395
- package/templates/module-presets/files/packages/files/src/forgeon-files.module.ts +126 -2
- package/templates/module-presets/files/packages/files/src/index.ts +2 -1
- package/templates/module-presets/files-local/packages/files-local/src/forgeon-files-local-storage.module.ts +18 -0
- package/templates/module-presets/files-local/packages/files-local/src/index.ts +2 -0
- package/templates/module-presets/files-local/packages/files-local/src/local-files-storage.adapter.ts +53 -0
- package/templates/module-presets/files-quotas/packages/files-quotas/src/forgeon-files-quotas.module.ts +12 -4
- package/templates/module-presets/files-s3/packages/files-s3/src/forgeon-files-s3-storage.module.ts +18 -0
- package/templates/module-presets/files-s3/packages/files-s3/src/index.ts +2 -0
- package/templates/module-presets/files-s3/packages/files-s3/src/s3-files-storage.adapter.ts +130 -0
- package/templates/module-presets/i18n/apps/web/src/App.tsx +68 -41
- package/templates/module-presets/logger/packages/logger/src/index.ts +0 -1
- package/src/modules/jwt-auth.mjs +0 -390
- package/templates/base/docs/AI/ARCHITECTURE.md +0 -85
- package/templates/base/docs/AI/MODULE_CHECKS.md +0 -28
- package/templates/base/docs/AI/MODULE_SPEC.md +0 -77
- package/templates/base/docs/AI/PROJECT.md +0 -43
- package/templates/base/docs/AI/ROADMAP.md +0 -171
- package/templates/base/docs/AI/TASKS.md +0 -60
- package/templates/base/docs/AI/VALIDATION.md +0 -31
- package/templates/base/docs/README.md +0 -18
- package/templates/module-fragments/jwt-auth/20_scope.md +0 -19
- package/templates/module-fragments/jwt-auth/90_status_implemented.md +0 -8
- package/templates/module-fragments/jwt-auth/90_status_planned.md +0 -3
- package/templates/module-presets/jwt-auth/apps/api/prisma/migrations/0002_auth_refresh_token_hash/migration.sql +0 -3
- package/templates/module-presets/jwt-auth/apps/api/src/auth/prisma-auth-refresh-token.store.ts +0 -36
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth-refresh-token.store.ts +0 -23
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.controller.ts +0 -71
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.service.ts +0 -175
- package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.types.ts +0 -6
- package/templates/module-presets/jwt-auth/packages/auth-api/src/dto/index.ts +0 -2
- package/templates/module-presets/jwt-auth/packages/auth-api/src/forgeon-auth.module.ts +0 -47
- package/templates/module-presets/jwt-auth/packages/auth-api/src/index.ts +0 -12
- package/templates/module-presets/jwt-auth/packages/auth-contracts/src/index.ts +0 -47
- package/templates/module-presets/logger/packages/logger/src/http-logging.interceptor.ts +0 -94
- /package/templates/module-presets/{jwt-auth/packages/auth-api/src/jwt-auth.guard.ts → accounts/packages/accounts-api/src/access-token.guard.ts} +0 -0
- /package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/auth-config.module.ts +0 -0
- /package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/tsconfig.json +0 -0
- /package/templates/module-presets/{jwt-auth/packages/auth-contracts → accounts/packages/accounts-contracts}/tsconfig.json +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
1
|
+
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { copyRecursive, writeJson } from '../utils/fs.mjs';
|
|
4
4
|
import {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
ensureValidatorSchema,
|
|
15
15
|
upsertEnvLines,
|
|
16
16
|
} from './shared/patch-utils.mjs';
|
|
17
|
+
import { ensureWebProbeDefinition, resolveProbeTargets } from './shared/probes.mjs';
|
|
17
18
|
|
|
18
19
|
function copyFromPreset(packageRoot, targetRoot, relativePath) {
|
|
19
20
|
const source = path.join(packageRoot, 'templates', 'module-presets', 'files-quotas', relativePath);
|
|
@@ -49,7 +50,7 @@ function patchFilesPackage(targetRoot) {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
52
|
-
ensureDependency(packageJson, '@
|
|
53
|
+
ensureDependency(packageJson, '@nestjs/core', '^11.0.1');
|
|
53
54
|
writeJson(packagePath, packageJson);
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -74,10 +75,10 @@ function patchAppModule(targetRoot) {
|
|
|
74
75
|
content = ensureLineAfter(content, ' ForgeonFilesModule,', ' ForgeonFilesQuotasModule,');
|
|
75
76
|
} else if (content.includes(' ForgeonI18nModule.register({')) {
|
|
76
77
|
content = ensureLineBefore(content, ' ForgeonI18nModule.register({', ' ForgeonFilesQuotasModule,');
|
|
77
|
-
} else if (content.includes('
|
|
78
|
-
content = ensureLineBefore(content, '
|
|
79
|
-
} else if (content.includes('
|
|
80
|
-
content = ensureLineBefore(content, '
|
|
78
|
+
} else if (content.includes(' ForgeonAccountsModule.register({')) {
|
|
79
|
+
content = ensureLineBefore(content, ' ForgeonAccountsModule.register({', ' ForgeonFilesQuotasModule,');
|
|
80
|
+
} else if (content.includes(' ForgeonAccountsModule.register(),')) {
|
|
81
|
+
content = ensureLineBefore(content, ' ForgeonAccountsModule.register(),', ' ForgeonFilesQuotasModule,');
|
|
81
82
|
} else if (content.includes(' DbPrismaModule,')) {
|
|
82
83
|
content = ensureLineAfter(content, ' DbPrismaModule,', ' ForgeonFilesQuotasModule,');
|
|
83
84
|
} else if (content.includes(' ForgeonLoggerModule,')) {
|
|
@@ -99,9 +100,24 @@ function patchFilesController(targetRoot) {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
102
|
-
content = ensureImportLine(content, "import {
|
|
103
|
+
content = ensureImportLine(content, "import { ModuleRef } from '@nestjs/core';");
|
|
103
104
|
|
|
104
|
-
if (!content.includes(
|
|
105
|
+
if (!content.includes("const FORGEON_FILES_UPLOAD_QUOTA_SERVICE = 'FORGEON_FILES_UPLOAD_QUOTA_SERVICE';")) {
|
|
106
|
+
content = content.replace(
|
|
107
|
+
'};\n\n@Controller(\'files\')',
|
|
108
|
+
`};
|
|
109
|
+
|
|
110
|
+
const FORGEON_FILES_UPLOAD_QUOTA_SERVICE = 'FORGEON_FILES_UPLOAD_QUOTA_SERVICE';
|
|
111
|
+
|
|
112
|
+
type FilesUploadQuotaService = {
|
|
113
|
+
assertUploadAllowed(input: { ownerType: string; ownerId: string | null; fileSize: number }): Promise<void>;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
@Controller('files')`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!content.includes('private readonly moduleRef: ModuleRef')) {
|
|
105
121
|
const constructorMatch = content.match(/constructor\(([\s\S]*?)\)\s*\{/m);
|
|
106
122
|
if (constructorMatch) {
|
|
107
123
|
const original = constructorMatch[0];
|
|
@@ -109,29 +125,51 @@ function patchFilesController(targetRoot) {
|
|
|
109
125
|
const normalizedInner = inner.replace(/,\s*$/, '');
|
|
110
126
|
const separator = normalizedInner.length > 0 ? ',' : '';
|
|
111
127
|
const next = `constructor(${normalizedInner}${separator}
|
|
112
|
-
private readonly
|
|
128
|
+
private readonly moduleRef: ModuleRef,
|
|
113
129
|
) {`;
|
|
114
130
|
content = content.replace(original, next);
|
|
115
131
|
}
|
|
116
132
|
}
|
|
117
133
|
|
|
118
|
-
if (!content.includes('filesQuotasService.
|
|
134
|
+
if (!content.includes('const filesQuotasService = this.getFilesUploadQuotaService();')) {
|
|
119
135
|
content = content.replace(
|
|
120
136
|
' return this.filesService.create({',
|
|
121
|
-
`
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
` const filesQuotasService = this.getFilesUploadQuotaService();
|
|
138
|
+
if (filesQuotasService) {
|
|
139
|
+
await filesQuotasService.assertUploadAllowed({
|
|
140
|
+
ownerType: body.ownerType ?? 'system',
|
|
141
|
+
ownerId: body.ownerId ?? null,
|
|
142
|
+
fileSize: file.size,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
126
145
|
|
|
127
146
|
return this.filesService.create({`,
|
|
128
147
|
);
|
|
129
148
|
}
|
|
130
149
|
|
|
150
|
+
if (!content.includes('private getFilesUploadQuotaService(): FilesUploadQuotaService | null {')) {
|
|
151
|
+
content = content.replace(
|
|
152
|
+
' private parseVariant(variantQuery?: string): FileVariantKey {',
|
|
153
|
+
` private getFilesUploadQuotaService(): FilesUploadQuotaService | null {
|
|
154
|
+
try {
|
|
155
|
+
return this.moduleRef.get(FORGEON_FILES_UPLOAD_QUOTA_SERVICE, { strict: false });
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private parseVariant(variantQuery?: string): FileVariantKey {`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
131
165
|
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
132
166
|
}
|
|
133
167
|
|
|
134
|
-
function patchHealthController(targetRoot) {
|
|
168
|
+
function patchHealthController(targetRoot, probeTargets) {
|
|
169
|
+
if (!probeTargets.allowApi) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
135
173
|
const filePath = path.join(targetRoot, 'apps', 'api', 'src', 'health', 'health.controller.ts');
|
|
136
174
|
if (!fs.existsSync(filePath)) {
|
|
137
175
|
return;
|
|
@@ -177,79 +215,18 @@ function patchHealthController(targetRoot) {
|
|
|
177
215
|
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
178
216
|
}
|
|
179
217
|
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (!content.includes('filesQuotasProbeResult')) {
|
|
194
|
-
const stateAnchors = [
|
|
195
|
-
' const [filesAccessProbeResult, setFilesAccessProbeResult] = useState<ProbeResult | null>(null);',
|
|
196
|
-
' const [filesProbeResult, setFilesProbeResult] = useState<ProbeResult | null>(null);',
|
|
197
|
-
' const [rbacProbeResult, setRbacProbeResult] = useState<ProbeResult | null>(null);',
|
|
198
|
-
' const [rateLimitProbeResult, setRateLimitProbeResult] = useState<ProbeResult | null>(null);',
|
|
199
|
-
' const [dbProbeResult, setDbProbeResult] = useState<ProbeResult | null>(null);',
|
|
200
|
-
' const [authProbeResult, setAuthProbeResult] = useState<ProbeResult | null>(null);',
|
|
201
|
-
' const [validationProbeResult, setValidationProbeResult] = useState<ProbeResult | null>(null);',
|
|
202
|
-
];
|
|
203
|
-
const stateAnchor = stateAnchors.find((line) => content.includes(line));
|
|
204
|
-
if (stateAnchor) {
|
|
205
|
-
content = ensureLineAfter(
|
|
206
|
-
content,
|
|
207
|
-
stateAnchor,
|
|
208
|
-
' const [filesQuotasProbeResult, setFilesQuotasProbeResult] = useState<ProbeResult | null>(null);',
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (!content.includes('Check files quotas')) {
|
|
214
|
-
const probePath = content.includes("runProbe(setHealthResult, '/health')")
|
|
215
|
-
? '/health/files-quotas'
|
|
216
|
-
: '/api/health/files-quotas';
|
|
217
|
-
const button = ` <button onClick={() => runProbe(setFilesQuotasProbeResult, '${probePath}')}>
|
|
218
|
-
Check files quotas
|
|
219
|
-
</button>`;
|
|
220
|
-
|
|
221
|
-
const actionsStart = content.indexOf('<div className="actions">');
|
|
222
|
-
if (actionsStart >= 0) {
|
|
223
|
-
const actionsEnd = content.indexOf('\n </div>', actionsStart);
|
|
224
|
-
if (actionsEnd >= 0) {
|
|
225
|
-
content = `${content.slice(0, actionsEnd)}\n${button}${content.slice(actionsEnd)}`;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (!content.includes("{renderResult('Files quotas probe response', filesQuotasProbeResult)}")) {
|
|
231
|
-
const resultLine = " {renderResult('Files quotas probe response', filesQuotasProbeResult)}";
|
|
232
|
-
const networkLine = ' {networkError ? <p className="error">{networkError}</p> : null}';
|
|
233
|
-
if (content.includes(networkLine)) {
|
|
234
|
-
content = content.replace(networkLine, `${resultLine}\n${networkLine}`);
|
|
235
|
-
} else {
|
|
236
|
-
const anchors = [
|
|
237
|
-
" {renderResult('Files access probe response', filesAccessProbeResult)}",
|
|
238
|
-
" {renderResult('Files probe response', filesProbeResult)}",
|
|
239
|
-
" {renderResult('RBAC probe response', rbacProbeResult)}",
|
|
240
|
-
" {renderResult('Rate limit probe response', rateLimitProbeResult)}",
|
|
241
|
-
" {renderResult('Auth probe response', authProbeResult)}",
|
|
242
|
-
" {renderResult('DB probe response', dbProbeResult)}",
|
|
243
|
-
" {renderResult('Validation probe response', validationProbeResult)}",
|
|
244
|
-
];
|
|
245
|
-
const anchor = anchors.find((line) => content.includes(line));
|
|
246
|
-
if (anchor) {
|
|
247
|
-
content = ensureLineAfter(content, anchor, resultLine);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
218
|
+
function registerWebProbe(targetRoot, probeTargets) {
|
|
219
|
+
ensureWebProbeDefinition({
|
|
220
|
+
targetRoot,
|
|
221
|
+
probeTargets,
|
|
222
|
+
definition: {
|
|
223
|
+
id: 'files-quotas',
|
|
224
|
+
title: 'Files Quotas',
|
|
225
|
+
buttonLabel: 'Check files quotas',
|
|
226
|
+
resultTitle: 'Files quotas probe response',
|
|
227
|
+
path: '/health/files-quotas',
|
|
228
|
+
},
|
|
229
|
+
});
|
|
253
230
|
}
|
|
254
231
|
|
|
255
232
|
function patchApiDockerfile(targetRoot) {
|
|
@@ -264,7 +241,7 @@ function patchApiDockerfile(targetRoot) {
|
|
|
264
241
|
'COPY packages/files/package.json packages/files/package.json',
|
|
265
242
|
'COPY packages/files-local/package.json packages/files-local/package.json',
|
|
266
243
|
'COPY packages/files-s3/package.json packages/files-s3/package.json',
|
|
267
|
-
'COPY packages/
|
|
244
|
+
'COPY packages/accounts-api/package.json packages/accounts-api/package.json',
|
|
268
245
|
'COPY packages/rbac/package.json packages/rbac/package.json',
|
|
269
246
|
'COPY packages/rate-limit/package.json packages/rate-limit/package.json',
|
|
270
247
|
'COPY packages/logger/package.json packages/logger/package.json',
|
|
@@ -285,7 +262,7 @@ function patchApiDockerfile(targetRoot) {
|
|
|
285
262
|
'COPY packages/files packages/files',
|
|
286
263
|
'COPY packages/files-local packages/files-local',
|
|
287
264
|
'COPY packages/files-s3 packages/files-s3',
|
|
288
|
-
'COPY packages/
|
|
265
|
+
'COPY packages/accounts-api packages/accounts-api',
|
|
289
266
|
'COPY packages/rbac packages/rbac',
|
|
290
267
|
'COPY packages/rate-limit packages/rate-limit',
|
|
291
268
|
'COPY packages/logger packages/logger',
|
|
@@ -379,12 +356,14 @@ Key env:
|
|
|
379
356
|
|
|
380
357
|
export function applyFilesQuotasModule({ packageRoot, targetRoot }) {
|
|
381
358
|
copyFromPreset(packageRoot, targetRoot, path.join('packages', 'files-quotas'));
|
|
359
|
+
const probeTargets = resolveProbeTargets({ targetRoot, moduleId: 'files-quotas' });
|
|
360
|
+
|
|
382
361
|
patchApiPackage(targetRoot);
|
|
383
362
|
patchFilesPackage(targetRoot);
|
|
384
363
|
patchAppModule(targetRoot);
|
|
385
364
|
patchFilesController(targetRoot);
|
|
386
|
-
patchHealthController(targetRoot);
|
|
387
|
-
|
|
365
|
+
patchHealthController(targetRoot, probeTargets);
|
|
366
|
+
registerWebProbe(targetRoot, probeTargets);
|
|
388
367
|
patchApiDockerfile(targetRoot);
|
|
389
368
|
patchCompose(targetRoot);
|
|
390
369
|
patchReadme(targetRoot);
|
|
@@ -400,3 +379,6 @@ export function applyFilesQuotasModule({ packageRoot, targetRoot }) {
|
|
|
400
379
|
'FILES_QUOTA_MAX_BYTES_PER_OWNER=104857600',
|
|
401
380
|
]);
|
|
402
381
|
}
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
package/src/modules/files-s3.mjs
CHANGED
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
ensureLineBefore,
|
|
10
10
|
ensureLoadItem,
|
|
11
11
|
ensureValidatorSchema,
|
|
12
|
+
ensureNamedImportSpecifier,
|
|
12
13
|
upsertEnvLines,
|
|
13
14
|
} from './shared/patch-utils.mjs';
|
|
15
|
+
import { resolveFilesStorageRuntimeModule, upsertFilesModuleRegistration } from './shared/files-runtime-wiring.mjs';
|
|
14
16
|
|
|
15
17
|
function copyFromPreset(packageRoot, targetRoot, relativePath) {
|
|
16
18
|
const source = path.join(packageRoot, 'templates', 'module-presets', 'files-s3', relativePath);
|
|
@@ -40,16 +42,18 @@ function patchAppModule(targetRoot) {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
43
|
-
content =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
);
|
|
45
|
+
content = ensureNamedImportSpecifier(content, '@forgeon/files-s3', 'filesS3Config');
|
|
46
|
+
content = ensureNamedImportSpecifier(content, '@forgeon/files-s3', 'filesS3EnvSchemaZod');
|
|
47
|
+
content = ensureNamedImportSpecifier(content, '@forgeon/files-s3', 'FilesS3ConfigModule');
|
|
48
|
+
content = ensureNamedImportSpecifier(content, '@forgeon/files-s3', 'ForgeonFilesS3StorageModule');
|
|
47
49
|
content = ensureLoadItem(content, 'filesS3Config');
|
|
48
50
|
content = ensureValidatorSchema(content, 'filesS3EnvSchemaZod');
|
|
49
51
|
|
|
50
52
|
if (!content.includes(' FilesS3ConfigModule,')) {
|
|
51
53
|
if (content.includes(' ForgeonFilesModule,')) {
|
|
52
54
|
content = ensureLineAfter(content, ' ForgeonFilesModule,', ' FilesS3ConfigModule,');
|
|
55
|
+
} else if (content.includes(' ForgeonFilesModule.register({')) {
|
|
56
|
+
content = ensureLineBefore(content, ' ForgeonFilesModule.register({', ' FilesS3ConfigModule,');
|
|
53
57
|
} else if (content.includes(' ForgeonI18nModule.register({')) {
|
|
54
58
|
content = ensureLineBefore(content, ' ForgeonI18nModule.register({', ' FilesS3ConfigModule,');
|
|
55
59
|
} else if (content.includes(' DbPrismaModule,')) {
|
|
@@ -61,6 +65,10 @@ function patchAppModule(targetRoot) {
|
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
if (content.includes('ForgeonFilesModule')) {
|
|
69
|
+
content = upsertFilesModuleRegistration(content, resolveFilesStorageRuntimeModule(targetRoot));
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
65
73
|
}
|
|
66
74
|
|
|
@@ -75,7 +83,7 @@ function patchApiDockerfile(targetRoot) {
|
|
|
75
83
|
const packageAnchors = [
|
|
76
84
|
'COPY packages/files/package.json packages/files/package.json',
|
|
77
85
|
'COPY packages/files-local/package.json packages/files-local/package.json',
|
|
78
|
-
'COPY packages/
|
|
86
|
+
'COPY packages/accounts-api/package.json packages/accounts-api/package.json',
|
|
79
87
|
'COPY packages/rbac/package.json packages/rbac/package.json',
|
|
80
88
|
'COPY packages/rate-limit/package.json packages/rate-limit/package.json',
|
|
81
89
|
'COPY packages/logger/package.json packages/logger/package.json',
|
|
@@ -94,7 +102,7 @@ function patchApiDockerfile(targetRoot) {
|
|
|
94
102
|
const sourceAnchors = [
|
|
95
103
|
'COPY packages/files packages/files',
|
|
96
104
|
'COPY packages/files-local packages/files-local',
|
|
97
|
-
'COPY packages/
|
|
105
|
+
'COPY packages/accounts-api packages/accounts-api',
|
|
98
106
|
'COPY packages/rbac packages/rbac',
|
|
99
107
|
'COPY packages/rate-limit packages/rate-limit',
|
|
100
108
|
'COPY packages/logger packages/logger',
|
|
@@ -263,4 +271,7 @@ export function applyFilesS3Module({ packageRoot, targetRoot }) {
|
|
|
263
271
|
|
|
264
272
|
setEnvValue(path.join(targetRoot, 'apps', 'api', '.env.example'), 'FILES_STORAGE_DRIVER', 's3');
|
|
265
273
|
setEnvValue(path.join(targetRoot, 'infra', 'docker', '.env.example'), 'FILES_STORAGE_DRIVER', 's3');
|
|
274
|
+
|
|
275
|
+
patchAppModule(targetRoot);
|
|
266
276
|
}
|
|
277
|
+
|
package/src/modules/files.mjs
CHANGED
|
@@ -11,8 +11,11 @@ import {
|
|
|
11
11
|
ensureLoadItem,
|
|
12
12
|
ensureNestCommonImport,
|
|
13
13
|
ensureValidatorSchema,
|
|
14
|
+
ensureNamedImportSpecifier,
|
|
14
15
|
upsertEnvLines,
|
|
15
16
|
} from './shared/patch-utils.mjs';
|
|
17
|
+
import { ensureWebProbeDefinition, resolveProbeTargets } from './shared/probes.mjs';
|
|
18
|
+
import { resolveFilesStorageRuntimeModule, upsertFilesModuleRegistration } from './shared/files-runtime-wiring.mjs';
|
|
16
19
|
|
|
17
20
|
function copyFromPreset(packageRoot, targetRoot, relativePath) {
|
|
18
21
|
const source = path.join(packageRoot, 'templates', 'module-presets', 'files', relativePath);
|
|
@@ -167,27 +170,22 @@ function patchAppModule(targetRoot) {
|
|
|
167
170
|
content,
|
|
168
171
|
"import { filesConfig, filesEnvSchema, ForgeonFilesModule } from '@forgeon/files';",
|
|
169
172
|
);
|
|
170
|
-
content =
|
|
171
|
-
|
|
173
|
+
content = ensureImportLine(
|
|
174
|
+
content,
|
|
175
|
+
"import { ForgeonFilesDbPrismaModule } from './files/forgeon-files-db-prisma.module';",
|
|
176
|
+
);
|
|
172
177
|
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
} else if (content.includes(' ForgeonAuthModule.register(),')) {
|
|
179
|
-
content = ensureLineBefore(content, ' ForgeonAuthModule.register(),', ' ForgeonFilesModule,');
|
|
180
|
-
} else if (content.includes(' DbPrismaModule,')) {
|
|
181
|
-
content = ensureLineAfter(content, ' DbPrismaModule,', ' ForgeonFilesModule,');
|
|
182
|
-
} else if (content.includes(' ForgeonLoggerModule,')) {
|
|
183
|
-
content = ensureLineAfter(content, ' ForgeonLoggerModule,', ' ForgeonFilesModule,');
|
|
184
|
-
} else if (content.includes(' ForgeonSwaggerModule,')) {
|
|
185
|
-
content = ensureLineAfter(content, ' ForgeonSwaggerModule,', ' ForgeonFilesModule,');
|
|
186
|
-
} else {
|
|
187
|
-
content = ensureLineAfter(content, ' CoreErrorsModule,', ' ForgeonFilesModule,');
|
|
188
|
-
}
|
|
178
|
+
if (fs.existsSync(path.join(targetRoot, 'packages', 'files-local', 'package.json'))) {
|
|
179
|
+
content = ensureNamedImportSpecifier(content, '@forgeon/files-local', 'ForgeonFilesLocalStorageModule');
|
|
180
|
+
}
|
|
181
|
+
if (fs.existsSync(path.join(targetRoot, 'packages', 'files-s3', 'package.json'))) {
|
|
182
|
+
content = ensureNamedImportSpecifier(content, '@forgeon/files-s3', 'ForgeonFilesS3StorageModule');
|
|
189
183
|
}
|
|
190
184
|
|
|
185
|
+
content = ensureLoadItem(content, 'filesConfig');
|
|
186
|
+
content = ensureValidatorSchema(content, 'filesEnvSchema');
|
|
187
|
+
content = upsertFilesModuleRegistration(content, resolveFilesStorageRuntimeModule(targetRoot));
|
|
188
|
+
|
|
191
189
|
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
192
190
|
}
|
|
193
191
|
|
|
@@ -200,7 +198,7 @@ function patchApiDockerfile(targetRoot) {
|
|
|
200
198
|
let content = fs.readFileSync(dockerfilePath, 'utf8').replace(/\r\n/g, '\n');
|
|
201
199
|
|
|
202
200
|
const packageAnchors = [
|
|
203
|
-
'COPY packages/
|
|
201
|
+
'COPY packages/accounts-api/package.json packages/accounts-api/package.json',
|
|
204
202
|
'COPY packages/rbac/package.json packages/rbac/package.json',
|
|
205
203
|
'COPY packages/rate-limit/package.json packages/rate-limit/package.json',
|
|
206
204
|
'COPY packages/logger/package.json packages/logger/package.json',
|
|
@@ -213,7 +211,7 @@ function patchApiDockerfile(targetRoot) {
|
|
|
213
211
|
content = ensureLineAfter(content, packageAnchor, 'COPY packages/files/package.json packages/files/package.json');
|
|
214
212
|
|
|
215
213
|
const sourceAnchors = [
|
|
216
|
-
'COPY packages/
|
|
214
|
+
'COPY packages/accounts-api packages/accounts-api',
|
|
217
215
|
'COPY packages/rbac packages/rbac',
|
|
218
216
|
'COPY packages/rate-limit packages/rate-limit',
|
|
219
217
|
'COPY packages/logger packages/logger',
|
|
@@ -234,7 +232,11 @@ function patchApiDockerfile(targetRoot) {
|
|
|
234
232
|
fs.writeFileSync(dockerfilePath, `${content.trimEnd()}\n`, 'utf8');
|
|
235
233
|
}
|
|
236
234
|
|
|
237
|
-
function patchHealthController(targetRoot) {
|
|
235
|
+
function patchHealthController(targetRoot, probeTargets) {
|
|
236
|
+
if (!probeTargets.allowApi) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
238
240
|
const filePath = path.join(targetRoot, 'apps', 'api', 'src', 'health', 'health.controller.ts');
|
|
239
241
|
if (!fs.existsSync(filePath)) {
|
|
240
242
|
return;
|
|
@@ -303,107 +305,31 @@ function patchHealthController(targetRoot) {
|
|
|
303
305
|
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
304
306
|
}
|
|
305
307
|
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (!content.includes('filesVariantsProbeResult')) {
|
|
336
|
-
const stateAnchors = [
|
|
337
|
-
' const [filesProbeResult, setFilesProbeResult] = useState<ProbeResult | null>(null);',
|
|
338
|
-
' const [validationProbeResult, setValidationProbeResult] = useState<ProbeResult | null>(null);',
|
|
339
|
-
];
|
|
340
|
-
const stateAnchor = stateAnchors.find((line) => content.includes(line));
|
|
341
|
-
if (stateAnchor) {
|
|
342
|
-
content = ensureLineAfter(
|
|
343
|
-
content,
|
|
344
|
-
stateAnchor,
|
|
345
|
-
' const [filesVariantsProbeResult, setFilesVariantsProbeResult] = useState<ProbeResult | null>(null);',
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (!content.includes('Check files probe (create metadata)')) {
|
|
351
|
-
const probePath = content.includes("runProbe(setHealthResult, '/health')")
|
|
352
|
-
? '/health/files'
|
|
353
|
-
: '/api/health/files';
|
|
354
|
-
const button = ` <button onClick={() => runProbe(setFilesProbeResult, '${probePath}', { method: 'POST' })}>\n Check files probe (create metadata)\n </button>`;
|
|
355
|
-
|
|
356
|
-
const actionsStart = content.indexOf('<div className="actions">');
|
|
357
|
-
if (actionsStart >= 0) {
|
|
358
|
-
const actionsEnd = content.indexOf('\n </div>', actionsStart);
|
|
359
|
-
if (actionsEnd >= 0) {
|
|
360
|
-
content = `${content.slice(0, actionsEnd)}\n${button}${content.slice(actionsEnd)}`;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (!content.includes("{renderResult('Files probe response', filesProbeResult)}")) {
|
|
366
|
-
const resultLine = " {renderResult('Files probe response', filesProbeResult)}";
|
|
367
|
-
const networkLine = ' {networkError ? <p className="error">{networkError}</p> : null}';
|
|
368
|
-
if (content.includes(networkLine)) {
|
|
369
|
-
content = content.replace(networkLine, `${resultLine}\n${networkLine}`);
|
|
370
|
-
} else {
|
|
371
|
-
const anchor = "{renderResult('Validation probe response', validationProbeResult)}";
|
|
372
|
-
if (content.includes(anchor)) {
|
|
373
|
-
content = ensureLineAfter(content, anchor, resultLine);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (!content.includes('Check files variants capability')) {
|
|
379
|
-
const probePath = content.includes("runProbe(setHealthResult, '/health')")
|
|
380
|
-
? '/health/files-variants'
|
|
381
|
-
: '/api/health/files-variants';
|
|
382
|
-
const button = ` <button onClick={() => runProbe(setFilesVariantsProbeResult, '${probePath}')}>\n Check files variants capability\n </button>`;
|
|
383
|
-
|
|
384
|
-
const actionsStart = content.indexOf('<div className="actions">');
|
|
385
|
-
if (actionsStart >= 0) {
|
|
386
|
-
const actionsEnd = content.indexOf('\n </div>', actionsStart);
|
|
387
|
-
if (actionsEnd >= 0) {
|
|
388
|
-
content = `${content.slice(0, actionsEnd)}\n${button}${content.slice(actionsEnd)}`;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (!content.includes("{renderResult('Files variants probe response', filesVariantsProbeResult)}")) {
|
|
394
|
-
const resultLine = " {renderResult('Files variants probe response', filesVariantsProbeResult)}";
|
|
395
|
-
const networkLine = ' {networkError ? <p className="error">{networkError}</p> : null}';
|
|
396
|
-
if (content.includes(networkLine)) {
|
|
397
|
-
content = content.replace(networkLine, `${resultLine}\n${networkLine}`);
|
|
398
|
-
} else {
|
|
399
|
-
const anchor = "{renderResult('Files probe response', filesProbeResult)}";
|
|
400
|
-
if (content.includes(anchor)) {
|
|
401
|
-
content = ensureLineAfter(content, anchor, resultLine);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
fs.writeFileSync(filePath, `${content.trimEnd()}\n`, 'utf8');
|
|
308
|
+
function registerWebProbe(targetRoot, probeTargets) {
|
|
309
|
+
ensureWebProbeDefinition({
|
|
310
|
+
targetRoot,
|
|
311
|
+
probeTargets,
|
|
312
|
+
definition: {
|
|
313
|
+
id: 'files',
|
|
314
|
+
title: 'Files',
|
|
315
|
+
buttonLabel: 'Check files probe (create metadata)',
|
|
316
|
+
resultTitle: 'Files probe response',
|
|
317
|
+
path: '/health/files',
|
|
318
|
+
request: { method: 'POST' },
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
ensureWebProbeDefinition({
|
|
323
|
+
targetRoot,
|
|
324
|
+
probeTargets,
|
|
325
|
+
definition: {
|
|
326
|
+
id: 'files-variants',
|
|
327
|
+
title: 'Files Variants',
|
|
328
|
+
buttonLabel: 'Check files variants capability',
|
|
329
|
+
resultTitle: 'Files variants probe response',
|
|
330
|
+
path: '/health/files-variants',
|
|
331
|
+
},
|
|
332
|
+
});
|
|
407
333
|
}
|
|
408
334
|
|
|
409
335
|
function patchCompose(targetRoot) {
|
|
@@ -415,7 +341,7 @@ function patchCompose(targetRoot) {
|
|
|
415
341
|
let content = fs.readFileSync(composePath, 'utf8').replace(/\r\n/g, '\n');
|
|
416
342
|
if (!content.includes('FILES_ENABLED: ${FILES_ENABLED}')) {
|
|
417
343
|
const anchors = [
|
|
418
|
-
/^(\s+
|
|
344
|
+
/^(\s+AUTH_ARGON2_PARALLELISM:.*)$/m,
|
|
419
345
|
/^(\s+THROTTLE_TRUST_PROXY:.*)$/m,
|
|
420
346
|
/^(\s+LOGGER_LEVEL:.*)$/m,
|
|
421
347
|
/^(\s+SWAGGER_ENABLED:.*)$/m,
|
|
@@ -499,13 +425,16 @@ Key env:
|
|
|
499
425
|
|
|
500
426
|
export function applyFilesModule({ packageRoot, targetRoot }) {
|
|
501
427
|
copyFromPreset(packageRoot, targetRoot, path.join('packages', 'files'));
|
|
428
|
+
copyFromPreset(packageRoot, targetRoot, path.join('apps', 'api', 'src', 'files'));
|
|
429
|
+
const probeTargets = resolveProbeTargets({ targetRoot, moduleId: 'files' });
|
|
430
|
+
|
|
502
431
|
|
|
503
432
|
patchApiPackage(targetRoot);
|
|
504
433
|
patchPrismaSchema(targetRoot);
|
|
505
434
|
patchPrismaMigration(packageRoot, targetRoot);
|
|
506
435
|
patchAppModule(targetRoot);
|
|
507
|
-
patchHealthController(targetRoot);
|
|
508
|
-
|
|
436
|
+
patchHealthController(targetRoot, probeTargets);
|
|
437
|
+
registerWebProbe(targetRoot, probeTargets);
|
|
509
438
|
patchApiDockerfile(targetRoot);
|
|
510
439
|
patchCompose(targetRoot);
|
|
511
440
|
patchReadme(targetRoot);
|
|
@@ -525,3 +454,5 @@ export function applyFilesModule({ packageRoot, targetRoot }) {
|
|
|
525
454
|
'FILES_ALLOWED_MIME_PREFIXES=image/,application/pdf,text/',
|
|
526
455
|
]);
|
|
527
456
|
}
|
|
457
|
+
|
|
458
|
+
|