create-forgeon 0.3.19 → 0.3.21

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.
Files changed (38) hide show
  1. package/package.json +1 -1
  2. package/src/cli/add-help.mjs +3 -2
  3. package/src/core/docs.test.mjs +1 -0
  4. package/src/core/scaffold.test.mjs +1 -0
  5. package/src/modules/accounts.mjs +9 -18
  6. package/src/modules/dependencies.mjs +153 -4
  7. package/src/modules/dependencies.test.mjs +58 -0
  8. package/src/modules/executor.test.mjs +544 -515
  9. package/src/modules/files-access.mjs +375 -375
  10. package/src/modules/files-image.mjs +512 -510
  11. package/src/modules/files-quotas.mjs +365 -365
  12. package/src/modules/files.mjs +5 -6
  13. package/src/modules/idempotency.test.mjs +3 -2
  14. package/src/modules/registry.mjs +20 -0
  15. package/src/modules/shared/files-runtime-wiring.mjs +13 -10
  16. package/src/run-add-module.mjs +39 -26
  17. package/src/run-add-module.test.mjs +228 -152
  18. package/src/run-scan-integrations.mjs +1 -0
  19. package/templates/base/package.json +1 -0
  20. package/templates/module-presets/accounts/packages/accounts-api/package.json +1 -0
  21. package/templates/module-presets/accounts/packages/accounts-api/src/auth-core.service.ts +15 -19
  22. package/templates/module-presets/accounts/{apps/api/src/accounts/prisma-accounts-persistence.store.ts → packages/accounts-api/src/auth.store.ts} +44 -166
  23. package/templates/module-presets/accounts/packages/accounts-api/src/forgeon-accounts.module.ts +7 -1
  24. package/templates/module-presets/accounts/packages/accounts-api/src/index.ts +3 -4
  25. package/templates/module-presets/accounts/packages/accounts-api/src/users.service.ts +10 -11
  26. package/templates/module-presets/accounts/packages/accounts-api/src/users.store.ts +113 -0
  27. package/templates/module-presets/accounts/packages/accounts-api/src/users.types.ts +48 -0
  28. package/templates/module-presets/files/packages/files/package.json +1 -0
  29. package/templates/module-presets/files/packages/files/src/files.ports.ts +0 -95
  30. package/templates/module-presets/files/packages/files/src/files.service.ts +43 -36
  31. package/templates/module-presets/files/{apps/api/src/files/prisma-files-persistence.store.ts → packages/files/src/files.store.ts} +77 -13
  32. package/templates/module-presets/files/packages/files/src/forgeon-files.module.ts +7 -116
  33. package/templates/module-presets/files/packages/files/src/index.ts +1 -0
  34. package/templates/module-presets/files-quotas/packages/files-quotas/package.json +20 -20
  35. package/templates/module-presets/files-quotas/packages/files-quotas/src/files-quotas.service.ts +118 -118
  36. package/templates/module-presets/files-quotas/packages/files-quotas/src/forgeon-files-quotas.module.ts +18 -18
  37. package/templates/module-presets/accounts/packages/accounts-api/src/accounts-persistence.port.ts +0 -67
  38. package/templates/module-presets/files/apps/api/src/files/forgeon-files-db-prisma.module.ts +0 -17
