@nestledjs/api 0.0.1

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 (132) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +11 -0
  3. package/eslint.config.cjs +28 -0
  4. package/generators.json +69 -0
  5. package/package.json +21 -0
  6. package/project.json +47 -0
  7. package/src/account/files/data-access/src/index.ts__tmpl__ +5 -0
  8. package/src/account/files/data-access/src/lib/api-account-data-access.module.ts__tmpl__ +10 -0
  9. package/src/account/files/data-access/src/lib/api-account-data-access.service.ts__tmpl__ +152 -0
  10. package/src/account/files/data-access/src/lib/dto/account-create-email.input.ts__tmpl__ +9 -0
  11. package/src/account/files/data-access/src/lib/dto/account-update-password.input.ts__tmpl__ +16 -0
  12. package/src/account/files/data-access/src/lib/dto/account-update-profile.input.ts__tmpl__ +25 -0
  13. package/src/account/files/feature/src/index.ts__tmpl__ +1 -0
  14. package/src/account/files/feature/src/lib/api-account-feature.module.ts__tmpl__ +9 -0
  15. package/src/account/files/feature/src/lib/api-account-feature.resolver.ts__tmpl__ +83 -0
  16. package/src/account/generator.spec.ts +71 -0
  17. package/src/account/generator.ts +20 -0
  18. package/src/account/schema.d.ts +3 -0
  19. package/src/account/schema.json +13 -0
  20. package/src/app/files/src/app.config.ts__tmpl__ +66 -0
  21. package/src/app/files/src/app.module.ts__tmpl__ +43 -0
  22. package/src/app/files/src/applogger.middleware.ts__tmpl__ +21 -0
  23. package/src/app/files/src/main.ts__tmpl__ +33 -0
  24. package/src/app/files/webpack.config.js__tmpl__ +54 -0
  25. package/src/app/generator.spec.ts +112 -0
  26. package/src/app/generator.ts +105 -0
  27. package/src/app/schema.d.ts +1 -0
  28. package/src/app/schema.json +9 -0
  29. package/src/config/files/src/index.ts__tmpl__ +3 -0
  30. package/src/config/files/src/lib/config.service.ts__tmpl__ +51 -0
  31. package/src/config/files/src/lib/configuration.ts__tmpl__ +32 -0
  32. package/src/config/files/src/lib/validation.ts__tmpl__ +21 -0
  33. package/src/config/generator.spec.ts +47 -0
  34. package/src/config/generator.ts +16 -0
  35. package/src/config/schema.d.ts +3 -0
  36. package/src/config/schema.json +13 -0
  37. package/src/core/files/data-access/src/index.ts__tmpl__ +5 -0
  38. package/src/core/files/data-access/src/lib/api-core-data-access.module.ts__tmpl__ +9 -0
  39. package/src/core/files/data-access/src/lib/api-core-data-access.service.ts__tmpl__ +97 -0
  40. package/src/core/files/data-access/src/lib/api-core-pub-sub.ts__tmpl__ +37 -0
  41. package/src/core/files/data-access/src/lib/dto/core-paging.input.ts__tmpl__ +26 -0
  42. package/src/core/files/data-access/src/lib/dto/multi-select-input.ts__tmpl__ +7 -0
  43. package/src/core/files/data-access/src/lib/models/core-paging.ts__tmpl__ +19 -0
  44. package/src/core/files/feature/src/index.ts__tmpl__ +2 -0
  45. package/src/core/files/feature/src/lib/api-core-feature.controller.ts__tmpl__ +12 -0
  46. package/src/core/files/feature/src/lib/api-core-feature.module.ts__tmpl__ +86 -0
  47. package/src/core/files/feature/src/lib/api-core-feature.resolver.ts__tmpl__ +12 -0
  48. package/src/core/files/feature/src/lib/api-core-feature.service.ts__tmpl__ +55 -0
  49. package/src/core/files/feature/src/lib/config/configuration.ts__tmpl__ +32 -0
  50. package/src/core/files/feature/src/lib/config/validation.ts__tmpl__ +25 -0
  51. package/src/core/files/feature/src/lib/plugins/complexity.plugin.ts__tmpl__ +51 -0
  52. package/src/core/files/feature/src/lib/plugins/logging.plugin.ts__tmpl__ +17 -0
  53. package/src/core/files/models/src/index.ts__tmpl__ +1 -0
  54. package/src/core/files/models/src/lib/generate-models.ts__tmpl__ +294 -0
  55. package/src/core/files/models/src/lib/models/core-paging.model.ts__tmpl__ +25 -0
  56. package/src/core/generator.spec.ts +85 -0
  57. package/src/core/generator.ts +35 -0
  58. package/src/core/schema.d.ts +3 -0
  59. package/src/core/schema.json +13 -0
  60. package/src/custom/generator.spec.ts +75 -0
  61. package/src/custom/generator.ts +239 -0
  62. package/src/custom/schema.json +21 -0
  63. package/src/custom/schema.ts +5 -0
  64. package/src/extended/generator.spec.ts +95 -0
  65. package/src/extended/generator.ts +161 -0
  66. package/src/extended/index.ts +1 -0
  67. package/src/extended/schema.json +12 -0
  68. package/src/extended/schema.ts +3 -0
  69. package/src/generate-crud/files/data-access/src/index.ts__tmpl__ +3 -0
  70. package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.module.ts__tmpl__ +11 -0
  71. package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.service.ts__tmpl__ +72 -0
  72. package/src/generate-crud/files/data-access/src/lib/dto/index.ts__tmpl__ +224 -0
  73. package/src/generate-crud/files/feature/.gitkeep +0 -0
  74. package/src/generate-crud/generator.spec.ts +84 -0
  75. package/src/generate-crud/generator.ts +354 -0
  76. package/src/generate-crud/schema.json +32 -0
  77. package/src/generate-crud/schema.ts +8 -0
  78. package/src/index.ts +13 -0
  79. package/src/plugin/generator.spec.ts +18 -0
  80. package/src/plugin/generator.ts +74 -0
  81. package/src/plugin/schema.json +14 -0
  82. package/src/plugin/schema.ts +4 -0
  83. package/src/prisma/files/src/index.ts__tmpl__ +1 -0
  84. package/src/prisma/files/src/lib/.gitkeep +1 -0
  85. package/src/prisma/files/src/lib/schemas/schema.prisma__tmpl__ +402 -0
  86. package/src/prisma/files/src/lib/seed/seed-data/iso-3166-countries.ts__tmpl__ +3239 -0
  87. package/src/prisma/files/src/lib/seed/seed-data/seed-users.ts__tmpl__ +32 -0
  88. package/src/prisma/files/src/lib/seed/seed.ts__tmpl__ +64 -0
  89. package/src/prisma/generator.spec.ts +60 -0
  90. package/src/prisma/generator.ts +61 -0
  91. package/src/prisma/schema.d.ts +3 -0
  92. package/src/prisma/schema.json +13 -0
  93. package/src/setup/generator.md +49 -0
  94. package/src/setup/generator.spec.ts +18 -0
  95. package/src/setup/generator.ts +106 -0
  96. package/src/setup/schema.json +8 -0
  97. package/src/smtp-mailer/files/data-access/src/index.ts__tmpl__ +2 -0
  98. package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.module.ts__tmpl__ +10 -0
  99. package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.service.ts__tmpl__ +61 -0
  100. package/src/smtp-mailer/generator.spec.ts +41 -0
  101. package/src/smtp-mailer/generator.ts +14 -0
  102. package/src/smtp-mailer/schema.d.ts +0 -0
  103. package/src/smtp-mailer/schema.json +7 -0
  104. package/src/user/files/data-access/src/index.ts__tmpl__ +5 -0
  105. package/src/user/files/data-access/src/lib/api-user-data-access.module.ts__tmpl__ +10 -0
  106. package/src/user/files/data-access/src/lib/api-user-data-access.service.ts__tmpl__ +119 -0
  107. package/src/user/files/data-access/src/lib/dto/admin-create-user.input.ts__tmpl__ +20 -0
  108. package/src/user/files/data-access/src/lib/dto/admin-update-user.input.ts__tmpl__ +29 -0
  109. package/src/user/files/feature/src/index.ts__tmpl__ +1 -0
  110. package/src/user/files/feature/src/lib/api-user-feature-admin.resolver.ts__tmpl__ +57 -0
  111. package/src/user/files/feature/src/lib/api-user-feature.module.ts__tmpl__ +10 -0
  112. package/src/user/files/feature/src/lib/api-user-feature.resolver.ts__tmpl__ +17 -0
  113. package/src/user/generator.spec.ts +41 -0
  114. package/src/user/generator.ts +15 -0
  115. package/src/user/schema.d.ts +0 -0
  116. package/src/user/schema.json +7 -0
  117. package/src/utils/files/src/index.ts__tmpl__ +3 -0
  118. package/src/utils/files/src/lib/decorators/ctx-user.decorator.ts__tmpl__ +6 -0
  119. package/src/utils/files/src/lib/guards/gql-auth-admin.guard.ts__tmpl__ +39 -0
  120. package/src/utils/files/src/lib/guards/gql-auth.guard.ts__tmpl__ +11 -0
  121. package/src/utils/generator.ts +14 -0
  122. package/src/utils/schema.json +8 -0
  123. package/src/workspace-setup/generator.md +39 -0
  124. package/src/workspace-setup/generator.spec.ts +82 -0
  125. package/src/workspace-setup/generator.ts +49 -0
  126. package/src/workspace-setup/lib/helpers.ts +142 -0
  127. package/src/workspace-setup/schema.d.ts +3 -0
  128. package/src/workspace-setup/schema.json +7 -0
  129. package/tsconfig.json +16 -0
  130. package/tsconfig.lib.json +23 -0
  131. package/tsconfig.spec.json +22 -0
  132. package/vite.config.mts +37 -0
