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.
Files changed (129) hide show
  1. package/package.json +4 -2
  2. package/src/cli/add-options.test.mjs +5 -2
  3. package/src/cli/options.test.mjs +1 -0
  4. package/src/cli/prompt-select.test.mjs +1 -0
  5. package/src/core/docs.test.mjs +80 -40
  6. package/src/core/scaffold.test.mjs +100 -0
  7. package/src/core/validate.test.mjs +1 -0
  8. package/src/modules/accounts.mjs +416 -0
  9. package/src/modules/db-prisma.mjs +23 -55
  10. package/src/modules/dependencies.test.mjs +71 -29
  11. package/src/modules/executor.mjs +3 -2
  12. package/src/modules/executor.test.mjs +631 -500
  13. package/src/modules/files-access.mjs +36 -105
  14. package/src/modules/files-image.mjs +35 -107
  15. package/src/modules/files-local.mjs +15 -6
  16. package/src/modules/files-quotas.mjs +75 -93
  17. package/src/modules/files-s3.mjs +17 -6
  18. package/src/modules/files.mjs +56 -125
  19. package/src/modules/i18n.mjs +17 -121
  20. package/src/modules/idempotency.test.mjs +180 -0
  21. package/src/modules/logger.mjs +0 -9
  22. package/src/modules/probes.test.mjs +204 -0
  23. package/src/modules/queue.mjs +325 -440
  24. package/src/modules/rate-limit.mjs +36 -76
  25. package/src/modules/rbac.mjs +39 -78
  26. package/src/modules/registry.mjs +22 -35
  27. package/src/modules/scheduler.mjs +51 -171
  28. package/src/modules/shared/files-runtime-wiring.mjs +81 -0
  29. package/src/modules/shared/nest-runtime-wiring.mjs +110 -0
  30. package/src/modules/shared/patch-utils.mjs +29 -1
  31. package/src/modules/shared/probes.mjs +235 -0
  32. package/src/modules/sync-integrations.mjs +109 -396
  33. package/src/modules/sync-integrations.test.mjs +141 -0
  34. package/src/run-add-module.test.mjs +154 -0
  35. package/templates/base/README.md +7 -55
  36. package/templates/base/apps/web/src/App.tsx +70 -42
  37. package/templates/base/apps/web/src/probes.ts +61 -0
  38. package/templates/base/apps/web/src/styles.css +86 -25
  39. package/templates/base/package.json +21 -15
  40. package/templates/base/scripts/forgeon-sync-integrations.mjs +65 -281
  41. package/templates/module-fragments/{jwt-auth → accounts}/00_title.md +2 -1
  42. package/templates/module-fragments/{jwt-auth → accounts}/10_overview.md +5 -5
  43. package/templates/module-fragments/accounts/20_scope.md +29 -0
  44. package/templates/module-fragments/accounts/90_status_implemented.md +8 -0
  45. package/templates/module-fragments/accounts/90_status_planned.md +7 -0
  46. package/templates/module-fragments/rbac/30_what_it_adds.md +3 -2
  47. package/templates/module-fragments/rbac/40_how_it_works.md +2 -1
  48. package/templates/module-fragments/rbac/50_how_to_use.md +2 -1
  49. package/templates/module-fragments/swagger/20_scope.md +2 -1
  50. package/templates/module-presets/accounts/apps/api/prisma/migrations/0002_accounts_core/migration.sql +97 -0
  51. package/templates/module-presets/accounts/apps/api/src/accounts/forgeon-accounts-db-prisma.module.ts +17 -0
  52. package/templates/module-presets/accounts/apps/api/src/accounts/prisma-accounts-persistence.store.ts +332 -0
  53. package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/package.json +5 -5
  54. package/templates/module-presets/accounts/packages/accounts-api/src/accounts-email.port.ts +13 -0
  55. package/templates/module-presets/accounts/packages/accounts-api/src/accounts-persistence.port.ts +67 -0
  56. package/templates/module-presets/accounts/packages/accounts-api/src/accounts-rbac.port.ts +14 -0
  57. package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/auth-config.loader.ts +7 -7
  58. package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/auth-config.service.ts +7 -7
  59. package/templates/module-presets/accounts/packages/accounts-api/src/auth-core.service.ts +318 -0
  60. package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/auth-env.schema.ts +4 -4
  61. package/templates/module-presets/accounts/packages/accounts-api/src/auth-jwt.service.ts +58 -0
  62. package/templates/module-presets/accounts/packages/accounts-api/src/auth-password.service.ts +21 -0
  63. package/templates/module-presets/accounts/packages/accounts-api/src/auth.controller.ts +93 -0
  64. package/templates/module-presets/accounts/packages/accounts-api/src/auth.service.ts +48 -0
  65. package/templates/module-presets/accounts/packages/accounts-api/src/auth.types.ts +17 -0
  66. package/templates/module-presets/accounts/packages/accounts-api/src/dto/change-password.dto.ts +13 -0
  67. package/templates/module-presets/accounts/packages/accounts-api/src/dto/confirm-password-reset.dto.ts +12 -0
  68. package/templates/module-presets/accounts/packages/accounts-api/src/dto/index.ts +10 -0
  69. package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/dto/login.dto.ts +1 -1
  70. package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/dto/refresh.dto.ts +1 -1
  71. package/templates/module-presets/accounts/packages/accounts-api/src/dto/register.dto.ts +23 -0
  72. package/templates/module-presets/accounts/packages/accounts-api/src/dto/request-password-reset.dto.ts +7 -0
  73. package/templates/module-presets/accounts/packages/accounts-api/src/dto/update-user-profile.dto.ts +16 -0
  74. package/templates/module-presets/accounts/packages/accounts-api/src/dto/update-user-settings.dto.ts +16 -0
  75. package/templates/module-presets/accounts/packages/accounts-api/src/dto/update-user.dto.ts +8 -0
  76. package/templates/module-presets/accounts/packages/accounts-api/src/dto/verify-email.dto.ts +8 -0
  77. package/templates/module-presets/accounts/packages/accounts-api/src/forgeon-accounts.module.ts +82 -0
  78. package/templates/module-presets/accounts/packages/accounts-api/src/index.ts +24 -0
  79. package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/jwt.strategy.ts +3 -3
  80. package/templates/module-presets/accounts/packages/accounts-api/src/owner-access.guard.ts +39 -0
  81. package/templates/module-presets/accounts/packages/accounts-api/src/users-config.ts +13 -0
  82. package/templates/module-presets/accounts/packages/accounts-api/src/users.controller.ts +65 -0
  83. package/templates/module-presets/accounts/packages/accounts-api/src/users.service.ts +87 -0
  84. package/templates/module-presets/accounts/packages/accounts-api/src/users.types.ts +65 -0
  85. package/templates/module-presets/{jwt-auth/packages/auth-contracts → accounts/packages/accounts-contracts}/package.json +1 -1
  86. package/templates/module-presets/accounts/packages/accounts-contracts/src/index.ts +119 -0
  87. package/templates/module-presets/files/apps/api/src/files/forgeon-files-db-prisma.module.ts +17 -0
  88. package/templates/module-presets/files/apps/api/src/files/prisma-files-persistence.store.ts +164 -0
  89. package/templates/module-presets/files/packages/files/package.json +1 -2
  90. package/templates/module-presets/files/packages/files/src/files.ports.ts +107 -0
  91. package/templates/module-presets/files/packages/files/src/files.service.ts +81 -395
  92. package/templates/module-presets/files/packages/files/src/forgeon-files.module.ts +126 -2
  93. package/templates/module-presets/files/packages/files/src/index.ts +2 -1
  94. package/templates/module-presets/files-local/packages/files-local/src/forgeon-files-local-storage.module.ts +18 -0
  95. package/templates/module-presets/files-local/packages/files-local/src/index.ts +2 -0
  96. package/templates/module-presets/files-local/packages/files-local/src/local-files-storage.adapter.ts +53 -0
  97. package/templates/module-presets/files-quotas/packages/files-quotas/src/forgeon-files-quotas.module.ts +12 -4
  98. package/templates/module-presets/files-s3/packages/files-s3/src/forgeon-files-s3-storage.module.ts +18 -0
  99. package/templates/module-presets/files-s3/packages/files-s3/src/index.ts +2 -0
  100. package/templates/module-presets/files-s3/packages/files-s3/src/s3-files-storage.adapter.ts +130 -0
  101. package/templates/module-presets/i18n/apps/web/src/App.tsx +68 -41
  102. package/templates/module-presets/logger/packages/logger/src/index.ts +0 -1
  103. package/src/modules/jwt-auth.mjs +0 -390
  104. package/templates/base/docs/AI/ARCHITECTURE.md +0 -85
  105. package/templates/base/docs/AI/MODULE_CHECKS.md +0 -28
  106. package/templates/base/docs/AI/MODULE_SPEC.md +0 -77
  107. package/templates/base/docs/AI/PROJECT.md +0 -43
  108. package/templates/base/docs/AI/ROADMAP.md +0 -171
  109. package/templates/base/docs/AI/TASKS.md +0 -60
  110. package/templates/base/docs/AI/VALIDATION.md +0 -31
  111. package/templates/base/docs/README.md +0 -18
  112. package/templates/module-fragments/jwt-auth/20_scope.md +0 -19
  113. package/templates/module-fragments/jwt-auth/90_status_implemented.md +0 -8
  114. package/templates/module-fragments/jwt-auth/90_status_planned.md +0 -3
  115. package/templates/module-presets/jwt-auth/apps/api/prisma/migrations/0002_auth_refresh_token_hash/migration.sql +0 -3
  116. package/templates/module-presets/jwt-auth/apps/api/src/auth/prisma-auth-refresh-token.store.ts +0 -36
  117. package/templates/module-presets/jwt-auth/packages/auth-api/src/auth-refresh-token.store.ts +0 -23
  118. package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.controller.ts +0 -71
  119. package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.service.ts +0 -175
  120. package/templates/module-presets/jwt-auth/packages/auth-api/src/auth.types.ts +0 -6
  121. package/templates/module-presets/jwt-auth/packages/auth-api/src/dto/index.ts +0 -2
  122. package/templates/module-presets/jwt-auth/packages/auth-api/src/forgeon-auth.module.ts +0 -47
  123. package/templates/module-presets/jwt-auth/packages/auth-api/src/index.ts +0 -12
  124. package/templates/module-presets/jwt-auth/packages/auth-contracts/src/index.ts +0 -47
  125. package/templates/module-presets/logger/packages/logger/src/http-logging.interceptor.ts +0 -94
  126. /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
  127. /package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/src/auth-config.module.ts +0 -0
  128. /package/templates/module-presets/{jwt-auth/packages/auth-api → accounts/packages/accounts-api}/tsconfig.json +0 -0
  129. /package/templates/module-presets/{jwt-auth/packages/auth-contracts → accounts/packages/accounts-contracts}/tsconfig.json +0 -0