@@ -1,118 +1,118 @@
1
- import { ConflictException, Injectable } from '@nestjs/common';
2
- import { FilesService } from '@forgeon/files';
3
- import { FilesQuotasConfigService } from './files-quotas-config.service';
4
- import type { FilesQuotaCheckInput, FilesQuotaCheckResult } from './files-quotas.types';
5
-
6
- @Injectable()
7
- export class FilesQuotasService {
8
- constructor(
9
- private readonly filesService: FilesService,
10
- private readonly configService: FilesQuotasConfigService,
11
- ) {}
12
-
13
- get enabled(): boolean {
14
- return this.configService.enabled;
15
- }
16
-
17
- get limits() {
18
- return {
19
- maxFilesPerOwner: this.configService.maxFilesPerOwner,
20
- maxBytesPerOwner: this.configService.maxBytesPerOwner,
21
- };
22
- }
23
-
24
- async evaluateUpload(input: FilesQuotaCheckInput): Promise<FilesQuotaCheckResult> {
25
- const limits = this.limits;
26
- const emptyUsage = {
27
- filesCount: 0,
28
- totalBytes: 0,
29
- };
30
- const baseResult = {
31
- limits,
32
- current: emptyUsage,
33
- next: {
34
- filesCount: emptyUsage.filesCount + 1,
35
- totalBytes: emptyUsage.totalBytes + input.fileSize,
36
- },
37
- };
38
-
39
- if (!this.enabled) {
40
- return {
41
- allowed: true,
42
- reason: 'disabled',
43
- ...baseResult,
44
- };
45
- }
46
-
47
- if (!input.ownerId) {
48
- return {
49
- allowed: true,
50
- reason: 'owner-missing',
51
- ...baseResult,
52
- };
53
- }
54
-
55
- const usage = await this.filesService.getOwnerUsage(input.ownerType, input.ownerId);
56
- const next = {
57
- filesCount: usage.filesCount + 1,
58
- totalBytes: usage.totalBytes + input.fileSize,
59
- };
60
-
61
- if (next.filesCount > limits.maxFilesPerOwner) {
62
- return {
63
- allowed: false,
64
- reason: 'max-files',
65
- limits,
66
- current: usage,
67
- next,
68
- };
69
- }
70
- if (next.totalBytes > limits.maxBytesPerOwner) {
71
- return {
72
- allowed: false,
73
- reason: 'max-bytes',
74
- limits,
75
- current: usage,
76
- next,
77
- };
78
- }
79
-
80
- return {
81
- allowed: true,
82
- reason: 'ok',
83
- limits,
84
- current: usage,
85
- next,
86
- };
87
- }
88
-
89
- async assertUploadAllowed(input: FilesQuotaCheckInput): Promise<void> {
90
- const result = await this.evaluateUpload(input);
91
- if (result.allowed) {
92
- return;
93
- }
94
-
95
- throw new ConflictException({
96
- message: 'File quota exceeded for owner',
97
- details: {
98
- reason: result.reason,
99
- limits: result.limits,
100
- current: result.current,
101
- next: result.next,
102
- },
103
- });
104
- }
105
-
106
- async getProbeStatus(input: FilesQuotaCheckInput): Promise<{
107
- status: 'ok';
108
- feature: 'files-quotas';
109
- result: FilesQuotaCheckResult;
110
- }> {
111
- const result = await this.evaluateUpload(input);
112
- return {
113
- status: 'ok',
114
- feature: 'files-quotas',
115
- result,
116
- };
117
- }
118
- }
1
+ import { ConflictException, Injectable } from '@nestjs/common';
2
+ import { FilesService } from '@forgeon/files';
3
+ import { FilesQuotasConfigService } from './files-quotas-config.service';
4
+ import type { FilesQuotaCheckInput, FilesQuotaCheckResult } from './files-quotas.types';
5
+
6
+ @Injectable()
7
+ export class FilesQuotasService {
8
+ constructor(
9
+ private readonly filesService: FilesService,
10
+ private readonly configService: FilesQuotasConfigService,
11
+ ) {}
12
+
13
+ get enabled(): boolean {
14
+ return this.configService.enabled;
15
+ }
16
+
17
+ get limits() {
18
+ return {
19
+ maxFilesPerOwner: this.configService.maxFilesPerOwner,
20
+ maxBytesPerOwner: this.configService.maxBytesPerOwner,
21
+ };
22
+ }
23
+
24
+ async evaluateUpload(input: FilesQuotaCheckInput): Promise<FilesQuotaCheckResult> {
25
+ const limits = this.limits;
26
+ const emptyUsage = {
27
+ filesCount: 0,
28
+ totalBytes: 0,
29
+ };
30
+ const baseResult = {
31
+ limits,
32
+ current: emptyUsage,
33
+ next: {
34
+ filesCount: emptyUsage.filesCount + 1,
35
+ totalBytes: emptyUsage.totalBytes + input.fileSize,
36
+ },
37
+ };
38
+
39
+ if (!this.enabled) {
40
+ return {
41
+ allowed: true,
42
+ reason: 'disabled',
43
+ ...baseResult,
44
+ };
45
+ }
46
+
47
+ if (!input.ownerId) {
48
+ return {
49
+ allowed: true,
50
+ reason: 'owner-missing',
51
+ ...baseResult,
52
+ };
53
+ }
54
+
55
+ const usage = await this.filesService.getOwnerUsage(input.ownerType, input.ownerId);
56
+ const next = {
57
+ filesCount: usage.filesCount + 1,
58
+ totalBytes: usage.totalBytes + input.fileSize,
59
+ };
60
+
61
+ if (next.filesCount > limits.maxFilesPerOwner) {
62
+ return {
63
+ allowed: false,
64
+ reason: 'max-files',
65
+ limits,
66
+ current: usage,
67
+ next,
68
+ };
69
+ }
70
+ if (next.totalBytes > limits.maxBytesPerOwner) {
71
+ return {
72
+ allowed: false,
73
+ reason: 'max-bytes',
74
+ limits,
75
+ current: usage,
76
+ next,
77
+ };
78
+ }
79
+
80
+ return {
81
+ allowed: true,
82
+ reason: 'ok',
83
+ limits,
84
+ current: usage,
85
+ next,
86
+ };
87
+ }
88
+
89
+ async assertUploadAllowed(input: FilesQuotaCheckInput): Promise<void> {
90
+ const result = await this.evaluateUpload(input);
91
+ if (result.allowed) {
92
+ return;
93
+ }
94
+
95
+ throw new ConflictException({
96
+ message: 'File quota exceeded for owner',
97
+ details: {
98
+ reason: result.reason,
99
+ limits: result.limits,
100
+ current: result.current,
101
+ next: result.next,
102
+ },
103
+ });
104
+ }
105
+
106
+ async getProbeStatus(input: FilesQuotaCheckInput): Promise<{
107
+ status: 'ok';
108
+ feature: 'files-quotas';
109
+ result: FilesQuotaCheckResult;
110
+ }> {
111
+ const result = await this.evaluateUpload(input);
112
+ return {
113
+ status: 'ok',
114
+ feature: 'files-quotas',
115
+ result,
116
+ };
117
+ }
118
+ }
@@ -1,19 +1,19 @@
1
- import { Module } from '@nestjs/common';
2
- import { ForgeonFilesModule } from '@forgeon/files';
3
- import { FilesQuotasConfigModule } from './files-quotas-config.module';
4
- import { FilesQuotasService } from './files-quotas.service';
5
-
6
- const FORGEON_FILES_UPLOAD_QUOTA_SERVICE = 'FORGEON_FILES_UPLOAD_QUOTA_SERVICE';
7
-
8
- @Module({
9
- imports: [ForgeonFilesModule, FilesQuotasConfigModule],
10
- providers: [
11
- FilesQuotasService,
12
- {
13
- provide: FORGEON_FILES_UPLOAD_QUOTA_SERVICE,
14
- useExisting: FilesQuotasService,
15
- },
16
- ],
17
- exports: [FilesQuotasConfigModule, FilesQuotasService, FORGEON_FILES_UPLOAD_QUOTA_SERVICE],
18
- })
1
+ import { Module } from '@nestjs/common';
2
+ import { ForgeonFilesModule } from '@forgeon/files';
3
+ import { FilesQuotasConfigModule } from './files-quotas-config.module';
4
+ import { FilesQuotasService } from './files-quotas.service';
5
+
6
+ const FORGEON_FILES_UPLOAD_QUOTA_SERVICE = 'FORGEON_FILES_UPLOAD_QUOTA_SERVICE';
7
+
8
+ @Module({
9
+ imports: [ForgeonFilesModule, FilesQuotasConfigModule],
10
+ providers: [
11
+ FilesQuotasService,
12
+ {
13
+ provide: FORGEON_FILES_UPLOAD_QUOTA_SERVICE,
14
+ useExisting: FilesQuotasService,
15
+ },
16
+ ],
17
+ exports: [FilesQuotasConfigModule, FilesQuotasService, FORGEON_FILES_UPLOAD_QUOTA_SERVICE],
18
+ })
19
19
  export class ForgeonFilesQuotasModule {}