@@ -0,0 +1,119 @@
1
+ import { Injectable } from '@nestjs/common'
2
+ import { ApiCoreDataAccessService, CorePaging, CorePagingInput } from '@<%= npmScope %>/api/core/data-access'
3
+ import { getGravatarUrl, hashPassword, uniqueSuffix } from '@<%= npmScope %>/api/auth/data-access'
4
+ import { UserRole } from '@<%= npmScope %>/api/core/data-access';
5
+ import { AdminCreateUserInput } from './dto/admin-create-user.input'
6
+ import { AdminUpdateUserInput } from './dto/admin-update-user.input'
7
+
8
+
9
+ @Injectable()
10
+ export class ApiUserDataAccessService {
11
+ constructor(private readonly data: ApiCoreDataAccessService) {}
12
+
13
+ async adminUsers(userId: string, paging?: CorePagingInput) {
14
+ await this.data.ensureAdminUser(userId);
15
+ return this.data.user.findMany({
16
+ take: paging?.take ?? 20,
17
+ skip: paging?.skip ?? 0,
18
+ include: { emails: true },
19
+ });
20
+ }
21
+
22
+ async adminCountUsers(
23
+ adminId: string,
24
+ paging?: CorePagingInput
25
+ ): Promise<CorePaging> {
26
+ await this.data.ensureAdminUser(adminId);
27
+ const total = await this.data.user.count();
28
+ return {
29
+ take: paging?.take ?? 20,
30
+ skip: paging?.skip ?? 0,
31
+ total,
32
+ };
33
+ }
34
+
35
+ async adminUser(adminId: string, userId: string) {
36
+ await this.data.ensureAdminUser(adminId);
37
+ return this.data.user.findUnique({
38
+ where: { id: userId },
39
+ include: { emails: true },
40
+ });
41
+ }
42
+
43
+ async adminCreateUser(adminId: string, input: AdminCreateUserInput) {
44
+ await this.data.ensureAdminUser(adminId);
45
+ const email = input?.email?.trim();
46
+ if (!email) {
47
+ throw new Error('Email is required to create a user.');
48
+ }
49
+ const displayName =
50
+ input.displayName ||
51
+ `${input.firstName} ${input.lastName}`.trim() ||
52
+ email;
53
+
54
+ return this.data.user.create({
55
+ data: {
56
+ emails: { create: { email, primary: true } },
57
+ role: input.role ?? UserRole.USER,
58
+ firstName: input.firstName,
59
+ lastName: input.lastName,
60
+ displayName,
61
+ },
62
+ include: { emails: true },
63
+ });
64
+ }
65
+
66
+ async adminUpdateUser(
67
+ adminId: string,
68
+ userId: string,
69
+ input: AdminUpdateUserInput
70
+ ) {
71
+ await this.data.ensureAdminUser(adminId);
72
+ return this.data.user.update({
73
+ where: { id: userId },
74
+ data: { ...input },
75
+ include: { emails: true },
76
+ });
77
+ }
78
+
79
+ async adminSetUserPassword(
80
+ adminId: string,
81
+ userId: string,
82
+ password: string
83
+ ) {
84
+ await this.data.ensureAdminUser(adminId);
85
+ return this.data.user.update({
86
+ where: { id: userId },
87
+ data: { password: hashPassword(password) },
88
+ });
89
+ }
90
+
91
+ async adminDeleteUser(adminId: string, userId: string) {
92
+ await this.data.ensureAdminUser(adminId);
93
+ return this.data.user.delete({ where: { id: userId } });
94
+ }
95
+
96
+ private formatdisplayName(email: string, displayName?: string): string {
97
+ return displayName?.trim() || uniqueSuffix(email.trim().split('@')[0]);
98
+ }
99
+
100
+ private async ensuredisplayNameAvailable(
101
+ displayName: string
102
+ ): Promise<boolean> {
103
+ const user = await this.data.findUserByDisplayName(displayName);
104
+ if (user) {
105
+ throw new Error(`This displayName is not available`);
106
+ }
107
+ return true;
108
+ }
109
+
110
+ private async ensureDisplayNameAvailable(
111
+ displayName: string
112
+ ): Promise<boolean> {
113
+ const user = await this.data.findUserByDisplayName(displayName);
114
+ if (user) {
115
+ throw new Error(`This display name is not available`);
116
+ }
117
+ return true;
118
+ }
119
+ }
@@ -0,0 +1,20 @@
1
+ import { Field, InputType } from '@nestjs/graphql'
2
+ import { UserRole } from '@<%= npmScope %>/api/core/data-access'
3
+
4
+ @InputType()
5
+ export class AdminCreateUserInput {
6
+ @Field(() => UserRole, { nullable: true })
7
+ role?: UserRole
8
+
9
+ @Field({ nullable: true })
10
+ email?: string
11
+
12
+ @Field({ nullable: true })
13
+ displayName?: string
14
+
15
+ @Field({ nullable: true })
16
+ firstName?: string
17
+
18
+ @Field({ nullable: true })
19
+ lastName?: string
20
+ }
@@ -0,0 +1,29 @@
1
+ import { Field, InputType } from '@nestjs/graphql'
2
+ import { UserRole } from '@<%= npmScope %>/api/core/data-access'
3
+
4
+ @InputType()
5
+ export class AdminUpdateUserInput {
6
+ @Field(() => UserRole, { nullable: true })
7
+ role?: UserRole
8
+
9
+ @Field({ nullable: true })
10
+ displayName?: string
11
+
12
+ @Field({ nullable: true })
13
+ firstName?: string
14
+
15
+ @Field({ nullable: true })
16
+ lastName?: string
17
+
18
+ @Field({ nullable: true })
19
+ phone?: string
20
+
21
+ @Field({ nullable: true })
22
+ avatarUrl?: string
23
+
24
+ @Field({ nullable: true })
25
+ location?: string
26
+
27
+ @Field({ nullable: true })
28
+ bio?: string
29
+ }
@@ -0,0 +1 @@
1
+ export * from './lib/api-user-feature.module'
@@ -0,0 +1,57 @@
1
+ import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'
2
+ import { UseGuards } from '@nestjs/common'
3
+ import { CorePaging, CorePagingInput } from '@<%= npmScope %>/api/core/data-access'
4
+ import { User } from '@<%= npmScope %>/api/core/models'
5
+ import { CtxUser, GqlAuthAdminGuard } from '@<%= npmScope %>/api/custom'
6
+ import {
7
+ AdminCreateUserInput,
8
+ AdminUpdateUserInput,
9
+ ApiUserDataAccessService,
10
+ } from '@<%= npmScope %>/api/user/data-access'
11
+
12
+ @Resolver()
13
+ @UseGuards(GqlAuthAdminGuard)
14
+ export class ApiUserFeatureAdminResolver {
15
+ constructor(private readonly service: ApiUserDataAccessService) {}
16
+
17
+ @Query(() => [User], { nullable: true })
18
+ adminUsers(
19
+ @CtxUser() admin: User,
20
+ @Args({ name: 'paging', type: () => CorePagingInput, nullable: true }) paging?: CorePagingInput,
21
+ ) {
22
+ return this.service.adminUsers(admin.id, paging)
23
+ }
24
+
25
+ @Query(() => CorePaging, { nullable: true })
26
+ adminCountUsers(
27
+ @CtxUser() admin: User,
28
+ @Args({ name: 'paging', type: () => CorePagingInput, nullable: true }) paging?: CorePagingInput,
29
+ ) {
30
+ return this.service.adminCountUsers(admin.id, paging)
31
+ }
32
+
33
+ @Query(() => User, { nullable: true })
34
+ adminUser(@CtxUser() admin: User, @Args('userId') userId: string) {
35
+ return this.service.adminUser(admin.id, userId)
36
+ }
37
+
38
+ @Mutation(() => User, { nullable: true })
39
+ adminCreateUser(@CtxUser() admin: User, @Args('input') input: AdminCreateUserInput) {
40
+ return this.service.adminCreateUser(admin.id, input)
41
+ }
42
+
43
+ @Mutation(() => User, { nullable: true })
44
+ adminUpdateUser(@CtxUser() admin: User, @Args('userId') userId: string, @Args('input') input: AdminUpdateUserInput) {
45
+ return this.service.adminUpdateUser(admin.id, userId, input)
46
+ }
47
+
48
+ @Mutation(() => User, { nullable: true })
49
+ adminSetUserPassword(@CtxUser() admin: User, @Args('userId') userId: string, @Args('password') password: string) {
50
+ return this.service.adminSetUserPassword(admin.id, userId, password)
51
+ }
52
+
53
+ @Mutation(() => User, { nullable: true })
54
+ adminDeleteUser(@CtxUser() admin: User, @Args('userId') userId: string) {
55
+ return this.service.adminDeleteUser(admin.id, userId)
56
+ }
57
+ }
@@ -0,0 +1,10 @@
1
+ import { Module } from '@nestjs/common'
2
+ import { ApiUserDataAccessModule } from '@<%= npmScope %>/api/user/data-access'
3
+ import { ApiUserFeatureAdminResolver } from './api-user-feature-admin.resolver'
4
+ import { ApiUserFeatureResolver } from './api-user-feature.resolver'
5
+
6
+ @Module({
7
+ imports: [ApiUserDataAccessModule],
8
+ providers: [ApiUserFeatureResolver, ApiUserFeatureAdminResolver],
9
+ })
10
+ export class ApiUserFeatureModule {}
@@ -0,0 +1,17 @@
1
+ import { Parent, ResolveField, Resolver } from '@nestjs/graphql'
2
+ import { User } from '@<%= npmScope %>/api/core/models'
3
+
4
+ @Resolver(() => User)
5
+ export class ApiUserFeatureResolver {
6
+ @ResolveField(() => String, { nullable: true })
7
+ name(@Parent() user: User) {
8
+ const name = [user?.firstName, user?.lastName].join(' ').trim()
9
+ return name.length ? name : user?.displayName
10
+ }
11
+
12
+ @ResolveField(() => String, { nullable: true })
13
+ email(@Parent() user: User) {
14
+ const hasPrimary = user.emails?.find((email) => email.primary)
15
+ return hasPrimary?.email ?? user?.emails?.find((e) => e)?.email
16
+ }
17
+ }
@@ -0,0 +1,41 @@
1
+ import { formatFiles, installPackagesTask, Tree } from '@nx/devkit'
2
+ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
3
+ import { describe, expect, it, vi } from 'vitest'
4
+ import { apiLibraryGenerator } from '@nestledjs/utils'
5
+
6
+ import generator from './generator'
7
+
8
+ vi.mock('@nestledjs/utils', async () => {
9
+ const actual = await vi.importActual('@nestledjs/utils')
10
+ return {
11
+ ...actual,
12
+ apiLibraryGenerator: vi.fn(),
13
+ }
14
+ })
15
+
16
+ vi.mock('@nx/devkit', async () => {
17
+ const actual = await vi.importActual('@nx/devkit')
18
+ return {
19
+ ...actual,
20
+ formatFiles: vi.fn(),
21
+ installPackagesTask: vi.fn(),
22
+ }
23
+ })
24
+
25
+ describe('user generator', () => {
26
+ let tree: Tree
27
+
28
+ beforeEach(() => {
29
+ tree = createTreeWithEmptyWorkspace()
30
+ })
31
+
32
+ it('should run successfully', async () => {
33
+ const callback = await generator(tree)
34
+ callback()
35
+
36
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(tree, { name: 'user' }, expect.any(String), 'data-access')
37
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(tree, { name: 'user' }, expect.any(String), 'feature', true)
38
+ expect(formatFiles).toHaveBeenCalledWith(tree)
39
+ expect(installPackagesTask).toHaveBeenCalledWith(tree)
40
+ })
41
+ })
@@ -0,0 +1,15 @@
1
+ import { formatFiles, GeneratorCallback, installPackagesTask, joinPathFragments, Tree } from '@nx/devkit'
2
+ import { apiLibraryGenerator } from '@nestledjs/utils'
3
+
4
+ export default async function generateLibraries(tree: Tree): Promise<GeneratorCallback> {
5
+ const templateRootPath = joinPathFragments(__dirname, './files')
6
+
7
+ await apiLibraryGenerator(tree, { name: 'user' }, templateRootPath, 'data-access')
8
+ await apiLibraryGenerator(tree, { name: 'user' }, templateRootPath, 'feature', true)
9
+
10
+ await formatFiles(tree)
11
+
12
+ return () => {
13
+ installPackagesTask(tree)
14
+ }
15
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "$id": "ApiUser",
4
+ "title": "Create an API User Library",
5
+ "type": "object",
6
+ "properties": {}
7
+ }
@@ -0,0 +1,3 @@
1
+ export * from './lib/guards/gql-auth.guard'
2
+ export * from './lib/guards/gql-auth-admin.guard'
3
+ export * from './lib/decorators/ctx-user.decorator'
@@ -0,0 +1,6 @@
1
+ import { createParamDecorator } from '@nestjs/common'
2
+ import { GqlExecutionContext } from '@nestjs/graphql'
3
+
4
+ export const CtxUser = createParamDecorator(
5
+ (data, ctx) => GqlExecutionContext.create(ctx).getContext().req.user,
6
+ )
@@ -0,0 +1,39 @@
1
+ import { ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common'
2
+ import { GqlExecutionContext } from '@nestjs/graphql'
3
+ import { AuthGuard } from '@nestjs/passport'
4
+ import { User } from '@<%= npmScope %>/api/core/models'
5
+
6
+ @Injectable()
7
+ export class GqlAuthAdminGuard extends AuthGuard('jwt') {
8
+ private readonly _roles: string[] = ['Admin']
9
+
10
+ override getRequest(context: ExecutionContext) {
11
+ const ctx = GqlExecutionContext.create(context)
12
+
13
+ return ctx.getContext().req
14
+ }
15
+
16
+ constructor() {
17
+ super()
18
+ }
19
+
20
+ override async canActivate(context: ExecutionContext): Promise<boolean> {
21
+ await super.canActivate(context)
22
+ const ctx = GqlExecutionContext.create(context)
23
+ const req = ctx.getContext().req
24
+
25
+ if (!req || !req.user) {
26
+ return false
27
+ }
28
+ const hasAccess = this.hasAccess(req.user)
29
+
30
+ if (!hasAccess) {
31
+ throw new ForbiddenException(`You need to have Admin access`)
32
+ }
33
+ return req && req.user && this.hasAccess(req.user)
34
+ }
35
+
36
+ private hasAccess(user: User): boolean {
37
+ return !!(user.role && this._roles.includes(user.role))
38
+ }
39
+ }
@@ -0,0 +1,11 @@
1
+ import { ExecutionContext, Injectable } from '@nestjs/common'
2
+ import { GqlExecutionContext } from '@nestjs/graphql'
3
+ import { AuthGuard } from '@nestjs/passport'
4
+
5
+ @Injectable()
6
+ export class GqlAuthGuard extends AuthGuard('jwt') {
7
+ override getRequest(context: ExecutionContext): Promise<Record<string, unknown>> {
8
+ const ctx = GqlExecutionContext.create(context)
9
+ return ctx.getContext().req
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ import { formatFiles, joinPathFragments, Tree } from '@nx/devkit'
2
+ import { apiLibraryGenerator, deleteFiles } from '@nestledjs/utils'
3
+
4
+ export default async function generator(tree: Tree) {
5
+ const templateRootPath = joinPathFragments(__dirname, './files')
6
+ const overwrite = false
7
+
8
+ await apiLibraryGenerator(tree, { name: 'utils', overwrite }, templateRootPath, '', false)
9
+
10
+ // Remove the default module file if it exists
11
+ deleteFiles(tree, ['libs/api/utils/src/lib/api-utils.module.ts'])
12
+
13
+ await formatFiles(tree)
14
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "cli": "nx",
4
+ "id": "api-utils",
5
+ "type": "object",
6
+ "properties": {},
7
+ "required": []
8
+ }
@@ -0,0 +1,39 @@
1
+ # Overview
2
+ This code is a setup script that ensures the necessary environment and dependencies are in place for a project. It performs the following key tasks:
3
+
4
+ - Loads environment variables from a `.env` file
5
+ - Checks if the `DATABASE_URL` environment variable is set and if it points to a local database
6
+ - Ensures that Docker is running and that the necessary Docker Compose services are up and running
7
+ - Runs Prisma setup commands to generate the Prisma client and schema
8
+ - Generates GraphQL types from the Prisma schema
9
+ - Runs Prisma seed scripts to populate the database with initial data
10
+
11
+ The script is designed to be run during the project setup process to ensure a consistent and reliable development environment.
12
+
13
+ # Functions/Classes
14
+ The code imports several helper functions from the `./lib/helpers` module:
15
+
16
+ - `canConnect(DATABASE_URL)`: Checks if the application can connect to the database at the specified `DATABASE_URL`.
17
+ - `ensureDockerIsRunning()`: Ensures that the Docker daemon is running.
18
+ - `ensureDockerComposeIsRunning()`: Ensures that the required Docker Compose services are running.
19
+ - `ensureDotEnv()`: Ensures that a `.env` file exists in the project directory.
20
+ - `log(message)`: Logs a message to the console.
21
+ - `runPrismaSetup()`: Runs the Prisma setup commands to generate the Prisma client and schema.
22
+ - `runGraphQLTypeGeneration()`: Generates GraphQL types from the Prisma schema.
23
+ - `runPrismaSeed()`: Runs the Prisma seed scripts to populate the database with initial data.
24
+
25
+ The main function exported by the script is an asynchronous function that orchestrates the setup process.
26
+
27
+ # Behavior Flow
28
+ 1. The script loads the environment variables from the `.env` file.
29
+ 2. It checks if the `DATABASE_URL` environment variable is set and if it points to a local database. If not, it throws an error.
30
+ 3. The script then checks if the application can connect to the database at the specified `DATABASE_URL`. If not, it ensures that Docker is running and that the necessary Docker Compose services are up and running.
31
+ 4. The script then runs the Prisma setup commands to generate the Prisma client and schema.
32
+ 5. After a 2-second delay, the script generates the GraphQL types from the Prisma schema.
33
+ 6. Finally, the script runs the Prisma seed scripts to populate the database with initial data.
34
+
35
+ # Key Points
36
+ - The script relies on several environment variables, particularly `DATABASE_URL`, to configure the development environment.
37
+ - It ensures that the necessary Docker and Docker Compose services are running before proceeding with the setup.
38
+ - The script uses Prisma to manage the database schema and seed data, and to generate GraphQL types.
39
+ - The script includes error handling to provide informative error messages if any of the setup steps fail.
@@ -0,0 +1,82 @@
1
+ import { vi, describe, it, expect, beforeEach } from 'vitest'
2
+
3
+ const {
4
+ canConnect,
5
+ ensureDockerComposeIsRunning,
6
+ ensureDockerIsRunning,
7
+ ensureDotEnv,
8
+ log,
9
+ runPrismaSeed,
10
+ runPrismaSetup,
11
+ runGraphQLTypeGeneration,
12
+ } = vi.hoisted(() => {
13
+ return {
14
+ canConnect: vi.fn(),
15
+ ensureDockerComposeIsRunning: vi.fn(),
16
+ ensureDockerIsRunning: vi.fn(),
17
+ ensureDotEnv: vi.fn(),
18
+ log: vi.fn(),
19
+ runPrismaSeed: vi.fn(),
20
+ runPrismaSetup: vi.fn(),
21
+ runGraphQLTypeGeneration: vi.fn(),
22
+ }
23
+ })
24
+
25
+ vi.mock('./lib/helpers', () => ({
26
+ canConnect,
27
+ ensureDockerComposeIsRunning,
28
+ ensureDockerIsRunning,
29
+ ensureDotEnv,
30
+ log,
31
+ runPrismaSeed,
32
+ runPrismaSetup,
33
+ runGraphQLTypeGeneration,
34
+ }))
35
+
36
+ import generator from './generator'
37
+
38
+ describe('workspace-setup generator', () => {
39
+ beforeEach(() => {
40
+ vi.clearAllMocks()
41
+ delete process.env.DATABASE_URL
42
+ })
43
+
44
+ it('should throw an error if DATABASE_URL is not provided', async () => {
45
+ await expect(generator()).rejects.toThrow('Please provide DATABASE_URL env var')
46
+ })
47
+
48
+ it('should throw an error if DATABASE_URL is not on localhost', async () => {
49
+ process.env.DATABASE_URL = 'some-remote-db'
50
+ await expect(generator()).rejects.toThrow("Can't connect to DATABASE_URL if it's not on localhost")
51
+ })
52
+
53
+ it('should run setup without docker if already connected', async () => {
54
+ process.env.DATABASE_URL = 'localhost:5432'
55
+ canConnect.mockResolvedValue(true)
56
+
57
+ await generator()
58
+
59
+ expect(ensureDotEnv).toHaveBeenCalled()
60
+ expect(canConnect).toHaveBeenCalledWith('localhost:5432')
61
+ expect(ensureDockerIsRunning).not.toHaveBeenCalled()
62
+ expect(ensureDockerComposeIsRunning).not.toHaveBeenCalled()
63
+ expect(runPrismaSetup).toHaveBeenCalled()
64
+ expect(runGraphQLTypeGeneration).toHaveBeenCalled()
65
+ expect(runPrismaSeed).toHaveBeenCalled()
66
+ })
67
+
68
+ it('should start docker and run setup if not connected', async () => {
69
+ process.env.DATABASE_URL = 'localhost:5432'
70
+ canConnect.mockResolvedValue(false)
71
+
72
+ await generator()
73
+
74
+ expect(ensureDotEnv).toHaveBeenCalled()
75
+ expect(canConnect).toHaveBeenCalledWith('localhost:5432')
76
+ expect(ensureDockerIsRunning).toHaveBeenCalled()
77
+ expect(ensureDockerComposeIsRunning).toHaveBeenCalled()
78
+ expect(runPrismaSetup).toHaveBeenCalled()
79
+ expect(runGraphQLTypeGeneration).toHaveBeenCalled()
80
+ expect(runPrismaSeed).toHaveBeenCalled()
81
+ })
82
+ })
@@ -0,0 +1,49 @@
1
+ require('dotenv').config()
2
+ import {
3
+ canConnect,
4
+ ensureDockerComposeIsRunning,
5
+ ensureDockerIsRunning,
6
+ ensureDotEnv,
7
+ log,
8
+ runPrismaSeed,
9
+ runPrismaSetup,
10
+ runGraphQLTypeGeneration,
11
+ } from './lib/helpers'
12
+
13
+ export default async function () {
14
+ log('Setting up workspace ')
15
+
16
+ ensureDotEnv()
17
+ require('dotenv').config()
18
+
19
+ const DATABASE_URL = process.env.DATABASE_URL
20
+
21
+ if (!DATABASE_URL) {
22
+ throw new Error(`Please provide DATABASE_URL env var`)
23
+ }
24
+
25
+ if (!DATABASE_URL.includes('localhost')) {
26
+ throw new Error(`Can't connect to DATABASE_URL if it's not on localhost`)
27
+ }
28
+
29
+ const connected = await canConnect(DATABASE_URL)
30
+
31
+ if (!connected) {
32
+ ensureDockerIsRunning()
33
+ await ensureDockerComposeIsRunning()
34
+ }
35
+
36
+ try {
37
+ runPrismaSetup()
38
+ await new Promise(resolve => setTimeout(resolve, 2000))
39
+
40
+ // Generate GraphQL types from Prisma schema
41
+ log('Generating GraphQL types from Prisma schema...')
42
+ runGraphQLTypeGeneration()
43
+
44
+ runPrismaSeed()
45
+ log('Workspace setup done!')
46
+ } catch (error) {
47
+ console.error('Error during workspace setup:', error.message)
48
+ }
49
+ }