@@ -0,0 +1,16 @@
1
+ import type { UpdateUserSettingsRequest } from '@forgeon/accounts-contracts';
2
+ import { IsObject, IsOptional, IsString } from 'class-validator';
3
+
4
+ export class UpdateUserSettingsDto implements UpdateUserSettingsRequest {
5
+ @IsOptional()
6
+ @IsString()
7
+ theme?: string | null;
8
+
9
+ @IsOptional()
10
+ @IsString()
11
+ locale?: string | null;
12
+
13
+ @IsOptional()
14
+ @IsObject()
15
+ data?: Record<string, unknown>;
16
+ }
@@ -0,0 +1,8 @@
1
+ import type { UpdateUserRequest } from '@forgeon/accounts-contracts';
2
+ import { IsObject, IsOptional } from 'class-validator';
3
+
4
+ export class UpdateUserDto implements UpdateUserRequest {
5
+ @IsOptional()
6
+ @IsObject()
7
+ data?: Record<string, unknown>;
8
+ }
@@ -0,0 +1,8 @@
1
+ import type { VerifyEmailRequest } from '@forgeon/accounts-contracts';
2
+ import { IsString, MinLength } from 'class-validator';
3
+
4
+ export class VerifyEmailDto implements VerifyEmailRequest {
5
+ @IsString()
6
+ @MinLength(8)
7
+ token!: string;
8
+ }
@@ -0,0 +1,82 @@
1
+ import {
2
+ DynamicModule,
3
+ Module,
4
+ ModuleMetadata,
5
+ Provider,
6
+ } from '@nestjs/common';
7
+ import { JwtModule } from '@nestjs/jwt';
8
+ import { PassportModule } from '@nestjs/passport';
9
+ import {
10
+ ACCOUNTS_AUTHZ_CLAIMS_RESOLVER,
11
+ NoopAccountsAuthzClaimsResolver,
12
+ } from './accounts-rbac.port';
13
+ import { ACCOUNTS_EMAIL_PORT, StubAccountsEmailAdapter } from './accounts-email.port';
14
+ import { AuthConfigModule } from './auth-config.module';
15
+ import { AuthController } from './auth.controller';
16
+ import { AuthCoreService } from './auth-core.service';
17
+ import { AuthJwtService } from './auth-jwt.service';
18
+ import { AuthPasswordService } from './auth-password.service';
19
+ import { AuthService } from './auth.service';
20
+ import { JwtAuthGuard } from './access-token.guard';
21
+ import { JwtStrategy } from './jwt.strategy';
22
+ import { OwnerAccessGuard } from './owner-access.guard';
23
+ import { UsersController } from './users.controller';
24
+ import { UsersModule, USERS_MODULE_OPTIONS, type UsersModuleOptions } from './users-config';
25
+ import { UsersService } from './users.service';
26
+
27
+ export interface ForgeonAccountsModuleOptions {
28
+ imports?: ModuleMetadata['imports'];
29
+ providers?: Provider[];
30
+ users?: UsersModuleOptions;
31
+ }
32
+
33
+ @Module({})
34
+ export class ForgeonAccountsModule {
35
+ static register(options: ForgeonAccountsModuleOptions = {}): DynamicModule {
36
+ return {
37
+ module: ForgeonAccountsModule,
38
+ imports: [
39
+ AuthConfigModule,
40
+ PassportModule.register({ defaultStrategy: 'jwt' }),
41
+ JwtModule.register({}),
42
+ ...(options.imports ?? []),
43
+ ],
44
+ controllers: [AuthController, UsersController],
45
+ providers: [
46
+ {
47
+ provide: USERS_MODULE_OPTIONS,
48
+ useValue: UsersModule.register(options.users ?? {}),
49
+ },
50
+ {
51
+ provide: ACCOUNTS_EMAIL_PORT,
52
+ useClass: StubAccountsEmailAdapter,
53
+ },
54
+ {
55
+ provide: ACCOUNTS_AUTHZ_CLAIMS_RESOLVER,
56
+ useClass: NoopAccountsAuthzClaimsResolver,
57
+ },
58
+ AuthCoreService,
59
+ AuthJwtService,
60
+ AuthPasswordService,
61
+ AuthService,
62
+ UsersService,
63
+ JwtStrategy,
64
+ JwtAuthGuard,
65
+ OwnerAccessGuard,
66
+ ...(options.providers ?? []),
67
+ ],
68
+ exports: [
69
+ AuthConfigModule,
70
+ AuthCoreService,
71
+ AuthJwtService,
72
+ AuthPasswordService,
73
+ AuthService,
74
+ UsersService,
75
+ JwtAuthGuard,
76
+ OwnerAccessGuard,
77
+ ACCOUNTS_EMAIL_PORT,
78
+ ACCOUNTS_AUTHZ_CLAIMS_RESOLVER,
79
+ ],
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,24 @@
1
+ export * from './accounts-email.port';
2
+ export * from './accounts-persistence.port';
3
+ export * from './accounts-rbac.port';
4
+ export * from './auth-config.loader';
5
+ export * from './auth-config.module';
6
+ export * from './auth-config.service';
7
+ export * from './auth.controller';
8
+ export * from './auth-core.service';
9
+ export * from './auth-env.schema';
10
+ export * from './auth-jwt.service';
11
+ export * from './auth-password.service';
12
+ export * from './auth.service';
13
+ export * from './auth.types';
14
+ export * from './dto';
15
+ export * from './forgeon-accounts.module';
16
+ export * from './access-token.guard';
17
+ export * from './jwt.strategy';
18
+ export * from './owner-access.guard';
19
+ export * from './users-config';
20
+ export * from './users.controller';
21
+ export * from './users.service';
22
+ export * from './users.types';
23
+
24
+
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
2
2
  import { PassportStrategy } from '@nestjs/passport';
3
3
  import { ExtractJwt, Strategy } from 'passport-jwt';
4
4
  import { AuthConfigService } from './auth-config.service';
5
- import { AuthJwtPayload } from './auth.types';
5
+ import { AuthAccessTokenPayload } from './auth.types';
6
6
 
7
7
  @Injectable()
8
8
  export class JwtStrategy extends PassportStrategy(Strategy) {
@@ -14,7 +14,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
14
14
  });
15
15
  }
16
16
 
17
- validate(payload: AuthJwtPayload): AuthJwtPayload {
17
+ validate(payload: AuthAccessTokenPayload): AuthAccessTokenPayload {
18
18
  return payload;
19
19
  }
20
- }
20
+ }
@@ -0,0 +1,39 @@
1
+ import {
2
+ CanActivate,
3
+ ExecutionContext,
4
+ ForbiddenException,
5
+ Injectable,
6
+ } from '@nestjs/common';
7
+ import type { AuthAccessTokenPayload } from './auth.types';
8
+
9
+ type RequestWithUser = {
10
+ params?: Record<string, string>;
11
+ user?: AuthAccessTokenPayload;
12
+ };
13
+
14
+ @Injectable()
15
+ export class OwnerAccessGuard implements CanActivate {
16
+ canActivate(context: ExecutionContext): boolean {
17
+ const request = context.switchToHttp().getRequest<RequestWithUser>();
18
+ const user = request.user;
19
+ const id = request.params?.id;
20
+
21
+ if (!user?.sub || !id) {
22
+ throw new ForbiddenException('Owner scope could not be resolved');
23
+ }
24
+
25
+ if (id === 'me') {
26
+ request.params = {
27
+ ...(request.params ?? {}),
28
+ id: user.sub,
29
+ };
30
+ return true;
31
+ }
32
+
33
+ if (id !== user.sub) {
34
+ throw new ForbiddenException('Only the current account is accessible');
35
+ }
36
+
37
+ return true;
38
+ }
39
+ }
@@ -0,0 +1,13 @@
1
+ export interface UsersModuleOptions {
2
+ user?: Record<string, unknown>;
3
+ profile?: Record<string, unknown>;
4
+ settings?: Record<string, unknown>;
5
+ }
6
+
7
+ export const USERS_MODULE_OPTIONS = 'FORGEON_USERS_MODULE_OPTIONS';
8
+
9
+ export class UsersModule {
10
+ static register(options: UsersModuleOptions = {}): UsersModuleOptions {
11
+ return options;
12
+ }
13
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ Body,
3
+ Controller,
4
+ Delete,
5
+ Get,
6
+ Param,
7
+ Patch,
8
+ UseGuards,
9
+ } from '@nestjs/common';
10
+ import { JwtAuthGuard } from './access-token.guard';
11
+ import { OwnerAccessGuard } from './owner-access.guard';
12
+ import { UsersService } from './users.service';
13
+ import {
14
+ UpdateUserDto,
15
+ UpdateUserProfileDto,
16
+ UpdateUserSettingsDto,
17
+ } from './dto';
18
+
19
+ @Controller('users')
20
+ @UseGuards(JwtAuthGuard, OwnerAccessGuard)
21
+ export class UsersController {
22
+ constructor(private readonly usersService: UsersService) {}
23
+
24
+ @Get(':id')
25
+ getUser(@Param('id') userId: string) {
26
+ return this.usersService.getByIdOrThrow(userId);
27
+ }
28
+
29
+ @Patch(':id')
30
+ updateUser(@Param('id') userId: string, @Body() body: UpdateUserDto) {
31
+ return this.usersService.update(userId, body);
32
+ }
33
+
34
+ @Delete(':id')
35
+ async deleteUser(@Param('id') userId: string) {
36
+ await this.usersService.softDelete(userId);
37
+ return {
38
+ status: 'ok',
39
+ deleted: true,
40
+ };
41
+ }
42
+
43
+ @Get(':id/profile')
44
+ async getProfile(@Param('id') userId: string) {
45
+ const user = await this.usersService.getByIdOrThrow(userId);
46
+ return user.profile;
47
+ }
48
+
49
+ @Patch(':id/profile')
50
+ updateProfile(@Param('id') userId: string, @Body() body: UpdateUserProfileDto) {
51
+ return this.usersService.updateProfile(userId, body);
52
+ }
53
+
54
+ @Get(':id/settings')
55
+ async getSettings(@Param('id') userId: string) {
56
+ const user = await this.usersService.getByIdOrThrow(userId);
57
+ return user.settings;
58
+ }
59
+
60
+ @Patch(':id/settings')
61
+ updateSettings(@Param('id') userId: string, @Body() body: UpdateUserSettingsDto) {
62
+ return this.usersService.updateSettings(userId, body);
63
+ }
64
+ }
65
+
@@ -0,0 +1,87 @@
1
+ import { Inject, Injectable, NotFoundException } from '@nestjs/common';
2
+ import type { UpdateUserProfileRequest, UpdateUserSettingsRequest, UpdateUserRequest } from '@forgeon/accounts-contracts';
3
+ import { ACCOUNTS_PERSISTENCE_PORT, type AccountsPersistencePort } from './accounts-persistence.port';
4
+ import { USERS_MODULE_OPTIONS, type UsersModuleOptions } from './users-config';
5
+ import { mergeObjects, normalizeObject, toUserRecordDto } from './users.types';
6
+
7
+ @Injectable()
8
+ export class UsersService {
9
+ constructor(
10
+ @Inject(ACCOUNTS_PERSISTENCE_PORT)
11
+ private readonly persistence: AccountsPersistencePort,
12
+ @Inject(USERS_MODULE_OPTIONS)
13
+ private readonly usersModuleOptions: UsersModuleOptions,
14
+ ) {}
15
+
16
+ async findById(userId: string) {
17
+ const user = await this.persistence.findUserById(userId);
18
+ return user ? toUserRecordDto(user) : null;
19
+ }
20
+
21
+ async getByIdOrThrow(userId: string) {
22
+ const user = await this.findById(userId);
23
+ if (!user) {
24
+ throw new NotFoundException('User not found');
25
+ }
26
+ return user;
27
+ }
28
+
29
+ async update(userId: string, input: UpdateUserRequest) {
30
+ const current = await this.persistence.findUserById(userId);
31
+ if (!current) {
32
+ throw new NotFoundException('User not found');
33
+ }
34
+
35
+ const updated = await this.persistence.updateUser({
36
+ userId,
37
+ data: mergeObjects(current.data ?? this.usersModuleOptions.user, input.data),
38
+ });
39
+ return toUserRecordDto(updated);
40
+ }
41
+
42
+ async updateProfile(userId: string, input: UpdateUserProfileRequest) {
43
+ const current = await this.persistence.findUserById(userId);
44
+ if (!current) {
45
+ throw new NotFoundException('User not found');
46
+ }
47
+
48
+ const updated = await this.persistence.updateUserProfile({
49
+ userId,
50
+ name: input.name ?? current.profile?.name ?? null,
51
+ avatar: input.avatar ?? current.profile?.avatar ?? null,
52
+ data: mergeObjects(current.profile?.data ?? this.usersModuleOptions.profile, input.data),
53
+ });
54
+ return toUserRecordDto(updated).profile;
55
+ }
56
+
57
+ async updateSettings(userId: string, input: UpdateUserSettingsRequest) {
58
+ const current = await this.persistence.findUserById(userId);
59
+ if (!current) {
60
+ throw new NotFoundException('User not found');
61
+ }
62
+
63
+ const updated = await this.persistence.updateUserSettings({
64
+ userId,
65
+ theme: input.theme ?? current.settings?.theme ?? null,
66
+ locale: input.locale ?? current.settings?.locale ?? null,
67
+ data: mergeObjects(current.settings?.data ?? this.usersModuleOptions.settings, input.data),
68
+ });
69
+ return toUserRecordDto(updated).settings;
70
+ }
71
+
72
+ async softDelete(userId: string): Promise<void> {
73
+ await this.persistence.softDeleteUser(userId, new Date());
74
+ }
75
+
76
+ resolveUserData(input: unknown) {
77
+ return mergeObjects(this.usersModuleOptions.user, normalizeObject(input));
78
+ }
79
+
80
+ resolveProfileData(input: unknown) {
81
+ return mergeObjects(this.usersModuleOptions.profile, normalizeObject(input));
82
+ }
83
+
84
+ resolveSettingsData(input: unknown) {
85
+ return mergeObjects(this.usersModuleOptions.settings, normalizeObject(input));
86
+ }
87
+ }
@@ -0,0 +1,65 @@
1
+ import type { JsonObject, UserProfileDto, UserRecordDto, UserSettingsDto } from '@forgeon/accounts-contracts';
2
+
3
+ export type UserRecord = {
4
+ id: string;
5
+ email: string | null;
6
+ status: string;
7
+ data: JsonObject | null;
8
+ createdAt: Date;
9
+ updatedAt: Date;
10
+ deletedAt: Date | null;
11
+ profile: {
12
+ name: string | null;
13
+ avatar: string | null;
14
+ data: JsonObject | null;
15
+ } | null;
16
+ settings: {
17
+ theme: string | null;
18
+ locale: string | null;
19
+ data: JsonObject | null;
20
+ } | null;
21
+ };
22
+
23
+ export function normalizeObject(value: unknown): JsonObject | null {
24
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
25
+ return null;
26
+ }
27
+ return value as JsonObject;
28
+ }
29
+
30
+ export function mergeObjects(baseValue: unknown, patchValue: unknown): JsonObject | null {
31
+ const base = normalizeObject(baseValue) ?? {};
32
+ const patch = normalizeObject(patchValue) ?? {};
33
+ const merged = { ...base, ...patch };
34
+ return Object.keys(merged).length > 0 ? merged : null;
35
+ }
36
+
37
+ export function toProfileDto(record: UserRecord['profile']): UserProfileDto {
38
+ return {
39
+ name: record?.name ?? null,
40
+ avatar: record?.avatar ?? null,
41
+ data: record?.data ?? null,
42
+ };
43
+ }
44
+
45
+ export function toSettingsDto(record: UserRecord['settings']): UserSettingsDto {
46
+ return {
47
+ theme: record?.theme ?? null,
48
+ locale: record?.locale ?? null,
49
+ data: record?.data ?? null,
50
+ };
51
+ }
52
+
53
+ export function toUserRecordDto(record: UserRecord): UserRecordDto {
54
+ return {
55
+ id: record.id,
56
+ email: record.email,
57
+ status: record.status,
58
+ data: record.data,
59
+ createdAt: record.createdAt.toISOString(),
60
+ updatedAt: record.updatedAt.toISOString(),
61
+ deletedAt: record.deletedAt?.toISOString() ?? null,
62
+ profile: toProfileDto(record.profile),
63
+ settings: toSettingsDto(record.settings),
64
+ };
65
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "@forgeon/auth-contracts",
2
+ "name": "@forgeon/accounts-contracts",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "type": "module",
@@ -0,0 +1,119 @@
1
+ export const AUTH_API_ROUTES = {
2
+ register: '/api/auth/register',
3
+ login: '/api/auth/login',
4
+ refresh: '/api/auth/refresh',
5
+ logout: '/api/auth/logout',
6
+ me: '/api/auth/me',
7
+ changePassword: '/api/auth/change-password',
8
+ verifyEmail: '/api/auth/verify-email',
9
+ passwordResetRequest: '/api/auth/password-reset/request',
10
+ passwordResetConfirm: '/api/auth/password-reset/confirm',
11
+ } as const;
12
+
13
+ export const USERS_API_ROUTES = {
14
+ item: '/api/users/:id',
15
+ profile: '/api/users/:id/profile',
16
+ settings: '/api/users/:id/settings',
17
+ } as const;
18
+
19
+ export const AUTH_ERROR_CODES = {
20
+ invalidCredentials: 'AUTH_INVALID_CREDENTIALS',
21
+ refreshInvalid: 'AUTH_REFRESH_INVALID',
22
+ tokenExpired: 'AUTH_TOKEN_EXPIRED',
23
+ emailTaken: 'AUTH_EMAIL_TAKEN',
24
+ accountDisabled: 'AUTH_ACCOUNT_DISABLED',
25
+ } as const;
26
+
27
+ export type AuthErrorCode = (typeof AUTH_ERROR_CODES)[keyof typeof AUTH_ERROR_CODES];
28
+
29
+ export type IdentityProvider = 'email' | 'google' | 'apple' | 'facebook';
30
+ export type JsonObject = Record<string, unknown>;
31
+
32
+ export interface UserProfileDto {
33
+ name?: string | null;
34
+ avatar?: string | null;
35
+ data?: JsonObject | null;
36
+ }
37
+
38
+ export interface UserSettingsDto {
39
+ theme?: string | null;
40
+ locale?: string | null;
41
+ data?: JsonObject | null;
42
+ }
43
+
44
+ export interface UserRecordDto {
45
+ id: string;
46
+ email?: string | null;
47
+ status: string;
48
+ data?: JsonObject | null;
49
+ createdAt: string;
50
+ updatedAt: string;
51
+ deletedAt?: string | null;
52
+ profile: UserProfileDto;
53
+ settings: UserSettingsDto;
54
+ }
55
+
56
+ export interface AuthAccessClaims {
57
+ sub: string;
58
+ email?: string;
59
+ type: 'access';
60
+ }
61
+
62
+ export interface AuthRefreshClaims {
63
+ sub: string;
64
+ email?: string;
65
+ jti: string;
66
+ type: 'refresh';
67
+ }
68
+
69
+ export interface LoginRequest {
70
+ email: string;
71
+ password: string;
72
+ }
73
+
74
+ export interface RegisterRequest extends LoginRequest {
75
+ user?: JsonObject;
76
+ profile?: UserProfileDto;
77
+ settings?: UserSettingsDto;
78
+ }
79
+
80
+ export interface RefreshRequest {
81
+ refreshToken: string;
82
+ }
83
+
84
+ export interface ChangePasswordRequest {
85
+ currentPassword?: string;
86
+ newPassword: string;
87
+ }
88
+
89
+ export interface RequestPasswordResetRequest {
90
+ email: string;
91
+ }
92
+
93
+ export interface ConfirmPasswordResetRequest {
94
+ token: string;
95
+ newPassword: string;
96
+ }
97
+
98
+ export interface VerifyEmailRequest {
99
+ token: string;
100
+ }
101
+
102
+ export interface UpdateUserRequest {
103
+ data?: JsonObject;
104
+ }
105
+
106
+ export interface UpdateUserProfileRequest extends UserProfileDto {}
107
+ export interface UpdateUserSettingsRequest extends UserSettingsDto {}
108
+
109
+ export interface TokenPair {
110
+ accessToken: string;
111
+ refreshToken: string;
112
+ tokenType: 'Bearer';
113
+ accessTtl: string;
114
+ refreshTtl: string;
115
+ }
116
+
117
+ export interface AuthSessionResponse extends TokenPair {
118
+ user: UserRecordDto;
119
+ }
@@ -0,0 +1,17 @@
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 {}