@@ -1,67 +0,0 @@
1
- import type { IdentityProvider, JsonObject } from '@forgeon/accounts-contracts';
2
- import type { UserRecord } from './users.types';
3
-
4
- export const ACCOUNTS_PERSISTENCE_PORT = 'FORGEON_ACCOUNTS_PERSISTENCE_PORT';
5
-
6
- export type PasswordAccountRecord = UserRecord & {
7
- provider: IdentityProvider;
8
- providerId: string;
9
- passwordHash: string | null;
10
- };
11
-
12
- export type RefreshTokenRecord = {
13
- id: string;
14
- userId: string;
15
- tokenHash: string;
16
- expiresAt: Date;
17
- revokedAt: Date | null;
18
- createdAt: Date;
19
- };
20
-
21
- export interface CreatePasswordAccountInput {
22
- email: string;
23
- passwordHash: string;
24
- status: string;
25
- userData: JsonObject | null;
26
- profile: {
27
- name: string | null;
28
- avatar: string | null;
29
- data: JsonObject | null;
30
- };
31
- settings: {
32
- theme: string | null;
33
- locale: string | null;
34
- data: JsonObject | null;
35
- };
36
- }
37
-
38
- export interface AccountsPersistencePort {
39
- createPasswordAccount(input: CreatePasswordAccountInput): Promise<PasswordAccountRecord>;
40
- findPasswordAccountByEmail(email: string): Promise<PasswordAccountRecord | null>;
41
- findAccountByUserId(userId: string): Promise<PasswordAccountRecord | null>;
42
- updatePassword(userId: string, passwordHash: string): Promise<void>;
43
- createRefreshToken(input: {
44
- id: string;
45
- userId: string;
46
- tokenHash: string;
47
- expiresAt: Date;
48
- }): Promise<void>;
49
- findRefreshTokenById(id: string): Promise<RefreshTokenRecord | null>;
50
- revokeRefreshToken(id: string, revokedAt: Date): Promise<void>;
51
- revokeRefreshTokensForUser(userId: string, revokedAt: Date): Promise<void>;
52
- findUserById(userId: string): Promise<UserRecord | null>;
53
- updateUser(input: { userId: string; data: JsonObject | null }): Promise<UserRecord>;
54
- updateUserProfile(input: {
55
- userId: string;
56
- name: string | null;
57
- avatar: string | null;
58
- data: JsonObject | null;
59
- }): Promise<UserRecord>;
60
- updateUserSettings(input: {
61
- userId: string;
62
- theme: string | null;
63
- locale: string | null;
64
- data: JsonObject | null;
65
- }): Promise<UserRecord>;
66
- softDeleteUser(userId: string, deletedAt: Date): Promise<void>;
67
- }
@@ -1,17 +0,0 @@
1
- import { Module } from '@nestjs/common';
2
- import { FILES_PERSISTENCE_PORT } from '@forgeon/files';
3
- import { DbPrismaModule } from '@forgeon/db-prisma';
4
- import { PrismaFilesPersistenceStore } from './prisma-files-persistence.store';
5
-
6
- @Module({
7
- imports: [DbPrismaModule],
8
- providers: [
9
- PrismaFilesPersistenceStore,
10
- {
11
- provide: FILES_PERSISTENCE_PORT,
12
- useExisting: PrismaFilesPersistenceStore,
13
- },
14
- ],
15
- exports: [FILES_PERSISTENCE_PORT],
16
- })
17
- export class ForgeonFilesDbPrismaModule {}